TeaWeb/tools/dtsgen/import_organizer.ts

273 lines
9.4 KiB
TypeScript
Raw Normal View History

import * as ts from "typescript";
import {SyntaxKind} from "typescript";
interface RequiredType {
identifier: string;
}
class ImportsParserData {
readonly source_file: ts.SourceFile;
required_type: RequiredType[];
depth: number;
constructor(sf: ts.SourceFile) {
this.source_file = sf;
this.required_type = [];
this.depth = 0;
}
has_type(name: string) {
return this.required_type.findIndex(e => e.identifier === name) !== -1;
}
}
export function remove_unused(source_file: ts.SourceFile, nodes: ts.Node[]) : ts.Node[] {
const data = new ImportsParserData(source_file);
for(const node of nodes)
gather_required_types(node, data);
//console.log(data.required_type);
const result2d = nodes.map(e => ts.transform(e, [ctx => node => eliminate_imports(node, ctx, data)])).map(e => e.transformed);
const result = [];
for(const entry of result2d)
result.push(...entry);
return result;
}
function eliminate_imports(node: ts.Node, ctx: ts.TransformationContext, data: ImportsParserData) : ts.Node | undefined {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
const import_decl = node as ts.ImportDeclaration;
const clause = import_decl.importClause;
if(!clause?.namedBindings) return node;
let new_binding;
if(clause.namedBindings.kind === SyntaxKind.NamedImports) {
const bindings = clause.namedBindings as ts.NamedImports;
const elements = bindings.elements.filter(e => data.has_type(e.name.text));
if(!elements.length) return ts.createIdentifier("");
new_binding = ts.createNamedImports(elements);
} else if(clause.namedBindings.kind === SyntaxKind.NamespaceImport) {
const binding = clause.namedBindings as ts.NamespaceImport;
if(!data.has_type(binding.name.text))
return ts.createIdentifier("");
new_binding = binding;
} else
throw "unknown named binding";
return ts.createImportDeclaration(import_decl.decorators, import_decl.modifiers, new_binding, import_decl.moduleSpecifier);
default:
return ts.visitEachChild(node, e => eliminate_imports(e, ctx, data), ctx);
}
}
const import_parsers: {[key: number]:(node: ts.Node, data: ImportsParserData) => void} = {};
function gather_required_types(node: ts.Node, data: ImportsParserData) {
if(!node) return;
//console.log("%d %s", data.depth, SyntaxKind[node.kind]);
if(import_parsers[node.kind]) {
import_parsers[node.kind](node, data);
return;
}
data.depth++;
node.forEachChild(e => gather_required_types(e, data));
data.depth--;
}
import_parsers[SyntaxKind.Parameter] = (node: ts.ParameterDeclaration, data) => {
if(!node.type) return;
analyze_type_node(node.type, data);
};
import_parsers[SyntaxKind.TypeAliasDeclaration] = (node: ts.TypeAliasDeclaration, data) => {
(node.typeParameters || []).forEach(e => gather_required_types(e, data));
if(node.type) analyze_type_node(node.type, data);
if(node.decorators) node.decorators.forEach(e => analyze_type_node(e.expression, data));
};
import_parsers[SyntaxKind.HeritageClause] = (node: ts.HeritageClause, data) => {
const heritage = node as ts.HeritageClause;
for(const type of heritage.types)
analyze_type_node(type, data);
};
import_parsers[SyntaxKind.TypeParameter] = (node: ts.TypeParameterDeclaration, data) => {
if(node.constraint) analyze_type_node(node.constraint, data);
if(node.default) analyze_type_node(node.default, data);
};
import_parsers[SyntaxKind.FunctionDeclaration] = (node: ts.FunctionDeclaration, data) => {
if(node.type)
analyze_type_node(node.type, data);
(node.typeParameters || []).forEach(e => gather_required_types(e, data));
for(const param of node.parameters)
gather_required_types(param, data);
};
import_parsers[SyntaxKind.MethodSignature] = (node: ts.MethodSignature, data) => {
if(node.type)
analyze_type_node(node.type, data);
(node.typeParameters || []).forEach(e => gather_required_types(e, data));
for(const param of node.parameters)
gather_required_types(param, data);
};
import_parsers[SyntaxKind.ClassDeclaration] = (node: ts.ClassDeclaration, data) => {
for(const e of node.heritageClauses || [])
gather_required_types(e, data);
for(const e of node.typeParameters || [])
gather_required_types(e, data);
for(const e of node.members || [])
gather_required_types(e, data);
};
import_parsers[SyntaxKind.PropertySignature] = (node: ts.PropertySignature, data) => {
analyze_type_node(node.type, data);
};
import_parsers[SyntaxKind.PropertyDeclaration] = (node: ts.PropertyDeclaration, data) => {
analyze_type_node(node.type, data);
};
import_parsers[SyntaxKind.MethodDeclaration] = (node: ts.MethodDeclaration, data) => {
for(const e of node.parameters || [])
gather_required_types(e, data);
for(const e of node.typeParameters || [])
gather_required_types(e, data);
analyze_type_node(node.type, data);
};
function analyze_type_node(node: ts.TypeNode | ts.LeftHandSideExpression, data: ImportsParserData) {
if(!node) return;
//console.log("T: %s", SyntaxKind[node.kind]);
switch (node.kind) {
case SyntaxKind.AnyKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.ThisType:
case SyntaxKind.ThisKeyword:
case SyntaxKind.BooleanKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.StringLiteral:
case SyntaxKind.LiteralType:
case SyntaxKind.NumberKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.UndefinedKeyword:
/* no special export type */
break;
case SyntaxKind.UnionType:
const union = node as ts.UnionTypeNode;
union.types.forEach(e => analyze_type_node(e, data));
break;
case SyntaxKind.IntersectionType:
const intersection = node as ts.IntersectionTypeNode;
intersection.types.forEach(e => analyze_type_node(e, data));
break;
case SyntaxKind.TypeReference:
const ref = node as ts.TypeReferenceNode;
if(ref.typeName.kind === SyntaxKind.Identifier) {
data.required_type.push({
identifier: ref.typeName.text
});
} else if(ref.typeName.kind === SyntaxKind.QualifiedName) {
let left: ts.Identifier | ts.QualifiedName = ref.typeName.left;
while(left.kind !== SyntaxKind.Identifier)
left = left.left;
data.required_type.push({
identifier: left.text
});
} else
throw "invalid type name";
for(const e of ref.typeArguments || [])
analyze_type_node(e, data);
break;
case SyntaxKind.Identifier:
data.required_type.push({
identifier: (node as ts.Identifier).text
});
break;
case SyntaxKind.TypeLiteral:
const lit = node as ts.TypeLiteralNode;
for(const member of lit.members)
gather_required_types(member, data);
break;
case SyntaxKind.ArrayType:
const array = node as ts.ArrayTypeNode;
analyze_type_node(array.elementType, data);
break;
case SyntaxKind.FunctionType:
const fn = node as ts.FunctionTypeNode;
for(const param of fn.parameters || [])
gather_required_types(param, data);
for(const type of fn.typeParameters || [])
gather_required_types(type, data);
break;
case SyntaxKind.TypeOperator:
const to = node as ts.TypeOperatorNode;
analyze_type_node(to.type, data);
break;
case SyntaxKind.ExpressionWithTypeArguments:
analyze_type_node((node as ts.ExpressionWithTypeArguments).expression, data);
break;
case SyntaxKind.IndexedAccessType:
const ia = node as ts.IndexedAccessTypeNode;
analyze_type_node(ia.indexType, data);
analyze_type_node(ia.objectType, data);
break;
case SyntaxKind.ParenthesizedType:
const parenthesized = node as ts.ParenthesizedTypeNode;
analyze_type_node(parenthesized.type, data);
break;
2020-05-03 18:59:18 +00:00
case SyntaxKind.MappedType:
const mt = node as ts.MappedTypeNode;
analyze_type_node(mt.type, data);
break;
case SyntaxKind.PropertyAccessExpression:
2020-05-03 21:32:04 +00:00
let pae = node as ts.PropertyAccessExpression;
while(pae.expression.kind == SyntaxKind.PropertyAccessExpression)
pae = pae.expression as ts.PropertyAccessExpression;
analyze_type_node(pae.expression, data);
2020-05-03 18:59:18 +00:00
break;
case SyntaxKind.ConstructorType:
let ct = node as ts.ConstructorTypeNode;
analyze_type_node(ct.type, data);
break;
case SyntaxKind.TupleType:
break;
case SyntaxKind.ConditionalType:
break;
default:
throw "Unknown type " + SyntaxKind[node.kind] + ". Extend me :)";
}
}