diff --git a/docs/details/source-files.md b/docs/details/source-files.md index 85298ac25..10eb1f873 100644 --- a/docs/details/source-files.md +++ b/docs/details/source-files.md @@ -209,12 +209,40 @@ Getting the source files that reference this source file in nodes like import de const referencingSourceFiles = sourceFile.getReferencingSourceFiles(); ``` -To get the nodes that reference the source file in other source files, use: +To get the nodes that reference the source file in other source files: ```ts const referencingNodes = sourceFile.getReferencingNodesInOtherSourceFiles(); ``` +To get the string literals that reference this source file in other source files: + +```ts +const referencingLiterals = sourceFile.getReferencingLiteralsInOtherSourceFiles(); +``` + +### Getting referenced files + +The opposite of the referencing files, is the referenced files—files that are referenced in nodes within the current file. + +```ts +const referencedSourceFiles = sourceFile.getReferencedSourceFiles(); +``` + +To get the nodes that reference other source files: + +```ts +const nodesReferencingOtherSourceFiles = sourceFile.getNodesReferencingOtherSourceFiles(); +``` + +To get the string literals that reference other source files: + +```ts +const literalsReferencingOtherSourceFiles = sourceFile.getLiteralsReferencingOtherSourceFiles(); +// or to get all the literals that reference a module (and may not have been resolved to a source file) +const importLiterals = sourceFile.getImportStringLiterals(); +``` + ### Relative File Paths It might be useful to get the relative path from one source file to another source file or directory. diff --git a/lib/ts-morph.d.ts b/lib/ts-morph.d.ts index 5fb9b737c..bc57df8d3 100644 --- a/lib/ts-morph.d.ts +++ b/lib/ts-morph.d.ts @@ -8221,7 +8221,22 @@ export declare class SourceFile extends SourceFileBase { */ getReferencingLiteralsInOtherSourceFiles(): StringLiteral[]; /** - * Gets all the descendant string literals that reference a source file. + * Gets the source files this source file references in string literals. + */ + getReferencedSourceFiles(): SourceFile[]; + /** + * Gets the nodes that reference other source files in string literals. + */ + getNodesReferencingOtherSourceFiles(): SourceFileReferencingNodes[]; + /** + * Gets the string literals in this source file that references other source files. + * @remarks This is similar to `getImportStringLiterals()`, but `getImportStringLiterals()` + * will return import string literals that may not be referencing another source file + * or have not been able to be resolved. + */ + getLiteralsReferencingOtherSourceFiles(): StringLiteral[]; + /** + * Gets all the descendant string literals that reference a module. */ getImportStringLiterals(): StringLiteral[]; /** diff --git a/src/compiler/ast/module/SourceFile.ts b/src/compiler/ast/module/SourceFile.ts index a8837065c..d7db54eac 100644 --- a/src/compiler/ast/module/SourceFile.ts +++ b/src/compiler/ast/module/SourceFile.ts @@ -7,7 +7,8 @@ import { ProjectContext } from "../../../ProjectContext"; import { SourceFileSpecificStructure, SourceFileStructure, StructureKind } from "../../../structures"; import { Constructor } from "../../../types"; import { LanguageVariant, ScriptTarget, ts, ScriptKind } from "../../../typescript"; -import { ArrayUtils, EventContainer, FileUtils, Memoize, ModuleUtils, SourceFileReferenceContainer, StringUtils } from "../../../utils"; +import { ArrayUtils, EventContainer, FileUtils, Memoize, ModuleUtils, SourceFileReferenceContainer, StringUtils, TypeGuards, + SourceFileReferencingNodes } from "../../../utils"; import { Diagnostic, EmitOptionsBase, EmitOutput, EmitResult, FormatCodeSettings, TextChange, UserPreferences } from "../../tools"; import { ModuledNode, TextInsertableNode } from "../base"; import { callBaseGetStructure } from "../callBaseGetStructure"; @@ -514,7 +515,13 @@ export class SourceFile extends SourceFileBase { * Gets the import and exports in other source files that reference this source file. */ getReferencingNodesInOtherSourceFiles() { - return Array.from(this._referenceContainer.getReferencingNodesInOtherSourceFiles()); + const literals = this.getReferencingLiteralsInOtherSourceFiles(); + return Array.from(getNodes()); + + function* getNodes(): Iterable { + for (const literal of literals) + yield getReferencingNodeFromStringLiteral(literal); + } } /** @@ -525,7 +532,49 @@ export class SourceFile extends SourceFileBase { } /** - * Gets all the descendant string literals that reference a source file. + * Gets the source files this source file references in string literals. + */ + getReferencedSourceFiles() { + const entries = this._referenceContainer.getLiteralsReferencingOtherSourceFilesEntries(); + return Array.from(new Set(getSourceFilesFromEntries()).values()); + + function* getSourceFilesFromEntries(): Iterable { + for (const [, sourceFile] of entries) + yield sourceFile; + } + } + + /** + * Gets the nodes that reference other source files in string literals. + */ + getNodesReferencingOtherSourceFiles() { + const entries = this._referenceContainer.getLiteralsReferencingOtherSourceFilesEntries(); + return Array.from(getNodes()); + + function* getNodes(): Iterable { + for (const [literal] of entries) + yield getReferencingNodeFromStringLiteral(literal); + } + } + + /** + * Gets the string literals in this source file that references other source files. + * @remarks This is similar to `getImportStringLiterals()`, but `getImportStringLiterals()` + * will return import string literals that may not be referencing another source file + * or have not been able to be resolved. + */ + getLiteralsReferencingOtherSourceFiles() { + const entries = this._referenceContainer.getLiteralsReferencingOtherSourceFilesEntries(); + return Array.from(getLiteralsFromEntries()); + + function* getLiteralsFromEntries(): Iterable { + for (const [literal] of entries) + yield literal; + } + } + + /** + * Gets all the descendant string literals that reference a module. */ getImportStringLiterals() { this._ensureBound(); @@ -938,3 +987,12 @@ function updateStringLiteralReferences(nodeReferences: ReadonlyArray<[StringLite stringLiteral.setLiteralValue(stringLiteral._sourceFile.getRelativePathAsModuleSpecifierTo(sourceFile)); } } + +function getReferencingNodeFromStringLiteral(literal: StringLiteral) { + const parent = literal.getParentOrThrow(); + const grandParent = parent.getParent(); + if (grandParent != null && TypeGuards.isImportEqualsDeclaration(grandParent)) + return grandParent; + else + return parent as SourceFileReferencingNodes; +} diff --git a/src/tests/compiler/ast/module/sourceFileTests.ts b/src/tests/compiler/ast/module/sourceFileTests.ts index 64228e169..18c58eef8 100644 --- a/src/tests/compiler/ast/module/sourceFileTests.ts +++ b/src/tests/compiler/ast/module/sourceFileTests.ts @@ -1246,6 +1246,72 @@ function myFunction(param: MyClass) { }); }); + describe(nameof(s => s.getReferencedSourceFiles), () => { + function expectResult(file: SourceFile, expectedSourceFiles: SourceFile[]) { + expect(file.getReferencedSourceFiles().map(r => r.getFilePath()).sort()) + .to.deep.equal(expectedSourceFiles.map(s => s.getFilePath()).sort()); + } + + it("should get the source files that this source file references", () => { + const fileText = "export interface MyInterface {}"; + const { sourceFile, project } = getInfoFromText(fileText, { filePath: "/MyInterface.ts" }); + const file1 = project.createSourceFile("/file.ts", `import {MyInterface} from "./MyInterface"; export * from "../MyInterface";`); + const file2 = project.createSourceFile("/file2.ts", `import {MyInterface} from "./MyInterface";`); + const file3 = project.createSourceFile("/file3.ts", `export * from "./MyInterface";`); + const file4 = project.createSourceFile("/file4.ts", `const t = import("./MyInterface");`); + + expectResult(sourceFile, []); + expectResult(file1, [sourceFile]); + expectResult(file2, [sourceFile]); + expectResult(file3, [sourceFile]); + expectResult(file4, [sourceFile]); + }); + }); + + describe(nameof(s => s.getNodesReferencingOtherSourceFiles), () => { + function expectResult(file: SourceFile, expectedNodes: string[]) { + expect(file.getNodesReferencingOtherSourceFiles().map(n => n.getText()).sort()) + .to.deep.equal(expectedNodes.sort()); + } + + it("should get the nodes in this source file that reference other source files", () => { + const fileText = "export interface MyInterface {}"; + const { sourceFile, project } = getInfoFromText(fileText, { filePath: "/MyInterface.ts" }); + const file1 = project.createSourceFile("/file.ts", `import {MyInterface} from "./MyInterface"; export * from "../MyInterface";`); + const file2 = project.createSourceFile("/file2.ts", `import {MyInterface} from "./MyInterface";`); + const file3 = project.createSourceFile("/file3.ts", `export * from "./MyInterface";`); + const file4 = project.createSourceFile("/file4.ts", `const t = import("./MyInterface");`); + + expectResult(sourceFile, []); + expectResult(file1, [`import {MyInterface} from "./MyInterface";`, `export * from "../MyInterface";`]); + expectResult(file2, [`import {MyInterface} from "./MyInterface";`]); + expectResult(file3, [`export * from "./MyInterface";`]); + expectResult(file4, [`import("./MyInterface")`]); + }); + }); + + describe(nameof(s => s.getLiteralsReferencingOtherSourceFiles), () => { + function expectResult(file: SourceFile, expectedNodes: string[]) { + expect(file.getLiteralsReferencingOtherSourceFiles().map(n => n.getText()).sort()) + .to.deep.equal(expectedNodes.sort()); + } + + it("should get the nodes in this source file that reference other source files", () => { + const fileText = "export interface MyInterface {}"; + const { sourceFile, project } = getInfoFromText(fileText, { filePath: "/MyInterface.ts" }); + const file1 = project.createSourceFile("/file.ts", `import {MyInterface} from "./MyInterface"; export * from "../MyInterface";`); + const file2 = project.createSourceFile("/file2.ts", `import {MyInterface} from "./MyInterface";`); + const file3 = project.createSourceFile("/file3.ts", `export * from "./MyInterface";`); + const file4 = project.createSourceFile("/file4.ts", `const t = import("./MyInterface");`); + + expectResult(sourceFile, []); + expectResult(file1, [`"./MyInterface"`, `"../MyInterface"`]); + expectResult(file2, [`"./MyInterface"`]); + expectResult(file3, [`"./MyInterface"`]); + expectResult(file4, [`"./MyInterface"`]); + }); + }); + describe(nameof(s => s.getExtension), () => { function doTest(filePath: string, extension: string) { const { sourceFile } = getInfoFromText("", { filePath }); diff --git a/src/utils/references/SourceFileReferenceContainer.ts b/src/utils/references/SourceFileReferenceContainer.ts index e761df544..9c09e2b42 100644 --- a/src/utils/references/SourceFileReferenceContainer.ts +++ b/src/utils/references/SourceFileReferenceContainer.ts @@ -31,17 +31,6 @@ export class SourceFileReferenceContainer { return this.nodesInOther.getKeys(); } - *getReferencingNodesInOtherSourceFiles() { - for (const literal of this.getReferencingLiteralsInOtherSourceFiles()) { - const parent = literal.getParentOrThrow(); - const grandParent = parent.getParent(); - if (grandParent != null && TypeGuards.isImportEqualsDeclaration(grandParent)) - yield grandParent; - else - yield literal.getParentOrThrow() as SourceFileReferencingNodes; - } - } - refresh() { if (this.unresolvedLiterals.length > 0) this.sourceFile._context.compilerFactory.onSourceFileAdded(this.resolveUnresolved, false);