Skip to content

Instantly share code, notes, and snippets.

@danwbyrne
Last active October 26, 2018 23:22
Show Gist options
  • Save danwbyrne/ed8af85b9ab52cd019b03e43bb0fa8a0 to your computer and use it in GitHub Desktop.
Save danwbyrne/ed8af85b9ab52cd019b03e43bb0fa8a0 to your computer and use it in GitHub Desktop.
import { tsUtils } from '@neo-one/ts-utils';
import * as fs from 'fs-extra';
import path from 'path';
import ts from 'typescript';
import { Concatenator } from './Concatenator';
export type SymbolAndSources = { readonly [Symbol in string]: string };
export type SourceFileSymbolAndSources = { readonly [SourceFile in string]: SymbolAndSources };
export function concatenate(entry: string) {
const servicesHost: ts.LanguageServiceHost = {
getScriptFileNames: () => [entry],
getScriptVersion: () => '0',
getScriptSnapshot: (fileName) => {
// tslint:disable-next-line no-non-null-assertion
if (!servicesHost.fileExists!(fileName)) {
return undefined;
}
// tslint:disable-next-line no-non-null-assertion
return ts.ScriptSnapshot.fromString(servicesHost.readFile!(fileName)!);
},
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => {
const configPath = ts.findConfigFile(entry, ts.sys.fileExists);
if (configPath === undefined) {
return ts.getDefaultCompilerOptions();
}
const text = fs.readFileSync(configPath, 'utf8');
const parseResult = ts.parseConfigFileTextToJson(configPath, text);
return ts.parseJsonConfigFileContent(
parseResult.config,
{
useCaseSensitiveFileNames: true,
readDirectory: ts.sys.readDirectory,
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
},
path.dirname(configPath),
undefined,
undefined,
).options;
},
getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts),
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
getNewLine: () => ts.sys.newLine,
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory,
};
const languageService = ts.createLanguageService(servicesHost);
const program = languageService.getProgram();
if (program === undefined) {
throw new Error('hmmm');
}
const sourceFile = tsUtils.file.getSourceFileOrThrow(program, entry);
const typeChecker = program.getTypeChecker();
const getSymbol = (node: ts.Node) => {
const symbol = tsUtils.node.getSymbol(typeChecker, node);
if (symbol === undefined) {
return undefined;
}
const aliased = tsUtils.symbol.getAliasedSymbol(typeChecker, symbol);
if (aliased !== undefined) {
return aliased;
}
return symbol;
};
const globalExportSymbols = getGlobalExports(sourceFile, getSymbol, typeChecker);
console.log(globalExportSymbols);
const isGlobalFile = (node: ts.SourceFile) => program.isSourceFileFromExternalLibrary(node);
const isIgnoreFile = () => false;
const isGlobalIdentifier = () => false;
const isGlobalSymbol = (symbol: ts.Symbol, sourceString?: string) =>
globalExportSymbols[symbol.name] === sourceString;
const concatenator = new Concatenator({
context: {
typeChecker,
program,
languageService,
getSymbol,
isIgnoreFile,
isGlobalIdentifier,
isGlobalFile,
isGlobalSymbol,
},
sourceFile,
});
const sourceFiles = concatenator.sourceFiles;
if (sourceFiles.length === 0 || sourceFiles.length === 1) {
return undefined;
}
return tsUtils.printBundle(program, sourceFiles, concatenator.substituteNode);
}
// just bringing this in from ts-utils/utils because I want it and it isn't exported.
function throwIfNullOrUndefined<T>(value: T | null | undefined, name: string): T {
if (value == undefined) {
throw new Error(`Expected a ${name}`);
}
return value;
}
const getGlobalExports = (
node: ts.SourceFile,
getSymbol: (node: ts.Node) => ts.Symbol | undefined,
typeChecker: ts.TypeChecker,
): SymbolAndSources => {
type exportedNormType = ts.FunctionDeclaration | ts.ClassDeclaration;
type exportedNameType = ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.InterfaceDeclaration;
type exportedType = exportedNameType | exportedNormType;
const isExportedType = (value: ts.Statement): value is exportedType => {
return (
ts.isFunctionDeclaration(value) ||
ts.isClassDeclaration(value) ||
ts.isTypeAliasDeclaration(value) ||
ts.isEnumDeclaration(value) ||
ts.isInterfaceDeclaration(value)
);
};
const isNameType = (value: exportedType): value is exportedNameType => {
return value.name !== undefined;
};
const getExportedSymbolAndSources = (declarations: ReadonlyArray<exportedType>): SymbolAndSources => {
return declarations.reduce((acc: SymbolAndSources, decl) => {
const declName = isNameType(decl) ? decl.name : decl;
const symbol = throwIfNullOrUndefined(getSymbol(declName), 'get symbol - interfaceExports');
return symbol !== undefined && tsUtils.modifier.isNamedExport(decl)
? { [symbol.name]: node.fileName, ...acc }
: acc;
}, {});
};
let sourceFileToExportSymbols: SourceFileSymbolAndSources = {};
function collectSourceFilesToExportSymbols(node: ts.SourceFile): void {
const statements = tsUtils.statement.getStatements(node);
const pureExports = statements.filter(ts.isExportDeclaration);
// for each pure export declaration IF it has no named exports, then its asterisk
// and we move to the module it specifies to calculate what IT is exporting.
// if it HAS named exports, then add them to the list.
// TODO (not sure how re-exporting is going to play out here)
// we need to go DOWN the path of asterisks for dynamic programming.
const exportSymbolAndSourcesIn = pureExports.map((decl) => {
const declFile = tsUtils.importExport.getModuleSpecifierSourceFileOrThrow(typeChecker, decl);
const tsExports = tsUtils.exportDeclaration.getNamedExports(decl);
console.log(tsExports);
// if (declFileIn === undefined) {
// IF WE CAN'T FIND A DECL FILE, THEN WE HAVE A SITUATION LIKE:
// import { foo } from './foo';
// export { foo };
// so we just have to make a method for looking up what import they came from.
// thats for tomorrow I think, I'm tired.
// }
if (!sourceFileToExportSymbols[declFile.fileName]) {
collectSourceFilesToExportSymbols(declFile);
}
const declFileExports = sourceFileToExportSymbols[declFile.fileName];
if (tsExports.length === 0) {
return declFileExports;
}
return tsExports.reduce((acc: SymbolAndSources, tsExport) => {
if (tsExport.propertyName !== undefined) {
}
const symbolString = throwIfNullOrUndefined(
tsUtils.exportSpecifier.getLocalTargetSymbol(typeChecker, tsExport),
'symbol',
).name;
const sourceString = declFileExports[symbolString];
return {
[symbolString]: sourceString,
...acc,
};
}, {});
});
const exportSymbolAndSources = exportSymbolAndSourcesIn.reduce((acc: SymbolAndSources, data) => {
return {
...acc,
...data,
};
}, {});
// end pure export declaration block.
const variableExports = statements.filter(ts.isVariableStatement);
const exportedVariableSymbolAndSources = variableExports.reduce((acc: SymbolAndSources, decl) => {
const symbol = throwIfNullOrUndefined(
getSymbol(decl.declarationList.declarations[0].name),
'get symbol - variableExports',
);
return symbol !== undefined && tsUtils.modifier.isNamedExport(decl)
? {
[symbol.name]: node.fileName,
...acc,
}
: acc;
}, {});
const exportedStatementsSymbolAndSources = getExportedSymbolAndSources(statements.filter(isExportedType));
/* etc... */
sourceFileToExportSymbols = {
...sourceFileToExportSymbols,
[node.fileName]: {
...exportSymbolAndSources,
...exportedVariableSymbolAndSources,
...exportedStatementsSymbolAndSources,
},
};
}
collectSourceFilesToExportSymbols(node);
return sourceFileToExportSymbols[node.fileName];
};
import { AnyNameableNode, symbolKey, tsUtils } from '@neo-one/ts-utils';
import { utils } from '@neo-one/utils';
import _ from 'lodash';
import toposort from 'toposort';
import ts from 'typescript';
export interface ConcatenatorContext {
readonly typeChecker: ts.TypeChecker;
readonly program: ts.Program;
readonly languageService: ts.LanguageService;
readonly getSymbol: (node: ts.Node) => ts.Symbol | undefined;
readonly isIgnoreFile: (node: ts.ClassDeclaration) => boolean;
readonly isGlobalIdentifier: (value: string) => boolean;
readonly isGlobalFile: (node: ts.SourceFile) => boolean;
readonly isGlobalSymbol: (node: ts.Symbol, sourceString?: string) => boolean;
}
export interface ConcatenatorOptions {
readonly context: ConcatenatorContext;
readonly sourceFile: ts.SourceFile;
}
export class Concatenator {
public readonly sourceFiles: ReadonlyArray<ts.SourceFile>;
private readonly sourceFile: ts.SourceFile;
private readonly context: ConcatenatorContext;
private readonly duplicateIdentifiers: Set<string>;
private readonly sourceFileImported = new Set<ts.SourceFile>();
private readonly sourceFileToImports: Map<ts.SourceFile, ts.ImportDeclaration> = new Map<
ts.SourceFile,
ts.ImportDeclaration
>();
public constructor(options: ConcatenatorOptions) {
this.sourceFile = options.sourceFile;
this.context = options.context;
this.sourceFiles = this.getAllSourceFiles(this.sourceFile);
this.duplicateIdentifiers = this.getAllDuplicateIdentifiers();
this.consolidateAllImports();
}
public readonly substituteNode = (_hint: ts.EmitHint, node: ts.Node) => {
if (ts.isIdentifier(node)) {
return this.getIdentifierForIdentifier(node);
}
if (ts.isImportDeclaration(node)) {
return this.isConcatenatedImport(node)
? tsUtils.setOriginal(ts.createNotEmittedStatement(node), node)
: this.getCombinedImport(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)) {
const symbol = this.context.getSymbol(node.declarationList.declarations[0].name);
return tsUtils.setOriginal(
ts.createVariableStatement(
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: node.modifiers,
node.declarationList,
),
node,
);
}
if (
ts.isClassDeclaration(node) &&
((tsUtils.modifier.isNamedExport(node) && !this.isIgnoreFileInternal(node)) ||
tsUtils.modifier.isDefaultExport(node))
) {
const symbol = this.context.getSymbol(node);
return tsUtils.setOriginal(
ts.updateClassDeclaration(
node,
node.decorators,
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
node.members,
),
node,
);
}
if (
(ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node))
) {
const symbol = this.context.getSymbol(node);
return tsUtils.setOriginal(
ts.createFunctionDeclaration(
node.decorators,
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: 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)) {
const symbol = this.context.getSymbol(node.name);
return tsUtils.setOriginal(
ts.createInterfaceDeclaration(
node.decorators,
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
node.members,
),
node,
);
}
if (ts.isTypeAliasDeclaration(node) && tsUtils.modifier.isNamedExport(node)) {
const symbol = this.context.getSymbol(node);
return tsUtils.setOriginal(
ts.createTypeAliasDeclaration(
node.decorators,
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: node.modifiers,
node.name,
node.typeParameters,
node.type,
),
node,
);
}
if (
ts.isEnumDeclaration(node) &&
(tsUtils.modifier.isNamedExport(node) || tsUtils.modifier.isDefaultExport(node))
) {
const symbol = this.context.getSymbol(node);
return tsUtils.setOriginal(
ts.createEnumDeclaration(
node.decorators,
symbol === undefined || !this.context.isGlobalSymbol(symbol, node.getSourceFile().fileName)
? this.filterModifiers(node.modifiers)
: node.modifiers,
node.name,
node.members,
),
node,
);
}
if (ts.isExportDeclaration(node)) {
return tsUtils.setOriginal(ts.createNotEmittedStatement(node), node);
}
if (ts.isPropertyAccessExpression(node)) {
const name = tsUtils.node.getNameNode(node);
const identifier = this.getIdentifierForNode(name);
return identifier === undefined ? node : identifier;
}
return node;
};
private getCombinedImport(node: ts.ImportDeclaration): ts.Statement {
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;
}
private isIgnoreFileInternal(node: ts.ClassDeclaration): boolean {
return tsUtils.node.getSourceFile(node) === this.sourceFile && this.context.isIgnoreFile(node);
}
private getAllSourceFiles(sourceFile: ts.SourceFile): ReadonlyArray<ts.SourceFile> {
const sourceFilesMap = this.getAllSourceFilesWorker(sourceFile);
const graph = _.flatten(
[...sourceFilesMap.entries()].map(([file, files]) =>
files.map<[string, string]>((upstreamFile) => [file.fileName, upstreamFile.fileName]),
),
);
const sorted = _.reverse(toposort(graph));
const filePathToSourceFile = new Map(
[...sourceFilesMap.keys()].map<[string, ts.SourceFile]>((file) => [file.fileName, file]),
);
return sorted.map((filePath) => filePathToSourceFile.get(filePath)).filter(utils.notNull);
}
private getAllSourceFilesWorker(sourceFile: ts.SourceFile): Map<ts.SourceFile, ReadonlyArray<ts.SourceFile>> {
const sourceFileMap = new Map<ts.SourceFile, ReadonlyArray<ts.SourceFile>>();
const importSourceFiles = tsUtils.statement
.getStatements(sourceFile)
.filter(ts.isImportDeclaration)
.map((decl) => {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, decl);
if (this.isConcatenatedImport(decl) && file !== undefined) {
this.sourceFileImported.add(file);
return file;
}
return undefined;
})
.filter(utils.notNull);
const exportSourceFiles = tsUtils.statement
.getStatements(sourceFile)
.filter(ts.isExportDeclaration)
.map((decl) => {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, decl);
if (!this.isBuiltinFile(decl) && file !== undefined) {
this.sourceFileImported.add(file);
return file;
}
return undefined;
})
.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;
}
private getAllDuplicateIdentifiers(): Set<string> {
const fileIdentifiers = this.sourceFiles.map((file) => this.getAllIdentifiersForFile(file));
const duplicateIdentifiers = new Set<string>();
fileIdentifiers.forEach((identifiers) => {
identifiers.forEach((identifier) => {
if (
!duplicateIdentifiers.has(identifier) &&
(this.context.isGlobalIdentifier(identifier) ||
fileIdentifiers.some(
(otherIdentifiers) => identifiers !== otherIdentifiers && otherIdentifiers.has(identifier),
))
) {
duplicateIdentifiers.add(identifier);
}
});
});
return duplicateIdentifiers;
}
private getAllIdentifiersForFile(file: ts.SourceFile): Set<string> {
const identifiers = new Set<string>();
const visit = (node: ts.Node) => {
if (ts.isIdentifier(node) && this.isContainerSourceFileForDeclaration(node)) {
const symbol = this.context.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;
}
private consolidateAllImports(): void {
this.sourceFiles.forEach((sourceFile) => {
this.consolidateAllImportsForFile(sourceFile);
});
}
private consolidateAllImportsForFile(node: ts.SourceFile): void {
tsUtils.statement
.getStatements(node)
.filter(ts.isImportDeclaration)
.forEach((decl) => {
this.consolidateImports(decl);
});
}
private consolidateImports(node: ts.ImportDeclaration): void {
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.context.isGlobalFile(file)
? 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,
),
);
}
}
private filterModifiers(modifiers: ReadonlyArray<ts.Modifier> | undefined): ReadonlyArray<ts.Modifier> | undefined {
if (modifiers === undefined) {
return undefined;
}
return modifiers.filter(
(modifier) => modifier.kind !== ts.SyntaxKind.ExportKeyword && modifier.kind !== ts.SyntaxKind.DefaultKeyword,
);
}
private getIdentifierForIdentifier(node: ts.Identifier): ts.Identifier {
if (!tsUtils.isOriginal(node)) {
return node;
}
const identifier = this.getIdentifierForNode(node);
return identifier === undefined ? node : identifier;
}
private getIdentifierForNode(node: ts.Node): ts.Identifier | undefined {
const source = node.getSourceFile().fileName;
const identifier = this.getIdentifierStringForSymbol(this.context.getSymbol(node), source);
return identifier === undefined ? undefined : tsUtils.setOriginal(ts.createIdentifier(identifier), node);
}
private getIdentifierStringForSymbol(symbol: ts.Symbol | undefined, sourceString?: string): string | undefined {
if (symbol === undefined) {
return undefined;
}
let identifier: string | undefined;
if (this.context.isGlobalSymbol(symbol, sourceString)) {
identifier = tsUtils.symbol.getName(symbol);
}
if (identifier === undefined && this.isContainerSourceFileForDeclarationSymbol(symbol)) {
identifier =
this.isDuplicateIdentifier(symbol) || tsUtils.symbol.getName(symbol) === 'default'
? `s${symbolKey(symbol)}`
: tsUtils.symbol.getName(symbol);
}
return identifier;
}
private isDuplicateIdentifier(symbol: ts.Symbol): boolean {
return this.duplicateIdentifiers.has(tsUtils.symbol.getName(symbol));
}
private isContainerSourceFileForDeclaration(node: ts.Node): boolean {
return this.isContainerSourceFileForDeclarationSymbol(this.context.getSymbol(node));
}
private isContainerSourceFileForDeclarationSymbol(symbol: ts.Symbol | undefined): boolean {
if (symbol === undefined) {
return false;
}
const declarations = tsUtils.symbol.getDeclarations(symbol);
return declarations.length > 0 && this.isContainerSourceFile(declarations[0]);
}
private isContainerSourceFile(node: ts.Node): boolean {
const firstAncestor = tsUtils.node.getFirstAncestorByTest(
node,
(value): value is ts.Node =>
ts.isSourceFile(value) || ts.isFunctionLike(value) || ts.isBlock(value) || tsUtils.guards.isDeclaration(value),
);
return firstAncestor !== undefined && ts.isSourceFile(firstAncestor);
}
private isConcatenatedImport(node: ts.ImportDeclaration): boolean {
return this.hasValueReference(node) && !this.isBuiltinFile(node);
}
private isBuiltinFile(node: ts.ImportDeclaration | ts.ExportDeclaration): boolean {
const file = tsUtils.importExport.getModuleSpecifierSourceFile(this.context.typeChecker, node);
if (file === undefined) {
return false;
}
return this.context.isGlobalFile(file);
}
private hasValueReference(node: ts.ImportDeclaration): boolean {
const currentSourceFile = tsUtils.node.getSourceFile(node);
const namespaceImport = tsUtils.importDeclaration.getNamespaceImport(node);
if (namespaceImport !== undefined && this.hasLocalValueReferences(currentSourceFile, namespaceImport)) {
return true;
}
const defaultImport = tsUtils.importDeclaration.getDefaultImport(node);
if (defaultImport !== undefined && this.hasLocalValueReferences(currentSourceFile, defaultImport)) {
return true;
}
const namedImports = tsUtils.importDeclaration.getNamedImports(node);
return namedImports.some((namedImport) => {
const symbolIn = this.context.getSymbol(namedImport);
console.log(symbolIn);
const isGlobal =
symbolIn === undefined ? false : this.context.isGlobalSymbol(symbolIn, currentSourceFile.fileName);
return isGlobal || this.hasLocalValueReferences(currentSourceFile, this.getImportNameNode(namedImport));
});
}
private hasLocalValueReferences(currentSourceFile: ts.SourceFile, node: AnyNameableNode): boolean {
const references = tsUtils.reference.findReferencesAsNodes(
this.context.program,
this.context.languageService,
node,
);
return references.some(
(reference) =>
tsUtils.node.getSourceFile(reference) === currentSourceFile &&
tsUtils.node.getFirstAncestorByTest(reference, ts.isImportDeclaration) === undefined &&
!tsUtils.node.isPartOfTypeNode(reference),
);
}
private getImportNameNode(node: ts.ImportSpecifier): ts.ImportSpecifier | ts.Identifier {
const alias = tsUtils.node.getPropertyNameNode(node);
return alias === undefined ? node : alias;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment