Skip to content

Commit

Permalink
feat: #522 - Project should not return implicitly resolved files and …
Browse files Browse the repository at this point in the history
…directories in most scenarios.

BREAKING CHANGE: Implicitly resolved files and directories are no longer returned when calling `project.getSourceFiles()` or `project.getDirectories()`. They can be added by calling `project.addExistingSourceFiles(...)`-like methods or `project.addExistingDirectory(...)`. These source files and directories are still accessible when specifying their path though (ex. `project.getSourceFile("node_modules/typescript/lib/typescript.d.ts")`)
  • Loading branch information
dsherret committed Jan 8, 2019
1 parent a12a92c commit 73c5a39
Show file tree
Hide file tree
Showing 16 changed files with 799 additions and 151 deletions.
4 changes: 2 additions & 2 deletions docs/details/source-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ Note: This does not delete the file from the file system. To do delete it, call

### Referenced files

This returns any files that are referenced via `/// <reference path="..." />` statements:
This returns any files that are referenced via `/// <reference path="..." />` comments:

```ts
const referencedFiles = sourceFile.getReferencedFiles();
```

### Type reference directives

This returns any files that are referenced via `/// <reference types="..." />` statements:
This returns any files that are referenced via `/// <reference types="..." />` comments:

```ts
const typeReferenceDirectives = sourceFile.getTypeReferenceDirectives();
Expand Down
80 changes: 47 additions & 33 deletions src/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CompilerOptionsContainer, ManipulationSettings, ManipulationSettingsCon
import { SourceFileStructure } from "./structures";
import { WriterFunction } from "./types";
import { ts, CompilerOptions } from "./typescript";
import { ArrayUtils, FileUtils, matchGlobs, TsConfigResolver, getTextFromStringOrWriter } from "./utils";
import { ArrayUtils, FileUtils, matchGlobs, TsConfigResolver } from "./utils";

export interface ProjectOptions {
/** Compiler options */
Expand Down Expand Up @@ -117,13 +117,16 @@ export class Project {
resolveSourceFileDependencies() {
const sourceFiles: SourceFile[] = [];
const onSourceFileAdded = (sourceFile: SourceFile) => sourceFiles.push(sourceFile);
this._context.compilerFactory.onSourceFileAdded(onSourceFileAdded);
const { compilerFactory, inProjectCoordinator } = this._context;

compilerFactory.onSourceFileAdded(onSourceFileAdded);

try {
this.getProgram().compilerObject; // create the program
inProjectCoordinator.markAllSourceFilesAsInProject();
return sourceFiles;
} finally {
this._context.compilerFactory.onSourceFileAdded(onSourceFileAdded, false); // unsubscribe
compilerFactory.onSourceFileAdded(onSourceFileAdded, false); // unsubscribe
}
}

Expand All @@ -137,7 +140,8 @@ export class Project {
*/
addExistingDirectoryIfExists(dirPath: string, options: DirectoryAddOptions = {}): Directory | undefined {
dirPath = this._context.fileSystemWrapper.getStandardizedAbsolutePath(dirPath);
return this._context.directoryCoordinator.addExistingDirectoryIfExists(dirPath, options);
return this._context.directoryCoordinator.addExistingDirectoryIfExists(dirPath,
{ ...options, markInProject: true });
}

/**
Expand All @@ -150,7 +154,8 @@ export class Project {
*/
addExistingDirectory(dirPath: string, options: DirectoryAddOptions = {}): Directory {
dirPath = this._context.fileSystemWrapper.getStandardizedAbsolutePath(dirPath);
return this._context.directoryCoordinator.addExistingDirectory(dirPath, options);
return this._context.directoryCoordinator.addExistingDirectory(dirPath,
{ ...options, markInProject: true });
}

/**
Expand All @@ -159,7 +164,7 @@ export class Project {
*/
createDirectory(dirPath: string): Directory {
dirPath = this._context.fileSystemWrapper.getStandardizedAbsolutePath(dirPath);
return this._context.directoryCoordinator.createDirectoryOrAddIfExists(dirPath);
return this._context.directoryCoordinator.createDirectoryOrAddIfExists(dirPath, { markInProject: true });
}

/**
Expand All @@ -177,14 +182,16 @@ export class Project {
*/
getDirectory(dirPath: string): Directory | undefined {
dirPath = this._context.fileSystemWrapper.getStandardizedAbsolutePath(dirPath);
return this._context.compilerFactory.getDirectoryFromCache(dirPath);
const { compilerFactory } = this._context;
// when a directory path is specified, even return directories not in the project
return compilerFactory.getDirectoryFromCache(dirPath);
}

/**
* Gets all the directories.
*/
getDirectories() {
return ArrayUtils.from(this._context.compilerFactory.getDirectoriesByDepth());
return ArrayUtils.from(this._getProjectDirectoriesByDirectoryDepth());
}

/**
Expand All @@ -200,22 +207,7 @@ export class Project {
* @returns The matched source files.
*/
addExistingSourceFiles(fileGlobs: string | ReadonlyArray<string>): SourceFile[] {
if (typeof fileGlobs === "string")
fileGlobs = [fileGlobs];

const sourceFiles: SourceFile[] = [];
const globbedDirectories = FileUtils.getParentMostPaths(fileGlobs.filter(g => !FileUtils.isNegatedGlob(g)).map(g => FileUtils.getGlobDir(g)));

for (const filePath of this._context.fileSystemWrapper.glob(fileGlobs)) {
const sourceFile = this.addExistingSourceFileIfExists(filePath);
if (sourceFile != null)
sourceFiles.push(sourceFile);
}

for (const dirPath of globbedDirectories)
this.addExistingDirectoryIfExists(dirPath, { recursive: true });

return sourceFiles;
return this._context.directoryCoordinator.addExistingSourceFiles(fileGlobs, { markInProject: true });
}

/**
Expand All @@ -226,7 +218,7 @@ export class Project {
* @skipOrThrowCheck
*/
addExistingSourceFileIfExists(filePath: string): SourceFile | undefined {
return this._context.directoryCoordinator.addExistingSourceFileIfExists(filePath);
return this._context.directoryCoordinator.addExistingSourceFileIfExists(filePath, { markInProject: true });
}

/**
Expand All @@ -237,7 +229,7 @@ export class Project {
* @throws FileNotFoundError when the file is not found.
*/
addExistingSourceFile(filePath: string): SourceFile {
return this._context.directoryCoordinator.addExistingSourceFile(filePath);
return this._context.directoryCoordinator.addExistingSourceFile(filePath, { markInProject: true });
}

/**
Expand Down Expand Up @@ -273,7 +265,8 @@ export class Project {
* @throws - InvalidOperationError if a source file already exists at the provided file path.
*/
createSourceFile(filePath: string, sourceFileText?: string | SourceFileStructure | WriterFunction, options?: SourceFileCreateOptions): SourceFile {
return this._context.compilerFactory.createSourceFile(filePath, sourceFileText || "", options || {});
return this._context.compilerFactory.createSourceFile(filePath, sourceFileText || "",
{ ...(options || {}), markInProject: true });
}

/**
Expand Down Expand Up @@ -325,9 +318,12 @@ export class Project {
getSourceFile(fileNameOrSearchFunction: string | ((file: SourceFile) => boolean)): SourceFile | undefined {
const filePathOrSearchFunction = getFilePathOrSearchFunction(this._context.fileSystemWrapper);

if (typeof filePathOrSearchFunction === "string")
if (typeof filePathOrSearchFunction === "string") {
// when a file path is specified, return even source files not in the project
return this._context.compilerFactory.getSourceFileFromCacheFromFilePath(filePathOrSearchFunction);
return ArrayUtils.find(this._context.compilerFactory.getSourceFilesByDirectoryDepth(), filePathOrSearchFunction);
}

return ArrayUtils.find(this._getProjectSourceFilesByDirectoryDepth(), filePathOrSearchFunction);

function getFilePathOrSearchFunction(fileSystemWrapper: FileSystemWrapper): string | ((file: SourceFile) => boolean) {
if (fileNameOrSearchFunction instanceof Function)
Expand All @@ -342,23 +338,23 @@ export class Project {
}

/**
* Gets all the source files contained in the compiler wrapper.
* Gets all the source files added to the project.
* @param globPattern - Glob pattern for filtering out the source files.
*/
getSourceFiles(): SourceFile[];
/**
* Gets all the source files contained in the compiler wrapper that match a pattern.
* Gets all the source files added to the project that match a pattern.
* @param globPattern - Glob pattern for filtering out the source files.
*/
getSourceFiles(globPattern: string): SourceFile[];
/**
* Gets all the source files contained in the compiler wrapper that match the passed in patterns.
* Gets all the source files added to the project that match the passed in patterns.
* @param globPatterns - Glob patterns for filtering out the source files.
*/
getSourceFiles(globPatterns: ReadonlyArray<string>): SourceFile[];
getSourceFiles(globPatterns?: string | ReadonlyArray<string>): SourceFile[] {
const { compilerFactory, fileSystemWrapper } = this._context;
const sourceFiles = this._context.compilerFactory.getSourceFilesByDirectoryDepth();
const sourceFiles = this._getProjectSourceFilesByDirectoryDepth();

if (typeof globPatterns === "string" || globPatterns instanceof Array)
return ArrayUtils.from(getFilteredSourceFiles());
Expand All @@ -379,6 +375,24 @@ export class Project {
}
}

/** @internal */
private *_getProjectSourceFilesByDirectoryDepth() {
const { compilerFactory, inProjectCoordinator } = this._context;
for (const sourceFile of compilerFactory.getSourceFilesByDirectoryDepth()) {
if (inProjectCoordinator.isSourceFileInProject(sourceFile))
yield sourceFile;
}
}

/** @internal */
private *_getProjectDirectoriesByDirectoryDepth() {
const { compilerFactory, inProjectCoordinator } = this._context;
for (const directory of compilerFactory.getDirectoriesByDepth()) {
if (inProjectCoordinator.isDirectoryInProject(directory))
yield directory;
}
}

/**
* Gets the specified ambient module symbol or returns undefined if not found.
* @param moduleName - The ambient module name with or without quotes.
Expand Down
4 changes: 3 additions & 1 deletion src/ProjectContext.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CodeBlockWriter } from "./codeBlockWriter";
import { LanguageService, QuoteKind, TypeChecker, SourceFile, Diagnostic } from "./compiler";
import * as errors from "./errors";
import { CompilerFactory, StructurePrinterFactory } from "./factories";
import { CompilerFactory, StructurePrinterFactory, InProjectCoordinator } from "./factories";
import { DirectoryCoordinator, FileSystemWrapper } from "./fileSystem";
import { CompilerOptionsContainer, IndentationText, ManipulationSettingsContainer } from "./options";
import { CompilerOptions, ts } from "./typescript";
Expand Down Expand Up @@ -32,11 +32,13 @@ export class ProjectContext {
readonly manipulationSettings = new ManipulationSettingsContainer();
readonly structurePrinterFactory: StructurePrinterFactory;
readonly compilerFactory: CompilerFactory;
readonly inProjectCoordinator: InProjectCoordinator;

constructor(fileSystemWrapper: FileSystemWrapper, compilerOptions: CompilerOptions, opts: ProjectContextOptions) {
this.fileSystemWrapper = fileSystemWrapper;
this._compilerOptions.set(compilerOptions);
this.compilerFactory = new CompilerFactory(this);
this.inProjectCoordinator = new InProjectCoordinator(this.compilerFactory);
this.structurePrinterFactory = new StructurePrinterFactory(() => this.manipulationSettings.getFormatCodeSettings());
this.lazyReferenceCoordinator = new LazyReferenceCoordinator(this.compilerFactory);
this.directoryCoordinator = new DirectoryCoordinator(this.compilerFactory, fileSystemWrapper);
Expand Down
43 changes: 34 additions & 9 deletions src/compiler/ast/module/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
/** @internal */
_copyInternal(filePath: string, options: SourceFileCopyOptions = {}) {
const { overwrite = false } = options;
filePath = this._context.fileSystemWrapper.getStandardizedAbsolutePath(filePath, this.getDirectoryPath());
const { compilerFactory, fileSystemWrapper } = this._context;
filePath = fileSystemWrapper.getStandardizedAbsolutePath(filePath, this.getDirectoryPath());

if (filePath === this.getFilePath())
return false;
Expand All @@ -203,13 +204,21 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {

function getCopiedSourceFile(currentFile: SourceFile) {
try {
return currentFile._context.compilerFactory.createSourceFileFromText(filePath, currentFile.getFullText(), { overwrite });
return compilerFactory.createSourceFileFromText(filePath, currentFile.getFullText(),
{ overwrite, markInProject: getShouldBeInProject() });
} catch (err) {
if (err instanceof errors.InvalidOperationError)
throw new errors.InvalidOperationError(`Did you mean to provide the overwrite option? ` + err.message);
else
throw err;
}

function getShouldBeInProject() {
if (currentFile._isInProject())
return true;
const destinationFile = compilerFactory.getSourceFileFromCacheFromFilePath(filePath);
return destinationFile != null && destinationFile._isInProject();
}
}
}

Expand Down Expand Up @@ -299,11 +308,14 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
if (filePath === this.getFilePath())
return false;

let markAsInProject = false;
if (overwrite) {
// remove the past file if it exists
const existingSourceFile = this._context.compilerFactory.getSourceFileFromCacheFromFilePath(filePath);
if (existingSourceFile != null)
if (existingSourceFile != null) {
markAsInProject = existingSourceFile._isInProject();
existingSourceFile.forget();
}
}
else
this._context.compilerFactory.throwIfFileExists(filePath, "Did you mean to provide the overwrite option?");
Expand All @@ -313,6 +325,11 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
sourceFile: this
});

if (markAsInProject)
this._markAsInProject();
if (this._isInProject())
this.getDirectory()._markAsInProject();

return true;
}

Expand Down Expand Up @@ -427,24 +444,22 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}

/**
* Gets any referenced files.
* Gets any source files referenced via `/// <reference path="..." />` comments.
*/
getReferencedFiles() {
// todo: add tests
const dirPath = this.getDirectoryPath();
return (this.compilerNode.referencedFiles || [])
.map(f => this._context.compilerFactory.addOrGetSourceFileFromFilePath(FileUtils.pathJoin(dirPath, f.fileName)))
.map(f => this._context.compilerFactory.addOrGetSourceFileFromFilePath(FileUtils.pathJoin(dirPath, f.fileName), { markInProject: false }))
.filter(f => f != null) as SourceFile[];
}

