Skip to content

Commit

Permalink
feat: #680 - Add SourceFile#getReferencedSourceFiles(), `#getNodesR…
Browse files Browse the repository at this point in the history
…eferencingOtherSourceFiles()`, and `#getLiteralsReferencingOtherSourceFiles()`.
  • Loading branch information
dsherret committed Sep 2, 2019
1 parent 578adc7 commit c245acc
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 16 deletions.
30 changes: 29 additions & 1 deletion docs/details/source-files.md
Expand Up @@ -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.
Expand Down
17 changes: 16 additions & 1 deletion lib/ts-morph.d.ts
Expand Up @@ -8221,7 +8221,22 @@ export declare class SourceFile extends SourceFileBase<ts.SourceFile> {
*/
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[];
/**
Expand Down
64 changes: 61 additions & 3 deletions src/compiler/ast/module/SourceFile.ts
Expand Up @@ -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";
Expand Down Expand Up @@ -514,7 +515,13 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
* 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<SourceFileReferencingNodes> {
for (const literal of literals)
yield getReferencingNodeFromStringLiteral(literal);
}
}

/**
Expand All @@ -525,7 +532,49 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}

/**
* 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<SourceFile>(getSourceFilesFromEntries()).values());

function* getSourceFilesFromEntries(): Iterable<SourceFile> {
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<SourceFileReferencingNodes> {
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<StringLiteral> {
for (const [literal] of entries)
yield literal;
}
}

/**
* Gets all the descendant string literals that reference a module.
*/
getImportStringLiterals() {
this._ensureBound();
Expand Down Expand Up @@ -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;
}
66 changes: 66 additions & 0 deletions src/tests/compiler/ast/module/sourceFileTests.ts
Expand Up @@ -1246,6 +1246,72 @@ function myFunction(param: MyClass) {
});
});

describe(nameof<SourceFile>(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<SourceFile>(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<SourceFile>(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<SourceFile>(s => s.getExtension), () => {
function doTest(filePath: string, extension: string) {
const { sourceFile } = getInfoFromText("", { filePath });
Expand Down
11 changes: 0 additions & 11 deletions src/utils/references/SourceFileReferenceContainer.ts
Expand Up @@ -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);
Expand Down

0 comments on commit c245acc

Please sign in to comment.