@neo-one/typescript-concatenator-esnext-esm
Version:
NEO•ONE TypeScript Concatenator
359 lines (357 loc) • 20.8 kB
JavaScript
import { symbolKey, tsUtils } from '@neo-one/ts-utils-esnext-esm';
import { utils } from '@neo-one/utils-esnext-esm';
import _ from 'lodash';
import toposort from 'toposort';
import ts from 'typescript';
export class Concatenator {
constructor(options) {
this.sourceFileImported = new Set();
this.sourceFileToImports = new Map();
this.substituteNode = (_hint, node) => {
if (ts.isIdentifier(node)) {
const parent = tsUtils.node.getParent(node);
if (parent !== undefined && ts.isPropertyAccessExpression(parent) && tsUtils.node.getNameNode(parent) === node) {
return node;
}
return this.getIdentifierForIdentifier(node);
}
if (ts.isImportDeclaration(node)) {
if (this.isExternalFileImportExport(node)) {
return this.getCombinedImport(node);
}
const importFile = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
const namespaceIdentifier = tsUtils.importDeclaration.getNamespaceImportIdentifier(node);
if (importFile !== undefined && namespaceIdentifier !== undefined) {
const exportedSymbols = tsUtils.file.getExportedSymbols(this.context.typeChecker, importFile);
return tsUtils.setOriginalRecursive(ts.createVariableStatement(undefined, ts.createVariableDeclarationList([
ts.createVariableDeclaration(this.getIdentifierForIdentifier(namespaceIdentifier), undefined, ts.createObjectLiteral(exportedSymbols
.map((symbolIn) => {
const symbol = tsUtils.symbol.getSymbolOrAlias(this.context.typeChecker, symbolIn);
const identifier = this.getIdentifierForSymbol(symbol);
if (identifier === undefined) {
return undefined;
}
return ts.createPropertyAssignment(tsUtils.symbol.getName(symbolIn), identifier);
})
.filter(utils.notNull))),
], ts.NodeFlags.Const)), node);
}
return tsUtils.setOriginal(ts.createNotEmittedStatement(node), node);
}
if (ts.isExportDeclaration(node)) {
if (this.isEntryNode(node)) {
const moduleSpecifier = this.isExternalFileImportExport(node) ? node.moduleSpecifier : undefined;
const clause = node.exportClause;
let elements = [];
if (clause === undefined) {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
if (file !== undefined) {
const exportSymbols = tsUtils.file
.getExportedSymbols(this.context.typeChecker, file)
.filter((symbol) => this.isExportedSymbol(symbol));
if (exportSymbols.length > 0) {
elements = exportSymbols
.map((symbol) => {
const aliasedSymbol = tsUtils.symbol.getSymbolOrAlias(this.context.typeChecker, symbol);
const propertyIdentifier = this.getIdentifierForSymbol(aliasedSymbol);
const identifier = this.getIdentifierForSymbol(symbol);
if (propertyIdentifier === undefined || identifier === undefined) {
return undefined;
}
return ts.createExportSpecifier(propertyIdentifier, identifier);
})
.filter(utils.notNull);
}
}
}
else if (ts.isNamedExports(clause)) {
elements = clause.elements
.filter((element) => this.isExportedNode(element))
.map((element) => {
const propertyNameNode = tsUtils.node.getPropertyNameNode(element);
const nameNode = tsUtils.node.getNameNode(element);
const identifier = this.getIdentifierForIdentifier(propertyNameNode === undefined ? nameNode : propertyNameNode);
if (nameNode !== identifier) {
return ts.createExportSpecifier(identifier, nameNode);
}
return element;
});
}
else {
const exportFile = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
const namespaceIdentifier = clause.name;
if (exportFile !== undefined) {
const exportedSymbols = tsUtils.file.getExportedSymbols(this.context.typeChecker, exportFile);
return tsUtils.setOriginalRecursive(ts.createVariableStatement([ts.createModifier(ts.SyntaxKind.ExportKeyword)], ts.createVariableDeclarationList([
ts.createVariableDeclaration(this.getIdentifierForIdentifier(namespaceIdentifier), undefined, ts.createObjectLiteral(exportedSymbols
.map((symbolIn) => {
const symbol = tsUtils.symbol.getSymbolOrAlias(this.context.typeChecker, symbolIn);
const identifier = this.getIdentifierForSymbol(symbol);
if (identifier === undefined) {
return undefined;
}
return ts.createPropertyAssignment(tsUtils.symbol.getName(symbolIn), identifier);
})
.filter(utils.notNull))),
], ts.NodeFlags.Const)), node);
}
}
if (elements.length > 0) {
return tsUtils.setOriginalRecursive(ts.createExportDeclaration(node.decorators, node.modifiers, ts.createNamedExports(elements), moduleSpecifier), node);
}
}
return tsUtils.setOriginal(ts.createNotEmittedStatement(node), node);
}
if (ts.isExportAssignment(node)) {
const identifier = this.getIdentifierForNode(node);
if (identifier === undefined) {
return node;
}
const expression = node.expression;
if (ts.isFunctionExpression(expression)) {
return tsUtils.setOriginal(ts.createFunctionDeclaration(expression.decorators, expression.modifiers, expression.asteriskToken, identifier, expression.typeParameters, expression.parameters, expression.type, expression.body), node);
}
return tsUtils.setOriginalRecursive(ts.createVariableStatement(undefined, ts.createVariableDeclarationList([ts.createVariableDeclaration(identifier, undefined, node.expression)], ts.NodeFlags.Const)), node);
}
if (ts.isVariableStatement(node) && tsUtils.modifier.isNamedExport(node) && !this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createVariableStatement(node.modifiers === undefined
? undefined
: node.modifiers.filter((modifier) => modifier.kind !== ts.SyntaxKind.ExportKeyword), node.declarationList), node);
}
if (ts.isClassDeclaration(node) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node)) &&
!this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.updateClassDeclaration(node, node.decorators, node.modifiers === undefined
? undefined
: node.modifiers.filter((modifier) => modifier.kind !== ts.SyntaxKind.ExportKeyword && modifier.kind !== ts.SyntaxKind.DefaultKeyword), node.name, node.typeParameters, node.heritageClauses, node.members), node);
}
if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node)) &&
!this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createFunctionDeclaration(node.decorators, this.filterModifiers(node.modifiers), node.asteriskToken, node.name === undefined ? this.getIdentifierForNode(node) : node.name, node.typeParameters, node.parameters, node.type, node.body), node);
}
if (ts.isInterfaceDeclaration(node) && tsUtils.modifier.isNamedExport(node) && !this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createInterfaceDeclaration(node.decorators, this.filterModifiers(node.modifiers), node.name, node.typeParameters, node.heritageClauses, node.members), node);
}
if (ts.isTypeAliasDeclaration(node) && tsUtils.modifier.isNamedExport(node) && !this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createTypeAliasDeclaration(node.decorators, this.filterModifiers(node.modifiers), node.name, node.typeParameters, node.type), node);
}
if (ts.isEnumDeclaration(node) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node)) &&
!this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createEnumDeclaration(node.decorators, this.filterModifiers(node.modifiers), node.name, node.members), node);
}
if (ts.isModuleDeclaration(node) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node)) &&
!this.isExportedEntryNode(node)) {
return tsUtils.setOriginal(ts.createModuleDeclaration(node.decorators, this.filterModifiers(node.modifiers), node.name, node.body, node.flags), node);
}
return node;
};
this.sourceFile = options.sourceFile;
this.sourceFileExportSymbols = new Set(tsUtils.file.getExportedSymbols(options.context.typeChecker, options.sourceFile));
this.context = options.context;
this.sourceFiles = this.getAllSourceFiles(this.sourceFile);
this.duplicateIdentifiers = this.getAllDuplicateIdentifiers();
this.consolidateAllImports();
}
getCombinedImport(node) {
const sourceFile = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
if (sourceFile === undefined) {
return node;
}
if (this.sourceFileImported.has(sourceFile)) {
return tsUtils.setOriginal(ts.createNotEmittedStatement(node), node);
}
this.sourceFileImported.add(sourceFile);
const importDecl = this.sourceFileToImports.get(sourceFile);
return importDecl === undefined ? tsUtils.setOriginal(ts.createNotEmittedStatement(node), node) : importDecl;
}
getAllSourceFiles(sourceFile) {
const sourceFilesMap = this.getAllSourceFilesWorker(sourceFile);
const graph = _.flatten([...sourceFilesMap.entries()].map(([file, files]) => files.map((upstreamFile) => [file.fileName, upstreamFile.fileName])));
const sorted = _.reverse(toposort(graph));
const filePathToSourceFile = new Map([...sourceFilesMap.keys()].map((file) => [file.fileName, file]));
return sorted.map((filePath) => filePathToSourceFile.get(filePath)).filter(utils.notNull);
}
getAllSourceFilesWorker(sourceFile) {
const sourceFileMap = new Map();
const mapImportExport = (decl) => {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, decl);
if (!this.isExternalFileImportExport(decl) && file !== undefined) {
this.sourceFileImported.add(file);
return file;
}
return undefined;
};
const importSourceFiles = tsUtils.statement
.getStatements(sourceFile)
.filter(ts.isImportDeclaration)
.map(mapImportExport)
.filter(utils.notNull);
const exportSourceFiles = tsUtils.statement
.getStatements(sourceFile)
.filter(ts.isExportDeclaration)
.map(mapImportExport)
.filter(utils.notNull);
const sourceFiles = [...new Set(importSourceFiles.concat(exportSourceFiles))];
sourceFileMap.set(sourceFile, sourceFiles);
sourceFiles.forEach((importedFile) => {
this.getAllSourceFilesWorker(importedFile).forEach((files, file) => {
sourceFileMap.set(file, files);
});
});
return sourceFileMap;
}
getAllDuplicateIdentifiers() {
const fileIdentifiers = this.sourceFiles.map((file) => this.getAllIdentifiersForFile(file));
const duplicateIdentifiers = new Set();
fileIdentifiers.forEach((identifiers) => {
identifiers.forEach((identifier) => {
if (!duplicateIdentifiers.has(identifier) &&
fileIdentifiers.some((otherIdentifiers) => identifiers !== otherIdentifiers && otherIdentifiers.has(identifier))) {
duplicateIdentifiers.add(identifier);
}
});
});
return duplicateIdentifiers;
}
getAllIdentifiersForFile(file) {
const identifiers = new Set();
const visit = (node) => {
if (ts.isIdentifier(node) && this.isContainerSourceFileForDeclaration(node)) {
const symbol = this.getSymbol(node);
const declarations = symbol === undefined ? [] : tsUtils.symbol.getDeclarations(symbol);
if (symbol !== undefined &&
declarations.length > 0 &&
tsUtils.node.getSourceFile(node) === tsUtils.node.getSourceFile(declarations[0])) {
identifiers.add(tsUtils.symbol.getName(symbol));
}
}
ts.forEachChild(node, visit);
};
ts.forEachChild(file, visit);
return identifiers;
}
consolidateAllImports() {
this.sourceFiles.forEach((sourceFile) => {
this.consolidateAllImportsForFile(sourceFile);
});
}
consolidateAllImportsForFile(node) {
tsUtils.statement
.getStatements(node)
.filter(ts.isImportDeclaration)
.forEach((decl) => {
this.consolidateImports(decl);
});
}
consolidateImports(node) {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
if (file === undefined) {
return;
}
const namedImports = tsUtils.importDeclaration
.getNamedImports(node)
.map((namedImport) => tsUtils.setOriginal(ts.createImportSpecifier(undefined, namedImport.propertyName === undefined
? this.getIdentifierForIdentifier(namedImport.name)
: this.getIdentifierForIdentifier(namedImport.propertyName)), namedImport));
const existingImport = this.sourceFileToImports.get(file);
const moduleSpecifier = this.isExternalFileImportExport(node)
? ts.isStringLiteral(node.moduleSpecifier)
? tsUtils.setOriginal(ts.createStringLiteral(node.moduleSpecifier.text), node.moduleSpecifier)
: node.moduleSpecifier
: tsUtils.setOriginal(ts.createStringLiteral(tsUtils.file.getFilePath(file)), node.moduleSpecifier);
if (existingImport === undefined) {
this.sourceFileToImports.set(file, tsUtils.setOriginalRecursive(ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports(namedImports)), moduleSpecifier), node));
}
else {
const existingNamedImports = tsUtils.importDeclaration.getNamedImports(existingImport);
const existingNames = new Set(existingNamedImports.map((namedImport) => namedImport.name.text));
const filteredImports = namedImports.filter((namedImport) => !existingNames.has(namedImport.name.text));
this.sourceFileToImports.set(file, tsUtils.setOriginalRecursive(ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports(existingNamedImports.concat(filteredImports))), moduleSpecifier), existingImport));
}
}
filterModifiers(modifiers) {
if (modifiers === undefined) {
return undefined;
}
return modifiers.filter((modifier) => modifier.kind !== ts.SyntaxKind.ExportKeyword && modifier.kind !== ts.SyntaxKind.DefaultKeyword);
}
getIdentifierForIdentifier(node) {
if (!tsUtils.isOriginal(node)) {
return node;
}
const identifier = this.getIdentifierForNode(node);
return identifier === undefined ? node : identifier;
}
getIdentifierForNode(node) {
const identifier = this.getIdentifierStringForSymbol(this.getSymbol(node));
return identifier === undefined ? undefined : tsUtils.setOriginal(ts.createIdentifier(identifier), node);
}
getIdentifierForSymbol(symbol) {
const identifier = this.getIdentifierStringForSymbol(symbol);
return identifier === undefined ? undefined : ts.createIdentifier(identifier);
}
getIdentifierStringForSymbol(symbol) {
if (symbol === undefined) {
return undefined;
}
if (this.sourceFileExportSymbols.has(symbol)) {
return tsUtils.symbol.getName(symbol);
}
let identifier;
if (this.isContainerSourceFileForDeclarationSymbol(symbol)) {
identifier =
this.isDuplicateIdentifier(symbol) || tsUtils.symbol.getName(symbol) === 'default'
? `${tsUtils.symbol.getName(symbol)}${symbolKey(symbol)}`
: tsUtils.symbol.getName(symbol);
}
return identifier;
}
isEntryNode(node) {
return tsUtils.node.getSourceFile(node) === this.sourceFile;
}
isExportedEntryNode(node) {
return this.isExportedNode(node) && this.isEntryNode(node);
}
isExportedNode(node) {
return this.isExportedSymbol(tsUtils.node.getSymbol(this.context.typeChecker, node));
}
isExportedSymbol(symbol) {
return symbol !== undefined && this.sourceFileExportSymbols.has(symbol);
}
isDuplicateIdentifier(symbol) {
return this.duplicateIdentifiers.has(tsUtils.symbol.getName(symbol));
}
isContainerSourceFileForDeclaration(node) {
return this.isContainerSourceFileForDeclarationSymbol(this.getSymbol(node));
}
isContainerSourceFileForDeclarationSymbol(symbol) {
if (symbol === undefined) {
return false;
}
const declarations = tsUtils.symbol.getDeclarations(symbol);
return declarations.length > 0 && this.isContainerSourceFile(declarations[0]);
}
isContainerSourceFile(node) {
const firstAncestor = tsUtils.node.getFirstAncestorByTest(node, (value) => ts.isSourceFile(value) || ts.isFunctionLike(value) || ts.isBlock(value) || tsUtils.guards.isDeclaration(value));
return firstAncestor !== undefined && ts.isSourceFile(firstAncestor);
}
isExternalFileImportExport(node) {
const importPath = tsUtils.importExport.getModuleSpecifier(node);
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
if (importPath === undefined || file === undefined) {
return false;
}
return this.context.isExternalFile(file, tsUtils.literal.getLiteralValue(importPath), node);
}
getSymbol(node) {
if (this.context.getSymbol !== undefined) {
return this.context.getSymbol(node);
}
const symbol = tsUtils.node.getSymbol(this.context.typeChecker, node);
return symbol === undefined ? undefined : tsUtils.symbol.getSymbolOrAlias(this.context.typeChecker, symbol);
}
}
//# sourceMappingURL=Concatenator.js.map