Merge branch 'master' into guard-name-in-getSuggestionForNonexistentSymbol

This commit is contained in:
Nathan Shively-Sanders 2017-11-01 10:34:19 -07:00
commit f4d4e89fa9
839 changed files with 138721 additions and 114679 deletions

1
.gitignore vendored
View File

@ -58,5 +58,4 @@ internal/
!tests/baselines/reference/project/nodeModules*/**/*
.idea
yarn.lock
package-lock.json
.parallelperf.*

View File

@ -276,4 +276,15 @@ Francois Wouts <f@codonut.com>
Jan Melcher <jan.melcher@aeb.com> Jan Melcher <mail@jan-melcher.de>
Matt Mitchell <mmitche@microsoft.com>
Maxwell Paul Brickner <mbrickn@users.noreply.github.com>
Tycho Grouwstra <tychogrouwstra@gmail.com>
Tycho Grouwstra <tychogrouwstra@gmail.com>
Adrian Leonhard <adrianleonhard@gmail.com>
Alex Chugaev <achugaev93@gmail.com>
Henry Mercer <henry.mercer@me.com>
Ivan Enderlin <ivan.enderlin@hoa-project.net>
Joe Calzaretta <jcalz@mit.edu>
Magnus Kulke <mkulke@gmail.com>
Stas Vilchik <stas.vilchik@sonarsource.com>
Taras Mankovski <tarasm@gmail.com>
Thomas den Hollander <ThomasdenH@users.noreply.github.com>
Vakhurin Sergey <igelbox@gmail.com>
Zeeshan Ahmed <ziishaned@gmail.com>

View File

@ -17,6 +17,7 @@ branches:
only:
- master
- release-2.5
- release-2.6
install:
- npm uninstall typescript --no-save

View File

@ -3,8 +3,10 @@ TypeScript is authored by:
* Abubaker Bashir
* Adam Freidin
* Adi Dahiya
* Adrian Leonhard
* Ahmad Farid
* Akshar Patel
* Alex Chugaev
* Alex Eagle
* Alexander Kuvaev
* Alexander Rusakov
@ -105,6 +107,7 @@ TypeScript is authored by:
* Halasi Tamás
* Harald Niesche
* Hendrik Liebau
* Henry Mercer
* Herrington Darkholme
* Homa Wong
* Iain Monro
@ -112,6 +115,7 @@ TypeScript is authored by:
* Ika
* Ingvar Stepanyan
* Isiah Meadows
* Ivan Enderlin
* Ivo Gabe de Wolff
* Iwata Hidetaka
* Jakub Młokosiewicz
@ -127,6 +131,7 @@ TypeScript is authored by:
* Jeffrey Morlan
* Jesse Schalken
* Jiri Tobisek
* Joe Calzaretta
* Joe Chung
* Joel Day
* Joey Wilson
@ -161,6 +166,7 @@ TypeScript is authored by:
* Lucien Greathouse
* Lukas Elmer
* Magnus Hiie
* Magnus Kulke
* Manish Giri
* Marin Marinov
* Marius Schulz
@ -232,13 +238,16 @@ TypeScript is authored by:
* Soo Jae Hwang
* Stan Thomas
* Stanislav Sysoev
* Stas Vilchik
* Steve Lucco
* Sudheesh Singanamalla
* Sébastien Arod
* @T18970237136
* @t_
* Taras Mankovski
* Tarik Ozket
* Tetsuharu Ohzeki
* Thomas den Hollander
* Thomas Loubiou
* Tien Hoanhtien
* Tim Lancina
@ -253,6 +262,7 @@ TypeScript is authored by:
* TruongSinh Tran-Nguyen
* Tycho Grouwstra
* Vadi Taslim
* Vakhurin Sergey
* Vidar Tonaas Fauske
* Viktor Zozulyak
* Vilic Vane
@ -263,5 +273,6 @@ TypeScript is authored by:
* York Yao
* @yortus
* Yuichi Nukiyama
* Zeeshan Ahmed
* Zev Spitz
* Zhengbo Li

View File

@ -64,7 +64,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), {
browser: process.env.browser || process.env.b || "IE",
timeout: process.env.timeout || 40000,
tests: process.env.test || process.env.tests || process.env.t,
light: process.env.light || false,
light: process.env.light === undefined || process.env.light !== "false",
reporter: process.env.reporter || process.env.r,
lint: process.env.lint || true,
files: process.env.f || process.env.file || process.env.files || "",
@ -87,7 +87,7 @@ function possiblyQuote(cmd: string) {
}
let useDebugMode = true;
let host = cmdLineOptions["host"];
let host = cmdLineOptions.host;
// Constants
const compilerDirectory = "src/compiler/";
@ -171,15 +171,32 @@ const librarySourceMap = [
// JavaScript + all host library
{ target: "lib.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(hostsLibrarySources) },
{ target: "lib.es6.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.es2016.full.d.ts", sources: ["header.d.ts", "es2016.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.es2017.full.d.ts", sources: ["header.d.ts", "es2017.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.esnext.full.d.ts", sources: ["header.d.ts", "esnext.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.es2016.full.d.ts", sources: ["header.d.ts", "es2016.d.ts"].concat(hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.es2017.full.d.ts", sources: ["header.d.ts", "es2017.d.ts"].concat(hostsLibrarySources, "dom.iterable.d.ts") },
{ target: "lib.esnext.full.d.ts", sources: ["header.d.ts", "esnext.d.ts"].concat(hostsLibrarySources, "dom.iterable.d.ts") },
].concat(es2015LibrarySourceMap, es2016LibrarySourceMap, es2017LibrarySourceMap, esnextLibrarySourceMap);
const libraryTargets = librarySourceMap.map(function(f) {
return path.join(builtLocalDirectory, f.target);
});
/**
* .lcg file is what localization team uses to know what messages to localize.
* The file is always generated in 'enu\diagnosticMessages.generated.json.lcg'
*/
const generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg");
/**
* The localization target produces the two following transformations:
* 1. 'src\loc\lcl\<locale>\diagnosticMessages.generated.json.lcl' => 'built\local\<locale>\diagnosticMessages.generated.json'
* convert localized resources into a .json file the compiler can understand
* 2. 'src\compiler\diagnosticMessages.generated.json' => 'built\local\ENU\diagnosticMessages.generated.json.lcg'
* generate the lcg file (source of messages to localize) from the diagnosticMessages.generated.json
*/
const localizationTargets = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-CN", "zh-TW"].map(function (f) {
return path.join(builtLocalDirectory, f, "diagnosticMessages.generated.json");
}).concat(generatedLCGFile);
for (const i in libraryTargets) {
const entry = librarySourceMap[i];
const target = libraryTargets[i];
@ -398,7 +415,6 @@ gulp.task(generateLocalizedDiagnosticMessagesJs, /*help*/ false, [], () => {
});
// 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);
@ -576,8 +592,7 @@ 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.
concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d, "diagnosticMessages.generated.json"); })).
concat(generatedLCGFile).
concat(localizationTargets).
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 +
@ -636,15 +651,15 @@ function restoreSavedNodeEnv() {
}
function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: (e?: any) => void) {
const lintFlag = cmdLineOptions["lint"];
const lintFlag = cmdLineOptions.lint;
cleanTestDirs((err) => {
if (err) { console.error(err); failWithStatus(err, 1); }
let testTimeout = cmdLineOptions["timeout"];
const debug = cmdLineOptions["debug"];
const inspect = cmdLineOptions["inspect"];
const tests = cmdLineOptions["tests"];
const light = cmdLineOptions["light"];
const stackTraceLimit = cmdLineOptions["stackTraceLimit"];
let testTimeout = cmdLineOptions.timeout;
const debug = cmdLineOptions.debug;
const inspect = cmdLineOptions.inspect;
const tests = cmdLineOptions.tests;
const light = cmdLineOptions.light;
const stackTraceLimit = cmdLineOptions.stackTraceLimit;
const testConfigFile = "test.config";
if (fs.existsSync(testConfigFile)) {
fs.unlinkSync(testConfigFile);
@ -660,7 +675,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
} while (fs.existsSync(taskConfigsFolder));
fs.mkdirSync(taskConfigsFolder);
workerCount = cmdLineOptions["workers"];
workerCount = cmdLineOptions.workers;
}
if (tests || light || taskConfigsFolder) {
@ -671,8 +686,8 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
testTimeout = 400000;
}
const colors = cmdLineOptions["colors"];
const reporter = cmdLineOptions["reporter"] || defaultReporter;
const colors = cmdLineOptions.colors;
const reporter = cmdLineOptions.reporter || defaultReporter;
// timeout normally isn"t necessary but Travis-CI has been timing out on compiler baselines occasionally
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
@ -860,7 +875,7 @@ function cleanTestDirs(done: (e?: any) => void) {
// used to pass data from jake command line directly to run.js
function writeTestConfigFile(tests: string, light: boolean, taskConfigsFolder?: string, workerCount?: number, stackTraceLimit?: string) {
const testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, light, workerCount, stackTraceLimit, taskConfigsFolder, noColor: !cmdLineOptions["colors"] });
const testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, light, workerCount, stackTraceLimit, taskConfigsFolder, noColor: !cmdLineOptions.colors });
console.log("Running tests with config: " + testConfigContents);
fs.writeFileSync("test.config", testConfigContents);
}
@ -870,8 +885,8 @@ gulp.task("runtests-browser", "Runs the tests using the built run.js file like '
cleanTestDirs((err) => {
if (err) { console.error(err); done(err); process.exit(1); }
host = "node";
const tests = cmdLineOptions["tests"];
const light = cmdLineOptions["light"];
const tests = cmdLineOptions.tests;
const light = cmdLineOptions.light;
const testConfigFile = "test.config";
if (fs.existsSync(testConfigFile)) {
fs.unlinkSync(testConfigFile);
@ -881,8 +896,8 @@ gulp.task("runtests-browser", "Runs the tests using the built run.js file like '
}
const args = [nodeServerOutFile];
if (cmdLineOptions["browser"]) {
args.push(cmdLineOptions["browser"]);
if (cmdLineOptions.browser) {
args.push(cmdLineOptions.browser);
}
if (tests) {
args.push(JSON.stringify(tests));
@ -892,13 +907,13 @@ gulp.task("runtests-browser", "Runs the tests using the built run.js file like '
});
gulp.task("generate-code-coverage", "Generates code coverage data via istanbul", ["tests"], (done) => {
const testTimeout = cmdLineOptions["timeout"];
const testTimeout = cmdLineOptions.timeout;
exec("istanbul", ["cover", "node_modules/mocha/bin/_mocha", "--", "-R", "min", "-t", testTimeout.toString(), run], done, done);
});
function getDiffTool() {
const program = process.env["DIFF"];
const program = process.env.DIFF;
if (!program) {
console.error("Add the 'DIFF' environment variable to the path of the program you want to use.");
process.exit(1);
@ -1019,7 +1034,7 @@ gulp.task(instrumenterJsPath, /*help*/ false, [servicesFile], () => {
});
gulp.task("tsc-instrumented", "Builds an instrumented tsc.js - run with --test=[testname]", ["local", loggedIOJsPath, instrumenterJsPath, servicesFile], (done) => {
const test = cmdLineOptions["tests"] || "iocapture";
const test = cmdLineOptions.tests || "iocapture";
exec(host, [instrumenterJsPath, "record", test, builtLocalCompiler], done, done);
});
@ -1088,7 +1103,7 @@ function spawnLintWorker(files: {path: string}[], callback: (failures: number) =
gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", ["build-rules"], () => {
if (fold.isTravis()) console.log(fold.start("lint"));
const fileMatcher = cmdLineOptions["files"];
const fileMatcher = cmdLineOptions.files;
const files = fileMatcher
? `src/**/${fileMatcher}`
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";

View File

@ -154,6 +154,7 @@ var harnessSources = harnessCoreSources.concat([
"symbolWalker.ts",
"languageService.ts",
"publicApi.ts",
"hostNewLineSupport.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
@ -238,6 +239,23 @@ var libraryTargets = librarySourceMap.map(function (f) {
return path.join(builtLocalDirectory, f.target);
});
/**
* .lcg file is what localization team uses to know what messages to localize.
* The file is always generated in 'enu\diagnosticMessages.generated.json.lcg'
*/
var generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg");
/**
* The localization target produces the two following transformations:
* 1. 'src\loc\lcl\<locale>\diagnosticMessages.generated.json.lcl' => 'built\local\<locale>\diagnosticMessages.generated.json'
* convert localized resources into a .json file the compiler can understand
* 2. 'src\compiler\diagnosticMessages.generated.json' => 'built\local\ENU\diagnosticMessages.generated.json.lcg'
* generate the lcg file (source of messages to localize) from the diagnosticMessages.generated.json
*/
var localizationTargets = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-CN", "zh-TW"].map(function (f) {
return path.join(builtLocalDirectory, f);
}).concat(path.dirname(generatedLCGFile));
// Prepends the contents of prefixFile to destinationFile
function prependFile(prefixFile, destinationFile) {
if (!fs.existsSync(prefixFile)) {
@ -443,7 +461,6 @@ compileFile(generateLocalizedDiagnosticMessagesJs,
/*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);
@ -735,8 +752,7 @@ 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).
concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d) })).
concat(path.dirname(generatedLCGFile));
concat(localizationTargets);
var missingFiles = expectedFiles.filter(function (f) {
return !fs.existsSync(f);
});
@ -855,7 +871,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
var inspect = process.env.inspect || process.env["inspect-brk"] || process.env.i;
var testTimeout = process.env.timeout || defaultTestTimeout;
var tests = process.env.test || process.env.tests || process.env.t;
var light = process.env.light || false;
var light = process.env.light === undefined || process.env.light !== "false";
var stackTraceLimit = process.env.stackTraceLimit;
var testConfigFile = 'test.config';
if (fs.existsSync(testConfigFile)) {

View File

@ -2,7 +2,8 @@
<!-- QUESTIONS: This is not a general support forum! Ask Qs at http://stackoverflow.com/questions/tagged/typescript -->
<!-- SUGGESTIONS: See https://github.com/Microsoft/TypeScript-wiki/blob/master/Writing-Good-Design-Proposals.md -->
**TypeScript Version:** 2.4.0 / nightly (2.5.0-dev.201xxxxx)
<!-- Please try to reproduce the issue with `typescript@next`. It may have already been fixed. -->
**TypeScript Version:** 2.7.0-dev.201xxxxx
**Code**

5340
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "typescript",
"author": "Microsoft Corp.",
"homepage": "http://typescriptlang.org/",
"version": "2.6.0",
"version": "2.7.0",
"license": "Apache-2.0",
"description": "TypeScript is a language for application scale JavaScript development",
"keywords": [
@ -83,7 +83,7 @@
},
"scripts": {
"pretest": "jake tests",
"test": "jake runtests-parallel",
"test": "jake runtests-parallel light=false",
"build": "npm run build:compiler && npm run build:tests",
"build:compiler": "jake local",
"build:tests": "jake tests",

View File

@ -3,7 +3,7 @@ Thank you for submitting a pull request!
Here's a checklist you might find useful.
[ ] There is an associated issue that is labelled
'Bug' or 'Accepting PRs' or is in the Community milestone
'Bug' or 'help wanted' or is in the Community milestone
[ ] Code is up-to-date with the `master` branch
[ ] You've successfully run `jake runtests` locally
[ ] You've signed the CLA

View File

@ -51,22 +51,25 @@ class DeclarationsWalker {
return this.processType((<any>type).typeArguments[0]);
}
else {
for (const decl of s.getDeclarations()) {
const sourceFile = decl.getSourceFile();
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
return;
}
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) {
this.removedTypes.push(type);
return;
}
else {
// splice declaration in final d.ts file
let text = decl.getFullText();
this.text += `${text}\n`;
// recursively pull all dependencies into result dts file
const declarations = s.getDeclarations();
if (declarations) {
for (const decl of declarations) {
const sourceFile = decl.getSourceFile();
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
return;
}
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) {
this.removedTypes.push(type);
return;
}
else {
// splice declaration in final d.ts file
let text = decl.getFullText();
this.text += `${text}\n`;
// recursively pull all dependencies into result dts file
this.visitTypeNodes(decl);
this.visitTypeNodes(decl);
}
}
}
}

View File

@ -27,16 +27,55 @@ function main(): void {
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));
if (!result || !result.LCX || !result.LCX.$ || !result.LCX.$.TgtCul) {
console.error("Unexpected XML file structure. Expected to find result.LCX.$.TgtCul.");
process.exit(1);
}
const outputDirectoryName = getPreferedLocaleName(result.LCX.$.TgtCul);
if (!outputDirectoryName) {
console.error(`Invalid output locale name for '${result.LCX.$.TgtCul}'.`);
process.exit(1);
}
writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result));
});
});
}
/**
* A locale name is based on the language tagging conventions of RFC 4646 (Windows Vista
* and later), and is represented by LOCALE_SNAME.
* Generally, the pattern <language>-<REGION> is used. Here, language is a lowercase ISO 639
* language code. The codes from ISO 639-1 are used when available. Otherwise, codes from
* ISO 639-2/T are used. REGION specifies an uppercase ISO 3166-1 country/region identifier.
* For example, the locale name for English (United States) is "en-US" and the locale name
* for Divehi (Maldives) is "dv-MV".
*
* If the locale is a neutral locale (no region), the LOCALE_SNAME value follows the
* pattern <language>. If it is a neutral locale for which the script is significant, the
* pattern is <language>-<Script>.
*
* More at https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx
*
* Most of the languages we support are neutral locales, so we want to use the language name.
* There are three exceptions, zh-CN, zh-TW and pt-BR.
*/
function getPreferedLocaleName(localeName: string) {
localeName = localeName.toLowerCase();
switch (localeName) {
case "zh-cn":
case "zh-tw":
case "pt-br":
return localeName;
default:
return localeName.split("-")[0];
}
}
function handleError(err: null | object) {
if (err) {
console.error(err);
@ -46,9 +85,9 @@ function main(): void {
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];
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");

View File

@ -143,6 +143,15 @@ namespace ts {
let subtreeTransformFlags: TransformFlags = TransformFlags.None;
let skipTransformFlagAggregation: boolean;
/**
* Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file)
* If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node)
* This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations.
*/
function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2);
}
function bindSourceFile(f: SourceFile, opts: CompilerOptions) {
file = f;
options = opts;
@ -183,7 +192,7 @@ namespace ts {
return bindSourceFile;
function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean {
if ((opts.alwaysStrict === undefined ? opts.strict : opts.alwaysStrict) && !file.isDeclarationFile) {
if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) {
// bind in strict mode source files with alwaysStrict option
return true;
}
@ -2271,16 +2280,13 @@ namespace ts {
function isExportsOrModuleExportsOrAlias(node: Node): boolean {
return isExportsIdentifier(node) ||
isModuleExportsPropertyAccessExpression(node) ||
isNameOfExportsOrModuleExportsAliasDeclaration(node);
isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(node);
}
function isNameOfExportsOrModuleExportsAliasDeclaration(node: Node) {
if (isIdentifier(node)) {
const symbol = lookupSymbolForName(node.escapedText);
return symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) &&
symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(symbol.valueDeclaration.initializer);
}
return false;
function isNameOfExportsOrModuleExportsAliasDeclaration(node: Identifier): boolean {
const symbol = lookupSymbolForName(node.escapedText);
return symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) &&
symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(symbol.valueDeclaration.initializer);
}
function isExportsOrModuleExportsOrAliasOrAssignment(node: Node): boolean {
@ -2354,20 +2360,22 @@ namespace ts {
// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
const leftSideOfAssignment = node.left as PropertyAccessExpression;
const target = leftSideOfAssignment.expression as Identifier;
const target = leftSideOfAssignment.expression;
// Fix up parent pointers since we're going to use these nodes before we bind into them
leftSideOfAssignment.parent = node;
target.parent = leftSideOfAssignment;
if (isIdentifier(target)) {
// Fix up parent pointers since we're going to use these nodes before we bind into them
leftSideOfAssignment.parent = node;
target.parent = leftSideOfAssignment;
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
// var util = module.exports;
// util.property = function ...
bindExportsPropertyAssignment(node);
}
else {
bindPropertyAssignment(target.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ false);
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
// var util = module.exports;
// util.property = function ...
bindExportsPropertyAssignment(node);
}
else {
bindPropertyAssignment(target.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ false);
}
}
}
@ -2699,6 +2707,12 @@ namespace ts {
if (expression.kind === SyntaxKind.ImportKeyword) {
transformFlags |= TransformFlags.ContainsDynamicImport;
// A dynamic 'import()' call that contains a lexical 'this' will
// require a captured 'this' when emitting down-level.
if (subtreeFlags & TransformFlags.ContainsLexicalThis) {
transformFlags |= TransformFlags.ContainsCapturedLexicalThis;
}
}
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
@ -3282,6 +3296,9 @@ namespace ts {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxText:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.JsxOpeningFragment:
case SyntaxKind.JsxClosingFragment:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxSpreadAttribute:

View File

@ -6,58 +6,38 @@ namespace ts {
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 function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
const outputFiles: OutputFile[] = [];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
return { outputFiles, emitSkipped: emitResult.emitSkipped };
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
}
}
export interface Builder {
/**
* Call this to feed new program
*/
/** Called to inform builder about new program */
updateProgram(newProgram: Program): void;
getFilesAffectedBy(program: Program, path: Path): string[];
emitFile(program: Program, path: Path): EmitOutput;
/** Gets the files affected by the file path */
getFilesAffectedBy(program: Program, path: Path): ReadonlyArray<SourceFile>;
/** Emit the changed files and clear the cache of the changed files */
emitChangedFiles(program: Program): EmitOutputDetailed[];
emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray<EmitResult>;
/** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */
getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[];
getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
/** Called to reset the status of the builder */
clear(): void;
@ -73,31 +53,32 @@ namespace ts {
*/
onRemoveSourceFile(path: Path): void;
/**
* Called when sourceFile is changed
* For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called.
* If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called;
* otherwise "onUpdateSourceFileWithSameVersion" will be called.
*/
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)
* For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called.
* If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called;
* otherwise "onUpdateSourceFileWithSameVersion" will be called.
* This function should return whether the source file should be marked as changed (meaning that something associated with file has changed, e.g. module resolution)
*/
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[];
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile>;
}
interface FileInfo {
fileName: string;
version: string;
signature: string;
}
export interface BuilderOptions {
getCanonicalFileName: (fileName: string) => string;
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed;
computeHash: (data: string) => string;
shouldEmitFile: (sourceFile: SourceFile) => boolean;
}
export function createBuilder(options: BuilderOptions): Builder {
@ -105,12 +86,13 @@ namespace ts {
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>();
const changedFilesSet = createMap<true>();
const hasShapeChanged = createMap<true>();
let allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
let emitHandler: EmitHandler;
return {
updateProgram,
getFilesAffectedBy,
emitFile,
emitChangedFiles,
getSemanticDiagnostics,
clear
@ -124,6 +106,8 @@ namespace ts {
fileInfos.clear();
semanticDiagnosticsPerFile.clear();
}
hasShapeChanged.clear();
allFilesExcludingDefaultLibraryFile = undefined;
mutateMap(
fileInfos,
arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path),
@ -138,32 +122,34 @@ namespace ts {
);
}
function registerChangedFile(path: Path, fileName: string) {
changedFileNames.set(path, fileName);
function registerChangedFile(path: Path) {
changedFilesSet.set(path, true);
// 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);
registerChangedFile(sourceFile.path);
emitHandler.onAddSourceFile(program, sourceFile);
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
return { version: sourceFile.version, signature: undefined };
}
function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) {
registerChangedFile(path, existingFileInfo.fileName);
function removeExistingFileInfo(_existingFileInfo: FileInfo, path: Path) {
// Since we dont need to track removed file as changed file
// We can just remove its diagnostics
changedFilesSet.delete(path);
semanticDiagnosticsPerFile.delete(path);
emitHandler.onRemoveSourceFile(path);
}
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) {
if (existingInfo.version !== sourceFile.version) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
registerChangedFile(sourceFile.path);
existingInfo.version = sourceFile.version;
emitHandler.onUpdateSourceFile(program, sourceFile);
}
else if (program.hasInvalidatedResolution(sourceFile.path) &&
emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
else if (emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) {
registerChangedFile(sourceFile.path);
}
}
@ -179,114 +165,95 @@ namespace ts {
}
}
function getFilesAffectedBy(program: Program, path: Path): string[] {
function getFilesAffectedBy(program: Program, path: Path): ReadonlyArray<SourceFile> {
ensureProgramGraph(program);
const sourceFile = program.getSourceFile(path);
const singleFileResult = sourceFile && options.shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
const info = fileInfos.get(path);
if (!info || !updateShapeSignature(program, sourceFile, info)) {
return singleFileResult;
const sourceFile = program.getSourceFileByPath(path);
if (!sourceFile) {
return emptyArray;
}
Debug.assert(!!sourceFile);
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
if (!updateShapeSignature(program, sourceFile)) {
return [sourceFile];
}
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile);
}
function emitFile(program: Program, path: Path) {
function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray<EmitResult> {
ensureProgramGraph(program);
if (!fileInfos.has(path)) {
return { outputFiles: [], emitSkipped: true };
const compilerOptions = program.getCompilerOptions();
if (!changedFilesSet.size) {
return emptyArray;
}
return options.getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
}
// With --out or --outFile all outputs go into single file, do it only once
if (compilerOptions.outFile || compilerOptions.out) {
Debug.assert(semanticDiagnosticsPerFile.size === 0);
changedFilesSet.clear();
return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)];
}
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<true>();
enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => {
if (!seenFiles.has(fileName)) {
seenFiles.set(fileName, true);
if (sourceFile) {
// Any affected file shouldnt have the cached diagnostics
semanticDiagnosticsPerFile.delete(sourceFile.path);
let result: EmitResult[] | undefined;
changedFilesSet.forEach((_true, path) => {
// Get the affected Files by this program
const affectedFiles = getFilesAffectedBy(program, path as Path);
affectedFiles.forEach(affectedFile => {
// Affected files shouldnt have cached diagnostics
semanticDiagnosticsPerFile.delete(affectedFile.path);
const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
onEmitOutput(emitOutput, sourceFile);
if (!seenFiles.has(affectedFile.path)) {
seenFiles.set(affectedFile.path, true);
// mark all the emitted source files as seen
if (emitOutput.emittedSourceFiles) {
for (const file of emitOutput.emittedSourceFiles) {
seenFiles.set(file.fileName, true);
}
}
// Emit the affected file
(result || (result = [])).push(program.emit(affectedFile, writeFileCallback));
}
}
});
});
changedFilesSet.clear();
return result || emptyArray;
}
function emitChangedFiles(program: Program): EmitOutputDetailed[] {
function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
ensureProgramGraph(program);
const result: EmitOutputDetailed[] = [];
enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false,
/*onChangedFile*/ noop, emitOutput => result.push(emitOutput));
changedFileNames.clear();
return result;
}
Debug.assert(changedFilesSet.size === 0);
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);
}
});
const compilerOptions = program.getCompilerOptions();
if (compilerOptions.outFile || compilerOptions.out) {
Debug.assert(semanticDiagnosticsPerFile.size === 0);
// We dont need to cache the diagnostics just return them from program
return program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken);
}
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);
}
diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(program, sourceFile, cancellationToken));
}
return diagnostics || emptyArray;
}
function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
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) {
return cachedDiagnostics;
}
// Diagnostics werent cached, get them from program, and cache the result
const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken);
semanticDiagnosticsPerFile.set(path, diagnostics);
return diagnostics;
}
function clear() {
isModuleEmit = undefined;
emitHandler = undefined;
fileInfos.clear();
semanticDiagnosticsPerFile.clear();
changedFileNames.clear();
changedFilesSet.clear();
hasShapeChanged.clear();
}
/**
@ -307,15 +274,26 @@ namespace ts {
/**
* @return {boolean} indicates if the shape signature has changed since last update.
*/
function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) {
function updateShapeSignature(program: Program, sourceFile: SourceFile) {
Debug.assert(!!sourceFile);
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
if (hasShapeChanged.has(sourceFile.path)) {
return false;
}
hasShapeChanged.set(sourceFile.path, true);
const info = fileInfos.get(sourceFile.path);
Debug.assert(!!info);
const prevSignature = info.signature;
let latestSignature: string;
if (sourceFile.isDeclarationFile) {
latestSignature = options.computeHash(sourceFile.text);
latestSignature = sourceFile.version;
info.signature = latestSignature;
}
else {
const emitOutput = options.getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true);
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
latestSignature = options.computeHash(emitOutput.outputFiles[0].text);
info.signature = latestSignature;
@ -383,24 +361,27 @@ namespace ts {
}
/**
* Gets all the emittable files from the program.
* @param firstSourceFile This one will be emitted first. See https://github.com/Microsoft/TypeScript/issues/16888
* Gets all files of the program excluding the default library file
*/
function getAllEmittableFiles(program: Program, firstSourceFile: SourceFile): string[] {
const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
const sourceFiles = program.getSourceFiles();
const result: string[] = [];
add(firstSourceFile);
for (const sourceFile of sourceFiles) {
function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray<SourceFile> {
// Use cached result
if (allFilesExcludingDefaultLibraryFile) {
return allFilesExcludingDefaultLibraryFile;
}
let result: SourceFile[];
addSourceFile(firstSourceFile);
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile !== firstSourceFile) {
add(sourceFile);
addSourceFile(sourceFile);
}
}
return result;
allFilesExcludingDefaultLibraryFile = result || emptyArray;
return allFilesExcludingDefaultLibraryFile;
function add(sourceFile: SourceFile): void {
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && options.shouldEmitFile(sourceFile)) {
result.push(sourceFile.fileName);
function addSourceFile(sourceFile: SourceFile) {
if (!program.isSourceFileDefaultLibrary(sourceFile)) {
(result || (result = [])).push(sourceFile);
}
}
}
@ -414,14 +395,14 @@ namespace ts {
getFilesAffectedByUpdatedShape
};
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile> {
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 [sourceFile];
}
return getAllEmittableFiles(program, sourceFile);
return getAllFilesExcludingDefaultLibraryFile(program, sourceFile);
}
}
@ -478,7 +459,7 @@ namespace ts {
// add files referencing the removedFilePath, as changed files too
const referencedByInfo = fileInfos.get(filePath);
if (referencedByInfo) {
registerChangedFile(filePath as Path, referencedByInfo.fileName);
registerChangedFile(filePath as Path);
}
}
});
@ -492,37 +473,33 @@ namespace ts {
);
}
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile> {
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
return getAllEmittableFiles(program, sourceFile);
return getAllFilesExcludingDefaultLibraryFile(program, sourceFile);
}
const compilerOptions = program.getCompilerOptions();
if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) {
return singleFileResult;
return [sourceFile];
}
// 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 && options.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
};
const seenFileNamesMap = createMap<SourceFile>();
// Start with the paths this file was referenced by
const path = sourceFile.path;
setSeenFileName(path, sourceFile);
seenFileNamesMap.set(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))) {
seenFileNamesMap.set(currentPath, currentSourceFile);
if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) {
queue.push(...getReferencedByPaths(currentPath));
}
setSeenFileName(currentPath, currentSourceFile);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1138,6 +1138,13 @@ namespace ts {
reportInvalidOptionValue(option && option.type !== "number");
return Number((<NumericLiteral>valueExpression).text);
case SyntaxKind.PrefixUnaryExpression:
if ((<PrefixUnaryExpression>valueExpression).operator !== SyntaxKind.MinusToken || (<PrefixUnaryExpression>valueExpression).operand.kind !== SyntaxKind.NumericLiteral) {
break; // not valid JSON syntax
}
reportInvalidOptionValue(option && option.type !== "number");
return -Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text);
case SyntaxKind.ObjectLiteralExpression:
reportInvalidOptionValue(option && option.type !== "object");
const objectLiteralExpression = <ObjectLiteralExpression>valueExpression;
@ -1183,8 +1190,8 @@ namespace ts {
}
}
function isDoubleQuotedString(node: Node) {
return node.kind === SyntaxKind.StringLiteral && getSourceTextOfNodeFromSourceFile(sourceFile, node).charCodeAt(0) === CharacterCodes.doubleQuote;
function isDoubleQuotedString(node: Node): boolean {
return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile);
}
}
@ -1440,9 +1447,9 @@ namespace ts {
function getFileNames(): ExpandResult {
let filesSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "files") && !isNullOrUndefined(raw["files"])) {
if (isArray(raw["files"])) {
filesSpecs = <ReadonlyArray<string>>raw["files"];
if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
if (isArray(raw.files)) {
filesSpecs = <ReadonlyArray<string>>raw.files;
if (filesSpecs.length === 0) {
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
}
@ -1453,9 +1460,9 @@ namespace ts {
}
let includeSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "include") && !isNullOrUndefined(raw["include"])) {
if (isArray(raw["include"])) {
includeSpecs = <ReadonlyArray<string>>raw["include"];
if (hasProperty(raw, "include") && !isNullOrUndefined(raw.include)) {
if (isArray(raw.include)) {
includeSpecs = <ReadonlyArray<string>>raw.include;
}
else {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array");
@ -1463,16 +1470,16 @@ namespace ts {
}
let excludeSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw["exclude"])) {
if (isArray(raw["exclude"])) {
excludeSpecs = <ReadonlyArray<string>>raw["exclude"];
if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw.exclude)) {
if (isArray(raw.exclude)) {
excludeSpecs = <ReadonlyArray<string>>raw.exclude;
}
else {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array");
}
}
else {
const outDir = raw["compilerOptions"] && raw["compilerOptions"]["outDir"];
const outDir = raw.compilerOptions && raw.compilerOptions.outDir;
if (outDir) {
excludeSpecs = [outDir];
}
@ -1591,7 +1598,7 @@ namespace ts {
const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName);
// typingOptions has been deprecated and is only supported for backward compatibility purposes.
// It should be removed in future releases - use typeAcquisition instead.
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json["typeAcquisition"] || json["typingOptions"], basePath, errors, configFileName);
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName);
json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
let extendedConfigPath: Path;
@ -1748,7 +1755,7 @@ namespace ts {
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
return undefined;
}
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors);
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors);
if (typeof result === "boolean" && result) {
return result;
}

View File

