Jon*_*Sud 2 abstract-syntax-tree typescript
我使用transformtypescript 编译器 api 中的函数来更改我的代码。
该函数是递归的并访问每个节点。
当我找到一个StringLiteral时,foo我想添加foo到导入位置,my-lib如下所示:
import { foo } from 'my-lib';
Run Code Online (Sandbox Code Playgroud)
该代码可能已经导入了其他内容,my-lib例如:
import { bar } from 'my-lib';
Run Code Online (Sandbox Code Playgroud)
我想避免这种结果(重复导入):
import { foo } from 'my-lib';
import { bar } from 'my-lib';
Run Code Online (Sandbox Code Playgroud)
我能找到的最接近的解决方案是:
const file = (node as ts.Node) as ts.SourceFile;
const update = ts.updateSourceFileNode(file, [
ts.createImportDeclaration(
undefined,
undefined,
ts.createImportClause(
undefined,
ts.createNamedImports([ts.createImportSpecifier(ts.createIdentifier("default"), ts.createIdentifier("salami"))])
),
ts.createLiteral('salami')
),
...file.statements
]);
Run Code Online (Sandbox Code Playgroud)
但这些功能已被弃用。我无法返回,ts.createImportDeclaration因为我将得到 的而不是的import代码。StringLiteralfoo
是否有一个函数说“将 y 从 x 更新或插入到导入语句”?
到目前为止我能够执行的代码:
import * as ts from "typescript";
const code = `
console.log('foo');
`;
const node = ts.createSourceFile("x.ts", code, ts.ScriptTarget.Latest);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
export const upsertImport = (context) => (rootNode) => {
const { factory } = context;
function visit(node) {
if (ts.isStringLiteral(node) && node.text === "foo") {
// need to add: import { foo } from 'my-lib'; but how??
console.log("need to add: import { foo } from my-lib; but how??");
}
return ts.visitEachChild(node, visit, context);
}
return ts.visitNode(rootNode, visit);
};
const result = ts.transform(node, [upsertImport]);
const transformedSourceFile = result.transformed[0];
const out = printer.printFile(transformedSourceFile);
console.log({ out });
Run Code Online (Sandbox Code Playgroud)
您可以使用访问者遍历 AST 并存储已使用的成员,而无需编辑任何节点。之后(仍在转换中),您可以在已访问的成员上ImportDeclaration查找/更新插入:sourceFile
/**
* Helper function for updating an import declaration statement with a new set of members
* unhandled edge cases include:
* existing `import "my-lib";`
* existing `import * as myLib from "my-lib";
*/
const updateNamedImports = (factory: ts.NodeFactory, node: ts.ImportDeclaration, utilizedMembers: Set<string>): ts.ImportDeclaration => {
// We're not using updateNamedImports since we've verified
// which imports are actually being used already
const namedImports = factory.createNamedImports(
Array.from(utilizedMembers)
.map(name =>
factory.createImportSpecifier(
undefined,
factory.createIdentifier(name)
)
)
)
let importClause: ts.ImportClause;
if (node.importClause && node.importClause.namedBindings) {
importClause = factory.updateImportClause(
node.importClause,
node.importClause.isTypeOnly,
node.importClause.name,
namedImports
);
}
else {
importClause = factory.createImportClause(
false,
undefined,
namedImports
)
}
return factory.updateImportDeclaration(
node,
node.decorators,
node.modifiers,
importClause,
node.moduleSpecifier
);
}
/**
* Main transform function
*/
const upsertImport: ts.TransformerFactory<ts.SourceFile> = (context) => (rootNode) => {
const { factory } = context;
const MY_LIB = "my-lib";
// use a set to keep track of members which have been accessed,
// and guarantee uniqueness
const utilizedMembers = new Set<string>();
// I ventured a guess you want to check more than just "foo"
const availableMembers = new Set([ "foo", "bar", "baz" ]);
// Find which imports you need
function collectUtilizedMembers(node: ts.Node): ts.Node {
if (ts.isStringLiteral(node) && availableMembers.has(node.text)) {
utilizedMembers.add(node.text);
}
return ts.visitEachChild(node, collectUtilizedMembers, context);
}
// run your visitor which will fill up the `utilizedMembers` set.
ts.visitNode(rootNode, collectUtilizedMembers);
// find the existing import if it exists using findIndex
// so we can splice it back into the existing statements
const matchedImportIdx = rootNode.statements
.findIndex(s => ts.isImportDeclaration(s)
&& (s.moduleSpecifier as ts.StringLiteral).text === MY_LIB
);
// if it exists, update it
if (matchedImportIdx !== -1) {
const node = rootNode.statements[matchedImportIdx] as ts.ImportDeclaration;
// update source file with updated import statement
return factory.updateSourceFile(rootNode, [
...rootNode.statements.slice(0, matchedImportIdx),
updateNamedImports(factory, node, utilizedMembers),
...rootNode.statements.slice(matchedImportIdx + 1)
]);
}
else {
// if it doesn't exist, create it and insert it at
// the top of the source file
return factory.updateSourceFile(rootNode, [
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports(
Array.from(utilizedMembers).map(name =>
factory.createImportSpecifier(
undefined,
factory.createIdentifier(name)
)
)
)
),
factory.createStringLiteral(MY_LIB)
),
...rootNode.statements
]);
}
};
Run Code Online (Sandbox Code Playgroud)
因为此代码检查实际使用了哪些成员,所以它替换了现有的namedImports. 如果您想保留现有的语句(即使它们尚未使用或未包含在 中availableMembers,您可以将集合utilizedMembers与 中的现有importDeclaration.importClause.namedImports数组合并。这也不能处理现有语句/updateNamedImports的情况,但这应该很容易添加,具体取决于您想要如何处理它。import * as myLib from "my-lib";import "my-lib";
| 归档时间: |
|
| 查看次数: |
2638 次 |
| 最近记录: |