Skip to content

Commit

Permalink
feat: #276 - Improvements to moving source files.
Browse files Browse the repository at this point in the history
* Adds sourceFile.getReferencingNodesInOtherSourceFiles()
* Drastically improves the performance of moving many source files.
* Updates dynamic imports.
* Updates ImportEqualsDeclarations.
  • Loading branch information
dsherret committed Mar 18, 2018
1 parent 8d5ff33 commit dd03789
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 114 deletions.
2 changes: 1 addition & 1 deletion scripts/createTypeGuardsUtility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function createTypeGuardsUtility(inspector: TsSimpleAstInspector) {
}
}

return ArrayUtils.sortByProperty(ArrayUtils.from(methodInfos.getValues()), info => info.name);
return ArrayUtils.sortByProperty(methodInfos.getValuesAsArray(), info => info.name);

function fillBase(node: WrappedNode, nodeBase: WrappedNode) {
if (nodeBase.getName() === "Node")
Expand Down
9 changes: 8 additions & 1 deletion src/GlobalContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {LanguageService, TypeChecker} from "./compiler";
import {createWrappedNode} from "./createWrappedNode";
import {ManipulationSettingsContainer} from "./ManipulationSettings";
import {FileSystemWrapper} from "./fileSystem";
import {Logger, ConsoleLogger} from "./utils";
import {Logger, ConsoleLogger, LazyReferenceCoordinator} from "./utils";

/**
* @internal
Expand All @@ -22,6 +22,7 @@ export interface GlobalContainerOptions {
export class GlobalContainer {
private readonly _manipulationSettings = new ManipulationSettingsContainer();
private readonly _compilerFactory: CompilerFactory;
private readonly _lazyReferenceCoordinator: LazyReferenceCoordinator;
private readonly _languageService: LanguageService | undefined;
private readonly _fileSystemWrapper: FileSystemWrapper;
private readonly _compilerOptions: CompilerOptions;
Expand All @@ -32,6 +33,7 @@ export class GlobalContainer {
this._fileSystemWrapper = fileSystemWrapper;
this._compilerOptions = compilerOptions;
this._compilerFactory = new CompilerFactory(this);
this._lazyReferenceCoordinator = new LazyReferenceCoordinator(this._compilerFactory);
this._languageService = opts.createLanguageService ? new LanguageService(this) : undefined;

if (opts.typeChecker != null) {
Expand Down Expand Up @@ -98,6 +100,11 @@ export class GlobalContainer {
return this._logger;
}

/** Gets the lazy reference coordinator. */
get lazyReferenceCoordinator() {
return this._lazyReferenceCoordinator;
}

/**
* Gets if this object has a language service.
*/
Expand Down
5 changes: 2 additions & 3 deletions src/compiler/file/ExportDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ts, SyntaxKind} from "./../../typescript";
import * as errors from "./../../errors";
import {ExportSpecifierStructure} from "./../../structures";
import {insertIntoParentTextRange, verifyAndGetIndex, insertIntoCommaSeparatedNodes, getNodesToReturn} from "./../../manipulation";
import {ArrayUtils, TypeGuards, StringUtils} from "./../../utils";
import {ArrayUtils, TypeGuards, ModuleUtils} from "./../../utils";
import {Identifier} from "./../common";
import {Statement} from "./../statement";
import {ExportSpecifier} from "./ExportSpecifier";
Expand Down Expand Up @@ -87,8 +87,7 @@ export class ExportDeclaration extends Statement<ts.ExportDeclaration> {
const moduleSpecifier = this.getModuleSpecifier();
if (moduleSpecifier == null)
return false;
return StringUtils.startsWith(moduleSpecifier, "./") ||
StringUtils.startsWith(moduleSpecifier, "../");
return ModuleUtils.isModuleSpecifierRelative(moduleSpecifier);
}

