Merge branch 'master' into dynamicNames

This commit is contained in:
Ron Buckton
2017-10-02 19:08:09 -07:00
176 changed files with 135119 additions and 3599 deletions
+6 -6
View File
@@ -19,11 +19,6 @@ tests/baselines/local/projectOutput/*
tests/baselines/reference/testresults.tap
tests/services/baselines/prototyping/local/*
tests/services/browser/typescriptServices.js
scripts/authors.js
scripts/configureNightly.js
scripts/processDiagnosticMessages.d.ts
scripts/processDiagnosticMessages.js
scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js
src/harness/*.js
src/compiler/diagnosticInformationMap.generated.ts
src/compiler/diagnosticMessages.generated.json
@@ -43,7 +38,12 @@ scripts/run.bat
scripts/word2md.js
scripts/buildProtocol.js
scripts/ior.js
scripts/buildProtocol.js
scripts/authors.js
scripts/configureNightly.js
scripts/processDiagnosticMessages.d.ts
scripts/processDiagnosticMessages.js
scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js
scripts/generateLocalizedDiagnosticMessages.js
scripts/*.js.map
scripts/typings/
coverage/
+2 -1
View File
@@ -5,6 +5,7 @@ internal
issue_template.md
jenkins.sh
lib/README.md
lib/enu
netci.groovy
pull_request_template.md
scripts
@@ -16,5 +17,5 @@ Jakefile.js
.gitattributes
.settings/
.travis.yml
.vscode/
.vscode/
test.config
+15 -15
View File
@@ -1,15 +1,15 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
+51 -11
View File
@@ -34,6 +34,14 @@ const gulp = helpMaker(originalGulp);
Error.stackTraceLimit = 1000;
/**
* This regexp exists to capture our const enums and replace them with normal enums in our public API
* - this is fine since we compile with preserveConstEnums, and ensures our consumers are not locked
* to the TS version they compile with.
*/
const constEnumCaptureRegexp = /^(\s*)(export )?const enum (\S+) {(\s*)$/gm;
const constEnumReplacement = "$1$2enum $3 {$4";
const cmdLineOptions = minimist(process.argv.slice(2), {
boolean: ["debug", "inspect", "light", "colors", "lint", "soft"],
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
@@ -87,6 +95,7 @@ const harnessDirectory = "src/harness/";
const libraryDirectory = "src/lib/";
const scriptsDirectory = "scripts/";
const docDirectory = "doc/";
const lclDirectory = "src/loc/lcl";
const builtDirectory = "built/";
const builtLocalDirectory = "built/local/";
@@ -260,8 +269,8 @@ function getCompilerSettings(base: tsc.Settings, useBuiltCompiler?: boolean): ts
}
if (!useDebugMode) {
if (copy.removeComments === undefined) copy.removeComments = true;
copy.newLine = "lf";
}
copy.newLine = "lf";
if (useBuiltCompiler === true) {
copy.typescript = require("./built/local/typescript.js");
}
@@ -367,6 +376,36 @@ gulp.task(builtGeneratedDiagnosticMessagesJSON, [diagnosticInfoMapTs], (done) =>
gulp.task("generate-diagnostics", "Generates a diagnostic file in TypeScript based on an input JSON file", [diagnosticInfoMapTs]);
// Localize diagnostics script
const generateLocalizedDiagnosticMessagesJs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.js");
const generateLocalizedDiagnosticMessagesTs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.ts");
gulp.task(generateLocalizedDiagnosticMessagesJs, /*help*/ false, [], () => {
const settings: tsc.Settings = getCompilerSettings({
target: "es5",
declaration: false,
removeComments: true,
noResolve: false,
stripInternal: false,
types: ["node", "xml2js"]
}, /*useBuiltCompiler*/ false);
return gulp.src(generateLocalizedDiagnosticMessagesTs)
.pipe(newer(generateLocalizedDiagnosticMessagesJs))
.pipe(sourcemaps.init())
.pipe(tsc(settings))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest("."));
});
// Localize diagnostics
const generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg");
gulp.task(generatedLCGFile, [generateLocalizedDiagnosticMessagesJs, diagnosticInfoMapTs], (done) => {
if (fs.existsSync(builtLocalDirectory) && needsUpdate(generatedDiagnosticMessagesJSON, generatedLCGFile)) {
exec(host, [generateLocalizedDiagnosticMessagesJs, lclDirectory, builtLocalDirectory, generatedDiagnosticMessagesJSON], done, done);
}
});
gulp.task("localize", [generatedLCGFile]);
const servicesFile = path.join(builtLocalDirectory, "typescriptServices.js");
const standaloneDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts");
@@ -401,7 +440,7 @@ gulp.task(servicesFile, /*help*/ false, ["lib", "generate-diagnostics"], () => {
const completedDts = dts.pipe(prependCopyright(/*outputCopyright*/ true))
.pipe(insert.transform((contents, file) => {
file.path = standaloneDefinitionsFile;
return contents.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4");
return contents.replace(constEnumCaptureRegexp, constEnumReplacement);
}));
return merge2([
completedJs,
@@ -411,7 +450,7 @@ gulp.task(servicesFile, /*help*/ false, ["lib", "generate-diagnostics"], () => {
completedDts.pipe(clone())
.pipe(insert.transform((content, file) => {
file.path = nodeDefinitionsFile;
return content + "\r\nexport = ts;";
return content + "\nexport = ts;";
}))
.pipe(gulp.dest("src/services")),
completedDts.pipe(clone())
@@ -478,7 +517,7 @@ gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile, typesMapJson], (do
.pipe(gulp.dest("src/server")),
dts.pipe(prependCopyright(/*outputCopyright*/ true))
.pipe(insert.transform((content) => {
return content + "\r\nexport = ts;\r\nexport as namespace ts;";
return content.replace(constEnumCaptureRegexp, constEnumReplacement) + "\nexport = ts;\nexport as namespace ts;";
}))
.pipe(gulp.dest("src/server"))
]);
@@ -494,7 +533,7 @@ gulp.task(typesMapJson, /*help*/ false, [], () => {
});
gulp.task("lssl", "Builds language service server library", [tsserverLibraryFile]);
gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile]);
gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile, "localize"]);
gulp.task("tsc", "Builds only the compiler", [builtLocalCompiler]);
// Generate Markdown spec
@@ -536,15 +575,16 @@ gulp.task("dontUseDebugMode", /*help*/ false, [], (done) => { useDebugMode = fal
gulp.task("VerifyLKG", /*help*/ false, [], () => {
const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, typingsInstallerJs, cancellationTokenJs].concat(libraryTargets);
const missingFiles = expectedFiles.filter(function(f) {
return !fs.existsSync(f);
});
const missingFiles = expectedFiles.
concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d, "diagnosticMessages.generated.json"); })).
concat(generatedLCGFile).
filter(f => !fs.existsSync(f));
if (missingFiles.length > 0) {
throw new Error("Cannot replace the LKG unless all built targets are present in directory " + builtLocalDirectory +
". The following files are missing:\n" + missingFiles.join("\n"));
}
// Copy all the targets into the LKG directory
return gulp.src(expectedFiles).pipe(gulp.dest(LKGDirectory));
return gulp.src([...expectedFiles, path.join(builtLocalDirectory, "**"), `!${path.join(builtLocalDirectory, "tslint")}`, `!${path.join(builtLocalDirectory, "*.*")}`]).pipe(gulp.dest(LKGDirectory));
});
gulp.task("LKGInternal", /*help*/ false, ["lib", "local"]);
@@ -556,7 +596,7 @@ gulp.task("LKG", "Makes a new LKG out of the built js files", ["clean", "dontUse
// Task to build the tests infrastructure using the built compiler
const run = path.join(builtLocalDirectory, "run.js");
gulp.task(run, /*help*/ false, [servicesFile], () => {
gulp.task(run, /*help*/ false, [servicesFile, tsserverLibraryFile], () => {
const testProject = tsc.createProject("src/harness/tsconfig.json", getCompilerSettings({}, /*useBuiltCompiler*/ true));
return testProject.src()
.pipe(newer(run))
@@ -1051,7 +1091,7 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are:
const fileMatcher = cmdLineOptions["files"];
const files = fileMatcher
? `src/**/${fileMatcher}`
: "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
console.log("Linting: " + cmd);
child_process.execSync(cmd, { stdio: [0, 1, 2] });
+49 -13
View File
@@ -17,6 +17,7 @@ var libraryDirectory = "src/lib/";
var scriptsDirectory = "scripts/";
var unittestsDirectory = "src/harness/unittests/";
var docDirectory = "doc/";
var lclDirectory = "src/loc/lcl";
var builtDirectory = "built/";
var builtLocalDirectory = "built/local/";
@@ -93,6 +94,7 @@ var typesMapOutputPath = path.join(builtLocalDirectory, 'typesMap.json');
var harnessCoreSources = [
"harness.ts",
"virtualFileSystem.ts",
"virtualFileSystemWithWatch.ts",
"sourceMapRecorder.ts",
"harnessLanguageService.ts",
"fourslash.ts",
@@ -125,7 +127,6 @@ var harnessSources = harnessCoreSources.concat([
"transpile.ts",
"reuseProgramStructure.ts",
"textStorage.ts",
"cachingInServerLSHost.ts",
"moduleResolution.ts",
"tsconfigParsing.ts",
"commandLineParsing.ts",
@@ -133,6 +134,7 @@ var harnessSources = harnessCoreSources.concat([
"convertCompilerOptionsFromJson.ts",
"convertTypeAcquisitionFromJson.ts",
"tsserverProjectSystem.ts",
"tscWatchMode.ts",
"compileOnSave.ts",
"typingsInstaller.ts",
"projectErrors.ts",
@@ -150,6 +152,7 @@ var harnessSources = harnessCoreSources.concat([
"programMissingFiles.ts",
"symbolWalker.ts",
"languageService.ts",
"publicApi.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
@@ -157,7 +160,6 @@ var harnessSources = harnessCoreSources.concat([
"utilities.ts",
"scriptVersionCache.ts",
"scriptInfo.ts",
"lsHost.ts",
"project.ts",
"typingsCache.ts",
"editorServices.ts",
@@ -340,9 +342,7 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts
options += " -sourcemap";
}
}
else {
options += " --newLine LF";
}
options += " --newLine LF";
if (opts.stripInternal) {
options += " --stripInternal";
@@ -427,7 +427,40 @@ compileFile(processDiagnosticMessagesJs,
[processDiagnosticMessagesTs],
[processDiagnosticMessagesTs],
[],
/*useBuiltCompiler*/ false);
/*useBuiltCompiler*/ false);
// Localize diagnostics script
var generateLocalizedDiagnosticMessagesJs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.js");
var generateLocalizedDiagnosticMessagesTs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.ts");
file(generateLocalizedDiagnosticMessagesTs);
compileFile(generateLocalizedDiagnosticMessagesJs,
[generateLocalizedDiagnosticMessagesTs],
[generateLocalizedDiagnosticMessagesTs],
[],
/*useBuiltCompiler*/ false, { noOutFile: true, types: ["node", "xml2js"] });
// Localize diagnostics
var generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg");
file(generatedLCGFile, [generateLocalizedDiagnosticMessagesJs, diagnosticInfoMapTs, generatedDiagnosticMessagesJSON], function () {
var cmd = host + " " + generateLocalizedDiagnosticMessagesJs + " " + lclDirectory + " " + builtLocalDirectory + " " + generatedDiagnosticMessagesJSON;
console.log(cmd);
var ex = jake.createExec([cmd]);
// Add listeners for output and error
ex.addListener("stdout", function (output) {
process.stdout.write(output);
});
ex.addListener("stderr", function (error) {
process.stderr.write(error);
});
ex.addListener("cmdEnd", function () {
complete();
});
ex.run();
}, { async: true });
task("localize", [generatedLCGFile]);
var buildProtocolTs = path.join(scriptsDirectory, "buildProtocol.ts");
var buildProtocolJs = path.join(scriptsDirectory, "buildProtocol.js");
@@ -574,7 +607,7 @@ compileFile(servicesFile, servicesSources, [builtLocalDirectory, copyright].conc
// Official node package definition file, pointed to by 'typings' in package.json
// Created by appending 'export = ts;' at the end of the standalone file to turn it into an external module
var nodeDefinitionsFileContents = definitionFileContents + "\r\nexport = ts;";
var nodeDefinitionsFileContents = definitionFileContents + "\nexport = ts;";
fs.writeFileSync(nodeDefinitionsFile, nodeDefinitionsFileContents);
// Node package definition file to be distributed without the package. Created by replacing
@@ -621,8 +654,8 @@ compileFile(
// Appending exports at the end of the server library
var tsserverLibraryDefinitionFileContents =
fs.readFileSync(tsserverLibraryDefinitionFile).toString() +
"\r\nexport = ts;" +
"\r\nexport as namespace ts;";
"\nexport = ts;" +
"\nexport as namespace ts;";
tsserverLibraryDefinitionFileContents = removeConstModifierFromEnumDeclarations(tsserverLibraryDefinitionFileContents);
fs.writeFileSync(tsserverLibraryDefinitionFile, tsserverLibraryDefinitionFileContents);
@@ -644,7 +677,7 @@ task("build-fold-end", [], function () {
// Local target to build the compiler and services
desc("Builds the full compiler and services");
task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, buildProtocolDts, builtGeneratedDiagnosticMessagesJSON, "lssl", "build-fold-end"]);
task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, buildProtocolDts, builtGeneratedDiagnosticMessagesJSON, "lssl", "localize", "build-fold-end"]);
// Local target to build only tsc.js
desc("Builds only the compiler");
@@ -699,7 +732,10 @@ task("generate-spec", [specMd]);
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
desc("Makes a new LKG out of the built js files");
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () {
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts, watchGuardFile].concat(libraryTargets);
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts, watchGuardFile].
concat(libraryTargets).
concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d) })).
concat(path.dirname(generatedLCGFile));
var missingFiles = expectedFiles.filter(function (f) {
return !fs.existsSync(f);
});
@@ -726,7 +762,7 @@ var run = path.join(builtLocalDirectory, "run.js");
compileFile(
/*outFile*/ run,
/*source*/ harnessSources,
/*prereqs*/[builtLocalDirectory, tscFile].concat(libraryTargets).concat(servicesSources).concat(harnessSources),
/*prereqs*/[builtLocalDirectory, tscFile, tsserverLibraryFile].concat(libraryTargets).concat(servicesSources).concat(harnessSources),
/*prefixes*/[],
/*useBuiltCompiler:*/ true,
/*opts*/ { types: ["node", "mocha", "chai"], lib: "es6" });
@@ -1229,7 +1265,7 @@ task("lint", ["build-rules"], () => {
const fileMatcher = process.env.f || process.env.file || process.env.files;
const files = fileMatcher
? `src/**/${fileMatcher}`
: "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
console.log("Linting: " + cmd);
jake.exec([cmd], { interactive: true }, () => {
+2
View File
@@ -49,6 +49,8 @@
"@types/q": "latest",
"@types/run-sequence": "latest",
"@types/through2": "latest",
"@types/xml2js": "^0.4.0",
"xml2js": "^0.4.19",
"browser-resolve": "^1.11.2",
"browserify": "latest",
"chai": "latest",
@@ -0,0 +1,139 @@
import * as fs from "fs";
import * as path from "path";
import * as xml2js from "xml2js";
function main(): void {
const args = process.argv.slice(2);
if (args.length !== 3) {
console.log("Usage:");
console.log("\tnode generateLocalizedDiagnosticMessages.js <lcl source directory> <output directory> <generated diagnostics map file>");
return;
}
const inputPath = args[0];
const outputPath = args[1];
const diagnosticsMapFilePath = args[2];
// generate the lcg file for enu
generateLCGFile();
// generate other langs
fs.readdir(inputPath, (err, files) => {
handleError(err);
files.forEach(visitDirectory);
});
return;
function visitDirectory(name: string) {
const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl");
const outputFilePath = path.join(outputPath, name, "diagnosticMessages.generated.json");
fs.readFile(inputFilePath, (err, data) => {
handleError(err);
xml2js.parseString(data.toString(), (err, result) => {
handleError(err);
writeFile(outputFilePath, xmlObjectToString(result));
});
});
}
function handleError(err: null | object) {
if (err) {
console.error(err);
process.exit(1);
}
}
function xmlObjectToString(o: any) {
const out: any = {};
for (const item of o["LCX"]["Item"][0]["Item"][0]["Item"]) {
let ItemId = item["$"]["ItemId"];
let Val = item["Str"][0]["Tgt"] ? item["Str"][0]["Tgt"][0]["Val"][0] : item["Str"][0]["Val"][0];
if (typeof ItemId !== "string" || typeof Val !== "string") {
console.error("Unexpected XML file structure");
process.exit(1);
}
if (ItemId.charAt(0) === ";") {
ItemId = ItemId.slice(1); // remove leading semicolon
}
Val = Val.replace(/]5D;/, "]"); // unescape `]`
out[ItemId] = Val;
}
return JSON.stringify(out, undefined, 2);
}
function ensureDirectoryExists(directoryPath: string, action: () => void) {
fs.exists(directoryPath, exists => {
if (!exists) {
const basePath = path.dirname(directoryPath);
if (basePath !== directoryPath) {
return ensureDirectoryExists(basePath, () => fs.mkdir(directoryPath, action));
}
}
action();
});
}
function writeFile(fileName: string, contents: string) {
ensureDirectoryExists(path.dirname(fileName), () => {
fs.writeFile(fileName, contents, handleError);
});
}
function objectToList(o: Record<string, string>) {
const list: { key: string, value: string }[] = [];
for (const key in o) {
list.push({ key, value: o[key] });
}
return list;
}
function generateLCGFile() {
return fs.readFile(diagnosticsMapFilePath, (err, data) => {
handleError(err);
writeFile(
path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"),
getLCGFileXML(
objectToList(JSON.parse(data.toString()))
.sort((a, b) => a.key > b.key ? 1 : -1) // lcg sorted by property keys
.reduce((s, { key, value }) => s + getItemXML(key, value), "")
));
});
function getItemXML(key: string, value: string) {
// escape entrt value
value = value.replace(/]/, "]5D;");
return `
<Item ItemId=";${key}" ItemType="0" PsrId="306" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[${value}]]></Val>
</Str>
<Disp Icon="Str" />
</Item>`;
}
function getLCGFileXML(items: string) {
return `<?xml version="1.0" encoding="utf-8"?>
<LCX SchemaVersion="6.0" Name="diagnosticMessages.generated.json" PsrId="306" FileType="1" SrcCul="en-US" xmlns="http://schemas.microsoft.com/locstudio/2006/6/lcx">
<OwnedComments>
<Cmt Name="Dev" />
<Cmt Name="LcxAdmin" />
<Cmt Name="Rccx" />
</OwnedComments>
<Item ItemId=";String Table" ItemType="0" PsrId="306" Leaf="false">
<Disp Icon="Expand" Expand="true" Disp="true" LocTbl="false" />
<Item ItemId=";Strings" ItemType="0" PsrId="306" Leaf="false">
<Disp Icon="Str" Disp="true" LocTbl="false" />${items}
</Item>
</Item>
</LCX>`;
}
}
}
main();
+524
View File
@@ -0,0 +1,524 @@
/// <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 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);
}
}
}
}
/* @internal */
namespace ts {
export interface Builder {
/**
* Call this to feed new program
*/
updateProgram(newProgram: Program): 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[];
/** 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 {
/**
* Called when sourceFile is added to the program
*/
onAddSourceFile(program: Program, sourceFile: SourceFile): void;
/**
* Called when sourceFile is removed from the program
*/
onRemoveSourceFile(path: Path): void;
/**
* Called when sourceFile is changed
*/
onUpdateSourceFile(program: Program, sourceFile: SourceFile): void;
/**
* Called when source file has not changed but has some of the resolutions invalidated
* If returned true, builder will mark the file as changed (noting that something associated with file has changed)
*/
onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean;
/**
* Gets the files affected by the script info which has updated shape from the known one
*/
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
}
interface FileInfo {
fileName: string;
version: string;
signature: string;
}
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;
const fileInfos = createMap<FileInfo>();
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
/** The map has key by source file's path that has been changed */
const changedFileNames = createMap<string>();
let emitHandler: EmitHandler;
return {
updateProgram,
getFilesAffectedBy,
emitFile,
emitChangedFiles,
getSemanticDiagnostics,
clear
};
function createProgramGraph(program: Program) {
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: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile)
}
);
}
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.onAddSourceFile(program, sourceFile);
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
}
function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) {
registerChangedFile(path, existingFileInfo.fileName);
emitHandler.onRemoveSourceFile(path);
}
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) {
if (existingInfo.version !== sourceFile.version) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
existingInfo.version = sourceFile.version;
emitHandler.onUpdateSourceFile(program, sourceFile);
}
else if (program.hasInvalidatedResolution(sourceFile.path) &&
emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
}
}
function ensureProgramGraph(program: Program) {
if (!emitHandler) {
createProgramGraph(program);
}
}
function updateProgram(newProgram: Program) {
if (emitHandler) {
createProgramGraph(newProgram);
}
}
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 = addRange(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 = addRange(diagnostics, cachedDiagnostics);
}
}
return diagnostics || emptyArray;
}
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> | undefined {
let referencedFiles: Map<true> | undefined;
// 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) {
addReferencedFile(declarationSourceFile.path);
}
}
}
}
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);
addReferencedFile(referencedPath);
}
}
// Handle type reference directives
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
if (!resolvedTypeReferenceDirective) {
return;
}
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
addReferencedFile(typeFilePath);
});
}
return referencedFiles;
function addReferencedFile(referencedPath: Path) {
if (!referencedFiles) {
referencedFiles = createMap<true>();
}
referencedFiles.set(referencedPath, true);
}
}
/**
* 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 {
onAddSourceFile: noop,
onRemoveSourceFile: noop,
onUpdateSourceFile: noop,
onUpdateSourceFileWithSameVersion: returnFalse,
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 {
onAddSourceFile: setReferences,
onRemoveSourceFile,
onUpdateSourceFile: updateReferences,
onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences,
getFilesAffectedByUpdatedShape
};
function setReferences(program: Program, sourceFile: SourceFile) {
const newReferences = getReferencedFiles(program, sourceFile);
if (newReferences) {
references.set(sourceFile.path, newReferences);
}
}
function updateReferences(program: Program, sourceFile: SourceFile) {
const newReferences = getReferencedFiles(program, sourceFile);
if (newReferences) {
references.set(sourceFile.path, newReferences);
}
else {
references.delete(sourceFile.path);
}
}
function updateReferencesTrackingChangedReferences(program: Program, sourceFile: SourceFile) {
const newReferences = getReferencedFiles(program, sourceFile);
if (!newReferences) {
// Changed if we had references
return references.delete(sourceFile.path);
}
const oldReferences = references.get(sourceFile.path);
references.set(sourceFile.path, newReferences);
if (!oldReferences || oldReferences.size !== newReferences.size) {
return true;
}
// If there are any new references that werent present previously there is change
return forEachEntry(newReferences, (_true, referencedPath) => !oldReferences.delete(referencedPath)) ||
// Otherwise its changed if there are more references previously than now
!!oldReferences.size;
}
function onRemoveSourceFile(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 flatMapIter(seenFileNamesMap.values(), value => value);
}
}
}
}
+178 -28
View File
@@ -69,6 +69,7 @@ namespace ts {
const noUnusedIdentifiers = !!compilerOptions.noUnusedLocals || !!compilerOptions.noUnusedParameters;
const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System;
const strictNullChecks = compilerOptions.strictNullChecks === undefined ? compilerOptions.strict : compilerOptions.strictNullChecks;
const strictFunctionTypes = compilerOptions.strictFunctionTypes === undefined ? compilerOptions.strict : compilerOptions.strictFunctionTypes;
const noImplicitAny = compilerOptions.noImplicitAny === undefined ? compilerOptions.strict : compilerOptions.noImplicitAny;
const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis;
@@ -284,6 +285,11 @@ namespace ts {
const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
markerSubType.constraint = markerSuperType;
const markerOtherType = <TypeParameter>createType(TypeFlags.TypeParameter);
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
@@ -1215,7 +1221,7 @@ namespace ts {
}
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) {
@@ -2527,7 +2533,7 @@ namespace ts {
return typeReferenceToTypeNode(<TypeReference>type);
}
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false);
const name = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false) : createIdentifier("?");
// Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter.
return createTypeReferenceNode(name, /*typeArguments*/ undefined);
}
@@ -6927,7 +6933,7 @@ namespace ts {
}
function getConstraintDeclaration(type: TypeParameter) {
return getDeclarationOfKind<TypeParameterDeclaration>(type.symbol, SyntaxKind.TypeParameter).constraint;
return type.symbol && getDeclarationOfKind<TypeParameterDeclaration>(type.symbol, SyntaxKind.TypeParameter).constraint;
}
function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type {
@@ -8797,6 +8803,9 @@ namespace ts {
source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes);
}
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
const strictVariance = strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
let result = Ternary.True;
const sourceThisType = getThisTypeOfSignature(source);
@@ -8804,7 +8813,7 @@ namespace ts {
const targetThisType = getThisTypeOfSignature(target);
if (targetThisType) {
// void sources are assignable to anything.
const related = compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false)
const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false)
|| compareTypes(targetThisType, sourceThisType, reportErrors);
if (!related) {
if (reportErrors) {
@@ -8838,7 +8847,7 @@ namespace ts {
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
const related = callbacks ?
compareSignaturesRelated(targetSig, sourceSig, /*checkAsCallback*/ true, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
!checkAsCallback && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
!checkAsCallback && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
if (!related) {
if (reportErrors) {
errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -9444,7 +9453,7 @@ namespace ts {
return result;
}
function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, reportErrors: boolean): Ternary {
function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, variances: Variance[], reportErrors: boolean): Ternary {
const sources = source.typeArguments || emptyArray;
const targets = target.typeArguments || emptyArray;
if (sources.length !== targets.length && relation === identityRelation) {
@@ -9453,11 +9462,45 @@ namespace ts {
const length = sources.length <= targets.length ? sources.length : targets.length;
let result = Ternary.True;
for (let i = 0; i < length; i++) {
const related = isRelatedTo(sources[i], targets[i], reportErrors);
if (!related) {
return Ternary.False;
// When variance information isn't available we default to covariance. This happens
// in the process of computing variance information for recursive types and when
// comparing 'this' type arguments.
const variance = i < variances.length ? variances[i] : Variance.Covariant;
// We ignore arguments for independent type parameters (because they're never witnessed).
if (variance !== Variance.Independent) {
const s = sources[i];
const t = targets[i];
let related = Ternary.True;
if (variance === Variance.Covariant) {
related = isRelatedTo(s, t, reportErrors);
}
else if (variance === Variance.Contravariant) {
related = isRelatedTo(t, s, reportErrors);
}
else if (variance === Variance.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
// errors. Then, if that doesn't succeed, we compare covariantly with error
// reporting. Thus, error elaboration will be based on the the covariant check,
// which is generally easier to reason about.
related = isRelatedTo(t, s, /*reportErrors*/ false);
if (!related) {
related = isRelatedTo(s, t, reportErrors);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, reportErrors);
if (related) {
related &= isRelatedTo(t, s, reportErrors);
}
}
if (!related) {
return Ternary.False;
}
result &= related;
}
result &= related;
}
return result;
}
@@ -9590,8 +9633,6 @@ namespace ts {
if (!constraint || constraint.flags & TypeFlags.Any) {
constraint = emptyObjectType;
}
// The constraint may need to be further instantiated with its 'this' type.
constraint = getTypeWithThisArgument(constraint, source);
// Report constraint errors only if the constraint is not the empty object type
const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
@@ -9620,11 +9661,34 @@ namespace ts {
}
}
else {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if relationship holds for all type arguments
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, reportErrors)) {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target &&
!(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) {
// We have type references to the same generic type, and the type references are not marker
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
return result;
}
// The type arguments did not relate appropriately, but it may be because we have no variance
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
// arguments). It might also be the case that the target type has a 'void' type argument for
// a covariant type parameter that is only used in return positions within the generic type
// (in which case any type argument is permitted on the source side). In those cases we proceed
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
// related and we can return here.
if (variances !== emptyArray && !hasCovariantVoidArgument(<TypeReference>target, variances)) {
// In some cases generic types that are covariant in regular type checking mode become
// invariant in --strictFunctionTypes mode because one or more type parameters are used in
// both co- and contravariant positions. In order to make it easier to diagnose *why* such
// types are invariant, if any of the type parameters are invariant we reset the reported
// errors and instead force a structural comparison (which will include elaborations that
// reveal the reason).
if (!(reportErrors && some(variances, v => v === Variance.Invariant))) {
return Ternary.False;
}
errorInfo = saveErrorInfo;
}
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
@@ -10035,6 +10099,69 @@ namespace ts {
}
}
// Return a type reference where the source type parameter is replaced with the target marker
// type, and flag the result as a marker type reference.
function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) {
const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t));
result.flags |= TypeFlags.MarkerType;
return result;
}
// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
// has been invoked recursively for the given generic type.
function getVariances(type: GenericType): Variance[] {
if (!strictFunctionTypes) {
return emptyArray;
}
const typeParameters = type.typeParameters || emptyArray;
let variances = type.variances;
if (!variances) {
if (type === globalArrayType || type === globalReadonlyArrayType) {
// Arrays are known to be covariant, no need to spend time computing this
variances = [Variance.Covariant];
}
else {
// The emptyArray singleton is used to signal a recursive invocation.
type.variances = emptyArray;
variances = [];
for (const tp of typeParameters) {
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType);
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
variance = Variance.Independent;
}
variances.push(variance);
}
}
type.variances = variances;
}
return variances;
}
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
// See comment at call in recursiveTypeRelatedTo for when this case matters.
function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean {
for (let i = 0; i < variances.length; i++) {
if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) {
return true;
}
}
return false;
}
function isUnconstrainedTypeParameter(type: Type) {
return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(<TypeParameter>type);
}
@@ -10310,6 +10437,11 @@ namespace ts {
getUnionType(types, /*subtypeReduction*/ true);
}
// Return the leftmost type for which no type to the right is a subtype.
function getCommonSubtype(types: Type[]) {
return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s);
}
function isArrayType(type: Type): boolean {
return getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target === globalArrayType;
}
@@ -10836,8 +10968,14 @@ namespace ts {
const sourceTypes = (<TypeReference>source).typeArguments || emptyArray;
const targetTypes = (<TypeReference>target).typeArguments || emptyArray;
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
const variances = getVariances((<TypeReference>source).target);
for (let i = 0; i < count; i++) {
inferFromTypes(sourceTypes[i], targetTypes[i]);
if (i < variances.length && variances[i] === Variance.Contravariant) {
inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
}
else {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
@@ -10908,6 +11046,17 @@ namespace ts {
}
}
function inferFromContravariantTypes(source: Type, target: Type) {
if (strictFunctionTypes) {
priority ^= InferencePriority.Contravariant;
inferFromTypes(source, target);
priority ^= InferencePriority.Contravariant;
}
else {
inferFromTypes(source, target);
}
}
function getInferenceInfoForType(type: Type) {
if (type.flags & TypeFlags.TypeVariable) {
for (const inference of inferences) {
@@ -10985,7 +11134,7 @@ namespace ts {
}
function inferFromSignature(source: Signature, target: Signature) {
forEachMatchingParameterType(source, target, inferFromTypes);
forEachMatchingParameterType(source, target, inferFromContravariantTypes);
if (source.typePredicate && target.typePredicate && source.typePredicate.kind === target.typePredicate.kind) {
inferFromTypes(source.typePredicate.type, target.typePredicate.type);
@@ -11058,11 +11207,13 @@ namespace ts {
!hasPrimitiveConstraint(inference.typeParameter) &&
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates;
// Infer widened union or supertype, or the unknown type for no common supertype. We infer union types
// for inferences coming from return types in order to avoid common supertype failures.
const unionOrSuperType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ?
getUnionType(baseCandidates, /*subtypeReduction*/ true) : getCommonSupertype(baseCandidates);
inferredType = getWidenedType(unionOrSuperType);
// If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if
// union types were requested or if all inferences were made from the return type position, infer a
// union type. Otherwise, infer a common supertype.
const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) :
context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, /*subtypeReduction*/ true) :
getCommonSupertype(baseCandidates);
inferredType = getWidenedType(unwidenedType);
}
else if (context.flags & InferenceFlags.NoDefault) {
// We use silentNeverType as the wildcard that signals no inferences.
@@ -16677,10 +16828,9 @@ namespace ts {
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures
*/
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined {
Debug.assert(!(elementType.flags & TypeFlags.Union));
const callSignature = resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
return callSignature;
return resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
}
/**
@@ -16692,7 +16842,7 @@ namespace ts {
* @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions.
* otherwise return undefined if tag-name of the opening-like element doesn't have call signatures
*/
function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined {
// If this function is called from language service, elementType can be a union type. This is not possible if the function is called from compiler (see: resolveCustomJsxElementAttributesType)
if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
@@ -16725,7 +16875,7 @@ namespace ts {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
// This code-path is called by language service
return resolveStatelessJsxOpeningLikeElement(<JsxOpeningLikeElement>node, checkExpression((<JsxOpeningLikeElement>node).tagName), candidatesOutArray);
return resolveStatelessJsxOpeningLikeElement(<JsxOpeningLikeElement>node, checkExpression((<JsxOpeningLikeElement>node).tagName), candidatesOutArray) || unknownSignature;
}
Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable.");
}
@@ -19154,7 +19304,7 @@ namespace ts {
const typeArgument = typeArguments[i];
result = result && checkTypeAssignableTo(
typeArgument,
getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument),
instantiateType(constraint, mapper),
typeArgumentNodes[i],
Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
}
+94 -55
View File
@@ -269,6 +269,13 @@ namespace ts {
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_null_checks
},
{
name: "strictFunctionTypes",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_checking_of_function_types
},
{
name: "noImplicitThis",
type: "boolean",
@@ -885,7 +892,7 @@ namespace ts {
*/
export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } {
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 +914,7 @@ namespace ts {
*/
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
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 {
@@ -1111,9 +1118,9 @@ namespace ts {
if (!isDoubleQuotedString(valueExpression)) {
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;
if (option && typeof option.type !== "string") {
if (option && !isString(option.type)) {
const customOption = <CommandLineOptionOfCustomType>option;
// Validate custom option type
if (!customOption.type.has(text.toLowerCase())) {
@@ -1184,7 +1191,7 @@ namespace ts {
function getCompilerOptionValueTypeString(option: CommandLineOption) {
return option.type === "list" ?
"Array" :
typeof option.type === "string" ? option.type : "string";
isString(option.type) ? option.type : "string";
}
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
@@ -1193,7 +1200,7 @@ namespace ts {
if (option.type === "list") {
return isArray(value);
}
const expectedType = typeof option.type === "string" ? option.type : "string";
const expectedType = isString(option.type) ? option.type : "string";
return typeof value === expectedType;
}
}
@@ -1413,12 +1420,13 @@ namespace ts {
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
const errors: Diagnostic[] = [];
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors);
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, getCanonicalFileName, resolutionStack, errors);
const { raw } = parsedConfig;
const options = extend(existingOptions, parsedConfig.options || {});
options.configFilePath = configFileName;
setConfigFileInOptions(options, sourceFile);
const { fileNames, wildcardDirectories } = getFileNames();
const { fileNames, wildcardDirectories, spec } = getFileNames();
return {
options,
fileNames,
@@ -1426,15 +1434,16 @@ namespace ts {
raw,
errors,
wildcardDirectories,
compileOnSave: !!raw.compileOnSave
compileOnSave: !!raw.compileOnSave,
configFileSpecs: spec
};
function getFileNames(): ExpandResult {
let fileNames: ReadonlyArray<string>;
let filesSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "files") && !isNullOrUndefined(raw["files"])) {
if (isArray(raw["files"])) {
fileNames = <ReadonlyArray<string>>raw["files"];
if (fileNames.length === 0) {
filesSpecs = <ReadonlyArray<string>>raw["files"];
if (filesSpecs.length === 0) {
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
}
}
@@ -1469,19 +1478,13 @@ namespace ts {
}
}
if (fileNames === undefined && includeSpecs === undefined) {
if (filesSpecs === undefined && includeSpecs === undefined) {
includeSpecs = ["**/*"];
}
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile);
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile);
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) {
errors.push(
createCompilerDiagnostic(
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
configFileName || "tsconfig.json",
JSON.stringify(includeSpecs || []),
JSON.stringify(excludeSpecs || [])));
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
}
return result;
@@ -1494,6 +1497,20 @@ namespace ts {
}
}
/*@internal*/
export function isErrorNoInputFiles(error: Diagnostic) {
return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code;
}
/*@internal*/
export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) {
return createCompilerDiagnostic(
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
configFileName || "tsconfig.json",
JSON.stringify(includeSpecs || []),
JSON.stringify(excludeSpecs || []));
}
interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
@@ -1515,11 +1532,11 @@ namespace ts {
host: ParseConfigHost,
basePath: string,
configFileName: string,
getCanonicalFileName: (fileName: string) => string,
resolutionStack: Path[],
errors: Push<Diagnostic>,
): ParsedTsconfig {
basePath = normalizeSlashes(basePath);
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
if (resolutionStack.indexOf(resolvedPath) >= 0) {
@@ -1579,7 +1596,7 @@ namespace ts {
let extendedConfigPath: Path;
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"));
}
else {
@@ -1673,7 +1690,7 @@ namespace ts {
return undefined;
}
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) {
extendedConfigPath = `${extendedConfigPath}.json` as Path;
if (!host.fileExists(extendedConfigPath)) {
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
@@ -1703,7 +1720,7 @@ namespace ts {
const extendedDirname = getDirectoryPath(extendedConfigPath);
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
getBaseFileName(extendedConfigPath), resolutionStack, errors);
getBaseFileName(extendedConfigPath), getCanonicalFileName, resolutionStack, errors);
if (sourceFile) {
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles);
}
@@ -1806,7 +1823,7 @@ namespace ts {
if (optType === "list" && isArray(value)) {
return convertJsonOptionOfListType(<CommandLineOptionOfListType>opt, value, basePath, errors);
}
else if (typeof optType !== "string") {
else if (!isString(optType)) {
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
}
return normalizeNonListOptionValue(opt, basePath, value);
@@ -1820,13 +1837,13 @@ namespace ts {
if (isNullOrUndefined(value)) return undefined;
if (option.type === "list") {
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 value;
}
else if (typeof option.type !== "string") {
return option.type.get(typeof value === "string" ? value.toLowerCase() : value);
else if (!isString(option.type)) {
return option.type.get(isString(value) ? value.toLowerCase() : value);
}
return normalizeNonListOptionValue(option, basePath, value);
}
@@ -1936,29 +1953,62 @@ namespace ts {
/**
* Expands an array of file specifications.
*
* @param fileNames The literal file names to include.
* @param include The wildcard file specifications to include.
* @param exclude The wildcard file specifications to exclude.
* @param filesSpecs The literal file names to include.
* @param includeSpecs The wildcard file specifications to include.
* @param excludeSpecs The wildcard file specifications to exclude.
* @param basePath The base path for any relative file specifications.
* @param options Compiler options.
* @param host The host used to resolve files and directories.
* @param errors An array for diagnostic reporting.
*/
function matchFileNames(
fileNames: ReadonlyArray<string>,
include: ReadonlyArray<string>,
exclude: ReadonlyArray<string>,
filesSpecs: ReadonlyArray<string>,
includeSpecs: ReadonlyArray<string>,
excludeSpecs: ReadonlyArray<string>,
basePath: string,
options: CompilerOptions,
host: ParseConfigHost,
errors: Push<Diagnostic>,
extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>,
jsonSourceFile: JsonSourceFile): ExpandResult {
jsonSourceFile: JsonSourceFile
): ExpandResult {
basePath = normalizePath(basePath);
let validatedIncludeSpecs: ReadonlyArray<string>, validatedExcludeSpecs: ReadonlyArray<string>;
// The exclude spec list is converted into a regular expression, which allows us to quickly
// test whether a file or directory should be excluded before recursively traversing the
// file system.
if (includeSpecs) {
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
}
if (excludeSpecs) {
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
}
// Wildcard directories (provided as part of a wildcard path) are stored in a
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
// or a recursive directory. This information is used by filesystem watchers to monitor for
// new entries in these paths.
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames);
const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories };
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions);
}
/**
* Gets the file names from the provided config file specs that contain, files, include, exclude and
* other properties needed to resolve the file names
* @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details
* @param basePath The base path for any relative file specifications.
* @param options Compiler options.
* @param host The host used to resolve files and directories.
* @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 {
basePath = normalizePath(basePath);
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
// Literal file names (provided via the "files" array in tsconfig.json) are stored in a
@@ -1971,19 +2021,7 @@ namespace ts {
// via wildcard, and to handle extension priority.
const wildcardFileMap = createMap<string>();
if (include) {
include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
}
if (exclude) {
exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
}
// Wildcard directories (provided as part of a wildcard path) are stored in a
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
// or a recursive directory. This information is used by filesystem watchers to monitor for
// new entries in these paths.
const wildcardDirectories = getWildcardDirectories(include, exclude, basePath, host.useCaseSensitiveFileNames);
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
// Rather than requery this for each file and filespec, we query the supported extensions
// once and store it on the expansion context.
@@ -1991,15 +2029,15 @@ namespace ts {
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
// remove a literal file.
if (fileNames) {
for (const fileName of fileNames) {
if (filesSpecs) {
for (const fileName of filesSpecs) {
const file = getNormalizedAbsolutePath(fileName, basePath);
literalFileMap.set(keyMapper(file), file);
}
}
if (include && include.length > 0) {
for (const file of host.readDirectory(basePath, supportedExtensions, exclude, include, /*depth*/ undefined)) {
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) {
for (const file of host.readDirectory(basePath, supportedExtensions, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
// If we have already included a literal or wildcard path with a
// higher priority extension, we should skip this file.
//
@@ -2027,11 +2065,12 @@ namespace ts {
const wildcardFiles = arrayFrom(wildcardFileMap.values());
return {
fileNames: literalFiles.concat(wildcardFiles),
wildcardDirectories
wildcardDirectories,
spec
};
}
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string) {
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string): ReadonlyArray<string> {
return specs.filter(spec => {
const diag = specToDiagnostic(spec, allowTrailingRecursion);
if (diag !== undefined) {
+245 -12
View File
@@ -467,13 +467,20 @@ namespace ts {
return result;
}
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U[] | undefined): U[] {
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U | U[] | undefined): U[] {
const result: U[] = [];
while (true) {
const { value, done } = iter.next();
if (done) break;
const res = mapfn(value);
if (res) result.push(...res);
if (res) {
if (isArray(res)) {
result.push(...res);
}
else {
result.push(res);
}
}
}
return result;
}
@@ -523,6 +530,19 @@ namespace ts {
return result;
}
export function mapDefinedIter<T, U>(iter: Iterator<T>, mapFn: (x: T) => U | undefined): U[] {
const result: U[] = [];
while (true) {
const { value, done } = iter.next();
if (done) break;
const res = mapFn(value);
if (res !== undefined) {
result.push(res);
}
}
return result;
}
/**
* Computes the first matching span of elements and returns a tuple of the first span
* and the remaining elements.
@@ -766,7 +786,7 @@ namespace ts {
* @param end The offset in `from` at which to stop copying values (non-inclusive).
*/
export function addRange<T>(to: T[] | undefined, from: ReadonlyArray<T> | undefined, start?: number, end?: number): T[] | undefined {
if (from === undefined) return to;
if (from === undefined || from.length === 0) return to;
if (to === undefined) return from.slice(start, end);
start = start === undefined ? 0 : toOffset(from, start);
end = end === undefined ? from.length : toOffset(from, end);
@@ -1222,6 +1242,13 @@ namespace ts {
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 {
return value !== undefined && test(value) ? value : undefined;
}
@@ -1232,7 +1259,13 @@ namespace ts {
}
/** Does nothing. */
export function noop(): void {}
export function noop(): void { }
/** Do nothing and return false */
export function returnFalse(): false { return false; }
/** Do nothing and return true */
export function returnTrue(): true { return true; }
/** Returns its argument. */
export function identity<T>(x: T) { return x; }
@@ -1476,16 +1509,16 @@ namespace ts {
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
while (text1 && text2) {
// We still have both chains.
const string1 = typeof text1 === "string" ? text1 : text1.messageText;
const string2 = typeof text2 === "string" ? text2 : text2.messageText;
const string1 = isString(text1) ? text1 : text1.messageText;
const string2 = isString(text2) ? text2 : text2.messageText;
const res = compareValues(string1, string2);
if (res) {
return res;
}
text1 = typeof text1 === "string" ? undefined : text1.next;
text2 = typeof text2 === "string" ? undefined : text2.next;
text1 = isString(text1) ? undefined : text1.next;
text2 = isString(text2) ? undefined : text2.next;
}
if (!text1 && !text2) {
@@ -1811,6 +1844,8 @@ namespace ts {
* Removes a trailing directory separator from a path.
* @param path The path.
*/
export function removeTrailingDirectorySeparator(path: Path): Path;
export function removeTrailingDirectorySeparator(path: string): string;
export function removeTrailingDirectorySeparator(path: string) {
if (path.charAt(path.length - 1) === directorySeparator) {
return path.substr(0, path.length - 1);
@@ -2073,8 +2108,8 @@ namespace ts {
}
export interface FileSystemEntries {
files: ReadonlyArray<string>;
directories: ReadonlyArray<string>;
readonly files: ReadonlyArray<string>;
readonly directories: ReadonlyArray<string>;
}
export interface FileMatcherPatterns {
@@ -2227,7 +2262,7 @@ namespace ts {
return ScriptKind.TS;
case Extension.Tsx:
return ScriptKind.TSX;
case ".json":
case Extension.Json:
return ScriptKind.JSON;
default:
return ScriptKind.Unknown;
@@ -2631,5 +2666,203 @@ namespace ts {
return (arg: T) => f(arg) && g(arg);
}
export function assertTypeIsNever(_: never): void {}
export function assertTypeIsNever(_: never): void { }
export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void;
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
clearCache(): void;
}
interface MutableFileSystemEntries {
readonly files: string[];
readonly directories: string[];
}
export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost {
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
return {
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
newLine: host.newLine,
readFile: (path, encoding) => host.readFile(path, encoding),
write: s => host.write(s),
writeFile,
fileExists,
directoryExists,
createDirectory,
getCurrentDirectory,
getDirectories,
readDirectory,
addOrDeleteFileOrDirectory,
addOrDeleteFile,
clearCache,
exit: code => host.exit(code)
};
function toPath(fileName: string) {
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
}
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
return cachedReadDirectoryResult.get(rootDirPath);
}
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
return getCachedFileSystemEntries(getDirectoryPath(path));
}
function getBaseNameOfFileName(fileName: string) {
return getBaseFileName(normalizePath(fileName));
}
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
const resultFromHost: MutableFileSystemEntries = {
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
directories: host.getDirectories(rootDir) || []
};
cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
return resultFromHost;
}
/**
* If the readDirectory result was already cached, it returns that
* Otherwise gets result from host and caches it.
* The host request is done under try catch block to avoid caching incorrect result
*/
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
const cachedResult = getCachedFileSystemEntries(rootDirPath);
if (cachedResult) {
return cachedResult;
}
try {
return createCachedFileSystemEntries(rootDir, rootDirPath);
}
catch (_e) {
// If there is exception to read directories, dont cache the result and direct the calls to host
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
return undefined;
}
}
function fileNameEqual(name1: string, name2: string) {
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
}
function hasEntry(entries: ReadonlyArray<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 addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
if (existingResult) {
// This was a folder already present, remove it if this doesnt exist any more
if (!host.directoryExists(fileOrDirectory)) {
cachedReadDirectoryResult.delete(fileOrDirectoryPath);
}
}
else {
// This was earlier a file (hence not in cached directory contents)
// or we never cached the directory containing it
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath);
if (parentResult) {
const baseName = getBaseNameOfFileName(fileOrDirectory);
if (parentResult) {
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath));
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath));
}
}
}
}
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();
}
}
}
+5 -5
View File
@@ -3142,10 +3142,6 @@
"category": "Message",
"code": 6128
},
"The config file '{0}' found doesn't contain any source files.": {
"category": "Error",
"code": 6129
},
"Resolving real path for '{0}', result '{1}'.": {
"category": "Message",
"code": 6130
@@ -3366,6 +3362,10 @@
"category": "Message",
"code": 6185
},
"Enable strict checking of function types.": {
"category": "Message",
"code": 6186
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
@@ -3772,7 +3772,7 @@
"code": 95003
},
"Extract to {0}": {
"Extract to {0} in {1}": {
"category": "Message",
"code": 95004
},
+4 -4
View File
@@ -81,7 +81,7 @@ namespace ts {
if (typeof value === "boolean") {
return value ? createTrue() : createFalse();
}
if (typeof value === "string") {
if (isString(value)) {
return createStringLiteral(value);
}
return createLiteralFromNode(value);
@@ -2208,7 +2208,7 @@ namespace ts {
export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) {
const node = <CatchClause>createSynthesizedNode(SyntaxKind.CatchClause);
node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
node.block = block;
return node;
}
@@ -2534,11 +2534,11 @@ namespace ts {
function asName(name: string | EntityName): EntityName;
function asName(name: string | Identifier | ThisTypeNode): Identifier | 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) {
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 {
+3 -20
View File
@@ -98,7 +98,7 @@ namespace ts {
}
const fileName = jsonContent[fieldName];
if (typeof fileName !== "string") {
if (!isString(fileName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
}
@@ -663,8 +663,8 @@ namespace ts {
}
if (matchedPattern) {
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}
@@ -1153,21 +1153,4 @@ namespace ts {
function toSearchResult<T>(value: T | undefined): SearchResult<T> {
return value !== undefined ? { value } : undefined;
}
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => SearchResult<T>): SearchResult<T> {
while (true) {
const result = callback(directory);
if (result !== undefined) {
return result;
}
const parentPath = getDirectoryPath(directory);
if (parentPath === directory) {
return undefined;
}
directory = parentPath;
}
}
}
+4 -4
View File
@@ -1217,8 +1217,8 @@ namespace ts {
return createIdentifier(isIdentifier(), diagnosticMessage);
}
function parseIdentifierName(): Identifier {
return createIdentifier(tokenIsIdentifierOrKeyword(token()));
function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier {
return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage);
}
function isLiteralPropertyName(): boolean {
@@ -1956,7 +1956,7 @@ namespace ts {
}
function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName {
let entity: EntityName = allowReservedWords ? parseIdentifierName() : parseIdentifier(diagnosticMessage);
let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage);
let dotPos = scanner.getStartPos();
while (parseOptional(SyntaxKind.DotToken)) {
if (token() === SyntaxKind.LessThanToken) {
@@ -2096,7 +2096,7 @@ namespace ts {
function parseTypeReference(): TypeReferenceNode {
const node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference);
node.typeName = parseEntityName(/*allowReservedWords*/ !!(contextFlags & NodeFlags.JSDoc), Diagnostics.Type_expected);
node.typeName = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected);
if (!scanner.hasPrecedingLineBreak() && token() === SyntaxKind.LessThanToken) {
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
}
+133 -31
View File
@@ -1,6 +1,7 @@
/// <reference path="sys.ts" />
/// <reference path="emitter.ts" />
/// <reference path="core.ts" />
/// <reference path="builder.ts" />
namespace ts {
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
@@ -229,19 +230,25 @@ namespace ts {
let output = "";
for (const diagnostic of diagnostics) {
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));
output += `${relativeFileName}(${line + 1},${character + 1}): `;
}
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
output += formatDiagnostic(diagnostic, host);
}
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 yellowForegroundEscapeSequence = "\u001b[93m";
const blueForegroundEscapeSequence = "\u001b[93m";
@@ -344,7 +351,7 @@ namespace ts {
}
export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string {
if (typeof messageText === "string") {
if (isString(messageText)) {
return messageText;
}
else {
@@ -393,6 +400,78 @@ namespace ts {
allDiagnostics?: Diagnostic[];
}
/**
* Determines if program structure is upto date or needs to be recreated
*/
export function isProgramUptoDate(
program: Program | undefined,
rootFileNames: string[],
newOptions: CompilerOptions,
getSourceVersion: (path: Path) => string,
fileExists: (fileName: string) => boolean,
hasInvalidatedResolution: HasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: boolean,
): boolean {
// If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date
if (!program || hasChangedAutomaticTypeDirectiveNames) {
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)
);
}
/**
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
* that represent a compilation unit.
@@ -454,9 +533,10 @@ namespace ts {
let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression;
let moduleResolutionCache: ModuleResolutionCache;
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModuleFull[];
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
if (host.resolveModuleNames) {
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => {
resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile, reusedNames).map(resolved => {
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
return resolved as ResolvedModuleFull;
@@ -491,10 +571,12 @@ namespace ts {
let redirectTargetsSet = createMap<true>();
const filesByName = createMap<SourceFile | undefined>();
let missingFilePaths: ReadonlyArray<Path>;
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
const structuralIsReused = tryReuseStructureFromOldProgram();
if (structuralIsReused !== StructureIsReused.Completely) {
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
@@ -528,13 +610,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
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
oldProgram = undefined;
@@ -568,6 +663,7 @@ namespace ts {
getSourceFileFromReference,
sourceFileToPackageName,
redirectTargetsSet,
hasInvalidatedResolution
};
verifyCompilerOptions();
@@ -662,21 +758,22 @@ namespace ts {
* * ResolvedModuleFull instance: can be reused.
*/
let result: ResolvedModuleFull[];
let reusedNames: string[];
/** A transient placeholder used to mark predicted resolution in the result list. */
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = <any>{};
for (let i = 0; i < moduleNames.length; i++) {
const moduleName = moduleNames[i];
// If we want to reuse resolutions more aggressively, we can refine this to check for whether the
// text of the corresponding modulenames has changed.
if (file === oldSourceFile) {
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) {
const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules.get(moduleName);
if (oldResolvedModule) {
if (isTraceEnabled(options, host)) {
trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile);
}
(result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule;
(reusedNames || (reusedNames = [])).push(moduleName);
continue;
}
}
@@ -705,7 +802,7 @@ namespace ts {
}
const resolutions = unknownModuleNames && unknownModuleNames.length
? resolveModuleNamesWorker(unknownModuleNames, containingFile)
? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames)
: emptyArray;
// Combine results of resolutions and predicted results
@@ -793,14 +890,21 @@ namespace ts {
const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = [];
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 enum SeenPackageName { Exists, Modified }
const seenPackageNames = createMap<SeenPackageName>();
for (const oldSourceFile of oldSourceFiles) {
let newSourceFile = host.getSourceFileByPath
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target)
: host.getSourceFile(oldSourceFile.fileName, options.target);
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target, /*onError*/ undefined, shouldCreateNewSourceFile)
: host.getSourceFile(oldSourceFile.fileName, options.target, /*onError*/ undefined, shouldCreateNewSourceFile);
if (!newSourceFile) {
return oldProgram.structureIsReused = StructureIsReused.Not;
@@ -883,6 +987,13 @@ namespace ts {
// tentatively approve the file
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
newSourceFiles.push(newSourceFile);
@@ -929,20 +1040,11 @@ namespace ts {
return oldProgram.structureIsReused;
}
// If a file has ceased to be missing, then we need to discard some of the old
// 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))) {
if (host.hasChangedAutomaticTypeDirectiveNames) {
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
for (const p of oldProgram.getMissingFilePaths()) {
filesByName.set(p, undefined);
}
missingFilePaths = oldProgram.getMissingFilePaths();
// update fileName -> file mapping
for (let i = 0; i < newSourceFiles.length; i++) {
@@ -1689,7 +1791,7 @@ namespace ts {
else {
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
}
});
}, shouldCreateNewSourceFile);
if (packageId) {
const packageIdKey = `${packageId.name}/${packageId.subModuleName}@${packageId.version}`;
+595
View File
@@ -0,0 +1,595 @@
/// <reference path="types.ts"/>
/// <reference path="core.ts"/>
/// <reference path="watchUtilities.ts"/>
/*@internal*/
namespace ts {
/** This is the cache of module/typedirectives resolution that can be retained across program */
export interface ResolutionCache {
startRecordingFilesWithChangedResolutions(): void;
finishRecordingFilesWithChangedResolutions(): Path[];
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[];
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
invalidateResolutionOfFile(filePath: Path): void;
removeResolutionsOfFile(filePath: Path): void;
createHasInvalidatedResolution(): HasInvalidatedResolution;
startCachingPerDirectoryResolution(): void;
finishCachingPerDirectoryResolution(): void;
updateTypeRootsWatch(): void;
closeTypeRootsWatch(): void;
clear(): void;
}
interface ResolutionWithFailedLookupLocations {
readonly failedLookupLocations: ReadonlyArray<string>;
isInvalidated?: boolean;
}
interface ResolutionWithResolvedFileName {
resolvedFileName: string | undefined;
}
interface ResolvedModuleWithFailedLookupLocations extends ts.ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
}
interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
}
export interface ResolutionCacheHost extends ModuleResolutionHost {
toPath(fileName: string): Path;
getCompilationSettings(): CompilerOptions;
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
onInvalidatedResolution(): void;
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
onChangedAutomaticTypeDirectiveNames(): void;
getCachedDirectoryStructureHost?(): CachedDirectoryStructureHost;
projectName?: string;
getGlobalCache?(): string | undefined;
writeLog(s: string): void;
maxNumberOfFilesToIterateForInvalidation?: number;
}
interface DirectoryWatchesOfFailedLookup {
/** watcher for the directory of failed lookup */
watcher: FileWatcher;
/** ref count keeping this directory watch alive */
refCount: number;
}
interface DirectoryOfFailedLookupWatch {
dir: string;
dirPath: Path;
}
export const maxNumberOfFilesToIterateForInvalidation = 256;
interface GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> {
(resolution: T): R;
}
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache {
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
let filesWithInvalidatedResolutions: Map<true> | undefined;
let allFilesHaveInvalidatedResolution = false;
// 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 perDirectoryResolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
/**
* These are the extensions that failed lookup files will have by default,
* any other extension of failed lookup will be store that path in custom failed lookup path
* This helps in not having to comb through all resolutions when files are added/removed
* Note that .d.ts file also has .d.ts extension hence will be part of default extensions
*/
const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json];
const customFailedLookupPaths = createMap<number>();
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
const rootPath = rootDir && resolutionHost.toPath(rootDir);
// TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
const typeRootsWatches = createMap<FileWatcher>();
return {
startRecordingFilesWithChangedResolutions,
finishRecordingFilesWithChangedResolutions,
startCachingPerDirectoryResolution,
finishCachingPerDirectoryResolution,
resolveModuleNames,
resolveTypeReferenceDirectives,
removeResolutionsOfFile,
invalidateResolutionOfFile,
createHasInvalidatedResolution,
updateTypeRootsWatch,
closeTypeRootsWatch,
clear
};
function getResolvedModule(resolution: ResolvedModuleWithFailedLookupLocations) {
return resolution.resolvedModule;
}
function getResolvedTypeReferenceDirective(resolution: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
return resolution.resolvedTypeReferenceDirective;
}
function isInDirectoryPath(dir: Path, file: Path) {
if (dir === undefined || file.length <= dir.length) {
return false;
}
return startsWith(file, dir) && file[dir.length] === directorySeparator;
}
function clear() {
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
customFailedLookupPaths.clear();
closeTypeRootsWatch();
resolvedModuleNames.clear();
resolvedTypeReferenceDirectives.clear();
allFilesHaveInvalidatedResolution = false;
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
}
function startRecordingFilesWithChangedResolutions() {
filesWithChangedSetOfUnresolvedImports = [];
}
function finishRecordingFilesWithChangedResolutions() {
const collected = filesWithChangedSetOfUnresolvedImports;
filesWithChangedSetOfUnresolvedImports = undefined;
return collected;
}
function createHasInvalidatedResolution(): HasInvalidatedResolution {
if (allFilesHaveInvalidatedResolution) {
// Any file asked would have invalidated resolution
filesWithInvalidatedResolutions = undefined;
return returnTrue;
}
const collected = filesWithInvalidatedResolutions;
filesWithInvalidatedResolutions = undefined;
return path => collected && collected.has(path);
}
function startCachingPerDirectoryResolution() {
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
}
function finishCachingPerDirectoryResolution() {
allFilesHaveInvalidatedResolution = false;
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
if (watcher.refCount === 0) {
directoryWatchesOfFailedLookups.delete(path);
watcher.watcher.close();
}
});
perDirectoryResolvedModuleNames.clear();
perDirectoryResolvedTypeReferenceDirectives.clear();
}
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 (!resolutionHost.getGlobalCache) {
return primaryResult;
}
// otherwise try to load typings from @types
const globalCache = resolutionHost.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, resolutionHost.projectName, compilerOptions, host, globalCache);
if (resolvedModule) {
return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as string[], failedLookupLocations) };
}
}
// Default return the result from the first pass
return primaryResult;
}
function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
names: string[],
containingFile: string,
cache: Map<Map<T>>,
perDirectoryCache: Map<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
reusedNames: string[] | undefined,
logChanges: boolean): R[] {
const path = resolutionHost.toPath(containingFile);
const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path);
const dirPath = getDirectoryPath(path);
let perDirectoryResolution = perDirectoryCache.get(dirPath);
if (!perDirectoryResolution) {
perDirectoryResolution = createMap();
perDirectoryCache.set(dirPath, perDirectoryResolution);
}
const resolvedModules: R[] = [];
const compilerOptions = resolutionHost.getCompilationSettings();
const seenNamesInFile = createMap<true>();
for (const name of names) {
let resolution = resolutionsInFile.get(name);
// Resolution is valid if it is present and not invalidated
if (!seenNamesInFile.has(name) &&
allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated) {
const existingResolution = resolution;
const resolutionInDirectory = perDirectoryResolution.get(name);
if (resolutionInDirectory) {
resolution = resolutionInDirectory;
}
else {
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
perDirectoryResolution.set(name, resolution);
}
resolutionsInFile.set(name, resolution);
if (resolution.failedLookupLocations) {
if (existingResolution && existingResolution.failedLookupLocations) {
watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution);
}
else {
watchFailedLookupLocationOfResolution(resolution, 0);
}
}
else if (existingResolution) {
stopWatchFailedLookupLocationOfResolution(existingResolution);
}
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 && !resolution.isInvalidated);
seenNamesInFile.set(name, true);
resolvedModules.push(getResolutionWithResolvedFileName(resolution));
}
// Stop watching and remove the unused name
resolutionsInFile.forEach((resolution, name) => {
if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) {
stopWatchFailedLookupLocationOfResolution(resolution);
resolutionsInFile.delete(name);
}
});
return resolvedModules;
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
if (oldResolution === newResolution) {
return true;
}
if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
return false;
}
const oldResult = getResolutionWithResolvedFileName(oldResolution);
const newResult = getResolutionWithResolvedFileName(newResolution);
if (oldResult === newResult) {
return true;
}
if (!oldResult || !newResult) {
return false;
}
return oldResult.resolvedFileName === newResult.resolvedFileName;
}
}
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
return resolveNamesWithLocalCache(
typeDirectiveNames, containingFile,
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
resolveTypeReferenceDirective, getResolvedTypeReferenceDirective,
/*reusedNames*/ undefined, /*logChanges*/ false
);
}
function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[] {
return resolveNamesWithLocalCache(
moduleNames, containingFile,
resolvedModuleNames, perDirectoryResolvedModuleNames,
resolveModuleName, getResolvedModule,
reusedNames, logChanges
);
}
function isNodeModulesDirectory(dirPath: Path) {
return endsWith(dirPath, "/node_modules");
}
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
return { dir: rootDir, dirPath: rootPath };
}
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()));
let dirPath = getDirectoryPath(failedLookupLocationPath);
// If the directory is node_modules use it to watch
if (isNodeModulesDirectory(dirPath)) {
return { dir, dirPath };
}
// If directory path contains node module, get the node_modules directory for watching
if (dirPath.indexOf("/node_modules/") !== -1) {
while (!isNodeModulesDirectory(dirPath)) {
dir = getDirectoryPath(dir);
dirPath = getDirectoryPath(dirPath);
}
return { dir, dirPath };
}
// Use some ancestor of the root directory
if (rootPath !== undefined) {
while (!isInDirectoryPath(dirPath, rootPath)) {
const parentPath = getDirectoryPath(dirPath);
if (parentPath === dirPath) {
break;
}
dirPath = parentPath;
dir = getDirectoryPath(dir);
}
}
return { dir, dirPath };
}
function isPathWithDefaultFailedLookupExtension(path: Path) {
return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
}
function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) {
const failedLookupLocations = resolution.failedLookupLocations;
const existingFailedLookupLocations = existingResolution.failedLookupLocations;
for (let index = 0; index < failedLookupLocations.length; index++) {
if (index === existingFailedLookupLocations.length) {
// Additional failed lookup locations, watch from this index
watchFailedLookupLocationOfResolution(resolution, index);
return;
}
else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) {
// Different failed lookup locations,
// Watch new resolution failed lookup locations from this index and
// stop watching existing resolutions from this index
watchFailedLookupLocationOfResolution(resolution, index);
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index);
return;
}
}
// All new failed lookup locations are already watched (and are same),
// Stop watching failed lookup locations of existing resolution after failed lookup locations length
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length);
}
function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
for (let i = startIndex; i < failedLookupLocations.length; i++) {
const failedLookupLocation = failedLookupLocations[i];
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
// If the failed lookup location path is not one of the supported extensions,
// store it in the custom path
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
}
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
if (dirWatcher) {
dirWatcher.refCount++;
}
else {
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
}
}
}
function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
if (resolution.failedLookupLocations) {
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
}
}
function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
for (let i = startIndex; i < failedLookupLocations.length; i++) {
const failedLookupLocation = failedLookupLocations[i];
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
if (refCount) {
if (refCount === 1) {
customFailedLookupPaths.delete(failedLookupLocationPath);
}
else {
Debug.assert(refCount > 1);
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
}
}
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
// Do not close the watcher yet since it might be needed by other failed lookup locations.
dirWatcher.refCount--;
}
}
function createDirectoryWatcher(directory: string, dirPath: Path) {
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
if (resolutionHost.getCachedDirectoryStructureHost) {
// Since the file existance changed, update the sourceFiles cache
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
// If the files are added to project root or node_modules directory, always run through the invalidation process
// Otherwise run through invalidation only if adding to the immediate directory
if (!allFilesHaveInvalidatedResolution &&
dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath) {
if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
resolutionHost.onInvalidatedResolution();
}
}
}, WatchDirectoryFlags.Recursive);
}
function removeResolutionsOfFileFromCache(cache: Map<Map<ResolutionWithFailedLookupLocations>>, filePath: Path) {
// Deleted file, stop watching failed lookups for all the resolutions in the file
const resolutions = cache.get(filePath);
if (resolutions) {
resolutions.forEach(stopWatchFailedLookupLocationOfResolution);
cache.delete(filePath);
}
}
function removeResolutionsOfFile(filePath: Path) {
removeResolutionsOfFileFromCache(resolvedModuleNames, filePath);
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath);
}
function invalidateResolutionCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
cache: Map<Map<T>>,
isInvalidatedResolution: (resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>) => boolean,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>
) {
const seen = createMap<Map<true>>();
cache.forEach((resolutions, containingFilePath) => {
const dirPath = getDirectoryPath(containingFilePath);
let seenInDir = seen.get(dirPath);
if (!seenInDir) {
seenInDir = createMap<true>();
seen.set(dirPath, seenInDir);
}
resolutions.forEach((resolution, name) => {
if (seenInDir.has(name)) {
return;
}
seenInDir.set(name, true);
if (!resolution.isInvalidated && isInvalidatedResolution(resolution, getResolutionWithResolvedFileName)) {
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
resolution.isInvalidated = true;
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
}
});
});
}
function hasReachedResolutionIterationLimit() {
const maxSize = resolutionHost.maxNumberOfFilesToIterateForInvalidation || maxNumberOfFilesToIterateForInvalidation;
return resolvedModuleNames.size > maxSize || resolvedTypeReferenceDirectives.size > maxSize;
}
function invalidateResolutions(
isInvalidatedResolution: (resolution: ResolutionWithFailedLookupLocations, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean,
) {
// If more than maxNumberOfFilesToIterateForInvalidation present,
// just invalidated all files and recalculate the resolutions for files instead
if (hasReachedResolutionIterationLimit()) {
allFilesHaveInvalidatedResolution = true;
return;
}
invalidateResolutionCache(resolvedModuleNames, isInvalidatedResolution, getResolvedModule);
invalidateResolutionCache(resolvedTypeReferenceDirectives, isInvalidatedResolution, getResolvedTypeReferenceDirective);
}
function invalidateResolutionOfFile(filePath: Path) {
removeResolutionsOfFile(filePath);
invalidateResolutions(
// Resolution is invalidated if the resulting file name is same as the deleted file path
(resolution, getResolutionWithResolvedFileName) => {
const result = getResolutionWithResolvedFileName(resolution);
return result && resolutionHost.toPath(result.resolvedFileName) === filePath;
}
);
}
function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
let isChangedFailedLookupLocation: (location: string) => boolean;
if (isCreatingWatchedDirectory) {
// Watching directory is created
// Invalidate any resolution has failed lookup in this directory
isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location));
}
else {
// Some file or directory in the watching directory is created
// Return early if it does not have any of the watching extension or not the custom failed lookup path
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
return false;
}
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
}
const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation);
const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
invalidateResolutions(
// Resolution is invalidated if the resulting file name is same as the deleted file path
hasChangedFailedLookupLocation
);
return allFilesHaveInvalidatedResolution || filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount;
}
function closeTypeRootsWatch() {
clearMap(typeRootsWatches, closeFileWatcher);
}
function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher {
// Create new watch and recursive info
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
if (resolutionHost.getCachedDirectoryStructureHost) {
// Since the file existance changed, update the sourceFiles cache
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
// For now just recompile
// We could potentially store more data here about whether it was/would be really be used or not
// and with that determine to trigger compilation but for now this is enough
resolutionHost.onChangedAutomaticTypeDirectiveNames();
}, WatchDirectoryFlags.Recursive);
}
/**
* Watches the types that would get added as part of getAutomaticTypeDirectiveNames
* To be called when compiler options change
*/
function updateTypeRootsWatch() {
const options = resolutionHost.getCompilationSettings();
if (options.types) {
// No need to do any watch since resolution cache is going to handle the failed lookups
// for the types added by this
closeTypeRootsWatch();
return;
}
// we need to assume the directories exist to ensure that we can get all the type root directories that get included
const typeRoots = getEffectiveTypeRoots(options, { directoryExists: returnTrue, getCurrentDirectory });
if (typeRoots) {
mutateMap(
typeRootsWatches,
arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
{
createNewValue: createTypeRootsWatch,
onDeleteValue: closeFileWatcher
}
);
}
else {
closeTypeRootsWatch();
}
}
}
}
+119 -66
View File
@@ -30,14 +30,27 @@ namespace ts {
mtime?: Date;
}
export interface System {
args: string[];
/**
* Partial interface of the System thats needed to support the caching of directory structure
*/
export interface DirectoryStructureHost {
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
readFile(path: string, encoding?: string): string | undefined;
getFileSize?(path: string): number;
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
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[];
exit(exitCode?: number): void;
}
export interface System extends DirectoryStructureHost {
args: string[];
getFileSize?(path: string): number;
/**
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
@@ -45,13 +58,7 @@ namespace ts {
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
createDirectory(path: string): void;
getExecutingFilePath(): string;
getCurrentDirectory(): string;
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
getModifiedTime?(path: string): Date;
/**
* This should be cryptographically secure.
@@ -59,7 +66,6 @@ namespace ts {
*/
createHash?(data: string): string;
getMemoryUsage?(): number;
exit(exitCode?: number): void;
realpath?(path: string): string;
/*@internal*/ getEnvironmentVariable(name: string): string;
/*@internal*/ tryEnableSourceMapsForHost?(): void;
@@ -72,8 +78,7 @@ namespace ts {
close(): void;
}
export interface DirectoryWatcher extends FileWatcher {
directoryName: string;
interface DirectoryWatcher extends FileWatcher {
referenceCount: number;
}
@@ -152,11 +157,10 @@ namespace ts {
watcher.referenceCount += 1;
return;
}
watcher = _fs.watch(
watcher = fsWatchDirectory(
dirPath || ".",
{ persistent: true },
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
);
) as DirectoryWatcher;
watcher.referenceCount = 1;
dirWatchers.set(dirPath, watcher);
return;
@@ -182,9 +186,9 @@ namespace ts {
fileWatcherCallbacks.remove(filePath, callback);
}
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
function fileEventHandler(eventName: string, relativeFileName: string | undefined, baseDirPath: string) {
// 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
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
// Some applications save a working file via rename operations
@@ -223,6 +227,95 @@ namespace ts {
const platform: string = _os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
function fsWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher {
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
return {
close: () => _fs.unwatchFile(fileName, fileChanged)
};
function fileChanged(curr: any, prev: any) {
const isCurrZero = +curr.mtime === 0;
const isPrevZero = +prev.mtime === 0;
const created = !isCurrZero && isPrevZero;
const deleted = isCurrZero && !isPrevZero;
const eventKind = created
? FileWatcherEventKind.Created
: deleted
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
return;
}
callback(fileName, eventKind);
}
}
function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher {
let options: any;
/** Watcher for the directory depending on whether it is missing or present */
let watcher = !directoryExists(directoryName) ?
watchMissingDirectory() :
watchPresentDirectory();
return {
close: () => {
// Close the watcher (either existing directory watcher or missing directory watcher)
watcher.close();
}
};
/**
* Watch the directory that is currently present
* and when the watched directory is deleted, switch to missing directory watcher
*/
function watchPresentDirectory(): FileWatcher {
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
if (options === undefined) {
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
options = { persistent: true, recursive: !!recursive };
}
else {
options = { persistent: true };
}
}
const dirWatcher = _fs.watch(
directoryName,
options,
callback
);
dirWatcher.on("error", () => {
if (!directoryExists(directoryName)) {
// Deleting directory
watcher = watchMissingDirectory();
// Call the callback for current directory
callback("rename", "");
}
});
return dirWatcher;
}
/**
* Watch the directory that is missing
* and switch to existing directory when the directory is created
*/
function watchMissingDirectory(): FileWatcher {
return fsWatchFile(directoryName, (_fileName, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) {
watcher.close();
watcher = watchPresentDirectory();
// Call the callback for current directory
// For now it could be callback for the inner directory creation,
// but just return current directory, better than current no-op
callback("rename", "");
}
});
}
}
function readFile(fileName: string, _encoding?: string): string | undefined {
if (!fileExists(fileName)) {
return undefined;
@@ -340,7 +433,6 @@ namespace ts {
return filter<string>(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory));
}
const noOpFileWatcher: FileWatcher = { close: noop };
const nodeSystem: System = {
args: process.argv.slice(2),
newLine: _os.EOL,
@@ -358,60 +450,21 @@ namespace ts {
};
}
else {
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
return {
close: () => _fs.unwatchFile(fileName, fileChanged)
};
}
function fileChanged(curr: any, prev: any) {
const isCurrZero = +curr.mtime === 0;
const isPrevZero = +prev.mtime === 0;
const created = !isCurrZero && isPrevZero;
const deleted = isCurrZero && !isPrevZero;
const eventKind = created
? FileWatcherEventKind.Created
: deleted
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
return;
}
callback(fileName, eventKind);
return fsWatchFile(fileName, callback, pollingInterval);
}
},
watchDirectory: (directoryName, callback, recursive) => {
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
let options: any;
if (!directoryExists(directoryName)) {
// do nothing if target folder does not exist
return noOpFileWatcher;
}
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
options = { persistent: true, recursive: !!recursive };
}
else {
options = { persistent: true };
}
return _fs.watch(
directoryName,
options,
(eventName: string, relativeFileName: string) => {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName)));
}
return fsWatchDirectory(directoryName, (eventName, relativeFileName) => {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName)));
}
);
}, recursive);
},
resolvePath: path => _path.resolve(path),
fileExists,
+92 -350
View File
@@ -1,48 +1,13 @@
/// <reference path="program.ts"/>
/// <reference path="watch.ts"/>
/// <reference path="commandLineParser.ts"/>
namespace ts {
export interface SourceFile {
fileWatcher?: FileWatcher;
}
interface Statistic {
name: 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 {
let count = 0;
forEach(program.getSourceFiles(), file => {
@@ -56,25 +21,11 @@ namespace ts {
return <string>diagnostic.messageText;
}
function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
sys.write(ts.formatDiagnostics([diagnostic], host));
}
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 }): `;
let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply);
function udpateReportDiagnostic(options: CompilerOptions) {
if (options.pretty) {
reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext);
}
output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`;
sys.write(output);
}
function padLeft(s: string, length: number) {
@@ -98,25 +49,12 @@ namespace ts {
export function executeCommandLine(args: string[]): void {
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 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 (!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);
}
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
@@ -125,7 +63,7 @@ namespace ts {
// If there are any errors due to command line parsing and/or
// setting up localization, report them and quit.
if (commandLine.errors.length > 0) {
reportDiagnostics(commandLine.errors, compilerHost);
reportDiagnostics(commandLine.errors, reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
@@ -147,11 +85,11 @@ namespace ts {
if (commandLine.options.project) {
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);
}
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);
}
@@ -159,14 +97,14 @@ namespace ts {
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
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);
}
}
else {
configFileName = fileOrDirectory;
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);
}
}
@@ -182,249 +120,94 @@ namespace ts {
return sys.exit(ExitStatus.Success);
}
if (isWatchSet(commandLine.options)) {
if (!sys.watchFile) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (configFileName) {
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);
const commandLineOptions = commandLine.options;
if (configFileName) {
const reportWatchDiagnostic = createWatchDiagnosticReporter();
const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic);
udpateReportDiagnostic(configParseResult.options);
if (isWatchSet(configParseResult.options)) {
if (!sys.watchFile) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
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);
}
reportWatchModeWithoutSysSupport();
createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic));
}
return configParseResult;
}
// 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 {
performCompilation(configParseResult.fileNames, configParseResult.options);
}
}
// 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 startTimerForRecompilation() {
if (!sys.setTimeout || !sys.clearTimeout) {
return;
else {
udpateReportDiagnostic(commandLineOptions);
if (isWatchSet(commandLineOptions)) {
reportWatchModeWithoutSysSupport();
createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost());
}
if (timerHandleForRecompilation) {
sys.clearTimeout(timerHandleForRecompilation);
else {
performCompilation(commandLine.fileNames, commandLineOptions);
}
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) {
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
let statistics: Statistic[];
if (hasDiagnostics) {
function reportWatchModeWithoutSysSupport() {
if (!sys.watchFile || !sys.watchDirectory) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
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().slice();
// 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().slice();
}
}
// Emit and report any errors we ran into.
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit();
addRange(diagnostics, emitDiagnostics);
return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic);
}
function enableStatistics(compilerOptions: CompilerOptions) {
if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) {
performance.enable();
}
}
function reportStatistics(program: Program) {
let statistics: Statistic[];
const compilerOptions = program.getCompilerOptions();
if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) {
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;
reportCountStatistic("Files", program.getSourceFiles().length);
reportCountStatistic("Lines", countLines(program));
@@ -462,44 +245,6 @@ namespace ts {
performance.disable();
}
return { program, exitStatus };
function compileProgram(): ExitStatus {
let diagnostics: Diagnostic[];
// First get and report any syntactic errors.
diagnostics = program.getSyntacticDiagnostics().slice();
// 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().slice();
}
}
// Otherwise, emit and report any errors we ran into.
const emitOutput = program.emit();
addRange(diagnostics, 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() {
let nameSize = 0;
let valueSize = 0;
@@ -653,19 +398,17 @@ namespace ts {
const currentDirectory = sys.getCurrentDirectory();
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
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 {
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;
}
}
ts.setStackTraceLimit();
if (ts.Debug.isDebugging) {
ts.Debug.enableDebugInfo();
}
@@ -673,5 +416,4 @@ if (ts.Debug.isDebugging) {
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
ts.sys.tryEnableSourceMapsForHost();
}
ts.executeCommandLine(ts.sys.args);
+4
View File
@@ -36,7 +36,11 @@
"sourcemap.ts",
"declarationEmitter.ts",
"emitter.ts",
"watchUtilities.ts",
"program.ts",
"builder.ts",
"resolutionCache.ts",
"watch.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
+58 -15
View File
@@ -2414,6 +2414,7 @@ namespace ts {
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
/* @internal */ version: string;
}
export interface Bundle extends Node {
@@ -2530,6 +2531,8 @@ namespace ts {
/* @internal */ sourceFileToPackageName: Map<string>;
/** Set of all source files that some other source file redirects to. */
/* @internal */ redirectTargetsSet: Map<true>;
/** Returns true when file in the program had invalidated resolution at the time of program creation. */
hasInvalidatedResolution: HasInvalidatedResolution;
}
/* @internal */
@@ -2658,10 +2661,10 @@ namespace ts {
getRootSymbols(symbol: Symbol): Symbol[];
getContextualType(node: Expression): Type | undefined;
/**
* returns unknownSignature in the case of an error. Don't know when it returns undefined.
* returns unknownSignature in the case of an error.
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
*/
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature;
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
isImplementationOfOverload(node: FunctionLike): boolean | undefined;
isUndefinedSymbol(symbol: Symbol): boolean;
@@ -3228,6 +3231,7 @@ namespace ts {
NonPrimitive = 1 << 25, // intrinsic object type
/* @internal */
JsxAttributes = 1 << 26, // Jsx attributes type
MarkerType = 1 << 27, // Marker type used for variance probing
/* @internal */
Nullable = Undefined | Null,
@@ -3364,10 +3368,21 @@ namespace ts {
typeArguments?: Type[]; // Type reference type arguments (undefined if none)
}
/* @internal */
export const enum Variance {
Invariant = 0, // Neither covariant nor contravariant
Covariant = 1, // Covariant
Contravariant = 2, // Contravariant
Bivariant = 3, // Both covariant and contravariant
Independent = 4, // Unwitnessed type parameter
}
// Generic class and interface types
export interface GenericType extends InterfaceType, TypeReference {
/* @internal */
instantiations: Map<TypeReference>; // Generic instantiation cache
instantiations: Map<TypeReference>; // Generic instantiation cache
/* @internal */
variances?: Variance[]; // Variance of each type parameter
}
export interface UnionOrIntersectionType extends Type {
@@ -3543,9 +3558,10 @@ namespace ts {
}
export const enum InferencePriority {
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
MappedType = 1 << 1, // Reverse inference for mapped type
ReturnType = 1 << 2, // Inference made from return type of generic function
Contravariant = 1 << 0, // Inference from contravariant position
NakedTypeVariable = 1 << 1, // Naked type variable in union or intersection type
MappedType = 1 << 2, // Reverse inference for mapped type
ReturnType = 1 << 3, // Inference made from return type of generic function
}
export interface InferenceInfo {
@@ -3667,6 +3683,7 @@ namespace ts {
charset?: string;
checkJs?: boolean;
/* @internal */ configFilePath?: string;
/** configFile is set as non enumerable property so as to avoid checking of json source files */
/* @internal */ readonly configFile?: JsonSourceFile;
declaration?: boolean;
declarationDir?: string;
@@ -3728,6 +3745,7 @@ namespace ts {
sourceMap?: boolean;
sourceRoot?: string;
strict?: boolean;
strictFunctionTypes?: boolean; // Always combine with strict property
strictNullChecks?: boolean; // Always combine with strict property
/* @internal */ stripInternal?: boolean;
suppressExcessPropertyErrors?: boolean;
@@ -3835,6 +3853,7 @@ namespace ts {
errors: Diagnostic[];
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
compileOnSave?: boolean;
configFileSpecs?: ConfigFileSpecs;
}
export const enum WatchDirectoryFlags {
@@ -3842,9 +3861,25 @@ namespace ts {
Recursive = 1 << 0,
}
export interface ConfigFileSpecs {
filesSpecs: ReadonlyArray<string>;
/**
* Present to report errors (user specified specs), validatedIncludeSpecs are used for file name matching
*/
includeSpecs: ReadonlyArray<string>;
/**
* Present to report errors (user specified specs), validatedExcludeSpecs are used for file name matching
*/
excludeSpecs: ReadonlyArray<string>;
validatedIncludeSpecs: ReadonlyArray<string>;
validatedExcludeSpecs: ReadonlyArray<string>;
wildcardDirectories: MapLike<WatchDirectoryFlags>;
}
export interface ExpandResult {
fileNames: string[];
wildcardDirectories: MapLike<WatchDirectoryFlags>;
spec: ConfigFileSpecs;
}
/* @internal */
@@ -4093,31 +4128,36 @@ namespace ts {
Tsx = ".tsx",
Dts = ".d.ts",
Js = ".js",
Jsx = ".jsx"
Jsx = ".jsx",
Json = ".json"
}
export interface ResolvedModuleWithFailedLookupLocations {
resolvedModule: ResolvedModuleFull | undefined;
readonly resolvedModule: ResolvedModuleFull | undefined;
/* @internal */
failedLookupLocations: string[];
readonly failedLookupLocations: ReadonlyArray<string>;
}
export interface ResolvedTypeReferenceDirective {
// True if the type declaration file was found in a primary lookup location
primary: boolean;
// The location of the .d.ts file we located, or undefined if resolution failed
resolvedFileName?: string;
resolvedFileName: string | undefined;
packageId?: PackageId;
}
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
failedLookupLocations: string[];
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
readonly failedLookupLocations: ReadonlyArray<string>;
}
export interface HasInvalidatedResolution {
(sourceFile: Path): boolean;
}
export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined;
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined;
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getCancellationToken?(): CancellationToken;
getDefaultLibFileName(options: CompilerOptions): string;
getDefaultLibLocation?(): string;
@@ -4135,12 +4175,15 @@ namespace ts {
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
* 'throw new Error("NotImplemented")'
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
/**
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
*/
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
getEnvironmentVariable?(name: string): string;
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
hasInvalidatedResolution?: HasInvalidatedResolution;
hasChangedAutomaticTypeDirectiveNames?: boolean;
}
/* @internal */
+96 -9
View File
@@ -377,7 +377,7 @@ namespace ts {
}
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__'
@@ -414,7 +414,10 @@ namespace ts {
((<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 } {
return isModuleDeclaration(node) && isStringLiteral(node.name);
}
@@ -1476,8 +1479,8 @@ namespace ts {
if (node.kind === SyntaxKind.ExportDeclaration) {
return (<ExportDeclaration>node).moduleSpecifier;
}
if (node.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
return (<ModuleDeclaration>node).name;
if (isModuleWithStringLiteralName(node)) {
return node.name;
}
}
@@ -3212,17 +3215,14 @@ namespace ts {
const carriageReturnLineFeed = "\r\n";
const lineFeed = "\n";
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions): string {
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: System): string {
switch (options.newLine) {
case NewLineKind.CarriageReturnLineFeed:
return carriageReturnLineFeed;
case NewLineKind.LineFeed:
return lineFeed;
}
if (sys) {
return sys.newLine;
}
return carriageReturnLineFeed;
return system ? system.newLine : sys ? sys.newLine : carriageReturnLineFeed;
}
/**
@@ -3516,6 +3516,93 @@ namespace ts {
return parent.parent && parent.parent.kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
}
}
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: (valueInMap: T, key: string) => void) {
// Remove all
map.forEach(onDeleteValue);
map.clear();
}
export interface MutateMapOptions<T, U> {
createNewValue(key: string, valueInNewMap: U): T;
onDeleteValue(existingValue: T, key: string): 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?(existingValue: T, valueInNewMap: U, key: string): 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>) {
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(existingValue, key);
}
// If present notify about existing values
else if (onExistingValue) {
onExistingValue(existingValue, valueInNewMap, key);
}
});
// Add new values that are not already present
newMap.forEach((valueInNewMap, key) => {
if (!map.has(key)) {
// New values
map.set(key, createNewValue(key, valueInNewMap));
}
});
}
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T): T {
while (true) {
const result = callback(directory);
if (result !== undefined) {
return result;
}
const parentPath = getDirectoryPath(directory);
if (parentPath === directory) {
return undefined;
}
directory = parentPath;
}
}
}
namespace ts {
+638
View File
@@ -0,0 +1,638 @@
/// <reference path="program.ts" />
/// <reference path="builder.ts" />
/// <reference path="resolutionCache.ts"/>
/* @internal */
namespace ts {
export type DiagnosticReporter = (diagnostic: Diagnostic) => void;
export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, 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: DirectoryStructureHost, 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: DirectoryStructureHost, 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: DirectoryStructureHost): 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: DirectoryStructureHost, 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: DirectoryStructureHost, program: Program, builder: Builder) {
// First get and report any syntactic errors.
let diagnostics = program.getSyntacticDiagnostics().slice();
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 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 hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed
const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop;
const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher;
const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher;
const watchDirectoryWorker = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher;
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system;
if (configFileName) {
watchFile(system, configFileName, scheduleProgramReload, writeLog);
}
const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory());
const realpath = system.realpath && ((path: string) => system.realpath(path));
const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost);
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
let newLine = getNewLineCharacter(compilerOptions, system);
const compilerHost: CompilerHost & ResolutionCacheHost = {
// Members for CompilerHost
getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile),
getSourceFileByPath: getVersionedSourceFileByPath,
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile: notImplemented,
getCurrentDirectory,
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine,
fileExists,
readFile: fileName => system.readFile(fileName),
trace: s => system.write(s + newLine),
directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName),
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
getDirectories: path => directoryStructureHost.getDirectories(path),
realpath,
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false),
onReleaseOldSourceFile,
// Members for ResolutionCacheHost
toPath,
getCompilationSettings: () => compilerOptions,
watchDirectoryOfFailedLookupLocation: watchDirectory,
watchTypeRootsDirectory: watchDirectory,
getCachedDirectoryStructureHost,
onInvalidatedResolution: scheduleProgramUpdate,
onChangedAutomaticTypeDirectiveNames: () => {
hasChangedAutomaticTypeDirectiveNames = true;
scheduleProgramUpdate();
},
writeLog
};
// Cache for the module resolution
const resolutionCache = createResolutionCache(compilerHost, configFileName ?
getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) :
getCurrentDirectory()
);
// 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`);
if (hasChangedCompilerOptions) {
newLine = getNewLineCharacter(compilerOptions, system);
}
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) {
return;
}
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
resolutionCache.clear();
}
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
hasChangedCompilerOptions = false;
beforeCompile(compilerOptions);
// Compile the program
resolutionCache.startCachingPerDirectoryResolution();
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
resolutionCache.finishCachingPerDirectoryResolution();
builder.updateProgram(program);
// Update watches
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath);
if (needsUpdateInTypeRootWatch) {
resolutionCache.updateTypeRootsWatch();
}
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 this 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(directoryStructureHost, program, builder);
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
}
function toPath(fileName: string) {
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
}
function fileExists(fileName: string) {
const path = toPath(fileName);
const hostSourceFileInfo = sourceFilesCache.get(path);
if (hostSourceFileInfo !== undefined) {
return !isString(hostSourceFileInfo);
}
return directoryStructureHost.fileExists(fileName);
}
function getDefaultLibLocation(): string {
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
}
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 = watchFilePath(system, fileName, onSourceFileChange, path, writeLog);
}
}
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 = watchFilePath(system, fileName, onSourceFileChange, path, writeLog);
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 = system.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.removeResolutionsOfFile(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 = directoryStructureHost as CachedDirectoryStructureHost;
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 onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
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) {
(directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, path, eventKind);
}
}
function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
return watchDirectoryWorker(system, directory, cb, flags, writeLog);
}
function watchMissingFilePath(missingFilePath: Path) {
return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog);
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
updateCachedSystemWithFile(fileName, missingFilePath, eventKind);
if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
missingFilesMap.get(missingFilePath).close();
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
);
}
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
return watchDirectory(
directory,
fileOrDirectory => {
Debug.assert(!!configFileName);
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existance changed, update the sourceFiles cache
(directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
removeSourceFile(fileOrDirectoryPath);
// If the the added or created file or directory is not supported file name, ignore the file
// But when watched directory is added/removed, we need to reload the file list
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return;
}
// Reload is pending, do the reload
if (!needsReload) {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
rootFileNames = result.fileNames;
// Schedule Update the program
scheduleProgramUpdate();
}
},
flags
);
}
function computeHash(data: string) {
return system.createHash ? system.createHash(data) : data;
}
}
}
+136
View File
@@ -0,0 +1,136 @@
/// <reference path="core.ts" />
namespace ts {
/**
* 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,
) {
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: closeFileWatcher
}
);
}
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
) {
mutateMap(
existingWatchedForWildcards,
wildcardDirectories,
{
// Create new watch and recursive info
createNewValue: createWildcardDirectoryWatcher,
// Close existing watch thats not needed any more
onDeleteValue: closeFileWatcherOf,
// 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(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) {
// Watcher needs to be updated if the recursive flags dont match
if (existingWatcher.flags === flags) {
return;
}
existingWatcher.watcher.close();
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
}
}
}
/* @internal */
namespace ts {
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
return host.watchFile(file, cb);
}
export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb);
}
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher {
return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path));
}
export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path);
}
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
return host.watchDirectory(directory, cb, recursive);
}
export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags);
}
type WatchCallback<T, U> = (fileName: string, cbOptional1?: T, optional?: U) => void;
type AddWatch<T, U> = (host: System, file: string, cb: WatchCallback<T, U>, optional?: U) => FileWatcher;
function createWatcherWithLogging<T, U>(addWatch: AddWatch<T, U>, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
const info = `PathInfo: ${file}`;
log(`${watcherCaption}Added: ${info}`);
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
const start = timestamp();
cb(fileName, cbOptional1, optional);
const elapsed = timestamp() - start;
log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`);
}, optional);
return {
close: () => {
log(`${watcherCaption}Close: ${info}`);
watcher.close();
}
};
}
export function closeFileWatcher(watcher: FileWatcher) {
watcher.close();
}
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
objWithWatcher.watcher.close();
}
}
+5 -5
View File
@@ -390,7 +390,7 @@ namespace FourSlash {
// Entry points from fourslash.ts
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) {
this.openFile(marker.fileName);
}
@@ -399,7 +399,7 @@ namespace FourSlash {
if (marker.position === -1 || marker.position > content.length) {
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.goToPosition(marker.position);
}
@@ -1027,7 +1027,7 @@ namespace FourSlash {
public verifyNoReferences(markerNameOrRange?: string | Range) {
if (markerNameOrRange) {
if (typeof markerNameOrRange === "string") {
if (ts.isString(markerNameOrRange)) {
this.goToMarker(markerNameOrRange);
}
else {
@@ -1522,7 +1522,7 @@ Actual: ${stringify(fullActual)}`);
resultString += "Diagnostics:" + Harness.IO.newLine();
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram());
for (const diagnostic of diagnostics) {
if (typeof diagnostic.messageText !== "string") {
if (!ts.isString(diagnostic.messageText)) {
let chainedMessage = <ts.DiagnosticMessageChain>diagnostic.messageText;
let indentation = " ";
while (chainedMessage) {
@@ -2976,7 +2976,7 @@ Actual: ${stringify(fullActual)}`);
result = this.testData.files[index];
}
}
else if (typeof indexOrName === "string") {
else if (ts.isString(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
+5 -1
View File
@@ -227,7 +227,7 @@ namespace Utils {
return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " ");
function getKindName(k: number | string): string {
if (typeof k === "string") {
if (ts.isString(k)) {
return k;
}
@@ -757,6 +757,10 @@ namespace Harness {
}
}
export function mockHash(s: string): string {
return `hash-${s}`;
}
const environment = Utils.getExecutionEnvironment();
switch (environment) {
case Utils.ExecutionEnvironment.Node:
-4
View File
@@ -855,8 +855,4 @@ namespace Harness.LanguageService {
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."); }
}
export function mockHash(s: string): string {
return `hash-${s}`;
}
}
+1 -1
View File
@@ -254,7 +254,7 @@ class ProjectRunner extends RunnerBase {
if (option) {
const optType = option.type;
let value = <any>testCase[name];
if (typeof optType !== "string") {
if (!ts.isString(optType)) {
const key = value.toLowerCase();
const optTypeValue = optType.get(key);
if (optTypeValue) {
+6 -3
View File
@@ -1,4 +1,4 @@
{
{
"extends": "../tsconfig-base",
"compilerOptions": {
"removeComments": false,
@@ -45,6 +45,7 @@
"../compiler/declarationEmitter.ts",
"../compiler/emitter.ts",
"../compiler/program.ts",
"../compiler/builder.ts",
"../compiler/commandLineParser.ts",
"../compiler/diagnosticInformationMap.generated.ts",
"../services/breakpoints.ts",
@@ -96,6 +97,7 @@
"./parallel/host.ts",
"./parallel/worker.ts",
"runner.ts",
"virtualFileSystemWithWatch.ts",
"../server/protocol.ts",
"../server/session.ts",
"../server/client.ts",
@@ -112,7 +114,6 @@
"./unittests/convertToBase64.ts",
"./unittests/transpile.ts",
"./unittests/reuseProgramStructure.ts",
"./unittests/cachingInServerLSHost.ts",
"./unittests/moduleResolution.ts",
"./unittests/tsconfigParsing.ts",
"./unittests/commandLineParsing.ts",
@@ -120,6 +121,7 @@
"./unittests/convertCompilerOptionsFromJson.ts",
"./unittests/convertTypeAcquisitionFromJson.ts",
"./unittests/tsserverProjectSystem.ts",
"./unittests/tscWatchMode.ts",
"./unittests/matchFiles.ts",
"./unittests/initializeTSConfig.ts",
"./unittests/compileOnSave.ts",
@@ -135,6 +137,7 @@
"./unittests/textChanges.ts",
"./unittests/telemetry.ts",
"./unittests/languageService.ts",
"./unittests/programMissingFiles.ts"
"./unittests/programMissingFiles.ts",
"./unittests/publicApi.ts"
]
}
@@ -1,202 +0,0 @@
/// <reference path="..\harness.ts" />
namespace ts {
interface File {
name: string;
content: string;
}
function createDefaultServerHost(fileMap: Map<File>): server.ServerHost {
const existingDirectories = createMap<boolean>();
forEachKey(fileMap, name => {
let dir = getDirectoryPath(name);
let previous: string;
do {
existingDirectories.set(dir, true);
previous = dir;
dir = getDirectoryPath(dir);
} while (dir !== previous);
});
return {
args: <string[]>[],
newLine: "\r\n",
useCaseSensitiveFileNames: false,
write: noop,
readFile: path => {
const file = fileMap.get(path);
return file && file.content;
},
writeFile: notImplemented,
resolvePath: notImplemented,
fileExists: path => fileMap.has(path),
directoryExists: path => existingDirectories.get(path) || false,
createDirectory: noop,
getExecutingFilePath: () => "",
getCurrentDirectory: () => "",
getDirectories: () => [],
getEnvironmentVariable: () => "",
readDirectory: notImplemented,
exit: noop,
watchFile: () => ({
close: noop
}),
watchDirectory: () => ({
close: noop
}),
setTimeout,
clearTimeout,
setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0),
clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout,
createHash: Harness.LanguageService.mockHash,
};
}
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
const svcOpts: server.ProjectServiceOptions = {
host: serverHost,
logger: projectSystem.nullLogger,
cancellationToken: { isCancellationRequested: () => false },
useSingleInferredProject: false,
useInferredProjectPerProjectRoot: false,
typingsInstaller: undefined
};
const projectService = new server.ProjectService(svcOpts);
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true });
return {
project,
rootScriptInfo
};
}
describe("Caching in LSHost", () => {
it("works using legacy resolution logic", () => {
const root: File = {
name: "c:/d/f0.ts",
content: `import {x} from "f1"`
};
const imported: File = {
name: "c:/f1.ts",
content: `foo()`
};
const serverHost = createDefaultServerHost(createMapFromTemplate({ [root.name]: root, [imported.name]: imported }));
const { project, rootScriptInfo } = createProject(root.name, serverHost);
// ensure that imported file was found
let diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.equal(diags.length, 1);
const originalFileExists = serverHost.fileExists;
{
// patch fileExists to make sure that disk is not touched
serverHost.fileExists = notImplemented;
const newContent = `import {x} from "f1"
var x: string = 1;`;
rootScriptInfo.editContent(0, root.content.length, newContent);
// trigger synchronization to make sure that import will be fetched from the cache
diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
// ensure file has correct number of errors after edit
assert.equal(diags.length, 1);
}
{
let fileExistsIsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f2"`;
rootScriptInfo.editContent(0, root.content.length, newContent);
try {
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(false, `should not find file '${imported.name}'`);
}
catch (e) {
assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0);
}
assert.isTrue(fileExistsIsCalled);
}
{
let fileExistsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f1"`;
rootScriptInfo.editContent(0, root.content.length, newContent);
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
// setting compiler options discards module resolution cache
fileExistsCalled = false;
const compilerOptions = ts.cloneCompilerOptions(project.getCompilerOptions());
compilerOptions.target = ts.ScriptTarget.ES5;
project.setCompilerOptions(compilerOptions);
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
}
});
it("loads missing files from disk", () => {
const root: File = {
name: `c:/foo.ts`,
content: `import {x} from "bar"`
};
const imported: File = {
name: `c:/bar.d.ts`,
content: `export var y = 1`
};
const fileMap = createMapFromTemplate({ [root.name]: root });
const serverHost = createDefaultServerHost(fileMap);
const originalFileExists = serverHost.fileExists;
let fileExistsCalledForBar = false;
serverHost.fileExists = fileName => {
if (fileName === "lib.d.ts") {
return false;
}
if (!fileExistsCalledForBar) {
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
}
return originalFileExists.call(serverHost, fileName);
};
const { project, rootScriptInfo } = createProject(root.name, serverHost);
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
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");
fileMap.set(imported.name, imported);
fileExistsCalledForBar = false;
rootScriptInfo.editContent(0, root.content.length, `import {y} from "bar"`);
diags = project.getLanguageService().getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
assert.isTrue(diags.length === 0, "The import should succeed once the imported file appears on disk.");
});
});
}
+1 -5
View File
@@ -198,7 +198,6 @@ namespace ts.projectSystem {
file1Consumer1.content = `let y = 10;`;
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer1.path, FileWatcherEventKind.Changed);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
@@ -215,7 +214,6 @@ namespace ts.projectSystem {
session.executeCommand(changeModuleFile1ShapeRequest1);
// Delete file1Consumer2
host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer2.path, FileWatcherEventKind.Deleted);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
});
@@ -232,7 +230,6 @@ namespace ts.projectSystem {
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
};
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]);
host.triggerDirectoryWatcherCallback(ts.getDirectoryPath(file1Consumer3.path), file1Consumer3.path);
host.runQueuedTimeoutCallbacks();
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]);
@@ -465,7 +462,6 @@ namespace ts.projectSystem {
openFilesForSession([referenceFile1], session);
host.reloadFS([referenceFile1, configFile]);
host.triggerFileWatcherCallback(moduleFile1.path, FileWatcherEventKind.Deleted);
const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
@@ -593,4 +589,4 @@ namespace ts.projectSystem {
assert.isTrue(outFileContent.indexOf(file3.content) === -1);
});
});
}
}
@@ -104,7 +104,7 @@ namespace ts {
"/dev/tests/scenarios/first.json": "",
"/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 caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));
+138 -17
View File
@@ -40,7 +40,7 @@ namespace ts {
}
}`);
testExtractConstant("extractConstant_ClassInsertionPosition",
testExtractConstant("extractConstant_ClassInsertionPosition1",
`class C {
a = 1;
b = 2;
@@ -51,6 +51,28 @@ namespace ts {
}
}`);
testExtractConstant("extractConstant_ClassInsertionPosition2",
`class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = [#|1|];
}
}`);
testExtractConstant("extractConstant_ClassInsertionPosition3",
`class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
let x = [#|1|];
}
}`);
testExtractConstant("extractConstant_Parameters",
`function F() {
let w = 1;
@@ -62,26 +84,125 @@ namespace ts {
let x = [#|t + 1|];
}`);
// TODO (acasey): handle repeated substitution
// testExtractConstant("extractConstant_RepeatedSubstitution",
// `namespace X {
// export const j = 10;
// export const y = [#|j * j|];
// }`);
testExtractConstantFailed("extractConstant_BlockScopes_Dependencies",
`for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
let x = [#|i + 1|];
}
testExtractConstant("extractConstant_RepeatedSubstitution",
`namespace X {
export const j = 10;
export const y = [#|j * j|];
}`);
testExtractConstant("extractConstant_VariableList_const",
`const a = 1, b = [#|a + 1|];`);
// NOTE: this test isn't normative - it just documents our sub-optimal behavior.
testExtractConstant("extractConstant_VariableList_let",
`let a = 1, b = [#|a + 1|];`);
// NOTE: this test isn't normative - it just documents our sub-optimal behavior.
testExtractConstant("extractConstant_VariableList_MultipleLines",
`const /*About A*/a = 1,
/*About B*/b = [#|a + 1|];`);
testExtractConstant("extractConstant_BlockScopeMismatch", `
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const x = [#|i + 1|];
}
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition1", `
const i = 0;
for (let j = 0; j < 10; j++) {
const x = [#|i + 1|];
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition2", `
const i = 0;
function F() {
for (let j = 0; j < 10; j++) {
const x = [#|i + 1|];
}
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition3", `
for (let j = 0; j < 10; j++) {
const x = [#|2 + 1|];
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition4", `
function F() {
for (let j = 0; j < 10; j++) {
const x = [#|2 + 1|];
}
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition5", `
function F0() {
function F1() {
function F2(x = [#|2 + 1|]) {
}
}
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition6", `
class C {
x = [#|2 + 1|];
}
`);
testExtractConstant("extractConstant_StatementInsertionPosition7", `
const i = 0;
class C {
M() {
for (let j = 0; j < 10; j++) {
x = [#|i + 1|];
}
}
}
`);
testExtractConstant("extractConstant_TripleSlash", `
/// <reference path="path.js"/>
const x = [#|2 + 1|];
`);
testExtractConstant("extractConstant_PinnedComment", `
/*! Copyright */
const x = [#|2 + 1|];
`);
testExtractConstant("extractConstant_Directive", `
"strict";
const x = [#|2 + 1|];
`);
testExtractConstant("extractConstant_MultipleHeaders", `
/*! Copyright */
/// <reference path="path.js"/>
"strict";
const x = [#|2 + 1|];
`);
testExtractConstant("extractConstant_PinnedCommentAndDocComment", `
/*! Copyright */
/* About x */
const x = [#|2 + 1|];
`);
});
function testExtractConstant(caption: string, text: string) {
testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant);
}
function testExtractConstantFailed(caption: string, text: string) {
testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant);
}
}
+5 -6
View File
@@ -355,12 +355,11 @@ function parsePrimaryExpression(): any {
[#|function G() { }|]
}`);
// TODO (acasey): handle repeated substitution
// testExtractFunction("extractFunction_RepeatedSubstitution",
// `namespace X {
// export const j = 10;
// export const y = [#|j * j|];
// }`);
testExtractFunction("extractFunction_RepeatedSubstitution",
`namespace X {
export const j = 10;
export const y = [#|j * j|];
}`);
});
function testExtractFunction(caption: string, text: string) {
+1 -1
View File
@@ -113,7 +113,7 @@ namespace ts {
if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
assert.equal(Extension.Js, extension);
assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file");
return;
}
+22 -14
View File
@@ -1,6 +1,18 @@
/// <reference path="..\harness.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", () => {
const options: CompilerOptions = {
@@ -40,34 +52,31 @@ namespace ts {
it("handles no missing root files", () => {
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, []);
verifyMissingFilePaths(missing, []);
});
it("handles missing root file", () => {
const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts" as Path]); // Absolute path
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
});
it("handles multiple missing root files", () => {
const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().slice().sort();
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
const missing = program.getMissingFilePaths();
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles a mix of present and missing root files", () => {
const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().slice().sort();
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
const missing = program.getMissingFilePaths();
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles repeatedly specified root files", () => {
const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts" as Path]);
verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]);
});
it("normalizes file paths", () => {
@@ -81,9 +90,8 @@ namespace ts {
it("handles missing triple slash references", () => {
const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths().slice().sort();
assert.isDefined(missing);
assert.deepEqual(missing, [
const missing = program.getMissingFilePaths();
verifyMissingFilePaths(missing, [
// From absolute reference
"d:/imaginary/nonexistent1.ts",
@@ -100,4 +108,4 @@ namespace ts {
]);
});
});
}
}
+56 -45
View File
@@ -20,19 +20,34 @@ namespace ts.projectSystem {
}
}
function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) {
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => {
assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
});
}
}
it("external project - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
path: "/a/b/applib.ts",
content: ""
};
// only file1 exists - expect error
const host = createServerHost([file1]);
const projectService = createProjectService(host);
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
const projectFileName = "/a/b/test.csproj";
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName }
};
{
projectService.openExternalProject({
@@ -41,35 +56,25 @@ namespace ts.projectSystem {
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], ["File '/a/b/lib.ts' not found."]);
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
// only file1 exists - expect error
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
}
// only file2 exists - expect error
host.reloadFS([file2]);
host.reloadFS([file2, libFile]);
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], ["File '/a/b/app.ts' not found."]);
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
}
// both files exist - expect no errors
host.reloadFS([file1, file2]);
host.reloadFS([file1, file2, libFile]);
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], []);
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
checkDiagnosticsWithLinePos(diags, []);
}
});
@@ -79,25 +84,33 @@ namespace ts.projectSystem {
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
path: "/a/b/applib.ts",
content: ""
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const host = createServerHost([file1, config]);
const projectService = createProjectService(host);
const host = createServerHost([file1, config, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName: project.getProjectName() }
};
let diags = session.executeCommand(compilerOptionsRequest).response;
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
projectService.openClientFile(file1.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]);
host.reloadFS([file1, file2, config, libFile]);
host.reloadFS([file1, file2, config]);
projectService.openClientFile(file1.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectErrors(projectService.synchronizeProjectList([])[0], []);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
diags = session.executeCommand(compilerOptionsRequest).response;
checkDiagnosticsWithLinePos(diags, []);
});
it("configured projects - diagnostics for corrupted config 1", () => {
@@ -126,7 +139,7 @@ namespace ts.projectSystem {
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
@@ -135,13 +148,12 @@ namespace ts.projectSystem {
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
host.triggerFileWatcherCallback(correctConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
});
@@ -172,18 +184,17 @@ namespace ts.projectSystem {
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
host.triggerFileWatcherCallback(corruptedConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
+10
View File
@@ -0,0 +1,10 @@
/// <reference path="../harness.ts" />
describe("Public APIs", () => {
it("for the language service and compiler should be acknowledged when they change", () => {
Harness.Baseline.runBaseline("api/typescript.d.ts", () => Harness.IO.readFile("built/local/typescript.d.ts"));
});
it("for the language server should be acknowledged when they change", () => {
Harness.Baseline.runBaseline("api/tsserverlibrary.d.ts", () => Harness.IO.readFile("built/local/tsserverlibrary.d.ts"));
});
});
+169 -1
View File
@@ -346,7 +346,7 @@ namespace ts {
const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts);
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", () => {
@@ -869,4 +869,172 @@ namespace ts {
createProgram([], {});
});
});
import TestSystem = ts.TestFSWithWatch.TestServerHost;
type FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
import createTestSystem = ts.TestFSWithWatch.createWatchedSystem;
import libFile = ts.TestFSWithWatch.libFile;
describe("isProgramUptoDate should return true when there is no change in compiler options and", () => {
function verifyProgramIsUptoDate(
program: Program,
newRootFileNames: string[],
newOptions: CompilerOptions
) {
const actual = isProgramUptoDate(
program, newRootFileNames, newOptions,
path => program.getSourceFileByPath(path).version, /*fileExists*/ returnFalse,
/*hasInvalidatedResolution*/ returnFalse,
/*hasChangedAutomaticTypeDirectiveNames*/ false
);
assert.isTrue(actual);
}
function duplicate(options: CompilerOptions): CompilerOptions;
function duplicate(fileNames: string[]): string[];
function duplicate(filesOrOptions: CompilerOptions | string[]) {
return JSON.parse(JSON.stringify(filesOrOptions));
}
function createWatchingSystemHost(host: TestSystem) {
return ts.createWatchingSystemHost(/*pretty*/ undefined, host);
}
function verifyProgramWithoutConfigFile(watchingSystemHost: WatchingSystemHost, rootFiles: string[], options: CompilerOptions) {
const program = createWatchModeWithoutConfigFile(rootFiles, options, watchingSystemHost)();
verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options));
}
function getConfigParseResult(watchingSystemHost: WatchingSystemHost, configFileName: string) {
return parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic);
}
function verifyProgramWithConfigFile(watchingSystemHost: WatchingSystemHost, configFile: string) {
const result = getConfigParseResult(watchingSystemHost, configFile);
const program = createWatchModeWithConfigFile(result, {}, watchingSystemHost)();
const { fileNames, options } = getConfigParseResult(watchingSystemHost, configFile);
verifyProgramIsUptoDate(program, fileNames, options);
}
function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) {
const watchingSystemHost = createWatchingSystemHost(createTestSystem(files));
verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options);
verifyProgramWithConfigFile(watchingSystemHost, configFile);
}
it("has empty options", () => {
const file1: FileOrFolder = {
path: "/a/b/file1.ts",
content: "let x = 1"
};
const file2: FileOrFolder = {
path: "/a/b/file2.ts",
content: "let y = 1"
};
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: "{}"
};
verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path);
});
it("has lib specified in the options", () => {
const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] };
const app: FileOrFolder = {
path: "/src/app.ts",
content: "var x: Promise<string>;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
const es5Lib: FileOrFolder = {
path: "/compiler/lib.es5.d.ts",
content: "declare const eval: any"
};
const es2015Promise: FileOrFolder = {
path: "/compiler/lib.es2015.promise.d.ts",
content: "declare class Promise<T> {}"
};
verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path);
});
it("has paths specified in the options", () => {
const compilerOptions: CompilerOptions = {
baseUrl: ".",
paths: {
"*": [
"packages/mail/data/*",
"packages/styles/*",
"*"
]
}
};
const app: FileOrFolder = {
path: "/src/packages/framework/app.ts",
content: 'import classc from "module1/lib/file1";\
import classD from "module3/file3";\
let x = new classc();\
let y = new classD();'
};
const module1: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/file1.ts",
content: 'import classc from "module2/file2";export default classc;',
};
const module2: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
};
const module3: FileOrFolder = {
path: "/src/packages/styles/module3/file3.ts",
content: "class classD { method() { return 10; } }\nexport default classD;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path);
});
it("has include paths specified in tsconfig file", () => {
const compilerOptions: CompilerOptions = {
baseUrl: ".",
paths: {
"*": [
"packages/mail/data/*",
"packages/styles/*",
"*"
]
}
};
const app: FileOrFolder = {
path: "/src/packages/framework/app.ts",
content: 'import classc from "module1/lib/file1";\
import classD from "module3/file3";\
let x = new classc();\
let y = new classD();'
};
const module1: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/file1.ts",
content: 'import classc from "module2/file2";export default classc;',
};
const module2: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
};
const module3: FileOrFolder = {
path: "/src/packages/styles/module3/file3.ts",
content: "class classD { method() { return 10; } }\nexport default classD;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] })
};
const watchingSystemHost = createWatchingSystemHost(createTestSystem([app, module1, module2, module3, libFile, configFile]));
verifyProgramWithConfigFile(watchingSystemHost, configFile.path);
});
});
}
+1 -1
View File
@@ -25,7 +25,7 @@ namespace ts.server {
clearTimeout: noop,
setImmediate: () => 0,
clearImmediate: noop,
createHash: Harness.LanguageService.mockHash,
createHash: Harness.mockHash,
};
class TestSession extends Session {
+18 -12
View File
@@ -11,18 +11,24 @@ namespace ts.projectSystem {
});
it("only sends an event once", () => {
const file = makeFile("/a.ts");
const tsconfig = makeFile("/tsconfig.json", {});
const file = makeFile("/a/a.ts");
const file2 = makeFile("/b.ts");
const tsconfig = makeFile("/a/tsconfig.json", {});
const et = new EventTracker([file, tsconfig]);
const et = new EventTracker([file, file2, tsconfig]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({});
et.assertProjectInfoTelemetryEvent({}, tsconfig.path);
et.service.closeClientFile(file.path);
checkNumberOfProjects(et.service, { configuredProjects: 0 });
checkNumberOfProjects(et.service, { configuredProjects: 1 });
et.service.openClientFile(file2.path);
checkNumberOfProjects(et.service, { inferredProjects: 1 });
assert.equal(et.getEvents().length, 0);
et.service.openClientFile(file.path);
checkNumberOfProjects(et.service, { configuredProjects: 1 });
checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 });
assert.equal(et.getEvents().length, 0);
});
@@ -53,7 +59,7 @@ namespace ts.projectSystem {
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
et.assertProjectInfoTelemetryEvent({
projectId: Harness.LanguageService.mockHash("/hunter2/foo.csproj"),
projectId: Harness.mockHash("/hunter2/foo.csproj"),
compilerOptions: { strict: true },
compileOnSave: true,
// These properties can't be present for an external project, so they are undefined instead of false.
@@ -195,7 +201,7 @@ namespace ts.projectSystem {
const et = new EventTracker([jsconfig, file]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
projectId: Harness.mockHash("/jsconfig.json"),
fileStats: fileStats({ js: 1 }),
compilerOptions: autoJsCompilerOptions,
typeAcquisition: {
@@ -215,7 +221,7 @@ namespace ts.projectSystem {
et.service.openClientFile(file.path);
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true);
et.assertProjectInfoTelemetryEvent({
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
projectId: Harness.mockHash("/jsconfig.json"),
fileStats: fileStats({ js: 1 }),
compilerOptions: autoJsCompilerOptions,
configFileName: "jsconfig.json",
@@ -249,9 +255,9 @@ namespace ts.projectSystem {
return events;
}
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>): void {
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile?: string): void {
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
projectId: Harness.LanguageService.mockHash("/tsconfig.json"),
projectId: Harness.mockHash(configFile || "/tsconfig.json"),
fileStats: fileStats({ ts: 1 }),
compilerOptions: {},
extends: false,
@@ -282,7 +288,7 @@ namespace ts.projectSystem {
}
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 {
+6 -6
View File
@@ -21,7 +21,7 @@ namespace ts.textStorage {
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path));
ts1.useScriptVersionCache();
ts1.useScriptVersionCache_TestOnly();
ts2.useText();
const lineMap = computeLineStarts(f.content);
@@ -55,16 +55,16 @@ namespace ts.textStorage {
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
ts1.getSnapshot();
assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 1");
assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1");
ts1.edit(0, 5, " ");
assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 1");
assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 1");
ts1.useText();
assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 2");
assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2");
ts1.getLineInfo(0);
assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 2");
assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2");
});
});
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+21 -12
View File
@@ -38,7 +38,7 @@ namespace ts.projectSystem {
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[] | string, typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
self.addPostExecAction(installedTypings, success => {
for (const file of typingFiles) {
host.createFileOrFolder(file, /*createParentDirectory*/ true);
host.ensureFileOrFolder(file);
}
cb(success);
});
@@ -92,7 +92,7 @@ namespace ts.projectSystem {
const service = createProjectService(host, { typingsInstaller: installer });
service.openClientFile(f1.path);
service.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(service.configuredProjects[0], [f1.path, f2.path, config.path]);
checkProjectActualFiles(configuredProjectAt(service, 0), [f1.path, f2.path, config.path]);
installer.installAll(0);
});
});
@@ -144,12 +144,13 @@ namespace ts.projectSystem {
projectService.openClientFile(file1.path);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = projectService.configuredProjects[0];
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [file1.path, tsconfig.path]);
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]);
});
@@ -349,6 +350,8 @@ namespace ts.projectSystem {
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { externalProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]);
});
@@ -470,6 +473,8 @@ namespace ts.projectSystem {
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { externalProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]);
});
@@ -548,7 +553,7 @@ namespace ts.projectSystem {
for (const f of typingFiles) {
assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`);
}
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]);
});
@@ -651,7 +656,7 @@ namespace ts.projectSystem {
assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests");
installer.executePendingCommands();
host.checkTimeoutQueueLengthAndRun(3); // for 2 projects and 1 refreshing inferred project
checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]);
});
@@ -699,12 +704,13 @@ namespace ts.projectSystem {
projectService.openClientFile(app.path);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = projectService.configuredProjects[0];
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [app.path, jsconfig.path]);
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
});
@@ -745,13 +751,14 @@ namespace ts.projectSystem {
projectService.openClientFile(app.path);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = projectService.configuredProjects[0];
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [app.path, jsconfig.path]);
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
});
@@ -792,12 +799,13 @@ namespace ts.projectSystem {
projectService.openClientFile(app.path);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = projectService.configuredProjects[0];
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [app.path, jsconfig.path]);
installer.installAll(/*expectedCount*/ 1);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]);
});
@@ -836,10 +844,10 @@ namespace ts.projectSystem {
installer.checkPendingCommands(/*expectedCount*/ 0);
host.reloadFS([f, fixedPackageJson]);
host.triggerFileWatcherCallback(fixedPackageJson.path, FileWatcherEventKind.Changed);
host.checkTimeoutQueueLengthAndRun(2); // To refresh the project and refresh inferred projects
// expected install request
installer.installAll(/*expectedCount*/ 1);
host.checkTimeoutQueueLengthAndRun(2);
service.checkNumberOfProjects({ inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]);
});
@@ -963,8 +971,7 @@ namespace ts.projectSystem {
}
};
session.executeCommand(changeRequest);
host.checkTimeoutQueueLength(1);
host.runQueuedTimeoutCallbacks();
host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects
const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
assert.equal(version1, version2, "set of unresolved imports should not change");
});
@@ -1171,6 +1178,7 @@ namespace ts.projectSystem {
installer.installAll(/*expectedCount*/ 1);
assert.isTrue(seenTelemetryEvent);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
});
@@ -1224,6 +1232,7 @@ namespace ts.projectSystem {
assert.isTrue(!!endEvent);
assert.isTrue(beginEvent.eventId === endEvent.eventId);
assert.isTrue(endEvent.installSuccess);
host.checkTimeoutQueueLengthAndRun(2);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
});
+593
View File
@@ -0,0 +1,593 @@
/// <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: ReadonlyArray<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: ReadonlyArray<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 getDiffInKeys(map: Map<any>, expectedKeys: ReadonlyArray<string>) {
if (map.size === expectedKeys.length) {
return "";
}
const notInActual: string[] = [];
const duplicates: string[] = [];
const seen = createMap<true>();
forEach(expectedKeys, expectedKey => {
if (seen.has(expectedKey)) {
duplicates.push(expectedKey);
return;
}
seen.set(expectedKey, true);
if (!map.has(expectedKey)) {
notInActual.push(expectedKey);
}
});
const inActualNotExpected: string[] = [];
map.forEach((_value, key) => {
if (!seen.has(key)) {
inActualNotExpected.push(key);
}
seen.set(key, true);
});
return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`;
}
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: ReadonlyArray<string>) {
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, 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: ReadonlyArray<string>, expectedFileNames: string[]) {
assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${expectedFileNames}, got ${actualFileNames}`);
for (const f of expectedFileNames) {
assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${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 ? " recursive" : ""}`, 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 interface TestFileWatcher {
cb: FileWatcherCallback;
fileName: string;
}
export interface TestDirectoryWatcher {
cb: DirectoryWatcherCallback;
directoryName: string;
}
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<TestDirectoryWatcher>();
readonly watchedDirectoriesRecursive = createMultiMap<TestDirectoryWatcher>();
readonly watchedFiles = createMultiMap<TestFileWatcher>();
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: ReadonlyArray<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: ReadonlyArray<FileOrFolder>) {
const mapNewLeaves = createMap<true>();
const isNewFs = this.fs.size === 0;
// always inject safelist file in the list of files
for (const fileOrDirectory of fileOrFolderList.concat(this.withSafeList ? safeList : [])) {
const path = this.toFullPath(fileOrDirectory.path);
mapNewLeaves.set(path, true);
// If its a change
const currentEntry = this.fs.get(path);
if (currentEntry) {
if (isFile(currentEntry)) {
if (isString(fileOrDirectory.content)) {
// Update file
if (currentEntry.content !== fileOrDirectory.content) {
currentEntry.content = fileOrDirectory.content;
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
}
}
else {
// TODO: Changing from file => folder
}
}
else {
// Folder
if (isString(fileOrDirectory.content)) {
// TODO: Changing from folder => file
}
else {
// Folder update: Nothing to do.
}
}
}
else {
this.ensureFileOrFolder(fileOrDirectory);
}
}
if (!isNewFs) {
this.fs.forEach((fileOrDirectory, 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(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path));
}
}
});
}
}
ensureFileOrFolder(fileOrDirectory: FileOrFolder) {
if (isString(fileOrDirectory.content)) {
const file = this.toFile(fileOrDirectory);
Debug.assert(!this.fs.get(file.path));
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
this.addFileOrFolderInFolder(baseFolder, file);
}
else {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.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, fileOrDirectory: File | Folder) {
folder.entries.push(fileOrDirectory);
this.fs.set(fileOrDirectory.path, fileOrDirectory);
if (isFile(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) {
const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) {
Debug.assert(!!baseFolder);
filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory);
}
this.fs.delete(fileOrDirectory.path);
if (isFile(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
}
else {
Debug.assert(fileOrDirectory.entries.length === 0);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.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(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
}
if (basePath !== fileOrDirectory.path) {
if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) {
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
}
else {
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath);
}
}
}
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) {
const callbacks = this.watchedFiles.get(this.toPath(fileFullPath));
invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => 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 => this.directoryCallback(cb, relativePath));
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
}
private directoryCallback({ cb, directoryName }: TestDirectoryWatcher, relativePath: string) {
cb(combinePaths(directoryName, relativePath));
}
/**
* 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 => this.directoryCallback(cb, relativePath));
const basePath = getDirectoryPath(fullPath);
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
this.invokeRecursiveDirectoryWatcher(basePath, fileName);
}
}
private toFile(fileOrDirectory: FileOrFolder): File {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
return {
path: this.toPath(fullPath),
content: fileOrDirectory.content,
fullPath,
fileSize: fileOrDirectory.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, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {
const path = this.toFullPath(directoryName);
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
const callback: TestDirectoryWatcher = {
cb,
directoryName
};
map.add(path, callback);
return {
close: () => map.remove(path, callback)
};
}
createHash(s: string): string {
return Harness.mockHash(s);
}
watchFile(fileName: string, cb: FileWatcherCallback) {
const path = this.toFullPath(fileName);
const callback: TestFileWatcher = { fileName, cb };
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 -21
View File
@@ -4233,11 +4233,7 @@ interface HTMLBodyElement extends HTMLElement {
onafterprint: (this: HTMLBodyElement, ev: Event) => any;
onbeforeprint: (this: HTMLBodyElement, ev: Event) => any;
onbeforeunload: (this: HTMLBodyElement, ev: BeforeUnloadEvent) => any;
onblur: (this: HTMLBodyElement, ev: FocusEvent) => any;
onerror: (this: HTMLBodyElement, ev: ErrorEvent) => any;
onfocus: (this: HTMLBodyElement, ev: FocusEvent) => any;
onhashchange: (this: HTMLBodyElement, ev: HashChangeEvent) => any;
onload: (this: HTMLBodyElement, ev: Event) => any;
onmessage: (this: HTMLBodyElement, ev: MessageEvent) => any;
onoffline: (this: HTMLBodyElement, ev: Event) => any;
ononline: (this: HTMLBodyElement, ev: Event) => any;
@@ -4246,7 +4242,6 @@ interface HTMLBodyElement extends HTMLElement {
onpageshow: (this: HTMLBodyElement, ev: PageTransitionEvent) => any;
onpopstate: (this: HTMLBodyElement, ev: PopStateEvent) => any;
onresize: (this: HTMLBodyElement, ev: UIEvent) => any;
onscroll: (this: HTMLBodyElement, ev: UIEvent) => any;
onstorage: (this: HTMLBodyElement, ev: StorageEvent) => any;
onunload: (this: HTMLBodyElement, ev: Event) => any;
text: any;
@@ -4901,10 +4896,6 @@ interface HTMLFrameElement extends HTMLElement, GetSVGDocument {
* Sets or retrieves whether the user can resize the frame.
*/
noResize: boolean;
/**
* Raised when the object has been completely received from the server.
*/
onload: (this: HTMLFrameElement, ev: Event) => any;
/**
* Sets or retrieves whether the frame can be scrolled.
*/
@@ -4970,17 +4961,10 @@ interface HTMLFrameSetElement extends HTMLElement {
onafterprint: (this: HTMLFrameSetElement, ev: Event) => any;
onbeforeprint: (this: HTMLFrameSetElement, ev: Event) => any;
onbeforeunload: (this: HTMLFrameSetElement, ev: BeforeUnloadEvent) => any;
/**
* Fires when the object loses the input focus.
*/
onblur: (this: HTMLFrameSetElement, ev: FocusEvent) => any;
onerror: (this: HTMLFrameSetElement, ev: ErrorEvent) => any;
/**
* Fires when the object receives focus.
*/
onfocus: (this: HTMLFrameSetElement, ev: FocusEvent) => any;
onhashchange: (this: HTMLFrameSetElement, ev: HashChangeEvent) => any;
onload: (this: HTMLFrameSetElement, ev: Event) => any;
onmessage: (this: HTMLFrameSetElement, ev: MessageEvent) => any;
onoffline: (this: HTMLFrameSetElement, ev: Event) => any;
ononline: (this: HTMLFrameSetElement, ev: Event) => any;
@@ -4989,7 +4973,6 @@ interface HTMLFrameSetElement extends HTMLElement {
onpageshow: (this: HTMLFrameSetElement, ev: PageTransitionEvent) => any;
onpopstate: (this: HTMLFrameSetElement, ev: PopStateEvent) => any;
onresize: (this: HTMLFrameSetElement, ev: UIEvent) => any;
onscroll: (this: HTMLFrameSetElement, ev: UIEvent) => any;
onstorage: (this: HTMLFrameSetElement, ev: StorageEvent) => any;
onunload: (this: HTMLFrameSetElement, ev: Event) => any;
/**
@@ -5125,10 +5108,7 @@ interface HTMLIFrameElement extends HTMLElement, GetSVGDocument {
* Sets or retrieves whether the user can resize the frame.
*/
noResize: boolean;
/**
* Raised when the object has been completely received from the server.
*/
onload: (this: HTMLIFrameElement, ev: Event) => any;
readonly sandbox: DOMSettableTokenList;
/**
* Sets or retrieves whether the frame can be scrolled.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-359
View File
@@ -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 && !scriptInfo.isDynamic;
}
/**
* 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.isDynamic ? [] : [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.isDynamic ? [] : [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);
}
}
}
+2 -3
View File
@@ -342,7 +342,7 @@ namespace ts.server {
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
let category: 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];
}
}
@@ -573,7 +573,7 @@ namespace ts.server {
getEditsForRefactor(
fileName: string,
formatOptions: FormatCodeSettings,
_formatOptions: FormatCodeSettings,
positionOrRange: number | TextRange,
refactorName: string,
actionName: string): RefactorEditInfo {
@@ -581,7 +581,6 @@ namespace ts.server {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs;
args.refactor = refactorName;
args.action = actionName;
args.formatOptions = formatOptions;
const request = this.processRequest<protocol.GetEditsForRefactorRequest>(CommandNames.GetEditsForRefactor, args);
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
File diff suppressed because it is too large Load Diff
-249
View File
@@ -1,249 +0,0 @@
/// <reference path="..\services\services.ts" />
/// <reference path="utilities.ts" />
/// <reference path="scriptInfo.ts" />
namespace ts.server {
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
private compilationSettings: CompilerOptions;
private readonly resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
private readonly resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
private readonly getCanonicalFileName: (fileName: string) => string;
private filesWithChangedSetOfUnresolvedImports: Path[];
private resolveModuleName: typeof resolveModuleName;
readonly trace: (s: string) => void;
readonly realpath?: (path: string) => string;
constructor(private readonly host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) {
this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds);
this.getCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
if (host.trace) {
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) {
this.realpath = path => this.host.realpath(path);
}
}
dispose() {
this.project = undefined;
this.resolveModuleName = undefined;
}
public startRecordingFilesWithChangedResolutions() {
this.filesWithChangedSetOfUnresolvedImports = [];
}
public finishRecordingFilesWithChangedResolutions() {
const collected = this.filesWithChangedSetOfUnresolvedImports;
this.filesWithChangedSetOfUnresolvedImports = undefined;
return collected;
}
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, 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, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = createMap<T>();
const resolvedModules: R[] = [];
const compilerOptions = this.getCompilationSettings();
const lastDeletedFileName = this.project.projectService.lastDeletedFile && this.project.projectService.lastDeletedFile.fileName;
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) {
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) {
return false;
}
const result = getResult(resolution);
if (result) {
return getResultFileName(result) !== lastDeletedFileName;
}
// 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() {
return this.host.newLine;
}
getProjectVersion() {
return this.project.getProjectVersion();
}
getCompilationSettings() {
return this.compilationSettings;
}
useCaseSensitiveFileNames() {
return this.host.useCaseSensitiveFileNames;
}
getCancellationToken() {
return this.cancellationToken;
}
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective,
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false);
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] {
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName,
m => m.resolvedModule, r => r.resolvedFileName, /*logChanges*/ true);
}
getDefaultLibFileName() {
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath()));
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings));
}
getScriptSnapshot(filename: string): IScriptSnapshot {
const scriptInfo = this.project.getScriptInfoLSHost(filename);
if (scriptInfo) {
return scriptInfo.getSnapshot();
}
}
getScriptFileNames() {
return this.project.getRootFilesLSHost();
}
getTypeRootsVersion() {
return this.project.typesVersion;
}
getScriptKind(fileName: string) {
const info = this.project.getScriptInfoLSHost(fileName);
return info && info.scriptKind;
}
getScriptVersion(filename: string) {
const info = this.project.getScriptInfoLSHost(filename);
return info && info.getLatestVersion();
}
getCurrentDirectory(): string {
return this.host.getCurrentDirectory();
}
resolvePath(path: string): string {
return this.host.resolvePath(path);
}
fileExists(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName);
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
}
readFile(fileName: string): string | undefined {
return this.host.readFile(fileName);
}
directoryExists(path: string): boolean {
return this.host.directoryExists(path);
}
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
return this.host.readDirectory(path, extensions, exclude, include, depth);
}
getDirectories(path: string): string[] {
return this.host.getDirectories(path);
}
notifyFileRemoved(info: ScriptInfo) {
this.resolvedModuleNames.delete(info.path);
this.resolvedTypeReferenceDirectives.delete(info.path);
}
setCompilationSettings(opt: CompilerOptions) {
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
this.resolvedModuleNames.clear();
this.resolvedTypeReferenceDirectives.clear();
}
this.compilationSettings = opt;
}
}
}
+478 -299
View File
File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -494,7 +494,6 @@ namespace ts.server.protocol {
refactor: string;
/* The 'name' property from the refactoring action */
action: string;
formatOptions?: FormatCodeSettings,
};
@@ -2041,6 +2040,19 @@ namespace ts.server.protocol {
languageServiceEnabled: boolean;
}
export type ProjectsUpdatedInBackgroundEventName = "projectsUpdatedInBackground";
export interface ProjectsUpdatedInBackgroundEvent extends Event {
event: ProjectsUpdatedInBackgroundEventName;
body: ProjectsUpdatedInBackgroundEventBody;
}
export interface ProjectsUpdatedInBackgroundEventBody {
/**
* Current set of open files
*/
openFiles: string[];
}
/**
* Arguments for reload request.
*/
+155 -76
View File
@@ -4,13 +4,38 @@ namespace ts.server {
/* @internal */
export class TextStorage {
/**
* Generated only on demand (based on edits, or information requested)
* The property text is set to undefined when edits happen on the cache
*/
private svc: ScriptVersionCache | undefined;
private svcVersion = 0;
/**
* Stores the text when there are no changes to the script version cache
* The script version cache is generated on demand and text is still retained.
* Only on edits to the script version cache, the text will be set to undefined
*/
private text: string;
/**
* Line map for the text when there is no script version cache present
*/
private lineMap: number[];
private textVersion = 0;
/**
* True if the text is for the file thats open in the editor
*/
public isOpen: boolean;
/**
* True if the text present is the text from the file on the disk
*/
private ownFileText: boolean;
/**
* True when reloading contents of file from the disk is pending
*/
private pendingReloadFromDisk: boolean;
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
}
@@ -20,43 +45,75 @@ namespace ts.server {
: `Text-${this.textVersion}`;
}
public hasScriptVersionCache() {
public hasScriptVersionCache_TestOnly() {
return this.svc !== undefined;
}
public useScriptVersionCache(newText?: string) {
this.switchToScriptVersionCache(newText);
public useScriptVersionCache_TestOnly() {
this.switchToScriptVersionCache();
}
public useText(newText?: string) {
this.svc = undefined;
this.setText(newText);
this.text = newText;
this.lineMap = undefined;
this.textVersion++;
}
public edit(start: number, end: number, newText: string) {
this.switchToScriptVersionCache().edit(start, end - start, newText);
this.ownFileText = false;
this.text = undefined;
this.lineMap = undefined;
}
public reload(text: string) {
if (this.svc) {
this.svc.reload(text);
}
else {
this.setText(text);
/** returns true if text changed */
public reload(newText: string) {
Debug.assert(newText !== undefined);
// Reload always has fresh content
this.pendingReloadFromDisk = false;
// If text changed set the text
// This also ensures that if we had switched to version cache,
// we are switching back to text.
// The change to version cache will happen when needed
// Thus avoiding the computation if there are no changes
if (this.text !== newText) {
this.useText(newText);
// We cant guarantee new text is own file text
this.ownFileText = false;
return true;
}
}
public reloadFromFile(tempFileName?: string) {
if (this.svc || (tempFileName !== this.fileName)) {
this.reload(this.getFileText(tempFileName));
/** returns true if text changed */
public reloadFromDisk() {
let reloaded = false;
if (!this.pendingReloadFromDisk && !this.ownFileText) {
reloaded = this.reload(this.getFileText());
this.ownFileText = true;
}
else {
this.setText(undefined);
return reloaded;
}
public delayReloadFromFileIntoText() {
this.pendingReloadFromDisk = true;
}
/** returns true if text changed */
public reloadFromFile(tempFileName: string) {
let reloaded = false;
// Reload if different file or we dont know if we are working with own file text
if (tempFileName !== this.fileName || !this.ownFileText) {
reloaded = this.reload(this.getFileText(tempFileName));
this.ownFileText = !tempFileName || tempFileName === this.fileName;
}
return reloaded;
}
public getSnapshot(): IScriptSnapshot {
return this.svc
return this.useScriptVersionCacheIfValidOrOpen()
? this.svc.getSnapshot()
: ScriptSnapshot.fromString(this.getOrLoadText());
}
@@ -68,7 +125,7 @@ namespace ts.server {
* @param line 0 based index
*/
lineToTextSpan(line: number): TextSpan {
if (!this.svc) {
if (!this.useScriptVersionCacheIfValidOrOpen()) {
const lineMap = this.getLineMap();
const start = lineMap[line]; // -1 since line is 1-based
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
@@ -82,7 +139,7 @@ namespace ts.server {
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
if (!this.svc) {
if (!this.useScriptVersionCacheIfValidOrOpen()) {
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
}
@@ -91,7 +148,7 @@ namespace ts.server {
}
positionToLineOffset(position: number): protocol.Location {
if (!this.svc) {
if (!this.useScriptVersionCacheIfValidOrOpen()) {
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
return { line: line + 1, offset: character + 1 };
}
@@ -102,42 +159,43 @@ namespace ts.server {
return this.host.readFile(tempFileName || this.fileName) || "";
}
private ensureNoScriptVersionCache() {
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
}
private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
if (!this.svc) {
this.svc = ScriptVersionCache.fromString(newText !== undefined ? newText : this.getOrLoadText());
private switchToScriptVersionCache(): ScriptVersionCache {
if (!this.svc || this.pendingReloadFromDisk) {
this.svc = ScriptVersionCache.fromString(this.getOrLoadText());
this.svcVersion++;
this.text = undefined;
}
return this.svc;
}
private useScriptVersionCacheIfValidOrOpen(): ScriptVersionCache | undefined {
// If this is open script, use the cache
if (this.isOpen) {
return this.switchToScriptVersionCache();
}
// Else if the svc is uptodate with the text, we are good
return !this.pendingReloadFromDisk && this.svc;
}
private getOrLoadText() {
this.ensureNoScriptVersionCache();
if (this.text === undefined) {
this.setText(this.getFileText());
if (this.text === undefined || this.pendingReloadFromDisk) {
Debug.assert(!this.svc || this.pendingReloadFromDisk, "ScriptVersionCache should not be set when reloading from disk");
this.reload(this.getFileText());
this.ownFileText = true;
}
return this.text;
}
private getLineMap() {
this.ensureNoScriptVersionCache();
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
}
private setText(newText: string) {
this.ensureNoScriptVersionCache();
if (newText === undefined || this.text !== newText) {
this.text = newText;
this.lineMap = undefined;
this.textVersion++;
}
}
}
/*@internal*/
export function isDynamicFileName(fileName: NormalizedPath) {
return getBaseFileName(fileName)[0] === "^";
}
export class ScriptInfo {
/**
@@ -145,23 +203,24 @@ namespace ts.server {
*/
readonly containingProjects: Project[] = [];
private formatCodeSettings: FormatCodeSettings;
readonly path: Path;
private fileWatcher: FileWatcher;
/* @internal */
fileWatcher: FileWatcher;
private textStorage: TextStorage;
private isOpen: boolean;
/*@internal*/
readonly isDynamic: boolean;
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
readonly scriptKind: ScriptKind,
public hasMixedContent = false,
public isDynamic = false) {
public readonly hasMixedContent: boolean,
readonly path: Path) {
this.isDynamic = isDynamicFileName(fileName);
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
this.textStorage = new TextStorage(host, fileName);
if (hasMixedContent || isDynamic) {
if (hasMixedContent || this.isDynamic) {
this.textStorage.reload("");
}
this.scriptKind = scriptKind
@@ -169,20 +228,34 @@ namespace ts.server {
: getScriptKindFromFileName(fileName);
}
/*@internal*/
public isDynamicOrHasMixedContent() {
return this.hasMixedContent || this.isDynamic;
}
public isScriptOpen() {
return this.isOpen;
return this.textStorage.isOpen;
}
public open(newText: string) {
this.isOpen = true;
this.textStorage.useScriptVersionCache(newText);
this.markContainingProjectsAsDirty();
this.textStorage.isOpen = true;
if (newText !== undefined &&
this.textStorage.reload(newText)) {
// reload new contents only if the existing contents changed
this.markContainingProjectsAsDirty();
}
}
public close() {
this.isOpen = false;
this.textStorage.useText(this.hasMixedContent || this.isDynamic ? "" : undefined);
this.markContainingProjectsAsDirty();
this.textStorage.isOpen = false;
if (this.isDynamicOrHasMixedContent()) {
if (this.textStorage.reload("")) {
this.markContainingProjectsAsDirty();
}
}
else if (this.textStorage.reloadFromDisk()) {
this.markContainingProjectsAsDirty();
}
}
public getSnapshot() {
@@ -237,8 +310,17 @@ namespace ts.server {
detachAllProjects() {
for (const p of this.containingProjects) {
if (p.projectKind === ProjectKind.Configured) {
(p.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted);
}
const isInfoRoot = p.isRoot(this);
// detach is unnecessary since we'll clean the list of containing projects anyways
p.removeFile(this, /*detachFromProjects*/ false);
p.removeFile(this, /*fileExists*/ false, /*detachFromProjects*/ false);
// If the info was for the external or configured project's root,
// add missing file as the root
if (isInfoRoot && p.projectKind !== ProjectKind.Inferred) {
p.addMissingFileRoot(this.fileName);
}
}
clear(this.containingProjects);
}
@@ -281,42 +363,35 @@ namespace ts.server {
}
}
setWatcher(watcher: FileWatcher): void {
this.stopWatcher();
this.fileWatcher = watcher;
}
stopWatcher() {
if (this.fileWatcher) {
this.fileWatcher.close();
this.fileWatcher = undefined;
}
}
getLatestVersion() {
return this.textStorage.getVersion();
}
reload(script: string) {
this.textStorage.reload(script);
this.markContainingProjectsAsDirty();
}
saveTo(fileName: string) {
const snap = this.textStorage.getSnapshot();
this.host.writeFile(fileName, snap.getText(0, snap.getLength()));
}
/*@internal*/
delayReloadNonMixedContentFile() {
Debug.assert(!this.isDynamicOrHasMixedContent());
this.textStorage.delayReloadFromFileIntoText();
this.markContainingProjectsAsDirty();
}
reloadFromFile(tempFileName?: NormalizedPath) {
if (this.hasMixedContent || this.isDynamic) {
this.reload("");
if (this.isDynamicOrHasMixedContent()) {
this.textStorage.reload("");
this.markContainingProjectsAsDirty();
}
else {
this.textStorage.reloadFromFile(tempFileName);
this.markContainingProjectsAsDirty();
if (this.textStorage.reloadFromFile(tempFileName)) {
this.markContainingProjectsAsDirty();
}
}
}
/*@internal*/
getLineInfo(line: number): AbsolutePositionAndLineText {
return this.textStorage.getLineInfo(line);
}
@@ -332,6 +407,10 @@ namespace ts.server {
}
}
isOrphan() {
return this.containingProjects.length === 0;
}
/**
* @param line 1 based index
*/
+2 -19
View File
@@ -2,6 +2,7 @@
/// <reference path="..\services\services.ts" />
/// <reference path="session.ts" />
/*@internal*/
namespace ts.server {
const lineCollectionCapacity = 4;
@@ -285,24 +286,6 @@ namespace ts.server {
}
}
// reload whole script, leaving no change history behind reload
reload(script: string) {
this.currentVersion++;
this.changes = []; // history wiped out by reload
const snap = new LineIndexSnapshot(this.currentVersion, this, new LineIndex());
// delete all versions
for (let i = 0; i < this.versions.length; i++) {
this.versions[i] = undefined;
}
this.versions[this.currentVersionToIndex()] = snap;
const lm = LineIndex.linesFromText(script);
snap.index.load(lm.lines);
this.minVersion = this.currentVersion;
}
getSnapshot(): IScriptSnapshot { return this._getSnapshot(); }
private _getSnapshot(): LineIndexSnapshot {
@@ -843,4 +826,4 @@ namespace ts.server {
return 1;
}
}
}
}
+9 -1
View File
@@ -613,7 +613,15 @@ namespace ts.server {
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
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 {
const oldTime = watchedFile.mtime.getTime();
+143 -144
View File
@@ -332,14 +332,18 @@ namespace ts.server {
private defaultEventHandler(event: ProjectServiceEvent) {
switch (event.eventName) {
case ContextEvent:
const { project, fileName } = event.data;
this.projectService.logger.info(`got context event, updating diagnostics for ${fileName}`);
this.errorCheck.startNew(next => this.updateErrorCheck(next, [{ fileName, project }], 100));
case ProjectsUpdatedInBackgroundEvent:
const { openFiles } = event.data;
this.projectsUpdatedInBackgroundEvent(openFiles);
break;
case ConfigFileDiagEvent:
const { triggerFile, configFileName, diagnostics } = event.data;
this.configFileDiagnosticEvent(triggerFile, configFileName, diagnostics);
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
this.event<protocol.ConfigFileDiagnosticEventBody>({
triggerFile,
configFile,
diagnostics: bakedDiags
}, "configFileDiag");
break;
case ProjectLanguageServiceStateEvent: {
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
@@ -360,6 +364,22 @@ namespace ts.server {
}
}
private projectsUpdatedInBackgroundEvent(openFiles: string[]): void {
this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`);
if (openFiles.length) {
const checkList = this.createCheckList(openFiles);
// 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.ProjectsUpdatedInBackgroundEventBody>({
openFiles
}, "projectsUpdatedInBackground");
}
}
public logError(err: Error, cmd: string) {
let msg = "Exception on executing command " + cmd;
if (err.message) {
@@ -381,21 +401,6 @@ namespace ts.server {
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) {
const ev: protocol.Event = {
seq: 0,
@@ -451,16 +456,6 @@ namespace ts.server {
}
}
private updateProjectStructure() {
const ms = 1500;
const seq = this.changeSeq;
this.host.setTimeout(() => {
if (this.changeSeq === seq) {
this.projectService.refreshInferredProjects();
}
}, ms);
}
private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], ms: number, requireOpen = true) {
const seq = this.changeSeq;
const followMs = Math.min(ms, 200);
@@ -472,12 +467,14 @@ namespace ts.server {
index++;
if (checkSpec.project.containsFile(checkSpec.fileName, requireOpen)) {
this.syntacticCheck(checkSpec.fileName, checkSpec.project);
next.immediate(() => {
this.semanticCheck(checkSpec.fileName, checkSpec.project);
if (checkList.length > index) {
next.delay(followMs, checkOne);
}
});
if (this.changeSeq === seq) {
next.immediate(() => {
this.semanticCheck(checkSpec.fileName, checkSpec.project);
if (checkList.length > index) {
next.delay(followMs, checkOne);
}
});
}
}
}
};
@@ -499,7 +496,7 @@ namespace ts.server {
private cleanup() {
this.cleanProjects("inferred projects", this.projectService.inferredProjects);
this.cleanProjects("configured projects", this.projectService.configuredProjects);
this.cleanProjects("configured projects", arrayFrom(this.projectService.configuredProjects.values()));
this.cleanProjects("external projects", this.projectService.externalProjects);
if (this.host.gc) {
this.logger.info(`host.gc()`);
@@ -596,8 +593,7 @@ namespace ts.server {
private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileSpan> | ReadonlyArray<DefinitionInfo> {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
const definitions = project.getLanguageService().getDefinitionAtPosition(file, position);
if (!definitions) {
@@ -621,8 +617,7 @@ namespace ts.server {
private getTypeDefinition(args: protocol.FileLocationRequestArgs): ReadonlyArray<protocol.FileSpan> {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
const definitions = project.getLanguageService().getTypeDefinitionAtPosition(file, position);
if (!definitions) {
@@ -641,7 +636,7 @@ namespace ts.server {
private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileSpan> | ReadonlyArray<ImplementationLocation> {
const { file, project } = this.getFileAndProject(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
const position = this.getPositionInFile(args, file);
const implementations = project.getLanguageService().getImplementationAtPosition(file, position);
if (!implementations) {
return emptyArray;
@@ -663,8 +658,7 @@ namespace ts.server {
private getOccurrences(args: protocol.FileLocationRequestArgs): ReadonlyArray<protocol.OccurrencesResponseItem> {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
const occurrences = project.getLanguageService().getOccurrencesAtPosition(file, position);
@@ -711,8 +705,7 @@ namespace ts.server {
private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.DocumentHighlightsItem> | ReadonlyArray<DocumentHighlights> {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
const documentHighlights = project.getLanguageService().getDocumentHighlights(file, position, args.filesToSearch);
if (!documentHighlights) {
@@ -753,7 +746,8 @@ namespace ts.server {
}
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean, excludeConfigFiles: boolean) {
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*refreshInferredProjects*/ true, /*errorOnMissingProject*/ true);
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName);
project.updateGraph();
const projectInfo = {
configFileName: project.getProjectName(),
languageServiceDisabled: !project.languageServiceEnabled,
@@ -764,8 +758,7 @@ namespace ts.server {
private getRenameInfo(args: protocol.FileLocationRequestArgs) {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
return project.getLanguageService().getRenameInfo(file, position);
}
@@ -802,8 +795,7 @@ namespace ts.server {
private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | ReadonlyArray<RenameLocation> {
const file = toNormalizedPath(args.file);
const info = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, info);
const position = this.getPositionInFile(args, file);
const projects = this.getProjects(args);
if (simplifiedResult) {
@@ -908,14 +900,13 @@ namespace ts.server {
const projects = this.getProjects(args);
const defaultProject = this.getDefaultProject(args);
const scriptInfo = defaultProject.getScriptInfoForNormalizedPath(file);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
if (simplifiedResult) {
const nameInfo = defaultProject.getLanguageService().getQuickInfoAtPosition(file, position);
if (!nameInfo) {
return undefined;
}
const displayString = displayPartsToString(nameInfo.displayParts);
const nameSpan = nameInfo.textSpan;
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
@@ -991,26 +982,38 @@ namespace ts.server {
return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
}
private getFileAndProject(args: protocol.FileRequestArgs, errorOnMissingProject = true) {
return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ true, errorOnMissingProject);
private getPositionInFile(args: protocol.FileLocationRequestArgs, file: NormalizedPath): number {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
return this.getPosition(args, scriptInfo);
}
private getFileAndProjectWithoutRefreshingInferredProjects(args: protocol.FileRequestArgs, errorOnMissingProject = true) {
return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ false, errorOnMissingProject);
private getFileAndProject(args: protocol.FileRequestArgs) {
return this.getFileAndProjectWorker(args.file, args.projectFileName);
}
private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) {
const file = toNormalizedPath(uncheckedFileName);
const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, refreshInferredProjects);
if (!project && errorOnMissingProject) {
private getFileAndLanguageServiceForSyntacticOperation(args: protocol.FileRequestArgs) {
// Since this is syntactic operation, there should always be project for the file
// we wouldnt have to ensure project but rather throw if we dont get project
const file = toNormalizedPath(args.file);
const project = this.getProject(args.projectFileName) || this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ false);
if (!project) {
return Errors.ThrowNoProject();
}
return {
file,
languageService: project.getLanguageService(/*ensureSynchronized*/ false)
};
}
private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string) {
const file = toNormalizedPath(uncheckedFileName);
const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ true);
return { file, project };
}
private getOutliningSpans(args: protocol.FileRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
return project.getLanguageService(/*ensureSynchronized*/ false).getOutliningSpans(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
return languageService.getOutliningSpans(file);
}
private getTodoComments(args: protocol.TodoCommentRequestArgs) {
@@ -1019,49 +1022,47 @@ namespace ts.server {
}
private getDocCommentTemplate(args: protocol.FileLocationRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const position = this.getPositionInFile(args, file);
return languageService.getDocCommentTemplateAtPosition(file, position);
}
private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const onlyMultiLine = args.onlyMultiLine;
const position = this.getPosition(args, scriptInfo);
return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine);
const position = this.getPositionInFile(args, file);
return languageService.getSpanOfEnclosingComment(file, position, onlyMultiLine);
}
private getIndentation(args: protocol.IndentationRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const position = this.getPositionInFile(args, file);
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
const indentation = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, options);
const indentation = languageService.getIndentationAtPosition(file, position, options);
return { position, indentation };
}
private getBreakpointStatement(args: protocol.FileLocationRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
return project.getLanguageService(/*ensureSynchronized*/ false).getBreakpointStatementAtPosition(file, position);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const position = this.getPositionInFile(args, file);
return languageService.getBreakpointStatementAtPosition(file, position);
}
private getNameOrDottedNameSpan(args: protocol.FileLocationRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
return project.getLanguageService(/*ensureSynchronized*/ false).getNameOrDottedNameSpan(file, position, position);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const position = this.getPositionInFile(args, file);
return languageService.getNameOrDottedNameSpan(file, position, position);
}
private isValidBraceCompletion(args: protocol.BraceCompletionRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
return project.getLanguageService(/*ensureSynchronized*/ false).isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0));
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const position = this.getPositionInFile(args, file);
return languageService.isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0));
}
private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const quickInfo = project.getLanguageService().getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo));
if (!quickInfo) {
return undefined;
@@ -1087,14 +1088,14 @@ namespace ts.server {
}
private getFormattingEditsForRange(args: protocol.FormatRequestArgs): protocol.CodeEdit[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const startPosition = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
// TODO: avoid duplicate code (with formatonkey)
const edits = project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, startPosition, endPosition,
const edits = languageService.getFormattingEditsForRange(file, startPosition, endPosition,
this.projectService.getFormatCodeOptions(file));
if (!edits) {
return undefined;
@@ -1104,29 +1105,29 @@ namespace ts.server {
}
private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, args.position, args.endPosition, options);
return languageService.getFormattingEditsForRange(file, args.position, args.endPosition, options);
}
private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForDocument(file, options);
return languageService.getFormattingEditsForDocument(file, options);
}
private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file);
return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, args.position, args.key, options);
return languageService.getFormattingEditsAfterKeystroke(file, args.position, args.key, options);
}
private getFormattingEditsAfterKeystroke(args: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const formatOptions = this.projectService.getFormatCodeOptions(file);
const edits = project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, position, args.key,
const edits = languageService.getFormattingEditsAfterKeystroke(file, position, args.key,
formatOptions);
// Check whether we should auto-indent. This will be when
// the position is on a line containing only whitespace.
@@ -1137,7 +1138,7 @@ namespace ts.server {
if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line);
if (lineText && lineText.search("\\S") < 0) {
const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions);
const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions);
let hasIndent = 0;
let i: number, len: number;
for (i = 0, len = lineText.length; i < len; i++) {
@@ -1178,8 +1179,7 @@ namespace ts.server {
private getCompletions(args: protocol.CompletionsRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CompletionEntry> | CompletionInfo | undefined {
const prefix = args.prefix || "";
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const completions = project.getLanguageService().getCompletionsAtPosition(file, position);
@@ -1199,21 +1199,20 @@ namespace ts.server {
private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): ReadonlyArray<protocol.CompletionEntryDetails> {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const position = this.getPositionInFile(args, file);
return mapDefined(args.entryNames, entryName =>
project.getLanguageService().getCompletionEntryDetails(file, position, entryName));
}
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
const info = this.projectService.getScriptInfo(args.file);
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file);
if (!info) {
return emptyArray;
}
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
// if specified a project, we only return affected file list in this project
const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
for (const project of projectsToSearch) {
@@ -1221,7 +1220,7 @@ namespace ts.server {
result.push({
projectFileName: project.getProjectName(),
fileNames: project.getCompileOnSaveAffectedFileList(info),
projectUsesOutFile: !!project.getCompilerOptions().outFile || !!project.getCompilerOptions().out
projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out
});
}
}
@@ -1237,12 +1236,12 @@ namespace ts.server {
return false;
}
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 {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const helpItems = project.getLanguageService().getSignatureHelpItems(file, position);
if (!helpItems) {
@@ -1267,42 +1266,43 @@ namespace ts.server {
}
}
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
const checkList = mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] {
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
const fileName = toNormalizedPath(uncheckedFileName);
const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true);
const project = defaultProject || this.projectService.getDefaultProjectForFile(fileName, /*ensureProject*/ false);
return project && { fileName, project };
});
}
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
const checkList = this.createCheckList(fileNames);
if (checkList.length > 0) {
this.updateErrorCheck(next, checkList, delay);
}
}
private change(args: protocol.ChangeRequestArgs) {
const { file, project } = this.getFileAndProject(args, /*errorOnMissingProject*/ false);
if (project) {
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const start = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
if (start >= 0) {
scriptInfo.editContent(start, end, args.insertString);
this.changeSeq++;
}
this.updateProjectStructure();
const scriptInfo = this.projectService.getScriptInfo(args.file);
Debug.assert(!!scriptInfo);
const start = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
if (start >= 0) {
this.changeSeq++;
this.projectService.applyChangesToFile(scriptInfo, [{
span: { start, length: end - start },
newText: args.insertString
}]);
}
}
private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
const file = toNormalizedPath(args.file);
const tempFileName = args.tmpfile && toNormalizedPath(args.tmpfile);
const project = this.projectService.getDefaultProjectForFile(file, /*refreshInferredProjects*/ true);
if (project) {
this.changeSeq++;
// make sure no changes happen before this one is finished
if (project.reloadScript(file, tempFileName)) {
this.output(undefined, CommandNames.Reload, reqSeq);
}
const project = this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ true);
this.changeSeq++;
// make sure no changes happen before this one is finished
if (project.reloadScript(file, tempFileName)) {
this.output(undefined, CommandNames.Reload, reqSeq);
}
}
@@ -1333,12 +1333,12 @@ namespace ts.server {
}
private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] {
const { file, project } = this.getFileAndProject(args);
const items = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationBarItems(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const items = languageService.getNavigationBarItems(file);
return !items
? undefined
: simplifiedResult
? this.decorateNavigationBarItems(items, project.getScriptInfoForNormalizedPath(file))
? this.decorateNavigationBarItems(items, this.projectService.getScriptInfoForNormalizedPath(file))
: items;
}
@@ -1360,12 +1360,12 @@ namespace ts.server {
}
private getNavigationTree(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationTree | NavigationTree {
const { file, project } = this.getFileAndProject(args);
const tree = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationTree(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const tree = languageService.getNavigationTree(file);
return !tree
? undefined
: simplifiedResult
? this.decorateNavigationTree(tree, project.getScriptInfoForNormalizedPath(file))
? this.decorateNavigationTree(tree, this.projectService.getScriptInfoForNormalizedPath(file))
: tree;
}
@@ -1475,20 +1475,20 @@ namespace ts.server {
}
private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
return project.getLanguageService().getApplicableRefactors(file, position || textRange);
}
private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
const result = project.getLanguageService().getEditsForRefactor(
file,
args.formatOptions ? convertFormatOptions(args.formatOptions) : this.projectService.getFormatCodeOptions(),
this.projectService.getFormatCodeOptions(file),
position || textRange,
args.refactor,
args.action
@@ -1522,7 +1522,7 @@ namespace ts.server {
if (args.errorCodes.length === 0) {
return undefined;
}
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
@@ -1589,12 +1589,11 @@ namespace ts.server {
}
private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const spans = project.getLanguageService(/*ensureSynchronized*/ false).getBraceMatchingAtPosition(file, position);
const spans = languageService.getBraceMatchingAtPosition(file, position);
return !spans
? undefined
: simplifiedResult
@@ -1620,7 +1619,7 @@ namespace ts.server {
const lowPriorityFiles: NormalizedPath[] = [];
const veryLowPriorityFiles: NormalizedPath[] = [];
const normalizedFileName = toNormalizedPath(fileName);
const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*refreshInferredProjects*/ true);
const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*ensureProject*/ true);
for (const fileNameInProject of fileNamesInProject) {
if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) {
highPriorityFiles.push(fileNameInProject);
@@ -1699,8 +1698,8 @@ namespace ts.server {
return this.requiredResponse(converted);
},
[CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => {
this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles);
this.changeSeq++;
this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles);
// TODO: report errors
return this.requiredResponse(/*response*/ true);
},
-1
View File
@@ -15,7 +15,6 @@
"utilities.ts",
"scriptVersionCache.ts",
"scriptInfo.ts",
"lsHost.ts",
"typingsCache.ts",
"project.ts",
"editorServices.ts",
-1
View File
@@ -16,7 +16,6 @@
},
"files": [
"editorServices.ts",
"lsHost.ts",
"project.ts",
"protocol.ts",
"scriptInfo.ts",
+3 -3
View File
@@ -89,12 +89,12 @@ namespace ts.server {
if (forceRefresh ||
!entry ||
typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) ||
compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions) ||
compilerOptionsChanged(project.getCompilationSettings(), entry.compilerOptions) ||
unresolvedImportsChanged(unresolvedImports, entry.unresolvedImports)) {
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
// instead it acts as a placeholder to prevent issuing multiple requests
this.perProjectCache.set(project.getProjectName(), {
compilerOptions: project.getCompilerOptions(),
compilerOptions: project.getCompilationSettings(),
typeAcquisition,
typings: result,
unresolvedImports,
@@ -125,4 +125,4 @@ namespace ts.server {
this.installer.onProjectClosed(project);
}
}
}
}
+19 -6
View File
@@ -50,7 +50,7 @@ namespace ts.server {
return {
projectName: project.getProjectName(),
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]),
compilerOptions: project.getCompilerOptions(),
compilerOptions: project.getCompilationSettings(),
typeAcquisition,
unresolvedImports,
projectRootPath: getProjectRootPath(project),
@@ -173,10 +173,15 @@ namespace ts.server {
export function createSortedArray<T>(): SortedArray<T> {
return [] as SortedArray<T>;
}
}
/* @internal */
namespace ts.server {
export class ThrottledOperations {
private pendingTimeouts: Map<any> = createMap<any>();
constructor(private readonly host: ServerHost) {
private readonly pendingTimeouts: Map<any> = createMap<any>();
private readonly logger?: Logger | undefined;
constructor(private readonly host: ServerHost, logger: Logger) {
this.logger = logger.hasLevel(LogLevel.verbose) && logger;
}
/**
@@ -193,10 +198,16 @@ namespace ts.server {
}
// schedule new operation, pass arguments
this.pendingTimeouts.set(operationId, this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb));
if (this.logger) {
this.logger.info(`Scheduled: ${operationId}${pendingTimeout ? ", Cancelled earlier one" : ""}`);
}
}
private static run(self: ThrottledOperations, operationId: string, cb: () => void) {
self.pendingTimeouts.delete(operationId);
if (self.logger) {
self.logger.info(`Running: ${operationId}`);
}
cb();
}
}
@@ -227,10 +238,12 @@ namespace ts.server {
}
}
}
}
/* @internal */
namespace ts.server {
export function getBaseConfigFileName(configFilePath: NormalizedPath): "tsconfig.json" | "jsconfig.json" | undefined {
const base = getBaseFileName(configFilePath);
return base === "tsconfig.json" || base === "jsconfig.json" ? base : undefined;
}
export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Comparer<T>): void {
if (array.length === 0) {
array.push(insert);
-22
View File
@@ -407,28 +407,6 @@ namespace ts.codefix {
moduleSpecifierWithoutQuotes
);
function getSourceFileImportLocation(node: SourceFile) {
// For a source file, it is possible there are detached comments we should not skip
const text = node.text;
let ranges = getLeadingCommentRanges(text, 0);
if (!ranges) return 0;
let position = 0;
// However we should still skip a pinned comment at the top
if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) {
position = ranges[0].end + 1;
ranges = ranges.slice(1);
}
// As well as any triple slash references
for (const range of ranges) {
if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(node.text, range.pos, range.end)) {
position = range.end + 1;
continue;
}
break;
}
return position;
}
function getSingleQuoteStyleFromExistingImports() {
const firstModuleSpecifier = forEach(sourceFile.statements, node => {
if (isImportDeclaration(node) || isExportDeclaration(node)) {
+1 -1
View File
@@ -207,7 +207,7 @@ namespace ts.JsTyping {
}
// depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar`
const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
const fileNames = host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`);
const packageNames: string[] = [];
for (const fileName of fileNames) {
+1 -1
View File
@@ -35,7 +35,7 @@ namespace ts {
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
return flatMapIter(refactors.values(), refactor =>
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? [] : refactor.getAvailableActions(context));
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context));
}
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
+154 -59
View File
@@ -43,7 +43,7 @@ namespace ts.refactor.extractSymbol {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.functionDescription]);
const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.functionDescription, extraction.functionScopeDescription]);
if (!usedFunctionNames.has(description)) {
usedFunctionNames.set(description, true);
functionActions.push({
@@ -58,7 +58,7 @@ namespace ts.refactor.extractSymbol {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.constantDescription]);
const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.constantDescription, extraction.constantScopeDescription]);
if (!usedConstantNames.has(description)) {
usedConstantNames.set(description, true);
constantActions.push({
@@ -523,8 +523,10 @@ namespace ts.refactor.extractSymbol {
interface PossibleExtraction {
readonly functionDescription: string;
readonly functionScopeDescription: string;
readonly functionErrors: ReadonlyArray<Diagnostic>;
readonly constantDescription: string;
readonly constantScopeDescription: string;
readonly constantErrors: ReadonlyArray<Diagnostic>;
}
/**
@@ -535,12 +537,23 @@ namespace ts.refactor.extractSymbol {
function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): ReadonlyArray<PossibleExtraction> | undefined {
const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context);
// Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547
const extractions = scopes.map((scope, i): PossibleExtraction => ({
functionDescription: getDescriptionForFunctionInScope(scope),
functionErrors: functionErrorsPerScope[i],
constantDescription: getDescriptionForConstantInScope(scope),
constantErrors: constantErrorsPerScope[i],
}));
const extractions = scopes.map((scope, i): PossibleExtraction => {
const scopeDescription = isFunctionLikeDeclaration(scope)
? getDescriptionForFunctionLikeDeclaration(scope)
: isClassLike(scope)
? getDescriptionForClassLikeDeclaration(scope)
: getDescriptionForModuleLikeDeclaration(scope);
return {
functionDescription: getDescriptionForFunctionInScope(scope),
functionErrors: functionErrorsPerScope[i],
functionScopeDescription: scopeDescription,
constantDescription: getDescriptionForConstantInScope(scope),
constantErrors: constantErrorsPerScope[i],
constantScopeDescription: (i === 0 && !isClassLike(scope))
? "enclosing scope" // Like "global scope" and "module scope", this is not localized.
: scopeDescription,
};
});
return extractions;
}
@@ -569,17 +582,15 @@ namespace ts.refactor.extractSymbol {
function getDescriptionForFunctionInScope(scope: Scope): string {
return isFunctionLikeDeclaration(scope)
? `inner function in ${getDescriptionForFunctionLikeDeclaration(scope)}`
? "inner function"
: isClassLike(scope)
? `method in ${getDescriptionForClassLikeDeclaration(scope)}`
: `function in ${getDescriptionForModuleLikeDeclaration(scope)}`;
? "method"
: "function";
}
function getDescriptionForConstantInScope(scope: Scope): string {
return isFunctionLikeDeclaration(scope)
? `constant in ${getDescriptionForFunctionLikeDeclaration(scope)}`
: isClassLike(scope)
? `readonly field in ${getDescriptionForClassLikeDeclaration(scope)}`
: `constant in ${getDescriptionForModuleLikeDeclaration(scope)}`;
return isClassLike(scope)
? "readonly field"
: "constant";
}
function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string {
switch (scope.kind) {
@@ -869,33 +880,61 @@ namespace ts.refactor.extractSymbol {
createIdentifier(localNameText));
// Declare
const minInsertionPos = node.end;
const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope);
const maxInsertionPos = node.pos;
const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope);
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter });
// Consume
changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter });
}
else {
const newVariable = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList(
[createVariableDeclaration(localNameText, variableType, initializer)],
NodeFlags.Const));
const newVariableDeclaration = createVariableDeclaration(localNameText, variableType, initializer);
// If the parent is an expression statement, replace the statement with the declaration
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariable], { nodeSeparator: context.newLineCharacter });
}
else {
// If the node is part of an initializer in a list of variable declarations, insert a new
// variable declaration into the list (in case it depends on earlier ones).
// CONSIDER: If the declaration list isn't const, we might want to split it into multiple
// lists so that the newly extracted one can be const.
const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope);
if (oldVariableDeclaration) {
// Declare
const minInsertionPos = node.end;
const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope);
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter });
// CONSIDER: could detect that each is on a separate line
changeTracker.insertNodeAt(context.file, oldVariableDeclaration.getStart(), newVariableDeclaration, { suffix: ", " });
// Consume
const localReference = createIdentifier(localNameText);
changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter });
changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
}
else if (node.parent.kind === SyntaxKind.ExpressionStatement) {
// If the parent is an expression statement, replace the statement with the declaration.
const newVariableStatement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariableStatement], { nodeSeparator: context.newLineCharacter });
}
else {
const newVariableStatement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
// Declare
const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope);
if (nodeToInsertBefore.pos === 0) {
// If we're at the beginning of the file, we need to take care not to insert before header comments
// (e.g. copyright, triple-slash references). Fortunately, this problem has already been solved
// for imports.
const insertionPos = getSourceFileImportLocation(file);
changeTracker.insertNodeAt(context.file, insertionPos, newVariableStatement, {
prefix: insertionPos === 0 ? undefined : context.newLineCharacter,
suffix: isLineBreak(file.text.charCodeAt(insertionPos)) ? context.newLineCharacter : context.newLineCharacter + context.newLineCharacter
});
}
else {
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter });
}
// Consume
const localReference = createIdentifier(localNameText);
changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
}
}
@@ -906,6 +945,22 @@ namespace ts.refactor.extractSymbol {
return { renameFilename, renameLocation, edits };
}
function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) {
let prevNode = undefined;
while (node !== undefined && node !== scope) {
if (isVariableDeclaration(node) &&
node.initializer === prevNode &&
isVariableDeclarationList(node.parent) &&
node.parent.declarations.length > 1) {
return node;
}
prevNode = node;
node = node.parent;
}
}
/**
* @return The index of the (only) reference to the extracted symbol. We want the cursor
* to be on the reference, rather than the declaration, because it's closer to where the
@@ -993,7 +1048,7 @@ namespace ts.refactor.extractSymbol {
}
}
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<() => Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
if (isBlock(body) && !writes && substitutions.size === 0) {
// already block, no writes to propagate back, no substitutions - can use node as is
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
@@ -1041,21 +1096,21 @@ namespace ts.refactor.extractSymbol {
const oldIgnoreReturns = ignoreReturns;
ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node);
const substitution = substitutions.get(getNodeId(node).toString());
const result = substitution || visitEachChild(node, visitor, nullTransformationContext);
const result = substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
ignoreReturns = oldIgnoreReturns;
return result;
}
}
}
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<Node>): Expression {
function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<() => Node>): Expression {
return substitutions.size
? visitor(initializer) as Expression
: initializer;
function visitor(node: Node): VisitResult<Node> {
const substitution = substitutions.get(getNodeId(node).toString());
return substitution || visitEachChild(node, visitor, nullTransformationContext);
return substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext);
}
}
@@ -1088,26 +1143,61 @@ namespace ts.refactor.extractSymbol {
child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child));
}
// TODO (acasey): need to dig into nested statements
// TODO (acasey): don't insert before pinned comments, directives, or triple-slash references
function getNodeToInsertConstantBefore(maxPos: number, scope: Scope): Node {
const children = getStatementsOrClassElements(scope);
Debug.assert(children.length > 0); // There must be at least one child, since we extracted from one.
function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): Node {
const members = scope.members;
Debug.assert(members.length > 0); // There must be at least one child, since we extracted from one.
const isClassLikeScope = isClassLike(scope);
let prevChild: Statement | ClassElement | undefined = undefined;
for (const child of children) {
if (child.pos >= maxPos) {
break;
let prevMember: ClassElement | undefined = undefined;
let allProperties = true;
for (const member of members) {
if (member.pos > maxPos) {
return prevMember || members[0];
}
prevChild = child;
if (isClassLikeScope && !isPropertyDeclaration(child)) {
break;
if (allProperties && !isPropertyDeclaration(member)) {
// If it is non-vacuously true that all preceding members are properties,
// insert before the current member (i.e. at the end of the list of properties).
if (prevMember !== undefined) {
return member;
}
allProperties = false;
}
prevMember = member;
}
Debug.assert(prevMember !== undefined); // If the loop didn't return, then it did set prevMember.
return prevMember;
}
function getNodeToInsertConstantBefore(node: Node, scope: Scope): Node {
Debug.assert(!isClassLike(scope));
let prevScope: Scope | undefined = undefined;
for (let curr = node; curr !== scope; curr = curr.parent) {
if (isScope(curr)) {
prevScope = curr;
}
}
Debug.assert(prevChild !== undefined);
return prevChild;
for (let curr = (prevScope || node).parent; ; curr = curr.parent) {
if (isBlockLike(curr)) {
let prevStatement = undefined;
for (const statement of curr.statements) {
if (statement.pos > node.pos) {
break;
}
prevStatement = statement;
}
// There must be at least one statement since we started in one.
Debug.assert(prevStatement !== undefined);
return prevStatement;
}
if (curr === scope) {
Debug.fail("Didn't encounter a block-like before encountering scope");
break;
}
}
}
function getPropertyAssignmentsForWrites(writes: ReadonlyArray<UsageEntry>): ShorthandPropertyAssignment[] {
@@ -1149,7 +1239,7 @@ namespace ts.refactor.extractSymbol {
interface ScopeUsages {
readonly usages: Map<UsageEntry>;
readonly typeParameterUsages: Map<TypeParameter>; // Key is type ID
readonly substitutions: Map<Node>;
readonly substitutions: Map<() => Node>;
}
interface ReadsAndWrites {
@@ -1168,7 +1258,7 @@ namespace ts.refactor.extractSymbol {
const allTypeParameterUsages = createMap<TypeParameter>(); // Key is type ID
const usagesPerScope: ScopeUsages[] = [];
const substitutionsPerScope: Map<Node>[] = [];
const substitutionsPerScope: Map<() => Node>[] = [];
const functionErrorsPerScope: Diagnostic[][] = [];
const constantErrorsPerScope: Diagnostic[][] = [];
const visibleDeclarationsInExtractedRange: Symbol[] = [];
@@ -1180,8 +1270,8 @@ namespace ts.refactor.extractSymbol {
// initialize results
for (const scope of scopes) {
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<Expression>() });
substitutionsPerScope.push(createMap<Expression>());
usagesPerScope.push({ usages: createMap<UsageEntry>(), typeParameterUsages: createMap<TypeParameter>(), substitutions: createMap<() => Expression>() });
substitutionsPerScope.push(createMap<() => Expression>());
functionErrorsPerScope.push(
isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration
@@ -1249,7 +1339,10 @@ namespace ts.refactor.extractSymbol {
for (let i = 0; i < scopes.length; i++) {
if (!isReadonlyArray(targetRange.range)) {
const scopeUsages = usagesPerScope[i];
if (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0) {
// Special case: in the innermost scope, all usages are available.
// (The computed value reflects the value at the top-level of the scope, but the
// local will actually be declared at the same level as the extracted expression).
if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) {
constantErrorsPerScope[i].push(createDiagnosticForNode(targetRange.range, Messages.CannotAccessVariablesFromNestedScopes));
}
}
@@ -1474,18 +1567,20 @@ namespace ts.refactor.extractSymbol {
}
}
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName {
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): () => (PropertyAccessExpression | EntityName) {
if (!symbol) {
return undefined;
}
if (symbol.getDeclarations().some(d => d.parent === scopeDecl)) {
return createIdentifier(symbol.name);
return () => createIdentifier(symbol.name);
}
const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode);
if (prefix === undefined) {
return undefined;
}
return isTypeNode ? createQualifiedName(<EntityName>prefix, createIdentifier(symbol.name)) : createPropertyAccess(<Expression>prefix, symbol.name);
return isTypeNode
? () => createQualifiedName(<EntityName>prefix(), createIdentifier(symbol.name))
: () => createPropertyAccess(<Expression>prefix(), symbol.name);
}
}
+55 -119
View File
@@ -820,18 +820,21 @@ namespace ts {
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
// at each language service public entry point, since we don't know when
// the set of scripts handled by the host changes.
class HostCache {
private fileNameToEntry: Map<HostFileInformation>;
private fileNameToEntry: Map<CachedHostFileInformation>;
private _compilationSettings: CompilerOptions;
private currentDirectory: string;
constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) {
// script id => script index
this.currentDirectory = host.getCurrentDirectory();
this.fileNameToEntry = createMap<HostFileInformation>();
this.fileNameToEntry = createMap<CachedHostFileInformation>();
// Initialize the list with the root file names
const rootFileNames = host.getScriptFileNames();
@@ -848,7 +851,7 @@ namespace ts {
}
private createEntry(fileName: string, path: Path) {
let entry: HostFileInformation;
let entry: CachedHostFileInformation;
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
if (scriptSnapshot) {
entry = {
@@ -858,44 +861,41 @@ namespace ts {
scriptKind: getScriptKind(fileName, this.host)
};
}
else {
entry = fileName;
}
this.fileNameToEntry.set(path, entry);
return entry;
}
public getEntryByPath(path: Path): HostFileInformation {
public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
return this.fileNameToEntry.get(path);
}
public containsEntryByPath(path: Path): boolean {
return this.fileNameToEntry.has(path);
public getHostFileInformation(path: Path): HostFileInformation | undefined {
const entry = this.fileNameToEntry.get(path);
return !isString(entry) ? entry : undefined;
}
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
return this.containsEntryByPath(path)
? this.getEntryByPath(path)
: this.createEntry(fileName, path);
const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
return isString(info) ? undefined : info;
}
public getRootFileNames(): string[] {
const fileNames: string[] = [];
this.fileNameToEntry.forEach(value => {
if (value) {
fileNames.push(value.hostFileName);
}
return arrayFrom(this.fileNameToEntry.values(), entry => {
return isString(entry) ? entry : entry.hostFileName;
});
return fileNames;
}
public getVersion(path: Path): string {
const file = this.getEntryByPath(path);
const file = this.getHostFileInformation(path);
return file && file.version;
}
public getScriptSnapshot(path: Path): IScriptSnapshot {
const file = this.getEntryByPath(path);
const file = this.getHostFileInformation(path);
return file && file.scriptSnapshot;
}
}
@@ -1105,7 +1105,7 @@ namespace ts {
if (host.getProjectVersion) {
const hostProjectVersion = host.getProjectVersion();
if (hostProjectVersion) {
if (lastProjectVersion === hostProjectVersion) {
if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames) {
return;
}
@@ -1122,9 +1122,12 @@ namespace ts {
// Get a fresh cache of the host information
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 (programUpToDate()) {
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, host.hasChangedAutomaticTypeDirectiveNames)) {
return;
}
@@ -1134,18 +1137,7 @@ namespace ts {
// the program points to old source files that have been invalidated because of
// incremental parsing.
const oldSettings = program && program.getCompilerOptions();
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 !== newSettings.disableSizeLimit ||
oldSettings.baseUrl !== newSettings.baseUrl ||
!equalOwnProperties(oldSettings.paths, newSettings.paths));
// Now create a new compiler
const compilerHost: CompilerHost = {
@@ -1158,19 +1150,13 @@ namespace ts {
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
writeFile: noop,
getCurrentDirectory: () => currentDirectory,
fileExists: (fileName): boolean => {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
return hostCache.containsEntryByPath(path) ?
!!hostCache.getEntryByPath(path) :
(host.fileExists && host.fileExists(fileName));
},
fileExists,
readFile(fileName) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
if (hostCache.containsEntryByPath(path)) {
const entry = hostCache.getEntryByPath(path);
return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
const entry = hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
}
return host.readFile && host.readFile(fileName);
},
@@ -1179,14 +1165,17 @@ namespace ts {
},
getDirectories: path => {
return host.getDirectories ? host.getDirectories(path) : [];
}
},
onReleaseOldSourceFile,
hasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames
};
if (host.trace) {
compilerHost.trace = message => host.trace(message);
}
if (host.resolveModuleNames) {
compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile);
compilerHost.resolveModuleNames = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames);
}
if (host.resolveTypeReferenceDirectives) {
compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => {
@@ -1195,36 +1184,37 @@ namespace ts {
}
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
const newProgram = createProgram(hostCache.getRootFileNames(), 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);
}
}
}
program = createProgram(rootFileNames, newSettings, compilerHost, program);
// 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
hostCache = undefined;
program = newProgram;
// Make sure all the nodes in the program are both bound, and have their parent
// pointers set property.
program.getTypeChecker();
return;
function getOrCreateSourceFile(fileName: string): SourceFile {
return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName));
function fileExists(fileName: string) {
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);
// 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
@@ -1237,7 +1227,7 @@ namespace ts {
// 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
// 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
const oldSourceFile = program && program.getSourceFileByPath(path);
if (oldSourceFile) {
@@ -1277,49 +1267,6 @@ namespace ts {
// 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);
}
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 {
@@ -1562,23 +1509,12 @@ namespace ts {
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();
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 emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
return {
outputFiles,
emitSkipped: emitOutput.emitSkipped
};
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
}
// Signature help
+1 -1
View File
@@ -527,7 +527,7 @@ namespace ts {
if (logPerformance) {
const end = timestamp();
logger.log(`${actionDescription} completed in ${end - start} msec`);
if (typeof result === "string") {
if (isString(result)) {
let str = result;
if (str.length > 128) {
str = str.substring(0, 128) + "...";
-4
View File
@@ -136,10 +136,6 @@ namespace ts.SymbolDisplay {
if (callExpressionLike) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword);
+1 -1
View File
@@ -139,7 +139,7 @@ namespace ts {
const value = options[opt.name];
// Value should be a key of opt.type
if (typeof value === "string") {
if (isString(value)) {
// If value is not a string, this will fail
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
}
+4 -12
View File
@@ -183,8 +183,10 @@ namespace ts {
* if implementation is omitted then language service will use built-in module resolution logic and get answers to
* host specific questions using 'getScriptSnapshot'.
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
hasInvalidatedResolution?: HasInvalidatedResolution;
hasChangedAutomaticTypeDirectiveNames?: boolean;
directoryExists?(directoryName: string): boolean;
/*
@@ -277,6 +279,7 @@ namespace ts {
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;
@@ -693,23 +696,12 @@ namespace ts {
autoCollapse: boolean;
}
export interface EmitOutput {
outputFiles: OutputFile[];
emitSkipped: boolean;
}
export const enum OutputFileType {
JavaScript,
SourceMap,
Declaration
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
export const enum EndOfLineState {
None,
InMultiLineCommentTrivia,
+22 -20
View File
@@ -999,26 +999,6 @@ namespace ts {
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) {
if (node.kind === SyntaxKind.ArrayLiteralExpression ||
node.kind === SyntaxKind.ObjectLiteralExpression) {
@@ -1332,4 +1312,26 @@ namespace ts {
export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) {
return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false);
}
export function getSourceFileImportLocation(node: SourceFile) {
// For a source file, it is possible there are detached comments we should not skip
const text = node.text;
let ranges = getLeadingCommentRanges(text, 0);
if (!ranges) return 0;
let position = 0;
// However we should still skip a pinned comment at the top
if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) {
position = ranges[0].end + 1;
ranges = ranges.slice(1);
}
// As well as any triple slash references
for (const range of ranges) {
if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(node.text, range.pos, range.end)) {
position = range.end + 1;
continue;
}
break;
}
return position;
}
}
+1
View File
@@ -12,6 +12,7 @@
"stripInternal": true,
"sourceMap": true,
"target": "es5",
"newLine": "lf",
"types": []
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,16 +1,18 @@
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS1110: Type expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS2304: Cannot find name 'super'.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,13): error TS1005: '=' expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,14): error TS1109: Expression expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(10,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(13,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(17,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS1110: Type expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS2304: Cannot find name 'super'.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,20): error TS1005: '=' expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,21): error TS1109: Expression expected.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(22,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(25,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(29,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
==== tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts (10 errors) ====
==== tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts (12 errors) ====
// error to use super calls outside a constructor
class Base {
@@ -20,9 +22,11 @@ tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassS
class Derived extends Base {
a: super();
~~~~~
!!! error TS1110: Type expected.
~~~~~
!!! error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
!!! error TS2304: Cannot find name 'super'.
~
!!! error TS1005: '=' expected.
~
!!! error TS1109: Expression expected.
b() {
super();
~~~~~
@@ -42,9 +46,11 @@ tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassS
static a: super();
~~~~~
!!! error TS1110: Type expected.
~~~~~
!!! error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
!!! error TS2304: Cannot find name 'super'.
~
!!! error TS1005: '=' expected.
~
!!! error TS1109: Expression expected.
static b() {
super();
~~~~~
@@ -52,7 +52,7 @@ var Derived = /** @class */ (function (_super) {
__extends(Derived, _super);
function Derived() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.a = _this = _super.call(this) || this;
_this.a = ();
return _this;
}
Derived.prototype.b = function () {
@@ -83,6 +83,6 @@ var Derived = /** @class */ (function (_super) {
enumerable: true,
configurable: true
});
Derived.a = _this = _super.call(this) || this;
Derived.a = ();
return Derived;
}(Base));
@@ -14,9 +14,9 @@ class Derived extends Base {
a: super();
>a : any
> : No type information available!
>super() : void
>super : any
>super : No type information available!
>() : any
> : any
b() {
>b : () => void
@@ -46,9 +46,9 @@ class Derived extends Base {
static a: super();
>a : any
> : No type information available!
>super() : void
>super : any
>super : No type information available!
>() : any
> : any
static b() {
>b : () => void
@@ -0,0 +1,18 @@
// ==ORIGINAL==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const x = i + 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const newLocal = i + 1;
const x = /*RENAME*/newLocal;
}
}
@@ -0,0 +1,18 @@
// ==ORIGINAL==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const x = i + 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const newLocal = i + 1;
const x = /*RENAME*/newLocal;
}
}
@@ -4,11 +4,11 @@ for (let i = 0; i < 10; i++) {
let x = 1;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
// ==SCOPE::Extract to constant in enclosing scope==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
@@ -4,11 +4,11 @@ for (let i = 0; i < 10; i++) {
let x = 1;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
// ==SCOPE::Extract to constant in enclosing scope==
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
@@ -8,7 +8,7 @@ class C {
let x = 1;
}
}
// ==SCOPE::Extract to constant in method 'M3==
// ==SCOPE::Extract to constant in enclosing scope==
class C {
a = 1;
b = 2;
@@ -8,7 +8,7 @@ class C {
let x = 1;
}
}
// ==SCOPE::Extract to constant in method 'M3==
// ==SCOPE::Extract to constant in enclosing scope==
class C {
a = 1;
b = 2;
@@ -0,0 +1,34 @@
// ==ORIGINAL==
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = /*RENAME*/newLocal;
}
}
@@ -0,0 +1,46 @@
// ==ORIGINAL==
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
// ==SCOPE::Extract to readonly field in class 'C'==
class C {
a = 1;
private readonly newProperty = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = this./*RENAME*/newProperty;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
class C {
a = 1;
M1() { }
b = 2;
M2() { }
M3() {
let x = /*RENAME*/newLocal;
}
}
@@ -0,0 +1,34 @@
// ==ORIGINAL==
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
let x = 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
let x = /*RENAME*/newLocal;
}
}
@@ -0,0 +1,46 @@
// ==ORIGINAL==
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
let x = 1;
}
}
// ==SCOPE::Extract to constant in enclosing scope==
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
const newLocal = 1;
let x = /*RENAME*/newLocal;
}
}
// ==SCOPE::Extract to readonly field in class 'C'==
class C {
M1() { }
a = 1;
b = 2;
M2() { }
private readonly newProperty = 1;
M3() {
let x = this./*RENAME*/newProperty;
}
}
// ==SCOPE::Extract to constant in global scope==
const newLocal = 1;
class C {
M1() { }
a = 1;
b = 2;
M2() { }
M3() {
let x = /*RENAME*/newLocal;
}
}

Some files were not shown because too many files have changed in this diff Show More