@ -4,7 +4,7 @@
namespace ts {
// WARNING: The script `configureNightly.ts` uses a regexp to parse out these values.
// If changing the text in this section, be sure to test `configureNightly` too.
export const versionMajorMinor = "2.6";
export const versionMajorMinor = "2.7";
/** The version of the TypeScript compiler release */
export const version = `${versionMajorMinor}.0`;
}
@ -33,8 +33,8 @@ namespace ts {
// Using 'delete' on an object causes V8 to put the object in dictionary mode.
// This disables creation of hidden classes, which are expensive when an object is
// constantly changing shape.
map["__"] = undefined;
delete map["__"];
map.__ = undefined;
delete map.__;
return map;
}
@ -191,6 +191,18 @@ namespace ts {
}
return undefined;
}
/** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */
export function firstDefined<T, U>(array: ReadonlyArray<T> | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
for (let i = 0; i < array.length; i++) {
const result = callback(array[i], i);
if (result !== undefined) {
return result;
}
}
return undefined;
}
/**
* Iterates through the parent chain of a node and performs the callback on each parent until the callback
* returns a truthy value, then returns that value.
@ -213,11 +225,13 @@ namespace ts {
return undefined;
}
export function zipWith<T, U>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<U>, callback: (a: T, b: U, index: number) => void): void {
export function zipWith<T, U, V>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<U>, callback: (a: T, b: U, index: number) => V): V[] {
const result: V[] = [];
Debug.assert(arrayA.length === arrayB.length);
for (let i = 0; i < arrayA.length; i++) {
callback(arrayA[i], arrayB[i], i);
result.push(callback(arrayA[i], arrayB[i], i));
}
return result;
}
export function zipToMap<T>(keys: ReadonlyArray<string>, values: ReadonlyArray<T>): Map<T> {
@ -259,6 +273,16 @@ namespace ts {
return undefined;
}
export function findLast<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean): T | undefined {
for (let i = array.length - 1; i >= 0; i--) {
const value = array[i];
if (predicate(value, i)) {
return value;
}
}
return undefined;
}
/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */
export function findIndex<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean): number {
for (let i = 0; i < array.length; i++) {
@ -356,21 +380,6 @@ namespace ts {
return array;
}
export function removeWhere<T>(array: T[], f: (x: T) => boolean): boolean {
let outIndex = 0;
for (const item of array) {
if (!f(item)) {
array[outIndex] = item;
outIndex++;
}
}
if (outIndex !== array.length) {
array.length = outIndex;
return true;
}
return false;
}
export function filterMutate<T>(array: T[], f: (x: T, i: number, array: T[]) => boolean): void {
let outIndex = 0;
for (let i = 0; i < array.length; i++) {
@ -976,32 +985,6 @@ namespace ts {
return initial;
}
export function reduceRight<T, U>(array: ReadonlyArray<T>, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U;
export function reduceRight<T>(array: ReadonlyArray<T>, f: (memo: T, value: T, i: number) => T): T;
export function reduceRight<T>(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T {
if (array) {
const size = array.length;
if (size > 0) {
let pos = start === undefined || start > size - 1 ? size - 1 : start;
const end = count === undefined || pos - count < 0 ? 0 : pos - count;
let result: T;
if (arguments.length <= 2) {
result = array[pos];
pos--;
}
else {
result = initial;
}
while (pos >= end) {
result = f(result, array[pos], pos);
pos--;
}
return result;
}
}
return initial;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
@ -1160,6 +1143,14 @@ namespace ts {
return result;
}
export function arrayToNumericMap<T>(array: ReadonlyArray<T>, makeKey: (value: T) => number): T[] {
const result: T[] = [];
for (const value of array) {
result[makeKey(value)] = value;
}
return result;
}
/**
* Creates a set from the elements of an array.
*
@ -1663,7 +1654,7 @@ namespace ts {
}
export function isUrl(path: string) {
return path && !isRootedDiskPath(path) && path.indexOf("://") !== -1;
return path && !isRootedDiskPath(path) && stringContains(path, "://");
}
export function pathIsRelative(path: string): boolean {
@ -1693,6 +1684,12 @@ namespace ts {
return moduleResolution;
}
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "alwaysStrict";
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {
return compilerOptions[flag] === undefined ? compilerOptions.strict : compilerOptions[flag];
}
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
let seenAsterisk = false;
for (let i = 0; i < str.length; i++) {
@ -1844,7 +1841,7 @@ namespace ts {
return i < 0 ? path : path.substring(i + 1);
}
export function combinePaths(path1: string, path2: string) {
export function combinePaths(path1: string, path2: string): string {
if (!(path1 && path1.length)) return path2;
if (!(path2 && path2.length)) return path1;
if (getRootLength(path2) !== 0) return path2;
@ -1932,8 +1929,12 @@ namespace ts {
return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos;
}
export function stringContains(str: string, substring: string): boolean {
return str.indexOf(substring) !== -1;
}
export function hasExtension(fileName: string): boolean {
return getBaseFileName(fileName).indexOf(".") >= 0;
return stringContains(getBaseFileName(fileName), ".");
}
export function fileExtensionIs(path: string, extension: string): boolean {
@ -2670,6 +2671,16 @@ namespace ts {
return find<Extension>(supportedTypescriptExtensionsForExtractExtension, e => fileExtensionIs(path, e)) || find(supportedJavascriptExtensions, e => fileExtensionIs(path, e));
}
// Retrieves any string from the final "." onwards from a base file name.
// Unlike extensionFromPath, which throws an exception on unrecognized extensions.
export function getAnyExtensionFromPath(path: string): string | undefined {
const baseFileName = getBaseFileName(path);
const extensionIndex = baseFileName.lastIndexOf(".");
if (extensionIndex >= 0) {
return baseFileName.substring(extensionIndex);
}
}
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
}
@ -2680,8 +2691,14 @@ namespace ts {
export function assertTypeIsNever(_: never): void { }
export interface FileAndDirectoryExistence {
fileExists: boolean;
directoryExists: boolean;
}
export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void;
/** Returns the queried result for the file exists and directory exists if at all it was done */
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined;
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
clearCache(): void;
}
@ -2851,8 +2868,13 @@ namespace ts {
if (parentResult) {
const baseName = getBaseNameOfFileName(fileOrDirectory);
if (parentResult) {
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath));
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath));
const fsQueryResult: FileAndDirectoryExistence = {
fileExists: host.fileExists(fileOrDirectoryPath),
directoryExists: host.directoryExists(fileOrDirectoryPath)
};
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
updateFileSystemEntry(parentResult.directories, baseName, fsQueryResult.directoryExists);
return fsQueryResult;
}
}
}

View File

@ -172,7 +172,7 @@ namespace ts {
function hasInternalAnnotation(range: CommentRange) {
const comment = currentText.substring(range.pos, range.end);
return comment.indexOf("@internal") >= 0;
return stringContains(comment, "@internal");
}
function stripInternal(node: Node) {

View File

@ -1316,7 +1316,7 @@
"category": "Error",
"code": 2402
},
"Subsequent variable declarations must have the same type. Variable '{0}' must be of type '{1}', but here has type '{2}'.": {
"Subsequent variable declarations must have the same type. Variable '{0}' has type '{1}' at {2}, but here has type '{3}'.": {
"category": "Error",
"code": 2403
},
@ -2220,6 +2220,14 @@
"category": "Error",
"code": 2714
},
"Abstract property '{0}' in class '{1}' cannot be accessed in the constructor.": {
"category": "Error",
"code": 2715
},
"Type parameter '{0}' has a circular default.": {
"category": "Error",
"code": 2716
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@ -3515,11 +3523,11 @@
"category": "Error",
"code": 8021
},
"JSDoc '@augments' is not attached to a class declaration.": {
"JSDoc '@{0}' is not attached to a class.": {
"category": "Error",
"code": 8022
},
"JSDoc '@augments {0}' does not match the 'extends {1}' clause.": {
"JSDoc '@{0} {1}' does not match the 'extends {2}' clause.": {
"category": "Error",
"code": 8023
},
@ -3527,6 +3535,18 @@
"category": "Error",
"code": 8024
},
"Class declarations cannot have more than one `@augments` or `@extends` tag.": {
"category": "Error",
"code": 8025
},
"Expected {0} type arguments; provide these with an '@extends' tag.": {
"category": "Error",
"code": 8026
},
"Expected {0}-{1} type arguments; provide these with an '@extends' tag.": {
"category": "Error",
"code": 8027
},
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
"category": "Error",
"code": 9002
@ -3595,6 +3615,18 @@
"category": "Error",
"code": 17013
},
"JSX fragment has no corresponding closing tag.": {
"category": "Error",
"code": 17014
},
"Expected corresponding closing tag for JSX fragment.": {
"category": "Error",
"code": 17015
},
"JSX fragment is not supported when using --jsxFactory": {
"category": "Error",
"code":17016
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
@ -3649,15 +3681,15 @@
"category": "Error",
"code": 90010
},
"Import {0} from {1}.": {
"Import '{0}' from \"{1}\".": {
"category": "Message",
"code": 90013
},
"Change {0} to {1}.": {
"Change '{0}' to '{1}'.": {
"category": "Message",
"code": 90014
},
"Add {0} to existing import declaration from {1}.": {
"Add '{0}' to existing import declaration from \"{1}\".": {
"category": "Message",
"code": 90015
},
@ -3669,6 +3701,7 @@
"category": "Message",
"code": 90017
},
"Disable checking for this file.": {
"category": "Message",
"code": 90018
@ -3713,7 +3746,6 @@
"category": "Message",
"code": 90028
},
"Convert function to an ES2015 class": {
"category": "Message",
"code": 95001
@ -3722,34 +3754,44 @@
"category": "Message",
"code": 95002
},
"Extract symbol": {
"category": "Message",
"code": 95003
},
"Extract to {0} in {1}": {
"category": "Message",
"code": 95004
},
"Extract function": {
"category": "Message",
"code": 95005
},
"Extract constant": {
"category": "Message",
"code": 95006
},
"Extract to {0} in enclosing scope": {
"category": "Message",
"code": 95007
},
"Extract to {0} in {1} scope": {
"category": "Message",
"code": 95008
},
"Annotate with type from JSDoc": {
"category": "Message",
"code": 95009
},
"Annotate with types from JSDoc": {
"category": "Message",
"code": 95010
},
"Infer type of '{0}' from usage.": {
"category": "Message",
"code": 95011
},
"Infer parameter types from usage.": {
"category": "Message",
"code": 95012
}
}

View File

@ -546,6 +546,8 @@ namespace ts {
return emitTypeReference(<TypeReferenceNode>node);
case SyntaxKind.FunctionType:
return emitFunctionType(<FunctionTypeNode>node);
case SyntaxKind.JSDocFunctionType:
return emitJSDocFunctionType(node as JSDocFunctionType);
case SyntaxKind.ConstructorType:
return emitConstructorType(<ConstructorTypeNode>node);
case SyntaxKind.TypeQuery:
@ -574,6 +576,20 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.JSDocAllType:
write("*");
return;
case SyntaxKind.JSDocUnknownType:
write("?");
return;
case SyntaxKind.JSDocNullableType:
return emitJSDocNullableType(node as JSDocNullableType);
case SyntaxKind.JSDocNonNullableType:
return emitJSDocNonNullableType(node as JSDocNonNullableType);
case SyntaxKind.JSDocOptionalType:
return emitJSDocOptionalType(node as JSDocOptionalType);
case SyntaxKind.JSDocVariadicType:
return emitJSDocVariadicType(node as JSDocVariadicType);
// Binding patterns
case SyntaxKind.ObjectBindingPattern:
@ -683,9 +699,11 @@ namespace ts {
case SyntaxKind.JsxText:
return emitJsxText(<JsxText>node);
case SyntaxKind.JsxOpeningElement:
return emitJsxOpeningElement(<JsxOpeningElement>node);
case SyntaxKind.JsxOpeningFragment:
return emitJsxOpeningElementOrFragment(<JsxOpeningElement>node);
case SyntaxKind.JsxClosingElement:
return emitJsxClosingElement(<JsxClosingElement>node);
case SyntaxKind.JsxClosingFragment:
return emitJsxClosingElementOrFragment(<JsxClosingElement>node);
case SyntaxKind.JsxAttribute:
return emitJsxAttribute(<JsxAttribute>node);
case SyntaxKind.JsxAttributes:
@ -820,6 +838,8 @@ namespace ts {
return emitJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return emitJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return emitJsxFragment(<JsxFragment>node);
// Transformation nodes
case SyntaxKind.PartiallyEmittedExpression:
@ -914,9 +934,16 @@ namespace ts {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emitIfPresent(node.dotDotDotToken);
emit(node.name);
if (node.name) {
emit(node.name);
}
emitIfPresent(node.questionToken);
emitWithPrefix(": ", node.type);
if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) {
emit(node.type);
}
else {
emitWithPrefix(": ", node.type);
}
emitExpressionWithPrefix(" = ", node.initializer);
}
@ -1035,6 +1062,29 @@ namespace ts {
emit(node.type);
}
function emitJSDocFunctionType(node: JSDocFunctionType) {
write("function");
emitParameters(node, node.parameters);
write(":");
emit(node.type);
}
function emitJSDocNullableType(node: JSDocNullableType) {
write("?");
emit(node.type);
}
function emitJSDocNonNullableType(node: JSDocNonNullableType) {
write("!");
emit(node.type);
}
function emitJSDocOptionalType(node: JSDocOptionalType) {
emit(node.type);
write("=");
}
function emitConstructorType(node: ConstructorTypeNode) {
write("new ");
emitTypeParameters(node, node.typeParameters);
@ -1060,6 +1110,11 @@ namespace ts {
write("[]");
}
function emitJSDocVariadicType(node: JSDocVariadicType) {
write("...");
emit(node.type);
}
function emitTupleType(node: TupleTypeNode) {
write("[");
emitList(node, node.elementTypes, ListFormat.TupleTypeElements);
@ -1226,7 +1281,7 @@ namespace ts {
// check if numeric literal is a decimal literal that was originally written with a dot
const text = getLiteralTextOfNode(<LiteralExpression>expression);
return !expression.numericLiteralFlags
&& text.indexOf(tokenToString(SyntaxKind.DotToken)) < 0;
&& !stringContains(text, tokenToString(SyntaxKind.DotToken));
}
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
// check if constant enum value is integer
@ -2009,7 +2064,7 @@ namespace ts {
function emitJsxElement(node: JsxElement) {
emit(node.openingElement);
emitList(node, node.children, ListFormat.JsxElementChildren);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingElement);
}
@ -2024,14 +2079,24 @@ namespace ts {
write("/>");
}
function emitJsxOpeningElement(node: JsxOpeningElement) {
function emitJsxFragment(node: JsxFragment) {
emit(node.openingFragment);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingFragment);
}
function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) {
write("<");
emitJsxTagName(node.tagName);
writeIfAny(node.attributes.properties, " ");
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
if (isJsxOpeningElement(node)) {
emitJsxTagName(node.tagName);
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
write(" ");
emit(node.attributes);
}
}
write(">");
}
@ -2039,9 +2104,11 @@ namespace ts {
writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true));
}
function emitJsxClosingElement(node: JsxClosingElement) {
function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) {
write("</");
emitJsxTagName(node.tagName);
if (isJsxClosingElement(node)) {
emitJsxTagName(node.tagName);
}
write(">");
}
@ -2560,12 +2627,6 @@ namespace ts {
writer.decreaseIndent();
}
function writeIfAny(nodes: NodeArray<Node>, text: string) {
if (some(nodes)) {
write(text);
}
}
function writeToken(token: SyntaxKind, pos: number, contextNode?: Node) {
return onEmitSourceMapOfToken
? onEmitSourceMapOfToken(contextNode, token, pos, writeTokenText)
@ -3125,7 +3186,7 @@ namespace ts {
EnumMembers = CommaDelimited | Indented | MultiLine,
CaseBlockClauses = Indented | MultiLine,
NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces,
JsxElementChildren = SingleLine | NoInterveningComments,
JsxElementOrFragmentChildren = SingleLine | NoInterveningComments,
JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments,
CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty,
HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine,

View File

@ -47,10 +47,15 @@ namespace ts {
* Creates a shallow, memberwise clone of a node with no source map location.
*/
/* @internal */
export function getSynthesizedClone<T extends Node>(node: T | undefined): T {
export function getSynthesizedClone<T extends Node>(node: T | undefined): T | undefined {
// We don't use "clone" from core.ts here, as we need to preserve the prototype chain of
// the original node. We also need to exclude specific properties and only include own-
// properties (to skip members already defined on the shared prototype).
if (node === undefined) {
return undefined;
}
const clone = <T>createSynthesizedNode(node.kind);
clone.flags |= node.flags;
setOriginalNode(clone, node);
@ -1977,7 +1982,7 @@ namespace ts {
node.decorators = asNodeArray(decorators);
node.modifiers = asNodeArray(modifiers);
node.isExportEquals = isExportEquals;
node.expression = expression;
node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression);
return node;
}
@ -2110,6 +2115,22 @@ namespace ts {
: node;
}
export function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
const node = <JsxFragment>createSynthesizedNode(SyntaxKind.JsxFragment);
node.openingFragment = openingFragment;
node.children = createNodeArray(children);
node.closingFragment = closingFragment;
return node;
}
export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
return node.openingFragment !== openingFragment
|| node.children !== children
|| node.closingFragment !== closingFragment
? updateNode(createJsxFragment(openingFragment, children, closingFragment), node)
: node;
}
export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) {
const node = <JsxAttribute>createSynthesizedNode(SyntaxKind.JsxAttribute);
node.name = name;
@ -2607,6 +2628,16 @@ namespace ts {
return node;
}
/**
* Sets flags that control emit behavior of a node.
*/
/* @internal */
export function addEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) {
const emitNode = getOrCreateEmitNode(node);
emitNode.flags = emitNode.flags | emitFlags;
return node;
}
/**
* Gets a custom text range to use when emitting source maps.
*/
@ -2936,7 +2967,7 @@ namespace ts {
);
}
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
// To ensure the emit resolver can properly resolve the namespace, we need to
// treat this identifier as if it were a source tree node by clearing the `Synthesized`
// flag and setting a parent node.
@ -2948,7 +2979,7 @@ namespace ts {
return react;
}
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
if (isQualifiedName(jsxFactory)) {
const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent);
const right = createIdentifier(idText(jsxFactory.right));
@ -2960,7 +2991,7 @@ namespace ts {
}
}
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
return jsxFactoryEntity ?
createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) :
createPropertyAccess(
@ -3001,6 +3032,37 @@ namespace ts {
);
}
export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName, reactNamespace: string, children: Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
const tagName = createPropertyAccess(
createReactNamespace(reactNamespace, parentElement),
"Fragment"
);
const argumentsList = [<Expression>tagName];
argumentsList.push(createNull());
if (children && children.length > 0) {
if (children.length > 1) {
for (const child of children) {
child.startsOnNewLine = true;
argumentsList.push(child);
}
}
else {
argumentsList.push(children[0]);
}
}
return setTextRange(
createCall(
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
/*typeArguments*/ undefined,
argumentsList
),
location
);
}
// Helpers
export function getHelperName(name: string) {
@ -3870,6 +3932,27 @@ namespace ts {
: e;
}
/**
* [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but
* has a lookahead restriction for `function`, `async function`, and `class`.
*
* Basically, that means we need to parenthesize in the following cases:
*
* - BinaryExpression of CommaToken
* - CommaList (synthetic list of multiple comma expressions)
* - FunctionExpression
* - ClassExpression
*/
export function parenthesizeDefaultExpression(e: Expression) {
const check = skipPartiallyEmittedExpressions(e);
return (check.kind === SyntaxKind.ClassExpression ||
check.kind === SyntaxKind.FunctionExpression ||
check.kind === SyntaxKind.CommaListExpression ||
isBinaryExpression(check) && check.operatorToken.kind === SyntaxKind.CommaToken)
? createParen(e)
: e;
}
/**
* Wraps an expression in parentheses if it is needed in order to use the expression
* as the expression of a NewExpression node.

View File

@ -77,16 +77,20 @@ namespace ts {
traceEnabled: boolean;
}
interface PackageJson {
name?: string;
version?: string;
/** Just the fields that we use for module resolution. */
interface PackageJsonPathFields {
typings?: string;
types?: string;
main?: string;
}
interface PackageJson extends PackageJsonPathFields {
name?: string;
version?: string;
}
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState): string | undefined {
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJsonPathFields, baseDirectory: string, state: ModuleResolutionState): string | undefined {
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
@ -124,7 +128,11 @@ namespace ts {
}
}
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
export interface GetEffectiveTypeRootsHost {
directoryExists?(directoryName: string): boolean;
getCurrentDirectory?(): string;
}
export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined {
if (options.typeRoots) {
return options.typeRoots;
}
@ -886,7 +894,7 @@ namespace ts {
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, packageJsonContent));
}
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJson | undefined): PathAndExtension | undefined {
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJsonPathFields | undefined): PathAndExtension | undefined {
const fromPackageJson = packageJsonContent && loadModuleFromPackageJson(packageJsonContent, extensions, candidate, failedLookupLocations, state);
if (fromPackageJson) {
return fromPackageJson;
@ -901,7 +909,7 @@ namespace ts {
failedLookupLocations: Push<string>,
onlyRecordFailures: boolean,
{ host, traceEnabled }: ModuleResolutionState,
): { packageJsonContent: PackageJson | undefined, packageId: PackageId | undefined } {
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined } {
const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host);
const packageJsonPath = pathToPackageJson(nodeModuleDirectory);
if (directoryExists && host.fileExists(packageJsonPath)) {
@ -912,7 +920,7 @@ namespace ts {
const packageId: PackageId = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string"
? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version }
: undefined;
return { packageJsonContent, packageId };
return { found: true, packageJsonContent, packageId };
}
else {
if (directoryExists && traceEnabled) {
@ -920,11 +928,11 @@ namespace ts {
}
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
failedLookupLocations.push(packageJsonPath);
return { packageJsonContent: undefined, packageId: undefined };
return { found: false, packageJsonContent: undefined, packageId: undefined };
}
}
function loadModuleFromPackageJson(jsonContent: PackageJson, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
function loadModuleFromPackageJson(jsonContent: PackageJsonPathFields, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, jsonContent, candidate, state);
if (!file) {
return undefined;
@ -976,16 +984,29 @@ namespace ts {
}
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
const { packageName, rest } = getPackageName(moduleName);
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
const { packageJsonContent, packageId } = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state);
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
let packageJsonContent: PackageJsonPathFields | undefined;
let packageId: PackageId | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", failedLookupLocations, /*onlyRecordFailures*/ !nodeModulesFolderExists, state);
if (packageInfo.found) {
({ packageJsonContent, packageId } = packageInfo);
}
else {
const { packageName, rest } = getPackageName(moduleName);
if (rest !== "") { // If "rest" is empty, we just did this search above.
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId.
packageId = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state).packageId;
}
}
const pathAndExtension = loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, packageJsonContent);
return withPackageId(packageId, pathAndExtension);
}
function getPackageName(moduleName: string): { packageName: string, rest: string } {
/* @internal */
export function getPackageName(moduleName: string): { packageName: string, rest: string } {
let idx = moduleName.indexOf(directorySeparator);
if (moduleName[0] === "@") {
idx = moduleName.indexOf(directorySeparator, idx + 1);
@ -1043,25 +1064,34 @@ namespace ts {
const mangledScopedPackageSeparator = "__";
/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */
function mangleScopedPackage(moduleName: string, state: ModuleResolutionState): string {
if (startsWith(moduleName, "@")) {
const replaceSlash = moduleName.replace(ts.directorySeparator, mangledScopedPackageSeparator);
if (replaceSlash !== moduleName) {
const mangled = replaceSlash.slice(1); // Take off the "@"
if (state.traceEnabled) {
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
}
return mangled;
function mangleScopedPackage(packageName: string, state: ModuleResolutionState): string {
const mangled = getMangledNameForScopedPackage(packageName);
if (state.traceEnabled && mangled !== packageName) {
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
}
return mangled;
}
/* @internal */
export function getTypesPackageName(packageName: string): string {
return `@types/${getMangledNameForScopedPackage(packageName)}`;
}
function getMangledNameForScopedPackage(packageName: string): string {
if (startsWith(packageName, "@")) {
const replaceSlash = packageName.replace(ts.directorySeparator, mangledScopedPackageSeparator);
if (replaceSlash !== packageName) {
return replaceSlash.slice(1); // Take off the "@"
}
}
return moduleName;
return packageName;
}
/* @internal */
export function getPackageNameFromAtTypesDirectory(mangledName: string): string {
const withoutAtTypePrefix = removePrefix(mangledName, "@types/");
if (withoutAtTypePrefix !== mangledName) {
return withoutAtTypePrefix.indexOf(mangledScopedPackageSeparator) !== -1 ?
return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ?
"@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) :
withoutAtTypePrefix;
}

View File

@ -377,6 +377,10 @@ namespace ts {
return visitNode(cbNode, (<JsxElement>node).openingElement) ||
visitNodes(cbNode, cbNodes, (<JsxElement>node).children) ||
visitNode(cbNode, (<JsxElement>node).closingElement);
case SyntaxKind.JsxFragment:
return visitNode(cbNode, (<JsxFragment>node).openingFragment) ||
visitNodes(cbNode, cbNodes, (<JsxFragment>node).children) ||
visitNode(cbNode, (<JsxFragment>node).closingFragment);
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
@ -1423,6 +1427,11 @@ namespace ts {
return tokenIsIdentifierOrKeyword(token());
}
function nextTokenIsIdentifierOrKeywordOrGreaterThan() {
nextToken();
return tokenIsIdentifierOrKeywordOrGreaterThan(token());
}
function isHeritageClauseExtendsOrImplementsKeyword(): boolean {
if (token() === SyntaxKind.ImplementsKeyword ||
token() === SyntaxKind.ExtendsKeyword) {
@ -3802,9 +3811,9 @@ namespace ts {
node.operand = parseLeftHandSideExpressionOrHigher();
return finishNode(node);
}
else if (sourceFile.languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeyword)) {
else if (sourceFile.languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) {
// JSXElement is part of primaryExpression
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true);
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true);
}
const expression = parseLeftHandSideExpressionOrHigher();
@ -3959,14 +3968,14 @@ namespace ts {
}
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement {
const opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
let result: JsxElement | JsxSelfClosingElement;
function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment {
const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext);
let result: JsxElement | JsxSelfClosingElement | JsxFragment;
if (opening.kind === SyntaxKind.JsxOpeningElement) {
const node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
node.openingElement = opening;
node.children = parseJsxChildren(node.openingElement.tagName);
node.children = parseJsxChildren(node.openingElement);
node.closingElement = parseJsxClosingElement(inExpressionContext);
if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) {
@ -3975,6 +3984,14 @@ namespace ts {
result = finishNode(node);
}
else if (opening.kind === SyntaxKind.JsxOpeningFragment) {
const node = <JsxFragment>createNode(SyntaxKind.JsxFragment, opening.pos);
node.openingFragment = opening;
node.children = parseJsxChildren(node.openingFragment);
node.closingFragment = parseJsxClosingFragment(inExpressionContext);
result = finishNode(node);
}
else {
Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement);
// Nothing else to do for self-closing elements
@ -3989,7 +4006,7 @@ namespace ts {
// Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios
// of one sort or another.
if (inExpressionContext && token() === SyntaxKind.LessThanToken) {
const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true));
const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true));
if (invalidElement) {
parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element);
const badNode = <BinaryExpression>createNode(SyntaxKind.BinaryExpression, result.pos);
@ -4020,12 +4037,12 @@ namespace ts {
case SyntaxKind.OpenBraceToken:
return parseJsxExpression(/*inExpressionContext*/ false);
case SyntaxKind.LessThanToken:
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ false);
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false);
}
Debug.fail("Unknown JSX child kind " + token());
}
function parseJsxChildren(openingTagName: LeftHandSideExpression): NodeArray<JsxChild> {
function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray<JsxChild> {
const list = [];
const listPos = getNodePos();
const saveParsingContext = parsingContext;
@ -4040,7 +4057,13 @@ namespace ts {
else if (token() === SyntaxKind.EndOfFileToken) {
// If we hit EOF, issue the error at the tag that lacks the closing element
// rather than at the end of the file (which is useless)
parseErrorAtPosition(openingTagName.pos, openingTagName.end - openingTagName.pos, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTagName));
if (isJsxOpeningFragment(openingTag)) {
parseErrorAtPosition(openingTag.pos, openingTag.end - openingTag.pos, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag);
}
else {
const openingTagName = openingTag.tagName;
parseErrorAtPosition(openingTagName.pos, openingTagName.end - openingTagName.pos, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTagName));
}
break;
}
else if (token() === SyntaxKind.ConflictMarkerTrivia) {
@ -4063,11 +4086,17 @@ namespace ts {
return finishNode(jsxAttributes);
}
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement {
function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment {
const fullStart = scanner.getStartPos();
parseExpected(SyntaxKind.LessThanToken);
if (token() === SyntaxKind.GreaterThanToken) {
parseExpected(SyntaxKind.GreaterThanToken);
const node: JsxOpeningFragment = <JsxOpeningFragment>createNode(SyntaxKind.JsxOpeningFragment, fullStart);
return finishNode(node);
}
const tagName = parseJsxElementName();
const attributes = parseJsxAttributes();
@ -4179,6 +4208,23 @@ namespace ts {
return finishNode(node);
}
function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment {
const node = <JsxClosingFragment>createNode(SyntaxKind.JsxClosingFragment);
parseExpected(SyntaxKind.LessThanSlashToken);
if (tokenIsIdentifierOrKeyword(token())) {
const unexpectedTagName = parseJsxElementName();
parseErrorAtPosition(unexpectedTagName.pos, unexpectedTagName.end - unexpectedTagName.pos, Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment);
}
if (inExpressionContext) {
parseExpected(SyntaxKind.GreaterThanToken);
}
else {
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false);
scanJsxText();
}
return finishNode(node);
}
function parseTypeAssertion(): TypeAssertion {
const node = <TypeAssertion>createNode(SyntaxKind.TypeAssertionExpression);
parseExpected(SyntaxKind.LessThanToken);
@ -4724,7 +4770,7 @@ namespace ts {
parseExpected(SyntaxKind.OpenParenToken);
node.expression = allowInAnd(parseExpression);
parseExpected(SyntaxKind.CloseParenToken);
node.statement = parseStatement();
node.statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement);
return finishNode(node);
}
@ -6373,6 +6419,7 @@ namespace ts {
if (tagName) {
switch (tagName.escapedText) {
case "augments":
case "extends":
tag = parseAugmentsTag(atToken, tagName);
break;
case "class":

View File

@ -663,8 +663,7 @@ namespace ts {
dropDiagnosticsProducingTypeChecker,
getSourceFileFromReference,
sourceFileToPackageName,
redirectTargetsSet,
hasInvalidatedResolution
redirectTargetsSet
};
verifyCompilerOptions();
@ -1050,6 +1049,10 @@ namespace ts {
// update fileName -> file mapping
for (let i = 0; i < newSourceFiles.length; i++) {
filesByName.set(filePaths[i], newSourceFiles[i]);
// Set the file as found during node modules search if it was found that way in old progra,
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(filePaths[i]))) {
sourceFilesFoundSearchingNodeModules.set(filePaths[i], true);
}
}
files = newSourceFiles;
@ -1092,11 +1095,18 @@ namespace ts {
return true;
}
if (defaultLibraryPath && defaultLibraryPath.length !== 0) {
return containsPath(defaultLibraryPath, file.path, currentDirectory, /*ignoreCase*/ !host.useCaseSensitiveFileNames());
if (!options.noLib) {
return false;
}
return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
// If '--lib' is not specified, include default library file according to '--target'
// otherwise, using options specified in '--lib' instead of '--target' default library file
if (!options.lib) {
return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
}
else {
return forEach(options.lib, libFileName => compareStrings(file.fileName, combinePaths(defaultLibraryPath, libFileName), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo);
}
}
function getDiagnosticsProducingTypeChecker() {
@ -1272,8 +1282,10 @@ namespace ts {
const typeChecker = getDiagnosticsProducingTypeChecker();
Debug.assert(!!sourceFile.bindDiagnostics);
// For JavaScript files, we don't want to report semantic errors unless explicitly requested.
const includeBindAndCheckDiagnostics = !isSourceFileJavaScript(sourceFile) || isCheckJsEnabledForFile(sourceFile, options);
// By default, only type-check .ts, .tsx, and 'External' files (external files are added by plugins)
const includeBindAndCheckDiagnostics = sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX ||
sourceFile.scriptKind === ScriptKind.External || isCheckJsEnabledForFile(sourceFile, options);
const bindDiagnostics = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray;
const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray;
const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName);
@ -2119,7 +2131,7 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib");
}
if (options.noImplicitUseStrict && (options.alwaysStrict === undefined ? options.strict : options.alwaysStrict)) {
if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict");
}
@ -2348,7 +2360,7 @@ namespace ts {
return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set;
}
function needAllowJs() {
return options.allowJs || !options.noImplicitAny ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
return options.allowJs || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
}
}

View File

@ -64,6 +64,7 @@ namespace ts {
interface DirectoryOfFailedLookupWatch {
dir: string;
dirPath: Path;
ignore?: true;
}
export const maxNumberOfFilesToIterateForInvalidation = 256;
@ -107,7 +108,9 @@ namespace ts {
return {
startRecordingFilesWithChangedResolutions,
finishRecordingFilesWithChangedResolutions,
startCachingPerDirectoryResolution,
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
startCachingPerDirectoryResolution: clearPerDirectoryResolutions,
finishCachingPerDirectoryResolution,
resolveModuleNames,
resolveTypeReferenceDirectives,
@ -141,7 +144,9 @@ namespace ts {
resolvedModuleNames.clear();
resolvedTypeReferenceDirectives.clear();
allFilesHaveInvalidatedResolution = false;
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
clearPerDirectoryResolutions();
}
function startRecordingFilesWithChangedResolutions() {
@ -165,8 +170,9 @@ namespace ts {
return path => collected && collected.has(path);
}
function startCachingPerDirectoryResolution() {
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
function clearPerDirectoryResolutions() {
perDirectoryResolvedModuleNames.clear();
perDirectoryResolvedTypeReferenceDirectives.clear();
}
function finishCachingPerDirectoryResolution() {
@ -178,8 +184,7 @@ namespace ts {
}
});
perDirectoryResolvedModuleNames.clear();
perDirectoryResolvedTypeReferenceDirectives.clear();
clearPerDirectoryResolutions();
}
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
@ -315,6 +320,33 @@ namespace ts {
return endsWith(dirPath, "/node_modules");
}
function isDirectoryAtleastAtLevelFromFSRoot(dirPath: Path, minLevels: number) {
for (let searchIndex = getRootLength(dirPath); minLevels > 0; minLevels--) {
searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1;
if (searchIndex === 0) {
// Folder isnt at expected minimun levels
return false;
}
}
return true;
}
function canWatchDirectory(dirPath: Path) {
return isDirectoryAtleastAtLevelFromFSRoot(dirPath,
// When root is "/" do not watch directories like:
// "/", "/user", "/user/username", "/user/username/folderAtRoot"
// When root is "c:/" do not watch directories like:
// "c:/", "c:/folderAtRoot"
dirPath.charCodeAt(0) === CharacterCodes.slash ? 3 : 1);
}
function filterFSRootDirectoriesToWatch(watchPath: DirectoryOfFailedLookupWatch, dirPath: Path): DirectoryOfFailedLookupWatch {
if (!canWatchDirectory(dirPath)) {
watchPath.ignore = true;
}
return watchPath;
}
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
return { dir: rootDir, dirPath: rootPath };
@ -323,18 +355,15 @@ namespace ts {
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 most parent node_modules directory for watching
while (stringContains(dirPath, "/node_modules/")) {
dir = getDirectoryPath(dir);
dirPath = getDirectoryPath(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 };
// If the directory is node_modules use it to watch
if (isNodeModulesDirectory(dirPath)) {
return filterFSRootDirectoriesToWatch({ dir, dirPath }, getDirectoryPath(dirPath));
}
// Use some ancestor of the root directory
@ -349,7 +378,7 @@ namespace ts {
}
}
return { dir, dirPath };
return filterFSRootDirectoriesToWatch({ dir, dirPath }, dirPath);
}
function isPathWithDefaultFailedLookupExtension(path: Path) {
@ -390,13 +419,15 @@ namespace ts {
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 });
const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
if (!ignore) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
if (dirWatcher) {
dirWatcher.refCount++;
}
else {
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
}
}
}
}
@ -421,10 +452,12 @@ namespace ts {
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--;
const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
if (!ignore) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
// Do not close the watcher yet since it might be needed by other failed lookup locations.
dirWatcher.refCount--;
}
}
}
@ -576,7 +609,8 @@ namespace ts {
}
// 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 });
// But filter directories that are at root level to say directory doesnt exist, so that we arent watching them
const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory });
if (typeRoots) {
mutateMap(
typeRootsWatches,
@ -591,5 +625,16 @@ namespace ts {
closeTypeRootsWatch();
}
}
/**
* Use this function to return if directory exists to get type roots to watch
* If we return directory exists then only the paths will be added to type roots
* Hence return true for all directories except root directories which are filtered from watching
*/
function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) {
const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
const dirPath = resolutionHost.toPath(dir);
return dirPath === rootPath || canWatchDirectory(dirPath);
}
}
}

View File

@ -11,6 +11,11 @@ namespace ts {
return token >= SyntaxKind.Identifier;
}
/* @internal */
export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean {
return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token);
}
export interface Scanner {
getStartPos(): number;
getToken(): SyntaxKind;
@ -351,7 +356,7 @@ namespace ts {
/**
* We assume the first line starts at position 0 and 'position' is non-negative.
*/
export function computeLineAndCharacterOfPosition(lineStarts: ReadonlyArray<number>, position: number) {
export function computeLineAndCharacterOfPosition(lineStarts: ReadonlyArray<number>, position: number): LineAndCharacter {
let lineNumber = binarySearch(lineStarts, position);
if (lineNumber < 0) {
// If the actual position was not found,

View File

@ -122,7 +122,7 @@ namespace ts {
}
forEach(signature.typeParameters, visitType);
for (const parameter of signature.parameters){
for (const parameter of signature.parameters) {
visitSymbol(parameter);
}
visitType(getRestTypeOfSignature(signature));

View File

@ -131,7 +131,7 @@ namespace ts {
const _os = require("os");
const _crypto = require("crypto");
const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"];
const useNonPollingWatchers = process.env.TSC_NONPOLLING_WATCHER;
function createWatchedFileSet() {
const dirWatchers = createMap<DirectoryWatcher>();
@ -573,7 +573,7 @@ namespace ts {
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = directoryPath !== basePath && !sys.directoryExists(basePath);
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}

View File

@ -3676,7 +3676,8 @@ namespace ts {
// Do not do this in the global scope, as any variable we currently generate could conflict with
// variables from outside of the current compilation. In the future, we can revisit this behavior.
if (isExternalModule(currentSourceFile)) {
const tempVar = createTempVariable(recordTaggedTemplateString);
const tempVar = createUniqueName("templateObject");
recordTaggedTemplateString(tempVar);
templateArguments[0] = createLogicalOr(
tempVar,
createAssignment(

View File

@ -463,7 +463,7 @@ namespace ts {
);
// Mark this node as originally an async function
(generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody;
(generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope;
return createCall(
getHelperName("__awaiter"),

View File

@ -1112,7 +1112,7 @@ namespace ts {
}
function visitCallExpression(node: CallExpression) {
if (forEach(node.arguments, containsYield)) {
if (!isImportCall(node) && forEach(node.arguments, containsYield)) {
// [source]
// a.b(1, yield, 2);
//
@ -1123,7 +1123,6 @@ namespace ts {
// .yield resumeLabel
// .mark resumeLabel
// _b.apply(_a, _c.concat([%sent%, 2]));
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*cacheIdentifiers*/ true);
return setOriginalNode(
createFunctionApply(

View File

@ -41,6 +41,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ false);
case SyntaxKind.JsxFragment:
return visitJsxFragment(<JsxFragment>node, /*isChild*/ false);
case SyntaxKind.JsxExpression:
return visitJsxExpression(<JsxExpression>node);
@ -63,6 +66,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ true);
case SyntaxKind.JsxFragment:
return visitJsxFragment(<JsxFragment>node, /*isChild*/ true);
default:
Debug.failBadSyntaxKind(node);
return undefined;
@ -77,6 +83,10 @@ namespace ts {
return visitJsxOpeningLikeElement(node, /*children*/ undefined, isChild, /*location*/ node);
}
function visitJsxFragment(node: JsxFragment, isChild: boolean) {
return visitJsxOpeningFragment(node.openingFragment, node.children, isChild, /*location*/ node);
}
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
@ -126,6 +136,22 @@ namespace ts {
return element;
}
function visitJsxOpeningFragment(node: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const element = createExpressionForJsxFragment(
context.getEmitResolver().getJsxFactoryEntity(),
compilerOptions.reactNamespace,
mapDefined(children, transformJsxChildToExpression),
node,
location
);
if (isChild) {
startOnNewLine(element);
}
return element;
}
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
return visitNode(node.expression, visitor, isExpression);
}

View File

@ -21,7 +21,8 @@ namespace ts {
const {
startLexicalEnvironment,
endLexicalEnvironment
endLexicalEnvironment,
hoistVariableDeclaration
} = context;
const compilerOptions = context.getCompilerOptions();
@ -90,7 +91,7 @@ namespace ts {
startLexicalEnvironment();
const statements: Statement[] = [];
const ensureUseStrict = compilerOptions.alwaysStrict || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = addPrologue(statements, node.statements, ensureUseStrict, sourceElementVisitor);
if (shouldEmitUnderscoreUnderscoreESModule()) {
@ -519,18 +520,20 @@ namespace ts {
}
function visitImportCallExpression(node: ImportCall): Expression {
const argument = visitNode(firstOrUndefined(node.arguments), importCallExpressionVisitor);
const containsLexicalThis = !!(node.transformFlags & TransformFlags.ContainsLexicalThis);
switch (compilerOptions.module) {
case ModuleKind.AMD:
return transformImportCallExpressionAMD(node);
return createImportCallExpressionAMD(argument, containsLexicalThis);
case ModuleKind.UMD:
return transformImportCallExpressionUMD(node);
return createImportCallExpressionUMD(argument, containsLexicalThis);
case ModuleKind.CommonJS:
default:
return transformImportCallExpressionCommonJS(node);
return createImportCallExpressionCommonJS(argument, containsLexicalThis);
}
}
function transformImportCallExpressionUMD(node: ImportCall): Expression {
function createImportCallExpressionUMD(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
// (function (factory) {
// ... (regular UMD)
// }
@ -545,14 +548,25 @@ namespace ts {
// : new Promise(function (_a, _b) { require([x], _a, _b); }); /*Amd Require*/
// });
needUMDDynamicImportHelper = true;
return createConditional(
/*condition*/ createIdentifier("__syncRequire"),
/*whenTrue*/ transformImportCallExpressionCommonJS(node),
/*whenFalse*/ transformImportCallExpressionAMD(node)
);
if (isSimpleCopiableExpression(arg)) {
const argClone = isGeneratedIdentifier(arg) ? arg : isStringLiteral(arg) ? createLiteral(arg) : setEmitFlags(setTextRange(getSynthesizedClone(arg), arg), EmitFlags.NoComments);
return createConditional(
/*condition*/ createIdentifier("__syncRequire"),
/*whenTrue*/ createImportCallExpressionCommonJS(arg, containsLexicalThis),
/*whenFalse*/ createImportCallExpressionAMD(argClone, containsLexicalThis)
);
}
else {
const temp = createTempVariable(hoistVariableDeclaration);
return createComma(createAssignment(temp, arg), createConditional(
/*condition*/ createIdentifier("__syncRequire"),
/*whenTrue*/ createImportCallExpressionCommonJS(temp, containsLexicalThis),
/*whenFalse*/ createImportCallExpressionAMD(temp, containsLexicalThis)
));
}
}
function transformImportCallExpressionAMD(node: ImportCall): Expression {
function createImportCallExpressionAMD(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
// improt("./blah")
// emit as
// define(["require", "exports", "blah"], function (require, exports) {
@ -561,46 +575,89 @@ namespace ts {
// });
const resolve = createUniqueName("resolve");
const reject = createUniqueName("reject");
return createNew(
createIdentifier("Promise"),
/*typeArguments*/ undefined,
[createFunctionExpression(
const parameters = [
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ resolve),
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ reject)
];
const body = createBlock([
createStatement(
createCall(
createIdentifier("require"),
/*typeArguments*/ undefined,
[createArrayLiteral([arg || createOmittedExpression()]), resolve, reject]
)
)
]);
let func: FunctionExpression | ArrowFunction;
if (languageVersion >= ScriptTarget.ES2015) {
func = createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
parameters,
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
body);
}
else {
func = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
[createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ resolve),
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ reject)],
parameters,
/*type*/ undefined,
createBlock([createStatement(
createCall(
createIdentifier("require"),
/*typeArguments*/ undefined,
[createArrayLiteral([firstOrUndefined(node.arguments) || createOmittedExpression()]), resolve, reject]
))])
)]);
body);
// if there is a lexical 'this' in the import call arguments, ensure we indicate
// that this new function expression indicates it captures 'this' so that the
// es2015 transformer will properly substitute 'this' with '_this'.
if (containsLexicalThis) {
setEmitFlags(func, EmitFlags.CapturesThis);
}
}
return createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]);
}
function transformImportCallExpressionCommonJS(node: ImportCall): Expression {
function createImportCallExpressionCommonJS(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
// import("./blah")
// emit as
// Promise.resolve().then(function () { return require(x); }) /*CommonJs Require*/
// We have to wrap require in then callback so that require is done in asynchronously
// if we simply do require in resolve callback in Promise constructor. We will execute the loading immediately
return createCall(
createPropertyAccess(
createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []),
"then"),
/*typeArguments*/ undefined,
[createFunctionExpression(
const promiseResolveCall = createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []);
const requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []);
let func: FunctionExpression | ArrowFunction;
if (languageVersion >= ScriptTarget.ES2015) {
func = createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
requireCall);
}
else {
func = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
createBlock([createReturn(createCall(createIdentifier("require"), /*typeArguments*/ undefined, node.arguments))])
)]);
createBlock([createReturn(requireCall)]));
// if there is a lexical 'this' in the import call arguments, ensure we indicate
// that this new function expression indicates it captures 'this' so that the
// es2015 transformer will properly substitute 'this' with '_this'.
if (containsLexicalThis) {
setEmitFlags(func, EmitFlags.CapturesThis);
}
}
return createCall(createPropertyAccess(promiseResolveCall, "then"), /*typeArguments*/ undefined, [func]);
}
/**

View File

@ -225,7 +225,7 @@ namespace ts {
startLexicalEnvironment();
// Add any prologue directives.
const ensureUseStrict = compilerOptions.alwaysStrict || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = addPrologue(statements, node.statements, ensureUseStrict, sourceElementVisitor);
// var __moduleName = context_1 && context_1.id;
@ -826,7 +826,7 @@ namespace ts {
/*needsValue*/ false,
createAssignment
)
: createAssignment(node.name, visitNode(node.initializer, destructuringAndImportCallVisitor, isExpression));
: node.initializer ? createAssignment(node.name, visitNode(node.initializer, destructuringAndImportCallVisitor, isExpression)) : node.name;
}
/**
@ -1296,6 +1296,9 @@ namespace ts {
let expressions: Expression[];
for (const variable of node.declarations) {
expressions = append(expressions, transformInitializedVariable(variable, /*isExportedDeclaration*/ false));
if (!variable.initializer) {
hoistBindingElement(variable);
}
}
return expressions ? inlineExpressions(expressions) : createOmittedExpression();
@ -1495,7 +1498,7 @@ namespace ts {
createIdentifier("import")
),
/*typeArguments*/ undefined,
node.arguments
some(node.arguments) ? [visitNode(node.arguments[0], destructuringAndImportCallVisitor)] : []
);
}

View File

@ -26,7 +26,7 @@ namespace ts {
IsExportOfNamespace = 1 << 3,
IsNamedExternalExport = 1 << 4,
IsDefaultExternalExport = 1 << 5,
HasExtendsClause = 1 << 6,
IsDerivedClass = 1 << 6,
UseImmediatelyInvokedFunctionExpression = 1 << 7,
HasAnyDecorators = HasConstructorDecorators | HasMemberDecorators,
@ -45,6 +45,7 @@ namespace ts {
const resolver = context.getEmitResolver();
const compilerOptions = context.getCompilerOptions();
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
const languageVersion = getEmitScriptTarget(compilerOptions);
const moduleKind = getEmitModuleKind(compilerOptions);
@ -520,7 +521,7 @@ namespace ts {
}
function visitSourceFile(node: SourceFile) {
const alwaysStrict = (compilerOptions.alwaysStrict === undefined ? compilerOptions.strict : compilerOptions.alwaysStrict) &&
const alwaysStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") &&
!(isExternalModule(node) && moduleKind >= ModuleKind.ES2015);
return updateSourceFileNode(
node,
@ -553,7 +554,8 @@ namespace ts {
function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray<PropertyDeclaration>) {
let facts = ClassFacts.None;
if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties;
if (getClassExtendsHeritageClauseElement(node)) facts |= ClassFacts.HasExtendsClause;
const extendsClauseElement = getClassExtendsHeritageClauseElement(node);
if (extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword) facts |= ClassFacts.IsDerivedClass;
if (shouldEmitDecorateCallForClass(node)) facts |= ClassFacts.HasConstructorDecorators;
if (childIsDecorated(node)) facts |= ClassFacts.HasMemberDecorators;
if (isExportOfNamespace(node)) facts |= ClassFacts.IsExportOfNamespace;
@ -699,7 +701,7 @@ namespace ts {
name,
/*typeParameters*/ undefined,
visitNodes(node.heritageClauses, visitor, isHeritageClause),
transformClassMembers(node, (facts & ClassFacts.HasExtendsClause) !== 0)
transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0)
);
// To better align with the old emitter, we should not emit a trailing source map
@ -814,7 +816,7 @@ namespace ts {
// ${members}
// }
const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
const members = transformClassMembers(node, (facts & ClassFacts.HasExtendsClause) !== 0);
const members = transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0);
const classExpression = createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members);
setOriginalNode(classExpression, node);
setTextRange(classExpression, location);
@ -887,11 +889,11 @@ namespace ts {
* Transforms the members of a class.
*
* @param node The current class.
* @param hasExtendsClause A value indicating whether the class has an extends clause.
* @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
*/
function transformClassMembers(node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean) {
function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
const members: ClassElement[] = [];
const constructor = transformConstructor(node, hasExtendsClause);
const constructor = transformConstructor(node, isDerivedClass);
if (constructor) {
members.push(constructor);
}
@ -904,9 +906,9 @@ namespace ts {
* Transforms (or creates) a constructor for a class.
*
* @param node The current class.
* @param hasExtendsClause A value indicating whether the class has an extends clause.
* @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
*/
function transformConstructor(node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean) {
function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
// Check if we have property assignment inside class declaration.
// If there is a property assignment, we need to emit constructor whether users define it or not
// If there is no property assignment, we can omit constructor if users do not define it
@ -921,7 +923,7 @@ namespace ts {
}
const parameters = transformConstructorParameters(constructor);
const body = transformConstructorBody(node, constructor, hasExtendsClause);
const body = transformConstructorBody(node, constructor, isDerivedClass);
// constructor(${parameters}) {
// ${body}
@ -947,7 +949,6 @@ namespace ts {
* parameter property assignments or instance property initializers.
*
* @param constructor The constructor declaration.
* @param hasExtendsClause A value indicating whether the class has an extends clause.
*/
function transformConstructorParameters(constructor: ConstructorDeclaration) {
// The ES2015 spec specifies in 14.5.14. Runtime Semantics: ClassDefinitionEvaluation:
@ -975,9 +976,9 @@ namespace ts {
*
* @param node The current class.
* @param constructor The current class constructor.
* @param hasExtendsClause A value indicating whether the class has an extends clause.
* @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
*/
function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration, hasExtendsClause: boolean) {
function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration, isDerivedClass: boolean) {
let statements: Statement[] = [];
let indexOfFirstStatement = 0;
@ -1001,7 +1002,7 @@ namespace ts {
const propertyAssignments = getParametersWithPropertyAssignments(constructor);
addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment));
}
else if (hasExtendsClause) {
else if (isDerivedClass) {
// Add a synthetic `super` call:
//
// super(...arguments);
@ -1869,7 +1870,16 @@ namespace ts {
// Note when updating logic here also update getEntityNameForDecoratorMetadata
// so that aliases can be marked as referenced
let serializedUnion: SerializedTypeNode;
for (const typeNode of node.types) {
for (let typeNode of node.types) {
while (typeNode.kind === SyntaxKind.ParenthesizedType) {
typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be
}
if (typeNode.kind === SyntaxKind.NeverKeyword) {
continue; // Always elide `never` from the union/intersection if possible
}
if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) {
continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
}
const serializedIndividual = serializeTypeNode(typeNode);
if (isIdentifier(serializedIndividual) && serializedIndividual.escapedText === "Object") {
@ -1893,7 +1903,7 @@ namespace ts {
}
// If we were able to find common type, use it
return serializedUnion;
return serializedUnion || createVoidZero(); // Fallback is only hit if all union constituients are null/undefined/never
}
/**

View File

@ -178,4 +178,17 @@ namespace ts {
}
return values;
}
/**
* Used in the module transformer to check if an expression is reasonably without sideeffect,
* and thus better to copy into multiple places rather than to cache in a temporary variable
* - this is mostly subjective beyond the requirement that the expression not be sideeffecting
*/
export function isSimpleCopiableExpression(expression: Expression) {
return expression.kind === SyntaxKind.StringLiteral ||
expression.kind === SyntaxKind.NumericLiteral ||
expression.kind === SyntaxKind.NoSubstitutionTemplateLiteral ||
isKeyword(expression.kind) ||
isIdentifier(expression);
}
}

View File

@ -43,20 +43,12 @@ namespace ts {
return s;
}
function isJSONSupported() {
return typeof JSON === "object" && typeof JSON.parse === "function";
}
export function executeCommandLine(args: string[]): void {
const commandLine = parseCommandLine(args);
// 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"));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
}
@ -84,10 +76,6 @@ namespace ts {
}
if (commandLine.options.project) {
if (!isJSONSupported()) {
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));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
@ -109,7 +97,7 @@ namespace ts {
}
}
}
else if (commandLine.fileNames.length === 0 && isJSONSupported()) {
else if (commandLine.fileNames.length === 0) {
const searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath, sys.fileExists);
}

View File

@ -328,6 +328,9 @@ namespace ts {
JsxSelfClosingElement,
JsxOpeningElement,
JsxClosingElement,
JsxFragment,
JsxOpeningFragment,
JsxClosingFragment,
JsxAttribute,
JsxAttributes,
JsxSpreadAttribute,
@ -362,6 +365,7 @@ namespace ts {
JSDocFunctionType,
JSDocVariadicType,
JSDocComment,
JSDocTypeLiteral,
JSDocTag,
JSDocAugmentsTag,
JSDocClassTag,
@ -371,7 +375,6 @@ namespace ts {
JSDocTemplateTag,
JSDocTypedefTag,
JSDocPropertyTag,
JSDocTypeLiteral,
// Synthesized list
SyntaxList,
@ -413,9 +416,9 @@ namespace ts {
LastBinaryOperator = CaretEqualsToken,
FirstNode = QualifiedName,
FirstJSDocNode = JSDocTypeExpression,
LastJSDocNode = JSDocTypeLiteral,
LastJSDocNode = JSDocPropertyTag,
FirstJSDocTagNode = JSDocTag,
LastJSDocTagNode = JSDocTypeLiteral
LastJSDocTagNode = JSDocPropertyTag
}
export const enum NodeFlags {
@ -451,6 +454,7 @@ namespace ts {
/* @internal */
PossiblyContainsDynamicImport = 1 << 19,
JSDoc = 1 << 20, // If node was parsed inside jsdoc
/* @internal */ InWithStatement = 1 << 21, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
BlockScoped = Let | Const,
@ -458,7 +462,7 @@ namespace ts {
ReachabilityAndEmitFlags = ReachabilityCheckFlags | HasAsyncFunctions,
// Parsing context flags
ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile,
ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile | InWithStatement,
// Exclude these flags when parsing a Type
TypeExcludesFlags = YieldContext | AwaitContext,
@ -1055,6 +1059,7 @@ namespace ts {
export interface StringLiteral extends LiteralExpression {
kind: SyntaxKind.StringLiteral;
/* @internal */ textSourceNode?: Identifier | StringLiteral | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
/** Note: this is only set when synthesizing a node, not during parsing. */
/* @internal */ singleQuote?: boolean;
}
@ -1618,7 +1623,7 @@ namespace ts {
closingElement: JsxClosingElement;
}
/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
/// Either the opening tag in a <Tag>...</Tag> pair or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;
export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
@ -1644,6 +1649,26 @@ namespace ts {
attributes: JsxAttributes;
}
/// A JSX expression of the form <>...</>
export interface JsxFragment extends PrimaryExpression {
kind: SyntaxKind.JsxFragment;
openingFragment: JsxOpeningFragment;
children: NodeArray<JsxChild>;
closingFragment: JsxClosingFragment;
}
/// The opening element of a <>...</> JsxFragment
export interface JsxOpeningFragment extends Expression {
kind: SyntaxKind.JsxOpeningFragment;
parent?: JsxFragment;
}
/// The closing element of a <>...</> JsxFragment
export interface JsxClosingFragment extends Expression {
kind: SyntaxKind.JsxClosingFragment;
parent?: JsxFragment;
}
export interface JsxAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxAttribute;
parent?: JsxAttributes;
@ -1677,7 +1702,7 @@ namespace ts {
parent?: JsxElement;
}
export type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement;
export type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
export interface Statement extends Node {
_statementBrand: any;
@ -2159,6 +2184,10 @@ namespace ts {
kind: SyntaxKind.JSDocTag;
}
/**
* Note that `@extends` is a synonym of `@augments`.
* Both tags are represented by this interface.
*/
export interface JSDocAugmentsTag extends JSDocTag {
kind: SyntaxKind.JSDocAugmentsTag;
class: ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
@ -2525,8 +2554,6 @@ 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. */
/* @internal */ hasInvalidatedResolution: HasInvalidatedResolution;
}
/* @internal */
@ -2649,6 +2676,10 @@ namespace ts {
signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string;
typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string;
symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string;
/**
* @deprecated Use the createX factory functions or XToY typechecker methods and `createPrinter` or the `xToString` methods instead
* This will be removed in a future version.
*/
getSymbolDisplayBuilder(): SymbolDisplayBuilder;
getFullyQualifiedName(symbol: Symbol): string;
getAugmentedPropertiesOfType(type: Type): Symbol[];
@ -2689,6 +2720,24 @@ namespace ts {
getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
/* @internal */ getBaseConstraintOfType(type: Type): Type | undefined;
/* @internal */ getAnyType(): Type;
/* @internal */ getStringType(): Type;
/* @internal */ getNumberType(): Type;
/* @internal */ getBooleanType(): Type;
/* @internal */ getVoidType(): Type;
/* @internal */ getUndefinedType(): Type;
/* @internal */ getNullType(): Type;
/* @internal */ getESSymbolType(): Type;
/* @internal */ getNeverType(): Type;
/* @internal */ getUnionType(types: Type[], subtypeReduction?: boolean): Type;
/* @internal */ createArrayType(elementType: Type): Type;
/* @internal */ createPromiseType(type: Type): Type;
/* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo, numberIndexInfo: IndexInfo): Type;
/* @internal */ createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasLiteralTypes: boolean): Signature;
/* @internal */ createSymbol(flags: SymbolFlags, name: __String): TransientSymbol;
/* @internal */ createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo;
/* @internal */ isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
/* @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;
@ -3301,6 +3350,7 @@ namespace ts {
ObjectLiteral = 1 << 7, // Originates in an object literal
EvolvingArray = 1 << 8, // Evolving array type
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
ContainsSpread = 1 << 10, // Object literal contains spread operation
ClassOrInterface = Class | Interface
}
@ -3583,6 +3633,14 @@ namespace ts {
compareTypes: TypeComparer; // Type comparer function
}
/* @internal */
export interface WideningContext {
parent?: WideningContext; // Parent context
propertyName?: __String; // Name of property in parent
siblings?: Type[]; // Types of siblings
resolvedPropertyNames?: __String[]; // Property names occurring in sibling object literals
}
/* @internal */
export const enum SpecialPropertyAssignmentKind {
None,
@ -3601,6 +3659,7 @@ namespace ts {
export interface JsFileExtensionInfo {
extension: string;
isMixedContent: boolean;
scriptKind?: ScriptKind;
}
export interface DiagnosticMessage {
@ -3785,9 +3844,10 @@ namespace ts {
}
export interface LineAndCharacter {
/** 0-based. */
line: number;
/*
* This value denotes the character position in line and is different from the 'column' because of tab characters.
* 0-based. This value denotes the character position in line and is different from the 'column' because of tab characters.
*/
character: number;
}

View File

@ -520,6 +520,17 @@ namespace ts {
}
}
/* @internal */
export function isAnyImportSyntax(node: Node): node is AnyImportSyntax {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
return true;
default:
return false;
}
}
// Gets the nearest enclosing block scope container that has the provided node
// as a descendant, that is not the provided node.
export function getEnclosingBlockScopeContainer(node: Node): Node {
@ -571,14 +582,14 @@ namespace ts {
}
}
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
const sourceFile = getSourceFileOfNode(node);
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2);
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3);
}
export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
const span = getErrorSpanForNode(sourceFile, node);
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2);
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2, arg3);
}
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
@ -1121,7 +1132,7 @@ namespace ts {
}
/**
* Determines whether a node is a property or element access expression for super.
* Determines whether a node is a property or element access expression for `super`.
*/
export function isSuperProperty(node: Node): node is SuperProperty {
const kind = node.kind;
@ -1129,7 +1140,16 @@ namespace ts {
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
}
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
/**
* Determines whether a node is a property or element access expression for `this`.
*/
export function isThisProperty(node: Node): boolean {
const kind = node.kind;
return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression)
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.ThisKeyword;
}
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
switch (node.kind) {
case SyntaxKind.TypeReference:
return (<TypeReferenceNode>node).typeName;
@ -1217,7 +1237,7 @@ namespace ts {
return false;
}
export function isPartOfExpression(node: Node): boolean {
export function isExpressionNode(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.SuperKeyword:
case SyntaxKind.NullKeyword:
@ -1251,6 +1271,7 @@ namespace ts {
case SyntaxKind.OmittedExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.YieldExpression:
case SyntaxKind.AwaitExpression:
case SyntaxKind.MetaProperty:
@ -1320,7 +1341,7 @@ namespace ts {
case SyntaxKind.ExpressionWithTypeArguments:
return (<ExpressionWithTypeArguments>parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);
default:
return isPartOfExpression(parent);
return isExpressionNode(parent);
}
}
@ -1349,6 +1370,14 @@ namespace ts {
return node && !!(node.flags & NodeFlags.JSDoc);
}
export function isJSDocIndexSignature(node: TypeReferenceNode | ExpressionWithTypeArguments) {
return isTypeReferenceNode(node) &&
isIdentifier(node.typeName) &&
node.typeName.escapedText === "Object" &&
node.typeArguments && node.typeArguments.length === 2 &&
(node.typeArguments[0].kind === SyntaxKind.StringKeyword || node.typeArguments[0].kind === SyntaxKind.NumberKeyword);
}
/**
* Returns true if the node is a CallExpression to the identifier 'require' with
* exactly one argument (of the form 'require("name")').
@ -1375,6 +1404,10 @@ namespace ts {
return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote;
}
export function isStringDoubleQuoted(string: StringLiteral, sourceFile: SourceFile): boolean {
return getSourceTextOfNodeFromSourceFile(sourceFile, string).charCodeAt(0) === CharacterCodes.doubleQuote;
}
/**
* Returns true if the node is a variable declaration whose initializer is a function expression.
* This function does not test if the node is in a JavaScript file or not.
@ -1515,6 +1548,34 @@ namespace ts {
return getJSDocCommentsAndTags(node);
}
export function getSourceOfAssignment(node: Node): Node {
return isExpressionStatement(node) &&
node.expression && isBinaryExpression(node.expression) &&
node.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
node.expression.right;
}
export function getSingleInitializerOfVariableStatement(node: Node, child?: Node): Node {
return isVariableStatement(node) &&
node.declarationList.declarations.length > 0 &&
(!child || node.declarationList.declarations[0].initializer === child) &&
node.declarationList.declarations[0].initializer;
}
export function getSingleVariableOfVariableStatement(node: Node, child?: Node): Node {
return isVariableStatement(node) &&
node.declarationList.declarations.length > 0 &&
(!child || node.declarationList.declarations[0] === child) &&
node.declarationList.declarations[0];
}
export function getNestedModuleDeclaration(node: Node): Node {
return node.kind === SyntaxKind.ModuleDeclaration &&
(node as ModuleDeclaration).body &&
(node as ModuleDeclaration).body.kind === SyntaxKind.ModuleDeclaration &&
(node as ModuleDeclaration).body;
}
export function getJSDocCommentsAndTags(node: Node): (JSDoc | JSDocTag)[] {
let result: (JSDoc | JSDocTag)[] | undefined;
getJSDocCommentsAndTagsWorker(node);
@ -1528,35 +1589,15 @@ namespace ts {
// * @returns {number}
// */
// var x = function(name) { return name.length; }
const isInitializerOfVariableDeclarationInStatement =
isVariableLike(parent) &&
parent.initializer === node &&
parent.parent.parent.kind === SyntaxKind.VariableStatement;
const isVariableOfVariableDeclarationStatement = isVariableLike(node) &&
parent.parent.kind === SyntaxKind.VariableStatement;
const variableStatementNode =
isInitializerOfVariableDeclarationInStatement ? parent.parent.parent :
isVariableOfVariableDeclarationStatement ? parent.parent :
undefined;
if (variableStatementNode) {
getJSDocCommentsAndTagsWorker(variableStatementNode);
if (parent && (parent.kind === SyntaxKind.PropertyAssignment || getNestedModuleDeclaration(parent))) {
getJSDocCommentsAndTagsWorker(parent);
}
// Also recognize when the node is the RHS of an assignment expression
const isSourceOfAssignmentExpressionStatement =
parent && parent.parent &&
parent.kind === SyntaxKind.BinaryExpression &&
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
parent.parent.kind === SyntaxKind.ExpressionStatement;
if (isSourceOfAssignmentExpressionStatement) {
if (parent && parent.parent &&
(getSingleVariableOfVariableStatement(parent.parent, node) || getSourceOfAssignment(parent.parent))) {
getJSDocCommentsAndTagsWorker(parent.parent);
}
const isModuleDeclaration = node.kind === SyntaxKind.ModuleDeclaration &&
parent && parent.kind === SyntaxKind.ModuleDeclaration;
const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
if (isModuleDeclaration || isPropertyAssignmentExpression) {
getJSDocCommentsAndTagsWorker(parent);
if (parent && parent.parent && parent.parent.parent && getSingleInitializerOfVariableStatement(parent.parent.parent, node)) {
getJSDocCommentsAndTagsWorker(parent.parent.parent);
}
// Pull parameter comments from declaring function as well
@ -1583,13 +1624,16 @@ namespace ts {
return undefined;
}
const name = node.name.escapedText;
const func = getJSDocHost(node);
if (!isFunctionLike(func)) {
return undefined;
const host = getJSDocHost(node);
const decl = getSourceOfAssignment(host) ||
getSingleInitializerOfVariableStatement(host) ||
getSingleVariableOfVariableStatement(host) ||
getNestedModuleDeclaration(host) ||
host;
if (decl && isFunctionLike(decl)) {
const parameter = find(decl.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name);
return parameter && parameter.symbol;
}
const parameter = find(func.parameters, p =>
p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name);
return parameter && parameter.symbol;
}
export function getJSDocHost(node: JSDocTag): HasJSDoc {
@ -2136,6 +2180,7 @@ namespace ts {
case SyntaxKind.ClassExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.RegularExpressionLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateExpression:
@ -2700,11 +2745,11 @@ namespace ts {
* Gets the effective type annotation of a variable, parameter, or property. If the node was
* parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode | undefined {
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration, checkJSDoc?: boolean): TypeNode | undefined {
if (node.type) {
return node.type;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
return getJSDocType(node);
}
}
@ -2713,11 +2758,11 @@ namespace ts {
* Gets the effective return type annotation of a signature. If the node was parsed in a
* JavaScript file, gets the return type annotation from JSDoc.
*/
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode | undefined {
export function getEffectiveReturnTypeNode(node: SignatureDeclaration, checkJSDoc?: boolean): TypeNode | undefined {
if (node.type) {
return node.type;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
return getJSDocReturnType(node);
}
}
@ -2726,11 +2771,11 @@ namespace ts {
* Gets the effective type parameters. If the node was parsed in a
* JavaScript file, gets the type parameters from the `@template` tag from JSDoc.
*/
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters, checkJSDoc?: boolean): ReadonlyArray<TypeParameterDeclaration> {
if (node.typeParameters) {
return node.typeParameters;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
const templateTag = getJSDocTemplateTag(node);
return templateTag && templateTag.typeParameters;
}
@ -2740,9 +2785,9 @@ namespace ts {
* Gets the effective type annotation of the value parameter of a set accessor. If the node
* was parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode {
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration, checkJSDoc?: boolean): TypeNode {
const parameter = getSetAccessorValueParameter(node);
return parameter && getEffectiveTypeAnnotationNode(parameter);
return parameter && getEffectiveTypeAnnotationNode(parameter, checkJSDoc);
}
export function emitNewLineBeforeLeadingComments(lineMap: ReadonlyArray<number>, writer: EmitTextWriter, node: TextRange, leadingComments: ReadonlyArray<CommentRange>) {
@ -3184,7 +3229,7 @@ namespace ts {
const carriageReturnLineFeed = "\r\n";
const lineFeed = "\n";
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: System): string {
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: { newLine: string }): string {
switch (options.newLine) {
case NewLineKind.CarriageReturnLineFeed:
return carriageReturnLineFeed;
@ -4241,6 +4286,12 @@ namespace ts {
return find(tags, doc => doc.kind === kind);
}
/** Gets all JSDoc tags of a specified kind, or undefined if not present. */
export function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray<JSDocTag> | undefined {
const tags = getJSDocTags(node);
return filter(tags, doc => doc.kind === kind);
}
}
// Simple node tests of the form `node.kind === SyntaxKind.Foo`.
@ -4754,6 +4805,18 @@ namespace ts {
return node.kind === SyntaxKind.JsxClosingElement;
}
export function isJsxFragment(node: Node): node is JsxFragment {
return node.kind === SyntaxKind.JsxFragment;
}
export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment {
return node.kind === SyntaxKind.JsxOpeningFragment;
}
export function isJsxClosingFragment(node: Node): node is JsxClosingFragment {
return node.kind === SyntaxKind.JsxClosingFragment;
}
export function isJsxAttribute(node: Node): node is JsxAttribute {
return node.kind === SyntaxKind.JsxAttribute;
}
@ -5124,7 +5187,14 @@ namespace ts {
|| kind === SyntaxKind.UndefinedKeyword
|| kind === SyntaxKind.NullKeyword
|| kind === SyntaxKind.NeverKeyword
|| kind === SyntaxKind.ExpressionWithTypeArguments;
|| kind === SyntaxKind.ExpressionWithTypeArguments
|| kind === SyntaxKind.JSDocAllType
|| kind === SyntaxKind.JSDocUnknownType
|| kind === SyntaxKind.JSDocNullableType
|| kind === SyntaxKind.JSDocNonNullableType
|| kind === SyntaxKind.JSDocOptionalType
|| kind === SyntaxKind.JSDocFunctionType
|| kind === SyntaxKind.JSDocVariadicType;
}
/**
@ -5272,6 +5342,7 @@ namespace ts {
case SyntaxKind.CallExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.ParenthesizedExpression:
@ -5334,7 +5405,7 @@ namespace ts {
/* @internal */
/**
* Determines whether a node is an expression based only on its kind.
* Use `isPartOfExpression` if not in transforms.
* Use `isExpressionNode` if not in transforms.
*/
export function isExpression(node: Node): node is Expression {
return isExpressionKind(skipPartiallyEmittedExpressions(node).kind);
@ -5593,7 +5664,8 @@ namespace ts {
return kind === SyntaxKind.JsxElement
|| kind === SyntaxKind.JsxExpression
|| kind === SyntaxKind.JsxSelfClosingElement
|| kind === SyntaxKind.JsxText;
|| kind === SyntaxKind.JsxText
|| kind === SyntaxKind.JsxFragment;
}
/* @internal */
@ -5643,6 +5715,14 @@ namespace ts {
return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
}
export function isSetAccessor(node: Node): node is SetAccessorDeclaration {
return node.kind === SyntaxKind.SetAccessor;
}
export function isGetAccessor(node: Node): node is GetAccessorDeclaration {
return node.kind === SyntaxKind.GetAccessor;
}
/** True if has jsdoc nodes attached to it. */
/* @internal */
export function hasJSDocNodes(node: Node): node is HasJSDoc {

View File

@ -819,6 +819,12 @@ namespace ts {
return updateJsxClosingElement(<JsxClosingElement>node,
visitNode((<JsxClosingElement>node).tagName, visitor, isJsxTagNameExpression));
case SyntaxKind.JsxFragment:
return updateJsxFragment(<JsxFragment>node,
visitNode((<JsxFragment>node).openingFragment, visitor, isJsxOpeningFragment),
nodesVisitor((<JsxFragment>node).children, visitor, isJsxChild),
visitNode((<JsxFragment>node).closingFragment, visitor, isJsxClosingFragment));
case SyntaxKind.JsxAttribute:
return updateJsxAttribute(<JsxAttribute>node,
visitNode((<JsxAttribute>node).name, visitor, isIdentifier),
@ -1334,6 +1340,12 @@ namespace ts {
result = reduceNode((<JsxElement>node).closingElement, cbNode, result);
break;
case SyntaxKind.JsxFragment:
result = reduceNode((<JsxFragment>node).openingFragment, cbNode, result);
result = reduceLeft((<JsxFragment>node).children, cbNode, result);
result = reduceNode((<JsxFragment>node).closingFragment, cbNode, result);
break;
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
result = reduceNode((<JsxSelfClosingElement | JsxOpeningElement>node).tagName, cbNode, result);

View File

@ -144,13 +144,14 @@ namespace ts {
function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) {
// First get and report any syntactic errors.
let diagnostics = program.getSyntacticDiagnostics().slice();
const 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());
addRange(diagnostics, program.getOptionsDiagnostics());
addRange(diagnostics, program.getGlobalDiagnostics());
if (diagnostics.length === 0) {
reportSemanticDiagnostics = true;
@ -162,7 +163,7 @@ namespace ts {
let sourceMaps: SourceMapData[];
let emitSkipped: boolean;
const result = builder.emitChangedFiles(program);
const result = builder.emitChangedFiles(program, writeFile);
if (result.length === 0) {
emitSkipped = true;
}
@ -171,14 +172,13 @@ namespace ts {
if (emitOutput.emitSkipped) {
emitSkipped = true;
}
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
addRange(diagnostics, emitOutput.diagnostics);
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
writeOutputFiles(emitOutput.outputFiles);
}
}
if (reportSemanticDiagnostics) {
diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program));
addRange(diagnostics, builder.getSemanticDiagnostics(program));
}
return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped,
diagnostics, reportDiagnostic);
@ -191,31 +191,23 @@ namespace ts {
}
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
try {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
host.writeFile(fileName, data, writeByteOrderMark);
host.writeFile(fileName, text, writeByteOrderMark);
performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
if (emittedFiles) {
emittedFiles.push(fileName);
}
}
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);
}
if (onError) {
onError(e.message);
}
}
}
@ -249,10 +241,10 @@ namespace ts {
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;
const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(system.newLine); } : noop;
const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher;
const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher;
const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher;
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
@ -308,7 +300,7 @@ namespace ts {
getCurrentDirectory()
);
// There is no extra check needed since we can just rely on the program to decide emit
const builder = createBuilder({ getCanonicalFileName, getEmitOutput: getFileEmitOutput, computeHash, shouldEmitFile: () => true });
const builder = createBuilder({ getCanonicalFileName, computeHash });
synchronizeProgram();
@ -322,6 +314,9 @@ namespace ts {
if (hasChangedCompilerOptions) {
newLine = getNewLineCharacter(compilerOptions, system);
if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) {
resolutionCache.clear();
}
}
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
@ -329,14 +324,11 @@ namespace ts {
return;
}
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
resolutionCache.clear();
}
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
hasChangedCompilerOptions = false;
beforeCompile(compilerOptions);
// Compile the program
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
hasChangedCompilerOptions = false;
resolutionCache.startCachingPerDirectoryResolution();
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
@ -605,8 +597,17 @@ namespace ts {
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existance changed, update the sourceFiles cache
(directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
removeSourceFile(fileOrDirectoryPath);
const result = (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
// Instead of deleting the file, mark it as changed instead
// Many times node calls add/remove/file when watching directories recursively
const hostSourceFile = sourceFilesCache.get(fileOrDirectoryPath);
if (hostSourceFile && !isString(hostSourceFile) && (result ? result.fileExists : directoryStructureHost.fileExists(fileOrDirectory))) {
hostSourceFile.version++;
}
else {
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

View File

@ -1,5 +1,6 @@
/// <reference path="core.ts" />
/* @internal */
namespace ts {
/**
* Updates the existing missing file watches with the new set of missing files after new program is created
@ -72,17 +73,19 @@ namespace ts {
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);
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb);
}
export function addFileWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb);
}
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
@ -92,7 +95,12 @@ namespace ts {
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);
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb, path);
}
export function addFilePathWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb, path);
}
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
@ -102,14 +110,21 @@ namespace ts {
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);
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, directory, cb, flags);
}
export function addDirectoryWatcherWithOnlyTriggerLogging(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, /*logOnlyTrigger*/ true, 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 {
function createWatcherWithLogging<T, U>(addWatch: AddWatch<T, U>, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: System, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
const info = `PathInfo: ${file}`;
log(`${watcherCaption}Added: ${info}`);
if (!logOnlyTrigger) {
log(`${watcherCaption}Added: ${info}`);
}
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
@ -120,7 +135,9 @@ namespace ts {
}, optional);
return {
close: () => {
log(`${watcherCaption}Close: ${info}`);
if (!logOnlyTrigger) {
log(`${watcherCaption}Close: ${info}`);
}
watcher.close();
}
};

View File

@ -48,7 +48,7 @@ class CompilerBaselineRunner extends RunnerBase {
}
public checkTestCodeOutput(fileName: string) {
describe("compiler tests for " + fileName, () => {
describe(`${this.testSuiteName} tests for ${fileName}`, () => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Everything declared here should be cleared out in the "after" callback.
let justName: string;
@ -80,9 +80,9 @@ class CompilerBaselineRunner extends RunnerBase {
tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
}
else {
const baseUrl = harnessSettings["baseUrl"];
const baseUrl = harnessSettings.baseUrl;
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
harnessSettings["baseUrl"] = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
}
}
@ -94,7 +94,7 @@ class CompilerBaselineRunner extends RunnerBase {
toBeCompiled = [];
otherFiles = [];
if (testCaseContent.settings["noImplicitReferences"] || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
if (testCaseContent.settings.noImplicitReferences || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
toBeCompiled.push(this.createHarnessTestFile(lastUnit, rootDir));
units.forEach(unit => {
if (unit.name !== lastUnit.name) {
@ -114,7 +114,7 @@ class CompilerBaselineRunner extends RunnerBase {
}
const output = Harness.Compiler.compileFiles(
toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ harnessSettings["currentDirectory"]);
toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ harnessSettings.currentDirectory);
options = output.options;
result = output.result;

View File

@ -314,7 +314,7 @@ namespace FourSlash {
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
this.languageService = languageServiceAdapter.getLanguageService();
this.languageService = memoWrap(languageServiceAdapter.getLanguageService(), this); // Wrap the LS to cache some expensive operations certain tests call repeatedly
if (startResolveFileRef) {
// Add the entry-point file itself into the languageServiceShimHost
@ -381,6 +381,39 @@ namespace FourSlash {
// Open the first file by default
this.openFile(0);
function memoWrap(ls: ts.LanguageService, target: TestState): ts.LanguageService {
const cacheableMembers: (keyof typeof ls)[] = [
"getCompletionsAtPosition",
"getCompletionEntryDetails",
"getCompletionEntrySymbol",
"getQuickInfoAtPosition",
"getSignatureHelpItems",
"getReferencesAtPosition",
"getDocumentHighlights",
];
const proxy = {} as ts.LanguageService;
for (const k in ls) {
const key = k as keyof typeof ls;
if (cacheableMembers.indexOf(key) === -1) {
proxy[key] = (...args: any[]) => (ls[key] as Function)(...args);
continue;
}
const memo = Utils.memoize(
(_version: number, _active: string, _caret: number, _selectEnd: number, _marker: string, ...args: any[]) => (ls[key] as Function)(...args),
(...args) => args.join("|,|")
);
proxy[key] = (...args: any[]) => memo(
target.languageServiceAdapterHost.getScriptInfo(target.activeFile.fileName).version,
target.activeFile.fileName,
target.currentCaretPosition,
target.selectionEnd,
target.lastKnownMarker,
...args
);
}
return proxy;
}
}
private getFileContent(fileName: string): string {
@ -783,13 +816,13 @@ namespace FourSlash {
});
}
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
const completions = this.getCompletionListAtCaret();
if (completions) {
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex);
this.assertItemInCompletionList(completions.entries, entryId, text, documentation, kind, spanIndex, hasAction);
}
else {
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${JSON.stringify(entryId)}'.`);
}
}
@ -804,7 +837,7 @@ namespace FourSlash {
* @param expectedKind the kind of symbol (see ScriptElementKind)
* @param spanIndex the index of the range that the completion item's replacement text span should match
*/
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
const that = this;
let replacementSpan: ts.TextSpan;
if (spanIndex !== undefined) {
@ -833,14 +866,14 @@ namespace FourSlash {
const completions = this.getCompletionListAtCaret();
if (completions) {
let filterCompletions = completions.entries.filter(e => e.name === symbol);
let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source);
filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions;
filterCompletions = filterCompletions.filter(filterByTextOrDocumentation);
if (filterCompletions.length !== 0) {
// After filtered using all present criterion, if there are still symbol left in the list
// then these symbols must meet the criterion for Not supposed to be in the list. So we
// raise an error
let error = "Completion list did contain \'" + symbol + "\'.";
let error = `Completion list did contain '${JSON.stringify(entryId)}\'.`;
const details = this.getCompletionEntryDetails(filterCompletions[0].name);
if (expectedText) {
error += "Expected text: " + expectedText + " to equal: " + ts.displayPartsToString(details.displayParts) + ".";
@ -953,6 +986,10 @@ namespace FourSlash {
return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace);
}
public setTypesRegistry(map: ts.MapLike<void>): void {
this.languageServiceAdapterHost.typesRegistry = ts.createMapFromTemplate(map);
}
public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void {
const node = this.goToAndGetNode(range);
const checker = this.getChecker();
@ -1126,8 +1163,8 @@ Actual: ${stringify(fullActual)}`);
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
}
private getCompletionEntryDetails(entryName: string) {
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName);
private getCompletionEntryDetails(entryName: string, source?: string) {
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source);
}
private getReferencesAtCaret() {
@ -1636,7 +1673,7 @@ Actual: ${stringify(fullActual)}`);
const longestNameLength = max(entries, m => m.name.length);
const longestKindLength = max(entries, m => m.kind.length);
entries.sort((m, n) => m.sortText > n.sortText ? 1 : m.sortText < n.sortText ? -1 : m.name > n.name ? 1 : m.name < n.name ? -1 : 0);
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers}`).join("\n");
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers} ${m.source === undefined ? "" : m.source}`).join("\n");
Harness.IO.log(membersString);
}
@ -2289,6 +2326,29 @@ Actual: ${stringify(fullActual)}`);
this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index);
}
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
this.goToMarker(markerName);
const actualCompletion = this.getCompletionListAtCaret().entries.find(e => e.name === options.name && e.source === options.source);
if (!actualCompletion.hasAction) {
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
}
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source);
if (details.codeActions.length !== 1) {
this.raiseError(`Expected one code action, got ${details.codeActions.length}`);
}
if (details.codeActions[0].description !== options.description) {
this.raiseError(`Expected description to be:\n${options.description}\ngot:\n${details.codeActions[0].description}`);
}
this.applyCodeActions(details.codeActions);
this.verifyNewContent(options);
}
public verifyRangeIs(expectedText: string, includeWhiteSpace?: boolean) {
const ranges = this.getRanges();
if (ranges.length !== 1) {
@ -2360,6 +2420,10 @@ Actual: ${stringify(fullActual)}`);
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
}
this.verifyNewContent(options);
}
private verifyNewContent(options: FourSlashInterface.NewContentOptions) {
if (options.newFileContent) {
assert(!options.newRangeContent);
this.verifyCurrentFileContent(options.newFileContent);
@ -2381,7 +2445,7 @@ Actual: ${stringify(fullActual)}`);
}));
return ts.flatMap(ts.deduplicate(diagnosticsForCodeFix, ts.equalOwnProperties), diagnostic => {
if (errorCode && errorCode !== diagnostic.code) {
if (errorCode !== undefined && errorCode !== diagnostic.code) {
return;
}
@ -2750,16 +2814,26 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyCodeFixAvailable(negative: boolean) {
const codeFix = this.getCodeFixActions(this.activeFile.fileName);
public verifyCodeFixAvailable(negative: boolean, info: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined) {
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
if (negative && codeFix.length) {
this.raiseError(`verifyCodeFixAvailable failed - expected no fixes but found one.`);
if (negative) {
if (codeFixes.length) {
this.raiseError(`verifyCodeFixAvailable failed - expected no fixes but found one.`);
}
return;
}
if (!(negative || codeFix.length)) {
if (!codeFixes.length) {
this.raiseError(`verifyCodeFixAvailable failed - expected code fixes but none found.`);
}
if (info) {
assert.equal(info.length, codeFixes.length);
ts.zipWith(codeFixes, info, (fix, info) => {
assert.equal(fix.description, info.description);
this.assertObjectsEqual(fix.commands, info.commands);
});
}
}
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
@ -2803,6 +2877,14 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) {
const selection = this.getSelection();
const actualRefactors = (this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || ts.emptyArray)
.filter(r => r.name === name && r.actions.some(a => a.name === actionName));
this.assertObjectsEqual(actualRefactors, refactors);
}
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
const ranges = this.getRanges();
if (!(ranges && ranges.length === 1)) {
@ -2822,14 +2904,14 @@ Actual: ${stringify(fullActual)}`);
public applyRefactor({ refactorName, actionName, actionDescription, newContent: newContentWithRenameMarker }: FourSlashInterface.ApplyRefactorOptions) {
const range = this.getSelection();
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
const refactor = refactors.find(r => r.name === refactorName);
if (!refactor) {
const refactorsWithName = refactors.filter(r => r.name === refactorName);
if (refactorsWithName.length === 0) {
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.\nAvailable refactors: ${refactors.map(r => r.name)}`);
}
const action = refactor.actions.find(a => a.name === actionName);
const action = ts.firstDefined(refactorsWithName, refactor => refactor.actions.find(a => a.name === actionName));
if (!action) {
this.raiseError(`The expected action: ${action} is not included in: ${refactor.actions.map(a => a.name)}`);
this.raiseError(`The expected action: ${actionName} is not included in: ${ts.flatMap(refactorsWithName, r => r.actions.map(a => a.name))}`);
}
if (action.description !== actionDescription) {
this.raiseError(`Expected action description to be ${JSON.stringify(actionDescription)}, got: ${JSON.stringify(action.description)}`);
@ -2933,36 +3015,48 @@ Actual: ${stringify(fullActual)}`);
return text.substring(startPos, endPos);
}
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
private assertItemInCompletionList(
items: ts.CompletionEntry[],
entryId: ts.Completions.CompletionEntryIdentifier,
text: string | undefined,
documentation: string | undefined,
kind: string | undefined,
spanIndex: number | undefined,
hasAction: boolean | undefined,
) {
for (const item of items) {
if (item.name === name) {
if (documentation !== undefined || text !== undefined) {
const details = this.getCompletionEntryDetails(item.name);
if (item.name === entryId.name && item.source === entryId.source) {
if (documentation !== undefined || text !== undefined || entryId.source !== undefined) {
const details = this.getCompletionEntryDetails(item.name, item.source);
if (documentation !== undefined) {
assert.equal(ts.displayPartsToString(details.documentation), documentation, this.assertionMessageAtLastKnownMarker("completion item documentation for " + name));
assert.equal(ts.displayPartsToString(details.documentation), documentation, this.assertionMessageAtLastKnownMarker("completion item documentation for " + entryId));
}
if (text !== undefined) {
assert.equal(ts.displayPartsToString(details.displayParts), text, this.assertionMessageAtLastKnownMarker("completion item detail text for " + name));
assert.equal(ts.displayPartsToString(details.displayParts), text, this.assertionMessageAtLastKnownMarker("completion item detail text for " + entryId));
}
assert.deepEqual(details.source, entryId.source === undefined ? undefined : [ts.textPart(entryId.source)]);
}
if (kind !== undefined) {
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name));
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId));
}
if (spanIndex !== undefined) {
const span = this.getTextSpanForRangeAtIndex(spanIndex);
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId));
}
assert.equal(item.hasAction, hasAction);
return;
}
}
const itemsString = items.map(item => stringify({ name: item.name, kind: item.kind })).join(",\n");
this.raiseError(`Expected "${stringify({ name, text, documentation, kind })}" to be in list [${itemsString}]`);
this.raiseError(`Expected "${stringify({ entryId, text, documentation, kind })}" to be in list [${itemsString}]`);
}
private findFile(indexOrName: any) {
@ -3222,7 +3316,7 @@ ${code}
}
function containTSConfigJson(files: FourSlashFile[]): boolean {
return ts.forEach(files, f => f.fileOptions["Filename"] === "tsconfig.json");
return ts.forEach(files, f => f.fileOptions.Filename === "tsconfig.json");
}
function getNonFileNameOptionInFileList(files: FourSlashFile[]): string {
@ -3577,6 +3671,10 @@ namespace FourSlashInterface {
public symbolsInScope(range: FourSlash.Range): ts.Symbol[] {
return this.state.symbolsInScope(range);
}
public setTypesRegistry(map: ts.MapLike<void>): void {
this.state.setTypesRegistry(map);
}
}
export class GoTo {
@ -3669,12 +3767,15 @@ namespace FourSlashInterface {
// Verifies the completion list contains the specified symbol. The
// completion list is brought up if necessary
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
if (typeof entryId === "string") {
entryId = { name: entryId, source: undefined };
}
if (this.negative) {
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
this.state.verifyCompletionListDoesNotContain(entryId, text, documentation, kind, spanIndex);
}
else {
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex);
this.state.verifyCompletionListContains(entryId, text, documentation, kind, spanIndex, hasAction);
}
}
@ -3752,8 +3853,8 @@ namespace FourSlashInterface {
this.state.verifyCodeFix(options);
}
public codeFixAvailable() {
this.state.verifyCodeFixAvailable(this.negative);
public codeFixAvailable(options?: VerifyCodeFixAvailableOptions[]) {
this.state.verifyCodeFixAvailable(this.negative, options);
}
public applicableRefactorAvailableAtMarker(markerName: string) {
@ -3764,6 +3865,10 @@ namespace FourSlashInterface {
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
}
public refactor(options: VerifyRefactorOptions) {
this.state.verifyRefactor(options);
}
public refactorAvailable(name: string, actionName?: string) {
this.state.verifyRefactorAvailable(this.negative, name, actionName);
}
@ -3999,6 +4104,10 @@ namespace FourSlashInterface {
this.state.getAndApplyCodeActions(errorCode, index);
}
public applyCodeActionFromCompletion(markerName: string, options: VerifyCompletionActionOptions): void {
this.state.applyCodeActionFromCompletion(markerName, options);
}
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
}
@ -4396,12 +4505,32 @@ namespace FourSlashInterface {
isNewIdentifierLocation?: boolean;
}
export interface VerifyCodeFixOptions {
description: string;
// One of these should be defined.
export interface NewContentOptions {
// Exactly one of these should be defined.
newFileContent?: string;
newRangeContent?: string;
}
export interface VerifyCodeFixOptions extends NewContentOptions {
description: string;
errorCode?: number;
index?: number;
}
export interface VerifyCodeFixAvailableOptions {
description: string;
commands?: ts.CodeActionCommand[];
}
export interface VerifyRefactorOptions {
name: string;
actionName: string;
refactors: ts.ApplicableRefactorInfo[];
}
export interface VerifyCompletionActionOptions extends NewContentOptions {
name: string;
source?: string;
description: string;
}
}

View File

@ -32,6 +32,9 @@
// this will work in the browser via browserify
var _chai: typeof chai = require("chai");
var assert: typeof _chai.assert = _chai.assert;
// chai's builtin `assert.isFalse` is featureful but slow - we don't use those features,
// so we'll just overwrite it as an alterative to migrating a bunch of code off of chai
assert.isFalse = (expr, msg) => { if (expr as any as boolean !== false) throw new Error(msg); };
declare var __dirname: string; // Node-specific
var global: NodeJS.Global = <any>Function("return this").call(undefined);
@ -844,9 +847,7 @@ namespace Harness {
export const es2015DefaultLibFileName = "lib.es2015.d.ts";
// Cache of lib files from "built/local"
const libFileNameSourceFileMap = ts.createMapFromTemplate<ts.SourceFile>({
[defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest)
});
let libFileNameSourceFileMap: ts.Map<ts.SourceFile> | undefined;
// Cache of lib files from "tests/lib/"
const testLibFileNameSourceFileMap = ts.createMap<ts.SourceFile>();
@ -857,6 +858,12 @@ namespace Harness {
return undefined;
}
if (!libFileNameSourceFileMap) {
libFileNameSourceFileMap = ts.createMapFromTemplate({
[defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest)
});
}
let sourceFile = libFileNameSourceFileMap.get(fileName);
if (!sourceFile) {
libFileNameSourceFileMap.set(fileName, sourceFile = createSourceFileAndAssertInvariants(fileName, IO.readFile(libFolder + fileName), ts.ScriptTarget.Latest));
@ -912,8 +919,8 @@ namespace Harness {
if (file.content !== undefined) {
const fileName = ts.normalizePath(file.unitName);
const path = ts.toPath(file.unitName, currentDirectory, getCanonicalFileName);
if (file.fileOptions && file.fileOptions["symlink"]) {
const links = file.fileOptions["symlink"].split(",");
if (file.fileOptions && file.fileOptions.symlink) {
const links = file.fileOptions.symlink.split(",");
for (const link of links) {
const linkPath = ts.toPath(link, currentDirectory, getCanonicalFileName);
realPathMap.set(linkPath, fileName);
@ -1192,6 +1199,9 @@ namespace Harness {
traceResults = [];
compilerHost.trace = text => traceResults.push(text);
}
else {
compilerHost.directoryExists = () => true; // This only visibly affects resolution traces, so to save time we always return true where possible
}
const program = ts.createProgram(programFileNames, options, compilerHost);
const emitResult = program.emit();
@ -1228,7 +1238,7 @@ namespace Harness {
if (options.declaration && result.errors.length === 0 && result.declFilesCode.length > 0) {
ts.forEach(inputFiles, file => addDtsFile(file, declInputFiles));
ts.forEach(otherFiles, file => addDtsFile(file, declOtherFiles));
return { declInputFiles, declOtherFiles, harnessSettings, options, currentDirectory: currentDirectory || harnessSettings["currentDirectory"] };
return { declInputFiles, declOtherFiles, harnessSettings, options, currentDirectory: currentDirectory || harnessSettings.currentDirectory };
}
function addDtsFile(file: TestFile, dtsFiles: TestFile[]) {
@ -1452,7 +1462,7 @@ namespace Harness {
});
}
export function doTypeAndSymbolBaseline(baselinePath: string, program: ts.Program, allFiles: {unitName: string, content: string}[], opts?: Harness.Baseline.BaselineOptions, multifile?: boolean, skipTypeAndSymbolbaselines?: boolean) {
export function doTypeAndSymbolBaseline(baselinePath: string, program: ts.Program, allFiles: {unitName: string, content: string}[], opts?: Harness.Baseline.BaselineOptions, multifile?: boolean, skipTypeBaselines?: boolean, skipSymbolBaselines?: boolean) {
// The full walker simulates the types that you would get from doing a full
// compile. The pull walker simulates the types you get when you just do
// a type query for a random node (like how the LS would do it). Most of the
@ -1510,19 +1520,19 @@ namespace Harness {
baselinePath.replace(/\.tsx?/, "") : baselinePath;
if (!multifile) {
const fullBaseLine = generateBaseLine(isSymbolBaseLine, skipTypeAndSymbolbaselines);
const fullBaseLine = generateBaseLine(isSymbolBaseLine, isSymbolBaseLine ? skipSymbolBaselines : skipTypeBaselines);
Harness.Baseline.runBaseline(outputFileName + fullExtension, () => fullBaseLine, opts);
}
else {
Harness.Baseline.runMultifileBaseline(outputFileName, fullExtension, () => {
return iterateBaseLine(isSymbolBaseLine, skipTypeAndSymbolbaselines);
return iterateBaseLine(isSymbolBaseLine, isSymbolBaseLine ? skipSymbolBaselines : skipTypeBaselines);
}, opts);
}
}
function generateBaseLine(isSymbolBaseline: boolean, skipTypeAndSymbolbaselines?: boolean): string {
function generateBaseLine(isSymbolBaseline: boolean, skipBaseline?: boolean): string {
let result = "";
const gen = iterateBaseLine(isSymbolBaseline, skipTypeAndSymbolbaselines);
const gen = iterateBaseLine(isSymbolBaseline, skipBaseline);
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
const [, content] = value;
result += content;
@ -1532,8 +1542,8 @@ namespace Harness {
/* tslint:enable:no-null-keyword */
}
function *iterateBaseLine(isSymbolBaseline: boolean, skipTypeAndSymbolbaselines?: boolean): IterableIterator<[string, string]> {
if (skipTypeAndSymbolbaselines) {
function *iterateBaseLine(isSymbolBaseline: boolean, skipBaseline?: boolean): IterableIterator<[string, string]> {
if (skipBaseline) {
return;
}
const dupeCase = ts.createMap<number>();
@ -1672,7 +1682,7 @@ namespace Harness {
}
function fileOutput(file: GeneratedFile, harnessSettings: Harness.TestCaseParser.CompilerSettings): string {
const fileName = harnessSettings["fullEmitPaths"] ? file.fileName : ts.getBaseFileName(file.fileName);
const fileName = harnessSettings.fullEmitPaths ? file.fileName : ts.getBaseFileName(file.fileName);
return "//// [" + fileName + "]\r\n" + getByteOrderMarkText(file) + file.code;
}

View File

@ -123,6 +123,7 @@ namespace Harness.LanguageService {
}
export class LanguageServiceAdapterHost {
public typesRegistry: ts.Map<void> | undefined;
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false);
constructor(protected cancellationToken = DefaultHostCancellationToken.Instance,
@ -182,6 +183,11 @@ namespace Harness.LanguageService {
/// Native adapter
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost {
isKnownTypesPackageName(name: string): boolean {
return this.typesRegistry && this.typesRegistry.has(name);
}
installPackage = ts.notImplemented;
getCompilationSettings() { return this.settings; }
getCancellationToken() { return this.cancellationToken; }
getDirectories(path: string): string[] {
@ -206,6 +212,11 @@ namespace Harness.LanguageService {
return script ? script.version.toString() : undefined;
}
directoryExists(dirName: string): boolean {
const fileEntry = this.virtualFileSystem.traversePath(dirName);
return fileEntry && fileEntry.isDirectory();
}
fileExists(fileName: string): boolean {
const script = this.getScriptSnapshot(fileName);
return script !== undefined;
@ -405,8 +416,8 @@ namespace Harness.LanguageService {
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
}
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions | undefined, source: string | undefined): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options), source));
}
getCompletionEntrySymbol(): ts.Symbol {
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");
@ -493,6 +504,7 @@ namespace Harness.LanguageService {
getCodeFixesAtPosition(): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
applyCodeActionCommand = ts.notImplemented;
getCodeFixDiagnostics(): ts.Diagnostic[] {
throw new Error("Not supported on the shim.");
}

View File

@ -89,6 +89,7 @@ interface PlaybackControl {
namespace Playback {
let recordLog: IOLog = undefined;
let replayLog: IOLog = undefined;
let replayFilesRead: ts.Map<IOLogFile> | undefined = undefined;
let recordLogFileNameBase = "";
interface Memoized<T> {
@ -157,11 +158,20 @@ namespace Playback {
return log;
}
const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
function sanitizeTestFilePath(name: string) {
const path = ts.toPath(ts.normalizeSlashes(name.replace(/[\^<>:"|?*%]/g, "_")).replace(/\.\.\//g, "__dotdot/"), "", canonicalizeForHarness);
if (ts.startsWith(path, "/")) {
return path.substring(1);
}
return path;
}
export function oldStyleLogIntoNewStyleLog(log: IOLog, writeFile: typeof Harness.IO.writeFile, baseTestName: string) {
if (log.filesAppended) {
for (const file of log.filesAppended) {
if (file.contents !== undefined) {
file.contentsPath = ts.combinePaths("appended", Harness.Compiler.sanitizeTestFilePath(file.path));
file.contentsPath = ts.combinePaths("appended", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, file.contentsPath), file.contents);
delete file.contents;
}
@ -170,7 +180,7 @@ namespace Playback {
if (log.filesWritten) {
for (const file of log.filesWritten) {
if (file.contents !== undefined) {
file.contentsPath = ts.combinePaths("written", Harness.Compiler.sanitizeTestFilePath(file.path));
file.contentsPath = ts.combinePaths("written", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, file.contentsPath), file.contents);
delete file.contents;
}
@ -180,7 +190,7 @@ namespace Playback {
for (const file of log.filesRead) {
const { contents } = file.result;
if (contents !== undefined) {
file.result.contentsPath = ts.combinePaths("read", Harness.Compiler.sanitizeTestFilePath(file.path));
file.result.contentsPath = ts.combinePaths("read", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, file.result.contentsPath), contents);
const len = contents.length;
if (len >= 2 && contents.charCodeAt(0) === 0xfeff) {
@ -213,10 +223,15 @@ namespace Playback {
replayLog = log;
// Remove non-found files from the log (shouldn't really need them, but we still record them for diagnostic purposes)
replayLog.filesRead = replayLog.filesRead.filter(f => f.result.contents !== undefined);
replayFilesRead = ts.createMap();
for (const file of replayLog.filesRead) {
replayFilesRead.set(ts.normalizeSlashes(file.path).toLowerCase(), file);
}
};
wrapper.endReplay = () => {
replayLog = undefined;
replayFilesRead = undefined;
};
wrapper.startRecord = (fileNameBase) => {
@ -234,18 +249,38 @@ namespace Playback {
wrapper.endRecord = () => {
if (recordLog !== undefined) {
let i = 0;
const fn = () => recordLogFileNameBase + i;
while (underlying.fileExists(fn() + ".json")) i++;
underlying.writeFile(ts.combinePaths(fn(), "test.json"), JSON.stringify(oldStyleLogIntoNewStyleLog(recordLog, (path, string) => underlying.writeFile(ts.combinePaths(fn(), path), string), fn()), null, 4)); // tslint:disable-line:no-null-keyword
const getBase = () => recordLogFileNameBase + i;
while (underlying.fileExists(ts.combinePaths(getBase(), "test.json"))) i++;
const newLog = oldStyleLogIntoNewStyleLog(recordLog, (path, string) => underlying.writeFile(path, string), getBase());
underlying.writeFile(ts.combinePaths(getBase(), "test.json"), JSON.stringify(newLog, null, 4)); // tslint:disable-line:no-null-keyword
const syntheticTsconfig = generateTsconfig(newLog);
if (syntheticTsconfig) {
underlying.writeFile(ts.combinePaths(getBase(), "tsconfig.json"), JSON.stringify(syntheticTsconfig, null, 4)); // tslint:disable-line:no-null-keyword
}
recordLog = undefined;
}
};
function generateTsconfig(newLog: IOLog): undefined | { compilerOptions: ts.CompilerOptions, files: string[] } {
if (newLog.filesRead.some(file => /tsconfig.+json$/.test(file.path))) {
return;
}
const files = [];
for (const file of newLog.filesRead) {
if (file.result.contentsPath &&
Harness.isDefaultLibraryFile(file.result.contentsPath) &&
/\.[tj]s$/.test(file.result.contentsPath)) {
files.push(file.result.contentsPath);
}
}
return { compilerOptions: ts.parseCommandLine(newLog.arguments).options, files };
}
wrapper.fileExists = recordReplay(wrapper.fileExists, underlying)(
path => callAndRecord(underlying.fileExists(path), recordLog.fileExists, { path }),
memoize(path => {
// If we read from the file, it must exist
if (findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ false)) {
if (findFileByPath(path, /*throwFileNotFoundError*/ false)) {
return true;
}
else {
@ -289,7 +324,7 @@ namespace Playback {
recordLog.filesRead.push(logEntry);
return result;
},
memoize(path => findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ true).contents));
memoize(path => findFileByPath(path, /*throwFileNotFoundError*/ true).contents));
wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)(
(path, extensions, exclude, include, depth) => {
@ -370,14 +405,12 @@ namespace Playback {
return results[0].result;
}
function findFileByPath(logArray: IOLogFile[],
expectedPath: string, throwFileNotFoundError: boolean): FileInformation {
function findFileByPath(expectedPath: string, throwFileNotFoundError: boolean): FileInformation {
const normalizedName = ts.normalizePath(expectedPath).toLowerCase();
// Try to find the result through normal fileName
for (const log of logArray) {
if (ts.normalizeSlashes(log.path).toLowerCase() === normalizedName) {
return log.result;
}
const result = replayFilesRead.get(normalizedName);
if (result) {
return result.result;
}
// If we got here, we didn't find a match
@ -415,4 +448,4 @@ namespace Playback {
initWrapper(wrapper, underlying);
return wrapper;
}
}
}

View File

@ -38,20 +38,45 @@ namespace Harness.Parallel.Host {
return undefined;
}
function hashName(runner: TestRunnerKind, test: string) {
function hashName(runner: TestRunnerKind | "unittest", test: string) {
return `tsrunner-${runner}://${test}`;
}
let tasks: { runner: TestRunnerKind | "unittest", file: string, size: number }[] = [];
const newTasks: { runner: TestRunnerKind | "unittest", file: string, size: number }[] = [];
let unknownValue: string | undefined;
export function start() {
initializeProgressBarsDependencies();
console.log("Discovering tests...");
const discoverStart = +(new Date());
const { statSync }: { statSync(path: string): { size: number }; } = require("fs");
let tasks: { runner: TestRunnerKind, file: string, size: number }[] = [];
const newTasks: { runner: TestRunnerKind, file: string, size: number }[] = [];
const perfData = readSavedPerfData(configOption);
let totalCost = 0;
let unknownValue: string | undefined;
if (runUnitTests) {
(global as any).describe = (suiteName: string) => {
// Note, sub-suites are not indexed (we assume such granularity is not required)
let size = 0;
if (perfData) {
size = perfData[hashName("unittest", suiteName)];
if (size === undefined) {
newTasks.push({ runner: "unittest", file: suiteName, size: 0 });
unknownValue = suiteName;
return;
}
}
tasks.push({ runner: "unittest", file: suiteName, size });
totalCost += size;
};
}
else {
(global as any).describe = ts.noop;
}
setTimeout(() => startDelayed(perfData, totalCost), 0); // Do real startup on next tick, so all unit tests have been collected
}
function startDelayed(perfData: {[testHash: string]: number}, totalCost: number) {
initializeProgressBarsDependencies();
console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : "."));
console.log("Discovering runner-based tests...");
const discoverStart = +(new Date());
const { statSync }: { statSync(path: string): { size: number }; } = require("fs");
for (const runner of runners) {
const files = runner.enumerateTestFiles();
for (const file of files) {
@ -87,8 +112,7 @@ namespace Harness.Parallel.Host {
}
tasks.sort((a, b) => a.size - b.size);
tasks = tasks.concat(newTasks);
// 1 fewer batches than threads to account for unittests running on the final thread
const batchCount = runners.length === 1 ? workerCount : workerCount - 1;
const batchCount = workerCount;
const packfraction = 0.9;
const chunkSize = 1000; // ~1KB or 1s for sending batches near the end of a test
const batchSize = (totalCost / workerCount) * packfraction; // Keep spare tests for unittest thread in reserve
@ -113,7 +137,7 @@ namespace Harness.Parallel.Host {
let closedWorkers = 0;
for (let i = 0; i < workerCount; i++) {
// TODO: Just send the config over the IPC channel or in the command line arguments
const config: TestConfig = { light: Harness.lightMode, listenForWork: true, runUnitTests: runners.length === 1 ? false : i === workerCount - 1 };
const config: TestConfig = { light: Harness.lightMode, listenForWork: true, runUnitTests };
const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`);
Harness.IO.writeFile(configPath, JSON.stringify(config));
const child = fork(__filename, [`--config="${configPath}"`]);
@ -187,7 +211,7 @@ namespace Harness.Parallel.Host {
// It's only really worth doing an initial batching if there are a ton of files to go through
if (totalFiles > 1000) {
console.log("Batching initial test lists...");
const batches: { runner: TestRunnerKind, file: string, size: number }[][] = new Array(batchCount);
const batches: { runner: TestRunnerKind | "unittest", file: string, size: number }[][] = new Array(batchCount);
const doneBatching = new Array(batchCount);
let scheduledTotal = 0;
batcher: while (true) {
@ -230,7 +254,7 @@ namespace Harness.Parallel.Host {
if (payload) {
worker.send({ type: "batch", payload });
}
else { // Unittest thread - send off just one test
else { // Out of batches, send off just one test
const payload = tasks.pop();
ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios
worker.send({ type: "test", payload });

View File

@ -1,14 +1,14 @@
/// <reference path="./host.ts" />
/// <reference path="./worker.ts" />
namespace Harness.Parallel {
export type ParallelTestMessage = { type: "test", payload: { runner: TestRunnerKind, file: string } } | never;
export type ParallelTestMessage = { type: "test", payload: { runner: TestRunnerKind | "unittest", file: string } } | never;
export type ParallelBatchMessage = { type: "batch", payload: ParallelTestMessage["payload"][] } | never;
export type ParallelCloseMessage = { type: "close" } | never;
export type ParallelHostMessage = ParallelTestMessage | ParallelCloseMessage | ParallelBatchMessage;
export type ParallelErrorMessage = { type: "error", payload: { error: string, stack: string, name?: string[] } } | never;
export type ErrorInfo = ParallelErrorMessage["payload"] & { name: string[] };
export type ParallelResultMessage = { type: "result", payload: { passing: number, errors: ErrorInfo[], duration: number, runner: TestRunnerKind, file: string } } | never;
export type ParallelResultMessage = { type: "result", payload: { passing: number, errors: ErrorInfo[], duration: number, runner: TestRunnerKind | "unittest", file: string } } | never;
export type ParallelBatchProgressMessage = { type: "progress", payload: ParallelResultMessage["payload"] } | never;
export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage;
}

View File

@ -1,22 +1,13 @@
namespace Harness.Parallel.Worker {
let errors: ErrorInfo[] = [];
let passing = 0;
let reportedUnitTests = false;
type Executor = {name: string, callback: Function, kind: "suite" | "test"} | never;
function resetShimHarnessAndExecute(runner: RunnerBase) {
if (reportedUnitTests) {
errors = [];
passing = 0;
testList.length = 0;
}
reportedUnitTests = true;
if (testList.length) {
// Execute unit tests
testList.forEach(({ name, callback, kind }) => executeCallback(name, callback, kind));
testList.length = 0;
}
errors = [];
passing = 0;
testList.length = 0;
const start = +(new Date());
runner.initializeTests();
testList.forEach(({ name, callback, kind }) => executeCallback(name, callback, kind));
@ -226,13 +217,46 @@ namespace Harness.Parallel.Worker {
shimMochaHarness();
}
function handleTest(runner: TestRunnerKind, file: string) {
if (!runners.has(runner)) {
runners.set(runner, createRunner(runner));
function handleTest(runner: TestRunnerKind | "unittest", file: string) {
collectUnitTestsIfNeeded();
if (runner === unittest) {
return executeUnitTest(file);
}
else {
if (!runners.has(runner)) {
runners.set(runner, createRunner(runner));
}
const instance = runners.get(runner);
instance.tests = [file];
return { ...resetShimHarnessAndExecute(instance), runner, file };
}
const instance = runners.get(runner);
instance.tests = [file];
return { ...resetShimHarnessAndExecute(instance), runner, file };
}
}
const unittest: "unittest" = "unittest";
let unitTests: {[name: string]: Function};
function collectUnitTestsIfNeeded() {
if (!unitTests && testList.length) {
unitTests = {};
for (const test of testList) {
unitTests[test.name] = test.callback;
}
testList.length = 0;
}
}
function executeUnitTest(name: string) {
if (!unitTests) {
throw new Error(`Asked to run unit test ${name}, but no unit tests were discovered!`);
}
if (unitTests[name]) {
errors = [];
passing = 0;
const start = +(new Date());
executeSuiteCallback(name, unitTests[name]);
delete unitTests[name];
return { file: name, runner: unittest, errors, passing, duration: +(new Date()) - start };
}
throw new Error(`Unit test with name "${name}" was asked to be run, but such a test does not exist!`);
}
}

View File

@ -443,7 +443,7 @@ class ProjectRunner extends RunnerBase {
const name = "Compiling project for " + testCase.scenario + ": testcase " + testCaseFileName;
describe("Projects tests", () => {
describe("projects tests", () => {
describe(name, () => {
function verifyCompilerResults(moduleKind: ts.ModuleKind) {
let compilerResult: BatchCompileProjectTestCaseResult;

View File

@ -82,7 +82,7 @@ let testConfigContent =
let taskConfigsFolder: string;
let workerCount: number;
let runUnitTests = true;
let runUnitTests: boolean | undefined;
let noColors = false;
interface TestConfig {
@ -108,9 +108,7 @@ function handleTestConfig() {
if (testConfig.light) {
Harness.lightMode = true;
}
if (testConfig.runUnitTests !== undefined) {
runUnitTests = testConfig.runUnitTests;
}
runUnitTests = testConfig.runUnitTests;
if (testConfig.workerCount) {
workerCount = +testConfig.workerCount;
}
@ -199,6 +197,9 @@ function handleTestConfig() {
runners.push(new FourSlashRunner(FourSlashTestType.Server));
// runners.push(new GeneratedFourslashRunner());
}
if (runUnitTests === undefined) {
runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for
}
}
function beginTests() {

View File

@ -26,7 +26,7 @@ namespace RWC {
}
export function runRWCTest(jsonPath: string) {
describe("Testing a RWC project: " + jsonPath, () => {
describe("Testing a rwc project: " + jsonPath, () => {
let inputFiles: Harness.Compiler.TestFile[] = [];
let otherFiles: Harness.Compiler.TestFile[] = [];
let tsconfigFiles: Harness.Compiler.TestFile[] = [];
@ -39,8 +39,6 @@ namespace RWC {
const baseName = ts.getBaseFileName(jsonPath);
let currentDirectory: string;
let useCustomLibraryFile: boolean;
let skipTypeAndSymbolbaselines = false;
const typeAndSymbolSizeLimit = 10000000;
after(() => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
@ -54,7 +52,6 @@ namespace RWC {
// or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file
// otherwise use the lib.d.ts from built/local
useCustomLibraryFile = undefined;
skipTypeAndSymbolbaselines = false;
});
it("can compile", function(this: Mocha.ITestCallbackContext) {
@ -64,7 +61,6 @@ namespace RWC {
const ioLog: IOLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)), Harness.IO, `internal/cases/rwc/${baseName}`);
currentDirectory = ioLog.currentDirectory;
useCustomLibraryFile = ioLog.useCustomLibraryFile;
skipTypeAndSymbolbaselines = ioLog.filesRead.reduce((acc, elem) => (elem && elem.result && elem.result.contents) ? acc + elem.result.contents.length : acc, 0) > typeAndSymbolSizeLimit;
runWithIOLog(ioLog, () => {
opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName));
assert.equal(opts.errors.length, 0);
@ -199,14 +195,6 @@ namespace RWC {
}, baselineOpts, [".map"]);
});
/*it("has correct source map record", () => {
if (compilerOptions.sourceMap) {
Harness.Baseline.runBaseline(baseName + ".sourcemap.txt", () => {
return compilerResult.getSourceMapRecord();
}, baselineOpts);
}
});*/
it("has the expected errors", () => {
Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => {
if (compilerResult.errors.length === 0) {
@ -219,14 +207,6 @@ namespace RWC {
}, baselineOpts);
});
it("has the expected types", () => {
// We don't need to pass the extension here because "doTypeAndSymbolBaseline" will append appropriate extension of ".types" or ".symbols"
Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult.program, inputFiles
.concat(otherFiles)
.filter(file => !!compilerResult.program.getSourceFile(file.unitName))
.filter(e => !Harness.isDefaultLibraryFile(e.unitName)), baselineOpts, /*multifile*/ true, skipTypeAndSymbolbaselines);
});
// Ideally, a generated declaration file will have no errors. But we allow generated
// declaration file errors as part of the baseline.
it("has the expected errors in generated declaration files", () => {
@ -266,6 +246,7 @@ class RWCRunner extends RunnerBase {
public initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
for (let i = 0; i < testList.length; i++) {
this.runTest(testList[i]);
}

View File

@ -139,6 +139,7 @@
"./unittests/telemetry.ts",
"./unittests/languageService.ts",
"./unittests/programMissingFiles.ts",
"./unittests/publicApi.ts"
"./unittests/publicApi.ts",
"./unittests/hostNewLineSupport.ts"
]
}

View File

@ -52,7 +52,7 @@ class TypeWriterWalker {
}
private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator<TypeWriterResult> {
if (ts.isPartOfExpression(node) || node.kind === ts.SyntaxKind.Identifier) {
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier) {
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
if (result) {
yield result;
@ -101,8 +101,8 @@ class TypeWriterWalker {
}
count++;
symbolString += ", ";
if ((declaration as any)["__symbolTestOutputCache"]) {
symbolString += (declaration as any)["__symbolTestOutputCache"];
if ((declaration as any).__symbolTestOutputCache) {
symbolString += (declaration as any).__symbolTestOutputCache;
continue;
}
const declSourceFile = declaration.getSourceFile();
@ -111,7 +111,7 @@ class TypeWriterWalker {
const isLibFile = /lib(.*)\.d\.ts/i.test(fileName);
const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`;
symbolString += declText;
(declaration as any)["__symbolTestOutputCache"] = declText;
(declaration as any).__symbolTestOutputCache = declText;
}
}
symbolString += ")";

View File

@ -46,15 +46,14 @@ namespace ts {
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
const builder = createBuilder({
getCanonicalFileName: identity,
getEmitOutput: getFileEmitOutput,
computeHash: identity,
shouldEmitFile: returnTrue,
computeHash: identity
});
return fileNames => {
const program = getProgram();
builder.updateProgram(program);
const changedFiles = builder.emitChangedFiles(program);
assert.deepEqual(changedFileNames(changedFiles), fileNames);
const outputFileNames: string[] = [];
builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName));
assert.deepEqual(outputFileNames, fileNames);
};
}
@ -63,11 +62,4 @@ namespace ts {
updateProgramText(files, fileName, fileContent);
});
}
function changedFileNames(changedFiles: ReadonlyArray<EmitOutputDetailed>): string[] {
return changedFiles.map(f => {
assert.lengthOf(f.outputFiles, 1);
return f.outputFiles[0].name;
});
}
}

View File

@ -12,7 +12,7 @@ namespace ts.projectSystem {
describe("CompileOnSave affected list", () => {
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) {
const response: server.protocol.CompileOnSaveAffectedFileListSingleProject[] = session.executeCommand(request).response;
const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[];
const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
@ -563,7 +563,7 @@ namespace ts.projectSystem {
path: "/a/b/file3.js",
content: "console.log('file3');"
};
const externalProjectName = "externalproject";
const externalProjectName = "/a/b/externalproject";
const host = createServerHost([file1, file2, file3, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
@ -588,5 +588,48 @@ namespace ts.projectSystem {
assert.isTrue(outFileContent.indexOf(file2.content) === -1);
assert.isTrue(outFileContent.indexOf(file3.content) === -1);
});
it("should use project root as current directory so that compile on save results in correct file mapping", () => {
const inputFileName = "Foo.ts";
const file1 = {
path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`,
content: "consonle.log('file1');"
};
const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj";
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
const outFileName = "bar.js";
projectService.openExternalProject({
rootFiles: toExternalFiles([file1.path]),
options: {
outFile: outFileName,
sourceMap: true,
compileOnSave: true
},
projectFileName: externalProjectName
});
const emitRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path });
session.executeCommand(emitRequest);
// Verify js file
const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName;
assert.isTrue(host.fileExists(expectedOutFileName));
const outFileContent = host.readFile(expectedOutFileName);
verifyContentHasString(outFileContent, file1.content);
verifyContentHasString(outFileContent, `//# ${"sourceMappingURL"}=${outFileName}.map`); // Sometimes tools can sometimes see this line as a source mapping url comment, so we obfuscate it a little
// Verify map file
const expectedMapFileName = expectedOutFileName + ".map";
assert.isTrue(host.fileExists(expectedMapFileName));
const mapFileContent = host.readFile(expectedMapFileName);
verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`);
function verifyContentHasString(content: string, string: string) {
assert.isTrue(content.indexOf(string) !== -1, `Expected "${content}" to have "${string}"`);
}
});
});
}

View File

@ -9,7 +9,7 @@ namespace ts {
}
function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) {
const { options: actualCompilerOptions, errors: actualErrors} = convertCompilerOptionsFromJson(json["compilerOptions"], "/apath/", configFileName);
const { options: actualCompilerOptions, errors: actualErrors} = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName);
const parsedCompilerOptions = JSON.stringify(actualCompilerOptions);
const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions);
@ -33,7 +33,7 @@ namespace ts {
assert(!!result.endOfFileToken);
const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []);
const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName);
expectedResult.compilerOptions["configFilePath"] = configFileName;
expectedResult.compilerOptions.configFilePath = configFileName;
const parsedCompilerOptions = JSON.stringify(actualCompilerOptions);
const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions);
@ -421,6 +421,24 @@ namespace ts {
);
});
it("Convert negative numbers in tsconfig.json ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"allowJs": true,
"maxNodeModuleJsDepth": -1
}
}, "tsconfig.json",
{
compilerOptions: {
allowJs: true,
maxNodeModuleJsDepth: -1
},
errors: []
}
);
});
// jsconfig.json
it("Convert correctly format jsconfig.json to compiler-options ", () => {
assertCompilerOptions(

View File

@ -32,7 +32,7 @@ namespace ts {
}
function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) {
const jsonOptions = json["typeAcquisition"] || json["typingOptions"];
const jsonOptions = json.typeAcquisition || json.typingOptions;
const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName);
verifyAcquisition(actualTypeAcquisition, expectedResult);
verifyErrors(actualErrors, expectedResult);

View File

@ -223,6 +223,14 @@ const f = () => {
testExtractConstant("extractConstant_ArrowFunction_Expression",
`const f = () => [#|2 + 1|];`);
testExtractConstant("extractConstant_PreserveTrivia", `
// a
var q = /*b*/ //c
/*d*/ [#|1 /*e*/ //f
/*g*/ + /*h*/ //i
/*j*/ 2|] /*k*/ //l
/*m*/; /*n*/ //o`);
testExtractConstantFailed("extractConstant_Void", `
function f(): void { }
[#|f();|]`);
@ -230,6 +238,30 @@ function f(): void { }
testExtractConstantFailed("extractConstant_Never", `
function f(): never { }
[#|f();|]`);
testExtractConstant("extractConstant_This_Constructor", `
class C {
constructor() {
[#|this.m2()|];
}
m2() { return 1; }
}`);
testExtractConstant("extractConstant_This_Method", `
class C {
m1() {
[#|this.m2()|];
}
m2() { return 1; }
}`);
testExtractConstant("extractConstant_This_Property", `
namespace N { // Force this test to be TS-only
class C {
x = 1;
y = [#|this.x|];
}
}`);
});
function testExtractConstant(caption: string, text: string) {

View File

@ -195,12 +195,12 @@ namespace ts {
function G<U extends T[]>(t2: U) {
[#|t2.toString();|]
}
}`);
}`, /*includeLib*/ true);
// Confirm that the contextual type of an extracted expression counts as a use.
testExtractFunction("extractFunction16",
`function F<T>() {
const array: T[] = [#|[]|];
}`);
}`, /*includeLib*/ true);
// Class type parameter
testExtractFunction("extractFunction17",
`class C<T1, T2> {
@ -219,7 +219,7 @@ namespace ts {
testExtractFunction("extractFunction19",
`function F<T, U extends T[], V extends U[]>(v: V) {
[#|v.toString()|];
}`);
}`, /*includeLib*/ true);
testExtractFunction("extractFunction20",
`const _ = class {
@ -360,9 +360,195 @@ function parsePrimaryExpression(): any {
export const j = 10;
export const y = [#|j * j|];
}`);
testExtractFunction("extractFunction_VariableDeclaration_Var", `
[#|var x = 1;
"hello"|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Let_Type", `
[#|let x: number = 1;
"hello";|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", `
[#|let x = 1;
"hello";|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Const_Type", `
[#|const x: number = 1;
"hello";|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", `
[#|const x = 1;
"hello";|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Multiple1", `
[#|const x = 1, y: string = "a";|]
x; y;
`);
testExtractFunction("extractFunction_VariableDeclaration_Multiple2", `
[#|const x = 1, y = "a";
const z = 3;|]
x; y; z;
`);
testExtractFunction("extractFunction_VariableDeclaration_Multiple3", `
[#|const x = 1, y: string = "a";
let z = 3;|]
x; y; z;
`);
testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", `
[#|const x: number = 1;
"hello";|]
x; x;
`);
testExtractFunction("extractFunction_VariableDeclaration_DeclaredTwice", `
[#|var x = 1;
var x = 2;|]
x;
`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Var", `
function f() {
let a = 1;
[#|var x = 1;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_NoType", `
function f() {
let a = 1;
[#|let x = 1;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_Type", `
function f() {
let a = 1;
[#|let x: number = 1;
a++;|]
a; x;
}`);
// We propagate numericLiteralFlags, but it's not consumed by the emitter,
// so everything comes out decimal. It would be nice to improve this.
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", `
function f() {
let a = 1;
[#|let x: 0o10 | 10 | 0b10 = 10;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType2", `
function f() {
let a = 1;
[#|let x: "a" | 'b' = 'a';
a++;|]
a; x;
}`);
// We propagate numericLiteralFlags, but it's not consumed by the emitter,
// so everything comes out decimal. It would be nice to improve this.
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", `
function f() {
let a = 1;
[#|let x: 0o10 | 10 | 0b10 = 10;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_TypeWithComments", `
function f() {
let a = 1;
[#|let x: /*A*/ "a" /*B*/ | /*C*/ 'b' /*D*/ = 'a';
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_NoType", `
function f() {
let a = 1;
[#|const x = 1;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_Type", `
function f() {
let a = 1;
[#|const x: number = 1;
a++;|]
a; x;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed1", `
function f() {
let a = 1;
[#|const x = 1;
let y = 2;
a++;|]
a; x; y;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed2", `
function f() {
let a = 1;
[#|var x = 1;
let y = 2;
a++;|]
a; x; y;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed3", `
function f() {
let a = 1;
[#|let x: number = 1;
let y = 2;
a++;|]
a; x; y;
}`);
testExtractFunction("extractFunction_VariableDeclaration_Writes_UnionUndefined", `
function f() {
let a = 1;
[#|let x: number | undefined = 1;
let y: undefined | number = 2;
let z: (undefined | number) = 3;
a++;|]
a; x; y; z;
}`);
testExtractFunction("extractFunction_VariableDeclaration_ShorthandProperty", `
function f() {
[#|let x;|]
return { x };
}`);
testExtractFunction("extractFunction_PreserveTrivia", `
// a
var q = /*b*/ //c
/*d*/ [#|1 /*e*/ //f
/*g*/ + /*h*/ //i
/*j*/ 2|] /*k*/ //l
/*m*/; /*n*/ //o`);
});
function testExtractFunction(caption: string, text: string) {
testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function);
function testExtractFunction(caption: string, text: string, includeLib?: boolean) {
testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function, includeLib);
}
}

View File

@ -152,6 +152,29 @@ namespace ts {
}
}
`);
testExtractRange(`
function f(x: number) {
[#|[$|try {
x++;
}
finally {
return 1;
}|]|]
}
`);
// Variable statements
testExtractRange(`[#|let x = [$|1|];|]`);
testExtractRange(`[#|let x = [$|1|], y;|]`);
testExtractRange(`[#|[$|let x = 1, y = 1;|]|]`);
// Variable declarations
testExtractRange(`let [#|x = [$|1|]|];`);
testExtractRange(`let [#|x = [$|1|]|], y = 2;`);
testExtractRange(`let x = 1, [#|y = [$|2|]|];`);
// Return statements
testExtractRange(`[#|return [$|1|];|]`);
});
testExtractRangeFailed("extractRangeFailed1",
@ -313,6 +336,35 @@ switch (x) {
refactor.extractSymbol.Messages.CannotExtractRange.message
]);
testExtractRangeFailed("extractRangeFailed11",
`
function f(x: number) {
while (true) {
[#|try {
x++;
}
finally {
break;
}|]
}
}
`,
[
refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message
]);
testExtractRangeFailed("extractRangeFailed12",
`let [#|x|];`,
[
refactor.extractSymbol.Messages.StatementOrExpressionExpected.message
]);
testExtractRangeFailed("extractRangeFailed13",
`[#|return;|]`,
[
refactor.extractSymbol.Messages.CannotExtractRange.message
]);
testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.CannotExtractIdentifier.message]);
});
}

View File

@ -67,7 +67,8 @@ namespace ts {
}
export const newLineCharacter = "\n";
export function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
export const getRuleProvider = memoize(getRuleProviderInternal);
function getRuleProviderInternal() {
const options = {
indentSize: 4,
tabSize: 4,
@ -89,15 +90,21 @@ namespace ts {
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
};
if (action) {
action(options);
}
const rulesProvider = new formatting.RulesProvider();
rulesProvider.ensureUpToDate(options);
return rulesProvider;
}
export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage) {
const notImplementedHost: LanguageServiceHost = {
getCompilationSettings: notImplemented,
getScriptFileNames: notImplemented,
getScriptVersion: notImplemented,
getScriptSnapshot: notImplemented,
getDefaultLibFileName: notImplemented,
getCurrentDirectory: notImplemented,
};
export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {
@ -109,7 +116,7 @@ namespace ts {
function runBaseline(extension: Extension) {
const path = "/a" + extension;
const program = makeProgram({ path, content: t.source });
const program = makeProgram({ path, content: t.source }, includeLib);
if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
@ -125,6 +132,7 @@ namespace ts {
file: sourceFile,
startPosition: selectionRange.start,
endPosition: selectionRange.end,
host: notImplementedHost,
rulesProvider: getRuleProvider()
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
@ -144,15 +152,15 @@ namespace ts {
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
data.push(newTextWithRename);
const diagProgram = makeProgram({ path, content: newText });
const diagProgram = makeProgram({ path, content: newText }, includeLib);
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
}
return data.join(newLineCharacter);
});
}
function makeProgram(f: {path: string, content: string }) {
const host = projectSystem.createServerHost([f, projectSystem.libFile]);
function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
@ -188,6 +196,7 @@ namespace ts {
file: sourceFile,
startPosition: selectionRange.start,
endPosition: selectionRange.end,
host: notImplementedHost,
rulesProvider: getRuleProvider()
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));

View File

@ -0,0 +1,67 @@
/// <reference path="..\harness.ts" />
namespace ts {
describe("hostNewLineSupport", () => {
function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) {
function snapFor(path: string): IScriptSnapshot {
if (path === "lib.d.ts") {
return {
dispose() {},
getChangeRange() { return undefined; },
getLength() { return 0; },
getText(_start, _end) {
return "";
}
};
}
const result = forEach(files, f => f.unitName === path ? f : undefined);
if (result) {
return {
dispose() {},
getChangeRange() { return undefined; },
getLength() { return result.content.length; },
getText(start, end) {
return result.content.substring(start, end);
}
};
}
return undefined;
}
const lshost: LanguageServiceHost = {
getCompilationSettings: () => settings,
getScriptFileNames: () => map(files, f => f.unitName),
getScriptVersion: () => "1",
getScriptSnapshot: name => snapFor(name),
getDefaultLibFileName: () => "lib.d.ts",
getCurrentDirectory: () => "",
};
return ts.createLanguageService(lshost);
}
function verifyNewLines(content: string, options: CompilerOptions) {
const ls = testLSWithFiles(options, [{
content,
fileOptions: {},
unitName: "input.ts"
}]);
const result = ls.getEmitOutput("input.ts");
assert(!result.emitSkipped, "emit was skipped");
assert(result.outputFiles.length === 1, "a number of files other than 1 was output");
assert(result.outputFiles[0].name === "input.js", `Expected output file name input.js, but got ${result.outputFiles[0].name}`);
assert(result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines");
assert(!result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines");
}
function verifyBothNewLines(content: string) {
verifyNewLines(content, { newLine: NewLineKind.CarriageReturnLineFeed });
verifyNewLines(content, { newLine: NewLineKind.LineFeed });
}
it("should exist and respect provided compiler options", () => {
verifyBothNewLines(`
function foo() {
return 2 + 2;
}
`);
});
});
}

View File

@ -392,7 +392,7 @@ export = C;
});
describe("Files with different casing", () => {
const library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5);
let library: SourceFile;
function test(files: Map<string>, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
if (!useCaseSensitiveFileNames) {
@ -406,6 +406,9 @@ export = C;
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
if (fileName === "lib.d.ts") {
if (!library) {
library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5);
}
return library;
}
const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName)));

View File

@ -57,7 +57,7 @@ namespace ts.projectSystem {
});
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
// only file1 exists - expect error
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
}
@ -65,7 +65,7 @@ namespace ts.projectSystem {
{
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
}
@ -73,7 +73,7 @@ namespace ts.projectSystem {
{
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response;
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
}
});
@ -103,13 +103,13 @@ namespace ts.projectSystem {
seq: 2,
arguments: { projectFileName: project.getProjectName() }
};
let diags = session.executeCommand(compilerOptionsRequest).response;
let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
host.reloadFS([file1, file2, config, libFile]);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
diags = session.executeCommand(compilerOptionsRequest).response;
diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
});

View File

@ -16,8 +16,8 @@ namespace ts.server {
directoryExists: () => false,
getDirectories: () => [],
createDirectory: noop,
getExecutingFilePath(): string { return void 0; },
getCurrentDirectory(): string { return void 0; },
getExecutingFilePath(): string { return ""; },
getCurrentDirectory(): string { return ""; },
getEnvironmentVariable(): string { return ""; },
readDirectory() { return []; },
exit: noop,
@ -53,6 +53,17 @@ namespace ts.server {
return new TestSession(opts);
}
// Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test
let oldPrepare: Function;
before(() => {
oldPrepare = (Error as any).prepareStackTrace;
delete (Error as any).prepareStackTrace;
});
after(() => {
(Error as any).prepareStackTrace = oldPrepare;
});
beforeEach(() => {
session = createSession();
session.send = (msg: protocol.Message) => {
@ -315,7 +326,7 @@ namespace ts.server {
item: false
};
const command = "newhandle";
const result = {
const result: ts.server.HandlerResponse = {
response: respBody,
responseRequired: true
};
@ -332,7 +343,7 @@ namespace ts.server {
const respBody = {
item: false
};
const resp = {
const resp: ts.server.HandlerResponse = {
response: respBody,
responseRequired: true
};
@ -372,7 +383,7 @@ namespace ts.server {
};
const command = "test";
session.output(body, command);
session.output(body, command, /*reqSeq*/ 0);
expect(lastSent).to.deep.equal({
seq: 0,
@ -386,6 +397,73 @@ namespace ts.server {
});
});
describe("exceptions", () => {
// Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test
let oldPrepare: Function;
before(() => {
oldPrepare = (Error as any).prepareStackTrace;
delete (Error as any).prepareStackTrace;
});
after(() => {
(Error as any).prepareStackTrace = oldPrepare;
});
const command = "testhandler";
class TestSession extends Session {
lastSent: protocol.Message;
private exceptionRaisingHandler(_request: protocol.Request): { response?: any, responseRequired: boolean } {
f1();
return;
function f1() {
throw new Error("myMessage");
}
}
constructor() {
super({
host: mockHost,
cancellationToken: nullCancellationToken,
useSingleInferredProject: false,
useInferredProjectPerProjectRoot: false,
typingsInstaller: undefined,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: projectSystem.nullLogger,
canUseEvents: true
});
this.addProtocolHandler(command, this.exceptionRaisingHandler);
}
send(msg: protocol.Message) {
this.lastSent = msg;
}
}
it("raised in a protocol handler generate an event", () => {
const session = new TestSession();
const request = {
command,
seq: 0,
type: "request"
};
session.onMessage(JSON.stringify(request));
const lastSent = session.lastSent as protocol.Response;
expect(lastSent).to.contain({
seq: 0,
type: "response",
command,
success: false
});
expect(lastSent.message).has.string("myMessage").and.has.string("f1");
});
});
describe("how Session is extendable via subclassing", () => {
class TestSession extends Session {
lastSent: protocol.Message;
@ -420,7 +498,7 @@ namespace ts.server {
};
const command = "test";
session.output(body, command);
session.output(body, command, /*reqSeq*/ 0);
expect(session.lastSent).to.deep.equal({
seq: 0,
@ -487,7 +565,7 @@ namespace ts.server {
handleRequest(msg: protocol.Request) {
let response: protocol.Response;
try {
({ response } = this.executeCommand(msg));
response = this.executeCommand(msg).response as protocol.Response;
}
catch (e) {
this.output(undefined, msg.command, msg.seq, e.toString());
@ -606,4 +684,28 @@ namespace ts.server {
session.consumeQueue();
});
});
describe("helpers", () => {
it(getLocationInNewDocument.name, () => {
const text = `// blank line\nconst x = 0;`;
const renameLocationInOldText = text.indexOf("0");
const fileName = "/a.ts";
const edits: ts.FileTextChanges = {
fileName,
textChanges: [
{
span: { start: 0, length: 0 },
newText: "const newLocal = 0;\n\n",
},
{
span: { start: renameLocationInOldText, length: 1 },
newText: "newLocal",
},
],
};
const renameLocationInNewText = renameLocationInOldText + edits.textChanges[0].newText.length;
const res = getLocationInNewDocument(text, fileName, renameLocationInNewText, [edits]);
assert.deepEqual(res, { line: 4, offset: 11 });
});
});
}

View File

@ -5,9 +5,9 @@ namespace ts.projectSystem {
describe("project telemetry", () => {
it("does nothing for inferred project", () => {
const file = makeFile("/a.js");
const et = new EventTracker([file]);
const et = new TestServerEventManager([file]);
et.service.openClientFile(file.path);
assert.equal(et.getEvents().length, 0);
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
});
it("only sends an event once", () => {
@ -15,7 +15,7 @@ namespace ts.projectSystem {
const file2 = makeFile("/b.ts");
const tsconfig = makeFile("/a/tsconfig.json", {});
const et = new EventTracker([file, file2, tsconfig]);
const et = new TestServerEventManager([file, file2, tsconfig]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({}, tsconfig.path);
@ -25,12 +25,12 @@ namespace ts.projectSystem {
et.service.openClientFile(file2.path);
checkNumberOfProjects(et.service, { inferredProjects: 1 });
assert.equal(et.getEvents().length, 0);
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
et.service.openClientFile(file.path);
checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 });
assert.equal(et.getEvents().length, 0);
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
});
it("counts files by extension", () => {
@ -39,7 +39,7 @@ namespace ts.projectSystem {
const compilerOptions: ts.CompilerOptions = { allowJs: true };
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, include: ["src"] });
const et = new EventTracker([...files, notIncludedFile, tsconfig]);
const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
et.service.openClientFile(files[0].path);
et.assertProjectInfoTelemetryEvent({
fileStats: { ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 },
@ -50,7 +50,7 @@ namespace ts.projectSystem {
it("works with external project", () => {
const file1 = makeFile("/a.ts");
const et = new EventTracker([file1]);
const et = new TestServerEventManager([file1]);
const compilerOptions: ts.server.protocol.CompilerOptions = { strict: true };
const projectFileName = "/hunter2/foo.csproj";
@ -148,7 +148,7 @@ namespace ts.projectSystem {
(compilerOptions as any).unknownCompilerOption = "hunter2"; // These are always ignored.
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, files: ["/a.ts"] });
const et = new EventTracker([file, tsconfig]);
const et = new TestServerEventManager([file, tsconfig]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
@ -168,7 +168,7 @@ namespace ts.projectSystem {
compileOnSave: true,
});
const et = new EventTracker([tsconfig, file]);
const et = new TestServerEventManager([tsconfig, file]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
extends: true,
@ -198,7 +198,7 @@ namespace ts.projectSystem {
exclude: [],
},
});
const et = new EventTracker([jsconfig, file]);
const et = new TestServerEventManager([jsconfig, file]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
projectId: Harness.mockHash("/jsconfig.json"),
@ -216,10 +216,10 @@ namespace ts.projectSystem {
it("detects whether language service was disabled", () => {
const file = makeFile("/a.js");
const tsconfig = makeFile("/jsconfig.json", {});
const et = new EventTracker([tsconfig, file]);
const et = new TestServerEventManager([tsconfig, file]);
et.host.getFileSize = () => server.maxProgramSizeForNonTsFiles + 1;
et.service.openClientFile(file.path);
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true);
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent);
et.assertProjectInfoTelemetryEvent({
projectId: Harness.mockHash("/jsconfig.json"),
fileStats: fileStats({ js: 1 }),
@ -235,63 +235,7 @@ namespace ts.projectSystem {
});
});
class EventTracker {
private events: server.ProjectServiceEvent[] = [];
readonly service: TestProjectService;
readonly host: projectSystem.TestServerHost;
constructor(files: projectSystem.FileOrFolder[]) {
this.host = createServerHost(files);
this.service = createProjectService(this.host, {
eventHandler: event => {
this.events.push(event);
},
});
}
getEvents(): ReadonlyArray<server.ProjectServiceEvent> {
const events = this.events;
this.events = [];
return events;
}
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile?: string): void {
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
projectId: Harness.mockHash(configFile || "/tsconfig.json"),
fileStats: fileStats({ ts: 1 }),
compilerOptions: {},
extends: false,
files: false,
include: false,
exclude: false,
compileOnSave: false,
typeAcquisition: {
enable: false,
exclude: false,
include: false,
},
configFileName: "tsconfig.json",
projectType: "configured",
languageServiceEnabled: true,
version: ts.version,
...partial,
});
}
getEvent<T extends server.ProjectServiceEvent>(eventName: T["eventName"], mayBeMore = false): T["data"] {
if (mayBeMore) { assert(this.events.length !== 0); }
else { assert.equal(this.events.length, 1); }
const event = this.events.shift();
assert.equal(event.eventName, eventName);
return event.data;
}
}
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
return { path, content: isString(content) ? "" : JSON.stringify(content) };
}
function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {
return { ts: 0, tsx: 0, dts: 0, js: 0, jsx: 0, ...nonZeroStats };
}
}

View File

@ -23,7 +23,7 @@ namespace ts {
const printerOptions = { newLine: NewLineKind.LineFeed };
const newLineCharacter = getNewLineCharacter(printerOptions);
function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
const getRuleProviderDefault = memoize(() => {
const options = {
indentSize: 4,
tabSize: 4,
@ -45,12 +45,38 @@ namespace ts {
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
};
if (action) {
action(options);
}
const rulesProvider = new formatting.RulesProvider();
rulesProvider.ensureUpToDate(options);
return rulesProvider;
});
const getRuleProviderNewlineBrace = memoize(() => {
const options = {
indentSize: 4,
tabSize: 4,
newLineCharacter,
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: true,
placeOpenBraceOnNewLineForControlBlocks: false,
};
const rulesProvider = new formatting.RulesProvider();
rulesProvider.ensureUpToDate(options);
return rulesProvider;
});
function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean) {
return placeOpenBraceOnNewLineForFunctions ? getRuleProviderNewlineBrace() : getRuleProviderDefault();
}
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.
@ -78,11 +104,11 @@ namespace ts {
}
}
function runSingleFileTest(caption: string, setupFormatOptions: (opts: FormatCodeSettings) => void, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) {
function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) {
it(caption, () => {
Harness.Baseline.runBaseline(`textChanges/${caption}.js`, () => {
const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true);
const rulesProvider = getRuleProvider(setupFormatOptions);
const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions);
const changeTracker = new textChanges.ChangeTracker(printerOptions.newLine, rulesProvider, validateNodes ? verifyPositions : undefined);
testBlock(sourceFile, changeTracker);
const changes = changeTracker.getChanges();
@ -94,10 +120,6 @@ namespace ts {
});
}
function setNewLineForOpenBraceInFunctions(opts: FormatCodeSettings) {
opts.placeOpenBraceOnNewLineForFunctions = true;
}
{
const text = `
namespace M
@ -120,7 +142,7 @@ namespace M
}
}
}`;
runSingleFileTest("extractMethodLike", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("extractMethodLike", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
const statements = (<Block>(<FunctionDeclaration>findChild("foo", sourceFile)).body).statements.slice(1);
const newFunction = createFunctionDeclaration(
/*decorators*/ undefined,
@ -155,7 +177,7 @@ function bar() {
return 2;
}
`;
runSingleFileTest("deleteRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") });
});
}
@ -175,19 +197,19 @@ var x = 1; // some comment - 1
var y = 2; // comment 3
var z = 3; // comment 4
`;
runSingleFileTest("deleteNode1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile));
});
runSingleFileTest("deleteNode2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNode3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("x", sourceFile));
});
}
@ -201,18 +223,18 @@ var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
`;
runSingleFileTest("deleteNodeRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile));
});
runSingleFileTest("deleteNodeRange2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNodeRange3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNodeRange4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
@ -257,14 +279,14 @@ var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceRange", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceRangeWithForcedIndentation", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 });
});
runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("replaceRangeNoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ true, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode);
});
@ -275,7 +297,7 @@ namespace A {
const x = 1, y = "2";
}
`;
runSingleFileTest("replaceNode1NoLineBreakBefore", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode1NoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode);
});
@ -289,19 +311,19 @@ var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("replaceNode5", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
@ -314,16 +336,16 @@ var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
@ -336,10 +358,10 @@ var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAt1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAt2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1"));
});
}
@ -354,16 +376,16 @@ namespace M {
// comment 6
var a = 4; // comment 7
}`;
runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter });
});
}
@ -389,7 +411,7 @@ class A {
}
}
`;
runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text2 = `
@ -399,7 +421,7 @@ class A {
}
}
`;
runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text3 = `
@ -409,31 +431,31 @@ class A {
}
}
`;
runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfter3-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
}
{
const text = `var a = 1, b = 2, c = 3;`;
runSingleFileTest("deleteNodeInList1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `var a = 1,b = 2,c = 3;`;
runSingleFileTest("deleteNodeInList1_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
@ -444,13 +466,13 @@ namespace M {
b = 2,
c = 3;
}`;
runSingleFileTest("deleteNodeInList4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
@ -463,13 +485,13 @@ namespace M {
// comment 4
c = 3; // comment 5
}`;
runSingleFileTest("deleteNodeInList4_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
@ -478,13 +500,13 @@ namespace M {
function foo(a: number, b: string, c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
@ -493,13 +515,13 @@ function foo(a: number, b: string, c = true) {
function foo(a: number,b: string,c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
@ -511,40 +533,40 @@ function foo(
c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
const x = 1, y = 2;`;
runSingleFileTest("insertNodeInListAfter1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const /*x*/ x = 1, /*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const x = 1;`;
runSingleFileTest("insertNodeInListAfter5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
@ -552,10 +574,10 @@ const x = 1;`;
const text = `
const x = 1,
y = 2;`;
runSingleFileTest("insertNodeInListAfter6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
@ -563,10 +585,10 @@ const x = 1,
const text = `
const /*x*/ x = 1,
/*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
@ -575,7 +597,7 @@ const /*x*/ x = 1,
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
@ -584,7 +606,7 @@ import {
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
@ -593,7 +615,7 @@ import {
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
@ -602,7 +624,7 @@ import {
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
@ -612,7 +634,7 @@ import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
@ -622,7 +644,7 @@ import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
@ -632,7 +654,7 @@ import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter16", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
@ -642,7 +664,7 @@ import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter17", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
@ -651,7 +673,7 @@ import {
import {
x0, x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter18", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
@ -660,7 +682,7 @@ import {
class A {
x;
}`;
runSingleFileTest("insertNodeAfterMultipleNodes", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfterMultipleNodes", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNodes = [];
for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) {
newNodes.push(
@ -678,7 +700,7 @@ class A {
x
}
`;
runSingleFileTest("insertNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
@ -688,7 +710,7 @@ class A {
x;
}
`;
runSingleFileTest("insertNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
@ -699,7 +721,7 @@ class A {
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
@ -710,7 +732,7 @@ class A {
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
@ -720,7 +742,7 @@ class A {
x = foo
}
`;
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
@ -738,7 +760,7 @@ class A {
}
}
`;
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
@ -755,7 +777,7 @@ interface A {
x
}
`;
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
@ -772,7 +794,7 @@ interface A {
x()
}
`;
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
@ -787,7 +809,7 @@ interface A {
const text = `
let x = foo
`;
runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createStatement(createParen(createLiteral(1)));
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode, { suffix: newLineCharacter });
});

View File

@ -80,6 +80,92 @@ namespace ts.tscWatch {
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
}
function checkOutputErrors(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, isInitial?: true, skipWaiting?: true) {
const outputs = host.getOutput();
const expectedOutputCount = (isInitial ? 0 : 1) + errors.length + (skipWaiting ? 0 : 1);
assert.equal(outputs.length, expectedOutputCount, "Outputs = " + outputs.toString());
let index = 0;
if (!isInitial) {
assertWatchDiagnosticAt(host, index, Diagnostics.File_change_detected_Starting_incremental_compilation);
index++;
}
forEach(errors, error => {
assertDiagnosticAt(host, index, error);
index++;
});
if (!skipWaiting) {
assertWatchDiagnosticAt(host, index, Diagnostics.Compilation_complete_Watching_for_file_changes);
}
host.clearOutput();
}
function assertDiagnosticAt(host: WatchedSystem, outputAt: number, diagnostic: Diagnostic) {
const output = host.getOutput()[outputAt];
assert.equal(output, formatDiagnostic(diagnostic, host), "outputs[" + outputAt + "] is " + output);
}
function assertWatchDiagnosticAt(host: WatchedSystem, outputAt: number, diagnosticMessage: DiagnosticMessage) {
const output = host.getOutput()[outputAt];
assert.isTrue(endsWith(output, getWatchDiagnosticWithoutDate(host, diagnosticMessage)), "outputs[" + outputAt + "] is " + output);
}
function getWatchDiagnosticWithoutDate(host: WatchedSystem, diagnosticMessage: DiagnosticMessage) {
return ` - ${flattenDiagnosticMessageText(getLocaleSpecificMessage(diagnosticMessage), host.newLine)}${host.newLine + host.newLine + host.newLine}`;
}
function getDiagnosticOfFileFrom(file: SourceFile, text: string, start: number, length: number, message: DiagnosticMessage): Diagnostic {
return {
file,
start,
length,
messageText: text,
category: message.category,
code: message.code,
};
}
function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
let text = getLocaleSpecificMessage(message);
if (arguments.length > 1) {
text = formatStringFromArgs(text, arguments, 1);
}
return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message);
}
function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
let text = getLocaleSpecificMessage(message);
if (arguments.length > 4) {
text = formatStringFromArgs(text, arguments, 4);
}
return getDiagnosticOfFileFrom(file, text, start, length, message);
}
function getUnknownCompilerOption(program: Program, configFile: FileOrFolder, option: string) {
const quotedOption = `"${option}"`;
return getDiagnosticOfFile(program.getCompilerOptions().configFile, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
}
function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic {
let text = getLocaleSpecificMessage(message);
if (arguments.length > 5) {
text = formatStringFromArgs(text, arguments, 5);
}
return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())),
text, start, length, message);
}
function getDiagnosticModuleNotFoundOfFile(program: Program, file: FileOrFolder, moduleName: string) {
const quotedModuleName = `"${moduleName}"`;
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
}
describe("tsc-watch program updates", () => {
const commonFile1: FileOrFolder = {
path: "/a/b/commonFile1.ts",
@ -168,7 +254,7 @@ namespace ts.tscWatch {
checkProgramRootFiles(watch(), [file1.path, file2.path]);
checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]);
const configDir = getDirectoryPath(configFile.path);
checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true);
checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true);
});
// TODO: if watching for config file creation
@ -183,7 +269,7 @@ namespace ts.tscWatch {
const host = createWatchedSystem([commonFile1, libFile, configFile]);
const watch = createWatchModeWithConfigFile(configFile.path, host);
const configDir = getDirectoryPath(configFile.path);
checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true);
checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true);
checkProgramRootFiles(watch(), [commonFile1.path]);
@ -233,9 +319,10 @@ namespace ts.tscWatch {
});
it("handles the missing files - that were added to program because they were added with ///<ref", () => {
const commonFile2Name = "commonFile2.ts";
const file1: FileOrFolder = {
path: "/a/b/commonFile1.ts",
content: `/// <reference path="commonFile2.ts"/>
content: `/// <reference path="${commonFile2Name}"/>
let x = y`
};
const host = createWatchedSystem([file1, libFile]);
@ -243,18 +330,16 @@ namespace ts.tscWatch {
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path]);
const errors = [
`a/b/commonFile1.ts(1,22): error TS6053: File '${commonFile2.path}' not found.${host.newLine}`,
`a/b/commonFile1.ts(2,29): error TS2304: Cannot find name 'y'.${host.newLine}`
];
checkOutputContains(host, errors);
host.clearOutput();
checkOutputErrors(host, [
getDiagnosticOfFileFromProgram(watch(), file1.path, file1.content.indexOf(commonFile2Name), commonFile2Name.length, Diagnostics.File_0_not_found, commonFile2.path),
getDiagnosticOfFileFromProgram(watch(), file1.path, file1.content.indexOf("y"), 1, Diagnostics.Cannot_find_name_0, "y")
], /*isInitial*/ true);
host.reloadFS([file1, commonFile2, libFile]);
host.runQueuedTimeoutCallbacks();
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path, commonFile2.path]);
checkOutputDoesNotContain(host, errors);
checkOutputErrors(host, emptyArray);
});
it("should reflect change in config file", () => {
@ -578,17 +663,19 @@ namespace ts.tscWatch {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ compilerOptions: {} })
};
const host = createWatchedSystem([file1, file2, config]);
const host = createWatchedSystem([file1, file2, libFile, config]);
const watch = createWatchModeWithConfigFile(config.path, host);
checkProgramActualFiles(watch(), [file1.path, file2.path]);
checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]);
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
host.clearOutput();
host.reloadFS([file1, file2]);
host.reloadFS([file1, file2, libFile]);
host.checkTimeoutQueueLengthAndRun(1);
assert.equal(host.exitCode, ExitStatus.DiagnosticsPresent_OutputsSkipped);
checkOutputContains(host, [`error TS6053: File '${config.path}' not found.${host.newLine}`]);
checkOutputErrors(host, [
getDiagnosticWithoutFile(Diagnostics.File_0_not_found, config.path)
], /*isInitial*/ undefined, /*skipWaiting*/ true);
});
it("Proper errors: document is not contained in project", () => {
@ -687,25 +774,25 @@ namespace ts.tscWatch {
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
content: 'import * as T from "./moduleFile"; T.bar();'
};
const host = createWatchedSystem([moduleFile, file1, libFile]);
createWatchModeWithoutConfigFile([file1.path], host);
const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n";
checkOutputDoesNotContain(host, [error]);
const watch = createWatchModeWithoutConfigFile([file1.path], host);
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
]);
host.clearOutput();
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
checkOutputErrors(host, emptyArray);
});
it("rename a module file and rename back should restore the states for configured projects", () => {
@ -715,31 +802,29 @@ namespace ts.tscWatch {
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
content: 'import * as T from "./moduleFile"; T.bar();'
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createWatchedSystem([moduleFile, file1, configFile, libFile]);
createWatchModeWithConfigFile(configFile.path, host);
const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n";
checkOutputDoesNotContain(host, [error]);
const watch = createWatchModeWithConfigFile(configFile.path, host);
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.clearOutput();
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
]);
host.clearOutput();
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
checkOutputErrors(host, emptyArray);
});
it("types should load from config file path if config exists", () => {
@ -771,18 +856,18 @@ namespace ts.tscWatch {
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
content: 'import * as T from "./moduleFile"; T.bar();'
};
const host = createWatchedSystem([file1, libFile]);
createWatchModeWithoutConfigFile([file1.path], host);
const watch = createWatchModeWithoutConfigFile([file1.path], host);
const error = `a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.${host.newLine}`;
checkOutputContains(host, [error]);
host.clearOutput();
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
], /*isInitial*/ true);
host.reloadFS([file1, moduleFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
checkOutputErrors(host, emptyArray);
});
it("Configure file diagnostics events are generated when the config file has errors", () => {
@ -801,14 +886,14 @@ namespace ts.tscWatch {
};
const host = createWatchedSystem([file, configFile, libFile]);
createWatchModeWithConfigFile(configFile.path, host);
checkOutputContains(host, [
`a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`,
`a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}`
]);
const watch = createWatchModeWithConfigFile(configFile.path, host);
checkOutputErrors(host, [
getUnknownCompilerOption(watch(), configFile, "foo"),
getUnknownCompilerOption(watch(), configFile, "allowJS")
], /*isInitial*/ true);
});
it("Configure file diagnostics events are generated when the config file doesn't have errors", () => {
it("If config file doesnt have errors, they are not reported", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
@ -822,13 +907,10 @@ namespace ts.tscWatch {
const host = createWatchedSystem([file, configFile, libFile]);
createWatchModeWithConfigFile(configFile.path, host);
checkOutputDoesNotContain(host, [
`a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`,
`a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}`
]);
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
});
it("Configure file diagnostics events are generated when the config file changes", () => {
it("Reports errors when the config file changes", () => {
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
@ -841,9 +923,8 @@ namespace ts.tscWatch {
};
const host = createWatchedSystem([file, configFile, libFile]);
createWatchModeWithConfigFile(configFile.path, host);
const error = `a/b/tsconfig.json(3,25): error TS5023: Unknown compiler option 'haha'.${host.newLine}`;
checkOutputDoesNotContain(host, [error]);
const watch = createWatchModeWithConfigFile(configFile.path, host);
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
configFile.content = `{
"compilerOptions": {
@ -852,15 +933,16 @@ namespace ts.tscWatch {
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [error]);
checkOutputErrors(host, [
getUnknownCompilerOption(watch(), configFile, "haha")
]);
host.clearOutput();
configFile.content = `{
"compilerOptions": {}
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputDoesNotContain(host, [error]);
checkOutputErrors(host, emptyArray);
});
it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => {
@ -935,29 +1017,28 @@ namespace ts.tscWatch {
}`;
const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment;
const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment;
const line = 5;
const errors = (line: number) => [
`a/b/tsconfig.json(${line},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n`,
`a/b/tsconfig.json(${line + 1},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n`
];
const configFile = {
path: "/a/b/tsconfig.json",
content: configFileContentWithComment
};
const host = createWatchedSystem([file, libFile, configFile]);
createWatchModeWithConfigFile(configFile.path, host);
checkOutputContains(host, errors(line));
checkOutputDoesNotContain(host, errors(line - 2));
host.clearOutput();
const files = [file, libFile, configFile];
const host = createWatchedSystem(files);
const watch = createWatchModeWithConfigFile(configFile.path, host);
const errors = () => [
getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"allowJs"'), '"allowJs"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration"),
getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"declaration"'), '"declaration"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration")
];
const intialErrors = errors();
checkOutputErrors(host, intialErrors, /*isInitial*/ true);
configFile.content = configFileContentWithoutCommentLine;
host.reloadFS([file, configFile]);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, errors(line - 2));
checkOutputDoesNotContain(host, errors(line));
const nowErrors = errors();
checkOutputErrors(host, nowErrors);
assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length);
assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length);
});
});
@ -1024,6 +1105,64 @@ namespace ts.tscWatch {
const outJs = "/a/out.js";
createWatchForOut(/*out*/ undefined, outJs);
});
function verifyFilesEmittedOnce(useOutFile: boolean) {
const file1: FileOrFolder = {
path: "/a/b/output/AnotherDependency/file1.d.ts",
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
};
const file2: FileOrFolder = {
path: "/a/b/dependencies/file2.d.ts",
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
};
const file3: FileOrFolder = {
path: "/a/b/project/src/main.ts",
content: "namespace Main { export function fooBar() {} }"
};
const file4: FileOrFolder = {
path: "/a/b/project/src/main2.ts",
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
};
const configFile: FileOrFolder = {
path: "/a/b/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: useOutFile ?
{ outFile: "../output/common.js", target: "es5" } :
{ outDir: "../output", target: "es5" },
files: [file1.path, file2.path, file3.path, file4.path]
})
};
const files = [file1, file2, file3, file4];
const allfiles = files.concat(configFile);
const host = createWatchedSystem(allfiles);
const originalWriteFile = host.writeFile.bind(host);
const mapOfFilesWritten = createMap<number>();
host.writeFile = (p: string, content: string) => {
const count = mapOfFilesWritten.get(p);
mapOfFilesWritten.set(p, count ? count + 1 : 1);
return originalWriteFile(p, content);
};
createWatchModeWithConfigFile(configFile.path, host);
if (useOutFile) {
// Only out file
assert.equal(mapOfFilesWritten.size, 1);
}
else {
// main.js and main2.js
assert.equal(mapOfFilesWritten.size, 2);
}
mapOfFilesWritten.forEach((value, key) => {
assert.equal(value, 1, "Key: " + key);
});
}
it("with --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ true);
});
it("without --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ false);
});
});
describe("tsc-watch emit for configured projects", () => {
@ -1485,23 +1624,20 @@ namespace ts.tscWatch {
path: "/a/d/f0.ts",
content: `import {x} from "f1"`
};
const imported = {
path: "/a/f1.ts",
content: `foo()`
};
const f1IsNotModule = `a/d/f0.ts(1,17): error TS2306: File '${imported.path}' is not a module.\n`;
const cannotFindFoo = `a/f1.ts(1,1): error TS2304: Cannot find name 'foo'.\n`;
const cannotAssignValue = "a/d/f0.ts(2,21): error TS2322: Type '1' is not assignable to type 'string'.\n";
const files = [root, imported, libFile];
const host = createWatchedSystem(files);
createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path);
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
// ensure that imported file was found
checkOutputContains(host, [f1IsNotModule, cannotFindFoo]);
host.clearOutput();
checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*isInitial*/ true);
const originalFileExists = host.fileExists;
{
@ -1517,8 +1653,11 @@ namespace ts.tscWatch {
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputContains(host, [f1IsNotModule, cannotAssignValue]);
host.clearOutput();
checkOutputErrors(host, [
f1IsNotModule,
getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"),
cannotFindFoo
]);
}
{
let fileExistsIsCalled = false;
@ -1534,13 +1673,13 @@ namespace ts.tscWatch {
root.content = `import {x} from "f2"`;
host.reloadFS(files);
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
host.runQueuedTimeoutCallbacks();
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
const cannotFindModuleF2 = `a/d/f0.ts(1,17): error TS2307: Cannot find module 'f2'.\n`;
checkOutputContains(host, [cannotFindModuleF2]);
host.clearOutput();
// ensure file has correct number of errors after edit
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "f2")
]);
assert.isTrue(fileExistsIsCalled);
}
@ -1561,7 +1700,7 @@ namespace ts.tscWatch {
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputContains(host, [f1IsNotModule, cannotFindFoo]);
checkOutputErrors(host, [f1IsNotModule, cannotFindFoo]);
assert.isTrue(fileExistsCalled);
}
});
@ -1593,12 +1732,12 @@ namespace ts.tscWatch {
return originalFileExists.call(host, fileName);
};
createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const barNotFound = `a/foo.ts(1,17): error TS2307: Cannot find module 'bar'.\n`;
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputContains(host, [barNotFound]);
host.clearOutput();
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
], /*isInitial*/ true);
fileExistsCalledForBar = false;
root.content = `import {y} from "bar"`;
@ -1606,7 +1745,7 @@ namespace ts.tscWatch {
host.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputDoesNotContain(host, [barNotFound]);
checkOutputErrors(host, emptyArray);
});
it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => {
@ -1617,7 +1756,7 @@ namespace ts.tscWatch {
const imported = {
path: `/a/bar.d.ts`,
content: `export const y = 1;`
content: `export const y = 1;export const x = 10;`
};
const files = [root, libFile];
@ -1635,25 +1774,227 @@ namespace ts.tscWatch {
return originalFileExists.call(host, fileName);
};
createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD });
const barNotFound = `a/foo.ts(1,17): error TS2307: Cannot find module 'bar'.\n`;
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputDoesNotContain(host, [barNotFound]);
host.clearOutput();
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
fileExistsCalledForBar = false;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputContains(host, [barNotFound]);
host.clearOutput();
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
]);
fileExistsCalledForBar = false;
host.reloadFS(filesWithImported);
host.checkTimeoutQueueLengthAndRun(1);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputDoesNotContain(host, [barNotFound]);
checkOutputErrors(host, emptyArray);
});
it("works when module resolution changes to ambient module", () => {
const root = {
path: "/a/b/foo.ts",
content: `import * as fs from "fs";`
};
const packageJson = {
path: "/a/b/node_modules/@types/node/package.json",
content: `
{
"main": ""
}
`
};
const nodeType = {
path: "/a/b/node_modules/@types/node/index.d.ts",
content: `
declare module "fs" {
export interface Stats {
isFile(): boolean;
}
}`
};
const files = [root, libFile];
const filesWithNodeType = files.concat(packageJson, nodeType);
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
const watch = createWatchModeWithoutConfigFile([root.path], host, { });
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
], /*isInitial*/ true);
host.reloadFS(filesWithNodeType);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray);
});
it("works when included file with ambient module changes", () => {
const root = {
path: "/a/b/foo.ts",
content: `
import * as fs from "fs";
import * as u from "url";
`
};
const file = {
path: "/a/b/bar.d.ts",
content: `
declare module "url" {
export interface Url {
href?: string;
}
}
`
};
const fileContentWithFS = `
declare module "fs" {
export interface Stats {
isFile(): boolean;
}
}
`;
const files = [root, file, libFile];
const host = createWatchedSystem(files, { currentDirectory: "/a/b" });
const watch = createWatchModeWithoutConfigFile([root.path, file.path], host, {});
checkOutputErrors(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
], /*isInitial*/ true);
file.content += fileContentWithFS;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray);
});
it("works when reusing program with files from external library", () => {
interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; }
const configDir = "/a/b/projects/myProject/src/";
const file1: FileOrFolder = {
path: configDir + "file1.ts",
content: 'import module1 = require("module1");\nmodule1("hello");'
};
const file2: FileOrFolder = {
path: configDir + "file2.ts",
content: 'import module11 = require("module1");\nmodule11("hello");'
};
const module1: FileOrFolder = {
path: "/a/b/projects/myProject/node_modules/module1/index.js",
content: "module.exports = options => { return options.toString(); }"
};
const configFile: FileOrFolder = {
path: configDir + "tsconfig.json",
content: JSON.stringify({
compilerOptions: {
allowJs: true,
rootDir: ".",
outDir: "../dist",
moduleResolution: "node",
maxNodeModuleJsDepth: 1
}
})
};
const outDirFolder = "/a/b/projects/myProject/dist/";
const programFiles = [file1, file2, module1, libFile];
const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" });
const watch = createWatchModeWithConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray, /*isInitial*/ true);
const expectedFiles: ExpectedFile[] = [
createExpectedEmittedFile(file1),
createExpectedEmittedFile(file2),
createExpectedToNotEmitFile("index.js"),
createExpectedToNotEmitFile("src/index.js"),
createExpectedToNotEmitFile("src/file1.js"),
createExpectedToNotEmitFile("src/file2.js"),
createExpectedToNotEmitFile("lib.js"),
createExpectedToNotEmitFile("lib.d.ts")
];
verifyExpectedFiles(expectedFiles);
file1.content += "\n;";
expectedFiles[0].content += ";\n"; // Only emit file1 with this change
expectedFiles[1].isExpectedToEmit = false;
host.reloadFS(programFiles.concat(configFile));
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray);
verifyExpectedFiles(expectedFiles);
function verifyExpectedFiles(expectedFiles: ExpectedFile[]) {
forEach(expectedFiles, f => {
assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit"));
if (f.isExpectedToEmit) {
assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path);
}
});
}
function createExpectedToNotEmitFile(fileName: string): ExpectedFile {
return {
path: outDirFolder + fileName,
isExpectedToEmit: false
};
}
function createExpectedEmittedFile(file: FileOrFolder): ExpectedFile {
return {
path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js,
isExpectedToEmit: true,
content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n"
};
}
});
});
describe("tsc-watch with when module emit is specified as node", () => {
it("when instead of filechanged recursive directory watcher is invoked", () => {
const configFile: FileOrFolder = {
path: "/a/rootFolder/project/tsconfig.json",
content: JSON.stringify({
"compilerOptions": {
"module": "none",
"allowJs": true,
"outDir": "Static/scripts/"
},
"include": [
"Scripts/**/*"
],
})
};
const outputFolder = "/a/rootFolder/project/Static/scripts/";
const file1: FileOrFolder = {
path: "/a/rootFolder/project/Scripts/TypeScript.ts",
content: "var z = 10;"
};
const file2: FileOrFolder = {
path: "/a/rootFolder/project/Scripts/Javascript.js",
content: "var zz = 10;"
};
const files = [configFile, file1, file2, libFile];
const host = createWatchedSystem(files);
const watch = createWatchModeWithConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
file1.content = "var zz30 = 100;";
host.reloadFS(files, /*invokeDirectoryWatcherInsteadOfFileChanged*/ true);
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js");
assert.isTrue(host.fileExists(outputFile1));
assert.equal(host.readFile(outputFile1), file1.content + host.newLine);
});
});
}

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,8 @@
namespace ts.projectSystem {
import TI = server.typingsInstaller;
import validatePackageName = JsTyping.validatePackageName;
import PackageNameValidationResult = JsTyping.PackageNameValidationResult;
interface InstallerParams {
globalTypingsCacheLocation?: string;
@ -266,7 +268,7 @@ namespace ts.projectSystem {
};
const host = createServerHost([file1]);
let enqueueIsCalled = false;
const installer = new (class extends Installer {
const installer: Installer = new (class extends Installer {
constructor() {
super(host, { typesRegistry: createTypesRegistry("jquery") });
}
@ -983,21 +985,21 @@ namespace ts.projectSystem {
for (let i = 0; i < 8; i++) {
packageName += packageName;
}
assert.equal(TI.validatePackageName(packageName), TI.PackageNameValidationResult.NameTooLong);
assert.equal(validatePackageName(packageName), PackageNameValidationResult.NameTooLong);
});
it("name cannot start with dot", () => {
assert.equal(TI.validatePackageName(".foo"), TI.PackageNameValidationResult.NameStartsWithDot);
assert.equal(validatePackageName(".foo"), PackageNameValidationResult.NameStartsWithDot);
});
it("name cannot start with underscore", () => {
assert.equal(TI.validatePackageName("_foo"), TI.PackageNameValidationResult.NameStartsWithUnderscore);
assert.equal(validatePackageName("_foo"), PackageNameValidationResult.NameStartsWithUnderscore);
});
it("scoped packages not supported", () => {
assert.equal(TI.validatePackageName("@scope/bar"), TI.PackageNameValidationResult.ScopedPackagesNotSupported);
assert.equal(validatePackageName("@scope/bar"), PackageNameValidationResult.ScopedPackagesNotSupported);
});
it("non URI safe characters are not supported", () => {
assert.equal(TI.validatePackageName(" scope "), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
assert.equal(TI.validatePackageName("; say Hello from TypeScript! #"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
assert.equal(TI.validatePackageName("a/b/c"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
assert.equal(validatePackageName(" scope "), PackageNameValidationResult.NameContainsNonURISafeCharacters);
assert.equal(validatePackageName("; say Hello from TypeScript! #"), PackageNameValidationResult.NameContainsNonURISafeCharacters);
assert.equal(validatePackageName("a/b/c"), PackageNameValidationResult.NameContainsNonURISafeCharacters);
});
});
@ -1250,7 +1252,7 @@ namespace ts.projectSystem {
const host = createServerHost([f1, packageFile]);
let beginEvent: server.BeginInstallTypes;
let endEvent: server.EndInstallTypes;
const installer = new (class extends Installer {
const installer: Installer = new (class extends Installer {
constructor() {
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
}

View File

@ -1,10 +1,17 @@
/// <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
content: `/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> {}`
};
export const safeList = {
@ -28,6 +35,7 @@ namespace ts.TestFSWithWatch {
executingFilePath?: string;
currentDirectory?: string;
newLine?: string;
useWindowsStylePaths?: boolean;
}
export function createWatchedSystem(fileOrFolderList: ReadonlyArray<FileOrFolder>, params?: TestServerHostCreationParameters): TestServerHost {
@ -39,7 +47,8 @@ namespace ts.TestFSWithWatch {
params.executingFilePath || getExecutingFilePathFromLibFile(),
params.currentDirectory || "/",
fileOrFolderList,
params.newLine);
params.newLine,
params.useWindowsStylePaths);
return host;
}
@ -52,7 +61,8 @@ namespace ts.TestFSWithWatch {
params.executingFilePath || getExecutingFilePathFromLibFile(),
params.currentDirectory || "/",
fileOrFolderList,
params.newLine);
params.newLine,
params.useWindowsStylePaths);
return host;
}
@ -95,7 +105,7 @@ namespace ts.TestFSWithWatch {
}
}
function getDiffInKeys(map: Map<any>, expectedKeys: ReadonlyArray<string>) {
function getDiffInKeys<T>(map: Map<T>, expectedKeys: ReadonlyArray<string>) {
if (map.size === expectedKeys.length) {
return "";
}
@ -122,8 +132,12 @@ namespace ts.TestFSWithWatch {
return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`;
}
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: ReadonlyArray<string>) {
export function verifyMapSize(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)}`);
}
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: ReadonlyArray<string>) {
verifyMapSize(caption, map, expectedKeys);
for (const name of expectedKeys) {
assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`);
}
@ -212,13 +226,13 @@ namespace ts.TestFSWithWatch {
directoryName: string;
}
export class TestServerHost implements server.ServerHost {
export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost {
args: string[] = [];
private readonly output: string[] = [];
private fs: Map<FSEntry> = createMap<FSEntry>();
private getCanonicalFileName: (s: string) => string;
getCanonicalFileName: (s: string) => string;
private toPath: (f: string) => Path;
private timeoutCallbacks = new Callbacks();
private immediateCallbacks = new Callbacks();
@ -226,14 +240,21 @@ namespace ts.TestFSWithWatch {
readonly watchedDirectories = createMultiMap<TestDirectoryWatcher>();
readonly watchedDirectoriesRecursive = createMultiMap<TestDirectoryWatcher>();
readonly watchedFiles = createMultiMap<TestFileWatcher>();
private readonly executingFilePath: string;
private readonly currentDirectory: string;
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n") {
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean) {
this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
this.executingFilePath = this.getHostSpecificPath(executingFilePath);
this.currentDirectory = this.getHostSpecificPath(currentDirectory);
this.reloadFS(fileOrFolderList);
}
getNewLine() {
return this.newLine;
}
toNormalizedAbsolutePath(s: string) {
return getNormalizedAbsolutePath(s, this.currentDirectory);
}
@ -242,11 +263,24 @@ namespace ts.TestFSWithWatch {
return this.toPath(this.toNormalizedAbsolutePath(s));
}
reloadFS(fileOrFolderList: ReadonlyArray<FileOrFolder>) {
getHostSpecificPath(s: string) {
if (this.useWindowsStylePath && s.startsWith(directorySeparator)) {
return "c:/" + s.substring(1);
}
return s;
}
reloadFS(fileOrFolderList: ReadonlyArray<FileOrFolder>, invokeDirectoryWatcherInsteadOfFileChanged?: boolean) {
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 : [])) {
fileOrFolderList = fileOrFolderList.concat(this.withSafeList ? safeList : []);
const filesOrFoldersToLoad: ReadonlyArray<FileOrFolder> = !this.useWindowsStylePath ? fileOrFolderList :
fileOrFolderList.map<FileOrFolder>(f => {
const result = clone(f);
result.path = this.getHostSpecificPath(f.path);
return result;
});
for (const fileOrDirectory of filesOrFoldersToLoad) {
const path = this.toFullPath(fileOrDirectory.path);
mapNewLeaves.set(path, true);
// If its a change
@ -257,7 +291,12 @@ namespace ts.TestFSWithWatch {
// Update file
if (currentEntry.content !== fileOrDirectory.content) {
currentEntry.content = fileOrDirectory.content;
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
if (invokeDirectoryWatcherInsteadOfFileChanged) {
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
}
else {
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
}
}
}
else {
@ -548,7 +587,7 @@ namespace ts.TestFSWithWatch {
const folder = this.toFolder(directoryName);
// base folder has to be present
const base = getDirectoryPath(folder.fullPath);
const base = getDirectoryPath(folder.path);
const baseFolder = this.fs.get(base) as Folder;
Debug.assert(isFolder(baseFolder));
@ -560,7 +599,7 @@ namespace ts.TestFSWithWatch {
const file = this.toFile({ path, content });
// base folder has to be present
const base = getDirectoryPath(file.fullPath);
const base = getDirectoryPath(file.path);
const folder = this.fs.get(base) as Folder;
Debug.assert(isFolder(folder));

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ interface Array<T> {
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T | undefined;
/**
@ -350,6 +351,7 @@ interface ReadonlyArray<T> {
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find<S extends T>(predicate: (this: void, value: T, index: number, obj: ReadonlyArray<T>) => value is S, thisArg?: any): S | undefined;
find(predicate: (value: T, index: number, obj: ReadonlyArray<T>) => boolean, thisArg?: any): T | undefined;
/**

View File

@ -3,7 +3,7 @@ interface ObjectConstructor {
* Returns an array of values of the enumerable properties of an object
* @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
values<T>(o: { [s: string]: T }): T[];
values<T>(o: { [s: string]: T } | { [n: number]: T }): T[];
/**
* Returns an array of values of the enumerable properties of an object
@ -15,7 +15,7 @@ interface ObjectConstructor {
* Returns an array of key/values of the enumerable properties of an object
* @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
entries<T>(o: { [s: string]: T }): [string, T][];
entries<T>(o: { [s: string]: T } | { [n: number]: T }): [string, T][];
/**
* Returns an array of key/values of the enumerable properties of an object

66
src/lib/es5.d.ts vendored
View File

@ -1050,7 +1050,8 @@ interface ReadonlyArray<T> {
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T, initialValue?: T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
@ -1062,7 +1063,8 @@ interface ReadonlyArray<T> {
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T, initialValue?: T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
@ -1200,7 +1202,8 @@ interface Array<T> {
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
@ -1212,7 +1215,8 @@ interface Array<T> {
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
@ -1647,7 +1651,8 @@ interface Int8Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -1671,7 +1676,8 @@ interface Int8Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -1914,7 +1920,8 @@ interface Uint8Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -1938,7 +1945,8 @@ interface Uint8Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -2181,7 +2189,8 @@ interface Uint8ClampedArray {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -2205,7 +2214,8 @@ interface Uint8ClampedArray {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -2446,7 +2456,8 @@ interface Int16Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -2470,7 +2481,8 @@ interface Int16Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -2714,7 +2726,8 @@ interface Uint16Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -2738,7 +2751,8 @@ interface Uint16Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -2981,7 +2995,8 @@ interface Int32Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -3005,7 +3020,8 @@ interface Int32Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -3247,7 +3263,8 @@ interface Uint32Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -3271,7 +3288,8 @@ interface Uint32Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -3514,7 +3532,8 @@ interface Float32Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -3538,7 +3557,8 @@ interface Float32Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.
@ -3782,7 +3802,8 @@ interface Float64Array {
* the accumulation. The first call to the callbackfn function provides this value as an argument
* instead of an array value.
*/
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue?: number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number): number;
reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array. The return value of
@ -3806,7 +3827,8 @@ interface Float64Array {
* the accumulation. The first call to the callbackfn function provides this value as an
* argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number): number;
reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue: number): number;
/**
* Calls the specified callback function for all the elements in an array, in descending order.

View File

@ -443,6 +443,8 @@ interface FileReader extends EventTarget, MSBaseReader {
readAsText(blob: Blob, encoding?: string): void;
addEventListener<K extends keyof MSBaseReaderEventMap>(type: K, listener: (this: FileReader, ev: MSBaseReaderEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof MSBaseReaderEventMap>(type: K, listener: (this: FileReader, ev: MSBaseReaderEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var FileReader: {
@ -525,6 +527,8 @@ interface IDBDatabase extends EventTarget {
addEventListener(type: "versionchange", listener: (ev: IDBVersionChangeEvent) => any, useCapture?: boolean): void;
addEventListener<K extends keyof IDBDatabaseEventMap>(type: K, listener: (this: IDBDatabase, ev: IDBDatabaseEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof IDBDatabaseEventMap>(type: K, listener: (this: IDBDatabase, ev: IDBDatabaseEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var IDBDatabase: {
@ -610,6 +614,8 @@ interface IDBOpenDBRequest extends IDBRequest {
onupgradeneeded: (this: IDBOpenDBRequest, ev: IDBVersionChangeEvent) => any;
addEventListener<K extends keyof IDBOpenDBRequestEventMap>(type: K, listener: (this: IDBOpenDBRequest, ev: IDBOpenDBRequestEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof IDBOpenDBRequestEventMap>(type: K, listener: (this: IDBOpenDBRequest, ev: IDBOpenDBRequestEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var IDBOpenDBRequest: {
@ -632,6 +638,8 @@ interface IDBRequest extends EventTarget {
readonly transaction: IDBTransaction;
addEventListener<K extends keyof IDBRequestEventMap>(type: K, listener: (this: IDBRequest, ev: IDBRequestEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof IDBRequestEventMap>(type: K, listener: (this: IDBRequest, ev: IDBRequestEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var IDBRequest: {
@ -659,6 +667,8 @@ interface IDBTransaction extends EventTarget {
readonly VERSION_CHANGE: string;
addEventListener<K extends keyof IDBTransactionEventMap>(type: K, listener: (this: IDBTransaction, ev: IDBTransactionEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof IDBTransactionEventMap>(type: K, listener: (this: IDBTransaction, ev: IDBTransactionEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var IDBTransaction: {
@ -725,6 +735,8 @@ interface MessagePort extends EventTarget {
start(): void;
addEventListener<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var MessagePort: {
@ -754,6 +766,8 @@ interface Notification extends EventTarget {
close(): void;
addEventListener<K extends keyof NotificationEventMap>(type: K, listener: (this: Notification, ev: NotificationEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof NotificationEventMap>(type: K, listener: (this: Notification, ev: NotificationEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var Notification: {
@ -982,6 +996,8 @@ interface ServiceWorker extends EventTarget, AbstractWorker {
postMessage(message: any, transfer?: any[]): void;
addEventListener<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var ServiceWorker: {
@ -1007,6 +1023,8 @@ interface ServiceWorkerRegistration extends EventTarget {
update(): Promise<void>;
addEventListener<K extends keyof ServiceWorkerRegistrationEventMap>(type: K, listener: (this: ServiceWorkerRegistration, ev: ServiceWorkerRegistrationEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof ServiceWorkerRegistrationEventMap>(type: K, listener: (this: ServiceWorkerRegistration, ev: ServiceWorkerRegistrationEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var ServiceWorkerRegistration: {
@ -1073,6 +1091,8 @@ interface WebSocket extends EventTarget {
readonly OPEN: number;
addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var WebSocket: {
@ -1094,6 +1114,8 @@ interface Worker extends EventTarget, AbstractWorker {
terminate(): void;
addEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var Worker: {
@ -1135,6 +1157,8 @@ interface XMLHttpRequest extends EventTarget, XMLHttpRequestEventTarget {
readonly UNSENT: number;
addEventListener<K extends keyof XMLHttpRequestEventMap>(type: K, listener: (this: XMLHttpRequest, ev: XMLHttpRequestEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof XMLHttpRequestEventMap>(type: K, listener: (this: XMLHttpRequest, ev: XMLHttpRequestEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var XMLHttpRequest: {
@ -1150,6 +1174,8 @@ declare var XMLHttpRequest: {
interface XMLHttpRequestUpload extends EventTarget, XMLHttpRequestEventTarget {
addEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestUpload, ev: XMLHttpRequestEventTargetEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestUpload, ev: XMLHttpRequestEventTargetEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var XMLHttpRequestUpload: {
@ -1165,6 +1191,8 @@ interface AbstractWorker {
onerror: (this: AbstractWorker, ev: ErrorEvent) => any;
addEventListener<K extends keyof AbstractWorkerEventMap>(type: K, listener: (this: AbstractWorker, ev: AbstractWorkerEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof AbstractWorkerEventMap>(type: K, listener: (this: AbstractWorker, ev: AbstractWorkerEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
interface Body {
@ -1203,6 +1231,8 @@ interface MSBaseReader {
readonly LOADING: number;
addEventListener<K extends keyof MSBaseReaderEventMap>(type: K, listener: (this: MSBaseReader, ev: MSBaseReaderEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof MSBaseReaderEventMap>(type: K, listener: (this: MSBaseReader, ev: MSBaseReaderEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
interface NavigatorBeacon {
@ -1258,6 +1288,8 @@ interface XMLHttpRequestEventTarget {
ontimeout: (this: XMLHttpRequest, ev: ProgressEvent) => any;
addEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
interface Client {
@ -1294,6 +1326,8 @@ interface DedicatedWorkerGlobalScope extends WorkerGlobalScope {
postMessage(message: any, transfer?: any[]): void;
addEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>(type: K, listener: (this: DedicatedWorkerGlobalScope, ev: DedicatedWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>(type: K, listener: (this: DedicatedWorkerGlobalScope, ev: DedicatedWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var DedicatedWorkerGlobalScope: {
@ -1405,6 +1439,8 @@ interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
skipWaiting(): Promise<void>;
addEventListener<K extends keyof ServiceWorkerGlobalScopeEventMap>(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof ServiceWorkerGlobalScopeEventMap>(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var ServiceWorkerGlobalScope: {
@ -1450,6 +1486,8 @@ interface WorkerGlobalScope extends EventTarget, WorkerUtils, WindowConsole, Glo
createImageBitmap(image: ImageBitmap | ImageData | Blob, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
addEventListener<K extends keyof WorkerGlobalScopeEventMap>(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
removeEventListener<K extends keyof WorkerGlobalScopeEventMap>(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
}
declare var WorkerGlobalScope: {
@ -1642,7 +1680,7 @@ interface EcKeyAlgorithm extends KeyAlgorithm {
typedCurve: string;
}
interface EcKeyImportParams {
interface EcKeyImportParams extends Algorithm {
namedCurve: string;
}
@ -1819,7 +1857,6 @@ declare function msWriteProfilerMark(profilerMarkName: string): void;
declare function createImageBitmap(image: ImageBitmap | ImageData | Blob, options?: ImageBitmapOptions): Promise<ImageBitmap>;
declare function createImageBitmap(image: ImageBitmap | ImageData | Blob, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
declare function dispatchEvent(evt: Event): boolean;
declare function removeEventListener(type: string, listener?: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
declare var indexedDB: IDBFactory;
declare var msIndexedDB: IDBFactory;
declare var navigator: WorkerNavigator;
@ -1838,9 +1875,10 @@ declare function btoa(rawString: string): string;
declare var console: Console;
declare function fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
declare function dispatchEvent(evt: Event): boolean;
declare function removeEventListener(type: string, listener?: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
declare function addEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>(type: K, listener: (this: DedicatedWorkerGlobalScope, ev: DedicatedWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
declare function addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
declare function removeEventListener<K extends keyof DedicatedWorkerGlobalScopeEventMap>(type: K, listener: (this: DedicatedWorkerGlobalScope, ev: DedicatedWorkerGlobalScopeEventMap[K]) => any, useCapture?: boolean): void;
declare function removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
type AlgorithmIdentifier = string | Algorithm;
type BodyInit = any;
type IDBKeyPath = string;

View File

@ -14,7 +14,7 @@ namespace ts.server {
}
/* @internal */
export function extractMessage(message: string) {
export function extractMessage(message: string): string {
// Read the content length
const contentLengthPrefix = "Content-Length: ";
const lines = message.split(/\r?\n/);
@ -198,7 +198,9 @@ namespace ts.server {
const request = this.processRequest<protocol.CompletionDetailsRequest>(CommandNames.CompletionDetails, args);
const response = this.processResponse<protocol.CompletionDetailsResponse>(request);
Debug.assert(response.body.length === 1, "Unexpected length of completion details response body.");
return response.body[0];
const convertedCodeActions = map(response.body[0].codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
return { ...response.body[0], codeActions: convertedCodeActions };
}
getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol {
@ -540,6 +542,8 @@ namespace ts.server {
return response.body.map(entry => this.convertCodeActions(entry, file));
}
applyCodeActionCommand = notImplemented;
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
return typeof positionOrRange === "number"
? this.createFileLocationRequestArgs(fileName, positionOrRange)

View File

@ -220,13 +220,28 @@ namespace ts.server {
interface FilePropertyReader<T> {
getFileName(f: T): string;
getScriptKind(f: T): ScriptKind;
getScriptKind(f: T, extraFileExtensions?: JsFileExtensionInfo[]): ScriptKind;
hasMixedContent(f: T, extraFileExtensions: JsFileExtensionInfo[]): boolean;
}
const fileNamePropertyReader: FilePropertyReader<string> = {
getFileName: x => x,
getScriptKind: _ => undefined,
getScriptKind: (fileName, extraFileExtensions) => {
let result: ScriptKind;
if (extraFileExtensions) {
const fileExtension = getAnyExtensionFromPath(fileName);
if (fileExtension) {
some(extraFileExtensions, info => {
if (info.extension === fileExtension) {
result = info.scriptKind;
return true;
}
return false;
});
}
}
return result;
},
hasMixedContent: (fileName, extraFileExtensions) => some(extraFileExtensions, ext => ext.isMixedContent && fileExtensionIs(fileName, ext.extension)),
};
@ -337,9 +352,9 @@ namespace ts.server {
*/
readonly configuredProjects = createMap<ConfiguredProject>();
/**
* list of open files
* Open files: with value being project root path, and key being Path of the file that is open
*/
readonly openFiles: ScriptInfo[] = [];
readonly openFiles = createMap<NormalizedPath>();
private compilerOptionsForInferredProjects: CompilerOptions;
private compilerOptionsForInferredProjectsPerProjectRoot = createMap<CompilerOptions>();
@ -403,7 +418,7 @@ namespace ts.server {
this.globalPlugins = opts.globalPlugins || emptyArray;
this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray;
this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads;
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.host.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
@ -431,6 +446,11 @@ namespace ts.server {
this.watchFilePath = (host, file, cb, path, watchType, project) => ts.addFilePathWatcherWithLogging(host, file, cb, path, this.createWatcherLog(watchType, project));
this.watchDirectory = (host, dir, cb, flags, watchType, project) => ts.addDirectoryWatcherWithLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project));
}
else if (this.logger.loggingEnabled()) {
this.watchFile = (host, file, cb, watchType, project) => ts.addFileWatcherWithOnlyTriggerLogging(host, file, cb, this.createWatcherLog(watchType, project));
this.watchFilePath = (host, file, cb, path, watchType, project) => ts.addFilePathWatcherWithOnlyTriggerLogging(host, file, cb, path, this.createWatcherLog(watchType, project));
this.watchDirectory = (host, dir, cb, flags, watchType, project) => ts.addDirectoryWatcherWithOnlyTriggerLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project));
}
else {
this.watchFile = ts.addFileWatcher;
this.watchFilePath = ts.addFilePathWatcher;
@ -447,6 +467,16 @@ namespace ts.server {
return toPath(fileName, this.currentDirectory, this.toCanonicalFileName);
}
/*@internal*/
getExecutingFilePath() {
return this.getNormalizedAbsolutePath(this.host.getExecutingFilePath());
}
/*@internal*/
getNormalizedAbsolutePath(fileName: string) {
return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory());
}
/* @internal */
getChangedFiles_TestOnly() {
return this.changedFiles;
@ -539,6 +569,11 @@ namespace ts.server {
});
}
/*@internal*/
hasPendingProjectUpdate(project: Project) {
return this.pendingProjectUpdates.has(project.getProjectName());
}
private sendProjectsUpdatedInBackgroundEvent() {
if (!this.eventHandler) {
return;
@ -547,7 +582,7 @@ namespace ts.server {
const event: ProjectsUpdatedInBackgroundEvent = {
eventName: ProjectsUpdatedInBackgroundEvent,
data: {
openFiles: this.openFiles.map(f => f.fileName)
openFiles: arrayFrom(this.openFiles.keys(), path => this.getScriptInfoForPath(path as Path).fileName)
}
};
this.eventHandler(event);
@ -575,9 +610,9 @@ namespace ts.server {
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
// previously we did not expose a way for user to change these settings and this option was enabled by default
compilerOptions.allowNonTsExtensions = true;
if (projectRootPath) {
this.compilerOptionsForInferredProjectsPerProjectRoot.set(projectRootPath, compilerOptions);
const canonicalProjectRootPath = projectRootPath && this.toCanonicalFileName(projectRootPath);
if (canonicalProjectRootPath) {
this.compilerOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, compilerOptions);
}
else {
this.compilerOptionsForInferredProjects = compilerOptions;
@ -593,9 +628,9 @@ namespace ts.server {
// root path
// - Inferred projects with a projectRootPath, if the new options apply to that
// project root path.
if (projectRootPath ?
project.projectRootPath === projectRootPath :
!project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) {
if (canonicalProjectRootPath ?
project.projectRootPath === canonicalProjectRootPath :
!project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) {
project.setCompilerOptions(compilerOptions);
project.compileOnSaveEnabled = compilerOptions.compileOnSave;
project.markAsDirty();
@ -780,8 +815,14 @@ namespace ts.server {
);
}
/** Gets the config file existence info for the configured project */
/*@internal*/
getConfigFileExistenceInfo(project: ConfiguredProject) {
return this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
}
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (eventKind === FileWatcherEventKind.Deleted) {
// Update the cached status
// We arent updating or removing the cached config file presence info as that will be taken care of by
@ -827,6 +868,9 @@ namespace ts.server {
this.logger.info(`remove project: ${project.getRootFiles().toString()}`);
project.close();
if (Debug.shouldAssert(AssertionLevel.Normal)) {
this.filenameToScriptInfo.forEach(info => Debug.assert(!info.isAttached(project)));
}
// Remove the project from pending project updates
this.pendingProjectUpdates.delete(project.getProjectName());
@ -847,7 +891,7 @@ namespace ts.server {
}
/*@internal*/
assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) {
assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
Debug.assert(info.isOrphan());
const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) ||
@ -880,18 +924,6 @@ namespace ts.server {
return project;
}
private addToListOfOpenFiles(info: ScriptInfo) {
Debug.assert(!info.isOrphan());
for (const p of info.containingProjects) {
// file is the part of configured project, addref the project
if (p.projectKind === ProjectKind.Configured) {
((<ConfiguredProject>p)).addOpenRef();
}
}
this.openFiles.push(info);
}
/**
* Remove this file from the set of open, non-configured files.
* @param info The file that has been closed or newly configured
@ -903,7 +935,7 @@ namespace ts.server {
info.close();
this.stopWatchingConfigFilesForClosedScriptInfo(info);
unorderedRemoveItem(this.openFiles, info);
this.openFiles.delete(info.path);
const fileExists = this.host.fileExists(info.fileName);
@ -914,10 +946,8 @@ namespace ts.server {
if (info.hasMixedContent) {
info.registerFileUpdate();
}
// Delete the reference to the open configured projects but
// do not remove the project so that we can reuse this project
// Do not remove the project so that we can reuse this project
// if it would need to be re-created with next file open
(<ConfiguredProject>p).deleteOpenRef();
}
else if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) {
// If this was the open root file of inferred project
@ -944,11 +974,12 @@ namespace ts.server {
}
// collect orphaned files and assign them to inferred project just like we treat open of a file
for (const f of this.openFiles) {
this.openFiles.forEach((projectRootPath, path) => {
const f = this.getScriptInfoForPath(path as Path);
if (f.isOrphan()) {
this.assignOrphanScriptInfoToInferredProject(f);
this.assignOrphanScriptInfoToInferredProject(f, projectRootPath);
}
}
});
// Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
// is postponed to next file open so that if file from same project is opened,
@ -1007,7 +1038,7 @@ namespace ts.server {
}
private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (configFileExistenceInfo) {
Debug.assert(configFileExistenceInfo.exists);
// close existing watcher
@ -1036,7 +1067,7 @@ namespace ts.server {
}
private setConfigFileExistenceInfoByClosedConfiguredProject(closedProject: ConfiguredProject) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(closedProject.canonicalConfigFilePath);
const configFileExistenceInfo = this.getConfigFileExistenceInfo(closedProject);
Debug.assert(!!configFileExistenceInfo);
if (configFileExistenceInfo.openFilesImpactedByConfigFile.size) {
const configFileName = closedProject.getConfigFilePath();
@ -1142,7 +1173,7 @@ namespace ts.server {
* This is called by inferred project whenever script info is added as a root
*/
/* @internal */
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
Debug.assert(info.isScriptOpen());
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
@ -1164,7 +1195,7 @@ namespace ts.server {
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
this.createConfigFileWatcherOfConfigFileExistence(configFileName, canonicalConfigFilePath, configFileExistenceInfo);
}
});
}, projectRootPath);
}
/**
@ -1200,7 +1231,7 @@ namespace ts.server {
projectRootPath?: NormalizedPath) {
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) {
while (!projectRootPath || containsPath(projectRootPath, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames)) {
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json"));
@ -1232,7 +1263,7 @@ namespace ts.server {
* The server must start searching from the directory containing
* the newly opened file.
*/
private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) {
private getConfigFileNameForFile(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
Debug.assert(info.isScriptOpen());
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
const configFileName = this.forEachConfigFileLocation(info,
@ -1271,9 +1302,9 @@ namespace ts.server {
printProjects(this.inferredProjects, counter);
this.logger.info("Open files: ");
for (const rootFile of this.openFiles) {
this.logger.info(`\t${rootFile.fileName}`);
}
this.openFiles.forEach((projectRootPath, path) => {
this.logger.info(`\tFileName: ${this.getScriptInfoForPath(path as Path).fileName} ProjectRootPath: ${projectRootPath}`);
});
this.logger.endGroup();
}
@ -1320,10 +1351,10 @@ namespace ts.server {
const projectOptions: ProjectOptions = {
files: parsedCommandLine.fileNames,
compilerOptions: parsedCommandLine.options,
configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined,
configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined,
configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined,
configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined,
configHasExtendsProperty: parsedCommandLine.raw.extends !== undefined,
configHasFilesProperty: parsedCommandLine.raw.files !== undefined,
configHasIncludeProperty: parsedCommandLine.raw.include !== undefined,
configHasExcludeProperty: parsedCommandLine.raw.exclude !== undefined,
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories),
typeAcquisition: parsedCommandLine.typeAcquisition,
compileOnSave: parsedCommandLine.compileOnSave
@ -1486,7 +1517,7 @@ namespace ts.server {
scriptInfo = normalizedPath;
}
else {
const scriptKind = propertyReader.getScriptKind(f);
const scriptKind = propertyReader.getScriptKind(f, this.hostConfiguration.extraFileExtensions);
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.directoryStructureHost);
path = scriptInfo.path;
@ -1561,26 +1592,30 @@ namespace ts.server {
project.watchWildcards(projectOptions.wildcardDirectories);
}
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave);
this.sendConfigFileDiagEvent(project, configFileName);
}
private sendConfigFileDiagEvent(project: ConfiguredProject, triggerFile: NormalizedPath) {
if (!this.eventHandler) {
return;
}
this.eventHandler(<ConfigFileDiagEvent>{
eventName: ConfigFileDiagEvent,
data: { configFileName, diagnostics: project.getGlobalProjectErrors() || [], triggerFile: configFileName }
data: { configFileName: project.getConfigFilePath(), diagnostics: project.getAllProjectErrors(), triggerFile }
});
}
private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: string | undefined): InferredProject | undefined {
private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: NormalizedPath | undefined): InferredProject | undefined {
if (!this.useInferredProjectPerProjectRoot) {
return undefined;
}
if (projectRootPath) {
const canonicalProjectRootPath = this.toCanonicalFileName(projectRootPath);
// if we have an explicit project root path, find (or create) the matching inferred project.
for (const project of this.inferredProjects) {
if (project.projectRootPath === projectRootPath) {
if (project.projectRootPath === canonicalProjectRootPath) {
return project;
}
}
@ -1621,12 +1656,13 @@ namespace ts.server {
return this.inferredProjects[0];
}
return this.createInferredProject(/*rootDirectoryForResolution*/ undefined, /*isSingleInferredProject*/ true);
// Single inferred project does not have a project root and hence no current directory
return this.createInferredProject(/*currentDirectory*/ undefined, /*isSingleInferredProject*/ true);
}
private createInferredProject(rootDirectoryForResolution: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject {
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, rootDirectoryForResolution);
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory);
if (isSingleInferredProject) {
this.inferredProjects.unshift(project);
}
@ -1761,23 +1797,19 @@ namespace ts.server {
// as there is no need to load contents of the files from the disk
// Reload Projects
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false);
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue);
this.refreshInferredProjects();
}
private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) {
// Get open files to reload projects for
const openFiles = mapDefinedIter(
configFileExistenceInfo.openFilesImpactedByConfigFile.entries(),
([path, isRootOfInferredProject]) => {
if (!ignoreIfNotRootOfInferredProject || isRootOfInferredProject) {
const info = this.getScriptInfoForPath(path as Path);
Debug.assert(!!info);
return info;
}
}
this.reloadConfiguredProjectForFiles(
configFileExistenceInfo.openFilesImpactedByConfigFile,
/*delayReload*/ true,
ignoreIfNotRootOfInferredProject ?
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
returnTrue // Reload all the open files impacted by config file
);
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
this.delayInferredProjectsRefresh();
}
@ -1786,16 +1818,24 @@ namespace ts.server {
* If the config file is found and it refers to existing project, it reloads it either immediately
* or schedules it for reload depending on delayReload option
* If the there is no existing project it just opens the configured project for the config file
* reloadForInfo provides a way to filter out files to reload configured project for
*/
private reloadConfiguredProjectForFiles(openFiles: ReadonlyArray<ScriptInfo>, delayReload: boolean) {
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean) {
const updatedProjects = createMap<true>();
// try to reload config file for all open files
for (const info of openFiles) {
openFiles.forEach((openFileValue, path) => {
// Filter out the files that need to be ignored
if (!shouldReloadProjectFor(openFileValue)) {
return;
}
const info = this.getScriptInfoForPath(path as Path);
Debug.assert(info.isScriptOpen());
// This tries to search for a tsconfig.json for the given file. If we found it,
// we first detect if there is already a configured project created for it: if so,
// we re- read the tsconfig file content and update the project only if we havent already done so
// otherwise we create a new one.
const configFileName = this.getConfigFileNameForFile(info);
const configFileName = this.getConfigFileNameForFile(info, this.openFiles.get(path));
if (configFileName) {
const project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
@ -1813,7 +1853,7 @@ namespace ts.server {
updatedProjects.set(configFileName, true);
}
}
}
});
}
/**
@ -1858,16 +1898,17 @@ namespace ts.server {
this.logger.info("refreshInferredProjects: updating project structure from ...");
this.printProjects();
for (const info of this.openFiles) {
this.openFiles.forEach((projectRootPath, path) => {
const info = this.getScriptInfoForPath(path as Path);
// collect all orphaned script infos from open files
if (info.isOrphan()) {
this.assignOrphanScriptInfoToInferredProject(info);
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
}
else {
// Or remove the root of inferred project if is referenced in more than one projects
this.removeRootOfInferredProjectIfNowPartOfOtherProject(info);
}
}
});
for (const p of this.inferredProjects) {
p.updateGraph();
@ -1888,6 +1929,7 @@ namespace ts.server {
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
let configFileName: NormalizedPath;
let sendConfigFileDiagEvent = false;
let configFileErrors: ReadonlyArray<Diagnostic>;
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, fileContent, scriptKind, hasMixedContent);
@ -1898,14 +1940,8 @@ namespace ts.server {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createConfiguredProject(configFileName);
// even if opening config file was successful, it could still
// contain errors that were tolerated.
const errors = project.getGlobalProjectErrors();
if (errors && errors.length > 0) {
// set configFileErrors only when the errors array is non-empty
configFileErrors = errors;
}
// Send the event only if the project got created as part of this open request
sendConfigFileDiagEvent = true;
}
}
}
@ -1919,9 +1955,19 @@ namespace ts.server {
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
// So if it still doesnt have any containing projects, it needs to be part of inferred project
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file event
configFileName = undefined;
sendConfigFileDiagEvent = false;
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
}
this.addToListOfOpenFiles(info);
Debug.assert(!info.isOrphan());
this.openFiles.set(info.path, projectRootPath);
if (sendConfigFileDiagEvent) {
configFileErrors = project.getAllProjectErrors();
this.sendConfigFileDiagEvent(project as ConfiguredProject, fileName);
}
// Remove the configured projects that have zero references from open files.
// This was postponed from closeOpenFile to after opening next file,
@ -1938,6 +1984,7 @@ namespace ts.server {
// the file from that old project is reopened because of opening file from here.
this.deleteOrphanScriptInfoNotInAnyProject();
this.printProjects();
return { configFileName, configFileErrors };
}
@ -2015,11 +2062,14 @@ namespace ts.server {
}
}
private closeConfiguredProject(configFile: NormalizedPath): boolean {
private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath): boolean {
const configuredProject = this.findConfiguredProjectByProjectName(configFile);
if (configuredProject && configuredProject.deleteOpenRef() === 0) {
this.removeProject(configuredProject);
return true;
if (configuredProject) {
configuredProject.deleteExternalProjectReference();
if (!configuredProject.hasOpenRef()) {
this.removeProject(configuredProject);
return true;
}
}
return false;
}
@ -2030,7 +2080,7 @@ namespace ts.server {
if (configFiles) {
let shouldRefreshInferredProjects = false;
for (const configFile of configFiles) {
if (this.closeConfiguredProject(configFile)) {
if (this.closeConfiguredProjectReferencedFromExternalProject(configFile)) {
shouldRefreshInferredProjects = true;
}
}
@ -2225,7 +2275,7 @@ namespace ts.server {
const newConfig = tsConfigFiles[iNew];
const oldConfig = oldConfigFiles[iOld];
if (oldConfig < newConfig) {
this.closeConfiguredProject(oldConfig);
this.closeConfiguredProjectReferencedFromExternalProject(oldConfig);
iOld++;
}
else if (oldConfig > newConfig) {
@ -2240,7 +2290,7 @@ namespace ts.server {
}
for (let i = iOld; i < oldConfigFiles.length; i++) {
// projects for all remaining old config files should be closed
this.closeConfiguredProject(oldConfigFiles[i]);
this.closeConfiguredProjectReferencedFromExternalProject(oldConfigFiles[i]);
}
}
}
@ -2255,7 +2305,7 @@ namespace ts.server {
}
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
project.addOpenRef();
project.addExternalProjectReference();
}
}
}

View File

@ -195,6 +195,9 @@ namespace ts.server {
return result.module;
}
/*@internal*/
readonly currentDirectory: string;
/*@internal*/
constructor(
/*@internal*/readonly projectName: string,
@ -206,7 +209,8 @@ namespace ts.server {
private compilerOptions: CompilerOptions,
public compileOnSaveEnabled: boolean,
/*@internal*/public directoryStructureHost: DirectoryStructureHost,
rootDirectoryForResolution: string | undefined) {
currentDirectory: string | undefined) {
this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || "");
this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds);
if (!this.compilerOptions) {
@ -229,18 +233,35 @@ namespace ts.server {
this.realpath = path => host.realpath(path);
}
// Use the current directory as resolution root only if the project created using current directory string
this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory);
this.languageService = createLanguageService(this, this.documentRegistry);
this.resolutionCache = createResolutionCache(this, rootDirectoryForResolution);
if (!languageServiceEnabled) {
this.disableLanguageService();
}
this.markAsDirty();
}
isKnownTypesPackageName(name: string): boolean {
return this.typingsCache.isKnownTypesPackageName(name);
}
installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult> {
return this.typingsCache.installPackage({ ...options, projectRootPath: this.toPath(this.currentDirectory) });
}
private get typingsCache(): TypingsCache {
return this.projectService.typingsCache;
}
// Method of LanguageServiceHost
getCompilationSettings() {
return this.compilerOptions;
}
// Method to support public API
getCompilerOptions() {
return this.getCompilationSettings();
}
getNewLine() {
return this.directoryStructureHost.newLine;
}
@ -301,11 +322,11 @@ namespace ts.server {
}
getCurrentDirectory(): string {
return this.directoryStructureHost.getCurrentDirectory();
return this.currentDirectory;
}
getDefaultLibFileName() {
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.host.getExecutingFilePath()));
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.getExecutingFilePath()));
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions));
}
@ -422,35 +443,36 @@ namespace ts.server {
if (!this.builder) {
this.builder = createBuilder({
getCanonicalFileName: this.projectService.toCanonicalFileName,
getEmitOutput: (_program, sourceFile, emitOnlyDts, isDetailed) =>
this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
computeHash: data =>
this.projectService.host.createHash(data),
shouldEmitFile: sourceFile =>
!this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
computeHash: data => this.projectService.host.createHash(data)
});
}
}
private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
}
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
if (!this.languageServiceEnabled) {
return [];
}
this.updateGraph();
this.ensureBuilder();
return this.builder.getFilesAffectedBy(this.program, scriptInfo.path);
return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path),
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
}
/**
* Returns true if emit was conducted
*/
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
this.ensureBuilder();
const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path);
if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) {
return false;
}
const { emitSkipped, outputFiles } = this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(scriptInfo.fileName);
if (!emitSkipped) {
const projectRootPath = this.getProjectRootPath();
for (const outputFile of outputFiles) {
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory);
writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
}
}
@ -479,7 +501,6 @@ namespace ts.server {
getProjectName() {
return this.projectName;
}
abstract getProjectRootPath(): string | undefined;
abstract getTypeAcquisition(): TypeAcquisition;
getExternalFiles(): SortedReadonlyArray<string> {
@ -495,25 +516,23 @@ namespace ts.server {
close() {
if (this.program) {
// if we have a program - release all files that are enlisted in program
// if we have a program - release all files that are enlisted in program but arent root
// The releasing of the roots happens later
// The project could have pending update remaining and hence the info could be in the files but not in program graph
for (const f of this.program.getSourceFiles()) {
const info = this.projectService.getScriptInfo(f.fileName);
// We might not find the script info in case its not associated with the project any more
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
if (info) {
info.detachFromProject(this);
}
this.detachScriptInfoIfNotRoot(f.fileName);
}
}
if (!this.program || !this.languageServiceEnabled) {
// release all root files either if there is no program or language service is disabled.
// in the latter case set of root files can be larger than the set of files in program.
for (const root of this.rootFiles) {
root.detachFromProject(this);
}
// Release external files
forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile));
// Always remove root files from the project
for (const root of this.rootFiles) {
root.detachFromProject(this);
}
this.rootFiles = undefined;
this.rootFilesMap = undefined;
this.externalFiles = undefined;
this.program = undefined;
this.builder = undefined;
this.resolutionCache.clear();
@ -532,6 +551,15 @@ namespace ts.server {
this.languageService = undefined;
}
private detachScriptInfoIfNotRoot(uncheckedFilename: string) {
const info = this.projectService.getScriptInfo(uncheckedFilename);
// We might not find the script info in case its not associated with the project any more
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
if (info && !this.isRoot(info)) {
info.detachFromProject(this);
}
}
isClosed() {
return this.rootFiles === undefined;
}
@ -561,19 +589,12 @@ namespace ts.server {
return map(this.program.getSourceFiles(), sourceFile => {
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
if (!scriptInfo) {
Debug.fail(`scriptInfo for a file '${sourceFile.fileName}' is missing.`);
Debug.fail(`scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' is missing.`);
}
return scriptInfo;
});
}
private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) {
if (!this.languageServiceEnabled) {
return undefined;
}
return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
}
getExcludedFiles(): ReadonlyArray<NormalizedPath> {
return emptyArray;
}
@ -732,7 +753,6 @@ namespace ts.server {
*/
updateGraph(): boolean {
this.resolutionCache.startRecordingFilesWithChangedResolutions();
this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
let hasChanges = this.updateGraphWorker();
@ -792,9 +812,10 @@ namespace ts.server {
private updateGraphWorker() {
const oldProgram = this.program;
Debug.assert(!this.isClosed(), "Called update graph worker of closed project");
this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`);
const start = timestamp();
this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
this.resolutionCache.startCachingPerDirectoryResolution();
this.program = this.languageService.getProgram();
this.resolutionCache.finishCachingPerDirectoryResolution();
@ -882,9 +903,7 @@ namespace ts.server {
}
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(
fileName, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.directoryStructureHost
);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(fileName);
if (scriptInfo && !scriptInfo.isAttached(this)) {
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
}
@ -892,7 +911,7 @@ namespace ts.server {
}
getScriptInfo(uncheckedFileName: string) {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
return this.projectService.getScriptInfo(uncheckedFileName);
}
filesToString(writeProjectFileNames: boolean) {
@ -927,16 +946,6 @@ namespace ts.server {
}
}
reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean {
const script = this.projectService.getScriptInfoForNormalizedPath(filename);
if (script) {
Debug.assert(script.isAttached(this));
script.reloadFromFile(tempFileName);
return true;
}
return false;
}
/* @internal */
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
this.updateGraph();
@ -1037,13 +1046,16 @@ namespace ts.server {
super.setCompilerOptions(newOptions);
}
/** this is canonical project root path */
readonly projectRootPath: string | undefined;
/*@internal*/
constructor(
projectService: ProjectService,
documentRegistry: DocumentRegistry,
compilerOptions: CompilerOptions,
public readonly projectRootPath: string | undefined,
rootDirectoryForResolution: string | undefined) {
projectRootPath: NormalizedPath | undefined,
currentDirectory: string | undefined) {
super(InferredProject.newName(),
ProjectKind.Inferred,
projectService,
@ -1053,11 +1065,13 @@ namespace ts.server {
compilerOptions,
/*compileOnSaveEnabled*/ false,
projectService.host,
rootDirectoryForResolution);
currentDirectory);
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
}
addRoot(info: ScriptInfo) {
this.projectService.startWatchingConfigFilesForInferredProjectRoot(info);
Debug.assert(info.isScriptOpen());
this.projectService.startWatchingConfigFilesForInferredProjectRoot(info, this.projectService.openFiles.get(info.path));
if (!this._isJsInferredProject && info.isJavaScript()) {
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
}
@ -1082,12 +1096,6 @@ namespace ts.server {
this.getRootScriptInfos().length === 1;
}
getProjectRootPath() {
return this.projectRootPath ||
// Single inferred project does not have a project root.
!this.projectService.useSingleInferredProject && getDirectoryPath(this.getRootFiles()[0]);
}
close() {
forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info));
super.close();
@ -1122,8 +1130,8 @@ namespace ts.server {
private plugins: PluginModule[] = [];
/** Used for configured projects which may have multiple open roots */
openRefCount = 0;
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;
private projectErrors: Diagnostic[];
@ -1183,7 +1191,7 @@ namespace ts.server {
// Search our peer node_modules, then any globally-specified probe paths
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
const searchPaths = [combinePaths(host.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
const searchPaths = [combinePaths(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
if (this.projectService.allowLocalPluginLoads) {
const local = getDirectoryPath(this.canonicalConfigFilePath);
@ -1263,22 +1271,18 @@ namespace ts.server {
}
}
getProjectRootPath() {
return getDirectoryPath(this.getConfigFilePath());
}
/**
* Get the errors that dont have any file name associated
*/
getGlobalProjectErrors(): ReadonlyArray<Diagnostic> {
return filter(this.projectErrors, diagnostic => !diagnostic.file);
return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray;
}
/**
* Get all the project errors
*/
getAllProjectErrors(): ReadonlyArray<Diagnostic> {
return this.projectErrors;
return this.projectErrors || emptyArray;
}
setProjectErrors(projectErrors: Diagnostic[]) {
@ -1327,27 +1331,54 @@ namespace ts.server {
}
close() {
super.close();
if (this.configFileWatcher) {
this.configFileWatcher.close();
this.configFileWatcher = undefined;
}
this.stopWatchingWildCards();
this.projectErrors = undefined;
this.configFileSpecs = undefined;
super.close();
}
addOpenRef() {
this.openRefCount++;
/* @internal */
addExternalProjectReference() {
this.externalProjectRefCount++;
}
deleteOpenRef() {
this.openRefCount--;
return this.openRefCount;
/* @internal */
deleteExternalProjectReference() {
this.externalProjectRefCount--;
}
/** Returns true if the project is needed by any of the open script info/external project */
/* @internal */
hasOpenRef() {
return !!this.openRefCount;
if (!!this.externalProjectRefCount) {
return true;
}
// Closed project doesnt have any reference
if (this.isClosed()) {
return false;
}
const configFileExistenceInfo = this.projectService.getConfigFileExistenceInfo(this);
if (this.projectService.hasPendingProjectUpdate(this)) {
// If there is pending update for this project,
// we dont know if this project would be needed by any of the open files impacted by this config file
// In that case keep the project alive if there are open files impacted by this project
return !!configFileExistenceInfo.openFilesImpactedByConfigFile.size;
}
// If there is no pending update for this project,
// We know exact set of open files that get impacted by this configured project as the files in the project
// The project is referenced only if open files impacted by this project are present in this project
return forEachEntry(
configFileExistenceInfo.openFilesImpactedByConfigFile,
(_value, infoPath) => this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path))
) || false;
}
getEffectiveTypeRoots() {
@ -1379,13 +1410,14 @@ namespace ts.server {
compilerOptions: CompilerOptions,
languageServiceEnabled: boolean,
public compileOnSaveEnabled: boolean,
private readonly projectFilePath?: string) {
projectFilePath?: string) {
super(externalProjectName,
ProjectKind.External,
projectService,
documentRegistry,
/*hasExplicitListOfFiles*/ true,
languageServiceEnabled, compilerOptions,
languageServiceEnabled,
compilerOptions,
compileOnSaveEnabled,
projectService.host,
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
@ -1395,16 +1427,6 @@ namespace ts.server {
return this.excludedFiles;
}
getProjectRootPath() {
if (this.projectFilePath) {
return getDirectoryPath(this.projectFilePath);
}
// if the projectFilePath is not given, we make the assumption that the project name
// is the path of the project file. AS the project name is provided by VS, we need to
// normalize slashes before using it as a file name.
return getDirectoryPath(normalizeSlashes(this.getProjectName()));
}
getTypeAcquisition() {
return this.typeAcquisition;
}

View File

@ -94,6 +94,7 @@ namespace ts.server.protocol {
BreakpointStatement = "breakpointStatement",
CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects",
GetCodeFixes = "getCodeFixes",
ApplyCodeActionCommand = "applyCodeActionCommand",
/* @internal */
GetCodeFixesFull = "getCodeFixes-full",
GetSupportedCodeFixes = "getSupportedCodeFixes",
@ -125,6 +126,8 @@ namespace ts.server.protocol {
* Client-initiated request message
*/
export interface Request extends Message {
type: "request";
/**
* The command to execute
*/
@ -147,6 +150,8 @@ namespace ts.server.protocol {
* Server-initiated event message
*/
export interface Event extends Message {
type: "event";
/**
* Name of event
*/
@ -162,6 +167,8 @@ namespace ts.server.protocol {
* Response by server to client request message.
*/
export interface Response extends Message {
type: "response";
/**
* Sequence number of the request message.
*/
@ -178,7 +185,8 @@ namespace ts.server.protocol {
command: string;
/**
* Contains error message if success === false.
* If success === false, this should always be provided.
* Otherwise, may (or may not) contain a success message.
*/
message?: string;
@ -520,6 +528,14 @@ namespace ts.server.protocol {
arguments: CodeFixRequestArgs;
}
export interface ApplyCodeActionCommandRequest extends Request {
command: CommandTypes.ApplyCodeActionCommand;
arguments: ApplyCodeActionCommandRequestArgs;
}
// All we need is the `success` and `message` fields of Response.
export interface ApplyCodeActionCommandResponse extends Response {}
export interface FileRangeRequestArgs extends FileRequestArgs {
/**
* The line number for the request (1-based).
@ -564,6 +580,10 @@ namespace ts.server.protocol {
errorCodes?: number[];
}
export interface ApplyCodeActionCommandRequestArgs extends FileRequestArgs {
command: {};
}
/**
* Response for GetCodeFixes request.
*/
@ -1541,6 +1561,8 @@ namespace ts.server.protocol {
description: string;
/** Text changes to apply to each file as part of the code action */
changes: FileCodeEdits[];
/** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */
commands?: {}[];
}
/**
@ -1603,7 +1625,12 @@ namespace ts.server.protocol {
/**
* Names of one or more entries for which to obtain details.
*/
entryNames: string[];
entryNames: (string | CompletionEntryIdentifier)[];
}
export interface CompletionEntryIdentifier {
name: string;
source: string;
}
/**
@ -1658,6 +1685,15 @@ namespace ts.server.protocol {
* this span should be used instead of the default one.
*/
replacementSpan?: TextSpan;
/**
* Indicates whether commiting this completion entry will require additional code actions to be
* made to avoid errors. The CompletionEntryDetails will have these actions.
*/
hasAction?: true;
/**
* Identifier (not necessarily human-readable) identifying where this completion came from.
*/
source?: string;
}
/**
@ -1690,6 +1726,16 @@ namespace ts.server.protocol {
* JSDoc tags for the symbol.
*/
tags: JSDocTagInfo[];
/**
* The associated code actions for this entry
*/
codeActions?: CodeAction[];
/**
* Human-readable description of the `source` from the CompletionEntry.
*/
source?: SymbolDisplayPart[];
}
export interface CompletionsResponse extends Response {

View File

@ -67,7 +67,10 @@ namespace ts.server {
this.lineMap = undefined;
}
/** returns true if text changed */
/**
* Set the contents as newText
* returns true if text changed
*/
public reload(newText: string) {
Debug.assert(newText !== undefined);
@ -87,31 +90,31 @@ namespace ts.server {
}
}
/** returns true if text changed */
public reloadFromDisk() {
let reloaded = false;
if (!this.pendingReloadFromDisk && !this.ownFileText) {
reloaded = this.reload(this.getFileText());
this.ownFileText = true;
}
/**
* Reads the contents from tempFile(if supplied) or own file and sets it as contents
* returns true if text changed
*/
public reloadWithFileText(tempFileName?: string) {
const reloaded = this.reload(this.getFileText(tempFileName));
this.ownFileText = !tempFileName || tempFileName === this.fileName;
return reloaded;
}
/**
* Reloads the contents from the file if there is no pending reload from disk or the contents of file are same as file text
* returns true if text changed
*/
public reloadFromDisk() {
if (!this.pendingReloadFromDisk && !this.ownFileText) {
return this.reloadWithFileText();
}
return false;
}
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.useScriptVersionCacheIfValidOrOpen()
? this.svc.getSnapshot()
@ -180,8 +183,7 @@ namespace ts.server {
private getOrLoadText() {
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;
this.reloadWithFileText();
}
return this.text;
}
@ -385,7 +387,7 @@ namespace ts.server {
this.markContainingProjectsAsDirty();
}
else {
if (this.textStorage.reloadFromFile(tempFileName)) {
if (this.textStorage.reloadWithFileText(tempFileName)) {
this.markContainingProjectsAsDirty();
}
}

View File

@ -250,6 +250,9 @@ namespace ts.server {
private activeRequestCount = 0;
private requestQueue: QueuedOperation[] = [];
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
private requestedRegistry: boolean;
private typesRegistryCache: Map<void> | undefined;
// This number is essentially arbitrary. Processing more than one typings request
// at a time makes sense, but having too many in the pipe results in a hang
@ -258,7 +261,7 @@ namespace ts.server {
// buffer, but we have yet to find a way to retrieve that value.
private static readonly maxActiveRequestCount = 10;
private static readonly requestDelayMillis = 100;
private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void, reject(reason: any): void };
constructor(
private readonly telemetryEnabled: boolean,
@ -278,6 +281,31 @@ namespace ts.server {
}
}
isKnownTypesPackageName(name: string): boolean {
// We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package.
const validationResult = JsTyping.validatePackageName(name);
if (validationResult !== JsTyping.PackageNameValidationResult.Ok) {
return false;
}
if (this.requestedRegistry) {
return !!this.typesRegistryCache && this.typesRegistryCache.has(name);
}
this.requestedRegistry = true;
this.send({ kind: "typesRegistry" });
return false;
}
installPackage(options: InstallPackageOptionsWithProjectRootPath): Promise<ApplyCodeActionCommandResult> {
const rq: InstallPackageRequest = { kind: "installPackage", ...options };
this.send(rq);
Debug.assert(this.packageInstalledPromise === undefined);
return new Promise((resolve, reject) => {
this.packageInstalledPromise = { resolve, reject };
});
}
private reportInstallerProcessId() {
if (this.installerPidReported) {
return;
@ -321,13 +349,13 @@ namespace ts.server {
const execArgv: string[] = [];
for (const arg of process.execArgv) {
const match = /^--(debug|inspect)(=(\d+))?$/.exec(arg);
const match = /^--((?:debug|inspect)(?:-brk)?)(?:=(\d+))?$/.exec(arg);
if (match) {
// if port is specified - use port + 1
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
const currentPort = match[3] !== undefined
? +match[3]
: match[1] === "debug" ? 5858 : 9229;
const currentPort = match[2] !== undefined
? +match[2]
: match[1].charAt(0) === "d" ? 5858 : 9229;
execArgv.push(`--${match[1]}=${currentPort + 1}`);
break;
}
@ -343,23 +371,27 @@ namespace ts.server {
}
onProjectClosed(p: Project): void {
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
this.send({ projectName: p.getProjectName(), kind: "closeProject" });
}
private send(rq: TypingInstallerRequestUnion): void {
this.installer.send(rq);
}
enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void {
const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
if (this.logger.hasLevel(LogLevel.verbose)) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
this.logger.info(`Scheduling throttled operation:${stringifyIndented(request)}`);
}
}
const operationId = project.getProjectName();
const operation = () => {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
this.logger.info(`Sending request:${stringifyIndented(request)}`);
}
this.installer.send(request);
this.send(request);
};
const queuedRequest: QueuedOperation = { operationId, operation };
@ -375,12 +407,26 @@ namespace ts.server {
}
}
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Received response: ${JSON.stringify(response)}`);
this.logger.info(`Received response:${stringifyIndented(response)}`);
}
switch (response.kind) {
case EventTypesRegistry:
this.typesRegistryCache = ts.createMapFromTemplate(response.typesRegistry);
break;
case EventPackageInstalled: {
const { success, message } = response;
if (success) {
this.packageInstalledPromise.resolve({ successMessage: message });
}
else {
this.packageInstalledPromise.reject(message);
}
this.packageInstalledPromise = undefined;
break;
}
case EventInitializationFailed:
{
if (!this.eventSender) {
@ -580,7 +626,7 @@ namespace ts.server {
function createLogger() {
const cmdLineLogFileName = findArgument("--logFile");
const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity"));
const envLogOptions = parseLoggingEnvironmentString(process.env["TSS_LOG"]);
const envLogOptions = parseLoggingEnvironmentString(process.env.TSS_LOG);
const logFileName = cmdLineLogFileName
? stripQuotes(cmdLineLogFileName)
@ -753,10 +799,23 @@ namespace ts.server {
const sys = <ServerHost>ts.sys;
// use watchGuard process on Windows when node version is 4 or later
const useWatchGuard = process.platform === "win32" && getNodeMajorVersion() >= 4;
const originalWatchDirectory: ServerHost["watchDirectory"] = sys.watchDirectory.bind(sys);
const noopWatcher: FileWatcher = { close: noop };
// This is the function that catches the exceptions when watching directory, and yet lets project service continue to function
// Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point
function watchDirectorySwallowingException(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
try {
return originalWatchDirectory(path, callback, recursive);
}
catch (e) {
logger.info(`Exception when creating directory watcher: ${e.message}`);
return noopWatcher;
}
}
if (useWatchGuard) {
const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined);
const statusCache = createMap<boolean>();
const originalWatchDirectory = sys.watchDirectory;
sys.watchDirectory = function (path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive);
let status = cacheKey && statusCache.get(cacheKey);
@ -767,7 +826,7 @@ namespace ts.server {
try {
const args = [combinePaths(__dirname, "watchGuard.js"), path];
if (logger.hasLevel(LogLevel.verbose)) {
logger.info(`Starting ${process.execPath} with args ${JSON.stringify(args)}`);
logger.info(`Starting ${process.execPath} with args:${stringifyIndented(args)}`);
}
childProcess.execFileSync(process.execPath, args, { stdio: "ignore", env: { "ELECTRON_RUN_AS_NODE": "1" } });
status = true;
@ -790,14 +849,17 @@ namespace ts.server {
}
if (status) {
// this drive is safe to use - call real 'watchDirectory'
return originalWatchDirectory.call(sys, path, callback, recursive);
return watchDirectorySwallowingException(path, callback, recursive);
}
else {
// this drive is unsafe - return no-op watcher
return { close() { } };
return noopWatcher;
}
};
}
else {
sys.watchDirectory = watchDirectorySwallowingException;
}
// Override sys.write because fs.writeSync is not reliable on Node 4
sys.write = (s: string) => writeMessage(new Buffer(s, "utf8"));

View File

@ -131,7 +131,7 @@ namespace ts.server {
const json = JSON.stringify(msg);
if (verboseLogging) {
logger.info(msg.type + ": " + json);
logger.info(`${msg.type}:${indent(json)}`);
}
const len = byteLength(json, "utf8");
@ -383,9 +383,9 @@ namespace ts.server {
public logError(err: Error, cmd: string) {
let msg = "Exception on executing command " + cmd;
if (err.message) {
msg += ":\n" + err.message;
msg += ":\n" + indent(err.message);
if ((<StackTraceError>err).stack) {
msg += "\n" + (<StackTraceError>err).stack;
msg += "\n" + indent((<StackTraceError>err).stack);
}
}
this.logger.msg(msg, Msg.Err);
@ -411,19 +411,27 @@ namespace ts.server {
this.send(ev);
}
public output(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) {
// For backwards-compatibility only.
public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void {
this.doOutput(info, cmdName, reqSeq, /*success*/ !errorMsg, errorMsg);
}
private doOutput(info: {} | undefined, cmdName: string, reqSeq: number, success: boolean, message?: string): void {
const res: protocol.Response = {
seq: 0,
type: "response",
command: cmdName,
request_seq: reqSeq,
success: !errorMsg,
success,
};
if (!errorMsg) {
if (success) {
res.body = info;
}
else {
res.message = errorMsg;
Debug.assert(info === undefined);
}
if (message) {
res.message = message;
}
this.send(res);
}
@ -969,13 +977,7 @@ namespace ts.server {
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
*/
private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: NormalizedPath) {
const { configFileName, configFileErrors } = this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath);
if (this.eventHandler) {
this.eventHandler({
eventName: "configFileDiag",
data: { triggerFile: fileName, configFileName, diagnostics: configFileErrors || emptyArray }
});
}
this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath);
}
private getPosition(args: protocol.FileLocationRequestArgs, scriptInfo: ScriptInfo): number {
@ -1184,11 +1186,12 @@ namespace ts.server {
const completions = project.getLanguageService().getCompletionsAtPosition(file, position);
if (simplifiedResult) {
return mapDefined(completions && completions.entries, entry => {
return mapDefined<CompletionEntry, protocol.CompletionEntry>(completions && completions.entries, entry => {
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source } = entry;
const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
// Use `hasAction || undefined` to avoid serializing `false`.
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source };
}
}).sort((a, b) => compareStrings(a.name, b.name));
}
@ -1199,10 +1202,21 @@ namespace ts.server {
private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): ReadonlyArray<protocol.CompletionEntryDetails> {
const { file, project } = this.getFileAndProject(args);
const position = this.getPositionInFile(args, file);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const formattingOptions = project.projectService.getFormatCodeOptions(file);
return mapDefined(args.entryNames, entryName =>
project.getLanguageService().getCompletionEntryDetails(file, position, entryName));
return mapDefined<string | protocol.CompletionEntryIdentifier, protocol.CompletionEntryDetails>(args.entryNames, entryName => {
const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName;
const details = project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source);
if (details) {
const mappedCodeActions = map(details.codeActions, action => this.mapCodeAction(action, scriptInfo));
return { ...details, codeActions: mappedCodeActions };
}
else {
return undefined;
}
});
}
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
@ -1298,11 +1312,13 @@ namespace ts.server {
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, /*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);
const info = this.projectService.getScriptInfoForNormalizedPath(file);
if (info) {
this.changeSeq++;
// make sure no changes happen before this one is finished
if (info.reloadFromFile(tempFileName)) {
this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
}
}
}
@ -1501,16 +1517,18 @@ namespace ts.server {
}
if (simplifiedResult) {
const file = result.renameFilename;
let location: protocol.Location | undefined;
if (file !== undefined && result.renameLocation !== undefined) {
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(file));
location = renameScriptInfo.positionToLineOffset(result.renameLocation);
const { renameFilename, renameLocation, edits } = result;
let mappedRenameLocation: protocol.Location | undefined;
if (renameFilename !== undefined && renameLocation !== undefined) {
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename));
const snapshot = renameScriptInfo.getSnapshot();
const oldText = snapshot.getText(0, snapshot.getLength());
mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits);
}
return {
renameLocation: location,
renameFilename: file,
edits: result.edits.map(change => this.mapTextChangesToCodeEdits(project, change))
renameLocation: mappedRenameLocation,
renameFilename,
edits: edits.map(change => this.mapTextChangesToCodeEdits(project, change))
};
}
else {
@ -1540,6 +1558,15 @@ namespace ts.server {
}
}
private applyCodeActionCommand(commandName: string, requestSeq: number, args: protocol.ApplyCodeActionCommandRequestArgs): void {
const { file, project } = this.getFileAndProject(args);
const output = (success: boolean, message: string) => this.doOutput({}, commandName, requestSeq, success, message);
const command = args.command as CodeActionCommand; // They should be sending back the command we sent them.
project.getLanguageService().applyCodeActionCommand(file, command).then(
({ successMessage }) => { output(/*success*/ true, successMessage); },
error => { output(/*success*/ false, error); });
}
private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) {
let startPosition: number = undefined, endPosition: number = undefined;
if (args.startPosition !== undefined) {
@ -1562,14 +1589,12 @@ namespace ts.server {
return { startPosition, endPosition };
}
private mapCodeAction(codeAction: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
return {
description: codeAction.description,
changes: codeAction.changes.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
}))
};
private mapCodeAction({ description, changes: unmappedChanges, commands }: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
const changes = unmappedChanges.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
}));
return { description, changes, commands };
}
private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits {
@ -1608,7 +1633,7 @@ namespace ts.server {
}
// No need to analyze lib.d.ts
const fileNamesInProject = fileNames.filter(value => value.indexOf("lib.d.ts") < 0);
const fileNamesInProject = fileNames.filter(value => !stringContains(value, "lib.d.ts"));
if (fileNamesInProject.length === 0) {
return;
}
@ -1655,15 +1680,15 @@ namespace ts.server {
exit() {
}
private notRequired() {
private notRequired(): HandlerResponse {
return { responseRequired: false };
}
private requiredResponse(response: any) {
private requiredResponse(response: {}): HandlerResponse {
return { response, responseRequired: true };
}
private handlers = createMapFromTemplate<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({
private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false);
// TODO: report errors
@ -1841,7 +1866,7 @@ namespace ts.server {
},
[CommandNames.Configure]: (request: protocol.ConfigureRequest) => {
this.projectService.setHostConfiguration(request.arguments);
this.output(undefined, CommandNames.Configure, request.seq);
this.doOutput(/*info*/ undefined, CommandNames.Configure, request.seq, /*success*/ true);
return this.notRequired();
},
[CommandNames.Reload]: (request: protocol.ReloadRequest) => {
@ -1908,6 +1933,10 @@ namespace ts.server {
[CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => {
this.applyCodeActionCommand(request.command, request.seq, request.arguments);
return this.notRequired(); // Response will come asynchronously.
},
[CommandNames.GetSupportedCodeFixes]: () => {
return this.requiredResponse(this.getSupportedCodeFixes());
},
@ -1922,7 +1951,7 @@ namespace ts.server {
}
});
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
if (this.handlers.has(command)) {
throw new Error(`Protocol handler already exists for command "${command}"`);
}
@ -1951,14 +1980,14 @@ namespace ts.server {
}
}
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
public executeCommand(request: protocol.Request): HandlerResponse {
const handler = this.handlers.get(request.command);
if (handler) {
return this.executeWithRequestId(request.seq, () => handler(request));
}
else {
this.logger.msg(`Unrecognized JSON command: ${JSON.stringify(request)}`, Msg.Err);
this.output(undefined, CommandNames.Unknown, request.seq, `Unrecognized JSON command: ${request.command}`);
this.logger.msg(`Unrecognized JSON command:${stringifyIndented(request)}`, Msg.Err);
this.doOutput(/*info*/ undefined, CommandNames.Unknown, request.seq, /*success*/ false, `Unrecognized JSON command: ${request.command}`);
return { responseRequired: false };
}
}
@ -1969,7 +1998,7 @@ namespace ts.server {
if (this.logger.hasLevel(LogLevel.requestTime)) {
start = this.hrtime();
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`request: ${message}`);
this.logger.info(`request:${indent(message)}`);
}
}
@ -1989,25 +2018,53 @@ namespace ts.server {
}
if (response) {
this.output(response, request.command, request.seq);
this.doOutput(response, request.command, request.seq, /*success*/ true);
}
else if (responseRequired) {
this.output(undefined, request.command, request.seq, "No content available.");
this.doOutput(/*info*/ undefined, request.command, request.seq, /*success*/ false, "No content available.");
}
}
catch (err) {
if (err instanceof OperationCanceledException) {
// Handle cancellation exceptions
this.output({ canceled: true }, request.command, request.seq);
this.doOutput({ canceled: true }, request.command, request.seq, /*success*/ true);
return;
}
this.logError(err, message);
this.output(
undefined,
this.doOutput(
/*info*/ undefined,
request ? request.command : CommandNames.Unknown,
request ? request.seq : 0,
/*success*/ false,
"Error processing request. " + (<StackTraceError>err).message + "\n" + (<StackTraceError>err).stack);
}
}
}
export interface HandlerResponse {
response?: {};
responseRequired?: boolean;
}
/* @internal */ // Exported only for tests
export function getLocationInNewDocument(oldText: string, renameFilename: string, renameLocation: number, edits: ReadonlyArray<FileTextChanges>): protocol.Location {
const newText = applyEdits(oldText, renameFilename, edits);
const { line, character } = computeLineAndCharacterOfPosition(computeLineStarts(newText), renameLocation);
return { line: line + 1, offset: character + 1 };
}
function applyEdits(text: string, textFilename: string, edits: ReadonlyArray<FileTextChanges>): string {
for (const { fileName, textChanges } of edits) {
if (fileName !== textFilename) {
continue;
}
for (let i = textChanges.length - 1; i >= 0; i--) {
const { newText, span: { start, length } } = textChanges[i];
text = text.slice(0, start) + newText + text.slice(start + length);
}
}
return text;
}
}

View File

@ -3,6 +3,8 @@
namespace ts.server {
export const ActionSet: ActionSet = "action::set";
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
export const EventTypesRegistry: EventTypesRegistry = "event::typesRegistry";
export const EventPackageInstalled: EventPackageInstalled = "event::packageInstalled";
export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes";
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
export const EventInitializationFailed: EventInitializationFailed = "event::initializationFailed";

View File

@ -28,12 +28,14 @@ declare namespace ts.server {
" __sortedArrayBrand": any;
}
export interface TypingInstallerRequest {
export interface TypingInstallerRequestWithProjectName {
readonly projectName: string;
readonly kind: "discover" | "closeProject";
}
export interface DiscoverTypings extends TypingInstallerRequest {
/* @internal */
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest;
export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
@ -43,18 +45,46 @@ declare namespace ts.server {
readonly kind: "discover";
}
export interface CloseProject extends TypingInstallerRequest {
export interface CloseProject extends TypingInstallerRequestWithProjectName {
readonly kind: "closeProject";
}
export interface TypesRegistryRequest {
readonly kind: "typesRegistry";
}
export interface InstallPackageRequest {
readonly kind: "installPackage";
readonly fileName: Path;
readonly packageName: string;
readonly projectRootPath: Path;
}
export type ActionSet = "action::set";
export type ActionInvalidate = "action::invalidate";
export type EventTypesRegistry = "event::typesRegistry";
export type EventPackageInstalled = "event::packageInstalled";
export type EventBeginInstallTypes = "event::beginInstallTypes";
export type EventEndInstallTypes = "event::endInstallTypes";
export type EventInitializationFailed = "event::initializationFailed";
export interface TypingInstallerResponse {
readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | EventPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
}
/* @internal */
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
/* @internal */
export interface TypesRegistryResponse extends TypingInstallerResponse {
readonly kind: EventTypesRegistry;
readonly typesRegistry: MapLike<void>;
}
/* @internal */
export interface PackageInstalledResponse extends TypingInstallerResponse {
readonly kind: EventPackageInstalled;
readonly success: boolean;
readonly message: string;
}
export interface InitializationFailedResponse extends TypingInstallerResponse {

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