/**
Expand Down
11 changes: 3 additions & 8 deletions src/compiler/file/ExternalModuleReference.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ts, SyntaxKind} from "../../typescript";
import {TypeGuards, StringUtils} from "../../utils";
import {TypeGuards, ModuleUtils} from "../../utils";
import * as errors from "../../errors";
import {Node} from "../common";
import {Expression} from "../expression";
Expand Down Expand Up @@ -34,9 +34,7 @@ export class ExternalModuleReference extends Node<ts.ExternalModuleReference> {
const expression = this.getExpression();
if (expression == null || !TypeGuards.isStringLiteral(expression))
return false;
const text = expression.getLiteralText();
return StringUtils.startsWith(text, "./")
|| StringUtils.startsWith(text, "../");
return ModuleUtils.isModuleSpecifierRelative(expression.getLiteralText());
}

/**
Expand All @@ -49,9 +47,6 @@ export class ExternalModuleReference extends Node<ts.ExternalModuleReference> {
const symbol = expression.getSymbol();
if (symbol == null)
return undefined;
const declarations = symbol.getDeclarations();
if (declarations.length === 0 || declarations[0].getKind() !== SyntaxKind.SourceFile)
return undefined;
return declarations[0] as SourceFile;
return ModuleUtils.getReferencedSourceFileFromSymbol(symbol);
}
}
11 changes: 3 additions & 8 deletions src/compiler/file/ImportDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ts, SyntaxKind} from "./../../typescript";
import * as errors from "./../../errors";
import {ImportSpecifierStructure} from "./../../structures";
import {insertIntoParentTextRange, verifyAndGetIndex, insertIntoCommaSeparatedNodes, removeChildren, getNodesToReturn} from "./../../manipulation";
import {ArrayUtils, TypeGuards, StringUtils} from "./../../utils";
import {ArrayUtils, TypeGuards, StringUtils, ModuleUtils} from "./../../utils";
import {Identifier} from "./../common";
import {Statement} from "./../statement";
import {ImportSpecifier} from "./ImportSpecifier";
Expand Down Expand Up @@ -57,19 +57,14 @@ export class ImportDeclaration extends Statement<ts.ImportDeclaration> {
const symbol = moduleSpecifier.getSymbol();
if (symbol == null)
return undefined;
const declarations = symbol.getDeclarations();
if (declarations.length === 0 || declarations[0].getKind() !== SyntaxKind.SourceFile)
return undefined;
return declarations[0] as SourceFile;
return ModuleUtils.getReferencedSourceFileFromSymbol(symbol);
}

/**
* Gets if the module specifier starts with `./` or `../`.
*/
isModuleSpecifierRelative() {
const moduleSpecifier = this.getModuleSpecifier();
return StringUtils.startsWith(moduleSpecifier, "./")
|| StringUtils.startsWith(moduleSpecifier, "../");
return ModuleUtils.isModuleSpecifierRelative(this.getModuleSpecifier());
}

/**
Expand Down
158 changes: 87 additions & 71 deletions src/compiler/file/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {getPreviousMatchingPos, getNextMatchingPos} from "./../../manipulation/t
import {Constructor} from "./../../Constructor";
import {ImportDeclarationStructure, ExportDeclarationStructure, ExportAssignmentStructure, SourceFileStructure} from "./../../structures";
import {ImportDeclarationStructureToText, ExportDeclarationStructureToText, ExportAssignmentStructureToText} from "./../../structureToTexts";
import {ArrayUtils, FileUtils, TypeGuards, StringUtils, createHashSet} from "./../../utils";
import {ArrayUtils, FileUtils, TypeGuards, StringUtils, createHashSet, EventContainer, SourceFileReferenceContainer,
SourceFileReferencingNodes, ModuleUtils} from "./../../utils";
import {callBaseFill} from "./../callBaseFill";
import {TextInsertableNode} from "./../base";
import {Node, Symbol, Identifier} from "./../common";
Expand All @@ -33,6 +34,10 @@ export const SourceFileBase: Constructor<StatementedNode> & Constructor<TextInse
export class SourceFile extends SourceFileBase<ts.SourceFile> {
/** @internal */
private _isSaved = false;
/** @internal */
private readonly _modifiedEventContainer = new EventContainer();
/** @internal */
readonly _referenceContainer = new SourceFileReferenceContainer(this);

