Typescript 如何使用TS编译器API查找变量在另一个文件中的定义位置

Typescript 如何使用TS编译器API查找变量在另一个文件中的定义位置,typescript,typescript-compiler-api,Typescript,Typescript Compiler Api,鉴于: 如果我在“foo.ts”中有一个ts.Symbol用于bar,我如何才能到达“bar.ts”中的bar 理想情况下,TS编译器API将公开一个定义使用链,我可以遍历它来找到定义。不过,我认为没有 所以现在我想: 使用模块说明符“/bar.ts”和当前ts.SourceFile获取表示“bar.ts”的ts.ResolvedModule对象,该对象包含完整的文件路径 执行ts.SourceFile(fullFilePath)以获取“bar.ts”的ts.SourceFile 执行chec

鉴于:

如果我在“foo.ts”中有一个
ts.Symbol
用于
bar
,我如何才能到达“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编译器API的两个部分似乎很有前途:

  • host.resolveModuleNames
    ,不幸的是,只有当主机实现了它,而默认编译器主机没有实现它时,它才可用
  • 使用
    (program.getSourceFile(pathtoo)作为任何).resolvedModules
    resolvedModules
    属性似乎正是我想要的,但它不是公共API的一部分
有更好的办法吗?我希望:

  • 停止使用非私有API
  • 停止做太多编译器已经知道如何做的工作

    “酒吧”

在这种情况下,很容易看到“/bar”是指文件系统上具有匹配名称的相邻源文件,但当有人使用“路径”或“节点模块”或“@types”等时,则模块解析非常重要

更新 关于一般性问题:

如果我在“foo.ts”中有一个
ts.Symbol
用于
bar
,我如何才能到达“bar.ts”中的
bar

大部分时间都能用

但是,在以下情况下,它不符合我的要求:

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

// bar.ts
export const bar = 3;

TypeChecker#getAliasedSymbol说,“foo.ts”中的
baz
指向“baz.ts”中的
bar
,完全跳过了“bar.ts”。这对我来说是行不通的,因为我试图找出,给定一组入口点,.d.ts文件的哪些部分不再需要,并删除不需要的部分。在这种情况下,删除“bar.ts”不是一个好主意。

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

例如:

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

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

// baz.ts
export const bar = 3;

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

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

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

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`
从那里,您可以检查其导出的特定名称:

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

要解析模块名称,请使用
ts.resolveModuleName

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

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());

或者,要直接转到符号的原始定义,请参见

您提出的简单问题:p.对于编译器API,GH不是更好的地方吗?谢谢!我不确定我是否会将此称为功能请求,除非有人确认我试图实现的功能没有公共API,否则我可能会提出一个问题。如果我没有弄错的话,你可以在GitHub上提出一个问题@TitianCernicova Dragomir是撰稿人之一…@Hereticsmonkey Max Heiber也是撰稿人:)发表了一个问题:感谢您的反馈和建议谢谢!但这并不能100%解决我的问题。我问如何从“/bar”导入{bar}中的“bar”,这个答案中的方法并不总是有效,因为getAliasedSymbol在文件之间跳过。也就是说,如果
foo.ts
正在从“/baz”导出{bar},那么别名符号将位于baz.ts中,而不是位于bar.ts中。这是一个问题,因为我正在进行死代码消除,在本例中需要将bar.ts标记为live。要么这样,要么重写模块说明符,但这也需要了解模块解析。@MaxHeiber您能用一个示例来更新您的问题,说明您的意思吗?我只是回答:“如果我在“foo.ts”中有一个酒吧的ts.Symbol,我怎么能在“bar.ts”中到达酒吧?”“有很多不同的场景需要处理。。。例如,非常抱歉我的耽搁!我已经更新了我的答案,我认为你在寻找什么。谢谢@davidSherret。我选择了正确的答案,但还是选择了使用ts.resolveModuleName,因为这是对代码的一个较小的更改-尽管这两种解决方案似乎都可以工作。@MaxHeiber我认为使用
ts.resolveModuleName
在程序主机实现自己的模块解析的情况下不起作用。看见我相信
ts.resolveModuleNames
是程序在
ProgramHost\resolveModuleNames
未定义时使用的默认模块解析。
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[];