【发布时间】:2020-11-16 05:35:16
【问题描述】:
意图
我想使用 TypeScript 的 Compiler API 在 TypeScript 代码中试验 运算符重载。具体来说,我想找到x + y 的所有实例并将它们转换为op_add(x, y)。但是,我希望语言服务(例如 VS Code 中的 IntelliSense)能够了解转换并显示正确的类型。
例如在这段代码中:
interface Vector2 { x: number, y: number }
declare function op_add(x: Vector2, y: Vector2): Vector2
declare let a: Vector2, b: Vector2
let c = a + b
我希望当我将鼠标悬停在c 上时,它会显示Vector2。
计划
为了实现这一点,我必须:
- 创建一个程序,该程序公开与
typescript相同的 API - 与ttypescript所做的方式相同。 - 使该程序在将源代码传递给
typescript之前对其进行修改 - 让 VS Code(或任何编辑器)使用我的包而不是
typescript
处决
我首先创建了一个名为compile.ts 的短脚本,它使用编译器API 将一个名为sample.ts 的文件解析为AST。然后直接修改AST,将Binary(x, PlusToken, y)改为Call(op_add, x, y)。最后它将修改后的代码打印到控制台,然后尝试发出。仅此一项对于 IDE 集成来说是不够的,但它是一个好的开始。
compile.ts:
import * as ts from "typescript"
import { possibleChildProperties } from "./visit";
let program = ts.createProgram(['sample.ts'], { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS })
let inputFiles = program.getSourceFiles()
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
let outputCode: string
for (let input of inputFiles) {
if (input.fileName === 'sample.ts') {
ts.visitNode(input, visitor) // modifies input's AST
outputCode = printer.printNode(ts.EmitHint.Unspecified, input, input)
break
}
}
console.log(outputCode) // works
let emitResult = program.emit() // fails
function visitor(node: ts.Node): ts.Node {
if (node.kind === ts.SyntaxKind.BinaryExpression) {
let expr = node as ts.BinaryExpression
if (expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
return ts.createCall(ts.createIdentifier('op_add'), [], [expr.left, expr.right])
}
}
return visitChildren(node, visitor)
}
function visitChildren(node: ts.Node, visitor: ts.Visitor) {
for (const prop of possibleChildProperties) {
if (node[prop] !== undefined) {
if (Array.isArray(node[prop]))
node[prop] = node[prop].map(visitor)
else
node[prop] = visitor(node[prop])
}
}
return node
}
sample.ts:
let a = { a: 4 }
let b = { b: 3 }
let c = a + b
console.log 输出:
let a = { a: 4 };
let b = { b: 3 };
let c = op_add(a, b);
问题
虽然代码打印机工作正常并输出正确代码,但调用 program.emit() 会导致未指定的内部错误。这可能意味着我正在以不受支持的方式修改 AST。
/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:100920
throw e;
^
Error: start < 0
at createTextSpan (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:10559:19)
at Object.createTextSpanFromBounds (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:10568:16)
at getErrorSpanForNode (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:13914:19)
at createDiagnosticForNodeInSourceFile (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:13808:20)
at Object.createDiagnosticForNode (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:13799:16)
at error (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:35703:22)
at resolveNameHelper (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:36602:29)
at resolveName (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:36274:20)
at getResolvedSymbol (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:52602:21)
at checkIdentifier (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:54434:26)
问题
在运行类型检查器之前修改程序的 AST 的正确方法是什么? 我知道 AST 最好是只读的,但标准的 ts.visitEachChild 只能使用 类型检查之后。而且我自己对节点进行深度克隆似乎也不是一个可行的选择,因为没有任何方法可以从代码生成的 AST 创建Program。
更新
编辑 1:正如 @jdaz 所注意到的,我的 sample.ts 缺少 op_add 的声明,这可能会导致问题。我在文件顶部添加了这一行:
declare function op_add(x: {}, y: {}): string
现在有一个不同的错误——文件诊断的生成失败:
/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:100920
throw e;
^
Error: Debug Failure. Expected -2 >= 0
at Object.createFileDiagnostic (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:17868:18)
at grammarErrorAtPos (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:69444:36)
at checkGrammarForAtLeastOneTypeArgument (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:68771:24)
at checkGrammarTypeArguments (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:68777:17)
at checkCallExpression (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:59255:18)
at checkExpressionWorker (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:61687:28)
at checkExpression (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:61597:38)
at checkExpressionCached (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:61275:38)
at checkVariableLikeDeclaration (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:63983:69)
at checkVariableDeclaration (/home/m93a/Dokumenty/tsc-experiments/node_modules/typescript/lib/typescript.js:64051:20)
【问题讨论】:
-
错误可能只是因为您没有在任何一个文件中定义
op_add吗?所以在typescript.js中,当它调用node.getStart时,它找不到任何东西并返回-1。 -
@jdaz 很棒的观察!但遗憾的是,一个错误消失了,另一个错误又出现了。我编辑了问题。
-
嗯,你需要实际定义整个函数而不是只声明它吗?
-
也试过了,和只做delcaring完全一样。
标签: typescript typescript-compiler-api