/**
* Initializes a new instance.
Expand Down Expand Up @@ -74,6 +79,7 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
super.replaceCompilerNodeFromFactory(compilerNode);
this.global.resetProgram(); // make sure the program has the latest source file
this._isSaved = false;
this._modifiedEventContainer.fire(undefined);
}

/**
Expand Down Expand Up @@ -142,15 +148,10 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
if (filePath === this.getFilePath())
return this;

const fileImportAndExportsWithSourceFiles = getFileImportAndExportsWithSourceFiles(this, filePath);
const copiedSourceFile = getCopiedSourceFile(this);

// update the declarations in this list to point to the declarations in the copied source file
for (const item of fileImportAndExportsWithSourceFiles)
item.declaration = copiedSourceFile.getChildSyntaxListOrThrow().getChildAtPos(item.declaration.getStart())! as (ImportDeclaration | ExportDeclaration);
// update the import & export declarations in the copied file
for (const item of fileImportAndExportsWithSourceFiles)
item.declaration.setModuleSpecifier(item.sourceFile);
if (copiedSourceFile.getDirectoryPath() !== this.getDirectoryPath())
updateReferences(this);

return copiedSourceFile;

Expand All @@ -167,6 +168,19 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
throw err;
}
}

function updateReferences(currentFile: SourceFile) {
const nodeReferences = ArrayUtils.from(currentFile._referenceContainer.getNodesReferencingOtherSourceFilesEntries());

// update the nodes in this list to point to the nodes in the copied source file
for (const reference of nodeReferences)
reference[0] = copiedSourceFile.getChildSyntaxListOrThrow().getChildAtPos(reference[0].getStart())! as SourceFileReferencingNodes;
// update the import & export declarations in the copied file
updateNodeReferences(nodeReferences);

// the current files references won't have changed after the modifications
currentFile.global.lazyReferenceCoordinator.clearDityForSourceFile(currentFile);
}
}

/**
Expand Down Expand Up @@ -205,14 +219,14 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
move(filePath: string, options: SourceFileMoveOptions = {}): SourceFile {
const {overwrite = false} = options;
const oldFilePath = this.getFilePath();
filePath = this.global.fileSystemWrapper.getStandardizedAbsolutePath(filePath, this.getDirectoryPath());
const oldDirPath = this.getDirectoryPath();
filePath = this.global.fileSystemWrapper.getStandardizedAbsolutePath(filePath, oldDirPath);

if (filePath === oldFilePath)
return this;

// todo: first check to see if this has any exports or imports (add isExternalModule()?)
const fileImportAndExportsWithSourceFiles = getFileImportAndExportsWithSourceFiles(this, filePath);
const referencingImportsAndExports = this.getReferencingImportAndExportDeclarations();
const nodeReferences = ArrayUtils.from(this._referenceContainer.getNodesReferencingOtherSourceFilesEntries());
const referencingNodes = ArrayUtils.from(this._referenceContainer.getReferencingNodesInOtherSourceFiles());

if (overwrite) {
// remove the past file if it exists
Expand All @@ -229,14 +243,20 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
});
this.global.fileSystemWrapper.queueDelete(oldFilePath);

// update the import & export declarations in this file
for (const item of fileImportAndExportsWithSourceFiles)
item.declaration.setModuleSpecifier(item.sourceFile);
// update the import & export declarations in other files
for (const importAndExport of referencingImportsAndExports)
importAndExport.setModuleSpecifier(this);
updateReferences(this);

return this;

function updateReferences(currentSourceFile: SourceFile) {
// update the import & export declarations in this file if the directory hasn't changed
if (oldDirPath !== currentSourceFile.getDirectoryPath())
updateNodeReferences(nodeReferences);
// update the import & export declarations in other files
updateNodeReferences(referencingNodes.map(node => ([node, currentSourceFile]) as [SourceFileReferencingNodes, SourceFile]));

// everything should be up to date, so ignore any modifications above
currentSourceFile.global.lazyReferenceCoordinator.clearDirtySourceFiles();
}
}

/**
Expand Down Expand Up @@ -342,54 +362,36 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}

/**
* Get any source files that reference this source file in an import or export declaration.
* Get any source files that reference this source file.
*/
getReferencingSourceFiles() {
const sourceFiles = createHashSet<SourceFile>();

for (const importOrExport of this.getReferencingImportAndExportDeclarations()) {
if (sourceFiles.has(importOrExport.sourceFile))
continue;
sourceFiles.add(importOrExport.sourceFile);
}

return ArrayUtils.from(sourceFiles.values());
return ArrayUtils.from(this._referenceContainer.getDependentSourceFiles());
}

