Merge pull request #17669 from Microsoft/builder
Improvements to tsc --watch
This commit is contained in:
commit
9e570c375b
|
@ -91,6 +91,7 @@ var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "
|
||||||
var harnessCoreSources = [
|
var harnessCoreSources = [
|
||||||
"harness.ts",
|
"harness.ts",
|
||||||
"virtualFileSystem.ts",
|
"virtualFileSystem.ts",
|
||||||
|
"virtualFileSystemWithWatch.ts",
|
||||||
"sourceMapRecorder.ts",
|
"sourceMapRecorder.ts",
|
||||||
"harnessLanguageService.ts",
|
"harnessLanguageService.ts",
|
||||||
"fourslash.ts",
|
"fourslash.ts",
|
||||||
|
@ -128,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([
|
||||||
"convertCompilerOptionsFromJson.ts",
|
"convertCompilerOptionsFromJson.ts",
|
||||||
"convertTypeAcquisitionFromJson.ts",
|
"convertTypeAcquisitionFromJson.ts",
|
||||||
"tsserverProjectSystem.ts",
|
"tsserverProjectSystem.ts",
|
||||||
|
"tscWatchMode.ts",
|
||||||
"compileOnSave.ts",
|
"compileOnSave.ts",
|
||||||
"typingsInstaller.ts",
|
"typingsInstaller.ts",
|
||||||
"projectErrors.ts",
|
"projectErrors.ts",
|
||||||
|
|
492
src/compiler/builder.ts
Normal file
492
src/compiler/builder.ts
Normal file
|
@ -0,0 +1,492 @@
|
||||||
|
/// <reference path="program.ts" />
|
||||||
|
|
||||||
|
namespace ts {
|
||||||
|
export interface EmitOutput {
|
||||||
|
outputFiles: OutputFile[];
|
||||||
|
emitSkipped: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmitOutputDetailed extends EmitOutput {
|
||||||
|
diagnostics: Diagnostic[];
|
||||||
|
sourceMaps: SourceMapData[];
|
||||||
|
emittedSourceFiles: SourceFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OutputFile {
|
||||||
|
name: string;
|
||||||
|
writeByteOrderMark: boolean;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChangedProgramFiles {
|
||||||
|
/** Minimal set of list of files that require emit */
|
||||||
|
readonly filesToEmit: ReadonlyArray<string>;
|
||||||
|
/** File paths of source files changed/added/removed or affected by changed files */
|
||||||
|
readonly changedFiles: ReadonlyArray<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Builder {
|
||||||
|
/**
|
||||||
|
* This is the callback when file infos in the builder are updated
|
||||||
|
*/
|
||||||
|
onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution): void;
|
||||||
|
getFilesAffectedBy(program: Program, path: Path): string[];
|
||||||
|
emitFile(program: Program, path: Path): EmitOutput;
|
||||||
|
|
||||||
|
/** Emit the changed files and clear the cache of the changed files */
|
||||||
|
emitChangedFiles(program: Program): EmitOutputDetailed[];
|
||||||
|
/** Get the changed files since last query and then clear the cache of changed files */
|
||||||
|
getChangedProgramFiles(program: Program): ChangedProgramFiles;
|
||||||
|
/** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */
|
||||||
|
getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[];
|
||||||
|
|
||||||
|
/** Called to reset the status of the builder */
|
||||||
|
clear(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmitHandler {
|
||||||
|
addScriptInfo(program: Program, sourceFile: SourceFile): void;
|
||||||
|
removeScriptInfo(path: Path): void;
|
||||||
|
updateScriptInfo(program: Program, sourceFile: SourceFile): void;
|
||||||
|
/**
|
||||||
|
* Gets the files affected by the script info which has updated shape from the known one
|
||||||
|
*/
|
||||||
|
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean,
|
||||||
|
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed {
|
||||||
|
const outputFiles: OutputFile[] = [];
|
||||||
|
let emittedSourceFiles: SourceFile[];
|
||||||
|
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||||
|
if (!isDetailed) {
|
||||||
|
return { outputFiles, emitSkipped: emitResult.emitSkipped };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
outputFiles,
|
||||||
|
emitSkipped: emitResult.emitSkipped,
|
||||||
|
diagnostics: emitResult.diagnostics,
|
||||||
|
sourceMaps: emitResult.sourceMaps,
|
||||||
|
emittedSourceFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) {
|
||||||
|
outputFiles.push({ name: fileName, writeByteOrderMark, text });
|
||||||
|
if (isDetailed) {
|
||||||
|
emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBuilder(
|
||||||
|
getCanonicalFileName: (fileName: string) => string,
|
||||||
|
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed,
|
||||||
|
computeHash: (data: string) => string,
|
||||||
|
shouldEmitFile: (sourceFile: SourceFile) => boolean
|
||||||
|
): Builder {
|
||||||
|
let isModuleEmit: boolean | undefined;
|
||||||
|
// Last checked shape signature for the file info
|
||||||
|
type FileInfo = { fileName: string; version: string; signature: string; };
|
||||||
|
const fileInfos = createMap<FileInfo>();
|
||||||
|
const semanticDiagnosticsPerFile = createMap<Diagnostic[]>();
|
||||||
|
/** The map has key by source file's path that has been changed */
|
||||||
|
const changedFileNames = createMap<string>();
|
||||||
|
let emitHandler: EmitHandler;
|
||||||
|
return {
|
||||||
|
onProgramUpdateGraph,
|
||||||
|
getFilesAffectedBy,
|
||||||
|
emitFile,
|
||||||
|
emitChangedFiles,
|
||||||
|
getChangedProgramFiles,
|
||||||
|
getSemanticDiagnostics,
|
||||||
|
clear
|
||||||
|
};
|
||||||
|
|
||||||
|
function createProgramGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||||
|
const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None;
|
||||||
|
if (isModuleEmit !== currentIsModuleEmit) {
|
||||||
|
isModuleEmit = currentIsModuleEmit;
|
||||||
|
emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler();
|
||||||
|
fileInfos.clear();
|
||||||
|
semanticDiagnosticsPerFile.clear();
|
||||||
|
}
|
||||||
|
mutateMap(
|
||||||
|
fileInfos,
|
||||||
|
arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path),
|
||||||
|
{
|
||||||
|
// Add new file info
|
||||||
|
createNewValue: (_path, sourceFile) => addNewFileInfo(program, sourceFile),
|
||||||
|
// Remove existing file info
|
||||||
|
onDeleteValue: removeExistingFileInfo,
|
||||||
|
// We will update in place instead of deleting existing value and adding new one
|
||||||
|
onExistingValue: (_key, existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerChangedFile(path: Path, fileName: string) {
|
||||||
|
changedFileNames.set(path, fileName);
|
||||||
|
// All changed files need to re-evaluate its semantic diagnostics
|
||||||
|
semanticDiagnosticsPerFile.delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo {
|
||||||
|
registerChangedFile(sourceFile.path, sourceFile.fileName);
|
||||||
|
emitHandler.addScriptInfo(program, sourceFile);
|
||||||
|
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeExistingFileInfo(path: Path, existingFileInfo: FileInfo) {
|
||||||
|
registerChangedFile(path, existingFileInfo.fileName);
|
||||||
|
emitHandler.removeScriptInfo(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||||
|
if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) {
|
||||||
|
registerChangedFile(sourceFile.path, sourceFile.fileName);
|
||||||
|
existingInfo.version = sourceFile.version;
|
||||||
|
emitHandler.updateScriptInfo(program, sourceFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureProgramGraph(program: Program) {
|
||||||
|
if (!emitHandler) {
|
||||||
|
createProgramGraph(program, returnFalse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||||
|
if (emitHandler) {
|
||||||
|
createProgramGraph(program, hasInvalidatedResolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesAffectedBy(program: Program, path: Path): string[] {
|
||||||
|
ensureProgramGraph(program);
|
||||||
|
|
||||||
|
const sourceFile = program.getSourceFile(path);
|
||||||
|
const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
|
||||||
|
const info = fileInfos.get(path);
|
||||||
|
if (!info || !updateShapeSignature(program, sourceFile, info)) {
|
||||||
|
return singleFileResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.assert(!!sourceFile);
|
||||||
|
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitFile(program: Program, path: Path) {
|
||||||
|
ensureProgramGraph(program);
|
||||||
|
if (!fileInfos.has(path)) {
|
||||||
|
return { outputFiles: [], emitSkipped: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumerateChangedFilesSet(
|
||||||
|
program: Program,
|
||||||
|
onChangedFile: (fileName: string, path: Path) => void,
|
||||||
|
onAffectedFile: (fileName: string, sourceFile: SourceFile) => void
|
||||||
|
) {
|
||||||
|
changedFileNames.forEach((fileName, path) => {
|
||||||
|
onChangedFile(fileName, path as Path);
|
||||||
|
const affectedFiles = getFilesAffectedBy(program, path as Path);
|
||||||
|
for (const file of affectedFiles) {
|
||||||
|
onAffectedFile(file, program.getSourceFile(file));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumerateChangedFilesEmitOutput(
|
||||||
|
program: Program,
|
||||||
|
emitOnlyDtsFiles: boolean,
|
||||||
|
onChangedFile: (fileName: string, path: Path) => void,
|
||||||
|
onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void
|
||||||
|
) {
|
||||||
|
const seenFiles = createMap<SourceFile>();
|
||||||
|
enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => {
|
||||||
|
if (!seenFiles.has(fileName)) {
|
||||||
|
seenFiles.set(fileName, sourceFile);
|
||||||
|
if (sourceFile) {
|
||||||
|
// Any affected file shouldnt have the cached diagnostics
|
||||||
|
semanticDiagnosticsPerFile.delete(sourceFile.path);
|
||||||
|
|
||||||
|
const emitOutput = getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
|
||||||
|
onEmitOutput(emitOutput, sourceFile);
|
||||||
|
|
||||||
|
// mark all the emitted source files as seen
|
||||||
|
if (emitOutput.emittedSourceFiles) {
|
||||||
|
for (const file of emitOutput.emittedSourceFiles) {
|
||||||
|
seenFiles.set(file.fileName, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChangedFiles(program: Program): EmitOutputDetailed[] {
|
||||||
|
ensureProgramGraph(program);
|
||||||
|
const result: EmitOutputDetailed[] = [];
|
||||||
|
enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false,
|
||||||
|
/*onChangedFile*/ noop, emitOutput => result.push(emitOutput));
|
||||||
|
changedFileNames.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] {
|
||||||
|
ensureProgramGraph(program);
|
||||||
|
|
||||||
|
// Ensure that changed files have cleared their respective
|
||||||
|
enumerateChangedFilesSet(program, /*onChangedFile*/ noop, (_affectedFileName, sourceFile) => {
|
||||||
|
if (sourceFile) {
|
||||||
|
semanticDiagnosticsPerFile.delete(sourceFile.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let diagnostics: Diagnostic[];
|
||||||
|
for (const sourceFile of program.getSourceFiles()) {
|
||||||
|
const path = sourceFile.path;
|
||||||
|
const cachedDiagnostics = semanticDiagnosticsPerFile.get(path);
|
||||||
|
// Report the semantic diagnostics from the cache if we already have those diagnostics present
|
||||||
|
if (cachedDiagnostics) {
|
||||||
|
diagnostics = concatenate(diagnostics, cachedDiagnostics);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Diagnostics werent cached, get them from program, and cache the result
|
||||||
|
const cachedDiagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||||
|
semanticDiagnosticsPerFile.set(path, cachedDiagnostics);
|
||||||
|
diagnostics = concatenate(diagnostics, cachedDiagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diagnostics || emptyArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChangedProgramFiles(program: Program): ChangedProgramFiles {
|
||||||
|
ensureProgramGraph(program);
|
||||||
|
|
||||||
|
let filesToEmit: string[];
|
||||||
|
const changedFiles = createMap<string>();
|
||||||
|
enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ true,
|
||||||
|
// All the changed files are required to get diagnostics
|
||||||
|
(changedFileName, changedFilePath) => addFileForDiagnostics(changedFileName, changedFilePath),
|
||||||
|
// Emitted file is for emit as well as diagnostic
|
||||||
|
(_emitOutput, sourceFile) => {
|
||||||
|
(filesToEmit || (filesToEmit = [])).push(sourceFile.fileName);
|
||||||
|
addFileForDiagnostics(sourceFile.fileName, sourceFile.path);
|
||||||
|
});
|
||||||
|
changedFileNames.clear();
|
||||||
|
return {
|
||||||
|
filesToEmit: filesToEmit || emptyArray,
|
||||||
|
changedFiles: arrayFrom(changedFiles.values())
|
||||||
|
};
|
||||||
|
|
||||||
|
function addFileForDiagnostics(fileName: string, path: Path) {
|
||||||
|
changedFiles.set(path, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
isModuleEmit = undefined;
|
||||||
|
emitHandler = undefined;
|
||||||
|
fileInfos.clear();
|
||||||
|
semanticDiagnosticsPerFile.clear();
|
||||||
|
changedFileNames.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For script files that contains only ambient external modules, although they are not actually external module files,
|
||||||
|
* they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
|
||||||
|
* there are no point to rebuild all script files if these special files have changed. However, if any statement
|
||||||
|
* in the file is not ambient external module, we treat it as a regular script file.
|
||||||
|
*/
|
||||||
|
function containsOnlyAmbientModules(sourceFile: SourceFile) {
|
||||||
|
for (const statement of sourceFile.statements) {
|
||||||
|
if (!isModuleWithStringLiteralName(statement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} indicates if the shape signature has changed since last update.
|
||||||
|
*/
|
||||||
|
function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) {
|
||||||
|
const prevSignature = info.signature;
|
||||||
|
let latestSignature: string;
|
||||||
|
if (sourceFile.isDeclarationFile) {
|
||||||
|
latestSignature = computeHash(sourceFile.text);
|
||||||
|
info.signature = latestSignature;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
|
||||||
|
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||||
|
latestSignature = computeHash(emitOutput.outputFiles[0].text);
|
||||||
|
info.signature = latestSignature;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
latestSignature = prevSignature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !prevSignature || latestSignature !== prevSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
|
||||||
|
*/
|
||||||
|
function getReferencedFiles(program: Program, sourceFile: SourceFile): Map<true> {
|
||||||
|
const referencedFiles = createMap<true>();
|
||||||
|
|
||||||
|
// We need to use a set here since the code can contain the same import twice,
|
||||||
|
// but that will only be one dependency.
|
||||||
|
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
||||||
|
if (sourceFile.imports && sourceFile.imports.length > 0) {
|
||||||
|
const checker: TypeChecker = program.getTypeChecker();
|
||||||
|
for (const importName of sourceFile.imports) {
|
||||||
|
const symbol = checker.getSymbolAtLocation(importName);
|
||||||
|
if (symbol && symbol.declarations && symbol.declarations[0]) {
|
||||||
|
const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
|
||||||
|
if (declarationSourceFile) {
|
||||||
|
referencedFiles.set(declarationSourceFile.path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFileDirectory = getDirectoryPath(sourceFile.path);
|
||||||
|
// Handle triple slash references
|
||||||
|
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
|
||||||
|
for (const referencedFile of sourceFile.referencedFiles) {
|
||||||
|
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
|
||||||
|
referencedFiles.set(referencedPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle type reference directives
|
||||||
|
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
|
||||||
|
sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
|
||||||
|
if (!resolvedTypeReferenceDirective) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
||||||
|
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
|
||||||
|
referencedFiles.set(typeFilePath, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return referencedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the emittable files from the program
|
||||||
|
*/
|
||||||
|
function getAllEmittableFiles(program: Program) {
|
||||||
|
const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
|
||||||
|
const sourceFiles = program.getSourceFiles();
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const sourceFile of sourceFiles) {
|
||||||
|
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && shouldEmitFile(sourceFile)) {
|
||||||
|
result.push(sourceFile.fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNonModuleEmitHandler(): EmitHandler {
|
||||||
|
return {
|
||||||
|
addScriptInfo: noop,
|
||||||
|
removeScriptInfo: noop,
|
||||||
|
updateScriptInfo: noop,
|
||||||
|
getFilesAffectedByUpdatedShape
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFilesAffectedByUpdatedShape(program: Program, _sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||||
|
const options = program.getCompilerOptions();
|
||||||
|
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
|
||||||
|
// so returning the file itself is good enough.
|
||||||
|
if (options && (options.out || options.outFile)) {
|
||||||
|
return singleFileResult;
|
||||||
|
}
|
||||||
|
return getAllEmittableFiles(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleEmitHandler(): EmitHandler {
|
||||||
|
const references = createMap<Map<true>>();
|
||||||
|
return {
|
||||||
|
addScriptInfo: setReferences,
|
||||||
|
removeScriptInfo,
|
||||||
|
updateScriptInfo: setReferences,
|
||||||
|
getFilesAffectedByUpdatedShape
|
||||||
|
};
|
||||||
|
|
||||||
|
function setReferences(program: Program, sourceFile: SourceFile) {
|
||||||
|
references.set(sourceFile.path, getReferencedFiles(program, sourceFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeScriptInfo(removedFilePath: Path) {
|
||||||
|
// Remove existing references
|
||||||
|
references.forEach((referencesInFile, filePath) => {
|
||||||
|
if (referencesInFile.has(removedFilePath)) {
|
||||||
|
// add files referencing the removedFilePath, as changed files too
|
||||||
|
const referencedByInfo = fileInfos.get(filePath);
|
||||||
|
if (referencedByInfo) {
|
||||||
|
registerChangedFile(filePath as Path, referencedByInfo.fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Delete the entry for the removed file path
|
||||||
|
references.delete(removedFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReferencedByPaths(referencedFilePath: Path) {
|
||||||
|
return mapDefinedIter(references.entries(), ([filePath, referencesInFile]) =>
|
||||||
|
referencesInFile.has(referencedFilePath) ? filePath as Path : undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||||
|
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
|
||||||
|
return getAllEmittableFiles(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = program.getCompilerOptions();
|
||||||
|
if (options && (options.isolatedModules || options.out || options.outFile)) {
|
||||||
|
return singleFileResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to if each file in the referencedBy list has a shape change as well.
|
||||||
|
// Because if so, its own referencedBy files need to be saved as well to make the
|
||||||
|
// emitting result consistent with files on disk.
|
||||||
|
|
||||||
|
const seenFileNamesMap = createMap<string>();
|
||||||
|
const setSeenFileName = (path: Path, sourceFile: SourceFile) => {
|
||||||
|
seenFileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start with the paths this file was referenced by
|
||||||
|
const path = sourceFile.path;
|
||||||
|
setSeenFileName(path, sourceFile);
|
||||||
|
const queue = getReferencedByPaths(path);
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const currentPath = queue.pop();
|
||||||
|
if (!seenFileNamesMap.has(currentPath)) {
|
||||||
|
const currentSourceFile = program.getSourceFileByPath(currentPath);
|
||||||
|
if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) {
|
||||||
|
queue.push(...getReferencedByPaths(currentPath));
|
||||||
|
}
|
||||||
|
setSeenFileName(currentPath, currentSourceFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return array of values that needs emit
|
||||||
|
return arrayFrom(seenFileNamesMap.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1166,7 +1166,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
function diagnosticName(nameArg: __String | Identifier) {
|
function diagnosticName(nameArg: __String | Identifier) {
|
||||||
return typeof nameArg === "string" ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
|
return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {
|
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {
|
||||||
|
|
|
@ -885,7 +885,7 @@ namespace ts {
|
||||||
*/
|
*/
|
||||||
export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } {
|
export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } {
|
||||||
const textOrDiagnostic = tryReadFile(fileName, readFile);
|
const textOrDiagnostic = tryReadFile(fileName, readFile);
|
||||||
return typeof textOrDiagnostic === "string" ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -907,7 +907,7 @@ namespace ts {
|
||||||
*/
|
*/
|
||||||
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
|
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
|
||||||
const textOrDiagnostic = tryReadFile(fileName, readFile);
|
const textOrDiagnostic = tryReadFile(fileName, readFile);
|
||||||
return typeof textOrDiagnostic === "string" ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic {
|
function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic {
|
||||||
|
@ -1111,9 +1111,9 @@ namespace ts {
|
||||||
if (!isDoubleQuotedString(valueExpression)) {
|
if (!isDoubleQuotedString(valueExpression)) {
|
||||||
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected));
|
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected));
|
||||||
}
|
}
|
||||||
reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string"));
|
reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string"));
|
||||||
const text = (<StringLiteral>valueExpression).text;
|
const text = (<StringLiteral>valueExpression).text;
|
||||||
if (option && typeof option.type !== "string") {
|
if (option && !isString(option.type)) {
|
||||||
const customOption = <CommandLineOptionOfCustomType>option;
|
const customOption = <CommandLineOptionOfCustomType>option;
|
||||||
// Validate custom option type
|
// Validate custom option type
|
||||||
if (!customOption.type.has(text.toLowerCase())) {
|
if (!customOption.type.has(text.toLowerCase())) {
|
||||||
|
@ -1184,7 +1184,7 @@ namespace ts {
|
||||||
function getCompilerOptionValueTypeString(option: CommandLineOption) {
|
function getCompilerOptionValueTypeString(option: CommandLineOption) {
|
||||||
return option.type === "list" ?
|
return option.type === "list" ?
|
||||||
"Array" :
|
"Array" :
|
||||||
typeof option.type === "string" ? option.type : "string";
|
isString(option.type) ? option.type : "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
|
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
|
||||||
|
@ -1192,7 +1192,7 @@ namespace ts {
|
||||||
if (option.type === "list") {
|
if (option.type === "list") {
|
||||||
return isArray(value);
|
return isArray(value);
|
||||||
}
|
}
|
||||||
const expectedType = typeof option.type === "string" ? option.type : "string";
|
const expectedType = isString(option.type) ? option.type : "string";
|
||||||
return typeof value === expectedType;
|
return typeof value === expectedType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1578,7 +1578,7 @@ namespace ts {
|
||||||
let extendedConfigPath: Path;
|
let extendedConfigPath: Path;
|
||||||
|
|
||||||
if (json.extends) {
|
if (json.extends) {
|
||||||
if (typeof json.extends !== "string") {
|
if (!isString(json.extends)) {
|
||||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
|
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1803,7 +1803,7 @@ namespace ts {
|
||||||
if (optType === "list" && isArray(value)) {
|
if (optType === "list" && isArray(value)) {
|
||||||
return convertJsonOptionOfListType(<CommandLineOptionOfListType>opt, value, basePath, errors);
|
return convertJsonOptionOfListType(<CommandLineOptionOfListType>opt, value, basePath, errors);
|
||||||
}
|
}
|
||||||
else if (typeof optType !== "string") {
|
else if (!isString(optType)) {
|
||||||
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
|
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
|
||||||
}
|
}
|
||||||
return normalizeNonListOptionValue(opt, basePath, value);
|
return normalizeNonListOptionValue(opt, basePath, value);
|
||||||
|
@ -1816,13 +1816,13 @@ namespace ts {
|
||||||
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
|
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
|
||||||
if (option.type === "list") {
|
if (option.type === "list") {
|
||||||
const listOption = <CommandLineOptionOfListType>option;
|
const listOption = <CommandLineOptionOfListType>option;
|
||||||
if (listOption.element.isFilePath || typeof listOption.element.type !== "string") {
|
if (listOption.element.isFilePath || !isString(listOption.element.type)) {
|
||||||
return <CompilerOptionsValue>filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v);
|
return <CompilerOptionsValue>filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
else if (typeof option.type !== "string") {
|
else if (!isString(option.type)) {
|
||||||
return option.type.get(typeof value === "string" ? value.toLowerCase() : value);
|
return option.type.get(isString(value) ? value.toLowerCase() : value);
|
||||||
}
|
}
|
||||||
return normalizeNonListOptionValue(option, basePath, value);
|
return normalizeNonListOptionValue(option, basePath, value);
|
||||||
}
|
}
|
||||||
|
@ -1984,7 +1984,7 @@ namespace ts {
|
||||||
* @param host The host used to resolve files and directories.
|
* @param host The host used to resolve files and directories.
|
||||||
* @param extraFileExtensions optionaly file extra file extension information from host
|
* @param extraFileExtensions optionaly file extra file extension information from host
|
||||||
*/
|
*/
|
||||||
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>): ExpandResult {
|
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo> = []): ExpandResult {
|
||||||
basePath = normalizePath(basePath);
|
basePath = normalizePath(basePath);
|
||||||
|
|
||||||
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
|
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
|
||||||
|
|
|
@ -1202,6 +1202,13 @@ namespace ts {
|
||||||
return Array.isArray ? Array.isArray(value) : value instanceof Array;
|
return Array.isArray ? Array.isArray(value) : value instanceof Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether a value is string
|
||||||
|
*/
|
||||||
|
export function isString(text: any): text is string {
|
||||||
|
return typeof text === "string";
|
||||||
|
}
|
||||||
|
|
||||||
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
|
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
|
||||||
return value !== undefined && test(value) ? value : undefined;
|
return value !== undefined && test(value) ? value : undefined;
|
||||||
}
|
}
|
||||||
|
@ -1212,7 +1219,10 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Does nothing. */
|
/** Does nothing. */
|
||||||
export function noop(): void {}
|
export function noop(): void { }
|
||||||
|
|
||||||
|
/** Do nothing and return false */
|
||||||
|
export function returnFalse(): false { return false; }
|
||||||
|
|
||||||
/** Throws an error because a function is not implemented. */
|
/** Throws an error because a function is not implemented. */
|
||||||
export function notImplemented(): never {
|
export function notImplemented(): never {
|
||||||
|
@ -1455,16 +1465,16 @@ namespace ts {
|
||||||
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
|
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
|
||||||
while (text1 && text2) {
|
while (text1 && text2) {
|
||||||
// We still have both chains.
|
// We still have both chains.
|
||||||
const string1 = typeof text1 === "string" ? text1 : text1.messageText;
|
const string1 = isString(text1) ? text1 : text1.messageText;
|
||||||
const string2 = typeof text2 === "string" ? text2 : text2.messageText;
|
const string2 = isString(text2) ? text2 : text2.messageText;
|
||||||
|
|
||||||
const res = compareValues(string1, string2);
|
const res = compareValues(string1, string2);
|
||||||
if (res) {
|
if (res) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
text1 = typeof text1 === "string" ? undefined : text1.next;
|
text1 = isString(text1) ? undefined : text1.next;
|
||||||
text2 = typeof text2 === "string" ? undefined : text2.next;
|
text2 = isString(text2) ? undefined : text2.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!text1 && !text2) {
|
if (!text1 && !text2) {
|
||||||
|
@ -2066,8 +2076,8 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileSystemEntries {
|
export interface FileSystemEntries {
|
||||||
files: ReadonlyArray<string>;
|
readonly files: ReadonlyArray<string>;
|
||||||
directories: ReadonlyArray<string>;
|
readonly directories: ReadonlyArray<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileMatcherPatterns {
|
export interface FileMatcherPatterns {
|
||||||
|
@ -2620,4 +2630,204 @@ namespace ts {
|
||||||
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
|
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
|
||||||
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
|
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HostForCaching extends PartialSystem {
|
||||||
|
useCaseSensitiveFileNames: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CachedHost {
|
||||||
|
addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void;
|
||||||
|
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
|
||||||
|
clearCache(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CachedPartialSystem extends PartialSystem, CachedHost {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MutableFileSystemEntries {
|
||||||
|
readonly files: string[];
|
||||||
|
readonly directories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem {
|
||||||
|
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
|
||||||
|
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
|
||||||
|
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||||
|
return {
|
||||||
|
writeFile,
|
||||||
|
fileExists,
|
||||||
|
directoryExists,
|
||||||
|
createDirectory,
|
||||||
|
getCurrentDirectory,
|
||||||
|
getDirectories,
|
||||||
|
readDirectory,
|
||||||
|
addOrDeleteFileOrFolder,
|
||||||
|
addOrDeleteFile,
|
||||||
|
clearCache
|
||||||
|
};
|
||||||
|
|
||||||
|
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<string>, 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<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, 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 addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) {
|
||||||
|
const existingResult = getCachedFileSystemEntries(fileOrFolderPath);
|
||||||
|
if (existingResult) {
|
||||||
|
// This was a folder already present, remove it if this doesnt exist any more
|
||||||
|
if (!host.directoryExists(fileOrFolder)) {
|
||||||
|
cachedReadDirectoryResult.delete(fileOrFolderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// This was earlier a file (hence not in cached directory contents)
|
||||||
|
// or we never cached the directory containing it
|
||||||
|
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath);
|
||||||
|
if (parentResult) {
|
||||||
|
const baseName = getBaseNameOfFileName(fileOrFolder);
|
||||||
|
if (parentResult) {
|
||||||
|
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath));
|
||||||
|
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace ts {
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === "boolean") {
|
||||||
return value ? createTrue() : createFalse();
|
return value ? createTrue() : createFalse();
|
||||||
}
|
}
|
||||||
if (typeof value === "string") {
|
if (isString(value)) {
|
||||||
return createStringLiteral(value);
|
return createStringLiteral(value);
|
||||||
}
|
}
|
||||||
return createLiteralFromNode(value);
|
return createLiteralFromNode(value);
|
||||||
|
@ -2130,7 +2130,7 @@ namespace ts {
|
||||||
|
|
||||||
export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) {
|
export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) {
|
||||||
const node = <CatchClause>createSynthesizedNode(SyntaxKind.CatchClause);
|
const node = <CatchClause>createSynthesizedNode(SyntaxKind.CatchClause);
|
||||||
node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
|
node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
|
||||||
node.block = block;
|
node.block = block;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -2438,11 +2438,11 @@ namespace ts {
|
||||||
function asName(name: string | EntityName): EntityName;
|
function asName(name: string | EntityName): EntityName;
|
||||||
function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode;
|
function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode;
|
||||||
function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) {
|
function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) {
|
||||||
return typeof name === "string" ? createIdentifier(name) : name;
|
return isString(name) ? createIdentifier(name) : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function asExpression(value: string | number | Expression) {
|
function asExpression(value: string | number | Expression) {
|
||||||
return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value;
|
return isString(value) || typeof value === "number" ? createLiteral(value) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function asNodeArray<T extends Node>(array: ReadonlyArray<T> | undefined): NodeArray<T> | undefined {
|
function asNodeArray<T extends Node>(array: ReadonlyArray<T> | undefined): NodeArray<T> | undefined {
|
||||||
|
|
|
@ -94,7 +94,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = jsonContent[fieldName];
|
const fileName = jsonContent[fieldName];
|
||||||
if (typeof fileName !== "string") {
|
if (!isString(fileName)) {
|
||||||
if (state.traceEnabled) {
|
if (state.traceEnabled) {
|
||||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
|
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
|
||||||
}
|
}
|
||||||
|
@ -659,8 +659,8 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedPattern) {
|
if (matchedPattern) {
|
||||||
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
|
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
|
||||||
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
|
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
|
||||||
if (state.traceEnabled) {
|
if (state.traceEnabled) {
|
||||||
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
|
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/// <reference path="sys.ts" />
|
/// <reference path="sys.ts" />
|
||||||
/// <reference path="emitter.ts" />
|
/// <reference path="emitter.ts" />
|
||||||
/// <reference path="core.ts" />
|
/// <reference path="core.ts" />
|
||||||
|
/// <reference path="builder.ts" />
|
||||||
|
|
||||||
namespace ts {
|
namespace ts {
|
||||||
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
|
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
|
||||||
|
@ -227,19 +228,25 @@ namespace ts {
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (const diagnostic of diagnostics) {
|
for (const diagnostic of diagnostics) {
|
||||||
if (diagnostic.file) {
|
output += formatDiagnostic(diagnostic, host);
|
||||||
const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
|
||||||
const fileName = diagnostic.file.fileName;
|
|
||||||
const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName));
|
|
||||||
output += `${relativeFileName}(${line + 1},${character + 1}): `;
|
|
||||||
}
|
|
||||||
|
|
||||||
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
|
|
||||||
output += `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
|
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string {
|
||||||
|
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
|
||||||
|
const errorMessage = `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
|
||||||
|
|
||||||
|
if (diagnostic.file) {
|
||||||
|
const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
||||||
|
const fileName = diagnostic.file.fileName;
|
||||||
|
const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName));
|
||||||
|
return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
const redForegroundEscapeSequence = "\u001b[91m";
|
const redForegroundEscapeSequence = "\u001b[91m";
|
||||||
const yellowForegroundEscapeSequence = "\u001b[93m";
|
const yellowForegroundEscapeSequence = "\u001b[93m";
|
||||||
const blueForegroundEscapeSequence = "\u001b[93m";
|
const blueForegroundEscapeSequence = "\u001b[93m";
|
||||||
|
@ -337,7 +344,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string {
|
export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string {
|
||||||
if (typeof messageText === "string") {
|
if (isString(messageText)) {
|
||||||
return messageText;
|
return messageText;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -386,6 +393,142 @@ namespace ts {
|
||||||
allDiagnostics?: Diagnostic[];
|
allDiagnostics?: Diagnostic[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isProgramUptoDate(program: Program | undefined, rootFileNames: string[], newOptions: CompilerOptions,
|
||||||
|
getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean {
|
||||||
|
// If we haven't create a program yet, then it is not up-to-date
|
||||||
|
if (!program) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If number of files in the program do not match, it is not up-to-date
|
||||||
|
if (program.getRootFileNames().length !== rootFileNames.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any file is not up-to-date, then the whole program is not up-to-date
|
||||||
|
if (program.getSourceFiles().some(sourceFileNotUptoDate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the missing file paths are now created
|
||||||
|
if (program.getMissingFilePaths().some(fileExists)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentOptions = program.getCompilerOptions();
|
||||||
|
// If the compilation settings do no match, then the program is not up-to-date
|
||||||
|
if (!compareDataObjects(currentOptions, newOptions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If everything matches but the text of config file is changed,
|
||||||
|
// error locations can change for program options, so update the program
|
||||||
|
if (currentOptions.configFile && newOptions.configFile) {
|
||||||
|
return currentOptions.configFile.text === newOptions.configFile.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
function sourceFileNotUptoDate(sourceFile: SourceFile): boolean {
|
||||||
|
return sourceFile.version !== getSourceVersion(sourceFile.path) ||
|
||||||
|
hasInvalidatedResolution(sourceFile.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determined if source file needs to be re-created even if its text hasnt changed
|
||||||
|
*/
|
||||||
|
function shouldProgramCreateNewSourceFiles(program: Program, newOptions: CompilerOptions) {
|
||||||
|
// If any of these options change, we cant reuse old source file even if version match
|
||||||
|
// The change in options like these could result in change in syntax tree change
|
||||||
|
const oldOptions = program && program.getCompilerOptions();
|
||||||
|
return oldOptions &&
|
||||||
|
(oldOptions.target !== newOptions.target ||
|
||||||
|
oldOptions.module !== newOptions.module ||
|
||||||
|
oldOptions.moduleResolution !== newOptions.moduleResolution ||
|
||||||
|
oldOptions.noResolve !== newOptions.noResolve ||
|
||||||
|
oldOptions.jsx !== newOptions.jsx ||
|
||||||
|
oldOptions.allowJs !== newOptions.allowJs ||
|
||||||
|
oldOptions.disableSizeLimit !== newOptions.disableSizeLimit ||
|
||||||
|
oldOptions.baseUrl !== newOptions.baseUrl ||
|
||||||
|
!equalOwnProperties(oldOptions.paths, newOptions.paths));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the existing missing file watches with the new set of missing files after new program is created
|
||||||
|
*/
|
||||||
|
export function updateMissingFilePathsWatch(
|
||||||
|
program: Program,
|
||||||
|
missingFileWatches: Map<FileWatcher>,
|
||||||
|
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
|
||||||
|
closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void
|
||||||
|
) {
|
||||||
|
const missingFilePaths = program.getMissingFilePaths();
|
||||||
|
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||||
|
// Update the missing file paths watcher
|
||||||
|
mutateMap(
|
||||||
|
missingFileWatches,
|
||||||
|
newMissingFilePathMap,
|
||||||
|
{
|
||||||
|
// Watch the missing files
|
||||||
|
createNewValue: createMissingFileWatch,
|
||||||
|
// Files that are no longer missing (e.g. because they are no longer required)
|
||||||
|
// should no longer be watched.
|
||||||
|
onDeleteValue: closeExistingMissingFilePathFileWatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WildcardDirectoryWatcher {
|
||||||
|
watcher: FileWatcher;
|
||||||
|
flags: WatchDirectoryFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the existing wild card directory watches with the new set of wild card directories from the config file
|
||||||
|
* after new program is created because the config file was reloaded or program was created first time from the config file
|
||||||
|
* Note that there is no need to call this function when the program is updated with additional files without reloading config files,
|
||||||
|
* as wildcard directories wont change unless reloading config file
|
||||||
|
*/
|
||||||
|
export function updateWatchingWildcardDirectories(
|
||||||
|
existingWatchedForWildcards: Map<WildcardDirectoryWatcher>,
|
||||||
|
wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||||
|
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher,
|
||||||
|
closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flagsChanged: boolean) => void
|
||||||
|
) {
|
||||||
|
mutateMap(
|
||||||
|
existingWatchedForWildcards,
|
||||||
|
wildcardDirectories,
|
||||||
|
{
|
||||||
|
// Create new watch and recursive info
|
||||||
|
createNewValue: createWildcardDirectoryWatcher,
|
||||||
|
// Close existing watch thats not needed any more
|
||||||
|
onDeleteValue: (directory, wildcardDirectoryWatcher) =>
|
||||||
|
closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ false),
|
||||||
|
// Close existing watch that doesnt match in the flags
|
||||||
|
onExistingValue: updateWildcardDirectoryWatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher {
|
||||||
|
// Create new watch and recursive info
|
||||||
|
return {
|
||||||
|
watcher: watchDirectory(directory, flags),
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags) {
|
||||||
|
// Watcher needs to be updated if the recursive flags dont match
|
||||||
|
if (wildcardDirectoryWatcher.flags === flags) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true);
|
||||||
|
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
|
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
|
||||||
* that represent a compilation unit.
|
* that represent a compilation unit.
|
||||||
|
@ -446,6 +589,7 @@ namespace ts {
|
||||||
|
|
||||||
let moduleResolutionCache: ModuleResolutionCache;
|
let moduleResolutionCache: ModuleResolutionCache;
|
||||||
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
|
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
|
||||||
|
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
|
||||||
if (host.resolveModuleNames) {
|
if (host.resolveModuleNames) {
|
||||||
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => {
|
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => {
|
||||||
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
||||||
|
@ -482,10 +626,12 @@ namespace ts {
|
||||||
let redirectTargetsSet = createMap<true>();
|
let redirectTargetsSet = createMap<true>();
|
||||||
|
|
||||||
const filesByName = createMap<SourceFile | undefined>();
|
const filesByName = createMap<SourceFile | undefined>();
|
||||||
|
let missingFilePaths: ReadonlyArray<Path>;
|
||||||
// stores 'filename -> file association' ignoring case
|
// stores 'filename -> file association' ignoring case
|
||||||
// used to track cases when two file names differ only in casing
|
// used to track cases when two file names differ only in casing
|
||||||
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
|
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
|
||||||
|
|
||||||
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
||||||
const structuralIsReused = tryReuseStructureFromOldProgram();
|
const structuralIsReused = tryReuseStructureFromOldProgram();
|
||||||
if (structuralIsReused !== StructureIsReused.Completely) {
|
if (structuralIsReused !== StructureIsReused.Completely) {
|
||||||
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
|
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
|
||||||
|
@ -520,13 +666,26 @@ namespace ts {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
const missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
|
Debug.assert(!!missingFilePaths);
|
||||||
|
|
||||||
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
|
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
|
||||||
moduleResolutionCache = undefined;
|
moduleResolutionCache = undefined;
|
||||||
|
|
||||||
|
// Release any files we have acquired in the old program but are
|
||||||
|
// not part of the new program.
|
||||||
|
if (oldProgram && host.onReleaseOldSourceFile) {
|
||||||
|
const oldSourceFiles = oldProgram.getSourceFiles();
|
||||||
|
for (const oldSourceFile of oldSourceFiles) {
|
||||||
|
if (!getSourceFile(oldSourceFile.path) || shouldCreateNewSourceFile) {
|
||||||
|
host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
|
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
|
||||||
oldProgram = undefined;
|
oldProgram = undefined;
|
||||||
|
|
||||||
|
@ -682,7 +841,7 @@ namespace ts {
|
||||||
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile);
|
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (!hasInvalidatedResolution(oldProgramState.file.path)) {
|
||||||
resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState);
|
resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,14 +943,21 @@ namespace ts {
|
||||||
const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = [];
|
const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = [];
|
||||||
oldProgram.structureIsReused = StructureIsReused.Completely;
|
oldProgram.structureIsReused = StructureIsReused.Completely;
|
||||||
|
|
||||||
|
// If the missing file paths are now present, it can change the progam structure,
|
||||||
|
// and hence cant reuse the structure.
|
||||||
|
// This is same as how we dont reuse the structure if one of the file from old program is now missing
|
||||||
|
if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) {
|
||||||
|
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||||
|
}
|
||||||
|
|
||||||
const oldSourceFiles = oldProgram.getSourceFiles();
|
const oldSourceFiles = oldProgram.getSourceFiles();
|
||||||
const enum SeenPackageName { Exists, Modified }
|
const enum SeenPackageName { Exists, Modified }
|
||||||
const seenPackageNames = createMap<SeenPackageName>();
|
const seenPackageNames = createMap<SeenPackageName>();
|
||||||
|
|
||||||
for (const oldSourceFile of oldSourceFiles) {
|
for (const oldSourceFile of oldSourceFiles) {
|
||||||
let newSourceFile = host.getSourceFileByPath
|
let newSourceFile = host.getSourceFileByPath
|
||||||
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target)
|
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target, /*onError*/ undefined, shouldCreateNewSourceFile)
|
||||||
: host.getSourceFile(oldSourceFile.fileName, options.target);
|
: host.getSourceFile(oldSourceFile.fileName, options.target, /*onError*/ undefined, shouldCreateNewSourceFile);
|
||||||
|
|
||||||
if (!newSourceFile) {
|
if (!newSourceFile) {
|
||||||
return oldProgram.structureIsReused = StructureIsReused.Not;
|
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||||
|
@ -874,6 +1040,13 @@ namespace ts {
|
||||||
// tentatively approve the file
|
// tentatively approve the file
|
||||||
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
|
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
|
||||||
}
|
}
|
||||||
|
else if (hasInvalidatedResolution(oldSourceFile.path)) {
|
||||||
|
// 'module/types' references could have changed
|
||||||
|
oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||||
|
|
||||||
|
// add file to the modified list so that we will resolve it later
|
||||||
|
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
|
||||||
|
}
|
||||||
|
|
||||||
// if file has passed all checks it should be safe to reuse it
|
// if file has passed all checks it should be safe to reuse it
|
||||||
newSourceFiles.push(newSourceFile);
|
newSourceFiles.push(newSourceFile);
|
||||||
|
@ -920,20 +1093,7 @@ namespace ts {
|
||||||
return oldProgram.structureIsReused;
|
return oldProgram.structureIsReused;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a file has ceased to be missing, then we need to discard some of the old
|
missingFilePaths = oldProgram.getMissingFilePaths();
|
||||||
// structure in order to pick it up.
|
|
||||||
// Caution: if the file has created and then deleted between since it was discovered to
|
|
||||||
// be missing, then the corresponding file watcher will have been closed and no new one
|
|
||||||
// will be created until we encounter a change that prevents complete structure reuse.
|
|
||||||
// During this interval, creation of the file will go unnoticed. We expect this to be
|
|
||||||
// both rare and low-impact.
|
|
||||||
if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) {
|
|
||||||
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const p of oldProgram.getMissingFilePaths()) {
|
|
||||||
filesByName.set(p, undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update fileName -> file mapping
|
// update fileName -> file mapping
|
||||||
for (let i = 0; i < newSourceFiles.length; i++) {
|
for (let i = 0; i < newSourceFiles.length; i++) {
|
||||||
|
@ -1670,7 +1830,7 @@ namespace ts {
|
||||||
else {
|
else {
|
||||||
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
|
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
|
||||||
}
|
}
|
||||||
});
|
}, shouldCreateNewSourceFile);
|
||||||
|
|
||||||
if (packageId) {
|
if (packageId) {
|
||||||
const packageIdKey = `${packageId.name}@${packageId.version}`;
|
const packageIdKey = `${packageId.name}@${packageId.version}`;
|
||||||
|
|
304
src/compiler/resolutionCache.ts
Normal file
304
src/compiler/resolutionCache.ts
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
/// <reference path="types.ts"/>
|
||||||
|
/// <reference path="core.ts"/>
|
||||||
|
|
||||||
|
/*@internal*/
|
||||||
|
namespace ts {
|
||||||
|
/** This is the cache of module/typedirectives resolution that can be retained across program */
|
||||||
|
export interface ResolutionCache {
|
||||||
|
setModuleResolutionHost(host: ModuleResolutionHost): void;
|
||||||
|
|
||||||
|
startRecordingFilesWithChangedResolutions(): void;
|
||||||
|
finishRecordingFilesWithChangedResolutions(): Path[];
|
||||||
|
|
||||||
|
resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[];
|
||||||
|
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||||
|
|
||||||
|
invalidateResolutionOfFile(filePath: Path): void;
|
||||||
|
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void;
|
||||||
|
|
||||||
|
createHasInvalidatedResolution(): HasInvalidatedResolution;
|
||||||
|
|
||||||
|
clear(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NameResolutionWithFailedLookupLocations {
|
||||||
|
readonly failedLookupLocations: ReadonlyArray<string>;
|
||||||
|
isInvalidated?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FailedLookupLocationsWatcher {
|
||||||
|
fileWatcher: FileWatcher;
|
||||||
|
refCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createResolutionCache(
|
||||||
|
toPath: (fileName: string) => Path,
|
||||||
|
getCompilerOptions: () => CompilerOptions,
|
||||||
|
watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher,
|
||||||
|
log: (s: string) => void,
|
||||||
|
projectName?: string,
|
||||||
|
getGlobalCache?: () => string | undefined): ResolutionCache {
|
||||||
|
|
||||||
|
let host: ModuleResolutionHost;
|
||||||
|
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
|
||||||
|
let filesWithInvalidatedResolutions: Map<true> | undefined;
|
||||||
|
|
||||||
|
// The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
|
||||||
|
// The key in the map is source file's path.
|
||||||
|
// The values are Map of resolutions with key being name lookedup.
|
||||||
|
const resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||||
|
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||||
|
|
||||||
|
const failedLookupLocationsWatches = createMap<FailedLookupLocationsWatcher>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
setModuleResolutionHost,
|
||||||
|
startRecordingFilesWithChangedResolutions,
|
||||||
|
finishRecordingFilesWithChangedResolutions,
|
||||||
|
resolveModuleNames,
|
||||||
|
resolveTypeReferenceDirectives,
|
||||||
|
invalidateResolutionOfFile,
|
||||||
|
invalidateResolutionOfChangedFailedLookupLocation,
|
||||||
|
createHasInvalidatedResolution,
|
||||||
|
clear
|
||||||
|
};
|
||||||
|
|
||||||
|
function setModuleResolutionHost(updatedHost: ModuleResolutionHost) {
|
||||||
|
host = updatedHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
|
||||||
|
clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => {
|
||||||
|
log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`);
|
||||||
|
failedLookupLocationWatcher.fileWatcher.close();
|
||||||
|
});
|
||||||
|
resolvedModuleNames.clear();
|
||||||
|
resolvedTypeReferenceDirectives.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRecordingFilesWithChangedResolutions() {
|
||||||
|
filesWithChangedSetOfUnresolvedImports = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishRecordingFilesWithChangedResolutions() {
|
||||||
|
const collected = filesWithChangedSetOfUnresolvedImports;
|
||||||
|
filesWithChangedSetOfUnresolvedImports = undefined;
|
||||||
|
return collected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHasInvalidatedResolution(): HasInvalidatedResolution {
|
||||||
|
const collected = filesWithInvalidatedResolutions;
|
||||||
|
filesWithInvalidatedResolutions = undefined;
|
||||||
|
return path => collected && collected.has(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||||
|
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||||
|
// return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
|
||||||
|
if (!getGlobalCache) {
|
||||||
|
return primaryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise try to load typings from @types
|
||||||
|
const globalCache = getGlobalCache();
|
||||||
|
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) {
|
||||||
|
// create different collection of failed lookup locations for second pass
|
||||||
|
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
||||||
|
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache);
|
||||||
|
if (resolvedModule) {
|
||||||
|
return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as Array<string>, failedLookupLocations) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default return the result from the first pass
|
||||||
|
return primaryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNamesWithLocalCache<T extends NameResolutionWithFailedLookupLocations, R>(
|
||||||
|
names: string[],
|
||||||
|
containingFile: string,
|
||||||
|
cache: Map<Map<T>>,
|
||||||
|
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||||
|
getResult: (s: T) => R,
|
||||||
|
getResultFileName: (result: R) => string | undefined,
|
||||||
|
logChanges: boolean): R[] {
|
||||||
|
|
||||||
|
const path = toPath(containingFile);
|
||||||
|
const currentResolutionsInFile = cache.get(path);
|
||||||
|
|
||||||
|
const newResolutions: Map<T> = createMap<T>();
|
||||||
|
const resolvedModules: R[] = [];
|
||||||
|
const compilerOptions = getCompilerOptions();
|
||||||
|
|
||||||
|
for (const name of names) {
|
||||||
|
// check if this is a duplicate entry in the list
|
||||||
|
let resolution = newResolutions.get(name);
|
||||||
|
if (!resolution) {
|
||||||
|
const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name);
|
||||||
|
if (moduleResolutionIsValid(existingResolution)) {
|
||||||
|
// ok, it is safe to use existing name resolution results
|
||||||
|
resolution = existingResolution;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolution = loader(name, containingFile, compilerOptions, host);
|
||||||
|
updateFailedLookupLocationWatches(containingFile, name, existingResolution && existingResolution.failedLookupLocations, resolution.failedLookupLocations);
|
||||||
|
}
|
||||||
|
newResolutions.set(name, resolution);
|
||||||
|
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
||||||
|
filesWithChangedSetOfUnresolvedImports.push(path);
|
||||||
|
// reset log changes to avoid recording the same file multiple times
|
||||||
|
logChanges = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.assert(resolution !== undefined);
|
||||||
|
|
||||||
|
resolvedModules.push(getResult(resolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace old results with a new one
|
||||||
|
cache.set(path, newResolutions);
|
||||||
|
return resolvedModules;
|
||||||
|
|
||||||
|
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
|
||||||
|
if (oldResolution === newResolution) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const oldResult = getResult(oldResolution);
|
||||||
|
const newResult = getResult(newResolution);
|
||||||
|
if (oldResult === newResult) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!oldResult || !newResult) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getResultFileName(oldResult) === getResultFileName(newResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moduleResolutionIsValid(resolution: T): boolean {
|
||||||
|
if (!resolution || resolution.isInvalidated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = getResult(resolution);
|
||||||
|
if (result) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider situation if we have no candidate locations as valid resolution.
|
||||||
|
// after all there is no point to invalidate it if we have no idea where to look for the module.
|
||||||
|
return resolution.failedLookupLocations.length === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||||
|
return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, resolveTypeReferenceDirective,
|
||||||
|
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] {
|
||||||
|
return resolveNamesWithLocalCache(moduleNames, containingFile, resolvedModuleNames, resolveModuleName,
|
||||||
|
m => m.resolvedModule, r => r.resolvedFileName, logChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||||
|
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
|
||||||
|
if (failedLookupLocationWatcher) {
|
||||||
|
failedLookupLocationWatcher.refCount++;
|
||||||
|
log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
|
||||||
|
const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name);
|
||||||
|
failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||||
|
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
|
||||||
|
Debug.assert(!!failedLookupLocationWatcher);
|
||||||
|
failedLookupLocationWatcher.refCount--;
|
||||||
|
if (failedLookupLocationWatcher.refCount) {
|
||||||
|
log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}: refCount: ${failedLookupLocationWatcher.refCount}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log(`Watcher: FailedLookupLocations: Status: Closing the file watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
|
||||||
|
failedLookupLocationWatcher.fileWatcher.close();
|
||||||
|
failedLookupLocationsWatches.delete(failedLookupLocationPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void;
|
||||||
|
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string>, containingFile: string, name: string, fn: FailedLookupLocationAction) {
|
||||||
|
forEach(failedLookupLocations, failedLookupLocation => {
|
||||||
|
fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray<string> | undefined, failedLookupLocations: ReadonlyArray<string>) {
|
||||||
|
// Watch all the failed lookup locations
|
||||||
|
withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation);
|
||||||
|
|
||||||
|
// Close existing watches for the failed locations
|
||||||
|
withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
|
||||||
|
deletedFilePath: Path,
|
||||||
|
cache: Map<Map<T>>,
|
||||||
|
getResult: (s: T) => R,
|
||||||
|
getResultFileName: (result: R) => string | undefined) {
|
||||||
|
cache.forEach((value, path) => {
|
||||||
|
if (path === deletedFilePath) {
|
||||||
|
cache.delete(path);
|
||||||
|
value.forEach((resolution, name) => {
|
||||||
|
withFailedLookupLocations(resolution.failedLookupLocations, path, name, closeFailedLookupLocationWatcher);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (value) {
|
||||||
|
value.forEach(resolution => {
|
||||||
|
if (resolution && !resolution.isInvalidated) {
|
||||||
|
const result = getResult(resolution);
|
||||||
|
if (result) {
|
||||||
|
if (toPath(getResultFileName(result)) === deletedFilePath) {
|
||||||
|
resolution.isInvalidated = true;
|
||||||
|
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
|
||||||
|
failedLookupLocationPath: Path,
|
||||||
|
cache: Map<Map<T>>) {
|
||||||
|
cache.forEach((value, containingFile) => {
|
||||||
|
if (value) {
|
||||||
|
value.forEach(resolution => {
|
||||||
|
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) {
|
||||||
|
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
|
||||||
|
resolution.isInvalidated = true;
|
||||||
|
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFile, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateResolutionOfFile(filePath: Path) {
|
||||||
|
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName);
|
||||||
|
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) {
|
||||||
|
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames);
|
||||||
|
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,14 +30,26 @@ namespace ts {
|
||||||
mtime?: Date;
|
mtime?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface System {
|
/**
|
||||||
|
* Partial interface of the System thats needed to support the caching of directory structure
|
||||||
|
*/
|
||||||
|
export interface PartialSystem {
|
||||||
|
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<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface System extends PartialSystem {
|
||||||
args: string[];
|
args: string[];
|
||||||
newLine: string;
|
newLine: string;
|
||||||
useCaseSensitiveFileNames: boolean;
|
useCaseSensitiveFileNames: boolean;
|
||||||
write(s: string): void;
|
write(s: string): void;
|
||||||
readFile(path: string, encoding?: string): string | undefined;
|
readFile(path: string, encoding?: string): string | undefined;
|
||||||
getFileSize?(path: string): number;
|
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
|
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
|
||||||
* use native OS file watching
|
* use native OS file watching
|
||||||
|
@ -45,13 +57,7 @@ namespace ts {
|
||||||
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
||||||
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
||||||
resolvePath(path: string): string;
|
resolvePath(path: string): string;
|
||||||
fileExists(path: string): boolean;
|
|
||||||
directoryExists(path: string): boolean;
|
|
||||||
createDirectory(path: string): void;
|
|
||||||
getExecutingFilePath(): string;
|
getExecutingFilePath(): string;
|
||||||
getCurrentDirectory(): string;
|
|
||||||
getDirectories(path: string): string[];
|
|
||||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
|
|
||||||
getModifiedTime?(path: string): Date;
|
getModifiedTime?(path: string): Date;
|
||||||
/**
|
/**
|
||||||
* This should be cryptographically secure.
|
* This should be cryptographically secure.
|
||||||
|
@ -184,7 +190,7 @@ namespace ts {
|
||||||
|
|
||||||
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
|
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
|
||||||
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
|
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
|
||||||
const fileName = typeof relativeFileName !== "string"
|
const fileName = !isString(relativeFileName)
|
||||||
? undefined
|
? undefined
|
||||||
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
|
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
|
||||||
// Some applications save a working file via rename operations
|
// Some applications save a working file via rename operations
|
||||||
|
|
|
@ -1,48 +1,13 @@
|
||||||
/// <reference path="program.ts"/>
|
/// <reference path="program.ts"/>
|
||||||
|
/// <reference path="watchedProgram.ts"/>
|
||||||
/// <reference path="commandLineParser.ts"/>
|
/// <reference path="commandLineParser.ts"/>
|
||||||
|
|
||||||
namespace ts {
|
namespace ts {
|
||||||
export interface SourceFile {
|
|
||||||
fileWatcher?: FileWatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Statistic {
|
interface Statistic {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = {
|
|
||||||
getCurrentDirectory: () => sys.getCurrentDirectory(),
|
|
||||||
getNewLine: () => sys.newLine,
|
|
||||||
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames)
|
|
||||||
};
|
|
||||||
|
|
||||||
let reportDiagnosticWorker = reportDiagnosticSimply;
|
|
||||||
|
|
||||||
function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) {
|
|
||||||
reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void {
|
|
||||||
for (const diagnostic of diagnostics) {
|
|
||||||
reportDiagnostic(diagnostic, host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportEmittedFiles(files: string[]): void {
|
|
||||||
if (!files || files.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDir = sys.getCurrentDirectory();
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filepath = getNormalizedAbsolutePath(file, currentDir);
|
|
||||||
|
|
||||||
sys.write(`TSFILE: ${filepath}${sys.newLine}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function countLines(program: Program): number {
|
function countLines(program: Program): number {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
forEach(program.getSourceFiles(), file => {
|
forEach(program.getSourceFiles(), file => {
|
||||||
|
@ -56,25 +21,11 @@ namespace ts {
|
||||||
return <string>diagnostic.messageText;
|
return <string>diagnostic.messageText;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
|
let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply);
|
||||||
sys.write(ts.formatDiagnostics([diagnostic], host));
|
function udpateReportDiagnostic(options: CompilerOptions) {
|
||||||
}
|
if (options.pretty) {
|
||||||
|
reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext);
|
||||||
function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
|
|
||||||
sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportWatchDiagnostic(diagnostic: Diagnostic) {
|
|
||||||
let output = new Date().toLocaleTimeString() + " - ";
|
|
||||||
|
|
||||||
if (diagnostic.file) {
|
|
||||||
const loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
|
||||||
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`;
|
|
||||||
|
|
||||||
sys.write(output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function padLeft(s: string, length: number) {
|
function padLeft(s: string, length: number) {
|
||||||
|
@ -98,26 +49,12 @@ namespace ts {
|
||||||
|
|
||||||
export function executeCommandLine(args: string[]): void {
|
export function executeCommandLine(args: string[]): void {
|
||||||
const commandLine = parseCommandLine(args);
|
const commandLine = parseCommandLine(args);
|
||||||
let configFileName: string; // Configuration file name (if any)
|
|
||||||
let cachedConfigFileText: string; // Cached configuration file text, used for reparsing (if any)
|
|
||||||
let configFileWatcher: FileWatcher; // Configuration file watcher
|
|
||||||
let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal
|
|
||||||
let cachedProgram: Program; // Program cached from last compilation
|
|
||||||
let rootFileNames: string[]; // Root fileNames for compilation
|
|
||||||
let compilerOptions: CompilerOptions; // Compiler options for compilation
|
|
||||||
let compilerHost: CompilerHost; // Compiler host
|
|
||||||
let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
|
|
||||||
let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation
|
|
||||||
let timerHandleForDirectoryChanges: any; // Handle for 0.25s wait timer to trigger directory change handler
|
|
||||||
|
|
||||||
// This map stores and reuses results of fileExists check that happen inside 'createProgram'
|
|
||||||
// This allows to save time in module resolution heavy scenarios when existence of the same file might be checked multiple times.
|
|
||||||
let cachedExistingFiles: Map<boolean>;
|
|
||||||
let hostFileExists: typeof compilerHost.fileExists;
|
|
||||||
|
|
||||||
|
// Configuration file name (if any)
|
||||||
|
let configFileName: string;
|
||||||
if (commandLine.options.locale) {
|
if (commandLine.options.locale) {
|
||||||
if (!isJSONSupported()) {
|
if (!isJSONSupported()) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"));
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
|
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
|
||||||
|
@ -126,7 +63,7 @@ namespace ts {
|
||||||
// If there are any errors due to command line parsing and/or
|
// If there are any errors due to command line parsing and/or
|
||||||
// setting up localization, report them and quit.
|
// setting up localization, report them and quit.
|
||||||
if (commandLine.errors.length > 0) {
|
if (commandLine.errors.length > 0) {
|
||||||
reportDiagnostics(commandLine.errors, compilerHost);
|
reportDiagnostics(commandLine.errors, reportDiagnostic);
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,11 +85,11 @@ namespace ts {
|
||||||
|
|
||||||
if (commandLine.options.project) {
|
if (commandLine.options.project) {
|
||||||
if (!isJSONSupported()) {
|
if (!isJSONSupported()) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"));
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
if (commandLine.fileNames.length !== 0) {
|
if (commandLine.fileNames.length !== 0) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,14 +97,14 @@ namespace ts {
|
||||||
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
|
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
|
||||||
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
|
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
|
||||||
if (!sys.fileExists(configFileName)) {
|
if (!sys.fileExists(configFileName)) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project));
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
configFileName = fileOrDirectory;
|
configFileName = fileOrDirectory;
|
||||||
if (!sys.fileExists(configFileName)) {
|
if (!sys.fileExists(configFileName)) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project));
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,249 +120,94 @@ namespace ts {
|
||||||
return sys.exit(ExitStatus.Success);
|
return sys.exit(ExitStatus.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWatchSet(commandLine.options)) {
|
const commandLineOptions = commandLine.options;
|
||||||
if (!sys.watchFile) {
|
if (configFileName) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
|
const reportWatchDiagnostic = createWatchDiagnosticReporter();
|
||||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic);
|
||||||
}
|
udpateReportDiagnostic(configParseResult.options);
|
||||||
if (configFileName) {
|
|
||||||
configFileWatcher = sys.watchFile(configFileName, configFileChanged);
|
|
||||||
}
|
|
||||||
if (sys.watchDirectory && configFileName) {
|
|
||||||
const directory = ts.getDirectoryPath(configFileName);
|
|
||||||
directoryWatcher = sys.watchDirectory(
|
|
||||||
// When the configFileName is just "tsconfig.json", the watched directory should be
|
|
||||||
// the current directory; if there is a given "project" parameter, then the configFileName
|
|
||||||
// is an absolute file name.
|
|
||||||
directory === "" ? "." : directory,
|
|
||||||
watchedDirectoryChanged, /*recursive*/ true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
performCompilation();
|
|
||||||
|
|
||||||
function parseConfigFile(): ParsedCommandLine {
|
|
||||||
if (!cachedConfigFileText) {
|
|
||||||
try {
|
|
||||||
cachedConfigFileText = sys.readFile(configFileName);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
|
|
||||||
reportWatchDiagnostic(error);
|
|
||||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cachedConfigFileText) {
|
|
||||||
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
|
|
||||||
reportDiagnostics([error], /* compilerHost */ undefined);
|
|
||||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = parseJsonText(configFileName, cachedConfigFileText);
|
|
||||||
reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined);
|
|
||||||
|
|
||||||
const cwd = sys.getCurrentDirectory();
|
|
||||||
const configParseResult = parseJsonSourceFileConfigFileContent(result, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd));
|
|
||||||
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
|
|
||||||
|
|
||||||
if (isWatchSet(configParseResult.options)) {
|
if (isWatchSet(configParseResult.options)) {
|
||||||
if (!sys.watchFile) {
|
reportWatchModeWithoutSysSupport();
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
|
createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic));
|
||||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!directoryWatcher && sys.watchDirectory && configFileName) {
|
|
||||||
const directory = ts.getDirectoryPath(configFileName);
|
|
||||||
directoryWatcher = sys.watchDirectory(
|
|
||||||
// When the configFileName is just "tsconfig.json", the watched directory should be
|
|
||||||
// the current directory; if there is a given "project" parameter, then the configFileName
|
|
||||||
// is an absolute file name.
|
|
||||||
directory === "" ? "." : directory,
|
|
||||||
watchedDirectoryChanged, /*recursive*/ true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return configParseResult;
|
else {
|
||||||
}
|
performCompilation(configParseResult.fileNames, configParseResult.options);
|
||||||
|
|
||||||
// Invoked to perform initial compilation or re-compilation in watch mode
|
|
||||||
function performCompilation() {
|
|
||||||
|
|
||||||
if (!cachedProgram) {
|
|
||||||
if (configFileName) {
|
|
||||||
const configParseResult = parseConfigFile();
|
|
||||||
rootFileNames = configParseResult.fileNames;
|
|
||||||
compilerOptions = configParseResult.options;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rootFileNames = commandLine.fileNames;
|
|
||||||
compilerOptions = commandLine.options;
|
|
||||||
}
|
|
||||||
compilerHost = createCompilerHost(compilerOptions);
|
|
||||||
hostGetSourceFile = compilerHost.getSourceFile;
|
|
||||||
compilerHost.getSourceFile = getSourceFile;
|
|
||||||
|
|
||||||
hostFileExists = compilerHost.fileExists;
|
|
||||||
compilerHost.fileExists = cachedFileExists;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compilerOptions.pretty) {
|
|
||||||
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset the cache of existing files
|
|
||||||
cachedExistingFiles = createMap<boolean>();
|
|
||||||
|
|
||||||
const compileResult = compile(rootFileNames, compilerOptions, compilerHost);
|
|
||||||
|
|
||||||
if (!isWatchSet(compilerOptions)) {
|
|
||||||
return sys.exit(compileResult.exitStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCachedProgram(compileResult.program);
|
|
||||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
|
||||||
|
|
||||||
const missingPaths = compileResult.program.getMissingFilePaths();
|
|
||||||
missingPaths.forEach(path => {
|
|
||||||
const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => {
|
|
||||||
if (eventKind === FileWatcherEventKind.Created) {
|
|
||||||
fileWatcher.close();
|
|
||||||
startTimerForRecompilation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cachedFileExists(fileName: string): boolean {
|
|
||||||
let fileExists = cachedExistingFiles.get(fileName);
|
|
||||||
if (fileExists === undefined) {
|
|
||||||
cachedExistingFiles.set(fileName, fileExists = hostFileExists(fileName));
|
|
||||||
}
|
|
||||||
return fileExists;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) {
|
|
||||||
// Return existing SourceFile object if one is available
|
|
||||||
if (cachedProgram) {
|
|
||||||
const sourceFile = cachedProgram.getSourceFile(fileName);
|
|
||||||
// A modified source file has no watcher and should not be reused
|
|
||||||
if (sourceFile && sourceFile.fileWatcher) {
|
|
||||||
return sourceFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use default host function
|
|
||||||
const sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
|
|
||||||
if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) {
|
|
||||||
// Attach a file watcher
|
|
||||||
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName, eventKind) => sourceFileChanged(sourceFile, eventKind));
|
|
||||||
}
|
|
||||||
return sourceFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change cached program to the given program
|
|
||||||
function setCachedProgram(program: Program) {
|
|
||||||
if (cachedProgram) {
|
|
||||||
const newSourceFiles = program ? program.getSourceFiles() : undefined;
|
|
||||||
forEach(cachedProgram.getSourceFiles(), sourceFile => {
|
|
||||||
if (!(newSourceFiles && contains(newSourceFiles, sourceFile))) {
|
|
||||||
if (sourceFile.fileWatcher) {
|
|
||||||
sourceFile.fileWatcher.close();
|
|
||||||
sourceFile.fileWatcher = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cachedProgram = program;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a source file changes, mark it as unwatched and start the recompilation timer
|
|
||||||
function sourceFileChanged(sourceFile: SourceFile, eventKind: FileWatcherEventKind) {
|
|
||||||
sourceFile.fileWatcher.close();
|
|
||||||
sourceFile.fileWatcher = undefined;
|
|
||||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
|
||||||
unorderedRemoveItem(rootFileNames, sourceFile.fileName);
|
|
||||||
}
|
|
||||||
startTimerForRecompilation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the configuration file changes, forget cached program and start the recompilation timer
|
|
||||||
function configFileChanged() {
|
|
||||||
setCachedProgram(undefined);
|
|
||||||
cachedConfigFileText = undefined;
|
|
||||||
startTimerForRecompilation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchedDirectoryChanged(fileName: string) {
|
|
||||||
if (fileName && !ts.isSupportedSourceFileName(fileName, compilerOptions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startTimerForHandlingDirectoryChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTimerForHandlingDirectoryChanges() {
|
|
||||||
if (!sys.setTimeout || !sys.clearTimeout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timerHandleForDirectoryChanges) {
|
|
||||||
sys.clearTimeout(timerHandleForDirectoryChanges);
|
|
||||||
}
|
|
||||||
timerHandleForDirectoryChanges = sys.setTimeout(directoryChangeHandler, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
function directoryChangeHandler() {
|
|
||||||
const parsedCommandLine = parseConfigFile();
|
|
||||||
const newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName);
|
|
||||||
const canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName);
|
|
||||||
|
|
||||||
// We check if the project file list has changed. If so, we just throw away the old program and start fresh.
|
|
||||||
if (!arrayIsEqualTo(newFileNames && newFileNames.sort(), canonicalRootFileNames && canonicalRootFileNames.sort())) {
|
|
||||||
setCachedProgram(undefined);
|
|
||||||
startTimerForRecompilation();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
|
udpateReportDiagnostic(commandLineOptions);
|
||||||
// operations (such as saving all modified files in an editor) a chance to complete before we kick
|
if (isWatchSet(commandLineOptions)) {
|
||||||
// off a new compilation.
|
reportWatchModeWithoutSysSupport();
|
||||||
function startTimerForRecompilation() {
|
createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost());
|
||||||
if (!sys.setTimeout || !sys.clearTimeout) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (timerHandleForRecompilation) {
|
performCompilation(commandLine.fileNames, commandLineOptions);
|
||||||
sys.clearTimeout(timerHandleForRecompilation);
|
|
||||||
}
|
}
|
||||||
timerHandleForRecompilation = sys.setTimeout(recompile, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
function recompile() {
|
|
||||||
timerHandleForRecompilation = undefined;
|
|
||||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
|
||||||
performCompilation();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) {
|
function reportWatchModeWithoutSysSupport() {
|
||||||
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
if (!sys.watchFile || !sys.watchDirectory) {
|
||||||
let statistics: Statistic[];
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
|
||||||
if (hasDiagnostics) {
|
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) {
|
||||||
|
const compilerHost = createCompilerHost(compilerOptions);
|
||||||
|
enableStatistics(compilerOptions);
|
||||||
|
|
||||||
|
const program = createProgram(rootFileNames, compilerOptions, compilerHost);
|
||||||
|
const exitStatus = compileProgram(program);
|
||||||
|
|
||||||
|
reportStatistics(program);
|
||||||
|
return sys.exit(exitStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) {
|
||||||
|
const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic);
|
||||||
|
watchingHost.beforeCompile = enableStatistics;
|
||||||
|
const afterCompile = watchingHost.afterCompile;
|
||||||
|
watchingHost.afterCompile = (host, program, builder) => {
|
||||||
|
afterCompile(host, program, builder);
|
||||||
|
reportStatistics(program);
|
||||||
|
};
|
||||||
|
return watchingHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileProgram(program: Program): ExitStatus {
|
||||||
|
let diagnostics: Diagnostic[];
|
||||||
|
|
||||||
|
// First get and report any syntactic errors.
|
||||||
|
diagnostics = program.getSyntacticDiagnostics();
|
||||||
|
|
||||||
|
// If we didn't have any syntactic errors, then also try getting the global and
|
||||||
|
// semantic errors.
|
||||||
|
if (diagnostics.length === 0) {
|
||||||
|
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
|
||||||
|
|
||||||
|
if (diagnostics.length === 0) {
|
||||||
|
diagnostics = program.getSemanticDiagnostics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit and report any errors we ran into.
|
||||||
|
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit();
|
||||||
|
diagnostics = diagnostics.concat(emitDiagnostics);
|
||||||
|
|
||||||
|
return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableStatistics(compilerOptions: CompilerOptions) {
|
||||||
|
if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) {
|
||||||
performance.enable();
|
performance.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportStatistics(program: Program) {
|
||||||
|
let statistics: Statistic[];
|
||||||
|
const compilerOptions = program.getCompilerOptions();
|
||||||
|
if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) {
|
||||||
statistics = [];
|
statistics = [];
|
||||||
}
|
|
||||||
|
|
||||||
const program = createProgram(fileNames, compilerOptions, compilerHost);
|
|
||||||
const exitStatus = compileProgram();
|
|
||||||
|
|
||||||
if (compilerOptions.listFiles) {
|
|
||||||
forEach(program.getSourceFiles(), file => {
|
|
||||||
sys.write(file.fileName + sys.newLine);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasDiagnostics) {
|
|
||||||
const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
|
const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
|
||||||
reportCountStatistic("Files", program.getSourceFiles().length);
|
reportCountStatistic("Files", program.getSourceFiles().length);
|
||||||
reportCountStatistic("Lines", countLines(program));
|
reportCountStatistic("Lines", countLines(program));
|
||||||
|
@ -463,44 +245,6 @@ namespace ts {
|
||||||
performance.disable();
|
performance.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { program, exitStatus };
|
|
||||||
|
|
||||||
function compileProgram(): ExitStatus {
|
|
||||||
let diagnostics: Diagnostic[];
|
|
||||||
|
|
||||||
// First get and report any syntactic errors.
|
|
||||||
diagnostics = program.getSyntacticDiagnostics();
|
|
||||||
|
|
||||||
// If we didn't have any syntactic errors, then also try getting the global and
|
|
||||||
// semantic errors.
|
|
||||||
if (diagnostics.length === 0) {
|
|
||||||
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
|
|
||||||
|
|
||||||
if (diagnostics.length === 0) {
|
|
||||||
diagnostics = program.getSemanticDiagnostics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, emit and report any errors we ran into.
|
|
||||||
const emitOutput = program.emit();
|
|
||||||
diagnostics = diagnostics.concat(emitOutput.diagnostics);
|
|
||||||
|
|
||||||
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost);
|
|
||||||
|
|
||||||
reportEmittedFiles(emitOutput.emittedFiles);
|
|
||||||
|
|
||||||
if (emitOutput.emitSkipped && diagnostics.length > 0) {
|
|
||||||
// If the emitter didn't emit anything, then pass that value along.
|
|
||||||
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
|
|
||||||
}
|
|
||||||
else if (diagnostics.length > 0) {
|
|
||||||
// The emitter emitted something, inform the caller if that happened in the presence
|
|
||||||
// of diagnostics or not.
|
|
||||||
return ExitStatus.DiagnosticsPresent_OutputsGenerated;
|
|
||||||
}
|
|
||||||
return ExitStatus.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportStatistics() {
|
function reportStatistics() {
|
||||||
let nameSize = 0;
|
let nameSize = 0;
|
||||||
let valueSize = 0;
|
let valueSize = 0;
|
||||||
|
@ -654,19 +398,17 @@ namespace ts {
|
||||||
const currentDirectory = sys.getCurrentDirectory();
|
const currentDirectory = sys.getCurrentDirectory();
|
||||||
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
|
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
|
||||||
if (sys.fileExists(file)) {
|
if (sys.fileExists(file)) {
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine));
|
sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine));
|
||||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* host */ undefined);
|
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.setStackTraceLimit();
|
|
||||||
|
|
||||||
if (ts.Debug.isDebugging) {
|
if (ts.Debug.isDebugging) {
|
||||||
ts.Debug.enableDebugInfo();
|
ts.Debug.enableDebugInfo();
|
||||||
}
|
}
|
||||||
|
@ -674,5 +416,4 @@ if (ts.Debug.isDebugging) {
|
||||||
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
|
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
|
||||||
ts.sys.tryEnableSourceMapsForHost();
|
ts.sys.tryEnableSourceMapsForHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.executeCommandLine(ts.sys.args);
|
ts.executeCommandLine(ts.sys.args);
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
"declarationEmitter.ts",
|
"declarationEmitter.ts",
|
||||||
"emitter.ts",
|
"emitter.ts",
|
||||||
"program.ts",
|
"program.ts",
|
||||||
|
"builder.ts",
|
||||||
|
"resolutionCache.ts",
|
||||||
|
"watchedProgram.ts",
|
||||||
"commandLineParser.ts",
|
"commandLineParser.ts",
|
||||||
"tsc.ts",
|
"tsc.ts",
|
||||||
"diagnosticInformationMap.generated.ts"
|
"diagnosticInformationMap.generated.ts"
|
||||||
|
|
|
@ -2347,6 +2347,7 @@ namespace ts {
|
||||||
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
|
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
|
||||||
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
|
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
|
||||||
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
|
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
|
||||||
|
/* @internal */ version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Bundle extends Node {
|
export interface Bundle extends Node {
|
||||||
|
@ -2410,7 +2411,7 @@ namespace ts {
|
||||||
* program source file but could not be located.
|
* program source file but could not be located.
|
||||||
*/
|
*/
|
||||||
/* @internal */
|
/* @internal */
|
||||||
getMissingFilePaths(): Path[];
|
getMissingFilePaths(): ReadonlyArray<Path>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then
|
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then
|
||||||
|
@ -3570,6 +3571,7 @@ namespace ts {
|
||||||
charset?: string;
|
charset?: string;
|
||||||
checkJs?: boolean;
|
checkJs?: boolean;
|
||||||
/* @internal */ configFilePath?: string;
|
/* @internal */ configFilePath?: string;
|
||||||
|
/** configFile is set as non enumerable property so as to avoid checking of json source files */
|
||||||
/* @internal */ readonly configFile?: JsonSourceFile;
|
/* @internal */ readonly configFile?: JsonSourceFile;
|
||||||
declaration?: boolean;
|
declaration?: boolean;
|
||||||
declarationDir?: string;
|
declarationDir?: string;
|
||||||
|
@ -4014,7 +4016,7 @@ namespace ts {
|
||||||
export interface ResolvedModuleWithFailedLookupLocations {
|
export interface ResolvedModuleWithFailedLookupLocations {
|
||||||
readonly resolvedModule: ResolvedModuleFull | undefined;
|
readonly resolvedModule: ResolvedModuleFull | undefined;
|
||||||
/* @internal */
|
/* @internal */
|
||||||
readonly failedLookupLocations: string[];
|
readonly failedLookupLocations: ReadonlyArray<string>;
|
||||||
/*@internal*/
|
/*@internal*/
|
||||||
isInvalidated?: boolean;
|
isInvalidated?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -4028,14 +4030,18 @@ namespace ts {
|
||||||
|
|
||||||
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
||||||
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
|
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
|
||||||
readonly failedLookupLocations: string[];
|
readonly failedLookupLocations: ReadonlyArray<string>;
|
||||||
/*@internal*/
|
/*@internal*/
|
||||||
isInvalidated?: boolean;
|
isInvalidated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HasInvalidatedResolution {
|
||||||
|
(sourceFile: Path): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompilerHost extends ModuleResolutionHost {
|
export interface CompilerHost extends ModuleResolutionHost {
|
||||||
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
|
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile;
|
||||||
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
|
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile;
|
||||||
getCancellationToken?(): CancellationToken;
|
getCancellationToken?(): CancellationToken;
|
||||||
getDefaultLibFileName(options: CompilerOptions): string;
|
getDefaultLibFileName(options: CompilerOptions): string;
|
||||||
getDefaultLibLocation?(): string;
|
getDefaultLibLocation?(): string;
|
||||||
|
@ -4059,6 +4065,8 @@ namespace ts {
|
||||||
*/
|
*/
|
||||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||||
getEnvironmentVariable?(name: string): string;
|
getEnvironmentVariable?(name: string): string;
|
||||||
|
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
|
||||||
|
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @internal */
|
/* @internal */
|
||||||
|
|
|
@ -366,7 +366,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTextOfConstantValue(value: string | number) {
|
export function getTextOfConstantValue(value: string | number) {
|
||||||
return typeof value === "string" ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
|
return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
|
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
|
||||||
|
@ -403,7 +403,10 @@ namespace ts {
|
||||||
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
|
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @internal */
|
export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration {
|
||||||
|
return isModuleDeclaration(node) && node.name.kind === SyntaxKind.StringLiteral;
|
||||||
|
}
|
||||||
|
|
||||||
export function isNonGlobalAmbientModule(node: Node): node is ModuleDeclaration & { name: StringLiteral } {
|
export function isNonGlobalAmbientModule(node: Node): node is ModuleDeclaration & { name: StringLiteral } {
|
||||||
return isModuleDeclaration(node) && isStringLiteral(node.name);
|
return isModuleDeclaration(node) && isStringLiteral(node.name);
|
||||||
}
|
}
|
||||||
|
@ -1416,8 +1419,8 @@ namespace ts {
|
||||||
if (node.kind === SyntaxKind.ExportDeclaration) {
|
if (node.kind === SyntaxKind.ExportDeclaration) {
|
||||||
return (<ExportDeclaration>node).moduleSpecifier;
|
return (<ExportDeclaration>node).moduleSpecifier;
|
||||||
}
|
}
|
||||||
if (node.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
|
if (isModuleWithStringLiteralName(node)) {
|
||||||
return (<ModuleDeclaration>node).name;
|
return node.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3202,17 +3205,14 @@ namespace ts {
|
||||||
|
|
||||||
const carriageReturnLineFeed = "\r\n";
|
const carriageReturnLineFeed = "\r\n";
|
||||||
const lineFeed = "\n";
|
const lineFeed = "\n";
|
||||||
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions): string {
|
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: System): string {
|
||||||
switch (options.newLine) {
|
switch (options.newLine) {
|
||||||
case NewLineKind.CarriageReturnLineFeed:
|
case NewLineKind.CarriageReturnLineFeed:
|
||||||
return carriageReturnLineFeed;
|
return carriageReturnLineFeed;
|
||||||
case NewLineKind.LineFeed:
|
case NewLineKind.LineFeed:
|
||||||
return lineFeed;
|
return lineFeed;
|
||||||
}
|
}
|
||||||
if (sys) {
|
return system ? system.newLine : sys ? sys.newLine : carriageReturnLineFeed;
|
||||||
return sys.newLine;
|
|
||||||
}
|
|
||||||
return carriageReturnLineFeed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3466,6 +3466,84 @@ namespace ts {
|
||||||
export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags {
|
export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags {
|
||||||
return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags;
|
return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compareDataObjects(dst: any, src: any): boolean {
|
||||||
|
if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e in dst) {
|
||||||
|
if (typeof dst[e] === "object") {
|
||||||
|
if (!compareDataObjects(dst[e], src[e])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof dst[e] !== "function") {
|
||||||
|
if (dst[e] !== src[e]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
|
||||||
|
*/
|
||||||
|
export function clearMap<T>(map: Map<T>, onDeleteValue: (key: string, existingValue: T) => void) {
|
||||||
|
// Remove all
|
||||||
|
map.forEach((existingValue, key) => {
|
||||||
|
onDeleteValue(key, existingValue);
|
||||||
|
});
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MutateMapOptions<T, U> {
|
||||||
|
createNewValue(key: string, valueInNewMap: U): T;
|
||||||
|
onDeleteValue(key: string, existingValue: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present this is called with the key when there is value for that key both in new map as well as existing map provided
|
||||||
|
* Caller can then decide to update or remove this key.
|
||||||
|
* If the key is removed, caller will get callback of createNewValue for that key.
|
||||||
|
* If this callback is not provided, the value of such keys is not updated.
|
||||||
|
*/
|
||||||
|
onExistingValue?(key: string, existingValue: T, valueInNewMap: U): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutates the map with newMap such that keys in map will be same as newMap.
|
||||||
|
*/
|
||||||
|
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
|
||||||
|
// If there are new values update them
|
||||||
|
if (newMap) {
|
||||||
|
const { createNewValue, onDeleteValue, onExistingValue } = options;
|
||||||
|
// Needs update
|
||||||
|
map.forEach((existingValue, key) => {
|
||||||
|
const valueInNewMap = newMap.get(key);
|
||||||
|
// Not present any more in new map, remove it
|
||||||
|
if (valueInNewMap === undefined) {
|
||||||
|
map.delete(key);
|
||||||
|
onDeleteValue(key, existingValue);
|
||||||
|
}
|
||||||
|
// If present notify about existing values
|
||||||
|
else if (onExistingValue) {
|
||||||
|
onExistingValue(key, existingValue, valueInNewMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new values that are not already present
|
||||||
|
newMap.forEach((valueInNewMap, key) => {
|
||||||
|
if (!map.has(key)) {
|
||||||
|
// New values
|
||||||
|
map.set(key, createNewValue(key, valueInNewMap));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearMap(map, options.onDeleteValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ts {
|
namespace ts {
|
||||||
|
|
712
src/compiler/watchedProgram.ts
Normal file
712
src/compiler/watchedProgram.ts
Normal file
|
@ -0,0 +1,712 @@
|
||||||
|
/// <reference path="program.ts" />
|
||||||
|
/// <reference path="builder.ts" />
|
||||||
|
/// <reference path="resolutionCache.ts"/>
|
||||||
|
|
||||||
|
namespace ts {
|
||||||
|
export type DiagnosticReporter = (diagnostic: Diagnostic) => void;
|
||||||
|
export type DiagnosticWorker = (diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System) => void;
|
||||||
|
export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine;
|
||||||
|
export interface WatchingSystemHost {
|
||||||
|
// FS system to use
|
||||||
|
system: System;
|
||||||
|
|
||||||
|
// parse config file
|
||||||
|
parseConfigFile: ParseConfigFile;
|
||||||
|
|
||||||
|
// Reporting errors
|
||||||
|
reportDiagnostic: DiagnosticReporter;
|
||||||
|
reportWatchDiagnostic: DiagnosticReporter;
|
||||||
|
|
||||||
|
// Callbacks to do custom action before creating program and after creating program
|
||||||
|
beforeCompile(compilerOptions: CompilerOptions): void;
|
||||||
|
afterCompile(host: System, program: Program, builder: Builder): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? {
|
||||||
|
getCurrentDirectory: () => sys.getCurrentDirectory(),
|
||||||
|
getNewLine: () => sys.newLine,
|
||||||
|
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames)
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
export function createDiagnosticReporter(system = sys, worker = reportDiagnosticSimply, formatDiagnosticsHost?: FormatDiagnosticsHost): DiagnosticReporter {
|
||||||
|
return diagnostic => worker(diagnostic, getFormatDiagnosticsHost(), system);
|
||||||
|
|
||||||
|
function getFormatDiagnosticsHost() {
|
||||||
|
return formatDiagnosticsHost || (formatDiagnosticsHost = system === sys ? defaultFormatDiagnosticsHost : {
|
||||||
|
getCurrentDirectory: () => system.getCurrentDirectory(),
|
||||||
|
getNewLine: () => system.newLine,
|
||||||
|
getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchDiagnosticReporter(system = sys): DiagnosticReporter {
|
||||||
|
return diagnostic => {
|
||||||
|
let output = new Date().toLocaleTimeString() + " - ";
|
||||||
|
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine + system.newLine}`;
|
||||||
|
system.write(output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reportDiagnostics(diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter): void {
|
||||||
|
for (const diagnostic of diagnostics) {
|
||||||
|
reportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void {
|
||||||
|
system.write(ts.formatDiagnostic(diagnostic, host));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void {
|
||||||
|
system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine {
|
||||||
|
let configFileText: string;
|
||||||
|
try {
|
||||||
|
configFileText = system.readFile(configFileName);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
|
||||||
|
reportWatchDiagnostic(error);
|
||||||
|
system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!configFileText) {
|
||||||
|
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
|
||||||
|
reportDiagnostics([error], reportDiagnostic);
|
||||||
|
system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = parseJsonText(configFileName, configFileText);
|
||||||
|
reportDiagnostics(result.parseDiagnostics, reportDiagnostic);
|
||||||
|
|
||||||
|
const cwd = system.getCurrentDirectory();
|
||||||
|
const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
|
||||||
|
reportDiagnostics(configParseResult.errors, reportDiagnostic);
|
||||||
|
|
||||||
|
return configParseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportEmittedFiles(files: string[], system: System): void {
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentDir = system.getCurrentDirectory();
|
||||||
|
for (const file of files) {
|
||||||
|
const filepath = getNormalizedAbsolutePath(file, currentDir);
|
||||||
|
system.write(`TSFILE: ${filepath}${system.newLine}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleEmitOutputAndReportErrors(system: System, program: Program,
|
||||||
|
emittedFiles: string[], emitSkipped: boolean,
|
||||||
|
diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter
|
||||||
|
): ExitStatus {
|
||||||
|
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic);
|
||||||
|
reportEmittedFiles(emittedFiles, system);
|
||||||
|
|
||||||
|
if (program.getCompilerOptions().listFiles) {
|
||||||
|
forEach(program.getSourceFiles(), file => {
|
||||||
|
system.write(file.fileName + system.newLine);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emitSkipped && diagnostics.length > 0) {
|
||||||
|
// If the emitter didn't emit anything, then pass that value along.
|
||||||
|
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
|
||||||
|
}
|
||||||
|
else if (diagnostics.length > 0) {
|
||||||
|
// The emitter emitted something, inform the caller if that happened in the presence
|
||||||
|
// of diagnostics or not.
|
||||||
|
return ExitStatus.DiagnosticsPresent_OutputsGenerated;
|
||||||
|
}
|
||||||
|
return ExitStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys,
|
||||||
|
parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter,
|
||||||
|
reportWatchDiagnostic?: DiagnosticReporter
|
||||||
|
): WatchingSystemHost {
|
||||||
|
reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply);
|
||||||
|
reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system);
|
||||||
|
parseConfigFile = parseConfigFile || ts.parseConfigFile;
|
||||||
|
return {
|
||||||
|
system,
|
||||||
|
parseConfigFile,
|
||||||
|
reportDiagnostic,
|
||||||
|
reportWatchDiagnostic,
|
||||||
|
beforeCompile: noop,
|
||||||
|
afterCompile: compileWatchedProgram,
|
||||||
|
};
|
||||||
|
|
||||||
|
function compileWatchedProgram(host: System, program: Program, builder: Builder) {
|
||||||
|
// First get and report any syntactic errors.
|
||||||
|
let diagnostics = program.getSyntacticDiagnostics();
|
||||||
|
let reportSemanticDiagnostics = false;
|
||||||
|
|
||||||
|
// If we didn't have any syntactic errors, then also try getting the global and
|
||||||
|
// semantic errors.
|
||||||
|
if (diagnostics.length === 0) {
|
||||||
|
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
|
||||||
|
|
||||||
|
if (diagnostics.length === 0) {
|
||||||
|
reportSemanticDiagnostics = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit and report any errors we ran into.
|
||||||
|
const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined;
|
||||||
|
let sourceMaps: SourceMapData[];
|
||||||
|
let emitSkipped: boolean;
|
||||||
|
|
||||||
|
const result = builder.emitChangedFiles(program);
|
||||||
|
if (result.length === 0) {
|
||||||
|
emitSkipped = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const emitOutput of result) {
|
||||||
|
if (emitOutput.emitSkipped) {
|
||||||
|
emitSkipped = true;
|
||||||
|
}
|
||||||
|
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
|
||||||
|
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
|
||||||
|
writeOutputFiles(emitOutput.outputFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportSemanticDiagnostics) {
|
||||||
|
diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program));
|
||||||
|
}
|
||||||
|
return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped,
|
||||||
|
diagnostics, reportDiagnostic);
|
||||||
|
|
||||||
|
function ensureDirectoriesExist(directoryPath: string) {
|
||||||
|
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) {
|
||||||
|
const parentDirectory = getDirectoryPath(directoryPath);
|
||||||
|
ensureDirectoriesExist(parentDirectory);
|
||||||
|
host.createDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
|
||||||
|
try {
|
||||||
|
performance.mark("beforeIOWrite");
|
||||||
|
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
|
||||||
|
|
||||||
|
host.writeFile(fileName, data, writeByteOrderMark);
|
||||||
|
|
||||||
|
performance.mark("afterIOWrite");
|
||||||
|
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeOutputFiles(outputFiles: OutputFile[]) {
|
||||||
|
if (outputFiles) {
|
||||||
|
for (const outputFile of outputFiles) {
|
||||||
|
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
|
||||||
|
if (error) {
|
||||||
|
diagnostics.push(error);
|
||||||
|
}
|
||||||
|
if (emittedFiles) {
|
||||||
|
emittedFiles.push(outputFile.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions = {}, watchingHost?: WatchingSystemHost) {
|
||||||
|
return createWatchMode(configParseResult.fileNames, configParseResult.options, watchingHost, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost) {
|
||||||
|
return createWatchMode(rootFileNames, compilerOptions, watchingHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HostFileInfo {
|
||||||
|
version: number;
|
||||||
|
sourceFile: SourceFile;
|
||||||
|
fileWatcher: FileWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
|
||||||
|
let program: Program;
|
||||||
|
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
|
||||||
|
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
|
||||||
|
let configFileWatcher: FileWatcher; // watcher for the config file
|
||||||
|
let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
|
||||||
|
let timerToUpdateProgram: any; // timer callback to recompile the program
|
||||||
|
|
||||||
|
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
|
||||||
|
let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files
|
||||||
|
let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions
|
||||||
|
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
|
||||||
|
|
||||||
|
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
|
||||||
|
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
|
||||||
|
|
||||||
|
let host: System;
|
||||||
|
if (configFileName) {
|
||||||
|
host = createCachedSystem(system);
|
||||||
|
configFileWatcher = system.watchFile(configFileName, onConfigFileChanged);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
host = system;
|
||||||
|
}
|
||||||
|
const currentDirectory = host.getCurrentDirectory();
|
||||||
|
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||||
|
|
||||||
|
// Cache for the module resolution
|
||||||
|
const resolutionCache = createResolutionCache(
|
||||||
|
fileName => toPath(fileName),
|
||||||
|
() => compilerOptions,
|
||||||
|
watchFailedLookupLocation,
|
||||||
|
s => writeLog(s)
|
||||||
|
);
|
||||||
|
|
||||||
|
// There is no extra check needed since we can just rely on the program to decide emit
|
||||||
|
const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true);
|
||||||
|
|
||||||
|
synchronizeProgram();
|
||||||
|
|
||||||
|
// Update the wild card directory watch
|
||||||
|
watchConfigFileWildCardDirectories();
|
||||||
|
|
||||||
|
return () => program;
|
||||||
|
|
||||||
|
function synchronizeProgram() {
|
||||||
|
writeLog(`Synchronizing program`);
|
||||||
|
|
||||||
|
hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
|
||||||
|
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the compiler host
|
||||||
|
const compilerHost = createWatchedCompilerHost(compilerOptions);
|
||||||
|
resolutionCache.setModuleResolutionHost(compilerHost);
|
||||||
|
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
|
||||||
|
resolutionCache.clear();
|
||||||
|
}
|
||||||
|
hasChangedCompilerOptions = false;
|
||||||
|
beforeCompile(compilerOptions);
|
||||||
|
|
||||||
|
// Compile the program
|
||||||
|
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
|
||||||
|
builder.onProgramUpdateGraph(program, hasInvalidatedResolution);
|
||||||
|
|
||||||
|
// Update watches
|
||||||
|
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath, closeMissingFilePathWatcher);
|
||||||
|
if (missingFilePathsRequestedForRelease) {
|
||||||
|
// These are the paths that program creater told us as not in use any more but were missing on the disk.
|
||||||
|
// We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
|
||||||
|
// if there is already watcher for it (for missing files)
|
||||||
|
// At that point our watches were updated, hence now we know that these paths are not tracked and need to be removed
|
||||||
|
// so that at later time we have correct result of their presence
|
||||||
|
for (const missingFilePath of missingFilePathsRequestedForRelease) {
|
||||||
|
if (!missingFilesMap.has(missingFilePath)) {
|
||||||
|
sourceFilesCache.delete(missingFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
missingFilePathsRequestedForRelease = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterCompile(host, program, builder);
|
||||||
|
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
|
||||||
|
const newLine = getNewLineCharacter(options, system);
|
||||||
|
const realpath = host.realpath && ((path: string) => host.realpath(path));
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSourceFile: getVersionedSourceFile,
|
||||||
|
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||||
|
getDefaultLibLocation,
|
||||||
|
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||||
|
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
|
||||||
|
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
|
||||||
|
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames,
|
||||||
|
getCanonicalFileName,
|
||||||
|
getNewLine: () => newLine,
|
||||||
|
fileExists,
|
||||||
|
readFile: fileName => host.readFile(fileName),
|
||||||
|
trace: (s: string) => host.write(s + newLine),
|
||||||
|
directoryExists: directoryName => host.directoryExists(directoryName),
|
||||||
|
getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "",
|
||||||
|
getDirectories: (path: string) => host.getDirectories(path),
|
||||||
|
realpath,
|
||||||
|
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
|
||||||
|
resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false),
|
||||||
|
onReleaseOldSourceFile,
|
||||||
|
hasInvalidatedResolution
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPath(fileName: string) {
|
||||||
|
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExists(fileName: string) {
|
||||||
|
const path = toPath(fileName);
|
||||||
|
const hostSourceFileInfo = sourceFilesCache.get(path);
|
||||||
|
if (hostSourceFileInfo !== undefined) {
|
||||||
|
return !isString(hostSourceFileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return host.fileExists(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultLibLocation(): string {
|
||||||
|
return getDirectoryPath(normalizePath(host.getExecutingFilePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||||
|
return getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||||
|
const hostSourceFile = sourceFilesCache.get(path);
|
||||||
|
// No source file on the host
|
||||||
|
if (isString(hostSourceFile)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new source file if requested or the versions dont match
|
||||||
|
if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||||
|
const sourceFile = getNewSourceFile();
|
||||||
|
if (hostSourceFile) {
|
||||||
|
if (shouldCreateNewSourceFile) {
|
||||||
|
hostSourceFile.version++;
|
||||||
|
}
|
||||||
|
if (sourceFile) {
|
||||||
|
hostSourceFile.sourceFile = sourceFile;
|
||||||
|
sourceFile.version = hostSourceFile.version.toString();
|
||||||
|
if (!hostSourceFile.fileWatcher) {
|
||||||
|
hostSourceFile.fileWatcher = watchSourceFileForChanges(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// There is no source file on host any more, close the watch, missing file paths will track it
|
||||||
|
hostSourceFile.fileWatcher.close();
|
||||||
|
sourceFilesCache.set(path, hostSourceFile.version.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let fileWatcher: FileWatcher;
|
||||||
|
if (sourceFile) {
|
||||||
|
sourceFile.version = "0";
|
||||||
|
fileWatcher = watchSourceFileForChanges(path);
|
||||||
|
sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sourceFilesCache.set(path, "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceFile;
|
||||||
|
}
|
||||||
|
return hostSourceFile.sourceFile;
|
||||||
|
|
||||||
|
function getNewSourceFile() {
|
||||||
|
let text: string;
|
||||||
|
try {
|
||||||
|
performance.mark("beforeIORead");
|
||||||
|
text = host.readFile(fileName, compilerOptions.charset);
|
||||||
|
performance.mark("afterIORead");
|
||||||
|
performance.measure("I/O Read", "beforeIORead", "afterIORead");
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (onError) {
|
||||||
|
onError(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSourceFile(path: Path) {
|
||||||
|
const hostSourceFile = sourceFilesCache.get(path);
|
||||||
|
if (hostSourceFile !== undefined) {
|
||||||
|
if (!isString(hostSourceFile)) {
|
||||||
|
hostSourceFile.fileWatcher.close();
|
||||||
|
resolutionCache.invalidateResolutionOfFile(path);
|
||||||
|
}
|
||||||
|
sourceFilesCache.delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceVersion(path: Path): string {
|
||||||
|
const hostSourceFile = sourceFilesCache.get(path);
|
||||||
|
return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) {
|
||||||
|
const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path);
|
||||||
|
// If this is the source file thats in the cache and new program doesnt need it,
|
||||||
|
// remove the cached entry.
|
||||||
|
// Note we arent deleting entry if file became missing in new program or
|
||||||
|
// there was version update and new source file was created.
|
||||||
|
if (hostSourceFileInfo) {
|
||||||
|
// record the missing file paths so they can be removed later if watchers arent tracking them
|
||||||
|
if (isString(hostSourceFileInfo)) {
|
||||||
|
(missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
|
||||||
|
}
|
||||||
|
else if (hostSourceFileInfo.sourceFile === oldSourceFile) {
|
||||||
|
sourceFilesCache.delete(oldSourceFile.path);
|
||||||
|
resolutionCache.invalidateResolutionOfFile(oldSourceFile.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
|
||||||
|
// operations (such as saving all modified files in an editor) a chance to complete before we kick
|
||||||
|
// off a new compilation.
|
||||||
|
function scheduleProgramUpdate() {
|
||||||
|
if (!system.setTimeout || !system.clearTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerToUpdateProgram) {
|
||||||
|
system.clearTimeout(timerToUpdateProgram);
|
||||||
|
}
|
||||||
|
timerToUpdateProgram = system.setTimeout(updateProgram, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleProgramReload() {
|
||||||
|
Debug.assert(!!configFileName);
|
||||||
|
needsReload = true;
|
||||||
|
scheduleProgramUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgram() {
|
||||||
|
timerToUpdateProgram = undefined;
|
||||||
|
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
|
||||||
|
|
||||||
|
if (needsReload) {
|
||||||
|
reloadConfigFile();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
synchronizeProgram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfigFile() {
|
||||||
|
writeLog(`Reloading config file: ${configFileName}`);
|
||||||
|
needsReload = false;
|
||||||
|
|
||||||
|
const cachedHost = host as CachedSystem;
|
||||||
|
cachedHost.clearCache();
|
||||||
|
const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic);
|
||||||
|
rootFileNames = configParseResult.fileNames;
|
||||||
|
compilerOptions = configParseResult.options;
|
||||||
|
hasChangedCompilerOptions = true;
|
||||||
|
configFileSpecs = configParseResult.configFileSpecs;
|
||||||
|
configFileWildCardDirectories = configParseResult.wildcardDirectories;
|
||||||
|
|
||||||
|
synchronizeProgram();
|
||||||
|
|
||||||
|
// Update the wild card directory watch
|
||||||
|
watchConfigFileWildCardDirectories();
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchSourceFileForChanges(path: Path) {
|
||||||
|
return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||||
|
writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||||
|
|
||||||
|
updateCachedSystemWithFile(fileName, path, eventKind);
|
||||||
|
const hostSourceFile = sourceFilesCache.get(path);
|
||||||
|
if (hostSourceFile) {
|
||||||
|
// Update the cache
|
||||||
|
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||||
|
resolutionCache.invalidateResolutionOfFile(path);
|
||||||
|
if (!isString(hostSourceFile)) {
|
||||||
|
hostSourceFile.fileWatcher.close();
|
||||||
|
sourceFilesCache.set(path, (hostSourceFile.version++).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Deleted file created
|
||||||
|
if (isString(hostSourceFile)) {
|
||||||
|
sourceFilesCache.delete(path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// file changed - just update the version
|
||||||
|
hostSourceFile.version++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the program
|
||||||
|
scheduleProgramUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||||
|
if (configFileName) {
|
||||||
|
(host as CachedSystem).addOrDeleteFile(fileName, path, eventKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||||
|
return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, failedLookupLocationPath, containingFile, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||||
|
writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`);
|
||||||
|
updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind);
|
||||||
|
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
|
||||||
|
scheduleProgramUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchMissingFilePath(missingFilePath: Path) {
|
||||||
|
return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) {
|
||||||
|
fileWatcher.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMissingFileChange(fileName: string, missingFilePath: Path, eventKind: FileWatcherEventKind) {
|
||||||
|
writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||||
|
updateCachedSystemWithFile(fileName, missingFilePath, eventKind);
|
||||||
|
|
||||||
|
if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
|
||||||
|
closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath));
|
||||||
|
missingFilesMap.delete(missingFilePath);
|
||||||
|
|
||||||
|
// Delete the entry in the source files cache so that new source file is created
|
||||||
|
removeSourceFile(missingFilePath);
|
||||||
|
|
||||||
|
// When a missing file is created, we should update the graph.
|
||||||
|
scheduleProgramUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchConfigFileWildCardDirectories() {
|
||||||
|
updateWatchingWildcardDirectories(
|
||||||
|
watchedWildcardDirectories || (watchedWildcardDirectories = createMap()),
|
||||||
|
createMapFromTemplate(configFileWildCardDirectories),
|
||||||
|
watchWildCardDirectory,
|
||||||
|
stopWatchingWildCardDirectory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) {
|
||||||
|
return host.watchDirectory(directory, fileOrFolder =>
|
||||||
|
onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileOrFolder, directory)),
|
||||||
|
(flags & WatchDirectoryFlags.Recursive) !== 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatcher, _recursiveChanged: boolean) {
|
||||||
|
watcher.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) {
|
||||||
|
Debug.assert(!!configFileName);
|
||||||
|
|
||||||
|
const fileOrFolderPath = toPath(fileOrFolder);
|
||||||
|
|
||||||
|
// Since the file existance changed, update the sourceFiles cache
|
||||||
|
(host as CachedSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||||
|
removeSourceFile(fileOrFolderPath);
|
||||||
|
|
||||||
|
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||||
|
// one with the fileName being "folder/file", and the other one with "folder".
|
||||||
|
// We don't respond to the second one.
|
||||||
|
if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) {
|
||||||
|
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileOrFolder}`);
|
||||||
|
|
||||||
|
// Reload is pending, do the reload
|
||||||
|
if (!needsReload) {
|
||||||
|
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host);
|
||||||
|
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
|
||||||
|
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
|
||||||
|
}
|
||||||
|
rootFileNames = result.fileNames;
|
||||||
|
|
||||||
|
// Schedule Update the program
|
||||||
|
scheduleProgramUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) {
|
||||||
|
writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
|
||||||
|
scheduleProgramReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLog(s: string) {
|
||||||
|
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||||
|
if (hasDiagnostics) {
|
||||||
|
host.write(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeHash(data: string) {
|
||||||
|
return system.createHash ? system.createHash(data) : data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CachedSystem extends System, CachedHost {
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCachedSystem(host: System): CachedSystem {
|
||||||
|
const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined;
|
||||||
|
const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined;
|
||||||
|
const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined;
|
||||||
|
const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined;
|
||||||
|
const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined;
|
||||||
|
const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined;
|
||||||
|
const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined;
|
||||||
|
const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined;
|
||||||
|
const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined;
|
||||||
|
const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined;
|
||||||
|
|
||||||
|
const cachedPartialSystem = createCachedPartialSystem(host);
|
||||||
|
return {
|
||||||
|
args: host.args,
|
||||||
|
newLine: host.newLine,
|
||||||
|
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
|
||||||
|
write: s => host.write(s),
|
||||||
|
readFile: (path, encoding?) => host.readFile(path, encoding),
|
||||||
|
getFileSize,
|
||||||
|
writeFile: (fileName, data, writeByteOrderMark?) => cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark),
|
||||||
|
watchFile,
|
||||||
|
watchDirectory,
|
||||||
|
resolvePath: path => host.resolvePath(path),
|
||||||
|
fileExists: fileName => cachedPartialSystem.fileExists(fileName),
|
||||||
|
directoryExists: dir => cachedPartialSystem.directoryExists(dir),
|
||||||
|
createDirectory: dir => cachedPartialSystem.createDirectory(dir),
|
||||||
|
getExecutingFilePath: () => host.getExecutingFilePath(),
|
||||||
|
getCurrentDirectory: () => cachedPartialSystem.getCurrentDirectory(),
|
||||||
|
getDirectories: dir => cachedPartialSystem.getDirectories(dir),
|
||||||
|
readDirectory: (path, extensions, excludes, includes, depth) => cachedPartialSystem.readDirectory(path, extensions, excludes, includes, depth),
|
||||||
|
getModifiedTime,
|
||||||
|
createHash,
|
||||||
|
getMemoryUsage,
|
||||||
|
exit: exitCode => host.exit(exitCode),
|
||||||
|
realpath,
|
||||||
|
getEnvironmentVariable: name => host.getEnvironmentVariable(name),
|
||||||
|
tryEnableSourceMapsForHost,
|
||||||
|
debugMode: host.debugMode,
|
||||||
|
setTimeout,
|
||||||
|
clearTimeout,
|
||||||
|
addOrDeleteFileOrFolder: (fileOrFolder, fileOrFolderPath) => cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath),
|
||||||
|
addOrDeleteFile: (file, filePath, eventKind) => cachedPartialSystem.addOrDeleteFile(file, filePath, eventKind),
|
||||||
|
clearCache: () => cachedPartialSystem.clearCache()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -394,7 +394,7 @@ namespace FourSlash {
|
||||||
|
|
||||||
// Entry points from fourslash.ts
|
// Entry points from fourslash.ts
|
||||||
public goToMarker(name: string | Marker = "") {
|
public goToMarker(name: string | Marker = "") {
|
||||||
const marker = typeof name === "string" ? this.getMarkerByName(name) : name;
|
const marker = ts.isString(name) ? this.getMarkerByName(name) : name;
|
||||||
if (this.activeFile.fileName !== marker.fileName) {
|
if (this.activeFile.fileName !== marker.fileName) {
|
||||||
this.openFile(marker.fileName);
|
this.openFile(marker.fileName);
|
||||||
}
|
}
|
||||||
|
@ -403,7 +403,7 @@ namespace FourSlash {
|
||||||
if (marker.position === -1 || marker.position > content.length) {
|
if (marker.position === -1 || marker.position > content.length) {
|
||||||
throw new Error(`Marker "${name}" has been invalidated by unrecoverable edits to the file.`);
|
throw new Error(`Marker "${name}" has been invalidated by unrecoverable edits to the file.`);
|
||||||
}
|
}
|
||||||
const mName = typeof name === "string" ? name : this.markerName(marker);
|
const mName = ts.isString(name) ? name : this.markerName(marker);
|
||||||
this.lastKnownMarker = mName;
|
this.lastKnownMarker = mName;
|
||||||
this.goToPosition(marker.position);
|
this.goToPosition(marker.position);
|
||||||
}
|
}
|
||||||
|
@ -1026,7 +1026,7 @@ namespace FourSlash {
|
||||||
|
|
||||||
public verifyNoReferences(markerNameOrRange?: string | Range) {
|
public verifyNoReferences(markerNameOrRange?: string | Range) {
|
||||||
if (markerNameOrRange) {
|
if (markerNameOrRange) {
|
||||||
if (typeof markerNameOrRange === "string") {
|
if (ts.isString(markerNameOrRange)) {
|
||||||
this.goToMarker(markerNameOrRange);
|
this.goToMarker(markerNameOrRange);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1522,7 +1522,7 @@ namespace FourSlash {
|
||||||
resultString += "Diagnostics:" + Harness.IO.newLine();
|
resultString += "Diagnostics:" + Harness.IO.newLine();
|
||||||
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram());
|
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram());
|
||||||
for (const diagnostic of diagnostics) {
|
for (const diagnostic of diagnostics) {
|
||||||
if (typeof diagnostic.messageText !== "string") {
|
if (!ts.isString(diagnostic.messageText)) {
|
||||||
let chainedMessage = <ts.DiagnosticMessageChain>diagnostic.messageText;
|
let chainedMessage = <ts.DiagnosticMessageChain>diagnostic.messageText;
|
||||||
let indentation = " ";
|
let indentation = " ";
|
||||||
while (chainedMessage) {
|
while (chainedMessage) {
|
||||||
|
@ -2913,7 +2913,7 @@ namespace FourSlash {
|
||||||
result = this.testData.files[index];
|
result = this.testData.files[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (typeof indexOrName === "string") {
|
else if (ts.isString(indexOrName)) {
|
||||||
let name = <string>indexOrName;
|
let name = <string>indexOrName;
|
||||||
|
|
||||||
// names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName
|
// names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName
|
||||||
|
|
|
@ -225,7 +225,7 @@ namespace Utils {
|
||||||
return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " ");
|
return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " ");
|
||||||
|
|
||||||
function getKindName(k: number | string): string {
|
function getKindName(k: number | string): string {
|
||||||
if (typeof k === "string") {
|
if (ts.isString(k)) {
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,6 +754,10 @@ namespace Harness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockHash(s: string): string {
|
||||||
|
return `hash-${s}`;
|
||||||
|
}
|
||||||
|
|
||||||
const environment = Utils.getExecutionEnvironment();
|
const environment = Utils.getExecutionEnvironment();
|
||||||
switch (environment) {
|
switch (environment) {
|
||||||
case Utils.ExecutionEnvironment.Node:
|
case Utils.ExecutionEnvironment.Node:
|
||||||
|
|
|
@ -855,8 +855,4 @@ namespace Harness.LanguageService {
|
||||||
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
|
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
|
||||||
getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
|
getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mockHash(s: string): string {
|
|
||||||
return `hash-${s}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ class ProjectRunner extends RunnerBase {
|
||||||
if (option) {
|
if (option) {
|
||||||
const optType = option.type;
|
const optType = option.type;
|
||||||
let value = <any>testCase[name];
|
let value = <any>testCase[name];
|
||||||
if (typeof optType !== "string") {
|
if (!ts.isString(optType)) {
|
||||||
const key = value.toLowerCase();
|
const key = value.toLowerCase();
|
||||||
const optTypeValue = optType.get(key);
|
const optTypeValue = optType.get(key);
|
||||||
if (optTypeValue) {
|
if (optTypeValue) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig-base",
|
"extends": "../tsconfig-base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
|
@ -44,6 +44,7 @@
|
||||||
"../compiler/declarationEmitter.ts",
|
"../compiler/declarationEmitter.ts",
|
||||||
"../compiler/emitter.ts",
|
"../compiler/emitter.ts",
|
||||||
"../compiler/program.ts",
|
"../compiler/program.ts",
|
||||||
|
"../compiler/builder.ts",
|
||||||
"../compiler/commandLineParser.ts",
|
"../compiler/commandLineParser.ts",
|
||||||
"../compiler/diagnosticInformationMap.generated.ts",
|
"../compiler/diagnosticInformationMap.generated.ts",
|
||||||
"../services/breakpoints.ts",
|
"../services/breakpoints.ts",
|
||||||
|
@ -92,6 +93,7 @@
|
||||||
"rwcRunner.ts",
|
"rwcRunner.ts",
|
||||||
"test262Runner.ts",
|
"test262Runner.ts",
|
||||||
"runner.ts",
|
"runner.ts",
|
||||||
|
"virtualFileSystemWithWatch.ts",
|
||||||
"../server/protocol.ts",
|
"../server/protocol.ts",
|
||||||
"../server/session.ts",
|
"../server/session.ts",
|
||||||
"../server/client.ts",
|
"../server/client.ts",
|
||||||
|
@ -115,6 +117,7 @@
|
||||||
"./unittests/convertCompilerOptionsFromJson.ts",
|
"./unittests/convertCompilerOptionsFromJson.ts",
|
||||||
"./unittests/convertTypeAcquisitionFromJson.ts",
|
"./unittests/convertTypeAcquisitionFromJson.ts",
|
||||||
"./unittests/tsserverProjectSystem.ts",
|
"./unittests/tsserverProjectSystem.ts",
|
||||||
|
"./unittests/tscWatchMode.ts",
|
||||||
"./unittests/matchFiles.ts",
|
"./unittests/matchFiles.ts",
|
||||||
"./unittests/initializeTSConfig.ts",
|
"./unittests/initializeTSConfig.ts",
|
||||||
"./unittests/compileOnSave.ts",
|
"./unittests/compileOnSave.ts",
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace ts {
|
||||||
clearTimeout,
|
clearTimeout,
|
||||||
setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0),
|
setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0),
|
||||||
clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout,
|
clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout,
|
||||||
createHash: Harness.LanguageService.mockHash,
|
createHash: Harness.mockHash,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ namespace ts {
|
||||||
typingsInstaller: undefined
|
typingsInstaller: undefined
|
||||||
};
|
};
|
||||||
const projectService = new server.ProjectService(svcOpts);
|
const projectService = new server.ProjectService(svcOpts);
|
||||||
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
|
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, serverHost);
|
||||||
|
|
||||||
const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo);
|
const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo);
|
||||||
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
|
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
|
||||||
|
@ -188,7 +188,8 @@ namespace ts {
|
||||||
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
|
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
|
||||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||||
assert.isTrue(diags.length === 1, "one diagnostic expected");
|
assert.isTrue(diags.length === 1, "one diagnostic expected");
|
||||||
assert.isTrue(typeof diags[0].messageText === "string" && ((<string>diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
|
const messageText = diags[0].messageText;
|
||||||
|
assert.isTrue(isString(messageText) && messageText.indexOf("Cannot find module") === 0, "should be 'cannot find module' message");
|
||||||
|
|
||||||
fileMap.set(imported.name, imported);
|
fileMap.set(imported.name, imported);
|
||||||
fileExistsCalledForBar = false;
|
fileExistsCalledForBar = false;
|
||||||
|
|
|
@ -87,7 +87,7 @@ namespace ts {
|
||||||
"/dev/tests/scenarios/first.json": "",
|
"/dev/tests/scenarios/first.json": "",
|
||||||
"/dev/tests/baselines/first/output.ts": ""
|
"/dev/tests/baselines/first/output.ts": ""
|
||||||
});
|
});
|
||||||
const testContents = mapEntries(testContentsJson, (k, v) => [k, typeof v === "string" ? v : JSON.stringify(v)]);
|
const testContents = mapEntries(testContentsJson, (k, v) => [k, isString(v) ? v : JSON.stringify(v)]);
|
||||||
|
|
||||||
const caseInsensitiveBasePath = "c:/dev/";
|
const caseInsensitiveBasePath = "c:/dev/";
|
||||||
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));
|
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
/// <reference path="..\harness.ts" />
|
/// <reference path="..\harness.ts" />
|
||||||
|
|
||||||
namespace ts {
|
namespace ts {
|
||||||
|
function verifyMissingFilePaths(missingPaths: ReadonlyArray<Path>, expected: ReadonlyArray<string>) {
|
||||||
|
assert.isDefined(missingPaths);
|
||||||
|
const map = arrayToSet(expected) as Map<boolean>;
|
||||||
|
for (const missing of missingPaths) {
|
||||||
|
const value = map.get(missing);
|
||||||
|
assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`);
|
||||||
|
map.set(missing, false);
|
||||||
|
}
|
||||||
|
const notFound = mapDefinedIter(map.keys(), k => map.get(k) === true ? k : undefined);
|
||||||
|
assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`);
|
||||||
|
}
|
||||||
|
|
||||||
describe("Program.getMissingFilePaths", () => {
|
describe("Program.getMissingFilePaths", () => {
|
||||||
|
|
||||||
const options: CompilerOptions = {
|
const options: CompilerOptions = {
|
||||||
|
@ -40,34 +52,31 @@ namespace ts {
|
||||||
it("handles no missing root files", () => {
|
it("handles no missing root files", () => {
|
||||||
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
|
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.isDefined(missing);
|
verifyMissingFilePaths(missing, []);
|
||||||
assert.deepEqual(missing, []);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles missing root file", () => {
|
it("handles missing root file", () => {
|
||||||
const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
|
const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.isDefined(missing);
|
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
|
||||||
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles multiple missing root files", () => {
|
it("handles multiple missing root files", () => {
|
||||||
const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
|
const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths().sort();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
|
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles a mix of present and missing root files", () => {
|
it("handles a mix of present and missing root files", () => {
|
||||||
const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
|
const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths().sort();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
|
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles repeatedly specified root files", () => {
|
it("handles repeatedly specified root files", () => {
|
||||||
const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
|
const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.isDefined(missing);
|
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]);
|
||||||
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("normalizes file paths", () => {
|
it("normalizes file paths", () => {
|
||||||
|
@ -81,9 +90,8 @@ namespace ts {
|
||||||
|
|
||||||
it("handles missing triple slash references", () => {
|
it("handles missing triple slash references", () => {
|
||||||
const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
|
const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
|
||||||
const missing = program.getMissingFilePaths().sort();
|
const missing = program.getMissingFilePaths();
|
||||||
assert.isDefined(missing);
|
verifyMissingFilePaths(missing, [
|
||||||
assert.deepEqual(missing, [
|
|
||||||
// From absolute reference
|
// From absolute reference
|
||||||
"d:/imaginary/nonexistent1.ts",
|
"d:/imaginary/nonexistent1.ts",
|
||||||
|
|
||||||
|
@ -100,4 +108,4 @@ namespace ts {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,7 +346,7 @@ namespace ts {
|
||||||
const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts);
|
const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts);
|
||||||
assert.deepEqual(emptyArray, program_2.getMissingFilePaths());
|
assert.deepEqual(emptyArray, program_2.getMissingFilePaths());
|
||||||
|
|
||||||
assert.equal(StructureIsReused.SafeModules, program_1.structureIsReused);
|
assert.equal(StructureIsReused.Not, program_1.structureIsReused);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolution cache follows imports", () => {
|
it("resolution cache follows imports", () => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace ts.server {
|
||||||
clearTimeout: noop,
|
clearTimeout: noop,
|
||||||
setImmediate: () => 0,
|
setImmediate: () => 0,
|
||||||
clearImmediate: noop,
|
clearImmediate: noop,
|
||||||
createHash: Harness.LanguageService.mockHash,
|
createHash: Harness.mockHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TestSession extends Session {
|
class TestSession extends Session {
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace ts.projectSystem {
|
||||||
|
|
||||||
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
|
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
|
||||||
et.assertProjectInfoTelemetryEvent({
|
et.assertProjectInfoTelemetryEvent({
|
||||||
projectId: Harness.LanguageService.mockHash("/hunter2/foo.csproj"),
|
projectId: Harness.mockHash("/hunter2/foo.csproj"),
|
||||||
compilerOptions: { strict: true },
|
compilerOptions: { strict: true },
|
||||||
compileOnSave: true,
|
compileOnSave: true,
|
||||||
// These properties can't be present for an external project, so they are undefined instead of false.
|
// These properties can't be present for an external project, so they are undefined instead of false.
|
||||||
|
@ -195,7 +195,7 @@ namespace ts.projectSystem {
|
||||||
const et = new EventTracker([jsconfig, file]);
|
const et = new EventTracker([jsconfig, file]);
|
||||||
et.service.openClientFile(file.path);
|
et.service.openClientFile(file.path);
|
||||||
et.assertProjectInfoTelemetryEvent({
|
et.assertProjectInfoTelemetryEvent({
|
||||||
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
|
projectId: Harness.mockHash("/jsconfig.json"),
|
||||||
fileStats: fileStats({ js: 1 }),
|
fileStats: fileStats({ js: 1 }),
|
||||||
compilerOptions: autoJsCompilerOptions,
|
compilerOptions: autoJsCompilerOptions,
|
||||||
typeAcquisition: {
|
typeAcquisition: {
|
||||||
|
@ -215,7 +215,7 @@ namespace ts.projectSystem {
|
||||||
et.service.openClientFile(file.path);
|
et.service.openClientFile(file.path);
|
||||||
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true);
|
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true);
|
||||||
et.assertProjectInfoTelemetryEvent({
|
et.assertProjectInfoTelemetryEvent({
|
||||||
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
|
projectId: Harness.mockHash("/jsconfig.json"),
|
||||||
fileStats: fileStats({ js: 1 }),
|
fileStats: fileStats({ js: 1 }),
|
||||||
compilerOptions: autoJsCompilerOptions,
|
compilerOptions: autoJsCompilerOptions,
|
||||||
configFileName: "jsconfig.json",
|
configFileName: "jsconfig.json",
|
||||||
|
@ -251,7 +251,7 @@ namespace ts.projectSystem {
|
||||||
|
|
||||||
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>): void {
|
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>): void {
|
||||||
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
|
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
|
||||||
projectId: Harness.LanguageService.mockHash("/tsconfig.json"),
|
projectId: Harness.mockHash("/tsconfig.json"),
|
||||||
fileStats: fileStats({ ts: 1 }),
|
fileStats: fileStats({ ts: 1 }),
|
||||||
compilerOptions: {},
|
compilerOptions: {},
|
||||||
extends: false,
|
extends: false,
|
||||||
|
@ -282,7 +282,7 @@ namespace ts.projectSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
|
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
|
||||||
return { path, content: typeof content === "string" ? "" : JSON.stringify(content) };
|
return { path, content: isString(content) ? "" : JSON.stringify(content) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {
|
function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {
|
||||||
|
|
1657
src/harness/unittests/tscWatchMode.ts
Normal file
1657
src/harness/unittests/tscWatchMode.ts
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
550
src/harness/virtualFileSystemWithWatch.ts
Normal file
550
src/harness/virtualFileSystemWithWatch.ts
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
/// <reference path="harness.ts" />
|
||||||
|
|
||||||
|
namespace ts.TestFSWithWatch {
|
||||||
|
const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO);
|
||||||
|
export const libFile: FileOrFolder = {
|
||||||
|
path: "/a/lib/lib.d.ts",
|
||||||
|
content: libFileContent
|
||||||
|
};
|
||||||
|
|
||||||
|
export const safeList = {
|
||||||
|
path: <Path>"/safeList.json",
|
||||||
|
content: JSON.stringify({
|
||||||
|
commander: "commander",
|
||||||
|
express: "express",
|
||||||
|
jquery: "jquery",
|
||||||
|
lodash: "lodash",
|
||||||
|
moment: "moment",
|
||||||
|
chroma: "chroma-js"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
function getExecutingFilePathFromLibFile(): string {
|
||||||
|
return combinePaths(getDirectoryPath(libFile.path), "tsc.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestServerHostCreationParameters {
|
||||||
|
useCaseSensitiveFileNames?: boolean;
|
||||||
|
executingFilePath?: string;
|
||||||
|
currentDirectory?: string;
|
||||||
|
newLine?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWatchedSystem(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||||
|
if (!params) {
|
||||||
|
params = {};
|
||||||
|
}
|
||||||
|
const host = new TestServerHost(/*withSafelist*/ false,
|
||||||
|
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||||
|
params.executingFilePath || getExecutingFilePathFromLibFile(),
|
||||||
|
params.currentDirectory || "/",
|
||||||
|
fileOrFolderList,
|
||||||
|
params.newLine);
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createServerHost(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||||
|
if (!params) {
|
||||||
|
params = {};
|
||||||
|
}
|
||||||
|
const host = new TestServerHost(/*withSafelist*/ true,
|
||||||
|
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||||
|
params.executingFilePath || getExecutingFilePathFromLibFile(),
|
||||||
|
params.currentDirectory || "/",
|
||||||
|
fileOrFolderList,
|
||||||
|
params.newLine);
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileOrFolder {
|
||||||
|
path: string;
|
||||||
|
content?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FSEntry {
|
||||||
|
path: Path;
|
||||||
|
fullPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface File extends FSEntry {
|
||||||
|
content: string;
|
||||||
|
fileSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Folder extends FSEntry {
|
||||||
|
entries: FSEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFolder(s: FSEntry): s is Folder {
|
||||||
|
return s && isArray((<Folder>s).entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFile(s: FSEntry): s is File {
|
||||||
|
return s && isString((<File>s).content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeWatcherCallbacks<T>(callbacks: T[], invokeCallback: (cb: T) => void): void {
|
||||||
|
if (callbacks) {
|
||||||
|
// The array copy is made to ensure that even if one of the callback removes the callbacks,
|
||||||
|
// we dont miss any callbacks following it
|
||||||
|
const cbs = callbacks.slice();
|
||||||
|
for (const cb of cbs) {
|
||||||
|
invokeCallback(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: string[]) {
|
||||||
|
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`);
|
||||||
|
for (const name of expectedKeys) {
|
||||||
|
assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) {
|
||||||
|
assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`);
|
||||||
|
for (const f of expectedFileNames) {
|
||||||
|
assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
|
||||||
|
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) {
|
||||||
|
checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray<string>) {
|
||||||
|
const mapExpected = arrayToSet(expected);
|
||||||
|
const mapSeen = createMap<true>();
|
||||||
|
for (const f of host.getOutput()) {
|
||||||
|
assert.isUndefined(mapSeen.get(f), `Already found ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||||
|
if (mapExpected.has(f)) {
|
||||||
|
mapExpected.delete(f);
|
||||||
|
mapSeen.set(f, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray<string>) {
|
||||||
|
const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent);
|
||||||
|
for (const f of host.getOutput()) {
|
||||||
|
assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Callbacks {
|
||||||
|
private map: TimeOutCallback[] = [];
|
||||||
|
private nextId = 1;
|
||||||
|
|
||||||
|
register(cb: (...args: any[]) => void, args: any[]) {
|
||||||
|
const timeoutId = this.nextId;
|
||||||
|
this.nextId++;
|
||||||
|
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
|
||||||
|
return timeoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister(id: any) {
|
||||||
|
if (typeof id === "number") {
|
||||||
|
delete this.map[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count() {
|
||||||
|
let n = 0;
|
||||||
|
for (const _ in this.map) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke() {
|
||||||
|
// Note: invoking a callback may result in new callbacks been queued,
|
||||||
|
// so do not clear the entire callback list regardless. Only remove the
|
||||||
|
// ones we have invoked.
|
||||||
|
for (const key in this.map) {
|
||||||
|
this.map[key]();
|
||||||
|
delete this.map[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeOutCallback = () => any;
|
||||||
|
|
||||||
|
export class TestServerHost implements server.ServerHost {
|
||||||
|
args: string[] = [];
|
||||||
|
|
||||||
|
private readonly output: string[] = [];
|
||||||
|
|
||||||
|
private fs: Map<FSEntry> = createMap<FSEntry>();
|
||||||
|
private getCanonicalFileName: (s: string) => string;
|
||||||
|
private toPath: (f: string) => Path;
|
||||||
|
private timeoutCallbacks = new Callbacks();
|
||||||
|
private immediateCallbacks = new Callbacks();
|
||||||
|
|
||||||
|
readonly watchedDirectories = createMultiMap<DirectoryWatcherCallback>();
|
||||||
|
readonly watchedDirectoriesRecursive = createMultiMap<DirectoryWatcherCallback>();
|
||||||
|
readonly watchedFiles = createMultiMap<FileWatcherCallback>();
|
||||||
|
|
||||||
|
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") {
|
||||||
|
this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||||
|
this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
|
||||||
|
|
||||||
|
this.reloadFS(fileOrFolderList);
|
||||||
|
}
|
||||||
|
|
||||||
|
toNormalizedAbsolutePath(s: string) {
|
||||||
|
return getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
toFullPath(s: string) {
|
||||||
|
return this.toPath(this.toNormalizedAbsolutePath(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadFS(fileOrFolderList: FileOrFolder[]) {
|
||||||
|
const mapNewLeaves = createMap<true>();
|
||||||
|
const isNewFs = this.fs.size === 0;
|
||||||
|
// always inject safelist file in the list of files
|
||||||
|
for (const fileOrFolder of fileOrFolderList.concat(this.withSafeList ? safeList : [])) {
|
||||||
|
const path = this.toFullPath(fileOrFolder.path);
|
||||||
|
mapNewLeaves.set(path, true);
|
||||||
|
// If its a change
|
||||||
|
const currentEntry = this.fs.get(path);
|
||||||
|
if (currentEntry) {
|
||||||
|
if (isFile(currentEntry)) {
|
||||||
|
if (isString(fileOrFolder.content)) {
|
||||||
|
// Update file
|
||||||
|
if (currentEntry.content !== fileOrFolder.content) {
|
||||||
|
currentEntry.content = fileOrFolder.content;
|
||||||
|
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Changing from file => folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Folder
|
||||||
|
if (isString(fileOrFolder.content)) {
|
||||||
|
// TODO: Changing from folder => file
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Folder update: Nothing to do.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.ensureFileOrFolder(fileOrFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNewFs) {
|
||||||
|
this.fs.forEach((fileOrFolder, path) => {
|
||||||
|
// If this entry is not from the new file or folder
|
||||||
|
if (!mapNewLeaves.get(path)) {
|
||||||
|
// Leaf entries that arent in new list => remove these
|
||||||
|
if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) {
|
||||||
|
this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureFileOrFolder(fileOrFolder: FileOrFolder) {
|
||||||
|
if (isString(fileOrFolder.content)) {
|
||||||
|
const file = this.toFile(fileOrFolder);
|
||||||
|
Debug.assert(!this.fs.get(file.path));
|
||||||
|
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
|
||||||
|
this.addFileOrFolderInFolder(baseFolder, file);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
|
||||||
|
this.ensureFolder(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureFolder(fullPath: string): Folder {
|
||||||
|
const path = this.toPath(fullPath);
|
||||||
|
let folder = this.fs.get(path) as Folder;
|
||||||
|
if (!folder) {
|
||||||
|
folder = this.toFolder(fullPath);
|
||||||
|
const baseFullPath = getDirectoryPath(fullPath);
|
||||||
|
if (fullPath !== baseFullPath) {
|
||||||
|
// Add folder in the base folder
|
||||||
|
const baseFolder = this.ensureFolder(baseFullPath);
|
||||||
|
this.addFileOrFolderInFolder(baseFolder, folder);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// root folder
|
||||||
|
Debug.assert(this.fs.size === 0);
|
||||||
|
this.fs.set(path, folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.assert(isFolder(folder));
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) {
|
||||||
|
folder.entries.push(fileOrFolder);
|
||||||
|
this.fs.set(fileOrFolder.path, fileOrFolder);
|
||||||
|
|
||||||
|
if (isFile(fileOrFolder)) {
|
||||||
|
this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created);
|
||||||
|
}
|
||||||
|
this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) {
|
||||||
|
const basePath = getDirectoryPath(fileOrFolder.path);
|
||||||
|
const baseFolder = this.fs.get(basePath) as Folder;
|
||||||
|
if (basePath !== fileOrFolder.path) {
|
||||||
|
Debug.assert(!!baseFolder);
|
||||||
|
filterMutate(baseFolder.entries, entry => entry !== fileOrFolder);
|
||||||
|
}
|
||||||
|
this.fs.delete(fileOrFolder.path);
|
||||||
|
|
||||||
|
if (isFile(fileOrFolder)) {
|
||||||
|
this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Debug.assert(fileOrFolder.entries.length === 0);
|
||||||
|
const relativePath = this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath);
|
||||||
|
// Invoke directory and recursive directory watcher for the folder
|
||||||
|
// Here we arent invoking recursive directory watchers for the base folders
|
||||||
|
// since that is something we would want to do for both file as well as folder we are deleting
|
||||||
|
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => cb(relativePath));
|
||||||
|
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => cb(relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath !== fileOrFolder.path) {
|
||||||
|
if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) {
|
||||||
|
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) {
|
||||||
|
const callbacks = this.watchedFiles.get(this.toPath(fileFullPath));
|
||||||
|
const fileName = getBaseFileName(fileFullPath);
|
||||||
|
invokeWatcherCallbacks(callbacks, cb => cb(fileName, eventKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
|
||||||
|
return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will call the directory watcher for the folderFullPath and recursive directory watchers for this and base folders
|
||||||
|
*/
|
||||||
|
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
|
||||||
|
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
|
||||||
|
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => cb(relativePath));
|
||||||
|
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will call the recursive directory watcher for this directory as well as all the base directories
|
||||||
|
*/
|
||||||
|
private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) {
|
||||||
|
const relativePath = this.getRelativePathToDirectory(fullPath, fileName);
|
||||||
|
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => cb(relativePath));
|
||||||
|
const basePath = getDirectoryPath(fullPath);
|
||||||
|
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
|
||||||
|
this.invokeRecursiveDirectoryWatcher(basePath, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toFile(fileOrFolder: FileOrFolder): File {
|
||||||
|
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
|
||||||
|
return {
|
||||||
|
path: this.toPath(fullPath),
|
||||||
|
content: fileOrFolder.content,
|
||||||
|
fullPath,
|
||||||
|
fileSize: fileOrFolder.fileSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toFolder(path: string): Folder {
|
||||||
|
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
|
||||||
|
return {
|
||||||
|
path: this.toPath(fullPath),
|
||||||
|
entries: [],
|
||||||
|
fullPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists(s: string) {
|
||||||
|
const path = this.toFullPath(s);
|
||||||
|
return isFile(this.fs.get(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(s: string) {
|
||||||
|
const fsEntry = this.fs.get(this.toFullPath(s));
|
||||||
|
return isFile(fsEntry) ? fsEntry.content : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileSize(s: string) {
|
||||||
|
const path = this.toFullPath(s);
|
||||||
|
const entry = this.fs.get(path);
|
||||||
|
if (isFile(entry)) {
|
||||||
|
return entry.fileSize ? entry.fileSize : entry.content.length;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
directoryExists(s: string) {
|
||||||
|
const path = this.toFullPath(s);
|
||||||
|
return isFolder(this.fs.get(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirectories(s: string) {
|
||||||
|
const path = this.toFullPath(s);
|
||||||
|
const folder = this.fs.get(path);
|
||||||
|
if (isFolder(folder)) {
|
||||||
|
return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
|
||||||
|
}
|
||||||
|
Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||||
|
return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
|
||||||
|
const directories: string[] = [];
|
||||||
|
const files: string[] = [];
|
||||||
|
const dirEntry = this.fs.get(this.toPath(dir));
|
||||||
|
if (isFolder(dirEntry)) {
|
||||||
|
dirEntry.entries.forEach((entry) => {
|
||||||
|
if (isFolder(entry)) {
|
||||||
|
directories.push(getBaseFileName(entry.fullPath));
|
||||||
|
}
|
||||||
|
else if (isFile(entry)) {
|
||||||
|
files.push(getBaseFileName(entry.fullPath));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Debug.fail("Unknown entry");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { directories, files };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher {
|
||||||
|
const path = this.toFullPath(directoryName);
|
||||||
|
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
|
||||||
|
map.add(path, callback);
|
||||||
|
return {
|
||||||
|
referenceCount: 0,
|
||||||
|
directoryName,
|
||||||
|
close: () => map.remove(path, callback)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createHash(s: string): string {
|
||||||
|
return Harness.mockHash(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchFile(fileName: string, callback: FileWatcherCallback) {
|
||||||
|
const path = this.toFullPath(fileName);
|
||||||
|
this.watchedFiles.add(path, callback);
|
||||||
|
return { close: () => this.watchedFiles.remove(path, callback) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOOD: record and invoke callbacks to simulate timer events
|
||||||
|
setTimeout(callback: TimeOutCallback, _time: number, ...args: any[]) {
|
||||||
|
return this.timeoutCallbacks.register(callback, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(timeoutId: any): void {
|
||||||
|
this.timeoutCallbacks.unregister(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTimeoutQueueLengthAndRun(expected: number) {
|
||||||
|
this.checkTimeoutQueueLength(expected);
|
||||||
|
this.runQueuedTimeoutCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTimeoutQueueLength(expected: number) {
|
||||||
|
const callbacksCount = this.timeoutCallbacks.count();
|
||||||
|
assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
runQueuedTimeoutCallbacks() {
|
||||||
|
try {
|
||||||
|
this.timeoutCallbacks.invoke();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e.message === this.existMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runQueuedImmediateCallbacks() {
|
||||||
|
this.immediateCallbacks.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
setImmediate(callback: TimeOutCallback, _time: number, ...args: any[]) {
|
||||||
|
return this.immediateCallbacks.register(callback, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearImmediate(timeoutId: any): void {
|
||||||
|
this.immediateCallbacks.unregister(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
createDirectory(directoryName: string): void {
|
||||||
|
const folder = this.toFolder(directoryName);
|
||||||
|
|
||||||
|
// base folder has to be present
|
||||||
|
const base = getDirectoryPath(folder.fullPath);
|
||||||
|
const baseFolder = this.fs.get(base) as Folder;
|
||||||
|
Debug.assert(isFolder(baseFolder));
|
||||||
|
|
||||||
|
Debug.assert(!this.fs.get(folder.path));
|
||||||
|
this.addFileOrFolderInFolder(baseFolder, folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(path: string, content: string): void {
|
||||||
|
const file = this.toFile({ path, content });
|
||||||
|
|
||||||
|
// base folder has to be present
|
||||||
|
const base = getDirectoryPath(file.fullPath);
|
||||||
|
const folder = this.fs.get(base) as Folder;
|
||||||
|
Debug.assert(isFolder(folder));
|
||||||
|
|
||||||
|
this.addFileOrFolderInFolder(folder, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(message: string) {
|
||||||
|
this.output.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutput(): ReadonlyArray<string> {
|
||||||
|
return this.output;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearOutput() {
|
||||||
|
clear(this.output);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly existMessage = "System Exit";
|
||||||
|
exitCode: number;
|
||||||
|
readonly resolvePath = (s: string) => s;
|
||||||
|
readonly getExecutingFilePath = () => this.executingFilePath;
|
||||||
|
readonly getCurrentDirectory = () => this.currentDirectory;
|
||||||
|
exit(exitCode?: number) {
|
||||||
|
this.exitCode = exitCode;
|
||||||
|
throw new Error(this.existMessage);
|
||||||
|
}
|
||||||
|
readonly getEnvironmentVariable = notImplemented;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,359 +0,0 @@
|
||||||
/// <reference path="..\compiler\commandLineParser.ts" />
|
|
||||||
/// <reference path="..\services\services.ts" />
|
|
||||||
/// <reference path="session.ts" />
|
|
||||||
|
|
||||||
namespace ts.server {
|
|
||||||
|
|
||||||
export function shouldEmitFile(scriptInfo: ScriptInfo) {
|
|
||||||
return !scriptInfo.hasMixedContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract file info that maintains a shape signature.
|
|
||||||
*/
|
|
||||||
export class BuilderFileInfo {
|
|
||||||
|
|
||||||
private lastCheckedShapeSignature: string;
|
|
||||||
|
|
||||||
constructor(public readonly scriptInfo: ScriptInfo, public readonly project: Project) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public isExternalModuleOrHasOnlyAmbientExternalModules() {
|
|
||||||
const sourceFile = this.getSourceFile();
|
|
||||||
return isExternalModule(sourceFile) || this.containsOnlyAmbientModules(sourceFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For script files that contains only ambient external modules, although they are not actually external module files,
|
|
||||||
* they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
|
|
||||||
* there are no point to rebuild all script files if these special files have changed. However, if any statement
|
|
||||||
* in the file is not ambient external module, we treat it as a regular script file.
|
|
||||||
*/
|
|
||||||
private containsOnlyAmbientModules(sourceFile: SourceFile) {
|
|
||||||
for (const statement of sourceFile.statements) {
|
|
||||||
if (statement.kind !== SyntaxKind.ModuleDeclaration || (<ModuleDeclaration>statement).name.kind !== SyntaxKind.StringLiteral) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private computeHash(text: string): string {
|
|
||||||
return this.project.projectService.host.createHash(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSourceFile(): SourceFile {
|
|
||||||
return this.project.getSourceFile(this.scriptInfo.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean} indicates if the shape signature has changed since last update.
|
|
||||||
*/
|
|
||||||
public updateShapeSignature() {
|
|
||||||
const sourceFile = this.getSourceFile();
|
|
||||||
if (!sourceFile) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastSignature = this.lastCheckedShapeSignature;
|
|
||||||
if (sourceFile.isDeclarationFile) {
|
|
||||||
this.lastCheckedShapeSignature = this.computeHash(sourceFile.text);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const emitOutput = this.project.getFileEmitOutput(this.scriptInfo, /*emitOnlyDtsFiles*/ true);
|
|
||||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
|
||||||
this.lastCheckedShapeSignature = this.computeHash(emitOutput.outputFiles[0].text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !lastSignature || this.lastCheckedShapeSignature !== lastSignature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Builder {
|
|
||||||
readonly project: Project;
|
|
||||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
|
||||||
onProjectUpdateGraph(): void;
|
|
||||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean;
|
|
||||||
clear(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractBuilder<T extends BuilderFileInfo> implements Builder {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* stores set of files from the project.
|
|
||||||
* NOTE: this field is created on demand and should not be accessed directly.
|
|
||||||
* Use 'getFileInfos' instead.
|
|
||||||
*/
|
|
||||||
private fileInfos_doNotAccessDirectly: Map<T>;
|
|
||||||
|
|
||||||
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFileInfos() {
|
|
||||||
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createMap<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected hasFileInfos() {
|
|
||||||
return !!this.fileInfos_doNotAccessDirectly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public clear() {
|
|
||||||
// drop the existing list - it will be re-created as necessary
|
|
||||||
this.fileInfos_doNotAccessDirectly = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getFileInfo(path: Path): T {
|
|
||||||
return this.getFileInfos().get(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getOrCreateFileInfo(path: Path): T {
|
|
||||||
let fileInfo = this.getFileInfo(path);
|
|
||||||
if (!fileInfo) {
|
|
||||||
const scriptInfo = this.project.getScriptInfo(path);
|
|
||||||
fileInfo = new this.ctor(scriptInfo, this.project);
|
|
||||||
this.setFileInfo(path, fileInfo);
|
|
||||||
}
|
|
||||||
return fileInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getFileInfoPaths(): Path[] {
|
|
||||||
return arrayFrom(this.getFileInfos().keys() as Iterator<Path>);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setFileInfo(path: Path, info: T) {
|
|
||||||
this.getFileInfos().set(path, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected removeFileInfo(path: Path) {
|
|
||||||
this.getFileInfos().delete(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected forEachFileInfo(action: (fileInfo: T) => any) {
|
|
||||||
this.getFileInfos().forEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
|
||||||
abstract onProjectUpdateGraph(): void;
|
|
||||||
protected abstract ensureFileInfoIfInProject(scriptInfo: ScriptInfo): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} whether the emit was conducted or not
|
|
||||||
*/
|
|
||||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
|
|
||||||
this.ensureFileInfoIfInProject(scriptInfo);
|
|
||||||
const fileInfo = this.getFileInfo(scriptInfo.path);
|
|
||||||
if (!fileInfo) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false);
|
|
||||||
if (!emitSkipped) {
|
|
||||||
const projectRootPath = this.project.getProjectRootPath();
|
|
||||||
for (const outputFile of outputFiles) {
|
|
||||||
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
|
|
||||||
writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !emitSkipped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NonModuleBuilder extends AbstractBuilder<BuilderFileInfo> {
|
|
||||||
|
|
||||||
constructor(public readonly project: Project) {
|
|
||||||
super(project, BuilderFileInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) {
|
|
||||||
if (this.project.containsScriptInfo(scriptInfo)) {
|
|
||||||
this.getOrCreateFileInfo(scriptInfo.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onProjectUpdateGraph() {
|
|
||||||
if (this.hasFileInfos()) {
|
|
||||||
this.forEachFileInfo(fileInfo => {
|
|
||||||
if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) {
|
|
||||||
// This file was deleted from this project
|
|
||||||
this.removeFileInfo(fileInfo.scriptInfo.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: didn't use path as parameter because the returned file names will be directly
|
|
||||||
* consumed by the API user, which will use it to interact with file systems. Path
|
|
||||||
* should only be used internally, because the case sensitivity is not trustable.
|
|
||||||
*/
|
|
||||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[] {
|
|
||||||
const info = this.getOrCreateFileInfo(scriptInfo.path);
|
|
||||||
const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName];
|
|
||||||
if (info.updateShapeSignature()) {
|
|
||||||
const options = this.project.getCompilerOptions();
|
|
||||||
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
|
|
||||||
// so returning the file itself is good enough.
|
|
||||||
if (options && (options.out || options.outFile)) {
|
|
||||||
return singleFileResult;
|
|
||||||
}
|
|
||||||
return this.project.getAllEmittableFiles();
|
|
||||||
}
|
|
||||||
return singleFileResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModuleBuilderFileInfo extends BuilderFileInfo {
|
|
||||||
references = createSortedArray<ModuleBuilderFileInfo>();
|
|
||||||
readonly referencedBy = createSortedArray<ModuleBuilderFileInfo>();
|
|
||||||
scriptVersionForReferences: string;
|
|
||||||
|
|
||||||
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison {
|
|
||||||
return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
addReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
|
||||||
insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
|
||||||
removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFileReferences() {
|
|
||||||
for (const reference of this.references) {
|
|
||||||
reference.removeReferencedBy(this);
|
|
||||||
}
|
|
||||||
clear(this.references);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModuleBuilder extends AbstractBuilder<ModuleBuilderFileInfo> {
|
|
||||||
|
|
||||||
constructor(public readonly project: Project) {
|
|
||||||
super(project, ModuleBuilderFileInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private projectVersionForDependencyGraph: string;
|
|
||||||
|
|
||||||
public clear() {
|
|
||||||
this.projectVersionForDependencyGraph = undefined;
|
|
||||||
super.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray<ModuleBuilderFileInfo> {
|
|
||||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
|
||||||
return createSortedArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path);
|
|
||||||
return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) {
|
|
||||||
this.ensureProjectDependencyGraphUpToDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
onProjectUpdateGraph() {
|
|
||||||
// Update the graph only if we have computed graph earlier
|
|
||||||
if (this.hasFileInfos()) {
|
|
||||||
this.ensureProjectDependencyGraphUpToDate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureProjectDependencyGraphUpToDate() {
|
|
||||||
if (!this.projectVersionForDependencyGraph || this.project.getProjectVersion() !== this.projectVersionForDependencyGraph) {
|
|
||||||
const currentScriptInfos = this.project.getScriptInfos();
|
|
||||||
for (const scriptInfo of currentScriptInfos) {
|
|
||||||
const fileInfo = this.getOrCreateFileInfo(scriptInfo.path);
|
|
||||||
this.updateFileReferences(fileInfo);
|
|
||||||
}
|
|
||||||
this.forEachFileInfo(fileInfo => {
|
|
||||||
if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) {
|
|
||||||
// This file was deleted from this project
|
|
||||||
fileInfo.removeFileReferences();
|
|
||||||
this.removeFileInfo(fileInfo.scriptInfo.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.projectVersionForDependencyGraph = this.project.getProjectVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateFileReferences(fileInfo: ModuleBuilderFileInfo) {
|
|
||||||
// Only need to update if the content of the file changed.
|
|
||||||
if (fileInfo.scriptVersionForReferences === fileInfo.scriptInfo.getLatestVersion()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newReferences = this.getReferencedFileInfos(fileInfo);
|
|
||||||
const oldReferences = fileInfo.references;
|
|
||||||
enumerateInsertsAndDeletes(newReferences, oldReferences,
|
|
||||||
/*inserted*/ newReference => newReference.addReferencedBy(fileInfo),
|
|
||||||
/*deleted*/ oldReference => {
|
|
||||||
// New reference is greater then current reference. That means
|
|
||||||
// the current reference doesn't exist anymore after parsing. So delete
|
|
||||||
// references.
|
|
||||||
oldReference.removeReferencedBy(fileInfo);
|
|
||||||
},
|
|
||||||
/*compare*/ ModuleBuilderFileInfo.compareFileInfos);
|
|
||||||
|
|
||||||
fileInfo.references = newReferences;
|
|
||||||
fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[] {
|
|
||||||
this.ensureProjectDependencyGraphUpToDate();
|
|
||||||
|
|
||||||
const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName];
|
|
||||||
const fileInfo = this.getFileInfo(scriptInfo.path);
|
|
||||||
if (!fileInfo || !fileInfo.updateShapeSignature()) {
|
|
||||||
return singleFileResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
|
||||||
return this.project.getAllEmittableFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = this.project.getCompilerOptions();
|
|
||||||
if (options && (options.isolatedModules || options.out || options.outFile)) {
|
|
||||||
return singleFileResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we need to if each file in the referencedBy list has a shape change as well.
|
|
||||||
// Because if so, its own referencedBy files need to be saved as well to make the
|
|
||||||
// emitting result consistent with files on disk.
|
|
||||||
|
|
||||||
// Use slice to clone the array to avoid manipulating in place
|
|
||||||
const queue = fileInfo.referencedBy.slice(0);
|
|
||||||
const fileNameSet = createMap<ScriptInfo>();
|
|
||||||
fileNameSet.set(scriptInfo.fileName, scriptInfo);
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const processingFileInfo = queue.pop();
|
|
||||||
if (processingFileInfo.updateShapeSignature() && processingFileInfo.referencedBy.length > 0) {
|
|
||||||
for (const potentialFileInfo of processingFileInfo.referencedBy) {
|
|
||||||
if (!fileNameSet.has(potentialFileInfo.scriptInfo.fileName)) {
|
|
||||||
queue.push(potentialFileInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileNameSet.set(processingFileInfo.scriptInfo.fileName, processingFileInfo.scriptInfo);
|
|
||||||
}
|
|
||||||
const result: string[] = [];
|
|
||||||
fileNameSet.forEach((scriptInfo, fileName) => {
|
|
||||||
if (shouldEmitFile(scriptInfo)) {
|
|
||||||
result.push(fileName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createBuilder(project: Project): Builder {
|
|
||||||
const moduleKind = project.getCompilerOptions().module;
|
|
||||||
switch (moduleKind) {
|
|
||||||
case ModuleKind.None:
|
|
||||||
return new NonModuleBuilder(project);
|
|
||||||
default:
|
|
||||||
return new ModuleBuilder(project);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -342,7 +342,7 @@ namespace ts.server {
|
||||||
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
|
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
|
||||||
let category: DiagnosticCategory;
|
let category: DiagnosticCategory;
|
||||||
for (const id in DiagnosticCategory) {
|
for (const id in DiagnosticCategory) {
|
||||||
if (typeof id === "string" && entry.category === id.toLowerCase()) {
|
if (isString(id) && entry.category === id.toLowerCase()) {
|
||||||
category = (<any>DiagnosticCategory)[id];
|
category = (<any>DiagnosticCategory)[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,14 @@
|
||||||
namespace ts.server {
|
namespace ts.server {
|
||||||
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;
|
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;
|
||||||
|
|
||||||
export const ContextEvent = "context";
|
export const ProjectChangedEvent = "projectChanged";
|
||||||
export const ConfigFileDiagEvent = "configFileDiag";
|
export const ConfigFileDiagEvent = "configFileDiag";
|
||||||
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
|
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
|
||||||
export const ProjectInfoTelemetryEvent = "projectInfo";
|
export const ProjectInfoTelemetryEvent = "projectInfo";
|
||||||
|
|
||||||
export interface ContextEvent {
|
export interface ProjectChangedEvent {
|
||||||
eventName: typeof ContextEvent;
|
eventName: typeof ProjectChangedEvent;
|
||||||
data: { project: Project; fileName: NormalizedPath };
|
data: { project: Project; filesToEmit: string[]; changedFiles: string[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigFileDiagEvent {
|
export interface ConfigFileDiagEvent {
|
||||||
|
@ -77,7 +77,7 @@ namespace ts.server {
|
||||||
readonly dts: number;
|
readonly dts: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
|
export type ProjectServiceEvent = ProjectChangedEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
|
||||||
|
|
||||||
export interface ProjectServiceEventHandler {
|
export interface ProjectServiceEventHandler {
|
||||||
(event: ProjectServiceEvent): void;
|
(event: ProjectServiceEvent): void;
|
||||||
|
@ -159,7 +159,7 @@ namespace ts.server {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings {
|
export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings {
|
||||||
if (typeof protocolOptions.indentStyle === "string") {
|
if (isString(protocolOptions.indentStyle)) {
|
||||||
protocolOptions.indentStyle = indentStyle.get(protocolOptions.indentStyle.toLowerCase());
|
protocolOptions.indentStyle = indentStyle.get(protocolOptions.indentStyle.toLowerCase());
|
||||||
Debug.assert(protocolOptions.indentStyle !== undefined);
|
Debug.assert(protocolOptions.indentStyle !== undefined);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ namespace ts.server {
|
||||||
export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin {
|
export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin {
|
||||||
compilerOptionConverters.forEach((mappedValues, id) => {
|
compilerOptionConverters.forEach((mappedValues, id) => {
|
||||||
const propertyValue = protocolOptions[id];
|
const propertyValue = protocolOptions[id];
|
||||||
if (typeof propertyValue === "string") {
|
if (isString(propertyValue)) {
|
||||||
protocolOptions[id] = mappedValues.get(propertyValue.toLowerCase());
|
protocolOptions[id] = mappedValues.get(propertyValue.toLowerCase());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -177,9 +177,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
|
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
|
||||||
return typeof scriptKindName === "string"
|
return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName;
|
||||||
? convertScriptKindName(scriptKindName)
|
|
||||||
: scriptKindName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) {
|
export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) {
|
||||||
|
@ -249,7 +247,8 @@ namespace ts.server {
|
||||||
WildcardDirectories = "Wild card directory",
|
WildcardDirectories = "Wild card directory",
|
||||||
TypeRoot = "Type root of the project",
|
TypeRoot = "Type root of the project",
|
||||||
ClosedScriptInfo = "Closed Script info",
|
ClosedScriptInfo = "Closed Script info",
|
||||||
ConfigFileForInferredRoot = "Config file for the inferred project root"
|
ConfigFileForInferredRoot = "Config file for the inferred project root",
|
||||||
|
FailedLookupLocation = "Failed lookup locations in module resolution"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @internal */
|
/* @internal */
|
||||||
|
@ -493,9 +492,32 @@ namespace ts.server {
|
||||||
if (this.pendingProjectUpdates.delete(projectName)) {
|
if (this.pendingProjectUpdates.delete(projectName)) {
|
||||||
project.updateGraph();
|
project.updateGraph();
|
||||||
}
|
}
|
||||||
|
// Send the update event to notify about the project changes
|
||||||
|
this.sendProjectChangedEvent(project);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sendProjectChangedEvent(project: Project) {
|
||||||
|
if (project.isClosed() || !this.eventHandler || !project.languageServiceEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { filesToEmit, changedFiles } = project.getChangedFiles();
|
||||||
|
if (changedFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: ProjectChangedEvent = {
|
||||||
|
eventName: ProjectChangedEvent,
|
||||||
|
data: {
|
||||||
|
project,
|
||||||
|
filesToEmit: filesToEmit as string[],
|
||||||
|
changedFiles: changedFiles as string[]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.eventHandler(event);
|
||||||
|
}
|
||||||
|
|
||||||
/* @internal */
|
/* @internal */
|
||||||
delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) {
|
delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) {
|
||||||
this.delayUpdateProjectGraph(project);
|
this.delayUpdateProjectGraph(project);
|
||||||
|
@ -567,6 +589,11 @@ namespace ts.server {
|
||||||
return scriptInfo && !scriptInfo.isOrphan() && scriptInfo.getDefaultProject();
|
return scriptInfo && !scriptInfo.isOrphan() && scriptInfo.getDefaultProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScriptInfoEnsuringProjectsUptoDate(uncheckedFileName: string) {
|
||||||
|
this.ensureProjectStructuresUptoDate();
|
||||||
|
return this.getScriptInfo(uncheckedFileName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the project structures are upto date
|
* Ensures the project structures are upto date
|
||||||
* This means,
|
* This means,
|
||||||
|
@ -674,25 +701,12 @@ namespace ts.server {
|
||||||
|
|
||||||
// update projects to make sure that set of referenced files is correct
|
// update projects to make sure that set of referenced files is correct
|
||||||
this.delayUpdateProjectGraphs(containingProjects);
|
this.delayUpdateProjectGraphs(containingProjects);
|
||||||
|
|
||||||
// TODO: (sheetalkamat) Someway to send this event so that error checks are updated?
|
|
||||||
// if (!this.eventHandler) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (const openFile of this.openFiles) {
|
|
||||||
// const event: ContextEvent = {
|
|
||||||
// eventName: ContextEvent,
|
|
||||||
// data: { project: openFile.getDefaultProject(), fileName: openFile.fileName }
|
|
||||||
// };
|
|
||||||
// this.eventHandler(event);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @internal */
|
/* @internal */
|
||||||
onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) {
|
onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: NormalizedPath) {
|
||||||
project.getCachedServerHost().addOrDeleteFileOrFolder(fileName);
|
project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
|
||||||
project.updateTypes();
|
project.updateTypes();
|
||||||
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
|
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
|
||||||
}
|
}
|
||||||
|
@ -703,15 +717,15 @@ namespace ts.server {
|
||||||
* @param fileName the absolute file name that changed in watched directory
|
* @param fileName the absolute file name that changed in watched directory
|
||||||
*/
|
*/
|
||||||
/* @internal */
|
/* @internal */
|
||||||
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: NormalizedPath) {
|
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: NormalizedPath) {
|
||||||
project.getCachedServerHost().addOrDeleteFileOrFolder(fileName);
|
project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
|
||||||
const configFilename = project.getConfigFilePath();
|
const configFilename = project.getConfigFilePath();
|
||||||
|
|
||||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||||
// one with the fileName being "folder/file", and the other one with "folder".
|
// one with the fileName being "folder/file", and the other one with "folder".
|
||||||
// We don't respond to the second one.
|
// We don't respond to the second one.
|
||||||
if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
|
if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
|
||||||
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileName}`);
|
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1019,7 +1033,7 @@ namespace ts.server {
|
||||||
if (this.configuredProjects.has(canonicalConfigFilePath)) {
|
if (this.configuredProjects.has(canonicalConfigFilePath)) {
|
||||||
watches.push(WatchType.ConfigFilePath);
|
watches.push(WatchType.ConfigFilePath);
|
||||||
}
|
}
|
||||||
this.logger.info(`ConfigFilePresence:: Current Watches: ['${watches.join("','")}']:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
|
this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1371,7 +1385,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) {
|
private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) {
|
||||||
const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName);
|
const cachedServerHost = new CachedServerHost(this.host);
|
||||||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
|
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
|
||||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||||
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||||
|
@ -1425,7 +1439,7 @@ namespace ts.server {
|
||||||
else {
|
else {
|
||||||
const scriptKind = propertyReader.getScriptKind(f);
|
const scriptKind = propertyReader.getScriptKind(f);
|
||||||
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
||||||
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent);
|
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host);
|
||||||
path = scriptInfo.path;
|
path = scriptInfo.path;
|
||||||
// If this script info is not already a root add it
|
// If this script info is not already a root add it
|
||||||
if (!project.isRoot(scriptInfo)) {
|
if (!project.isRoot(scriptInfo)) {
|
||||||
|
@ -1579,9 +1593,12 @@ namespace ts.server {
|
||||||
* @param uncheckedFileName is absolute pathname
|
* @param uncheckedFileName is absolute pathname
|
||||||
* @param fileContent is a known version of the file content that is more up to date than the one on disk
|
* @param fileContent is a known version of the file content that is more up to date than the one on disk
|
||||||
*/
|
*/
|
||||||
|
/*@internal*/
|
||||||
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
|
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) {
|
||||||
return this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName), openedByClient, fileContent, scriptKind);
|
return this.getOrCreateScriptInfoForNormalizedPath(
|
||||||
|
toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined,
|
||||||
|
/*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getScriptInfo(uncheckedFileName: string) {
|
getScriptInfo(uncheckedFileName: string) {
|
||||||
|
@ -1606,11 +1623,11 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
|
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) {
|
||||||
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
|
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
|
||||||
let info = this.getScriptInfoForPath(path);
|
let info = this.getScriptInfoForPath(path);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
if (openedByClient || this.host.fileExists(fileName)) {
|
if (openedByClient || (hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
|
||||||
info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path);
|
info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path);
|
||||||
|
|
||||||
this.filenameToScriptInfo.set(info.path, info);
|
this.filenameToScriptInfo.set(info.path, info);
|
||||||
|
@ -2063,7 +2080,7 @@ namespace ts.server {
|
||||||
// RegExp group numbers are 1-based, but the first element in groups
|
// RegExp group numbers are 1-based, but the first element in groups
|
||||||
// is actually the original string, so it all works out in the end.
|
// is actually the original string, so it all works out in the end.
|
||||||
if (typeof groupNumberOrString === "number") {
|
if (typeof groupNumberOrString === "number") {
|
||||||
if (typeof groups[groupNumberOrString] !== "string") {
|
if (!isString(groups[groupNumberOrString])) {
|
||||||
// Specification was wrong - exclude nothing!
|
// Specification was wrong - exclude nothing!
|
||||||
this.logger.info(`Incorrect RegExp specification in safelist rule ${name} - not enough groups`);
|
this.logger.info(`Incorrect RegExp specification in safelist rule ${name} - not enough groups`);
|
||||||
// * can't appear in a filename; escape it because it's feeding into a RegExp
|
// * can't appear in a filename; escape it because it's feeding into a RegExp
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
/// <reference path="..\services\services.ts" />
|
/// <reference path="..\services\services.ts" />
|
||||||
/// <reference path="utilities.ts" />
|
/// <reference path="utilities.ts" />
|
||||||
/// <reference path="scriptInfo.ts" />
|
/// <reference path="scriptInfo.ts" />
|
||||||
|
/// <reference path="..\compiler\resolutionCache.ts" />
|
||||||
|
|
||||||
namespace ts.server {
|
namespace ts.server {
|
||||||
|
/*@internal*/
|
||||||
export class CachedServerHost implements ServerHost {
|
export class CachedServerHost implements ServerHost {
|
||||||
args: string[];
|
args: string[];
|
||||||
newLine: string;
|
newLine: string;
|
||||||
useCaseSensitiveFileNames: boolean;
|
useCaseSensitiveFileNames: boolean;
|
||||||
|
|
||||||
|
private readonly cachedPartialSystem: CachedPartialSystem;
|
||||||
|
|
||||||
readonly trace: (s: string) => void;
|
readonly trace: (s: string) => void;
|
||||||
readonly realpath?: (path: string) => string;
|
readonly realpath?: (path: string) => string;
|
||||||
|
|
||||||
private cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
constructor(private readonly host: ServerHost) {
|
||||||
private readonly currentDirectory: string;
|
|
||||||
|
|
||||||
constructor(private readonly host: ServerHost, private getCanonicalFileName: (fileName: string) => string) {
|
|
||||||
this.args = host.args;
|
this.args = host.args;
|
||||||
this.newLine = host.newLine;
|
this.newLine = host.newLine;
|
||||||
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
|
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
|
||||||
|
@ -24,41 +25,7 @@ namespace ts.server {
|
||||||
if (this.host.realpath) {
|
if (this.host.realpath) {
|
||||||
this.realpath = path => this.host.realpath(path);
|
this.realpath = path => this.host.realpath(path);
|
||||||
}
|
}
|
||||||
this.currentDirectory = this.host.getCurrentDirectory();
|
this.cachedPartialSystem = createCachedPartialSystem(host);
|
||||||
}
|
|
||||||
|
|
||||||
private toPath(fileName: string) {
|
|
||||||
return toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFileSystemEntries(rootDir: string) {
|
|
||||||
const path = this.toPath(rootDir);
|
|
||||||
const cachedResult = this.cachedReadDirectoryResult.get(path);
|
|
||||||
if (cachedResult) {
|
|
||||||
return cachedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultFromHost: FileSystemEntries = {
|
|
||||||
files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
|
|
||||||
directories: this.host.getDirectories(rootDir) || []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.cachedReadDirectoryResult.set(path, resultFromHost);
|
|
||||||
return resultFromHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
private canWorkWithCacheForDir(rootDir: string) {
|
|
||||||
// Some of the hosts might not be able to handle read directory or getDirectories
|
|
||||||
const path = this.toPath(rootDir);
|
|
||||||
if (this.cachedReadDirectoryResult.get(path)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return this.getFileSystemEntries(rootDir);
|
|
||||||
}
|
|
||||||
catch (_e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write(s: string) {
|
write(s: string) {
|
||||||
|
@ -66,13 +33,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
|
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
|
||||||
const path = this.toPath(fileName);
|
this.cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark);
|
||||||
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
|
||||||
const baseFileName = getBaseFileName(toNormalizedPath(fileName));
|
|
||||||
if (result) {
|
|
||||||
result.files = this.updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true);
|
|
||||||
}
|
|
||||||
return this.host.writeFile(fileName, data, writeByteOrderMark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvePath(path: string) {
|
resolvePath(path: string) {
|
||||||
|
@ -88,7 +49,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentDirectory() {
|
getCurrentDirectory() {
|
||||||
return this.currentDirectory;
|
return this.cachedPartialSystem.getCurrentDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(exitCode?: number) {
|
exit(exitCode?: number) {
|
||||||
|
@ -101,78 +62,35 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectories(rootDir: string) {
|
getDirectories(rootDir: string) {
|
||||||
if (this.canWorkWithCacheForDir(rootDir)) {
|
return this.cachedPartialSystem.getDirectories(rootDir);
|
||||||
return this.getFileSystemEntries(rootDir).directories.slice();
|
|
||||||
}
|
|
||||||
return this.host.getDirectories(rootDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
|
||||||
if (this.canWorkWithCacheForDir(rootDir)) {
|
return this.cachedPartialSystem.readDirectory(rootDir, extensions, excludes, includes, depth);
|
||||||
return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path));
|
|
||||||
}
|
|
||||||
return this.host.readDirectory(rootDir, extensions, excludes, includes, depth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileExists(fileName: string): boolean {
|
fileExists(fileName: string): boolean {
|
||||||
const path = this.toPath(fileName);
|
return this.cachedPartialSystem.fileExists(fileName);
|
||||||
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
|
||||||
const baseName = getBaseFileName(toNormalizedPath(fileName));
|
|
||||||
return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
directoryExists(dirPath: string) {
|
directoryExists(dirPath: string) {
|
||||||
const path = this.toPath(dirPath);
|
return this.cachedPartialSystem.directoryExists(dirPath);
|
||||||
return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readFile(path: string, encoding?: string): string {
|
readFile(path: string, encoding?: string): string {
|
||||||
return this.host.readFile(path, encoding);
|
return this.host.readFile(path, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fileNameEqual(name1: string, name2: string) {
|
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath, fileOrFolderPath: Path) {
|
||||||
return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2);
|
return this.cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasEntry(entries: ReadonlyArray<string>, name: string) {
|
addOrDeleteFile(file: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||||
return some(entries, file => this.fileNameEqual(file, name));
|
return this.cachedPartialSystem.addOrDeleteFile(file, path, eventKind);
|
||||||
}
|
|
||||||
|
|
||||||
private updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
|
|
||||||
if (this.hasEntry(entries, baseName)) {
|
|
||||||
if (!isValid) {
|
|
||||||
return filter(entries, entry => !this.fileNameEqual(entry, baseName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isValid) {
|
|
||||||
return entries.concat(baseName);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) {
|
|
||||||
const path = this.toPath(fileOrFolder);
|
|
||||||
const existingResult = this.cachedReadDirectoryResult.get(path);
|
|
||||||
if (existingResult) {
|
|
||||||
// This was a folder already present, remove it if this doesnt exist any more
|
|
||||||
if (!this.host.directoryExists(fileOrFolder)) {
|
|
||||||
this.cachedReadDirectoryResult.delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// This was earlier a file (hence not in cached directory contents)
|
|
||||||
// or we never cached the directory containing it
|
|
||||||
const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
|
|
||||||
if (parentResult) {
|
|
||||||
const baseName = getBaseFileName(fileOrFolder);
|
|
||||||
parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path));
|
|
||||||
parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCache() {
|
clearCache() {
|
||||||
this.cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
return this.cachedPartialSystem.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
|
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
|
||||||
|
@ -190,17 +108,16 @@ namespace ts.server {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean };
|
|
||||||
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
|
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
|
||||||
private compilationSettings: CompilerOptions;
|
/*@internal*/
|
||||||
private readonly resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
compilationSettings: CompilerOptions;
|
||||||
private readonly resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
|
||||||
|
|
||||||
private filesWithChangedSetOfUnresolvedImports: Path[];
|
|
||||||
|
|
||||||
private resolveModuleName: typeof resolveModuleName;
|
|
||||||
readonly trace: (s: string) => void;
|
readonly trace: (s: string) => void;
|
||||||
readonly realpath?: (path: string) => string;
|
readonly realpath?: (path: string) => string;
|
||||||
|
|
||||||
|
/*@internal*/
|
||||||
|
hasInvalidatedResolution: HasInvalidatedResolution;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the host that is associated with the project. This is normally same as projectService's host
|
* This is the host that is associated with the project. This is normally same as projectService's host
|
||||||
* except in Configured projects where it is CachedServerHost so that we can cache the results of the
|
* except in Configured projects where it is CachedServerHost so that we can cache the results of the
|
||||||
|
@ -217,25 +134,6 @@ namespace ts.server {
|
||||||
this.trace = s => host.trace(s);
|
this.trace = s => host.trace(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => {
|
|
||||||
const globalCache = this.project.getTypeAcquisition().enable
|
|
||||||
? this.project.projectService.typingsInstaller.globalTypingsCacheLocation
|
|
||||||
: undefined;
|
|
||||||
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
|
||||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
|
||||||
if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
|
|
||||||
// otherwise try to load typings from @types
|
|
||||||
|
|
||||||
// create different collection of failed lookup locations for second pass
|
|
||||||
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
|
||||||
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, this.project.getProjectName(), compilerOptions, host, globalCache);
|
|
||||||
if (resolvedModule) {
|
|
||||||
return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return primaryResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.host.realpath) {
|
if (this.host.realpath) {
|
||||||
this.realpath = path => this.host.realpath(path);
|
this.realpath = path => this.host.realpath(path);
|
||||||
}
|
}
|
||||||
|
@ -243,99 +141,9 @@ namespace ts.server {
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.project = undefined;
|
this.project = undefined;
|
||||||
this.resolveModuleName = undefined;
|
|
||||||
this.host = undefined;
|
this.host = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public startRecordingFilesWithChangedResolutions() {
|
|
||||||
this.filesWithChangedSetOfUnresolvedImports = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public finishRecordingFilesWithChangedResolutions() {
|
|
||||||
const collected = this.filesWithChangedSetOfUnresolvedImports;
|
|
||||||
this.filesWithChangedSetOfUnresolvedImports = undefined;
|
|
||||||
return collected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveNamesWithLocalCache<T extends NameResolutionWithFailedLookupLocations, R>(
|
|
||||||
names: string[],
|
|
||||||
containingFile: string,
|
|
||||||
cache: Map<Map<T>>,
|
|
||||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
|
||||||
getResult: (s: T) => R,
|
|
||||||
getResultFileName: (result: R) => string | undefined,
|
|
||||||
logChanges: boolean): R[] {
|
|
||||||
|
|
||||||
const path = this.project.projectService.toPath(containingFile);
|
|
||||||
const currentResolutionsInFile = cache.get(path);
|
|
||||||
|
|
||||||
const newResolutions: Map<T> = createMap<T>();
|
|
||||||
const resolvedModules: R[] = [];
|
|
||||||
const compilerOptions = this.getCompilationSettings();
|
|
||||||
|
|
||||||
for (const name of names) {
|
|
||||||
// check if this is a duplicate entry in the list
|
|
||||||
let resolution = newResolutions.get(name);
|
|
||||||
if (!resolution) {
|
|
||||||
const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name);
|
|
||||||
if (moduleResolutionIsValid(existingResolution)) {
|
|
||||||
// ok, it is safe to use existing name resolution results
|
|
||||||
resolution = existingResolution;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resolution = loader(name, containingFile, compilerOptions, this);
|
|
||||||
newResolutions.set(name, resolution);
|
|
||||||
}
|
|
||||||
if (logChanges && this.filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
|
||||||
this.filesWithChangedSetOfUnresolvedImports.push(path);
|
|
||||||
// reset log changes to avoid recording the same file multiple times
|
|
||||||
logChanges = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.assert(resolution !== undefined);
|
|
||||||
|
|
||||||
resolvedModules.push(getResult(resolution));
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace old results with a new one
|
|
||||||
cache.set(path, newResolutions);
|
|
||||||
return resolvedModules;
|
|
||||||
|
|
||||||
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
|
|
||||||
if (oldResolution === newResolution) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const oldResult = getResult(oldResolution);
|
|
||||||
const newResult = getResult(newResolution);
|
|
||||||
if (oldResult === newResult) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!oldResult || !newResult) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return getResultFileName(oldResult) === getResultFileName(newResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moduleResolutionIsValid(resolution: T): boolean {
|
|
||||||
if (!resolution || resolution.isInvalidated) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = getResult(resolution);
|
|
||||||
if (result) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// consider situation if we have no candidate locations as valid resolution.
|
|
||||||
// after all there is no point to invalidate it if we have no idea where to look for the module.
|
|
||||||
return resolution.failedLookupLocations.length === 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getNewLine() {
|
getNewLine() {
|
||||||
return this.host.newLine;
|
return this.host.newLine;
|
||||||
}
|
}
|
||||||
|
@ -357,13 +165,11 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective,
|
return this.project.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile);
|
||||||
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] {
|
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] {
|
||||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName,
|
return this.project.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true);
|
||||||
m => m.resolvedModule, r => r.resolvedFileName, /*logChanges*/ true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultLibFileName() {
|
getDefaultLibFileName() {
|
||||||
|
@ -426,44 +232,5 @@ namespace ts.server {
|
||||||
getDirectories(path: string): string[] {
|
getDirectories(path: string): string[] {
|
||||||
return this.host.getDirectories(path);
|
return this.host.getDirectories(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyFileRemoved(info: ScriptInfo) {
|
|
||||||
this.invalidateResolutionOfDeletedFile(info, this.resolvedModuleNames,
|
|
||||||
m => m.resolvedModule, r => r.resolvedFileName);
|
|
||||||
this.invalidateResolutionOfDeletedFile(info, this.resolvedTypeReferenceDirectives,
|
|
||||||
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private invalidateResolutionOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
|
|
||||||
deletedInfo: ScriptInfo,
|
|
||||||
cache: Map<Map<T>>,
|
|
||||||
getResult: (s: T) => R,
|
|
||||||
getResultFileName: (result: R) => string | undefined) {
|
|
||||||
cache.forEach((value, path) => {
|
|
||||||
if (path === deletedInfo.path) {
|
|
||||||
cache.delete(path);
|
|
||||||
}
|
|
||||||
else if (value) {
|
|
||||||
value.forEach((resolution) => {
|
|
||||||
if (resolution && !resolution.isInvalidated) {
|
|
||||||
const result = getResult(resolution);
|
|
||||||
if (result) {
|
|
||||||
if (getResultFileName(result) === deletedInfo.path) {
|
|
||||||
resolution.isInvalidated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setCompilationSettings(opt: CompilerOptions) {
|
|
||||||
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
|
|
||||||
this.resolvedModuleNames.clear();
|
|
||||||
this.resolvedTypeReferenceDirectives.clear();
|
|
||||||
}
|
|
||||||
this.compilationSettings = opt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/// <reference path="scriptInfo.ts"/>
|
/// <reference path="scriptInfo.ts"/>
|
||||||
/// <reference path="lsHost.ts"/>
|
/// <reference path="lsHost.ts"/>
|
||||||
/// <reference path="typingsCache.ts"/>
|
/// <reference path="typingsCache.ts"/>
|
||||||
/// <reference path="builder.ts"/>
|
/// <reference path="..\compiler\builder.ts"/>
|
||||||
|
|
||||||
namespace ts.server {
|
namespace ts.server {
|
||||||
|
|
||||||
|
@ -127,10 +127,13 @@ namespace ts.server {
|
||||||
|
|
||||||
public languageServiceEnabled = true;
|
public languageServiceEnabled = true;
|
||||||
|
|
||||||
|
/*@internal*/
|
||||||
|
resolutionCache: ResolutionCache;
|
||||||
|
|
||||||
/*@internal*/
|
/*@internal*/
|
||||||
lsHost: LSHost;
|
lsHost: LSHost;
|
||||||
|
|
||||||
builder: Builder;
|
private builder: Builder;
|
||||||
/**
|
/**
|
||||||
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
||||||
*/
|
*/
|
||||||
|
@ -210,7 +213,16 @@ namespace ts.server {
|
||||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||||
|
|
||||||
this.lsHost = new LSHost(host, this, this.projectService.cancellationToken);
|
this.lsHost = new LSHost(host, this, this.projectService.cancellationToken);
|
||||||
this.lsHost.setCompilationSettings(this.compilerOptions);
|
this.resolutionCache = createResolutionCache(
|
||||||
|
fileName => this.projectService.toPath(fileName),
|
||||||
|
() => this.compilerOptions,
|
||||||
|
(failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name),
|
||||||
|
s => this.projectService.logger.info(s),
|
||||||
|
this.getProjectName(),
|
||||||
|
() => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined
|
||||||
|
);
|
||||||
|
this.lsHost.compilationSettings = this.compilerOptions;
|
||||||
|
this.resolutionCache.setModuleResolutionHost(this.lsHost);
|
||||||
|
|
||||||
this.languageService = createLanguageService(this.lsHost, this.documentRegistry);
|
this.languageService = createLanguageService(this.lsHost, this.documentRegistry);
|
||||||
|
|
||||||
|
@ -218,10 +230,22 @@ namespace ts.server {
|
||||||
this.disableLanguageService();
|
this.disableLanguageService();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.builder = createBuilder(this);
|
|
||||||
this.markAsDirty();
|
this.markAsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||||
|
// There is some kind of change in the failed lookup location, update the program
|
||||||
|
return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (fileName, eventKind) => {
|
||||||
|
this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
|
||||||
|
if (this.projectKind === ProjectKind.Configured) {
|
||||||
|
(this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind);
|
||||||
|
}
|
||||||
|
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
|
||||||
|
this.markAsDirty();
|
||||||
|
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private setInternalCompilerOptionsForEmittingJsFiles() {
|
private setInternalCompilerOptionsForEmittingJsFiles() {
|
||||||
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
|
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
|
||||||
this.compilerOptions.noEmitForJsFiles = true;
|
this.compilerOptions.noEmitForJsFiles = true;
|
||||||
|
@ -246,12 +270,47 @@ namespace ts.server {
|
||||||
return this.languageService;
|
return this.languageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureBuilder() {
|
||||||
|
if (!this.builder) {
|
||||||
|
this.builder = createBuilder(
|
||||||
|
this.projectService.toCanonicalFileName,
|
||||||
|
(_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
|
||||||
|
data => this.projectService.host.createHash(data),
|
||||||
|
sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
||||||
if (!this.languageServiceEnabled) {
|
if (!this.languageServiceEnabled) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
this.updateGraph();
|
this.updateGraph();
|
||||||
return this.builder.getFilesAffectedBy(scriptInfo);
|
this.ensureBuilder();
|
||||||
|
return this.builder.getFilesAffectedBy(this.program, scriptInfo.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if emit was conducted
|
||||||
|
*/
|
||||||
|
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
|
||||||
|
this.ensureBuilder();
|
||||||
|
const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path);
|
||||||
|
if (!emitSkipped) {
|
||||||
|
const projectRootPath = this.getProjectRootPath();
|
||||||
|
for (const outputFile of outputFiles) {
|
||||||
|
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
|
||||||
|
writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !emitSkipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChangedFiles() {
|
||||||
|
Debug.assert(this.languageServiceEnabled);
|
||||||
|
this.ensureBuilder();
|
||||||
|
return this.builder.getChangedProgramFiles(this.program);
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjectVersion() {
|
getProjectVersion() {
|
||||||
|
@ -320,6 +379,8 @@ namespace ts.server {
|
||||||
this.rootFilesMap = undefined;
|
this.rootFilesMap = undefined;
|
||||||
this.program = undefined;
|
this.program = undefined;
|
||||||
this.builder = undefined;
|
this.builder = undefined;
|
||||||
|
this.resolutionCache.clear();
|
||||||
|
this.resolutionCache = undefined;
|
||||||
this.cachedUnresolvedImportsPerFile = undefined;
|
this.cachedUnresolvedImportsPerFile = undefined;
|
||||||
this.lsHost.dispose();
|
this.lsHost.dispose();
|
||||||
this.lsHost = undefined;
|
this.lsHost = undefined;
|
||||||
|
@ -337,6 +398,10 @@ namespace ts.server {
|
||||||
this.languageService = undefined;
|
this.languageService = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isClosed() {
|
||||||
|
return this.lsHost === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
getCompilerOptions() {
|
getCompilerOptions() {
|
||||||
return this.compilerOptions;
|
return this.compilerOptions;
|
||||||
}
|
}
|
||||||
|
@ -391,11 +456,11 @@ namespace ts.server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
|
private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) {
|
||||||
if (!this.languageServiceEnabled) {
|
if (!this.languageServiceEnabled) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
|
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
|
||||||
|
@ -454,21 +519,6 @@ namespace ts.server {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllEmittableFiles() {
|
|
||||||
if (!this.languageServiceEnabled) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
|
|
||||||
const infos = this.getScriptInfos();
|
|
||||||
const result: string[] = [];
|
|
||||||
for (const info of infos) {
|
|
||||||
if (getBaseFileName(info.fileName) !== defaultLibraryFileName && shouldEmitFile(info)) {
|
|
||||||
result.push(info.fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
containsScriptInfo(info: ScriptInfo): boolean {
|
containsScriptInfo(info: ScriptInfo): boolean {
|
||||||
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
|
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
|
||||||
}
|
}
|
||||||
|
@ -505,7 +555,7 @@ namespace ts.server {
|
||||||
if (this.isRoot(info)) {
|
if (this.isRoot(info)) {
|
||||||
this.removeRoot(info);
|
this.removeRoot(info);
|
||||||
}
|
}
|
||||||
this.lsHost.notifyFileRemoved(info);
|
this.resolutionCache.invalidateResolutionOfFile(info.path);
|
||||||
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
||||||
|
|
||||||
if (detachFromProject) {
|
if (detachFromProject) {
|
||||||
|
@ -560,11 +610,12 @@ namespace ts.server {
|
||||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||||
*/
|
*/
|
||||||
updateGraph(): boolean {
|
updateGraph(): boolean {
|
||||||
this.lsHost.startRecordingFilesWithChangedResolutions();
|
this.resolutionCache.startRecordingFilesWithChangedResolutions();
|
||||||
|
this.lsHost.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
|
||||||
|
|
||||||
let hasChanges = this.updateGraphWorker();
|
let hasChanges = this.updateGraphWorker();
|
||||||
|
|
||||||
const changedFiles: ReadonlyArray<Path> = this.lsHost.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
const changedFiles: ReadonlyArray<Path> = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
||||||
|
|
||||||
for (const file of changedFiles) {
|
for (const file of changedFiles) {
|
||||||
// delete cached information for changed files
|
// delete cached information for changed files
|
||||||
|
@ -594,11 +645,14 @@ namespace ts.server {
|
||||||
|
|
||||||
// update builder only if language service is enabled
|
// update builder only if language service is enabled
|
||||||
// otherwise tell it to drop its internal state
|
// otherwise tell it to drop its internal state
|
||||||
if (this.languageServiceEnabled) {
|
// Note we are retaining builder so we can send events for project change
|
||||||
this.builder.onProjectUpdateGraph();
|
if (this.builder) {
|
||||||
}
|
if (this.languageServiceEnabled) {
|
||||||
else {
|
this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution);
|
||||||
this.builder.clear();
|
}
|
||||||
|
else {
|
||||||
|
this.builder.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
|
@ -639,41 +693,15 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const missingFilePaths = this.program.getMissingFilePaths();
|
|
||||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
|
||||||
// Update the missing file paths watcher
|
// Update the missing file paths watcher
|
||||||
mutateMap(
|
updateMissingFilePathsWatch(
|
||||||
|
this.program,
|
||||||
this.missingFilesMap || (this.missingFilesMap = createMap()),
|
this.missingFilesMap || (this.missingFilesMap = createMap()),
|
||||||
newMissingFilePathMap,
|
// Watch the missing files
|
||||||
{
|
missingFilePath => this.addMissingFileWatcher(missingFilePath),
|
||||||
// Watch the missing files
|
// Files that are no longer missing (e.g. because they are no longer required)
|
||||||
createNewValue: missingFilePath => {
|
// should no longer be watched.
|
||||||
const fileWatcher = this.projectService.addFileWatcher(
|
(missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded)
|
||||||
WatchType.MissingFilePath, this, missingFilePath,
|
|
||||||
(filename, eventKind) => {
|
|
||||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
|
||||||
this.missingFilesMap.delete(missingFilePath);
|
|
||||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
|
||||||
|
|
||||||
if (this.projectKind === ProjectKind.Configured) {
|
|
||||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
|
||||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a missing file is created, we should update the graph.
|
|
||||||
this.markAsDirty();
|
|
||||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fileWatcher;
|
|
||||||
},
|
|
||||||
// Files that are no longer missing (e.g. because they are no longer required)
|
|
||||||
// should no longer be watched.
|
|
||||||
onDeleteExistingValue: (missingFilePath, fileWatcher) => {
|
|
||||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,7 +712,7 @@ namespace ts.server {
|
||||||
// by the LSHost for files in the program when the program is retrieved above but
|
// by the LSHost for files in the program when the program is retrieved above but
|
||||||
// the program doesn't contain external files so this must be done explicitly.
|
// the program doesn't contain external files so this must be done explicitly.
|
||||||
inserted => {
|
inserted => {
|
||||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false);
|
const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host);
|
||||||
scriptInfo.attachToProject(this);
|
scriptInfo.attachToProject(this);
|
||||||
},
|
},
|
||||||
removed => {
|
removed => {
|
||||||
|
@ -697,12 +725,37 @@ namespace ts.server {
|
||||||
return hasChanges;
|
return hasChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addMissingFileWatcher(missingFilePath: Path) {
|
||||||
|
const fileWatcher = this.projectService.addFileWatcher(
|
||||||
|
WatchType.MissingFilePath, this, missingFilePath,
|
||||||
|
(fileName, eventKind) => {
|
||||||
|
if (this.projectKind === ProjectKind.Configured) {
|
||||||
|
(this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, missingFilePath, eventKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||||
|
this.missingFilesMap.delete(missingFilePath);
|
||||||
|
this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
||||||
|
|
||||||
|
// When a missing file is created, we should update the graph.
|
||||||
|
this.markAsDirty();
|
||||||
|
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return fileWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) {
|
||||||
|
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason);
|
||||||
|
}
|
||||||
|
|
||||||
isWatchedMissingFile(path: Path) {
|
isWatchedMissingFile(path: Path) {
|
||||||
return this.missingFilesMap && this.missingFilesMap.has(path);
|
return this.missingFilesMap && this.missingFilesMap.has(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
getScriptInfoLSHost(fileName: string) {
|
getScriptInfoLSHost(fileName: string) {
|
||||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host);
|
||||||
if (scriptInfo) {
|
if (scriptInfo) {
|
||||||
const existingValue = this.rootFilesMap.get(scriptInfo.path);
|
const existingValue = this.rootFilesMap.get(scriptInfo.path);
|
||||||
if (existingValue !== undefined && existingValue !== scriptInfo) {
|
if (existingValue !== undefined && existingValue !== scriptInfo) {
|
||||||
|
@ -716,7 +769,10 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
||||||
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
|
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(
|
||||||
|
fileName, /*openedByClient*/ false, /*fileContent*/ undefined,
|
||||||
|
/*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host
|
||||||
|
);
|
||||||
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
||||||
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
||||||
}
|
}
|
||||||
|
@ -746,9 +802,13 @@ namespace ts.server {
|
||||||
this.cachedUnresolvedImportsPerFile.clear();
|
this.cachedUnresolvedImportsPerFile.clear();
|
||||||
this.lastCachedUnresolvedImportsList = undefined;
|
this.lastCachedUnresolvedImportsList = undefined;
|
||||||
}
|
}
|
||||||
|
const oldOptions = this.compilerOptions;
|
||||||
this.compilerOptions = compilerOptions;
|
this.compilerOptions = compilerOptions;
|
||||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||||
this.lsHost.setCompilationSettings(compilerOptions);
|
if (changesAffectModuleResolution(oldOptions, compilerOptions)) {
|
||||||
|
this.resolutionCache.clear();
|
||||||
|
}
|
||||||
|
this.lsHost.compilationSettings = this.compilerOptions;
|
||||||
|
|
||||||
this.markAsDirty();
|
this.markAsDirty();
|
||||||
}
|
}
|
||||||
|
@ -814,58 +874,6 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getReferencedFiles(path: Path): Path[] {
|
|
||||||
if (!this.languageServiceEnabled) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceFile = this.getSourceFile(path);
|
|
||||||
if (!sourceFile) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// We need to use a set here since the code can contain the same import twice,
|
|
||||||
// but that will only be one dependency.
|
|
||||||
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
|
||||||
const referencedFiles = createMap<true>();
|
|
||||||
if (sourceFile.imports && sourceFile.imports.length > 0) {
|
|
||||||
const checker: TypeChecker = this.program.getTypeChecker();
|
|
||||||
for (const importName of sourceFile.imports) {
|
|
||||||
const symbol = checker.getSymbolAtLocation(importName);
|
|
||||||
if (symbol && symbol.declarations && symbol.declarations[0]) {
|
|
||||||
const declarationSourceFile = symbol.declarations[0].getSourceFile();
|
|
||||||
if (declarationSourceFile) {
|
|
||||||
referencedFiles.set(declarationSourceFile.path, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDirectory = getDirectoryPath(path);
|
|
||||||
// Handle triple slash references
|
|
||||||
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
|
|
||||||
for (const referencedFile of sourceFile.referencedFiles) {
|
|
||||||
const referencedPath = this.projectService.toPath(referencedFile.fileName, currentDirectory);
|
|
||||||
referencedFiles.set(referencedPath, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle type reference directives
|
|
||||||
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
|
|
||||||
sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
|
|
||||||
if (!resolvedTypeReferenceDirective) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
|
||||||
const typeFilePath = this.projectService.toPath(fileName, currentDirectory);
|
|
||||||
referencedFiles.set(typeFilePath, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const allFileNames = arrayFrom(referencedFiles.keys()) as Path[];
|
|
||||||
return filter(allFileNames, file => this.lsHost.host.fileExists(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove a root file from project
|
// remove a root file from project
|
||||||
protected removeRoot(info: ScriptInfo): void {
|
protected removeRoot(info: ScriptInfo): void {
|
||||||
orderedRemoveItem(this.rootFiles, info);
|
orderedRemoveItem(this.rootFiles, info);
|
||||||
|
@ -973,11 +981,6 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WildcardDirectoryWatcher {
|
|
||||||
watcher: FileWatcher;
|
|
||||||
flags: WatchDirectoryFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a file is opened, the server will look for a tsconfig (or jsconfig)
|
* If a file is opened, the server will look for a tsconfig (or jsconfig)
|
||||||
* and if successfull create a ConfiguredProject for it.
|
* and if successfull create a ConfiguredProject for it.
|
||||||
|
@ -1160,30 +1163,19 @@ namespace ts.server {
|
||||||
|
|
||||||
/*@internal*/
|
/*@internal*/
|
||||||
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
||||||
mutateMap(
|
updateWatchingWildcardDirectories(
|
||||||
this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()),
|
this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()),
|
||||||
wildcardDirectories,
|
wildcardDirectories,
|
||||||
{
|
// Create new directory watcher
|
||||||
// Watcher is same if the recursive flags match
|
(directory, flags) => this.projectService.addDirectoryWatcher(
|
||||||
isSameValue: ({ flags: existingFlags }, flags) => existingFlags !== flags,
|
WatchType.WildcardDirectories, this, directory,
|
||||||
// Create new watch and recursive info
|
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
||||||
createNewValue: (directory, flags) => {
|
flags
|
||||||
return {
|
),
|
||||||
watcher: this.projectService.addDirectoryWatcher(
|
// Close directory watcher
|
||||||
WatchType.WildcardDirectories, this, directory,
|
(directory, wildcardDirectoryWatcher, flagsChanged) => this.closeWildcardDirectoryWatcher(
|
||||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
directory, wildcardDirectoryWatcher, flagsChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded
|
||||||
flags
|
)
|
||||||
),
|
|
||||||
flags
|
|
||||||
};
|
|
||||||
},
|
|
||||||
// Close existing watch thats not needed any more
|
|
||||||
onDeleteExistingValue: (directory, wildcardDirectoryWatcher) =>
|
|
||||||
this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, WatcherCloseReason.NotNeeded),
|
|
||||||
// Close existing watch that doesnt match in the recursive flags
|
|
||||||
onDeleteExistingMismatchValue: (directory, wildcardDirectoryWatcher) =>
|
|
||||||
this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, WatcherCloseReason.RecursiveChanged),
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1214,7 +1206,7 @@ namespace ts.server {
|
||||||
path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None
|
path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None
|
||||||
),
|
),
|
||||||
// Close existing watch thats not needed any more
|
// Close existing watch thats not needed any more
|
||||||
onDeleteExistingValue: (directory, watcher) => this.projectService.closeDirectoryWatcher(
|
onDeleteValue: (directory, watcher) => this.projectService.closeDirectoryWatcher(
|
||||||
WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded
|
WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2040,6 +2040,29 @@ namespace ts.server.protocol {
|
||||||
languageServiceEnabled: boolean;
|
languageServiceEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProjectChangedEventName = "projectChanged";
|
||||||
|
export interface ProjectStructureChangedEvent extends Event {
|
||||||
|
event: ProjectChangedEventName;
|
||||||
|
body: ProjectChangedEventBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectChangedEventBody {
|
||||||
|
/**
|
||||||
|
* Project name that has changes
|
||||||
|
*/
|
||||||
|
projectName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum set of file names to emit
|
||||||
|
*/
|
||||||
|
fileNamesToEmit: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of files that have changed/added/removed or could have been affected by the changed files
|
||||||
|
*/
|
||||||
|
changedFiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments for reload request.
|
* Arguments for reload request.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -237,7 +237,7 @@ namespace ts.server {
|
||||||
detachAllProjects() {
|
detachAllProjects() {
|
||||||
for (const p of this.containingProjects) {
|
for (const p of this.containingProjects) {
|
||||||
if (p.projectKind === ProjectKind.Configured) {
|
if (p.projectKind === ProjectKind.Configured) {
|
||||||
(p.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(this.fileName);
|
(p.lsHost.host as CachedServerHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted);
|
||||||
}
|
}
|
||||||
const isInfoRoot = p.isRoot(this);
|
const isInfoRoot = p.isRoot(this);
|
||||||
// detach is unnecessary since we'll clean the list of containing projects anyways
|
// detach is unnecessary since we'll clean the list of containing projects anyways
|
||||||
|
|
|
@ -538,7 +538,15 @@ namespace ts.server {
|
||||||
|
|
||||||
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
|
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
|
if (err.code === "ENOENT") {
|
||||||
|
if (watchedFile.mtime.getTime() !== 0) {
|
||||||
|
watchedFile.mtime = new Date(0);
|
||||||
|
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const oldTime = watchedFile.mtime.getTime();
|
const oldTime = watchedFile.mtime.getTime();
|
||||||
|
|
|
@ -332,14 +332,18 @@ namespace ts.server {
|
||||||
|
|
||||||
private defaultEventHandler(event: ProjectServiceEvent) {
|
private defaultEventHandler(event: ProjectServiceEvent) {
|
||||||
switch (event.eventName) {
|
switch (event.eventName) {
|
||||||
case ContextEvent:
|
case ProjectChangedEvent:
|
||||||
const { project, fileName } = event.data;
|
const { project, filesToEmit, changedFiles } = event.data;
|
||||||
this.projectService.logger.info(`got context event, updating diagnostics for ${fileName}`);
|
this.projectChangedEvent(project, filesToEmit, changedFiles);
|
||||||
this.errorCheck.startNew(next => this.updateErrorCheck(next, [{ fileName, project }], 100));
|
|
||||||
break;
|
break;
|
||||||
case ConfigFileDiagEvent:
|
case ConfigFileDiagEvent:
|
||||||
const { triggerFile, configFileName, diagnostics } = event.data;
|
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
|
||||||
this.configFileDiagnosticEvent(triggerFile, configFileName, diagnostics);
|
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
|
||||||
|
this.event<protocol.ConfigFileDiagnosticEventBody>({
|
||||||
|
triggerFile,
|
||||||
|
configFile,
|
||||||
|
diagnostics: bakedDiags
|
||||||
|
}, "configFileDiag");
|
||||||
break;
|
break;
|
||||||
case ProjectLanguageServiceStateEvent: {
|
case ProjectLanguageServiceStateEvent: {
|
||||||
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
|
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
|
||||||
|
@ -360,6 +364,24 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private projectChangedEvent(project: Project, fileNamesToEmit: string[], changedFiles: string[]): void {
|
||||||
|
this.projectService.logger.info(`got project changed event, updating diagnostics for ${changedFiles}`);
|
||||||
|
if (changedFiles.length) {
|
||||||
|
const checkList = this.createCheckList(changedFiles, project);
|
||||||
|
|
||||||
|
// For now only queue error checking for open files. We can change this to include non open files as well
|
||||||
|
this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true));
|
||||||
|
|
||||||
|
|
||||||
|
// Send project changed event
|
||||||
|
this.event<protocol.ProjectChangedEventBody>({
|
||||||
|
projectName: project.getProjectName(),
|
||||||
|
changedFiles,
|
||||||
|
fileNamesToEmit
|
||||||
|
}, "projectChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public logError(err: Error, cmd: string) {
|
public logError(err: Error, cmd: string) {
|
||||||
let msg = "Exception on executing command " + cmd;
|
let msg = "Exception on executing command " + cmd;
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
|
@ -381,21 +403,6 @@ namespace ts.server {
|
||||||
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
|
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
|
||||||
}
|
}
|
||||||
|
|
||||||
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ReadonlyArray<Diagnostic>) {
|
|
||||||
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
|
|
||||||
const ev: protocol.ConfigFileDiagnosticEvent = {
|
|
||||||
seq: 0,
|
|
||||||
type: "event",
|
|
||||||
event: "configFileDiag",
|
|
||||||
body: {
|
|
||||||
triggerFile,
|
|
||||||
configFile,
|
|
||||||
diagnostics: bakedDiags
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.send(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event<T>(info: T, eventName: string) {
|
public event<T>(info: T, eventName: string) {
|
||||||
const ev: protocol.Event = {
|
const ev: protocol.Event = {
|
||||||
seq: 0,
|
seq: 0,
|
||||||
|
@ -1197,13 +1204,13 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
|
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
|
||||||
const info = this.projectService.getScriptInfo(args.file);
|
const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file);
|
||||||
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
|
|
||||||
|
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return emptyArray;
|
return emptyArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
|
||||||
|
|
||||||
// if specified a project, we only return affected file list in this project
|
// if specified a project, we only return affected file list in this project
|
||||||
const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
|
const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
|
||||||
for (const project of projectsToSearch) {
|
for (const project of projectsToSearch) {
|
||||||
|
@ -1227,7 +1234,7 @@ namespace ts.server {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const scriptInfo = project.getScriptInfo(file);
|
const scriptInfo = project.getScriptInfo(file);
|
||||||
return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
|
return project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems {
|
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems {
|
||||||
|
@ -1257,13 +1264,16 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
|
private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] {
|
||||||
const checkList = mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
|
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
|
||||||
const fileName = toNormalizedPath(uncheckedFileName);
|
const fileName = toNormalizedPath(uncheckedFileName);
|
||||||
const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true);
|
const project = defaultProject || this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true);
|
||||||
return project && { fileName, project };
|
return project && { fileName, project };
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
|
||||||
|
const checkList = this.createCheckList(fileNames);
|
||||||
if (checkList.length > 0) {
|
if (checkList.length > 0) {
|
||||||
this.updateErrorCheck(next, checkList, delay);
|
this.updateErrorCheck(next, checkList, delay);
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,59 +304,4 @@ namespace ts.server {
|
||||||
deleted(oldItems[oldIndex++]);
|
deleted(oldItems[oldIndex++]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
|
|
||||||
*/
|
|
||||||
export function clearMap<T>(map: Map<T>, onDeleteExistingValue: (key: string, existingValue: T) => void) {
|
|
||||||
// Remove all
|
|
||||||
map.forEach((existingValue, key) => {
|
|
||||||
onDeleteExistingValue(key, existingValue);
|
|
||||||
});
|
|
||||||
map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MutateMapOptions<T, U> {
|
|
||||||
createNewValue(key: string, valueInNewMap: U): T;
|
|
||||||
onDeleteExistingValue(key: string, existingValue: T, isNotSame?: boolean): void;
|
|
||||||
|
|
||||||
isSameValue?(existingValue: T, valueInNewMap: U): boolean;
|
|
||||||
onDeleteExistingMismatchValue?(key: string, existingValue: T): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutates the map with newMap such that keys in map will be same as newMap.
|
|
||||||
*/
|
|
||||||
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
|
|
||||||
// If there are new values update them
|
|
||||||
if (newMap) {
|
|
||||||
const { isSameValue, createNewValue, onDeleteExistingValue, onDeleteExistingMismatchValue } = options;
|
|
||||||
// Needs update
|
|
||||||
map.forEach((existingValue, key) => {
|
|
||||||
const valueInNewMap = newMap.get(key);
|
|
||||||
// Not present any more in new map, remove it
|
|
||||||
if (valueInNewMap === undefined) {
|
|
||||||
map.delete(key);
|
|
||||||
onDeleteExistingValue(key, existingValue);
|
|
||||||
}
|
|
||||||
// different value - remove it
|
|
||||||
else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) {
|
|
||||||
Debug.assert(!!onDeleteExistingMismatchValue);
|
|
||||||
map.delete(key);
|
|
||||||
onDeleteExistingMismatchValue(key, existingValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add new values that are not already present
|
|
||||||
newMap.forEach((valueInNewMap, key) => {
|
|
||||||
if (!map.has(key)) {
|
|
||||||
// New values
|
|
||||||
map.set(key, createNewValue(key, valueInNewMap));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearMap(map, options.onDeleteExistingValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -814,18 +814,21 @@ namespace ts {
|
||||||
return codefix.getSupportedErrorCodes();
|
return codefix.getSupportedErrorCodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Either it will be file name if host doesnt have file or it will be the host's file information
|
||||||
|
type CachedHostFileInformation = HostFileInformation | string;
|
||||||
|
|
||||||
// Cache host information about script Should be refreshed
|
// Cache host information about script Should be refreshed
|
||||||
// at each language service public entry point, since we don't know when
|
// at each language service public entry point, since we don't know when
|
||||||
// the set of scripts handled by the host changes.
|
// the set of scripts handled by the host changes.
|
||||||
class HostCache {
|
class HostCache {
|
||||||
private fileNameToEntry: Map<HostFileInformation>;
|
private fileNameToEntry: Map<CachedHostFileInformation>;
|
||||||
private _compilationSettings: CompilerOptions;
|
private _compilationSettings: CompilerOptions;
|
||||||
private currentDirectory: string;
|
private currentDirectory: string;
|
||||||
|
|
||||||
constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) {
|
constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) {
|
||||||
// script id => script index
|
// script id => script index
|
||||||
this.currentDirectory = host.getCurrentDirectory();
|
this.currentDirectory = host.getCurrentDirectory();
|
||||||
this.fileNameToEntry = createMap<HostFileInformation>();
|
this.fileNameToEntry = createMap<CachedHostFileInformation>();
|
||||||
|
|
||||||
// Initialize the list with the root file names
|
// Initialize the list with the root file names
|
||||||
const rootFileNames = host.getScriptFileNames();
|
const rootFileNames = host.getScriptFileNames();
|
||||||
|
@ -842,7 +845,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createEntry(fileName: string, path: Path) {
|
private createEntry(fileName: string, path: Path) {
|
||||||
let entry: HostFileInformation;
|
let entry: CachedHostFileInformation;
|
||||||
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
|
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
|
||||||
if (scriptSnapshot) {
|
if (scriptSnapshot) {
|
||||||
entry = {
|
entry = {
|
||||||
|
@ -852,36 +855,41 @@ namespace ts {
|
||||||
scriptKind: getScriptKind(fileName, this.host)
|
scriptKind: getScriptKind(fileName, this.host)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
entry = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
this.fileNameToEntry.set(path, entry);
|
this.fileNameToEntry.set(path, entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEntryByPath(path: Path): HostFileInformation {
|
public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
|
||||||
return this.fileNameToEntry.get(path);
|
return this.fileNameToEntry.get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public containsEntryByPath(path: Path): boolean {
|
public getHostFileInformation(path: Path): HostFileInformation | undefined {
|
||||||
return this.fileNameToEntry.has(path);
|
const entry = this.fileNameToEntry.get(path);
|
||||||
|
return !isString(entry) ? entry : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
|
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
|
||||||
return this.containsEntryByPath(path)
|
const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
|
||||||
? this.getEntryByPath(path)
|
return isString(info) ? undefined : info;
|
||||||
: this.createEntry(fileName, path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRootFileNames(): string[] {
|
public getRootFileNames(): string[] {
|
||||||
return this.host.getScriptFileNames();
|
return arrayFrom(this.fileNameToEntry.values(), entry => {
|
||||||
|
return isString(entry) ? entry : entry.hostFileName;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVersion(path: Path): string {
|
public getVersion(path: Path): string {
|
||||||
const file = this.getEntryByPath(path);
|
const file = this.getHostFileInformation(path);
|
||||||
return file && file.version;
|
return file && file.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getScriptSnapshot(path: Path): IScriptSnapshot {
|
public getScriptSnapshot(path: Path): IScriptSnapshot {
|
||||||
const file = this.getEntryByPath(path);
|
const file = this.getHostFileInformation(path);
|
||||||
return file && file.scriptSnapshot;
|
return file && file.scriptSnapshot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1108,9 +1116,12 @@ namespace ts {
|
||||||
|
|
||||||
// Get a fresh cache of the host information
|
// Get a fresh cache of the host information
|
||||||
let hostCache = new HostCache(host, getCanonicalFileName);
|
let hostCache = new HostCache(host, getCanonicalFileName);
|
||||||
|
const rootFileNames = hostCache.getRootFileNames();
|
||||||
|
|
||||||
|
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
|
||||||
|
|
||||||
// If the program is already up-to-date, we can reuse it
|
// If the program is already up-to-date, we can reuse it
|
||||||
if (programUpToDate()) {
|
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1120,18 +1131,7 @@ namespace ts {
|
||||||
// the program points to old source files that have been invalidated because of
|
// the program points to old source files that have been invalidated because of
|
||||||
// incremental parsing.
|
// incremental parsing.
|
||||||
|
|
||||||
const oldSettings = program && program.getCompilerOptions();
|
|
||||||
const newSettings = hostCache.compilationSettings();
|
const newSettings = hostCache.compilationSettings();
|
||||||
const shouldCreateNewSourceFiles = oldSettings &&
|
|
||||||
(oldSettings.target !== newSettings.target ||
|
|
||||||
oldSettings.module !== newSettings.module ||
|
|
||||||
oldSettings.moduleResolution !== newSettings.moduleResolution ||
|
|
||||||
oldSettings.noResolve !== newSettings.noResolve ||
|
|
||||||
oldSettings.jsx !== newSettings.jsx ||
|
|
||||||
oldSettings.allowJs !== newSettings.allowJs ||
|
|
||||||
oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit ||
|
|
||||||
oldSettings.baseUrl !== newSettings.baseUrl ||
|
|
||||||
!equalOwnProperties(oldSettings.paths, newSettings.paths));
|
|
||||||
|
|
||||||
// Now create a new compiler
|
// Now create a new compiler
|
||||||
const compilerHost: CompilerHost = {
|
const compilerHost: CompilerHost = {
|
||||||
|
@ -1144,19 +1144,13 @@ namespace ts {
|
||||||
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
|
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
|
||||||
writeFile: noop,
|
writeFile: noop,
|
||||||
getCurrentDirectory: () => currentDirectory,
|
getCurrentDirectory: () => currentDirectory,
|
||||||
fileExists: (fileName): boolean => {
|
fileExists,
|
||||||
// stub missing host functionality
|
|
||||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
|
||||||
return hostCache.containsEntryByPath(path) ?
|
|
||||||
!!hostCache.getEntryByPath(path) :
|
|
||||||
(host.fileExists && host.fileExists(fileName));
|
|
||||||
},
|
|
||||||
readFile(fileName) {
|
readFile(fileName) {
|
||||||
// stub missing host functionality
|
// stub missing host functionality
|
||||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||||
if (hostCache.containsEntryByPath(path)) {
|
const entry = hostCache.getEntryByPath(path);
|
||||||
const entry = hostCache.getEntryByPath(path);
|
if (entry) {
|
||||||
return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
|
return isString(entry) ? undefined : entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
|
||||||
}
|
}
|
||||||
return host.readFile && host.readFile(fileName);
|
return host.readFile && host.readFile(fileName);
|
||||||
},
|
},
|
||||||
|
@ -1165,7 +1159,9 @@ namespace ts {
|
||||||
},
|
},
|
||||||
getDirectories: path => {
|
getDirectories: path => {
|
||||||
return host.getDirectories ? host.getDirectories(path) : [];
|
return host.getDirectories ? host.getDirectories(path) : [];
|
||||||
}
|
},
|
||||||
|
onReleaseOldSourceFile,
|
||||||
|
hasInvalidatedResolution
|
||||||
};
|
};
|
||||||
if (host.trace) {
|
if (host.trace) {
|
||||||
compilerHost.trace = message => host.trace(message);
|
compilerHost.trace = message => host.trace(message);
|
||||||
|
@ -1181,36 +1177,37 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
|
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
|
||||||
const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program);
|
program = createProgram(rootFileNames, newSettings, compilerHost, program);
|
||||||
|
|
||||||
// Release any files we have acquired in the old program but are
|
|
||||||
// not part of the new program.
|
|
||||||
if (program) {
|
|
||||||
const oldSourceFiles = program.getSourceFiles();
|
|
||||||
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldSettings);
|
|
||||||
for (const oldSourceFile of oldSourceFiles) {
|
|
||||||
if (!newProgram.getSourceFile(oldSourceFile.fileName) || shouldCreateNewSourceFiles) {
|
|
||||||
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
|
// hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
|
||||||
// It needs to be cleared to allow all collected snapshots to be released
|
// It needs to be cleared to allow all collected snapshots to be released
|
||||||
hostCache = undefined;
|
hostCache = undefined;
|
||||||
|
|
||||||
program = newProgram;
|
|
||||||
|
|
||||||
// Make sure all the nodes in the program are both bound, and have their parent
|
// Make sure all the nodes in the program are both bound, and have their parent
|
||||||
// pointers set property.
|
// pointers set property.
|
||||||
program.getTypeChecker();
|
program.getTypeChecker();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
function getOrCreateSourceFile(fileName: string): SourceFile {
|
function fileExists(fileName: string) {
|
||||||
return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName));
|
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||||
|
const entry = hostCache.getEntryByPath(path);
|
||||||
|
return entry ?
|
||||||
|
!isString(entry) :
|
||||||
|
(host.fileExists && host.fileExists(fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOrCreateSourceFileByPath(fileName: string, path: Path): SourceFile {
|
// Release any files we have acquired in the old program but are
|
||||||
|
// not part of the new program.
|
||||||
|
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
|
||||||
|
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
|
||||||
|
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||||
|
return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||||
Debug.assert(hostCache !== undefined);
|
Debug.assert(hostCache !== undefined);
|
||||||
// The program is asking for this file, check first if the host can locate it.
|
// The program is asking for this file, check first if the host can locate it.
|
||||||
// If the host can not locate the file, then it does not exist. return undefined
|
// If the host can not locate the file, then it does not exist. return undefined
|
||||||
|
@ -1223,7 +1220,7 @@ namespace ts {
|
||||||
// Check if the language version has changed since we last created a program; if they are the same,
|
// Check if the language version has changed since we last created a program; if they are the same,
|
||||||
// it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
|
// it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
|
||||||
// can not be reused. we have to dump all syntax trees and create new ones.
|
// can not be reused. we have to dump all syntax trees and create new ones.
|
||||||
if (!shouldCreateNewSourceFiles) {
|
if (!shouldCreateNewSourceFile) {
|
||||||
// Check if the old program had this file already
|
// Check if the old program had this file already
|
||||||
const oldSourceFile = program && program.getSourceFileByPath(path);
|
const oldSourceFile = program && program.getSourceFileByPath(path);
|
||||||
if (oldSourceFile) {
|
if (oldSourceFile) {
|
||||||
|
@ -1263,49 +1260,6 @@ namespace ts {
|
||||||
// Could not find this file in the old program, create a new SourceFile for it.
|
// Could not find this file in the old program, create a new SourceFile for it.
|
||||||
return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
|
return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
|
|
||||||
if (!sourceFile) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const path = sourceFile.path || toPath(sourceFile.fileName, currentDirectory, getCanonicalFileName);
|
|
||||||
return sourceFile.version === hostCache.getVersion(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function programUpToDate(): boolean {
|
|
||||||
// If we haven't create a program yet, then it is not up-to-date
|
|
||||||
if (!program) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If number of files in the program do not match, it is not up-to-date
|
|
||||||
const rootFileNames = hostCache.getRootFileNames();
|
|
||||||
if (program.getSourceFiles().length !== rootFileNames.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any file is not up-to-date, then the whole program is not up-to-date
|
|
||||||
for (const fileName of rootFileNames) {
|
|
||||||
if (!sourceFileUpToDate(program.getSourceFile(fileName))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentOptions = program.getCompilerOptions();
|
|
||||||
const newOptions = hostCache.compilationSettings();
|
|
||||||
// If the compilation settings do no match, then the program is not up-to-date
|
|
||||||
if (!compareDataObjects(currentOptions, newOptions)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If everything matches but the text of config file is changed,
|
|
||||||
// error locations can change for program options, so update the program
|
|
||||||
if (currentOptions.configFile && newOptions.configFile) {
|
|
||||||
return currentOptions.configFile.text === newOptions.configFile.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProgram(): Program {
|
function getProgram(): Program {
|
||||||
|
@ -1520,23 +1474,12 @@ namespace ts {
|
||||||
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
|
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput {
|
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) {
|
||||||
synchronizeHostData();
|
synchronizeHostData();
|
||||||
|
|
||||||
const sourceFile = getValidSourceFile(fileName);
|
const sourceFile = getValidSourceFile(fileName);
|
||||||
const outputFiles: OutputFile[] = [];
|
|
||||||
|
|
||||||
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
|
|
||||||
outputFiles.push({ name: fileName, writeByteOrderMark, text });
|
|
||||||
}
|
|
||||||
|
|
||||||
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
|
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
|
||||||
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
|
||||||
|
|
||||||
return {
|
|
||||||
outputFiles,
|
|
||||||
emitSkipped: emitOutput.emitSkipped
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature help
|
// Signature help
|
||||||
|
|
|
@ -527,7 +527,7 @@ namespace ts {
|
||||||
if (logPerformance) {
|
if (logPerformance) {
|
||||||
const end = timestamp();
|
const end = timestamp();
|
||||||
logger.log(`${actionDescription} completed in ${end - start} msec`);
|
logger.log(`${actionDescription} completed in ${end - start} msec`);
|
||||||
if (typeof result === "string") {
|
if (isString(result)) {
|
||||||
let str = result;
|
let str = result;
|
||||||
if (str.length > 128) {
|
if (str.length > 128) {
|
||||||
str = str.substring(0, 128) + "...";
|
str = str.substring(0, 128) + "...";
|
||||||
|
|
|
@ -139,7 +139,7 @@ namespace ts {
|
||||||
|
|
||||||
const value = options[opt.name];
|
const value = options[opt.name];
|
||||||
// Value should be a key of opt.type
|
// Value should be a key of opt.type
|
||||||
if (typeof value === "string") {
|
if (isString(value)) {
|
||||||
// If value is not a string, this will fail
|
// If value is not a string, this will fail
|
||||||
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
|
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,7 @@ namespace ts {
|
||||||
*/
|
*/
|
||||||
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
|
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
|
||||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||||
|
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||||
directoryExists?(directoryName: string): boolean;
|
directoryExists?(directoryName: string): boolean;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -276,7 +277,7 @@ namespace ts {
|
||||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||||
|
|
||||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
|
||||||
|
|
||||||
getProgram(): Program;
|
getProgram(): Program;
|
||||||
|
|
||||||
|
@ -694,23 +695,12 @@ namespace ts {
|
||||||
autoCollapse: boolean;
|
autoCollapse: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmitOutput {
|
|
||||||
outputFiles: OutputFile[];
|
|
||||||
emitSkipped: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum OutputFileType {
|
export const enum OutputFileType {
|
||||||
JavaScript,
|
JavaScript,
|
||||||
SourceMap,
|
SourceMap,
|
||||||
Declaration
|
Declaration
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OutputFile {
|
|
||||||
name: string;
|
|
||||||
writeByteOrderMark: boolean;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum EndOfLineState {
|
export const enum EndOfLineState {
|
||||||
None,
|
None,
|
||||||
InMultiLineCommentTrivia,
|
InMultiLineCommentTrivia,
|
||||||
|
|
|
@ -979,26 +979,6 @@ namespace ts {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compareDataObjects(dst: any, src: any): boolean {
|
|
||||||
if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const e in dst) {
|
|
||||||
if (typeof dst[e] === "object") {
|
|
||||||
if (!compareDataObjects(dst[e], src[e])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (typeof dst[e] !== "function") {
|
|
||||||
if (dst[e] !== src[e]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) {
|
export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) {
|
||||||
if (node.kind === SyntaxKind.ArrayLiteralExpression ||
|
if (node.kind === SyntaxKind.ArrayLiteralExpression ||
|
||||||
node.kind === SyntaxKind.ObjectLiteralExpression) {
|
node.kind === SyntaxKind.ObjectLiteralExpression) {
|
||||||
|
|
Loading…
Reference in a new issue