【问题标题】:How can I use the TS Compiler API to find where a variable was defined in another file如何使用 TS Compiler API 查找变量在另一个文件中的定义位置
【发布时间】:2020-05-07 07:57:54
【问题描述】:

给定:

// foo.ts
import { bar } from "./bar"

// bar.ts
export const bar = 3;

如果我在“foo.ts”中有barts.Symbol,我如何才能访问“bar.ts”中的bar

理想情况下,TS 编译器 API 会公开一个定义使用链,我可以遍历该链来查找定义。不过,我认为不会。

所以现在我正在尝试:

  • 使用模块说明符“./bar.ts”和当前的ts.SourceFile 获取代表“bar.ts”的ts.ResolvedModule 对象,其中包含完整的文件路径。
  • ts.SourceFile(fullFilePath) 为“bar.ts”获取ts.SourceFile
  • 执行checker.getExportsOfModule(symbolForBarDotTs) 以从“bar.ts”获取导出并找到具有匹配名称的导出。

棘手的部分似乎是从模块说明符中解析模块。我不想从头开始编写模块解析的逻辑,因为算法很复杂,并且依赖于至少六个编译器选项的交互。 TS Compiler API 的两个部分看起来很有希望:

  • host.resolveModuleNames,不幸的是只有宿主实现了它才可用,而默认编译器宿主没有实现它。
  • 使用(program.getSourceFile(pathToFoo) as any).resolvedModulesresolvedModules 属性似乎完全符合我的要求,但不是公共 API 的一部分。

有没有更好的方法?我希望:

  • 停止使用非私有 API
  • 不要做太多编译器已经知道该怎么做的工作

    “bar.ts”

在这种情况下,很容易看到“./bar”指的是文件系统上具有匹配名称的相邻源文件,但是当有人使用“paths”或“node_modules”或“@types”时,等等然后模块解析是不平凡的。

更新

一般问题:

如果我在“foo.ts”中有barts.Symbol,我如何才能访问“bar.ts”中的bar

@DavidSherret's answer 大部分时间都可以工作。

但是,在以下情况下,它并不能满足我的要求:

// foo.ts
import { bar } from "./bar"

// bar.ts
export { bar } from "./baz"

// baz.ts
export const bar = 3;

TypeChecker#getAliasedSymbol 表示“foo.ts”中的baz 指向“baz.ts”中的bar,完全跳过“bar.ts”。这对我的目的不起作用,因为我试图找出一组入口点,不再需要 .d.ts 文件的哪些部分,并删除不需要的部分。在这种情况下,删除“bar.ts”是个坏主意。

【问题讨论】:

  • 你问这么简单的问题:P。对于编译器 API 的东西,GH 不是更好的场所吗?
  • 谢谢!我不确定我是否会将其称为功能请求,除非有人确认我想要实现的目标没有公共 API,然后我可以打开一个问题。
  • 如果我没记错的话,你可以在 GitHub 上用问题打开一个问题。 @TitianCernicova-Dragomir 是贡献者之一...
  • @HereticMonkey Max Heiber 也是贡献者 :)
  • 打开了一个问题:感谢您的反馈和建议github.com/microsoft/TypeScript/issues/36351

标签: typescript typescript-compiler-api


【解决方案1】:

命名导入的符号将有一个关联的“别名符号”,它代表声明。因此,要获取变量声明的符号,您可以使用TypeChecker#getAliasedSymbol 方法,然后从中获取声明。

例如:

const barNamedImportSymbol = typeChecker.getSymbolAtLocation(barNamedImport.name)!;
const barSymbol = typeChecker.getAliasedSymbol(barNamedImportSymbol);
const barDeclaration = barSymbol.declarations[0] as ts.VariableDeclaration;

console.log(barDeclaration.getText(barFile)); // outputs `bar = 3`

导入声明的命名导入有一个单独的符号,因为这是特定于“foo.ts”文件的符号。

更新:获取模块说明符中引用的文件符号

要获取导入或导出声明模块说明符中引用的文件的符号,您可以获取模块说明符节点的符号:

const otherFileSymbol = typeChecker.getSymbolAtLocation(importDeclaration.moduleSpecifier)!;

从那里,您可以检查其导出的某个名称:

const barSymbol = otherFileSymbol.exports!.get(ts.escapeLeadingUnderscores("bar"))!;
// outputs: export { bar } from "./baz"; in second example above
console.log(barSymbol.declarations[0].parent.parent.getText());

【讨论】:

  • 谢谢!不过,这并不能 100% 解决我的问题。我问如何在import { bar } from "./bar" 中到达“栏” 这个答案中的方法并不总是有效,因为 getAliasedSymbol 在文件之间跳过。也就是说,如果foo.ts 正在执行export { bar } from "./baz",那么别名符号将存在于 baz.ts 中,而不是 bar.ts 中。这是一个问题,因为我正在消除死代码,并且在这种情况下需要将 bar.ts 标记为活动。要么,要么重写模块说明符,但这也需要了解模块解析。
  • @MaxHeiber 你能用一个例子来更新你的问题吗?我只是在回答:“如果我在“foo.ts”中有一个 ts.Symbol 用于酒吧,我怎样才能到达“bar.ts”中的酒吧?”有很多不同的场景需要处理......例如see here
  • 非常抱歉我的延误!我已经用我认为您正在寻找的内容更新了我的答案。
  • 感谢@davidSherret。我选择了您的答案作为正确答案,但使用 ts.resolveModuleName 是因为这是对代码的较小更改 - 两种解决方案似乎都有效。
  • @MaxHeiber 我认为在程序宿主实现自己的模块解析的情况下,使用ts.resolveModuleName 将不起作用。见ProgramHost#resolveModuleNames。如果ProgramHost#resolveModuleNames 未定义,我相信ts.resolveModuleNames 是程序使用的默认模块分辨率。
【解决方案2】:

要解析模块名称,请使用ts.resolveModuleNameas recommended on this TS issue

这是签名,来自typescript.d.ts

resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[];

或者,要直接转到符号的原始定义,请参阅@DavidSherret's answer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 2017-11-26
    • 1970-01-01
    • 2021-12-05
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    相关资源
    最近更新 更多