/**
* Gets the import and exports in other source files that reference this source file.
*/
getReferencingImportAndExportDeclarations() {
// todo: see if there's a better way of doing this
const baseNames = getBaseNames(this);
const result: (ExportDeclaration | ImportDeclaration)[] = [];
for (const sourceFile of this.global.compilerFactory.getSourceFilesByDirectoryDepth()) {
if (sourceFile === this)
continue;

const importAndExports = getFileImportAndExportDeclarations(sourceFile);

for (const importOrExport of importAndExports) {
const moduleSpecifier = importOrExport.getModuleSpecifier();
const referencesSourceFile = moduleSpecifier != null
&& baseNames.some(baseName => FileUtils.pathEndsWith(moduleSpecifier, baseName)) // for better performance since getModuleSpecifierSourceFile is slow
&& importOrExport.getModuleSpecifierSourceFile() === this;

if (referencesSourceFile)
result.push(importOrExport);
}
}
console.warn(`${nameof(this.getReferencingImportAndExportDeclarations)} will be replaced with ${nameof(this.getReferencingNodesInOtherSourceFiles)} in v10.`);

return result;
const nodes = this._referenceContainer.getReferencingNodesInOtherSourceFiles();
return ArrayUtils.from(filterNodes());

function getBaseNames(sourceFile: SourceFile) {
const baseName = sourceFile.getBaseName().replace(/(\.d\.ts|\.ts|\.js)$/i, "");
if (baseName === "index")
return [baseName, sourceFile.getDirectory().getBaseName()];
return [baseName];
function* filterNodes() {
for (const node of nodes) {
if (TypeGuards.isImportDeclaration(node) || TypeGuards.isExportDeclaration(node))
yield node;
}
}
}

/**
* Gets the import and exports in other source files that reference this source file.
*/
getReferencingNodesInOtherSourceFiles() {
return ArrayUtils.from(this._referenceContainer.getReferencingNodesInOtherSourceFiles());
}

/**
* Gets the source file language variant.
*/
Expand Down Expand Up @@ -888,6 +890,18 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
return StringUtils.startsWith(moduleSpecifier, "../") ? moduleSpecifier : "./" + moduleSpecifier;
}

/**
* Subscribe to when the source file is modified.
* @param subscription - Subscription.
* @param subscribe - Optional and defaults to true. Use an explicit false to unsubscribe.
*/
onModified(subscription: () => void, subscribe = true) {
if (subscribe)
this._modifiedEventContainer.subscribe(subscription);
else
this._modifiedEventContainer.unsubscribe(subscription);
}

private _refreshFromFileSystemInternal(fileReadResult: string | false): FileSystemRefreshResult {
if (fileReadResult === false) {
this.forget();
Expand All @@ -904,22 +918,24 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}
}

function getFileImportAndExportsWithSourceFiles(sourceFile: SourceFile, changingFilePath: string) {
const isChangingDirectory = FileUtils.getDirPath(changingFilePath) !== sourceFile.getDirectoryPath();
if (!isChangingDirectory)
return [];

return getFileImportAndExportDeclarations(sourceFile)
.filter(declaration => declaration.isModuleSpecifierRelative())
.map(declaration => ({
declaration,
sourceFile: declaration.getModuleSpecifierSourceFile()!
})).filter(item => item.sourceFile != null);
}

function getFileImportAndExportDeclarations(sourceFile: SourceFile) {
// efficient way of getting them
const compilerImportsAndExports = sourceFile.getChildSyntaxListOrThrow().getCompilerChildren()
.filter(c => c.kind === SyntaxKind.ImportDeclaration || c.kind === SyntaxKind.ExportDeclaration);
return compilerImportsAndExports.map(c => sourceFile.getNodeFromCompilerNode(c) as (ExportDeclaration | ImportDeclaration));
function updateNodeReferences(nodeReferences: [SourceFileReferencingNodes, SourceFile][]) {
for (const [node, sourceFile] of nodeReferences) {
if (TypeGuards.isImportDeclaration(node) || TypeGuards.isExportDeclaration(node)) {
if (node.isModuleSpecifierRelative())
node.setModuleSpecifier(sourceFile);
}
else if (TypeGuards.isImportEqualsDeclaration(node)) {
if (node.isExternalModuleReferenceRelative())
node.setExternalModuleReference(sourceFile);
}
else if (TypeGuards.isCallExpression(node)) {
const firstArg = node.getArguments()[0];
if (TypeGuards.isStringLiteral(firstArg) && ModuleUtils.isModuleSpecifierRelative(firstArg.getLiteralValue()))
firstArg.setLiteralValue(firstArg.sourceFile.sourceFile.getRelativePathToSourceFileAsModuleSpecifier(sourceFile));
}
else {
const expectNever: never = node;
// do nothing
}
}
}

0 comments on commit dd03789

Please sign in to comment.