/**
* Gets the source files for any type reference directives.
* Gets any source files referenced via `/// <reference types="..." />` comments.
*/
getTypeReferenceDirectives() {
// todo: add tests
const dirPath = this.getDirectoryPath();
return (this.compilerNode.typeReferenceDirectives || [])
.map(f => this._context.compilerFactory.addOrGetSourceFileFromFilePath(FileUtils.pathJoin(dirPath, f.fileName)))
.map(f => this._context.compilerFactory.addOrGetSourceFileFromFilePath(FileUtils.pathJoin(dirPath, f.fileName), { markInProject: false }))
.filter(f => f != null) as SourceFile[];
}

Expand Down Expand Up @@ -831,6 +846,16 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
this._setIsSaved(true); // saved when loaded from file system
return FileSystemRefreshResult.Updated;
}

/** @internal */
_isInProject() {
return this._context.inProjectCoordinator.isSourceFileInProject(this);
}

/** @internal */
_markAsInProject() {
this._context.inProjectCoordinator.markSourceFileAsInProject(this);
}
}

function updateStringLiteralReferences(nodeReferences: ReadonlyArray<[StringLiteral, SourceFile]>) {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class LanguageService {
getScriptSnapshot: fileName => {
if (!fileExistsSync(fileName))
return undefined;
return ts.ScriptSnapshot.fromString(this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName)!.getFullText());
return ts.ScriptSnapshot.fromString(this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false })!.getFullText());
},
getCurrentDirectory: () => context.fileSystemWrapper.getCurrentDirectory(),
getDefaultLibFileName: options => {
Expand All @@ -65,7 +65,7 @@ export class LanguageService {

this._compilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) => {
const sourceFile = this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName);
const sourceFile = this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false });
return sourceFile == null ? undefined : sourceFile.compilerNode;
},
// getSourceFileByPath: (...) => {}, // not providing these will force it to use the file name as the file path
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tools/results/Diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Diagnostic<TCompilerObject extends ts.Diagnostic = ts.Diagnostic> {
if (this._context == null)
return undefined;
const file = this.compilerObject.file;
return file == null ? undefined : this._context.compilerFactory.getSourceFile(file);
return file == null ? undefined : this._context.compilerFactory.getSourceFile(file, { markInProject: false });
}

/**
Expand Down

0 comments on commit 73c5a39

Please sign in to comment.