diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e1443215c91..b6472987398 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1398,6 +1398,9 @@ namespace ts { /** Returns its argument. */ export function identity(x: T) { return x; } + /** Returns lower case string */ + export function toLowerCase(x: string) { return x.toLowerCase(); } + /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); @@ -2877,9 +2880,7 @@ namespace ts { export type GetCanonicalFileName = (fileName: string) => string; export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames - ? ((fileName) => fileName) - : ((fileName) => fileName.toLowerCase()); + return useCaseSensitiveFileNames ? identity : toLowerCase; } /** @@ -3000,215 +3001,7 @@ namespace ts { export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty - export interface FileAndDirectoryExistence { - fileExists: boolean; - directoryExists: boolean; - } - - export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - /** Returns the queried result for the file exists and directory exists if at all it was done */ - addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; - addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; - clearCache(): void; - } - - interface MutableFileSystemEntries { - readonly files: string[]; - readonly directories: string[]; - } - - export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { - const cachedReadDirectoryResult = createMap(); - const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - return { - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - readFile: (path, encoding) => host.readFile(path, encoding), - writeFile, - fileExists, - directoryExists, - createDirectory, - getCurrentDirectory, - getDirectories, - readDirectory, - addOrDeleteFileOrDirectory, - addOrDeleteFile, - clearCache, - exit: code => host.exit(code) - }; - - function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); - } - - function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { - return cachedReadDirectoryResult.get(rootDirPath); - } - - function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { - return getCachedFileSystemEntries(getDirectoryPath(path)); - } - - function getBaseNameOfFileName(fileName: string) { - return getBaseFileName(normalizePath(fileName)); - } - - function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { - const resultFromHost: MutableFileSystemEntries = { - files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], - directories: host.getDirectories(rootDir) || [] - }; - - cachedReadDirectoryResult.set(rootDirPath, resultFromHost); - return resultFromHost; - } - - /** - * If the readDirectory result was already cached, it returns that - * Otherwise gets result from host and caches it. - * The host request is done under try catch block to avoid caching incorrect result - */ - function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { - const cachedResult = getCachedFileSystemEntries(rootDirPath); - if (cachedResult) { - return cachedResult; - } - - try { - return createCachedFileSystemEntries(rootDir, rootDirPath); - } - catch (_e) { - // If there is exception to read directories, dont cache the result and direct the calls to host - Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); - return undefined; - } - } - - function fileNameEqual(name1: string, name2: string) { - return getCanonicalFileName(name1) === getCanonicalFileName(name2); - } - - function hasEntry(entries: ReadonlyArray, name: string) { - return some(entries, file => fileNameEqual(file, name)); - } - - function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { - if (hasEntry(entries, baseName)) { - if (!isValid) { - return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); - } - } - else if (isValid) { - return entries.push(baseName); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - if (result) { - updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); - } - return host.writeFile(fileName, data, writeByteOrderMark); - } - - function fileExists(fileName: string): boolean { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || - host.fileExists(fileName); - } - - function directoryExists(dirPath: string): boolean { - const path = toPath(dirPath); - return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); - } - - function createDirectory(dirPath: string) { - const path = toPath(dirPath); - const result = getCachedFileSystemEntriesForBaseDir(path); - const baseFileName = getBaseNameOfFileName(dirPath); - if (result) { - updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); - } - host.createDirectory(dirPath); - } - - function getDirectories(rootDir: string): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return result.directories.slice(); - } - return host.getDirectories(rootDir); - } - - function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries); - } - return host.readDirectory(rootDir, extensions, excludes, includes, depth); - - function getFileSystemEntries(dir: string) { - const path = toPath(dir); - if (path === rootDirPath) { - return result; - } - return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); - } - } - - function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { - const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); - if (existingResult) { - // Just clear the cache for now - // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated - clearCache(); - } - else { - // This was earlier a file (hence not in cached directory contents) - // or we never cached the directory containing it - const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); - if (parentResult) { - const baseName = getBaseNameOfFileName(fileOrDirectory); - if (parentResult) { - const fsQueryResult: FileAndDirectoryExistence = { - fileExists: host.fileExists(fileOrDirectoryPath), - directoryExists: host.directoryExists(fileOrDirectoryPath) - }; - if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { - // Folder added or removed, clear the cache instead of updating the folder and its structure - clearCache(); - } - else { - // No need to update the directory structure, just files - updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); - } - return fsQueryResult; - } - } - } - } - - function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { - if (eventKind === FileWatcherEventKind.Changed) { - return; - } - - const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); - if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); - } - } - - function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { - updateFileSystemEntry(parentResult.files, baseName, fileExists); - } - - function clearCache() { - cachedReadDirectoryResult.clear(); - } + export function getBoundFunction(method: T | undefined, methodOf: {}): T | undefined { + return method && method.bind(methodOf); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9560859c7ce..9960b501d78 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -191,7 +191,6 @@ namespace ts { readFile: fileName => sys.readFile(fileName), trace: (s: string) => sys.write(s + newLine), directoryExists: directoryName => sys.directoryExists(directoryName), - getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "", getDirectories: (path: string) => sys.getDirectories(path), realpath }; diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 1c96dc21f08..92eea4140da 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -47,7 +47,7 @@ namespace ts { onInvalidatedResolution(): void; watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onChangedAutomaticTypeDirectiveNames(): void; - getCachedDirectoryStructureHost?(): CachedDirectoryStructureHost; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; projectName?: string; getGlobalCache?(): string | undefined; writeLog(s: string): void; @@ -87,6 +87,7 @@ namespace ts { const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); /** * These are the extensions that failed lookup files will have by default, @@ -467,9 +468,9 @@ namespace ts { function createDirectoryWatcher(directory: string, dirPath: Path) { return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process @@ -596,9 +597,9 @@ namespace ts { // Create new watch and recursive info return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c327bd608de..f744b9ab0eb 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,27 +30,14 @@ namespace ts { mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - export interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - - export interface System extends DirectoryStructureHost { - newLine: string; + export interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -58,7 +45,13 @@ namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -66,6 +59,7 @@ namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; /*@internal*/ getEnvironmentVariable(name: string): string; /*@internal*/ tryEnableSourceMapsForHost?(): void; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9a7e98d954c..daccfca33b8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -110,7 +110,7 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic); + const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic); udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0167bd50116..4b3c15df706 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4327,7 +4327,6 @@ namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index b5128be552e..0bd0fc499b7 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -33,34 +33,52 @@ namespace ts { }; } + /** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ + /*@internal*/ + export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; + } + + /** Parses config file using System interface */ + /*@internal*/ + export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) { + const host: ParseConfigFileHost = system; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(sys, reportDiagnostic, diagnostic); + const result = parseConfigFile(configFileName, optionsToExtend, host); + host.onConfigFileDiagnostic = undefined; + host.onUnRecoverableConfigFileDiagnostic = undefined; + return result; + } + /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ /*@internal*/ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { - configFileText = system.readFile(configFileName); + configFileText = host.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } const result = parseJsonText(configFileName, configFileText); - result.parseDiagnostics.forEach(reportDiagnostic); + result.parseDiagnostics.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); - const cwd = system.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - configParseResult.errors.forEach(reportDiagnostic); + const cwd = host.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + configParseResult.errors.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); return configParseResult; } @@ -90,7 +108,7 @@ namespace ts { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: system.createHash ? system.createHash.bind(system) : identity + computeHash: getBoundFunction(system.createHash, system) || identity }); return (host: DirectoryStructureHost, program: Program) => { @@ -173,8 +191,30 @@ namespace ts { // Sub set of compiler host methods to read and generate new program useCaseSensitiveFileNames(): boolean; getNewLine(): string; + getCurrentDirectory(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Symbol links resolution */ + realpath?(path: string): string; + + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } @@ -189,18 +229,36 @@ namespace ts { options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + export interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + /** * Host to create watch with config file */ - export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - // Reports errors in the config file - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } /** @@ -241,21 +299,39 @@ namespace ts { * Creates the watch compiler host that can be extended with config file or root file names and options host */ /*@internal*/ - export function createWatchCompilerHost(system = sys, reportDiagnostic?: DiagnosticReporter): WatchCompilerHost { + export function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { return { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, + getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + fileExists: getBoundFunction(system.fileExists, system), + readFile: getBoundFunction(system.readFile, system), + directoryExists: getBoundFunction(system.directoryExists, system), + getDirectories: getBoundFunction(system.getDirectories, system), + readDirectory: getBoundFunction(system.readDirectory, system), + realpath: getBoundFunction(system.realpath, system), system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; } + /** + * Report error and exit + */ + /*@internal*/ + export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + reportDiagnostic(diagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + /** * Create the watched program for config file */ export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - const host = createWatchCompilerHost(system) as WatchCompilerHostOfConfigFile; - host.onConfigFileDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); host.configFileName = configFileName; host.optionsToExtend = optionsToExtend; return createWatch(host); @@ -279,7 +355,7 @@ namespace ts { * Creates the watch from the host for config file */ export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -297,13 +373,30 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchCompilerHostOfConfigFile; - const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; - const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; - let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchCompilerHostOfConfigFile; + const system = host.system; + const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); + const currentDirectory = host.getCurrentDirectory(); + const getCurrentDirectory = () => currentDirectory; + const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); + const readFile = getBoundFunction(host.readFile, host); + const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; + const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = getBoundFunction(host.beforeProgramCreate, host) || noop; + const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = getBoundFunction(host.afterProgramCreate, host) || noop; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; + + const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; + const parseConfigFileHost: ParseConfigFileHost = { + useCaseSensitiveFileNames, + readDirectory: getBoundFunction(directoryStructureHost.readDirectory, directoryStructureHost), + fileExists: getBoundFunction(directoryStructureHost.fileExists, directoryStructureHost), + readFile, + getCurrentDirectory, + onConfigFileDiagnostic, + onUnRecoverableConfigFileDiagnostic: getBoundFunction(host.onUnRecoverableConfigFileDiagnostic, host) + }; // From tsc we want to get already parsed result and hence check for rootFileNames - const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName && !rootFileNames) { parseConfigFile(); } @@ -318,11 +411,7 @@ namespace ts { watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); - const realpath = system.realpath && ((path: string) => system.realpath(path)); - const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); - const useCaseSensitiveFileNames = memoize(() => host.useCaseSensitiveFileNames()); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); let newLine = updateNewLine(); const compilerHost: CompilerHost & ResolutionCacheHost = { @@ -333,23 +422,22 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: notImplemented, getCurrentDirectory, - useCaseSensitiveFileNames, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile: fileName => system.readFile(fileName), + readFile, trace: s => system.write(s + newLine), - directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: path => directoryStructureHost.getDirectories(path), - realpath, + directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), + getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), + realpath: getBoundFunction(host.realpath, host), onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation: watchDirectory, watchTypeRootsDirectory: watchDirectory, - getCachedDirectoryStructureHost, + getCachedDirectoryStructureHost: () => cachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, onChangedAutomaticTypeDirectiveNames: () => { hasChangedAutomaticTypeDirectiveNames = true; @@ -359,8 +447,8 @@ namespace ts { }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? - getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : - getCurrentDirectory(), + getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : + currentDirectory, /*logChangesWhenResolvingModule*/ false ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names @@ -442,7 +530,7 @@ namespace ts { } function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } function fileExists(fileName: string) { @@ -505,7 +593,7 @@ namespace ts { let text: string; try { performance.mark("beforeIORead"); - text = system.readFile(fileName, compilerOptions.charset); + text = host.readFile(fileName, compilerOptions.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -601,7 +689,7 @@ namespace ts { } function reloadFileNamesFromConfigFile() { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } @@ -615,8 +703,9 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); reloadLevel = ConfigFileProgramReloadLevel.None; - const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; - cachedHost.clearCache(); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.clearCache(); + } parseConfigFile(); hasChangedCompilerOptions = true; synchronizeProgram(); @@ -626,7 +715,7 @@ namespace ts { } function parseConfigFile() { - const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, directoryStructureHost as CachedDirectoryStructureHost, onConfigFileDiagnostic); + const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost); rootFileNames = configParseResult.fileNames; compilerOptions = configParseResult.options; configFileSpecs = configParseResult.configFileSpecs; @@ -662,8 +751,8 @@ namespace ts { } function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - if (configFileName) { - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, path, eventKind); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); } } @@ -712,7 +801,7 @@ namespace ts { const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - const result = (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + const result = cachedDirectoryStructureHost && cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); // Instead of deleting the file, mark it as changed instead // Many times node calls add/remove/file when watching directories recursively diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 0cf38f372d9..a1d22c20ac1 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -2,6 +2,247 @@ /* @internal */ namespace ts { + /** + * Partial interface of the System thats needed to support the caching of directory structure + */ + export interface DirectoryStructureHost { + fileExists(path: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + + directoryExists?(path: string): boolean; + getDirectories?(path: string): string[]; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + } + + interface FileAndDirectoryExistence { + fileExists: boolean; + directoryExists: boolean; + } + + export interface CachedDirectoryStructureHost extends DirectoryStructureHost { + useCaseSensitiveFileNames: boolean; + + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Returns the queried result for the file exists and directory exists if at all it was done */ + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; + addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; + clearCache(): void; + } + + interface MutableFileSystemEntries { + readonly files: string[]; + readonly directories: string[]; + } + + export function createCachedDirectoryStructureHost(host: DirectoryStructureHost, currentDirectory: string, useCaseSensitiveFileNames: boolean): CachedDirectoryStructureHost | undefined { + if (!host.getDirectories || !host.readDirectory) { + return undefined; + } + + const cachedReadDirectoryResult = createMap(); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return { + useCaseSensitiveFileNames, + fileExists, + readFile: getBoundFunction(host.readFile, host), + directoryExists: host.directoryExists && directoryExists, + getDirectories, + readDirectory, + createDirectory, + writeFile, + addOrDeleteFileOrDirectory, + addOrDeleteFile, + clearCache + }; + + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + + function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { + return cachedReadDirectoryResult.get(rootDirPath); + } + + function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { + return getCachedFileSystemEntries(getDirectoryPath(path)); + } + + function getBaseNameOfFileName(fileName: string) { + return getBaseFileName(normalizePath(fileName)); + } + + function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { + const resultFromHost: MutableFileSystemEntries = { + files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], + directories: host.getDirectories(rootDir) || [] + }; + + cachedReadDirectoryResult.set(rootDirPath, resultFromHost); + return resultFromHost; + } + + /** + * If the readDirectory result was already cached, it returns that + * Otherwise gets result from host and caches it. + * The host request is done under try catch block to avoid caching incorrect result + */ + function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { + const cachedResult = getCachedFileSystemEntries(rootDirPath); + if (cachedResult) { + return cachedResult; + } + + try { + return createCachedFileSystemEntries(rootDir, rootDirPath); + } + catch (_e) { + // If there is exception to read directories, dont cache the result and direct the calls to host + Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); + return undefined; + } + } + + function fileNameEqual(name1: string, name2: string) { + return getCanonicalFileName(name1) === getCanonicalFileName(name2); + } + + function hasEntry(entries: ReadonlyArray, name: string) { + return some(entries, file => fileNameEqual(file, name)); + } + + function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { + if (hasEntry(entries, baseName)) { + if (!isValid) { + return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); + } + } + else if (isValid) { + return entries.push(baseName); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + if (result) { + updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); + } + return host.writeFile(fileName, data, writeByteOrderMark); + } + + function fileExists(fileName: string): boolean { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || + host.fileExists(fileName); + } + + function directoryExists(dirPath: string): boolean { + const path = toPath(dirPath); + return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); + } + + function createDirectory(dirPath: string) { + const path = toPath(dirPath); + const result = getCachedFileSystemEntriesForBaseDir(path); + const baseFileName = getBaseNameOfFileName(dirPath); + if (result) { + updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + } + host.createDirectory(dirPath); + } + + function getDirectories(rootDir: string): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return result.directories.slice(); + } + return host.getDirectories(rootDir); + } + + function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries); + } + return host.readDirectory(rootDir, extensions, excludes, includes, depth); + + function getFileSystemEntries(dir: string) { + const path = toPath(dir); + if (path === rootDirPath) { + return result; + } + return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + } + } + + function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); + if (existingResult) { + // Just clear the cache for now + // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated + clearCache(); + return undefined; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); + if (!parentResult) { + return undefined; + } + + // This was earlier a file (hence not in cached directory contents) + // or we never cached the directory containing it + + if (!host.directoryExists) { + // Since host doesnt support directory exists, clear the cache as otherwise it might not be same + clearCache(); + return undefined; + } + + const baseName = getBaseNameOfFileName(fileOrDirectory); + const fsQueryResult: FileAndDirectoryExistence = { + fileExists: host.fileExists(fileOrDirectoryPath), + directoryExists: host.directoryExists(fileOrDirectoryPath) + }; + if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { + // Folder added or removed, clear the cache instead of updating the folder and its structure + clearCache(); + } + else { + // No need to update the directory structure, just files + updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); + } + return fsQueryResult; + + } + + function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { + if (eventKind === FileWatcherEventKind.Changed) { + return; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); + if (parentResult) { + updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); + } + } + + function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { + updateFileSystemEntry(parentResult.files, baseName, fileExists); + } + + function clearCache() { + cachedReadDirectoryResult.clear(); + } + } + export enum ConfigFileProgramReloadLevel { None, /** Update the file name list from the disk */ diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 92e40cc8f39..5ff1f08332c 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -903,7 +903,7 @@ namespace ts { function verifyProgramWithConfigFile(system: System, configFileName: string) { const program = createWatchOfConfigFile(configFileName, {}, system).getExistingProgram(); - const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); + const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..bafe90a59cb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1497,7 +1497,7 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath) { - const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host); + const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames); const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); diff --git a/src/server/project.ts b/src/server/project.ts index 40ddaeb2a19..9ae1b1d0b6e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -334,7 +334,7 @@ namespace ts.server { } useCaseSensitiveFileNames() { - return this.directoryStructureHost.useCaseSensitiveFileNames; + return this.projectService.host.useCaseSensitiveFileNames; } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { @@ -342,7 +342,7 @@ namespace ts.server { } readFile(fileName: string): string | undefined { - return this.directoryStructureHost.readFile(fileName); + return this.projectService.host.readFile(fileName); } fileExists(file: string): boolean { @@ -368,6 +368,11 @@ namespace ts.server { return this.directoryStructureHost.getDirectories(path); } + /*@internal*/ + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost { + return undefined; + } + /*@internal*/ toPath(fileName: string) { return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName); @@ -877,7 +882,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..329d403195d 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -313,7 +313,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + p.getCachedDirectoryStructureHost().addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f0f9318e687..64f595664b3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5026237f30e..c74fc46d60e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; @@ -3829,7 +3823,26 @@ declare namespace ts { afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + getCurrentDirectory(): string; + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + /** Symbol links resolution */ + realpath?(path: string): string; + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -3841,15 +3854,32 @@ declare namespace ts { /** Compiler options */ options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } /** * Host to create watch with config file */ - interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } interface Watch { /** Synchronize with host and get updated program */