diff --git a/.gitignore b/.gitignore index 19d96d0b046..5125de360b8 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ internal/ !tests/baselines/reference/project/nodeModules*/**/* .idea yarn.lock +yarn-error.log .parallelperf.* tests/cases/user/*/package-lock.json tests/cases/user/*/node_modules/ diff --git a/.gitmodules b/.gitmodules index e5cf4c016e9..bd4620625cf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,24 +2,19 @@ path = tests/cases/user/TypeScript-React-Starter/TypeScript-React-Starter url = https://github.com/Microsoft/TypeScript-React-Starter ignore = all - shallow = true [submodule "tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter"] path = tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter url = https://github.com/Microsoft/TypeScript-Node-Starter.git ignore = all - shallow = true [submodule "tests/cases/user/TypeScript-React-Native-Starter/TypeScript-React-Native-Starter"] path = tests/cases/user/TypeScript-React-Native-Starter/TypeScript-React-Native-Starter url = https://github.com/Microsoft/TypeScript-React-Native-Starter.git ignore = all - shallow = true [submodule "tests/cases/user/TypeScript-Vue-Starter/TypeScript-Vue-Starter"] path = tests/cases/user/TypeScript-Vue-Starter/TypeScript-Vue-Starter url = https://github.com/Microsoft/TypeScript-Vue-Starter.git ignore = all - shallow = true [submodule "tests/cases/user/TypeScript-WeChat-Starter/TypeScript-WeChat-Starter"] path = tests/cases/user/TypeScript-WeChat-Starter/TypeScript-WeChat-Starter url = https://github.com/Microsoft/TypeScript-WeChat-Starter.git ignore = all - shallow = true diff --git a/.travis.yml b/.travis.yml index 06e912c55f5..b4a5227c34a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ branches: - master - release-2.5 - release-2.6 + - release-2.7 install: - npm uninstall typescript --no-save diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6dbb7e514a0..1d11db2f87b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,14 @@ Issues that ask questions answered in the FAQ will be closed without elaboration ## 2. Search for Duplicates -[Search the existing issues](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=is%3Aissue) before logging a new one. +[Search the existing issues](https://github.com/Microsoft/TypeScript/search?type=Issues) before logging a new one. + +Some search tips: + * *Don't* restrict your search to only open issues. An issue with a title similar to yours may have been closed as a duplicate of one with a less-findable title. + * Check for synonyms. For example, if your bug involves an interface, it likely also occurs with type aliases or classes. + * Search for the title of the issue you're about to log. This sounds obvious but 80% of the time this is sufficient to find a duplicate when one exists. + * Read more than the first page of results. Many bugs here use the same words so relevancy sorting is not particularly strong. + * If you have a crash, search for the first few topmost function names shown in the call stack. ## 3. Do you have a question? @@ -183,3 +190,10 @@ jake baseline-accept ``` to establish the new baselines as the desired behavior. This will change the files in `tests\baselines\reference`, which should be included as part of your commit. It's important to carefully validate changes in the baselines. + +## Localization + +All strings the user may see are stored in [`diagnosticMessages.json`](./src/compiler/diagnosticMessages.json). +If you make changes to it, run `jake generate-diagnostics` to push them to the `Diagnostic` interface in [`diagnosticInformationMap.generated.ts`](./src/compiler/diagnosticInformationMap.generated.ts). + +See [coding guidelines on diagnostic messages](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-messages). diff --git a/Gulpfile.ts b/Gulpfile.ts index 4d90d5cf24a..2909b10471a 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -150,7 +150,9 @@ const es2018LibrarySourceMap = es2018LibrarySource.map(source => ({ target: "lib." + source, sources: ["header.d.ts", source] })); const esnextLibrarySource = [ - "esnext.asynciterable.d.ts" + "esnext.asynciterable.d.ts", + "esnext.array.d.ts", + "esnext.promise.d.ts" ]; const esnextLibrarySourceMap = esnextLibrarySource.map(source => @@ -679,14 +681,14 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: workerCount = cmdLineOptions.workers; } - if (tests || runners || light || taskConfigsFolder) { - writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit); - } - if (tests && tests.toLocaleLowerCase() === "rwc") { testTimeout = 400000; } + if (tests || runners || light || testTimeout || taskConfigsFolder) { + writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout); + } + const colors = cmdLineOptions.colors; const reporter = cmdLineOptions.reporter || defaultReporter; @@ -871,8 +873,17 @@ function cleanTestDirs(done: (e?: any) => void) { } // used to pass data from jake command line directly to run.js -function writeTestConfigFile(tests: string, runners: string, light: boolean, taskConfigsFolder?: string, workerCount?: number, stackTraceLimit?: string) { - const testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, runner: runners ? runners.split(",") : undefined, light, workerCount, stackTraceLimit, taskConfigsFolder, noColor: !cmdLineOptions.colors }); +function writeTestConfigFile(tests: string, runners: string, light: boolean, taskConfigsFolder?: string, workerCount?: number, stackTraceLimit?: string, timeout?: number) { + const testConfigContents = JSON.stringify({ + test: tests ? [tests] : undefined, + runner: runners ? runners.split(",") : undefined, + light, + workerCount, + stackTraceLimit, + taskConfigsFolder, + noColor: !cmdLineOptions.colors, + timeout, + }); console.log("Running tests with config: " + testConfigContents); fs.writeFileSync("test.config", testConfigContents); } diff --git a/Jakefile.js b/Jakefile.js index 1544ccec0a3..2026544a249 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -130,6 +130,7 @@ var harnessSources = harnessCoreSources.concat([ "textStorage.ts", "moduleResolution.ts", "tsconfigParsing.ts", + "asserts.ts", "builder.ts", "commandLineParsing.ts", "configurationExtension.ts", @@ -212,7 +213,9 @@ var es2018LibrarySourceMap = es2018LibrarySource.map(function (source) { }); var esnextLibrarySource = [ - "esnext.asynciterable.d.ts" + "esnext.asynciterable.d.ts", + "esnext.array.d.ts", + "esnext.promise.d.ts" ]; var esnextLibrarySourceMap = esnextLibrarySource.map(function (source) { @@ -795,7 +798,7 @@ compileFile( /*prereqs*/[builtLocalDirectory, tscFile, tsserverLibraryFile].concat(libraryTargets).concat(servicesSources).concat(harnessSources), /*prefixes*/[], /*useBuiltCompiler:*/ true, - /*opts*/ { types: ["node", "mocha"], lib: "es6" }); + /*opts*/ { types: ["node", "mocha", "chai"], lib: "es6" }); var internalTests = "internal/"; @@ -856,7 +859,7 @@ function cleanTestDirs() { } // used to pass data from jake command line directly to run.js -function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors) { +function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout) { var testConfigContents = JSON.stringify({ runners: runners ? runners.split(",") : undefined, test: tests ? [tests] : undefined, @@ -864,7 +867,8 @@ function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCou workerCount: workerCount, taskConfigsFolder: taskConfigsFolder, stackTraceLimit: stackTraceLimit, - noColor: !colors + noColor: !colors, + timeout: testTimeout }); fs.writeFileSync('test.config', testConfigContents); } @@ -906,14 +910,14 @@ function runConsoleTests(defaultReporter, runInParallel) { workerCount = process.env.workerCount || process.env.p || os.cpus().length; } - if (tests || runners || light || taskConfigsFolder) { - writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors); - } - if (tests && tests.toLocaleLowerCase() === "rwc") { testTimeout = 800000; } + if (tests || runners || light || testTimeout || taskConfigsFolder) { + writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout); + } + var colorsFlag = process.env.color || process.env.colors; var colors = colorsFlag !== "false" && colorsFlag !== "0"; var reporter = process.env.reporter || process.env.r || defaultReporter; @@ -1200,6 +1204,7 @@ var tslintRules = [ "debugAssertRule", "nextLineRule", "noBomRule", + "noDoubleSpaceRule", "noIncrementDecrementRule", "noInOperatorRule", "noTypeAssertionWhitespaceRule", diff --git a/issue_template.md b/issue_template.md index dbc021c28ad..22acf3e9cc5 100644 --- a/issue_template.md +++ b/issue_template.md @@ -15,3 +15,5 @@ **Expected behavior:** **Actual behavior:** + +**Related:** diff --git a/package-lock.json b/package-lock.json index 5dc35c5bb26..736551168ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,13 +4,30 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@browserify/acorn5-object-spread": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@browserify/acorn5-object-spread/-/acorn5-object-spread-5.0.1.tgz", + "integrity": "sha512-sFCUPzgeEjdq3rinwy4TFXtak2YZdhqpj6MdNusxkdTFr9TXAUEYK4YQSamR8Joqt/yii1drgl5hk8q/AtJDKA==", + "dev": true, + "requires": { + "acorn": "5.3.0" + }, + "dependencies": { + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + } + } + }, "@gulp-sourcemaps/identity-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", "dev": true, "requires": { - "acorn": "5.2.1", + "acorn": "5.3.0", "css": "2.2.1", "normalize-path": "2.1.1", "source-map": "0.5.7", @@ -18,9 +35,9 @@ }, "dependencies": { "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", "dev": true } } @@ -42,19 +59,13 @@ "dev": true, "requires": { "@types/insert-module-globals": "7.0.0", - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/chai": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.7.tgz", - "integrity": "sha512-OsFOcyJH0SViMMHzDKZdapG8sfoH7qcpYgKhgyw9xn5MgkCvAplkf0FUaRFB+HTrVH6gQ/GU7sbIUDM1usVEUA==", - "dev": true - }, - "@types/colors": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.1.3.tgz", - "integrity": "sha1-VBOwp6GxbdGL4OP9V9L+7Mgcx3Y=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.0.tgz", + "integrity": "sha512-OuYBlXWHYthxIudMXMeQr92f6D97YoT9CUYCRb0BEP4OavC95jNcczjjr4Ob3H5E1IqlWqwj+leUZPSeth27Qw==", "dev": true }, "@types/convert-source-map": { @@ -69,7 +80,7 @@ "integrity": "sha512-18mSs54BvzV8+TTQxt0ancig6tsuPZySnhp3cQkWFFDmDMavU4pmWwR+bHHqRBWODYqpzIzVkqKLuk/fP6yypQ==", "dev": true, "requires": { - "@types/glob": "5.0.33" + "@types/glob": "5.0.34" } }, "@types/events": { @@ -79,24 +90,25 @@ "dev": true }, "@types/glob": { - "version": "5.0.33", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.33.tgz", - "integrity": "sha512-BcD4yyWz+qmCggaYMSFF0Xn7GkO6tgwm3Fh9Gxk/kQmEU3Z7flQTnVlMyKBUNvXXNTCCyjqK4XT4/2hLd1gQ2A==", + "version": "5.0.34", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.34.tgz", + "integrity": "sha512-sUvpieq+HsWTLdkeOI8Mi8u22Ag3AoGuM3sv+XMP1bKtbaIAHpEA2f52K2mz6vK5PVhTa3bFyRZLZMqTxOo2Cw==", "dev": true, "requires": { - "@types/minimatch": "3.0.1", - "@types/node": "8.0.54" + "@types/events": "1.1.0", + "@types/minimatch": "3.0.3", + "@types/node": "8.5.5" } }, "@types/gulp": { - "version": "3.8.35", - "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-3.8.35.tgz", - "integrity": "sha512-h9clNJu8X6+zW74ZLa5zhh5HP0LxnvlelVXdXby6pM/DDEj/gKqmmFXKwjzvupZKlMpof02jr6c3JokPbHXQgg==", + "version": "3.8.36", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-3.8.36.tgz", + "integrity": "sha512-u6/zWPzYRNPAtvyFJ3/RSXjmBaBM1dVs5kW22/jU6J786ZGLfSndhLoNOpFI6FGQvqTA+QzFHjSMhpkAN+wxcQ==", "dev": true, "requires": { - "@types/node": "8.0.54", + "@types/node": "8.5.5", "@types/orchestrator": "0.3.2", - "@types/vinyl": "2.0.1" + "@types/vinyl": "2.0.2" } }, "@types/gulp-concat": { @@ -105,7 +117,7 @@ "integrity": "sha512-CUCFADlITzzBfBa2bdGzhKtvBr4eFh+evb+4igVbvPoO5RyPfHifmyQlZl6lM7q19+OKncRlFXDU7B4X9Ayo2g==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/gulp-help": { @@ -114,8 +126,8 @@ "integrity": "sha512-MkW7psZznxxJg2MBk2P2qHE+T8jEZVFz3FG/qGjUYazkyJt7hBJWx5Nuewmay5RVNtUvSWPrdZLr/WTXY3T/6A==", "dev": true, "requires": { - "@types/gulp": "3.8.35", - "@types/node": "8.0.54", + "@types/gulp": "3.8.36", + "@types/node": "8.5.5", "@types/orchestrator": "0.3.2" } }, @@ -125,7 +137,7 @@ "integrity": "sha512-e7J/Zv5Wd7CC0WpuA2syWVitgwrkG0u221e41w7r07XUR6hMH6kHPkq9tUrusHkbeW8QbuLbis5fODOwQCyggQ==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/gulp-sourcemaps": { @@ -134,7 +146,7 @@ "integrity": "sha512-+7BAmptW2bxyJnJcCEuie7vLoop3FwWgCdBMzyv7MYXED/HeNMeQuX7uPCkp4vfU1TTu4CYFH0IckNPvo0VePA==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/insert-module-globals": { @@ -143,7 +155,7 @@ "integrity": "sha512-zudCJPwluh1VUDB6Gl/OQdRp+fYy3+47huJB/JMQubMS2p+sH18MCVK4WUz3FqaWLB12yh5ELxVR/+tqwlm/qA==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/merge2": { @@ -152,13 +164,13 @@ "integrity": "sha512-GjaXY4OultxbaOOk7lCLO7xvEcFpdjExC605YmfI6X29vhHKpJfMWKCDZd3x+BITrZaXKg97DgV/SdGVSwdzxA==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/minimatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.1.tgz", - "integrity": "sha512-rUO/jz10KRSyA9SHoCWQ8WX9BICyj5jZYu1/ucKEJKb4KzLZCKMURdYbadP157Q6Zl1x0vHsrU+Z/O0XlhYQDw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, "@types/minimist": { @@ -173,23 +185,20 @@ "integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/mocha": { - "version": "2.2.44", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.44.tgz", - "integrity": "sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==", + "version": "2.2.46", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.46.tgz", + "integrity": "sha512-fwTTP5QLf4xHMkv7ovcKvmlLWX3GrxCa5DRQDOilVyYGCp+arZTAQJCy7/4GKezzYJjfWMpB/Cy4e8nrc9XioA==", "dev": true }, "@types/node": { - "version": "8.0.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.54.tgz", - "integrity": "sha512-qetMdTv3Ytz9u9ESLdcYs45LPI0mczYZIbC184n7kY0jczOqPNQsabBfVCh+na3B2shAfvC459JqHV771A8Rxg==", - "dev": true, - "requires": { - "@types/events": "1.1.0" - } + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz", + "integrity": "sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA==", + "dev": true }, "@types/orchestrator": { "version": "0.3.2", @@ -197,7 +206,7 @@ "integrity": "sha512-cKB4yTX0wGaRCSkdHDX2fkGQbMAA8UOshC2U7DQky1CE5o+5q2iQQ8VkbPbE/88uaTtsusvBPMcCX7dgmjxBhQ==", "dev": true, "requires": { - "@types/node": "8.0.54", + "@types/node": "8.5.5", "@types/q": "1.0.6" } }, @@ -213,26 +222,38 @@ "integrity": "sha512-XwGr1b4yCGUILKeBkzmeWcxmGHQ0vFFFpA6D6y1yLO6gKmYorF+PHqdU5KG+nWt38OvtrkDptmrSmlHX/XtpLw==", "dev": true, "requires": { - "@types/gulp": "3.8.35", - "@types/node": "8.0.54" + "@types/gulp": "3.8.36", + "@types/node": "8.5.5" } }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "@types/through2": { "version": "2.0.33", "resolved": "https://registry.npmjs.org/@types/through2/-/through2-2.0.33.tgz", "integrity": "sha1-H/LoihAN+1sUDnu5h5HxGUQA0TE=", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/vinyl": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.1.tgz", - "integrity": "sha512-Joudabfn2ZofU2usW04y8OLmN75u7ZQkW0MCT3AnoBf5oUBp5iQ3Pgfz9+y1RdWkzhCPZo9/wBJ7FMWW2JrY0g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.2.tgz", + "integrity": "sha512-2iYpNuOl98SrLPBZfEN9Mh2JCJ2EI9HU35SfgBEb51DcmaHkhp8cKMblYeBqMQiwXMgAD3W60DbQ4i/UdLiXhw==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "@types/xml2js": { @@ -241,13 +262,13 @@ "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", "dev": true, "requires": { - "@types/node": "8.0.54" + "@types/node": "8.5.5" } }, "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", "dev": true, "requires": { "jsonparse": "1.3.1", @@ -275,6 +296,17 @@ "kind-of": "3.2.2", "longest": "1.0.1", "repeat-string": "1.6.1" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } } }, "amdefine": { @@ -283,6 +315,33 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -290,9 +349,18 @@ "dev": true }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, "archy": { @@ -311,13 +379,10 @@ } }, "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-flatten": { "version": "1.1.0", @@ -325,6 +390,12 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -343,12 +414,6 @@ "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", "dev": true }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -383,9 +448,9 @@ "dev": true }, "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, "arrify": { @@ -415,9 +480,15 @@ } }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, "astw": { @@ -436,9 +507,9 @@ "dev": true }, "atob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", - "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", + "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", "dev": true }, "babel-code-frame": { @@ -450,6 +521,33 @@ "chalk": "1.1.3", "esutils": "2.0.2", "js-tokens": "3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "balanced-match": { @@ -458,6 +556,21 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.5", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.0", + "pascalcase": "0.1.1" + } + }, "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", @@ -487,14 +600,22 @@ } }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", + "integrity": "sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==", "dev": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.1", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.1" } }, "brorand": { @@ -509,7 +630,7 @@ "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", "dev": true, "requires": { - "JSONStream": "1.3.1", + "JSONStream": "1.3.2", "combine-source-map": "0.7.2", "defined": "1.0.0", "through2": "2.0.3", @@ -532,12 +653,12 @@ "dev": true }, "browserify": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", - "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-15.0.0.tgz", + "integrity": "sha512-dERxjzl4yacUzaB4XVVXDFFHARzDr6tLRhjqpE/NJUv0SkS3QVmZKiYTiKEQZhQ2HygCL02FUzSS5r3sY/SlTg==", "dev": true, "requires": { - "JSONStream": "1.3.1", + "JSONStream": "1.3.2", "assert": "1.4.1", "browser-pack": "6.0.2", "browser-resolve": "1.11.2", @@ -560,7 +681,7 @@ "inherits": "2.0.3", "insert-module-globals": "7.0.1", "labeled-stream-splicer": "2.0.0", - "module-deps": "4.1.1", + "module-deps": "5.0.1", "os-browserify": "0.3.0", "parents": "1.0.1", "path-browserify": "0.0.0", @@ -690,6 +811,23 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + } + }, "cached-path-relative": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", @@ -697,20 +835,11 @@ "dev": true }, "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", "dev": true, - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } + "optional": true }, "center-align": { "version": "0.1.3", @@ -721,6 +850,15 @@ "requires": { "align-text": "0.1.4", "lazy-cache": "1.0.4" + }, + "dependencies": { + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + } } }, "chai": { @@ -729,7 +867,7 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "check-error": "1.0.2", "deep-eql": "3.0.1", "get-func-name": "2.0.0", @@ -738,16 +876,14 @@ } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", "dev": true, "requires": { - "ansi-styles": "2.2.1", + "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "supports-color": "4.5.0" } }, "check-error": { @@ -766,6 +902,87 @@ "safe-buffer": "5.1.1" } }, + "class-utils": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.5.tgz", + "integrity": "sha1-F+eTEDdQ+WJ7IXbqNM/RtWWQPIA=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "lazy-cache": "2.0.2", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -816,6 +1033,16 @@ "through2": "2.0.3" } }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -831,10 +1058,10 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, "combine-source-map": { @@ -863,6 +1090,12 @@ "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -932,6 +1165,12 @@ "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1005,6 +1244,12 @@ "urix": "0.1.0" }, "dependencies": { + "atob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", + "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", + "dev": true + }, "source-map": { "version": "0.1.43", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", @@ -1013,18 +1258,27 @@ "requires": { "amdefine": "1.0.1" } + }, + "source-map-resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", + "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", + "dev": true, + "requires": { + "atob": "1.1.3", + "resolve-url": "0.2.1", + "source-map-url": "0.3.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", + "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", + "dev": true } } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "1.0.2" - } - }, "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", @@ -1047,29 +1301,47 @@ "dev": true }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } }, "debug-fabulous": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-0.2.1.tgz", - "integrity": "sha512-u0TV6HcfLsZ03xLBhdhSViQMldaiQ2o+8/nSILaXkuNSWvxkx66vYJUAam0Eu7gAilJRX/69J4kKdqajQPaPyw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.0.0.tgz", + "integrity": "sha512-dsd50qQ1atDeurcxL7XOjPp4nZCGZzWIONDujDXzl1atSyC3hMbZD+v6440etw+Vt0Pr8ce4TQzHfX3KZM05Mw==", "dev": true, "requires": { "debug": "3.1.0", "memoizee": "0.4.11", "object-assign": "4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "optional": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, "deep-eql": { @@ -1096,6 +1368,15 @@ "clone": "1.0.3" } }, + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, "defined": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", @@ -1128,7 +1409,7 @@ "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", "dev": true, "requires": { - "JSONStream": "1.3.1", + "JSONStream": "1.3.2", "shasum": "1.0.2", "subarg": "1.0.0", "through2": "2.0.3" @@ -1145,13 +1426,10 @@ } }, "detect-file": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", - "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", - "dev": true, - "requires": { - "fs-exists-sync": "0.1.0" - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true }, "detect-newline": { "version": "2.1.0", @@ -1160,19 +1438,20 @@ "dev": true }, "detective": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.0.tgz", - "integrity": "sha512-4mBqSEdMfBpRAo/DQZnTcAXenpiSIJmVKbCMSotS+SFWWcrP/CKM6iBRPdTiEO+wZhlfEsoZlGqpG6ycl5vTqw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.0.2.tgz", + "integrity": "sha512-NUsLoezj4wb9o7vpxS9F3L5vcO87ceyRBcl48op06YFNwkyIEY997JpSCA5lDlDuDc6JxOtaL5qfK3muoWxpMA==", "dev": true, "requires": { - "acorn": "5.2.1", + "@browserify/acorn5-object-spread": "5.0.1", + "acorn": "5.3.0", "defined": "1.0.0" }, "dependencies": { "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", "dev": true } } @@ -1267,15 +1546,6 @@ } } }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, "es5-ext": { "version": "0.10.37", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", @@ -1401,12 +1671,86 @@ } }, "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "is-posix-bracket": "0.1.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "expand-range": { @@ -1416,15 +1760,57 @@ "dev": true, "requires": { "fill-range": "2.2.3" + }, + "dependencies": { + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } } }, "expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "os-homedir": "1.0.2" + "homedir-polyfill": "1.0.1" } }, "extend": { @@ -1443,21 +1829,29 @@ } }, "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.3.tgz", + "integrity": "sha512-AyptZexgu7qppEPq59DtN/XJGZDrLcVxSHai+4hdgMMS9EpF4GBvygcWWApno8lL9qSjVpYt7Raao28qzJX1ww==", "dev": true, "requires": { - "is-extglob": "1.0.0" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" } }, "fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", "dev": true, "requires": { - "chalk": "1.1.3", + "ansi-gray": "0.1.1", + "color-support": "1.1.3", "time-stamp": "1.1.0" } }, @@ -1492,16 +1886,15 @@ "dev": true }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" } }, "find-index": { @@ -1510,26 +1903,16 @@ "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", "dev": true }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, "findup-sync": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", - "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "dev": true, "requires": { - "detect-file": "0.1.0", - "is-glob": "2.0.1", - "micromatch": "2.3.11", - "resolve-dir": "0.1.1" + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.5", + "resolve-dir": "1.0.1" } }, "fined": { @@ -1542,18 +1925,7 @@ "is-plain-object": "2.0.4", "object.defaults": "1.1.0", "object.pick": "1.3.0", - "parse-filepath": "1.0.1" - }, - "dependencies": { - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "1.0.1" - } - } + "parse-filepath": "1.0.2" } }, "first-chunk-stream": { @@ -1563,9 +1935,9 @@ "dev": true }, "flagged-respawn": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", - "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", "dev": true }, "for-in": { @@ -1575,19 +1947,22 @@ "dev": true }, "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { "for-in": "1.0.2" } }, - "fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", - "dev": true + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } }, "fs.realpath": { "version": "1.0.0", @@ -1616,10 +1991,10 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, "glob": { @@ -1644,15 +2019,42 @@ "requires": { "glob-parent": "2.0.0", "is-glob": "2.0.1" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } } }, "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "2.0.1" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" } }, "glob-stream": { @@ -1745,24 +2147,26 @@ } }, "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "global-prefix": "0.1.5", - "is-windows": "0.2.0" + "global-prefix": "1.0.2", + "is-windows": "1.0.1", + "resolve-dir": "1.0.1" } }, "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { + "expand-tilde": "2.0.2", "homedir-polyfill": "1.0.1", "ini": "1.3.5", - "is-windows": "0.2.0", + "is-windows": "1.0.1", "which": "1.3.0" } }, @@ -1848,7 +2252,7 @@ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "dev": true, "requires": { - "natives": "1.1.0" + "natives": "1.1.1" } }, "growl": { @@ -1868,7 +2272,7 @@ "deprecated": "0.0.1", "gulp-util": "3.0.8", "interpret": "1.1.0", - "liftoff": "2.3.0", + "liftoff": "2.5.0", "minimist": "1.2.0", "orchestrator": "0.3.8", "pretty-hrtime": "1.0.3", @@ -1876,226 +2280,47 @@ "tildify": "1.2.0", "v8flags": "2.1.1", "vinyl-fs": "0.3.14" - } - }, - "gulp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-clone/-/gulp-clone-1.0.0.tgz", - "integrity": "sha1-mubGVr2cTzae6AXu9WV4a8gQBbA=", - "dev": true, - "requires": { - "gulp-util": "2.2.20", - "through2": "0.4.2" }, "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "1.1.0", + "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", - "has-ansi": "0.1.0", - "strip-ansi": "0.3.0", - "supports-color": "0.2.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "requires": { - "chalk": "0.5.1", - "dateformat": "1.0.12", - "lodash._reinterpolate": "2.4.1", - "lodash.template": "2.4.1", - "minimist": "0.2.0", - "multipipe": "0.1.2", - "through2": "0.5.1", - "vinyl": "0.2.3" - }, - "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "requires": { - "readable-stream": "1.0.34", - "xtend": "3.0.0" - } - } - } - }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "dev": true, - "requires": { - "ansi-regex": "0.2.1" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", - "dev": true - }, - "lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", - "dev": true, - "requires": { - "lodash._escapehtmlchar": "2.4.1", - "lodash._reunescapedhtml": "2.4.1", - "lodash.keys": "2.4.1" - } - }, - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "2.4.1", - "lodash._shimkeys": "2.4.1", - "lodash.isobject": "2.4.1" - } - }, - "lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", - "dev": true, - "requires": { - "lodash._escapestringchar": "2.4.1", - "lodash._reinterpolate": "2.4.1", - "lodash.defaults": "2.4.1", - "lodash.escape": "2.4.1", - "lodash.keys": "2.4.1", - "lodash.templatesettings": "2.4.1", - "lodash.values": "2.4.1" - } - }, - "lodash.templatesettings": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", - "dev": true, - "requires": { - "lodash._reinterpolate": "2.4.1", - "lodash.escape": "2.4.1" - } - }, - "minimist": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", - "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "dev": true, - "requires": { - "ansi-regex": "0.2.1" + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - }, - "through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", - "dev": true, - "requires": { - "readable-stream": "1.0.34", - "xtend": "2.1.2" - }, - "dependencies": { - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "0.4.0" - } - } - } - }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", - "dev": true, - "requires": { - "clone-stats": "0.0.1" - } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } } }, + "gulp-clone": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gulp-clone/-/gulp-clone-1.1.1.tgz", + "integrity": "sha512-+9z6YKyup0pxL1nD9Hw5WjC29d280dsGkegagzt4xKAOvroyzZlA8Bs9MYs2WSSFIDqmcftMUVN/oentgBv69A==", + "dev": true, + "requires": { + "chai": "4.1.2", + "mocha": "4.1.0", + "plugin-error": "0.1.2", + "through2": "2.0.3" + } + }, "gulp-concat": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", @@ -2117,11 +2342,36 @@ "object-assign": "3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, @@ -2162,52 +2412,46 @@ } }, "gulp-newer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/gulp-newer/-/gulp-newer-1.3.0.tgz", - "integrity": "sha1-1Q7Ky7gi7aSStXMkpshaB/2aVcE=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-newer/-/gulp-newer-1.4.0.tgz", + "integrity": "sha512-h79fGO55S/P9eAADbLAP9aTtVYpLSR1ONj08VPaSdVVNVYhTS8p1CO1TW7kEMu+hC+sytmCqcUr5LesvZEtDoQ==", "dev": true, "requires": { "glob": "7.1.2", - "gulp-util": "3.0.8", - "kew": "0.7.0" + "kew": "0.7.0", + "plugin-error": "0.1.2" } }, "gulp-sourcemaps": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.1.tgz", - "integrity": "sha512-1qHCI3hdmsMdq/SUotxwUh/L8YzlI6J9zQ5ifNOtx4Y6KV5y5sGuORv1KZzWhuKtz/mXNh5xLESUtwC4EndCjA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.3.tgz", + "integrity": "sha1-EbAz91n5CeCl8Vt730esKcxU76Q=", "dev": true, "requires": { "@gulp-sourcemaps/identity-map": "1.0.1", "@gulp-sourcemaps/map-sources": "1.0.0", - "acorn": "4.0.13", + "acorn": "5.3.0", "convert-source-map": "1.5.1", "css": "2.2.1", - "debug-fabulous": "0.2.1", + "debug-fabulous": "1.0.0", "detect-newline": "2.1.0", "graceful-fs": "4.1.11", "source-map": "0.5.7", "strip-bom-string": "1.0.0", - "through2": "2.0.3", - "vinyl": "1.2.0" + "through2": "2.0.3" }, "dependencies": { + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "1.0.3", - "clone-stats": "0.0.1", - "replace-ext": "0.0.1" - } } } }, @@ -2223,6 +2467,50 @@ "vinyl-fs": "2.4.4" }, "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, "glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", @@ -2236,16 +2524,6 @@ "path-is-absolute": "1.0.1" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - } - }, "glob-stream": { "version": "5.3.5", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", @@ -2306,18 +2584,18 @@ } }, "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "1.0.0" } }, "isarray": { @@ -2335,6 +2613,36 @@ "jsonify": "0.0.0" } }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", @@ -2419,7 +2727,7 @@ "beeper": "1.1.1", "chalk": "1.1.3", "dateformat": "2.2.0", - "fancy-log": "1.3.0", + "fancy-log": "1.3.2", "gulplog": "1.0.0", "has-gulplog": "0.1.0", "lodash._reescape": "3.0.0", @@ -2434,12 +2742,37 @@ "vinyl": "0.5.3" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", "dev": true }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, "vinyl": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", @@ -2510,9 +2843,9 @@ "dev": true }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, "has-gulplog": { @@ -2524,6 +2857,38 @@ "sparkles": "1.0.0" } }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "hash-base": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", @@ -2569,12 +2934,6 @@ "parse-passwd": "1.0.0" } }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", - "dev": true - }, "htmlescape": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", @@ -2593,15 +2952,6 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", "dev": true }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -2645,7 +2995,7 @@ "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", "dev": true, "requires": { - "JSONStream": "1.3.1", + "JSONStream": "1.3.2", "combine-source-map": "0.7.2", "concat-stream": "1.5.2", "is-buffer": "1.1.6", @@ -2662,20 +3012,23 @@ "dev": true }, "is-absolute": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "0.2.1", - "is-windows": "0.2.0" + "is-relative": "1.0.0", + "is-windows": "1.0.1" } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } }, "is-buffer": { "version": "1.1.6", @@ -2683,13 +3036,24 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { + "is-data-descriptor": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-dotfile": { @@ -2714,36 +3078,47 @@ "dev": true }, "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "2.1.1" } }, "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-odd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", + "integrity": "sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=", + "dev": true, + "requires": { + "is-number": "3.0.0" } }, "is-path-cwd": { @@ -2777,14 +3152,6 @@ "dev": true, "requires": { "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "is-posix-bracket": { @@ -2806,12 +3173,12 @@ "dev": true }, "is-relative": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "0.1.2" + "is-unc-path": "1.0.0" } }, "is-stream": { @@ -2821,9 +3188,9 @@ "dev": true }, "is-unc-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "0.1.2" @@ -2842,9 +3209,9 @@ "dev": true }, "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz", + "integrity": "sha1-MQ23D3QtJZoWo2kgK1GvhCMzENk=", "dev": true }, "isarray": { @@ -2860,13 +3227,10 @@ "dev": true }, "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "istanbul": { "version": "0.4.5", @@ -2903,6 +3267,12 @@ "path-is-absolute": "1.0.1" } }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", @@ -3010,13 +3380,10 @@ "dev": true }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true }, "labeled-stream-splicer": { "version": "2.0.0", @@ -3038,11 +3405,13 @@ } }, "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", "dev": true, - "optional": true + "requires": { + "set-getter": "0.1.0" + } }, "lazystream": { "version": "1.0.0", @@ -3073,58 +3442,21 @@ } }, "liftoff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", - "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", "dev": true, "requires": { "extend": "3.0.1", - "findup-sync": "0.4.3", + "findup-sync": "2.0.0", "fined": "1.1.0", - "flagged-respawn": "0.3.2", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.mapvalues": "4.6.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", "rechoir": "0.6.2", "resolve": "1.1.7" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - } - } - }, "lodash": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", @@ -3149,51 +3481,18 @@ "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", "dev": true }, - "lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "dev": true, - "requires": { - "lodash._htmlescapes": "2.4.1" - } - }, - "lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", - "dev": true - }, "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", "dev": true }, - "lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", - "dev": true - }, "lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", "dev": true }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", - "dev": true - }, - "lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", - "dev": true - }, "lodash._reescape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", @@ -3212,67 +3511,12 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", - "dev": true, - "requires": { - "lodash._htmlescapes": "2.4.1", - "lodash.keys": "2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "2.4.1", - "lodash._shimkeys": "2.4.1", - "lodash.isobject": "2.4.1" - } - } - } - }, "lodash._root": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dev": true, - "requires": { - "lodash._objecttypes": "2.4.1" - } - }, - "lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "dev": true, - "requires": { - "lodash._objecttypes": "2.4.1", - "lodash.keys": "2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "2.4.1", - "lodash._shimkeys": "2.4.1", - "lodash.isobject": "2.4.1" - } - } - } - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -3300,27 +3544,6 @@ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "dev": true, - "requires": { - "lodash._objecttypes": "2.4.1" - } - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -3332,12 +3555,6 @@ "lodash.isarray": "3.0.4" } }, - "lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", - "dev": true - }, "lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", @@ -3377,44 +3594,12 @@ "lodash.escape": "3.2.0" } }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "dev": true, - "requires": { - "lodash.keys": "2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "2.4.1", - "lodash._shimkeys": "2.4.1", - "lodash.isobject": "2.4.1" - } - } - } - }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" - } - }, "lru-cache": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", @@ -3431,22 +3616,45 @@ } }, "make-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", - "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.2.tgz", + "integrity": "sha512-l9ra35l5VWLF24y75Tg8XgfGLX0ueRhph118WKM6H5denx4bB5QF59+4UAm9oJ2qsPQZas/CQUDdtDdfvYHBdQ==", "dev": true }, + "make-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.0.tgz", + "integrity": "sha1-V7713IXSOSO6I3ZzJNjo+PPZaUs=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } }, "md5.js": { "version": "1.3.4", @@ -3486,24 +3694,6 @@ "timers-ext": "0.1.2" } }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - } - }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -3520,24 +3710,24 @@ "dev": true }, "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.5.tgz", + "integrity": "sha512-ykttrLPQrz1PUJcXjwsTUjGoPJ64StIGNE2lGVD1c9CuguJ+L7/navsE8IcDNndOoCMvYV0qc/exfVbMHkUhvA==", "dev": true, "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.0", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "extglob": "2.0.3", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.7", + "object.pick": "1.3.0", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" } }, "miller-rabin": { @@ -3577,6 +3767,27 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "mixin-deep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", + "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -3595,9 +3806,9 @@ } }, "mocha": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", - "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -3612,11 +3823,14 @@ "supports-color": "4.4.0" }, "dependencies": { - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } }, "supports-color": { "version": "4.4.0", @@ -3636,17 +3850,17 @@ "dev": true }, "module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-5.0.1.tgz", + "integrity": "sha512-sigq/hm/L+Z5IGi1DDl0x2ptkw7S86aFh213QhPLD8v9Opv90IHzKIuWJrRa5bJ77DVKHco2CfIEuThcT/vDJA==", "dev": true, "requires": { - "JSONStream": "1.3.1", + "JSONStream": "1.3.2", "browser-resolve": "1.11.2", "cached-path-relative": "1.0.1", - "concat-stream": "1.5.2", + "concat-stream": "1.6.0", "defined": "1.0.0", - "detective": "4.7.0", + "detective": "5.0.2", "duplexer2": "0.1.4", "inherits": "2.0.3", "parents": "1.0.1", @@ -3656,6 +3870,19 @@ "subarg": "1.0.0", "through2": "2.0.3", "xtend": "4.0.1" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + } } }, "ms": { @@ -3708,10 +3935,37 @@ } } }, + "nanomatch": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.7.tgz", + "integrity": "sha512-/5ldsnyurvEw7wNpxLFgjVvBLMta43niEYOy0CJ4ntcYSbx6bugRUTQeFb4BR/WanEL1o3aQgHuVLHQaB6tOqg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "is-odd": "1.0.0", + "kind-of": "5.1.0", + "object.pick": "1.3.0", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, "natives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", + "integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA==", "dev": true }, "next-tick": { @@ -3729,18 +3983,6 @@ "abbrev": "1.0.9" } }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "4.3.6", - "validate-npm-package-license": "3.0.1" - } - }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -3750,23 +3992,88 @@ "remove-trailing-separator": "1.1.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } }, "object.defaults": { "version": "1.1.0", @@ -3778,23 +4085,16 @@ "array-slice": "1.1.0", "for-own": "1.0.0", "isobject": "3.0.1" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.0" } }, "object.omit": { @@ -3805,6 +4105,17 @@ "requires": { "for-own": "0.1.5", "is-extendable": "0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + } } }, "object.pick": { @@ -3814,14 +4125,6 @@ "dev": true, "requires": { "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "once": { @@ -3935,12 +4238,12 @@ } }, "parse-filepath": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", - "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "dev": true, "requires": { - "is-absolute": "0.2.6", + "is-absolute": "1.0.0", "map-cache": "0.2.2", "path-root": "0.1.1" } @@ -3955,15 +4258,23 @@ "is-dotfile": "1.0.3", "is-extglob": "1.0.0", "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } } }, "parse-passwd": { @@ -3972,6 +4283,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, "path-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", @@ -3984,15 +4301,6 @@ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4032,31 +4340,6 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -4097,6 +4380,64 @@ "pinkie": "2.0.4" } }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "0.1.1", + "ansi-red": "0.1.1", + "arr-diff": "1.1.0", + "arr-union": "2.1.0", + "extend-shallow": "1.1.4" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-slice": "0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -4174,26 +4515,6 @@ "kind-of": "4.0.0" }, "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -4233,27 +4554,6 @@ "readable-stream": "2.3.3" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - } - }, "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", @@ -4278,16 +4578,6 @@ "resolve": "1.1.7" } }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" - } - }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", @@ -4297,6 +4587,15 @@ "is-equal-shallow": "0.1.3" } }, + "regex-not": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", + "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -4315,15 +4614,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -4337,13 +4627,13 @@ "dev": true }, "resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { - "expand-tilde": "1.2.2", - "global-modules": "0.2.3" + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" } }, "resolve-url": { @@ -4382,13 +4672,41 @@ } }, "run-sequence": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.0.tgz", - "integrity": "sha512-xW5DmUwdvoyYQUMPKN8UW7TZSFs7AxtT59xo1m5y91jHbvwGlGgOmdV1Yw5P68fkjf3aHUZ4G1o1mZCtNe0qtw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", + "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", "dev": true, "requires": { "chalk": "1.1.3", - "gulp-util": "3.0.8" + "fancy-log": "1.3.2", + "plugin-error": "0.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "safe-buffer": { @@ -4435,6 +4753,27 @@ "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", "dev": true }, + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "dev": true, + "requires": { + "to-object-path": "0.3.0" + } + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + } + }, "sha.js": { "version": "2.4.9", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", @@ -4473,11 +4812,120 @@ "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", "dev": true }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "snapdragon": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", + "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "2.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } }, "sorcery": { "version": "0.10.0", @@ -4498,14 +4946,15 @@ "dev": true }, "source-map-resolve": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", - "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", "dev": true, "requires": { - "atob": "1.1.3", + "atob": "2.0.3", + "decode-uri-component": "0.2.0", "resolve-url": "0.2.1", - "source-map-url": "0.3.0", + "source-map-url": "0.4.0", "urix": "0.1.0" } }, @@ -4527,9 +4976,9 @@ } }, "source-map-url": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", - "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, "sourcemap-codec": { @@ -4547,33 +4996,120 @@ "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", "dev": true }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "extend-shallow": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, "stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -4719,15 +5255,6 @@ "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "4.0.1" - } - }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -4744,10 +5271,13 @@ } }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } }, "syntax-error": { "version": "1.3.0", @@ -4833,80 +5363,139 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.1.tgz", + "integrity": "sha1-FTWL7kosg712N3uh3ASdDxiDeq4=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "regex-not": "1.0.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, "travis-fold": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/travis-fold/-/travis-fold-0.1.2.tgz", "integrity": "sha1-/sAF+dyqJZo/lFnOWmkGq6TFRdo=", "dev": true }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, "ts-node": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", - "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.1.0.tgz", + "integrity": "sha512-xcZH12oVg9PShKhy3UHyDmuDLV3y7iKwX25aMVPt1SIXSuAfWkFiGPEkg+th8R4YKW/QCxDoW7lJdb15lx6QWg==", "dev": true, "requires": { "arrify": "1.0.1", "chalk": "2.3.0", "diff": "3.3.1", - "make-error": "1.3.0", + "make-error": "1.3.2", "minimist": "1.2.0", "mkdirp": "0.5.1", - "source-map-support": "0.4.18", - "tsconfig": "6.0.0", + "source-map-support": "0.5.0", + "tsconfig": "7.0.0", "v8flags": "3.0.1", "yn": "2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, "v8flags": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.1.tgz", @@ -4919,11 +5508,13 @@ } }, "tsconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", - "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", "dev": true, "requires": { + "@types/strip-bom": "3.0.0", + "@types/strip-json-comments": "0.0.30", "strip-bom": "3.0.0", "strip-json-comments": "2.0.1" }, @@ -4937,9 +5528,9 @@ } }, "tslib": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", - "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.1.tgz", + "integrity": "sha1-aUavLR1lGnsYY7Ux1uWvpBqkTqw=", "dev": true }, "tslint": { @@ -4957,36 +5548,10 @@ "minimatch": "3.0.4", "resolve": "1.5.0", "semver": "5.4.1", - "tslib": "1.8.0", - "tsutils": "2.13.0" + "tslib": "1.8.1", + "tsutils": "2.16.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, "resolve": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", @@ -5001,25 +5566,16 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", "dev": true - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } } } }, "tsutils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.13.0.tgz", - "integrity": "sha512-FuWzNJbMsp3gcZMbI3b5DomhW4Ia41vMxjN63nKWI0t7f+I3UmHfRl0TrXJTwI2LUduDG+eR1Mksp3pvtlyCFQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.16.0.tgz", + "integrity": "sha512-9Ier/60O7OZRNPiw+or5QAtAY4kQA+WDiO/r6xOYATEyefH9bdfvTRLCxrYnFhQlZfET2vYXKfpr3Vw2BiArZw==", "dev": true, "requires": { - "tslib": "1.8.0" + "tslib": "1.8.1" } }, "tty-browserify": { @@ -5050,9 +5606,9 @@ "dev": true }, "typescript": { - "version": "2.7.0-dev.20171203", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.0-dev.20171203.tgz", - "integrity": "sha512-FyhV7OvieIXzjktOb9YmixEIR8olL8IrnonCmJQWGnj8Wt6eoQQKQlkXWPy8mpwEaSIXw/nQO0NpGQ+nWokhRw==", + "version": "2.7.0-dev.20180108", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.0-dev.20180108.tgz", + "integrity": "sha512-ZlggGsch8Y2d0LqAlCYGRzm/gdm2bqSpb4rdthY+YvpPsQqFixT0tU8sgjeRibMXW9hbS2Hz6kibS8L2oUKWfQ==", "dev": true }, "uglify-js": { @@ -5086,12 +5642,78 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, "unique-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", "dev": true }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -5116,6 +5738,85 @@ } } }, + "use": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", + "integrity": "sha1-riig1y+TvyJCKhii43mZMRLeyOg=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "isobject": "3.0.1", + "lazy-cache": "2.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -5166,16 +5867,6 @@ "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", "dev": true }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, "vinyl": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", @@ -5354,15 +6045,6 @@ "cliui": "2.1.0", "decamelize": "1.2.0", "window-size": "0.1.0" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - } } }, "yn": { diff --git a/package.json b/package.json index b3c76e6d515..fb089b64918 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@types/browserify": "latest", - "@types/colors": "latest", + "@types/chai": "latest", "@types/convert-source-map": "latest", "@types/del": "latest", "@types/glob": "latest", @@ -44,7 +44,7 @@ "@types/minimist": "latest", "@types/mkdirp": "latest", "@types/mocha": "latest", - "@types/node": "latest", + "@types/node": "8.5.5", "@types/q": "latest", "@types/run-sequence": "latest", "@types/through2": "latest", @@ -52,6 +52,7 @@ "xml2js": "^0.4.19", "browser-resolve": "^1.11.2", "browserify": "latest", + "chai": "latest", "convert-source-map": "latest", "del": "latest", "gulp": "3.X", @@ -78,7 +79,7 @@ "ts-node": "latest", "tslint": "latest", "vinyl": "latest", - "colors": "latest", + "chalk": "latest", "typescript": "next" }, "scripts": { diff --git a/scripts/tslint/formatters/autolinkableStylishFormatter.ts b/scripts/tslint/formatters/autolinkableStylishFormatter.ts index 6a02ec24f05..23e01bc8587 100644 --- a/scripts/tslint/formatters/autolinkableStylishFormatter.ts +++ b/scripts/tslint/formatters/autolinkableStylishFormatter.ts @@ -1,5 +1,5 @@ import * as Lint from "tslint"; -import * as colors from "colors"; +import chalk from "chalk"; import { sep } from "path"; function groupBy(array: ReadonlyArray | undefined, getGroupId: (elem: T, index: number) => number | string): T[][] { if (!array) { @@ -52,7 +52,7 @@ function getLink(failure: Lint.RuleFailure, color: boolean): string { if (path.indexOf("/") === -1 && path.indexOf("\\") === -1) { path = `.${sep}${path}`; } - return `${color ? (sev === "WARNING" ? colors.blue(sev) : colors.red(sev)) : sev}: ${path}:${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`; + return `${color ? (sev === "WARNING" ? chalk.blue(sev) : chalk.red(sev)) : sev}: ${path}:${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`; } function getLinkMaxSize(failures: Lint.RuleFailure[]): number { @@ -91,7 +91,7 @@ export class Formatter extends Lint.Formatters.AbstractFormatter { const nameMaxSize = getNameMaxSize(group); return ` ${currentFile} -${group.map(f => `${pad(getLink(f, /*color*/ true), getLink(f, /*color*/ false).length, linkMaxSize)} ${colors.grey(pad(f.getRuleName(), f.getRuleName().length, nameMaxSize))} ${colors.yellow(f.getFailure())}`).join("\n")}`; +${group.map(f => `${pad(getLink(f, /*color*/ true), getLink(f, /*color*/ false).length, linkMaxSize)} ${chalk.grey(pad(f.getRuleName(), f.getRuleName().length, nameMaxSize))} ${chalk.yellow(f.getFailure())}`).join("\n")}`; }).join("\n"); } } \ No newline at end of file diff --git a/scripts/tslint/rules/noDoubleSpaceRule.ts b/scripts/tslint/rules/noDoubleSpaceRule.ts new file mode 100644 index 00000000000..34ab470ebce --- /dev/null +++ b/scripts/tslint/rules/noDoubleSpaceRule.ts @@ -0,0 +1,54 @@ +import * as Lint from "tslint/lib"; +import * as ts from "typescript"; + +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } +} + +function walk(ctx: Lint.WalkContext): void { + const { sourceFile } = ctx; + const lines = sourceFile.text.split("\n"); + const strings = getLiterals(sourceFile); + lines.forEach((line, idx) => { + // Skip indentation. + const firstNonSpace = /\S/.exec(line); + if (firstNonSpace === null) { + return; + } + // Allow common uses of double spaces + // * To align `=` or `!=` signs + // * To align comments at the end of lines + // * To indent inside a comment + // * To use two spaces after a period + // * To include aligned `->` in a comment + const rgx = /[^/*. ] [^-!/= ]/g; + rgx.lastIndex = firstNonSpace.index; + const doubleSpace = rgx.exec(line); + // Also allow to align comments after `@param` + if (doubleSpace !== null && !line.includes("@param")) { + const pos = lines.slice(0, idx).reduce((len, line) => len + 1 + line.length, 0) + doubleSpace.index; + if (!strings.some(s => s.getStart() <= pos && s.end > pos)) { + ctx.addFailureAt(pos + 1, 2, "Use only one space."); + } + } + }); +} + +function getLiterals(sourceFile: ts.SourceFile): ReadonlyArray { + const out: ts.Node[] = []; + sourceFile.forEachChild(function cb(node) { + switch (node.kind) { + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.TemplateHead: + case ts.SyntaxKind.TemplateMiddle: + case ts.SyntaxKind.TemplateTail: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + case ts.SyntaxKind.RegularExpressionLiteral: + out.push(node); + } + node.forEachChild(cb); + }); + return out; +} diff --git a/scripts/tslint/rules/typeOperatorSpacingRule.ts b/scripts/tslint/rules/typeOperatorSpacingRule.ts index d7da2e6b5e8..82dccfe0eac 100644 --- a/scripts/tslint/rules/typeOperatorSpacingRule.ts +++ b/scripts/tslint/rules/typeOperatorSpacingRule.ts @@ -2,7 +2,7 @@ import * as Lint from "tslint/lib"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { - public static FAILURE_STRING = "The '|' and '&' operators must be surrounded by single spaces"; + public static FAILURE_STRING = "The '|' and '&' operators must be surrounded by spaces"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithFunction(sourceFile, walk); @@ -11,26 +11,20 @@ export class Rule extends Lint.Rules.AbstractRule { function walk(ctx: Lint.WalkContext): void { const { sourceFile } = ctx; - ts.forEachChild(sourceFile, recur); - function recur(node: ts.Node): void { - if (node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType) { - check((node as ts.UnionOrIntersectionTypeNode).types); + sourceFile.forEachChild(function cb(node: ts.Node): void { + if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) { + check(node); } - ts.forEachChild(node, recur); - } + node.forEachChild(cb); + }); - function check(types: ReadonlyArray): void { - let expectedStart = types[0].end + 2; // space, | or & - for (let i = 1; i < types.length; i++) { - const currentType = types[i]; - if (expectedStart !== currentType.pos || currentType.getLeadingTriviaWidth() !== 1) { - const previousTypeEndPos = sourceFile.getLineAndCharacterOfPosition(types[i - 1].end); - const currentTypeStartPos = sourceFile.getLineAndCharacterOfPosition(currentType.pos); - if (previousTypeEndPos.line === currentTypeStartPos.line) { - ctx.addFailureAtNode(currentType, Rule.FAILURE_STRING); - } + function check(node: ts.UnionTypeNode | ts.IntersectionTypeNode): void { + const list = node.getChildren().find(child => child.kind === ts.SyntaxKind.SyntaxList)!; + for (const child of list.getChildren()) { + if ((child.kind === ts.SyntaxKind.BarToken || child.kind === ts.SyntaxKind.AmpersandToken) + && (/\S/.test(sourceFile.text[child.getStart(sourceFile) - 1]) || /\S/.test(sourceFile.text[child.end]))) { + ctx.addFailureAtNode(child, Rule.FAILURE_STRING); } - expectedStart = currentType.end + 2; } } } diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 71b5e1afbff..4e4b4f12705 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -303,7 +303,7 @@ namespace ts { // without names can only come from JSDocFunctionTypes. Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType); const functionType = node.parent; - const index = indexOf(functionType.parameters, node); + const index = functionType.parameters.indexOf(node as ParameterDeclaration); return "arg" + index as __String; case SyntaxKind.JSDocTypedefTag: const name = getNameOfJSDocTypedef(node as JSDocTypedefTag); @@ -746,7 +746,11 @@ namespace ts { } function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { - return expr1.kind === SyntaxKind.TypeOfExpression && isNarrowableOperand((expr1).expression) && expr2.kind === SyntaxKind.StringLiteral; + return expr1.kind === SyntaxKind.TypeOfExpression && isNarrowableOperand((expr1).expression) && (expr2.kind === SyntaxKind.StringLiteral || expr2.kind === SyntaxKind.NoSubstitutionTemplateLiteral); + } + + function isNarrowableInOperands(left: Expression, right: Expression) { + return (left.kind === SyntaxKind.StringLiteral || left.kind === SyntaxKind.NoSubstitutionTemplateLiteral) && isNarrowingExpression(right); } function isNarrowingBinaryExpression(expr: BinaryExpression) { @@ -761,6 +765,8 @@ namespace ts { isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); case SyntaxKind.InstanceOfKeyword: return isNarrowableOperand(expr.left); + case SyntaxKind.InKeyword: + return isNarrowableInOperands(expr.left, expr.right); case SyntaxKind.CommaToken: return isNarrowingExpression(expr.right); } @@ -1627,7 +1633,7 @@ namespace ts { // to the one we would get for: { <...>(...): T } // // We do that by making an anonymous type literal symbol, and then setting the function - // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable + // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable // from an actual type literal symbol you would have gotten had you used the long form. const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)); addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); @@ -2283,30 +2289,13 @@ namespace ts { declareSymbol(file.symbol.exports, file.symbol, node.left, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); } - function isExportsOrModuleExportsOrAlias(node: Node): boolean { - return isExportsIdentifier(node) || - isModuleExportsPropertyAccessExpression(node) || - isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(node); - } - - 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 { - return isExportsOrModuleExportsOrAlias(node) || - (isAssignmentExpression(node, /*excludeCompoundAssignements*/ true) && (isExportsOrModuleExportsOrAliasOrAssignment(node.left) || isExportsOrModuleExportsOrAliasOrAssignment(node.right))); - } - function bindModuleExportsAssignment(node: BinaryExpression) { // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' // is still pointing to 'module.exports'. // We do not want to consider this as 'export=' since a module can have only one of these. // Similarly we do not want to treat 'module.exports = exports' as an 'export='. const assignedExpression = getRightMostAssignedExpression(node.right); - if (isEmptyObjectLiteral(assignedExpression) || isExportsOrModuleExportsOrAlias(assignedExpression)) { + if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { // Mark it as a module in case there are no other exports in the file setCommonJsModuleIndicator(node); return; @@ -2387,7 +2376,7 @@ namespace ts { if (node.kind === SyntaxKind.BinaryExpression) { leftSideOfAssignment.parent = node; } - if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) { + if (container === file && isNameOfExportsOrModuleExportsAliasDeclaration(file, target)) { // This can be an alias for the 'exports' or 'module.exports' names, e.g. // var util = module.exports; // util.property = function ... @@ -2400,11 +2389,7 @@ namespace ts { } function lookupSymbolForName(name: __String) { - const local = container.locals && container.locals.get(name); - if (local) { - return local.exportSymbol || local; - } - return container.symbol && container.symbol.exports && container.symbol.exports.get(name); + return lookupSymbolForNameWorker(container, name); } function bindPropertyAssignment(functionName: __String, propertyAccess: PropertyAccessExpression, isPrototypeProperty: boolean) { @@ -2425,7 +2410,7 @@ namespace ts { if (!isPrototypeProperty && (!targetSymbol || !(targetSymbol.flags & SymbolFlags.Namespace)) && isLegalPosition) { Debug.assert(isIdentifier(propertyAccess.expression)); const identifier = propertyAccess.expression as Identifier; - const flags = SymbolFlags.Module | SymbolFlags.JSContainer; + const flags = SymbolFlags.Module | SymbolFlags.JSContainer; const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer; if (targetSymbol) { addDeclarationToSymbol(symbol, identifier, flags); @@ -2532,7 +2517,7 @@ namespace ts { } if (isBindingPattern(node.name)) { - bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + indexOf(node.parent.parameters, node) as __String); + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + node.parent.parameters.indexOf(node) as __String); } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); @@ -2643,6 +2628,33 @@ namespace ts { } } + /* @internal */ + export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { + return isExportsIdentifier(node) || + isModuleExportsPropertyAccessExpression(node) || + isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile, node); + } + + function isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile: SourceFile, node: Identifier): boolean { + const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText); + return symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && + symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, symbol.valueDeclaration.initializer); + } + + function isExportsOrModuleExportsOrAliasOrAssignment(sourceFile: SourceFile, node: Expression): boolean { + return isExportsOrModuleExportsOrAlias(sourceFile, node) || + (isAssignmentExpression(node, /*excludeCompoundAssignements*/ true) && ( + isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.left) || isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.right))); + } + + function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined { + const local = container.locals && container.locals.get(name); + if (local) { + return local.exportSymbol || local; + } + return container.symbol && container.symbol.exports && container.symbol.exports.get(name); + } + /** * Computes the transform flags for a node, given the transform flags of its subtree * diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 34ca1bdf0e9..7e44a9608f0 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -468,9 +468,9 @@ namespace ts { } function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(references.entries(), ([filePath, referencesInFile]) => + return arrayFrom(mapDefinedIterator(references.entries(), ([filePath, referencesInFile]) => referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); + )); } function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { @@ -504,7 +504,7 @@ namespace ts { } // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); + return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5f94c3ab7da..d530ef8e334 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -171,6 +171,7 @@ namespace ts { node = getParseTreeNode(node, isExpression); return node ? getContextualType(node) : undefined; }, + isContextSensitive, getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, theArgumentCount) => { node = getParseTreeNode(node, isCallLikeExpression); @@ -185,7 +186,11 @@ namespace ts { }, isValidPropertyAccess: (node, propertyName) => { node = getParseTreeNode(node, isPropertyAccessOrQualifiedName); - return node ? isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)) : false; + return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); + }, + isValidPropertyAccessForCompletions: (node, type, property) => { + node = getParseTreeNode(node, isPropertyAccessExpression); + return !!node && isValidPropertyAccessForCompletions(node, type, property); }, getSignatureFromDeclaration: declaration => { declaration = getParseTreeNode(declaration, isFunctionLike); @@ -210,7 +215,18 @@ namespace ts { getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, getExportsAndPropertiesOfModule, - getSymbolWalker: createGetSymbolWalker(getRestTypeOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType, getConstraintFromTypeParameter, getFirstIdentifier), + getSymbolWalker: createGetSymbolWalker( + getRestTypeOfSignature, + getTypePredicateOfSignature, + getReturnTypeOfSignature, + getBaseTypes, + resolveStructuredTypeMembers, + getTypeOfSymbol, + getResolvedSymbol, + getIndexTypeOfStructuredType, + getConstraintFromTypeParameter, + getFirstIdentifier, + ), getAmbientModules, getAllAttributesTypeFromJsxOpeningLikeElement: node => { node = getParseTreeNode(node, isJsxOpeningLikeElement); @@ -252,11 +268,12 @@ namespace ts { getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), getBaseConstraintOfType, getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, - resolveName(name, location, meaning) { - return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + resolveName(name, location, meaning, excludeGlobals) { + return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); }, getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()), getAccessibleSymbolChain, + resolveExternalModuleSymbol, }; const tupleTypes: GenericType[] = []; @@ -312,15 +329,18 @@ namespace ts { markerSubType.constraint = markerSuperType; const markerOtherType = createType(TypeFlags.TypeParameter); - const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const noTypePredicate = createIdentifierTypePredicate("<>", 0, anyType); + + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); const globals = createSymbolTable(); + const reverseMappedCache = createMap(); let ambientModulesCache: Symbol[] | undefined; /** * List of every ambient module with a "*" wildcard. @@ -516,6 +536,7 @@ namespace ts { Normal = 0, // Normal type checking SkipContextSensitive = 1, // Skip context sensitive function expressions Inferential = 2, // Inferential typing + Contextual = 3, // Normal type checking informed by a contextual type, therefore not cacheable } const enum CallbackCheck { @@ -825,7 +846,7 @@ namespace ts { return true; } const sourceFiles = host.getSourceFiles(); - return indexOf(sourceFiles, declarationFile) <= indexOf(sourceFiles, useFile); + return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); } if (declaration.pos <= usage.pos) { @@ -931,8 +952,9 @@ namespace ts { nameNotFoundMessage: DiagnosticMessage | undefined, nameArg: __String | Identifier, isUse: boolean, + excludeGlobals = false, suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, getSymbol, suggestedNameNotFoundMessage); + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage); } function resolveNameHelper( @@ -942,6 +964,7 @@ namespace ts { nameNotFoundMessage: DiagnosticMessage, nameArg: __String | Identifier, isUse: boolean, + excludeGlobals: boolean, lookup: typeof getSymbol, suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol { const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location @@ -1007,7 +1030,7 @@ namespace ts { // It's an external module. First see if the module has an export default and if the local // name of that export default matches. - if (result = moduleExports.get("default" as __String)) { + if (result = moduleExports.get(InternalSymbolName.Default)) { const localSymbol = getLocalSymbolForExportDefault(result); if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { break loop; @@ -1166,7 +1189,7 @@ namespace ts { } break; } - if (location.kind !== SyntaxKind.Block) { + if (isNonBlockLocation(location)) { lastNonBlockLocation = location; } lastLocation = location; @@ -1174,7 +1197,7 @@ namespace ts { } // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. - // If `result === lastLocation.symbol`, that means that we are somewhere inside `lastLocation` looking up a name, and resolving to `lastLocation` itself. + // If `result === lastNonBlockLocation.symbol`, that means that we are somewhere inside `lastNonBlockLocation` looking up a name, and resolving to `lastLocation` itself. // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. if (isUse && result && nameNotFoundMessage && noUnusedIdentifiers && result !== lastNonBlockLocation.symbol) { result.isReferenced = true; @@ -1188,7 +1211,9 @@ namespace ts { } } - result = lookup(globals, name, meaning); + if (!excludeGlobals) { + result = lookup(globals, name, meaning); + } } if (!result) { @@ -1198,7 +1223,7 @@ namespace ts { !checkAndReportErrorForExtendingInterface(errorLocation) && !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) { + !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) { let suggestion: string | undefined; if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) { suggestion = getSuggestionForNonexistentSymbol(originalLocation, name, meaning); @@ -1257,6 +1282,20 @@ namespace ts { return result; } + function isNonBlockLocation({ kind }: Node): boolean { + switch (kind) { + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return false; + default: + return true; + } + } + function diagnosticName(nameArg: __String | Identifier) { return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); } @@ -1450,6 +1489,43 @@ namespace ts { return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); } + function resolveExportByName(moduleSymbol: Symbol, name: __String, dontResolveAlias: boolean) { + const exportValue = moduleSymbol.exports.get(InternalSymbolName.ExportEquals); + return exportValue + ? getPropertyOfType(getTypeOfSymbol(exportValue), name) + : resolveSymbol(moduleSymbol.exports.get(name), dontResolveAlias); + } + + function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) { + if (!allowSyntheticDefaultImports) { + return false; + } + // Declaration files (and ambient modules) + if (!file || file.isDeclarationFile) { + // Definitely cannot have a synthetic default if they have a default member specified + if (resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias)) { + return false; + } + // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member + // So we check a bit more, + if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias)) { + // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), + // it definitely is a module and does not have a synthetic default + return false; + } + // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set + // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member + // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm + return true; + } + // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement + if (!isSourceFileJavaScript(file)) { + return hasExportAssignmentSymbol(moduleSymbol); + } + // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker + return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias); + } + function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); @@ -1459,16 +1535,16 @@ namespace ts { exportDefaultSymbol = moduleSymbol; } else { - const exportValue = moduleSymbol.exports.get("export=" as __String); - exportDefaultSymbol = exportValue - ? getPropertyOfType(getTypeOfSymbol(exportValue), "default" as __String) - : resolveSymbol(moduleSymbol.exports.get("default" as __String), dontResolveAlias); + exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias); } - if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { + const file = find(moduleSymbol.declarations, isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); + if (!exportDefaultSymbol && !hasSyntheticDefault) { error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); } - else if (!exportDefaultSymbol && allowSyntheticDefaultImports) { + else if (!exportDefaultSymbol && hasSyntheticDefault) { + // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } return exportDefaultSymbol; @@ -1551,7 +1627,7 @@ namespace ts { symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); let symbolFromModule = getExportOfModule(targetSymbol, name.escapedText, dontResolveAlias); // If the export member we're looking for is default, and there is no real default but allowSyntheticDefaultImports is on, return the entire module as the default - if (!symbolFromModule && allowSyntheticDefaultImports && name.escapedText === "default") { + if (!symbolFromModule && allowSyntheticDefaultImports && name.escapedText === InternalSymbolName.Default) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } const symbol = symbolFromModule && symbolFromVariable ? @@ -1868,8 +1944,40 @@ namespace ts { // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). function resolveESModuleSymbol(moduleSymbol: Symbol, moduleReferenceExpression: Expression, dontResolveAlias: boolean): Symbol { const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); - if (!dontResolveAlias && symbol && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { - error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); + if (!dontResolveAlias && symbol) { + if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { + error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); + return symbol; + } + if (compilerOptions.esModuleInterop) { + const referenceParent = moduleReferenceExpression.parent; + if ( + (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || + isImportCall(referenceParent) + ) { + const type = getTypeOfSymbol(symbol); + let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); + if (!sigs || !sigs.length) { + sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); + } + if (sigs && sigs.length) { + const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol); + // Create a new symbol which has the module's type less the call and construct signatures + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + result.target = symbol; + result.originatingImport = referenceParent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = cloneMap(symbol.members); + if (symbol.exports) result.exports = cloneMap(symbol.exports); + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); + return result; + } + } + } } return symbol; } @@ -1938,7 +2046,7 @@ namespace ts { function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { if (!source) return; source.forEach((sourceSymbol, id) => { - if (id === "default") return; + if (id === InternalSymbolName.Default) return; const targetSymbol = target.get(id); if (!targetSymbol) { @@ -2203,7 +2311,8 @@ namespace ts { // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain // but only if the symbolFromSymbolTable can be qualified - const accessibleSymbolsFromExports = resolvedImportedSymbol.exports ? getAccessibleSymbolChainFromSymbolTable(resolvedImportedSymbol.exports) : undefined; + const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); + const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable); if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); } @@ -2840,7 +2949,10 @@ namespace ts { typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context)); } if (resolvedType.stringIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context)); + const indexInfo = resolvedType.objectFlags & ObjectFlags.ReverseMapped ? + createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration) : + resolvedType.stringIndexInfo; + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(indexInfo, IndexKind.String, context)); } if (resolvedType.numberIndexInfo) { typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context)); @@ -2852,7 +2964,7 @@ namespace ts { } for (const propertySymbol of properties) { - const propertyType = getTypeOfSymbol(propertySymbol); + const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(propertySymbol); const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); @@ -2926,8 +3038,8 @@ namespace ts { parameters.unshift(thisParameter); } let returnTypeNode: TypeNode; - if (signature.typePredicate) { - const typePredicate = signature.typePredicate; + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { const parameterName = typePredicate.kind === TypePredicateKind.Identifier ? setEmitFlags(createIdentifier((typePredicate).parameterName), EmitFlags.NoAsciiEscaping) : createThisTypeNode(); @@ -3208,7 +3320,7 @@ namespace ts { * ensuring that any names written with literals use element accesses. */ function appendPropertyOrElementAccessForSymbol(symbol: Symbol, writer: SymbolWriter): void { - const symbolName = symbol.escapedName === "default" ? "default" : getNameOfSymbolAsWritten(symbol); + const symbolName = symbol.escapedName === InternalSymbolName.Default ? InternalSymbolName.Default : getNameOfSymbolAsWritten(symbol); const firstChar = symbolName.charCodeAt(0); const needsElementAccess = !isIdentifierStart(firstChar, languageVersion); @@ -3661,7 +3773,10 @@ namespace ts { writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } - buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); + const stringIndexInfo = resolved.objectFlags & ObjectFlags.ReverseMapped && resolved.stringIndexInfo ? + createIndexInfo(anyType, resolved.stringIndexInfo.isReadonly, resolved.stringIndexInfo.declaration) : + resolved.stringIndexInfo; + buildIndexSignatureDisplay(stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); for (const p of resolved.properties) { if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) { @@ -3672,7 +3787,7 @@ namespace ts { writer.reportPrivateInBaseOfClassExpression(symbolName(p)); } } - const t = getTypeOfSymbol(p); + const t = getCheckFlags(p) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(p); if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { const signatures = getSignaturesOfType(t, SignatureKind.Call); for (const signature of signatures) { @@ -3887,8 +4002,9 @@ namespace ts { } writeSpace(writer); - if (signature.typePredicate) { - buildTypePredicateDisplay(signature.typePredicate, writer, enclosingDeclaration, flags, symbolStack); + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags, symbolStack); } else { buildTypeDisplay(returnType, writer, enclosingDeclaration, flags, symbolStack); @@ -3937,7 +4053,12 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseBracketToken); writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); - buildTypeDisplay(info.type, writer, enclosingDeclaration, globalFlags, symbolStack); + if (info.type) { + buildTypeDisplay(info.type, writer, enclosingDeclaration, globalFlags, symbolStack); + } + else { + writeKeyword(writer, SyntaxKind.AnyKeyword); + } writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } @@ -4196,7 +4317,7 @@ namespace ts { // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been // assigned by contextual typing. - function getTypeForBindingElementParent(node: VariableLikeDeclaration) { + function getTypeForBindingElementParent(node: BindingElementGrandparent) { const symbol = getSymbolOfNode(node); return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); } @@ -4303,7 +4424,7 @@ namespace ts { } else { // Use specific property type when parent is a tuple or numeric index type when parent is an array - const propName = "" + indexOf(pattern.elements, declaration); + const propName = "" + pattern.elements.indexOf(declaration); type = isTupleLikeType(parentType) ? getTypeOfPropertyOfType(parentType, propName as __String) : elementType; @@ -4324,7 +4445,7 @@ namespace ts { type = getTypeWithFacts(type, TypeFacts.NEUndefined); } return declaration.initializer ? - getUnionType([type, checkExpressionCached(declaration.initializer)], /*subtypeReduction*/ true) : + getUnionType([type, checkExpressionCached(declaration.initializer)], UnionReduction.Subtype) : type; } @@ -4351,15 +4472,15 @@ namespace ts { } // Return the inferred type for a variable, parameter, or property declaration - function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type { + function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): Type { // A variable declared in a for..in statement is of type string, or of type keyof T when the // right hand expression is of a type parameter type. - if (declaration.parent.parent.kind === SyntaxKind.ForInStatement) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { const indexType = getIndexType(checkNonNullExpression((declaration.parent.parent).expression)); return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? indexType : stringType; } - if (declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { // checkRightHandSideOfForOf will return undefined if the for-of expression type was // missing properties/signatures required to get its iteratedType (like // [Symbol.iterator] or next). This may be because we accessed properties from anyType, @@ -4372,11 +4493,11 @@ namespace ts { return getTypeForBindingElement(declaration); } + const isOptional = !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken && includeOptionality; // Use type from type annotation if one is present - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - const declaredType = getTypeFromTypeNode(typeNode); - return addOptionality(declaredType, /*optional*/ !!declaration.questionToken && includeOptionality); + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (declaredType) { + return addOptionality(declaredType, isOptional); } if ((noImplicitAny || isInJavaScriptFile(declaration)) && @@ -4420,14 +4541,14 @@ namespace ts { type = getContextuallyTypedParameterType(declaration); } if (type) { - return addOptionality(type, /*optional*/ !!declaration.questionToken && includeOptionality); + return addOptionality(type, isOptional); } } // Use the type of the initializer expression if one is present if (declaration.initializer) { const type = checkDeclarationInitializer(declaration); - return addOptionality(type, /*optional*/ !!declaration.questionToken && includeOptionality); + return addOptionality(type, isOptional); } if (isJsxAttribute(declaration)) { @@ -4436,11 +4557,6 @@ namespace ts { return trueType; } - // If it is a short-hand property assignment, use the type of the identifier - if (declaration.kind === SyntaxKind.ShorthandPropertyAssignment) { - return checkIdentifier(declaration.name); - } - // If the declaration specifies a binding pattern, use the type implied by the binding pattern if (isBindingPattern(declaration.name)) { return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); @@ -4492,7 +4608,7 @@ namespace ts { } } - const type = jsDocType || getUnionType(types, /*subtypeReduction*/ true); + const type = jsDocType || getUnionType(types, UnionReduction.Subtype); return getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); } @@ -4585,7 +4701,7 @@ namespace ts { // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. - function getWidenedTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, reportErrors?: boolean): Type { + function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, reportErrors?: boolean): Type { let type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); if (type) { if (reportErrors) { @@ -4593,21 +4709,15 @@ namespace ts { } // always widen a 'unique symbol' type if the type was created for a different declaration. - if (type.flags & TypeFlags.UniqueESSymbol && !declaration.type && type.symbol !== getSymbolOfNode(declaration)) { + if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { type = esSymbolType; } - // During a normal type check we'll never get to here with a property assignment (the check of the containing - // object literal uses a different path). We exclude widening only so that language services and type verification - // tools see the actual type. - if (declaration.kind === SyntaxKind.PropertyAssignment) { - return type; - } return getWidenedType(type); } // Rest parameters default to type any[], other parameters default to type any - type = declaration.dotDotDotToken ? anyArrayType : anyType; + type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; // Report implicit any errors unless this is a private property within an ambient declaration if (reportErrors && noImplicitAny) { @@ -4624,6 +4734,13 @@ namespace ts { return isPrivateWithinAmbient(memberDeclaration); } + function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + } + function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { @@ -4658,8 +4775,39 @@ namespace ts { declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) { type = getWidenedTypeFromJSSpecialPropertyDeclarations(symbol); } + else if (isJSDocPropertyTag(declaration) + || isPropertyAccessExpression(declaration) + || isIdentifier(declaration) + || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) + || isMethodSignature(declaration)) { + + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); + } + type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType; + } + else if (isPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); + } + else if (isJsxAttribute(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); + } + else if (isShorthandPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); + } + else if (isObjectLiteralMethod(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); + } + else if (isParameter(declaration) + || isPropertyDeclaration(declaration) + || isPropertySignature(declaration) + || isVariableDeclaration(declaration) + || isBindingElement(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); + } else { - type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); + Debug.fail("Unhandled declaration kind! " + (ts as any).SyntaxKind[declaration.kind]); } if (!popTypeResolution()) { @@ -4847,6 +4995,9 @@ namespace ts { if (getCheckFlags(symbol) & CheckFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } + if (getCheckFlags(symbol) & CheckFlags.ReverseMapped) { + return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); + } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); } @@ -5075,11 +5226,11 @@ namespace ts { return type.resolvedBaseTypes; } - function resolveBaseTypesOfClass(type: InterfaceType): void { - type.resolvedBaseTypes = emptyArray; + function resolveBaseTypesOfClass(type: InterfaceType) { + type.resolvedBaseTypes = resolvingEmptyArray; const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { - return; + return type.resolvedBaseTypes = emptyArray; } const baseTypeNode = getBaseTypeNodeOfClass(type); const typeArgs = typeArgumentsFromTypeReferenceNode(baseTypeNode); @@ -5102,24 +5253,31 @@ namespace ts { const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); if (!constructors.length) { error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); - return; + return type.resolvedBaseTypes = emptyArray; } baseType = getReturnTypeOfSignature(constructors[0]); } if (baseType === unknownType) { - return; + return type.resolvedBaseTypes = emptyArray; } if (!isValidBaseType(baseType)) { error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type, typeToString(baseType)); - return; + return type.resolvedBaseTypes = emptyArray; } if (type === baseType || hasBaseType(baseType, type)) { error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - return; + return type.resolvedBaseTypes = emptyArray; } - type.resolvedBaseTypes = [baseType]; + if (type.resolvedBaseTypes === resolvingEmptyArray) { + // Circular reference, likely through instantiation of default parameters + // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset + // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a + // partial instantiation of the members without the base types fully resolved + (type as Type as ResolvedType).members = undefined; + } + return type.resolvedBaseTypes = [baseType]; } function areAllOuterTypeParametersApplied(type: Type): boolean { @@ -5325,7 +5483,7 @@ namespace ts { } } if (memberTypeList.length) { - const enumType = getUnionType(memberTypeList, /*subtypeReduction*/ false, symbol, /*aliasTypeArguments*/ undefined); + const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); if (enumType.flags & TypeFlags.Union) { enumType.flags |= TypeFlags.EnumLiteral; enumType.symbol = symbol; @@ -5431,7 +5589,7 @@ namespace ts { */ function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isThislessType(typeNode) : !node.initializer; + return typeNode ? isThislessType(typeNode) : !hasInitializer(node); } /** @@ -5754,6 +5912,7 @@ namespace ts { if (source.symbol && members === getMembersOfSymbol(source.symbol)) { members = createSymbolTable(source.declaredProperties); } + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); const thisArgument = lastOrUndefined(typeArguments); for (const baseType of baseTypes) { const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; @@ -5783,15 +5942,24 @@ namespace ts { resolveObjectTypeMembers(type, source, typeParameters, typeArguments); } - function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], - resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasLiteralTypes: boolean): Signature { + function createSignature( + declaration: SignatureDeclaration, + typeParameters: TypeParameter[], + thisParameter: Symbol | undefined, + parameters: Symbol[], + resolvedReturnType: Type | undefined, + resolvedTypePredicate: TypePredicate | undefined, + minArgumentCount: number, + hasRestParameter: boolean, + hasLiteralTypes: boolean, + ): Signature { const sig = new Signature(checker); sig.declaration = declaration; sig.typeParameters = typeParameters; sig.parameters = parameters; sig.thisParameter = thisParameter; sig.resolvedReturnType = resolvedReturnType; - sig.typePredicate = typePredicate; + sig.resolvedTypePredicate = resolvedTypePredicate; sig.minArgumentCount = minArgumentCount; sig.hasRestParameter = hasRestParameter; sig.hasLiteralTypes = hasLiteralTypes; @@ -5799,15 +5967,15 @@ namespace ts { } function cloneSignature(sig: Signature): Signature { - return createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, sig.resolvedReturnType, - sig.typePredicate, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes); + return createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes); } function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { const baseConstructorType = getBaseConstructorTypeOfClass(classType); const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); if (baseSignatures.length === 0) { - return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false)]; + return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false)]; } const baseTypeNode = getBaseTypeNodeOfClass(classType); const isJavaScript = isInJavaScriptFile(baseTypeNode); @@ -5817,7 +5985,7 @@ namespace ts { for (const baseSig of baseSignatures) { const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); const typeParamCount = length(baseSig.typeParameters); - if (isJavaScript || (typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount)) { + if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); sig.typeParameters = classType.localTypeParameters; sig.resolvedReturnType = classType; @@ -5877,13 +6045,13 @@ namespace ts { let s = signature; // Union the result types when more than one signature matches if (unionSignatures.length > 1) { - s = cloneSignature(signature); + let thisParameter = signature.thisParameter; if (forEach(unionSignatures, sig => sig.thisParameter)) { - const thisType = getUnionType(map(unionSignatures, sig => getTypeOfSymbol(sig.thisParameter) || anyType), /*subtypeReduction*/ true); - s.thisParameter = createSymbolWithType(signature.thisParameter, thisType); + const thisType = getUnionType(map(unionSignatures, sig => sig.thisParameter ? getTypeOfSymbol(sig.thisParameter) : anyType), UnionReduction.Subtype); + thisParameter = createSymbolWithType(signature.thisParameter, thisType); } - // Clear resolved return type we possibly got from cloneSignature - s.resolvedReturnType = undefined; + s = cloneSignature(signature); + s.thisParameter = thisParameter; s.unionSignatures = unionSignatures; } (result || (result = [])).push(s); @@ -5905,7 +6073,7 @@ namespace ts { indexTypes.push(indexInfo.type); isAnyReadonly = isAnyReadonly || indexInfo.isReadonly; } - return createIndexInfo(getUnionType(indexTypes, /*subtypeReduction*/ true), isAnyReadonly); + return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly); } function resolveUnionTypeMembers(type: UnionType) { @@ -6007,6 +6175,7 @@ namespace ts { if (symbol.exports) { members = getExportsOfSymbol(symbol); } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, undefined, undefined); if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseConstructorType = getBaseConstructorTypeOfClass(classType); @@ -6039,6 +6208,23 @@ namespace ts { } } + function resolveReverseMappedTypeMembers(type: ReverseMappedType) { + const indexInfo = getIndexInfoOfType(type.source, IndexKind.String); + const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true; + const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional; + const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly); + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type.source)) { + const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); + const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; + inferredProp.declarations = prop.declarations; + inferredProp.propertyType = getTypeOfSymbol(prop); + inferredProp.mappedType = type.mappedType; + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + } + /** Resolve the members of a mapped type { [P in K]: T } */ function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); @@ -6178,6 +6364,9 @@ namespace ts { else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type); } + else if ((type).objectFlags & ObjectFlags.ReverseMapped) { + resolveReverseMappedTypeMembers(type as ReverseMappedType); + } else if ((type).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type); } @@ -6439,14 +6628,13 @@ namespace ts { function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol { let props: Symbol[]; - const types = containingType.types; const isUnion = containingType.flags & TypeFlags.Union; const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; // Flags we want to propagate to the result if they exist in all source symbols let commonFlags = isUnion ? SymbolFlags.None : SymbolFlags.Optional; let syntheticFlag = CheckFlags.SyntheticMethod; let checkFlags = 0; - for (const current of types) { + for (const current of containingType.types) { const type = getApparentType(current); if (type !== unknownType) { const prop = getPropertyOfType(type, name); @@ -6599,7 +6787,7 @@ namespace ts { } } if (propTypes.length) { - return getUnionType(propTypes, /*subtypeReduction*/ true); + return getUnionType(propTypes, UnionReduction.Subtype); } } return undefined; @@ -6663,7 +6851,7 @@ namespace ts { if (node.initializer) { const signatureDeclaration = node.parent; const signature = getSignatureFromDeclaration(signatureDeclaration); - const parameterIndex = ts.indexOf(signatureDeclaration.parameters, node); + const parameterIndex = signatureDeclaration.parameters.indexOf(node); Debug.assert(parameterIndex >= 0); return parameterIndex >= signature.minArgumentCount; } @@ -6671,7 +6859,7 @@ namespace ts { if (iife) { return !node.type && !node.dotDotDotToken && - indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length; + node.parent.parameters.indexOf(node) >= iife.arguments.length; } return false; @@ -6679,22 +6867,26 @@ namespace ts { function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate { const { parameterName } = node; + const type = getTypeFromTypeNode(node.type); if (parameterName.kind === SyntaxKind.Identifier) { - return { - kind: TypePredicateKind.Identifier, - parameterName: parameterName ? parameterName.escapedText : undefined, - parameterIndex: parameterName ? getTypePredicateParameterIndex((node.parent as SignatureDeclaration).parameters, parameterName) : undefined, - type: getTypeFromTypeNode(node.type) - } as IdentifierTypePredicate; + return createIdentifierTypePredicate( + parameterName && parameterName.escapedText as string, // TODO: GH#18217 + parameterName && getTypePredicateParameterIndex((node.parent as SignatureDeclaration).parameters, parameterName), + type); } else { - return { - kind: TypePredicateKind.This, - type: getTypeFromTypeNode(node.type) - }; + return createThisTypePredicate(type); } } + function createIdentifierTypePredicate(parameterName: string | undefined, parameterIndex: number | undefined, type: Type): IdentifierTypePredicate { + return { kind: TypePredicateKind.Identifier, parameterName, parameterIndex, type }; + } + + function createThisTypePredicate(type: Type): ThisTypePredicate { + return { kind: TypePredicateKind.This, type }; + } + /** * Gets the minimum number of type arguments needed to satisfy all non-optional type * parameters. @@ -6810,11 +7002,8 @@ namespace ts { : undefined; const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); const returnType = getSignatureReturnTypeFromDeclaration(declaration, isJSConstructSignature, classType); - const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? - createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : - undefined; const hasRestLikeParameter = hasRestParameter(declaration) || isInJavaScriptFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters); - links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, hasRestLikeParameter, hasLiteralTypes); + links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, /*resolvedTypePredicate*/ undefined, minArgumentCount, hasRestLikeParameter, hasLiteralTypes); } return links.resolvedSignature; } @@ -6951,6 +7140,30 @@ namespace ts { } } + function signatureHasTypePredicate(signature: Signature): boolean { + return getTypePredicateOfSignature(signature) !== undefined; + } + + function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { + if (!signature.resolvedTypePredicate) { + if (signature.target) { + const targetTypePredicate = getTypePredicateOfSignature(signature.target); + signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper) : noTypePredicate; + } + else if (signature.unionSignatures) { + signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures) || noTypePredicate; + } + else { + const declaration = signature.declaration; + signature.resolvedTypePredicate = declaration && declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? + createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : + noTypePredicate; + } + Debug.assert(!!signature.resolvedTypePredicate); + } + return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + } + function getReturnTypeOfSignature(signature: Signature): Type { if (!signature.resolvedReturnType) { if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { @@ -6961,7 +7174,7 @@ namespace ts { type = instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper); } else if (signature.unionSignatures) { - type = getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + type = getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); } else { type = getReturnTypeFromBody(signature.declaration); @@ -7803,7 +8016,7 @@ namespace ts { // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + function getUnionType(types: Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { return neverType; } @@ -7815,11 +8028,15 @@ namespace ts { if (typeSet.containsAny) { return anyType; } - if (subtypeReduction) { - removeSubtypes(typeSet); - } - else if (typeSet.containsLiteralOrUniqueESSymbol) { - removeRedundantLiteralTypes(typeSet); + switch (unionReduction) { + case UnionReduction.Literal: + if (typeSet.containsLiteralOrUniqueESSymbol) { + removeRedundantLiteralTypes(typeSet); + } + break; + case UnionReduction.Subtype: + removeSubtypes(typeSet); + break; } if (typeSet.length === 0) { return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType : @@ -7829,6 +8046,42 @@ namespace ts { return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments); } + function getUnionTypePredicate(signatures: ReadonlyArray): TypePredicate { + let first: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (!pred) { + continue; + } + + if (first) { + if (!typePredicateKindsMatch(first, pred)) { + // No common type predicate. + return undefined; + } + } + else { + first = pred; + } + types.push(pred.type); + } + if (!first) { + // No union signatures had a type predicate. + return undefined; + } + const unionType = getUnionType(types); + return isIdentifierTypePredicate(first) + ? createIdentifierTypePredicate(first.parameterName, first.parameterIndex, unionType) + : createThisTypePredicate(unionType); + } + + function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { + return isIdentifierTypePredicate(a) + ? isIdentifierTypePredicate(b) && a.parameterIndex === b.parameterIndex + : !isIdentifierTypePredicate(b); + } + // This function assumes the constituent type list is sorted and deduplicated. function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { @@ -7859,7 +8112,7 @@ namespace ts { function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*subtypeReduction*/ false, + links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node)); } return links.resolvedType; @@ -7934,7 +8187,7 @@ namespace ts { // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. const unionType = typeSet[unionIndex]; return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), - /*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments); + UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } const id = getTypeListId(typeSet); let type = intersectionTypes.get(id); @@ -7967,7 +8220,7 @@ namespace ts { } function getLiteralTypeFromPropertyName(prop: Symbol) { - return getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || startsWith(prop.escapedName as string, "__@") ? + return getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || isKnownSymbol(prop) ? neverType : getLiteralType(symbolName(prop)); } @@ -8022,6 +8275,7 @@ namespace ts { const prop = getPropertyOfType(objectType, propName); if (prop) { if (accessExpression) { + markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) { error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, symbolToString(prop)); return unknownType; @@ -8245,10 +8499,7 @@ namespace ts { if (right.flags & TypeFlags.Union) { return mapType(right, t => getSpreadType(left, t, symbol, propagatedFlags)); } - if (right.flags & TypeFlags.NonPrimitive) { - return nonPrimitiveType; - } - if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike)) { + if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive)) { return left; } @@ -8309,8 +8560,7 @@ namespace ts { emptyArray, getNonReadonlyIndexSignature(stringIndexInfo), getNonReadonlyIndexSignature(numberIndexInfo)); - spread.flags |= propagatedFlags; - spread.flags |= TypeFlags.FreshLiteral | TypeFlags.ContainsObjectLiteral; + spread.flags |= propagatedFlags | TypeFlags.ContainsObjectLiteral; (spread as ObjectType).objectFlags |= (ObjectFlags.ObjectLiteral | ObjectFlags.ContainsSpread); return spread; } @@ -8551,7 +8801,7 @@ namespace ts { * This is used during inference when instantiating type parameter defaults. */ function createBackreferenceMapper(typeParameters: TypeParameter[], index: number): TypeMapper { - return t => indexOf(typeParameters, t) >= index ? emptyObjectType : t; + return t => typeParameters.indexOf(t) >= index ? emptyObjectType : t; } function isInferenceContext(mapper: TypeMapper): mapper is InferenceContext { @@ -8579,7 +8829,7 @@ namespace ts { return result; } - function cloneTypePredicate(predicate: TypePredicate, mapper: TypeMapper): ThisTypePredicate | IdentifierTypePredicate { + function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): ThisTypePredicate | IdentifierTypePredicate { if (isIdentifierTypePredicate(predicate)) { return { kind: TypePredicateKind.Identifier, @@ -8598,7 +8848,6 @@ namespace ts { function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { let freshTypeParameters: TypeParameter[]; - let freshTypePredicate: TypePredicate; if (signature.typeParameters && !eraseTypeParameters) { // First create a fresh set of type parameters, then include a mapping from the old to the // new type parameters in the mapper function. Finally store this mapper in the new type @@ -8609,15 +8858,17 @@ namespace ts { tp.mapper = mapper; } } - if (signature.typePredicate) { - freshTypePredicate = cloneTypePredicate(signature.typePredicate, mapper); - } + // Don't compute resolvedReturnType and resolvedTypePredicate now, + // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) + // See GH#17600. const result = createSignature(signature.declaration, freshTypeParameters, signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), instantiateList(signature.parameters, mapper, instantiateSymbol), /*resolvedReturnType*/ undefined, - freshTypePredicate, - signature.minArgumentCount, signature.hasRestParameter, signature.hasLiteralTypes); + /*resolvedTypePredicate*/ undefined, + signature.minArgumentCount, + signature.hasRestParameter, + signature.hasLiteralTypes); result.target = signature; result.mapper = mapper; return result; @@ -8772,7 +9023,7 @@ namespace ts { } } if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - return getUnionType(instantiateTypes((type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + return getUnionType(instantiateTypes((type).types, mapper), UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (type.flags & TypeFlags.Intersection) { return getIntersectionType(instantiateTypes((type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); @@ -8927,7 +9178,7 @@ namespace ts { return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); } - function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } @@ -8935,7 +9186,7 @@ namespace ts { * This is *not* a bi-directional relationship. * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. */ - function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); } @@ -9011,7 +9262,7 @@ namespace ts { // with respect to T. const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType)); - const callbacks = sourceSig && targetSig && !sourceSig.typePredicate && !targetSig.typePredicate && + const callbacks = sourceSig && targetSig && !signatureHasTypePredicate(sourceSig) && !signatureHasTypePredicate(targetSig) && (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); const related = callbacks ? compareSignaturesRelated(targetSig, sourceSig, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) : @@ -9035,11 +9286,13 @@ namespace ts { const sourceReturnType = getReturnTypeOfSignature(source); // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions - if (target.typePredicate) { - if (source.typePredicate) { - result &= compareTypePredicateRelatedTo(source.typePredicate, target.typePredicate, source.declaration, target.declaration, reportErrors, errorReporter, compareTypes); + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate) { + result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, source.declaration, target.declaration, reportErrors, errorReporter, compareTypes); } - else if (isIdentifierTypePredicate(target.typePredicate)) { + else if (isIdentifierTypePredicate(targetTypePredicate)) { if (reportErrors) { errorReporter(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); } @@ -9247,6 +9500,10 @@ namespace ts { return false; } + function isIgnoredJsxProperty(source: Type, sourceProp: Symbol, targetMemberType: Type | undefined) { + return source.flags & TypeFlags.JsxAttributes && !(isUnhyphenatedJsxName(sourceProp.escapedName) || targetMemberType); + } + /** * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. @@ -9263,7 +9520,7 @@ namespace ts { relation: Map, errorNode: Node, headMessage?: DiagnosticMessage, - containingMessageChain?: DiagnosticMessageChain): boolean { + containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { let errorInfo: DiagnosticMessageChain; let maybeKeys: string[]; @@ -9283,11 +9540,25 @@ namespace ts { } else if (errorInfo) { if (containingMessageChain) { - errorInfo = concatenateDiagnosticMessageChains(containingMessageChain, errorInfo); + const chain = containingMessageChain(); + if (chain) { + errorInfo = concatenateDiagnosticMessageChains(chain, errorInfo); + } } diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo)); } + // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement + if (headMessage && errorNode && !result && source.symbol) { + const links = getSymbolLinks(source.symbol); + if (links.originatingImport && !isImportCall(links.originatingImport)) { + const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target), target, relation, /*errorNode*/ undefined); + if (helpfulRetry) { + // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import + diagnostics.add(createDiagnosticForNode(links.originatingImport, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime)); + } + } + } return result !== Ternary.False; function reportError(message: DiagnosticMessage, arg0?: string, arg1?: string, arg2?: string): void { @@ -9493,7 +9764,7 @@ namespace ts { } if (target.flags & TypeFlags.Union) { const discriminantType = findMatchingDiscriminantType(source, target as UnionType); - if (discriminantType) { + if (discriminantType) { // check excess properties against discriminant type only, not the entire union return hasExcessProperties(source, discriminantType, reportErrors); } @@ -9975,6 +10246,9 @@ namespace ts { if (!(targetProp.flags & SymbolFlags.Prototype)) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (sourceProp && sourceProp !== targetProp) { + if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) { + continue; + } const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { @@ -10199,6 +10473,9 @@ namespace ts { function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { let result = Ternary.True; for (const prop of getPropertiesOfObjectType(source)) { + if (isIgnoredJsxProperty(source, prop, /*targetMemberType*/ undefined)) { + continue; + } if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors); if (!related) { @@ -10381,7 +10658,7 @@ namespace ts { let result = "" + type.target.id; for (const t of type.typeArguments) { if (isUnconstrainedTypeParameter(t)) { - let index = indexOf(typeParameters, t); + let index = typeParameters.indexOf(t); if (index < 0) { index = typeParameters.length; typeParameters.push(t); @@ -10584,11 +10861,20 @@ namespace ts { result &= related; } if (!ignoreReturnTypes) { - result &= compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + result &= sourceTypePredicate !== undefined || targetTypePredicate !== undefined + ? compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) + // If they're both type predicates their return types will both be `boolean`, so no need to compare those. + : compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } return result; } + function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + return source === undefined || target === undefined || !typePredicateKindsMatch(source, target) ? Ternary.False : compareTypes(source.type, target.type); + } + function isRestParameterIndex(signature: Signature, parameterIndex: number) { return signature.hasRestParameter && parameterIndex >= signature.parameters.length - 1; } @@ -10623,7 +10909,7 @@ namespace ts { const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); return primaryTypes.length ? getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : - getUnionType(types, /*subtypeReduction*/ true); + getUnionType(types, UnionReduction.Subtype); } // Return the leftmost type for which no type to the right is a subtype. @@ -10914,7 +11200,7 @@ namespace ts { // Widening an empty object literal transitions from a highly restrictive type to // a highly inclusive one. For that reason we perform subtype reduction here if the // union includes empty object types (e.g. reducing {} | string to just {}). - return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType)); + return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); } if (isArrayType(type) || isTupleType(type)) { return createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType)); @@ -11000,6 +11286,9 @@ namespace ts { } diagnostic = Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; break; + case SyntaxKind.MappedType: + error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + return; default: diagnostic = Diagnostics.Variable_0_implicitly_has_an_1_type; } @@ -11127,42 +11416,45 @@ namespace ts { * property is computed by inferring from the source property type to X for the type * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type { + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type { + const key = source.id + "," + target.id; + if (reverseMappedCache.has(key)) { + return reverseMappedCache.get(key); + } + reverseMappedCache.set(key, undefined); + const type = createReverseMappedType(source, target); + reverseMappedCache.set(key, type); + return type; + } + + function createReverseMappedType(source: Type, target: MappedType) { const properties = getPropertiesOfType(source); - let indexInfo = getIndexInfoOfType(source, IndexKind.String); - if (properties.length === 0 && !indexInfo) { + if (properties.length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { return undefined; } - const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); - const inference = createInferenceInfo(typeParameter); - const inferences = [inference]; - const templateType = getTemplateTypeFromMappedType(target); - const readonlyMask = target.declaration.readonlyToken ? false : true; - const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; - const members = createSymbolTable(); + // If any property contains context sensitive functions that have been skipped, the source type + // is incomplete and we can't infer a meaningful input type. for (const prop of properties) { - const propType = getTypeOfSymbol(prop); - // If any property contains context sensitive functions that have been skipped, the source type - // is incomplete and we can't infer a meaningful input type. - if (propType.flags & TypeFlags.ContainsAnyFunctionType) { + if (getTypeOfSymbol(prop).flags & TypeFlags.ContainsAnyFunctionType) { return undefined; } - const checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0; - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags); - inferredProp.declarations = prop.declarations; - inferredProp.type = inferTargetType(propType); - members.set(prop.escapedName, inferredProp); } - if (indexInfo) { - indexInfo = createIndexInfo(inferTargetType(indexInfo.type), readonlyMask && indexInfo.isReadonly); - } - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); + const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; + reversed.source = source; + reversed.mappedType = target; + return reversed; + } - function inferTargetType(sourceType: Type): Type { - inference.candidates = undefined; - inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack); - return inference.candidates ? getUnionType(inference.candidates, /*subtypeReduction*/ true) : emptyObjectType; - } + function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { + return inferReverseMappedType(symbol.propertyType, symbol.mappedType); + } + + function inferReverseMappedType(sourceType: Type, target: MappedType): Type { + const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); + const templateType = getTemplateTypeFromMappedType(target); + const inference = createInferenceInfo(typeParameter); + inferTypes([inference], sourceType, templateType); + return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType; } function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean) { @@ -11178,7 +11470,7 @@ namespace ts { return undefined; } - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) { + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) { let symbolStack: Symbol[]; let visited: Map; inferFromTypes(originalSource, originalTarget); @@ -11266,7 +11558,7 @@ namespace ts { return; } } - else if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // If source and target are references to the same generic type, infer from type arguments const sourceTypes = (source).typeArguments || emptyArray; const targetTypes = (target).typeArguments || emptyArray; @@ -11395,13 +11687,7 @@ namespace ts { // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); if (inference && !inference.isFixed) { - const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol); - if (contains(mappedTypeStack, key)) { - return; - } - (mappedTypeStack || (mappedTypeStack = [])).push(key); - const inferredType = inferTypeForHomomorphicMappedType(source, target, mappedTypeStack); - mappedTypeStack.pop(); + const inferredType = inferTypeForHomomorphicMappedType(source, target); if (inferredType) { const savePriority = priority; priority |= InferencePriority.MappedType; @@ -11453,8 +11739,10 @@ namespace ts { function inferFromSignature(source: Signature, target: Signature) { forEachMatchingParameterType(source, target, inferFromContravariantTypes); - if (source.typePredicate && target.typePredicate && source.typePredicate.kind === target.typePredicate.kind) { - inferFromTypes(source.typePredicate.type, target.typePredicate.type); + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + if (sourceTypePredicate && targetTypePredicate && sourceTypePredicate.kind === targetTypePredicate.kind) { + inferFromTypes(sourceTypePredicate.type, targetTypePredicate.type); } else { inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); @@ -11518,7 +11806,7 @@ namespace ts { if (candidates.length > 1) { const objectLiterals = filter(candidates, isObjectLiteralType); if (objectLiterals.length) { - const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, /*subtypeReduction*/ true)); + const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, UnionReduction.Subtype)); return concatenate(filter(candidates, t => !isObjectLiteralType(t)), [objectLiteralsType]); } } @@ -11545,7 +11833,7 @@ namespace ts { // union types were requested or if all inferences were made from the return type position, infer a // union type. Otherwise, infer a common supertype. const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) : - context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, /*subtypeReduction*/ true) : + context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, UnionReduction.Subtype) : getCommonSupertype(baseCandidates); inferredType = getWidenedType(unwidenedType); } @@ -11613,6 +11901,7 @@ namespace ts { Diagnostics.Cannot_find_name_0, node, !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol; } return links.resolvedSymbol; @@ -11649,7 +11938,7 @@ namespace ts { const container = (node as BindingElement).parent.parent; const key = container.kind === SyntaxKind.BindingElement ? getFlowCacheKey(container) : (container.initializer && getFlowCacheKey(container.initializer)); const text = getBindingElementNameText(node as BindingElement); - const result = key && text && (key + "." + text); + const result = key && text && (key + "." + text); return result; } return undefined; @@ -11943,7 +12232,7 @@ namespace ts { } function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { - return getTypeOfDestructuredArrayElement(getAssignedType(node), indexOf(node.elements, element)); + return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); } function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { @@ -11987,7 +12276,7 @@ namespace ts { const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? getTypeOfDestructuredProperty(parentType, node.propertyName || node.name) : !node.dotDotDotToken ? - getTypeOfDestructuredArrayElement(parentType, indexOf(pattern.elements, node)) : + getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : getTypeOfDestructuredSpreadExpression(parentType); return getTypeWithDefault(type, node.initializer); } @@ -12119,7 +12408,7 @@ namespace ts { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type): Type { + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type { if (!(type.flags & TypeFlags.Union)) { return mapper(type); } @@ -12140,7 +12429,7 @@ namespace ts { } } } - return mappedTypes ? getUnionType(mappedTypes) : mappedType; + return mappedTypes ? getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : mappedType; } function extractTypesOfKind(type: Type, kind: TypeFlags) { @@ -12199,7 +12488,7 @@ namespace ts { return elementType.flags & TypeFlags.Never ? autoArrayType : createArrayType(elementType.flags & TypeFlags.Union ? - getUnionType((elementType).types, /*subtypeReduction*/ true) : + getUnionType((elementType).types, UnionReduction.Subtype) : elementType); } @@ -12232,7 +12521,7 @@ namespace ts { // At flow control branch or loop junctions, if the type along every antecedent code path // is an evolving array type, we construct a combined evolving array type. Otherwise we // finalize all evolving array types. - function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: boolean) { + function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { return isEvolvingArrayTypeList(types) ? getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); @@ -12269,10 +12558,7 @@ namespace ts { const funcType = checkNonNullExpression(node.expression); if (funcType !== silentNeverType) { const apparentType = getApparentType(funcType); - if (apparentType !== unknownType) { - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - return !!forEach(callSignatures, sig => sig.typePredicate); - } + return apparentType !== unknownType && some(getSignaturesOfType(apparentType, SignatureKind.Call), signatureHasTypePredicate); } } return false; @@ -12527,7 +12813,7 @@ namespace ts { seenIncomplete = true; } } - return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction), seenIncomplete); + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { @@ -12556,7 +12842,7 @@ namespace ts { // path that leads to the top. for (let i = flowLoopStart; i < flowLoopCount; i++) { if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { - return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], /*subtypeReduction*/ false), /*incomplete*/ true); + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); } } // Add the flow loop junction and reference to the in-process stack and analyze @@ -12575,7 +12861,7 @@ namespace ts { firstAntecedentType = flowType; } const type = getTypeFromFlowType(flowType); - // If we see a value appear in the cache it is a sign that control flow analysis + // If we see a value appear in the cache it is a sign that control flow analysis // was restarted and completed by checkExpressionCached. We can simply pick up // the resulting type and bail out. const cached = cache.get(key); @@ -12598,7 +12884,7 @@ namespace ts { } // The result is incomplete if the first antecedent (the non-looping control flow path) // is incomplete. - const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction); + const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); if (isIncomplete(firstAntecedentType)) { return createFlowType(result, /*incomplete*/ true); } @@ -12633,6 +12919,25 @@ namespace ts { return type; } + function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + if (getIndexInfoOfType(type, IndexKind.String)) { + return true; + } + const prop = getPropertyOfType(type, propName); + if (prop) { + return prop.flags & SymbolFlags.Optional ? true : assumeTrue; + } + return !assumeTrue; + } + + function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + if ((type.flags & (TypeFlags.Union | TypeFlags.Object)) || (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType)) { + const propName = escapeLeadingUnderscores(literal.text); + return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); + } + return type; + } + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -12644,10 +12949,10 @@ namespace ts { const operator = expr.operatorToken.kind; const left = getReferenceCandidate(expr.left); const right = getReferenceCandidate(expr.right); - if (left.kind === SyntaxKind.TypeOfExpression && right.kind === SyntaxKind.StringLiteral) { + if (left.kind === SyntaxKind.TypeOfExpression && (right.kind === SyntaxKind.StringLiteral || right.kind === SyntaxKind.NoSubstitutionTemplateLiteral)) { return narrowTypeByTypeof(type, left, operator, right, assumeTrue); } - if (right.kind === SyntaxKind.TypeOfExpression && left.kind === SyntaxKind.StringLiteral) { + if (right.kind === SyntaxKind.TypeOfExpression && (left.kind === SyntaxKind.StringLiteral || left.kind === SyntaxKind.NoSubstitutionTemplateLiteral)) { return narrowTypeByTypeof(type, right, operator, left, assumeTrue); } if (isMatchingReference(reference, left)) { @@ -12668,6 +12973,12 @@ namespace ts { break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); + case SyntaxKind.InKeyword: + const target = getReferenceCandidate(expr.right); + if ((expr.left.kind === SyntaxKind.StringLiteral || expr.left.kind === SyntaxKind.NoSubstitutionTemplateLiteral) && isMatchingReference(reference, target)) { + return narrowByInKeyword(type, expr.left, assumeTrue); + } + break; case SyntaxKind.CommaToken: return narrowType(type, expr.right, assumeTrue); } @@ -12756,7 +13067,7 @@ namespace ts { const discriminantType = getUnionType(clauseTypes); const caseType = discriminantType.flags & TypeFlags.Never ? neverType : - replacePrimitivesWithLiterals(filterType(type, t => isTypeComparableTo(discriminantType, t)), discriminantType); + replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); if (!hasDefaultClause) { return caseType; } @@ -12845,7 +13156,7 @@ namespace ts { return type; } const signature = getResolvedSignature(callExpression); - const predicate = signature.typePredicate; + const predicate = getTypePredicateOfSignature(signature); if (!predicate) { return type; } @@ -13009,6 +13320,12 @@ namespace ts { return type; } + function markAliasReferenced(symbol: Symbol, location: Node) { + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol))) { + markAliasSymbolAsReferenced(symbol); + } + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { @@ -13037,9 +13354,9 @@ namespace ts { } // We should only mark aliases as referenced if there isn't a local value declaration - // for the symbol. - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol))) { - markAliasSymbolAsReferenced(symbol); + // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that + if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { + markAliasReferenced(symbol, node); } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); @@ -13127,6 +13444,7 @@ namespace ts { const declarationContainer = getControlFlowContainer(declaration); let flowContainer = getControlFlowContainer(node); const isOuterVariable = flowContainer !== declarationContainer; + const isSpreadDestructuringAsignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); // When the control flow originates in a function expression or arrow function and we are referencing // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. @@ -13138,7 +13456,7 @@ namespace ts { // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). - const assumeInitialized = isParameter || isAlias || isOuterVariable || + const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAsignmentTarget || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || @@ -13257,7 +13575,7 @@ namespace ts { } } - function findFirstSuperCall(n: Node): Node { + function findFirstSuperCall(n: Node): SuperCall | undefined { if (isSuperCall(n)) { return n; } @@ -13273,12 +13591,12 @@ namespace ts { * * @param constructor constructor-function to look for super statement */ - function getSuperCallInConstructor(constructor: ConstructorDeclaration): ExpressionStatement { + function getSuperCallInConstructor(constructor: ConstructorDeclaration): SuperCall | undefined { const links = getNodeLinks(constructor); // Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result if (links.hasSuperCall === undefined) { - links.superCall = findFirstSuperCall(constructor.body); + links.superCall = findFirstSuperCall(constructor.body); links.hasSuperCall = links.superCall ? true : false; } return links.superCall; @@ -13713,7 +14031,7 @@ namespace ts { if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const iife = getImmediatelyInvokedFunctionExpression(func); if (iife && iife.arguments) { - const indexOfParameter = indexOf(func.parameters, parameter); + const indexOfParameter = func.parameters.indexOf(parameter); if (parameter.dotDotDotToken) { const restTypes: Type[] = []; for (let i = indexOfParameter; i < iife.arguments.length; i++) { @@ -13734,7 +14052,7 @@ namespace ts { if (contextualSignature) { const funcHasRestParameters = hasRestParameter(func); const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); - let indexOfParameter = indexOf(func.parameters, parameter); + let indexOfParameter = func.parameters.indexOf(parameter); if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) { Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`. indexOfParameter -= 1; @@ -13765,7 +14083,7 @@ namespace ts { // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. function getContextualTypeForInitializerExpression(node: Expression): Type { const declaration = node.parent; - if (node === declaration.initializer || node.kind === SyntaxKind.EqualsToken) { + if (hasInitializer(declaration) && node === declaration.initializer) { const typeNode = getEffectiveTypeAnnotationNode(declaration); if (typeNode) { return getTypeFromTypeNode(typeNode); @@ -13781,7 +14099,7 @@ namespace ts { } if (isBindingPattern(declaration.parent)) { const parentDeclaration = declaration.parent.parent; - const name = declaration.propertyName || declaration.name; + const name = (declaration as BindingElement).propertyName || (declaration as BindingElement).name; if (parentDeclaration.kind !== SyntaxKind.BindingElement) { const parentTypeNode = getEffectiveTypeAnnotationNode(parentDeclaration); if (parentTypeNode && !isBindingPattern(name)) { @@ -13865,7 +14183,7 @@ namespace ts { // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type { const args = getEffectiveCallArguments(callTarget); - const argIndex = indexOf(args, arg); + const argIndex = args.indexOf(arg); if (argIndex >= 0) { // If we're already in the process of resolving the given signature, don't resolve again as // that could cause infinite recursion. Instead, return anySignature. @@ -13897,12 +14215,6 @@ namespace ts { case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.CommaToken: return node === right ? getContextualType(binaryExpression) : undefined; - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - // For completions after `x === ` - return node === operatorToken ? getTypeOfExpression(binaryExpression.left) : undefined; default: return undefined; } @@ -13932,11 +14244,11 @@ namespace ts { return mapType(type, t => { const prop = t.flags & TypeFlags.StructuredType ? getPropertyOfType(t, name) : undefined; return prop ? getTypeOfSymbol(prop) : undefined; - }); + }, /*noReductions*/ true); } function getIndexTypeOfContextualType(type: Type, kind: IndexKind) { - return mapType(type, t => getIndexTypeOfStructuredType(t, kind)); + return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); } // Return true if the given contextual type is a tuple-like type @@ -14118,12 +14430,8 @@ namespace ts { return getContextualTypeForReturnExpression(node); case SyntaxKind.YieldExpression: return getContextualTypeForYieldOperand(parent); + case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: - if (node.kind === SyntaxKind.NewKeyword) { // for completions after `new ` - return getContextualType(parent as NewExpression); - } - // falls through - case SyntaxKind.CallExpression: return getContextualTypeForArgument(parent, node); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: @@ -14158,12 +14466,6 @@ namespace ts { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return getAttributesTypeFromJsxOpeningLikeElement(parent); - case SyntaxKind.CaseClause: { - if (node.kind === SyntaxKind.CaseKeyword) { // for completions after `case ` - const switchStatement = (parent as CaseClause).parent.parent; - return getTypeOfExpression(switchStatement.expression); - } - } } return undefined; } @@ -14256,8 +14558,6 @@ namespace ts { let result: Signature; if (signatureList) { result = cloneSignature(signatureList[0]); - // Clear resolved return type we possibly got from cloneSignature - result.resolvedReturnType = undefined; result.unionSignatures = signatureList; } return result; @@ -14335,7 +14635,7 @@ namespace ts { if (patternElement.kind !== SyntaxKind.OmittedExpression) { error(patternElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); } - elementTypes.push(unknownType); + elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType); } } } @@ -14345,7 +14645,7 @@ namespace ts { } } return createArrayType(elementTypes.length ? - getUnionType(elementTypes, /*subtypeReduction*/ true) : + getUnionType(elementTypes, UnionReduction.Subtype) : strictNullChecks ? implicitNeverType : undefinedWideningType); } @@ -14424,7 +14724,7 @@ namespace ts { propTypes.push(getTypeOfSymbol(properties[i])); } } - const unionType = propTypes.length ? getUnionType(propTypes, /*subtypeReduction*/ true) : undefinedType; + const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; return createIndexInfo(unionType, /*isReadonly*/ false); } @@ -14436,7 +14736,7 @@ namespace ts { let propertiesTable = createSymbolTable(); let propertiesArray: Symbol[] = []; let spread: Type = emptyObjectType; - let propagatedFlags: TypeFlags = 0; + let propagatedFlags: TypeFlags = TypeFlags.FreshLiteral; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -14623,14 +14923,14 @@ namespace ts { type.flags & TypeFlags.UnionOrIntersection && !forEach((type).types, t => !isValidSpreadType(t))); } - function checkJsxSelfClosingElement(node: JsxSelfClosingElement): Type { - checkJsxOpeningLikeElementOrOpeningFragment(node); + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, checkMode: CheckMode): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node, checkMode); return getJsxGlobalElementType() || anyType; } - function checkJsxElement(node: JsxElement): Type { + function checkJsxElement(node: JsxElement, checkMode: CheckMode): Type { // Check attributes - checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement, checkMode); // Perform resolution on the closing tag so that rename/go to definition/etc work if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { @@ -14643,8 +14943,8 @@ namespace ts { return getJsxGlobalElementType() || anyType; } - function checkJsxFragment(node: JsxFragment): Type { - checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + function checkJsxFragment(node: JsxFragment, checkMode: CheckMode): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment, checkMode); if (compilerOptions.jsx === JsxEmit.React && compilerOptions.jsxFactory) { error(node, Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory); @@ -14677,6 +14977,12 @@ namespace ts { } } + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } + /** * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. * @@ -14686,11 +14992,10 @@ namespace ts { * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, * which also calls getSpreadType. */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?: (symbol: Symbol) => boolean, checkMode?: CheckMode) { + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode) { const attributes = openingLikeElement.attributes; let attributesTable = createSymbolTable(); let spread: Type = emptyObjectType; - let attributesArray: Symbol[] = []; let hasSpreadAnyType = false; let typeToIntersect: Type; let explicitlySpecifyChildrenAttribute = false; @@ -14699,9 +15004,7 @@ namespace ts { for (const attributeDecl of attributes.properties) { const member = attributeDecl.symbol; if (isJsxAttribute(attributeDecl)) { - const exprType = attributeDecl.initializer ? - checkExpression(attributeDecl.initializer, checkMode) : - trueType; // is sugar for + const exprType = checkJsxAttribute(attributeDecl, checkMode); const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName); attributeSymbol.declarations = member.declarations; @@ -14712,24 +15015,22 @@ namespace ts { attributeSymbol.type = exprType; attributeSymbol.target = member; attributesTable.set(attributeSymbol.escapedName, attributeSymbol); - attributesArray.push(attributeSymbol); if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { explicitlySpecifyChildrenAttribute = true; } } else { Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); - if (attributesArray.length > 0) { - spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable), openingLikeElement.symbol, /*propagatedFlags*/ 0); - attributesArray = []; + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, TypeFlags.JsxAttributes); attributesTable = createSymbolTable(); } - const exprType = checkExpression(attributeDecl.expression); + const exprType = checkExpressionCached(attributeDecl.expression, checkMode); if (isTypeAny(exprType)) { hasSpreadAnyType = true; } if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType, openingLikeElement.symbol, /*propagatedFlags*/ 0); + spread = getSpreadType(spread, exprType, openingLikeElement.symbol, TypeFlags.JsxAttributes); } else { typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; @@ -14738,18 +15039,8 @@ namespace ts { } if (!hasSpreadAnyType) { - if (spread !== emptyObjectType) { - if (attributesArray.length > 0) { - spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable), openingLikeElement.symbol, /*propagatedFlags*/ 0); - } - attributesArray = getPropertiesOfType(spread); - } - - attributesTable = createSymbolTable(); - for (const attr of attributesArray) { - if (!filter || filter(attr)) { - attributesTable.set(attr.escapedName, attr); - } + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, TypeFlags.JsxAttributes); } } @@ -14767,30 +15058,30 @@ namespace ts { error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); } - // If there are children in the body of JSX element, create dummy attribute "children" with anyType so that it will pass the attribute checking process + // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName); childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : - createArrayType(getUnionType(childrenTypes, /*subtypeReduction*/ false)); - attributesTable.set(jsxChildrenPropertyName, childrenPropSymbol); + createArrayType(getUnionType(childrenTypes)); + const childPropMap = createSymbolTable(); + childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); + spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined), attributes.symbol, TypeFlags.JsxAttributes); + } } if (hasSpreadAnyType) { return anyType; } - - const attributeType = createJsxAttributesType(attributes.symbol, attributesTable); - return typeToIntersect && attributesTable.size ? getIntersectionType([typeToIntersect, attributeType]) : - typeToIntersect ? typeToIntersect : attributeType; + return typeToIntersect && spread !== emptyObjectType ? getIntersectionType([typeToIntersect, spread]) : (typeToIntersect || spread); /** * Create anonymous type from given attributes symbol table. * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable * @param attributesTable a symbol table of attributes property */ - function createJsxAttributesType(symbol: Symbol, attributesTable: UnderscoreEscapedMap) { - const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); + function createJsxAttributesType() { + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral; result.objectFlags |= ObjectFlags.ObjectLiteral; return result; @@ -14819,8 +15110,8 @@ namespace ts { * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) * @param node a JSXAttributes to be resolved of its type */ - function checkJsxAttributes(node: JsxAttributes, checkMode?: CheckMode) { - return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement, /*filter*/ undefined, checkMode); + function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode) { + return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement, checkMode); } function getJsxType(name: __String) { @@ -14876,7 +15167,7 @@ namespace ts { * element is not a class element, or the class element type cannot be determined, returns 'undefined'. * For example, in the element , the element instance type is `MyClass` (not `typeof MyClass`). */ - function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) { + function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type, sourceAttributesType: Type | undefined) { Debug.assert(!(valueType.flags & TypeFlags.Union)); if (isTypeAny(valueType)) { // Short-circuit if the class tag is using an element type 'any' @@ -14895,19 +15186,27 @@ namespace ts { } } - const instantiatedSignatures = []; - for (const signature of signatures) { - if (signature.typeParameters) { - const isJavascript = isInJavaScriptFile(node); - const typeArguments = fillMissingTypeArguments(/*typeArguments*/ undefined, signature.typeParameters, /*minTypeArgumentCount*/ 0, isJavascript); - instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript)); + if (sourceAttributesType) { + // Instantiate in context of source type + const instantiatedSignatures = []; + for (const signature of signatures) { + if (signature.typeParameters) { + const isJavascript = isInJavaScriptFile(node); + const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : 0); + const typeArguments = inferJsxTypeArguments(signature, sourceAttributesType, inferenceContext); + instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript)); + } + else { + instantiatedSignatures.push(signature); + } } - else { - instantiatedSignatures.push(signature); - } - } - return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); + } + else { + // Do not instantiate if no source type is provided - type parameters and their constraints will be used by contextual typing + return getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype); + } } /** @@ -15084,6 +15383,7 @@ namespace ts { * * @param openingLikeElement a non-intrinsic JSXOPeningLikeElement * @param shouldIncludeAllStatelessAttributesType a boolean indicating whether to include all attributes types from all stateless function signature + * @param sourceAttributesType Is the attributes type the user passed, and is used to create inferences in the target type if present * @param elementType an instance type of the given opening-like element. If undefined, the function will check type openinglikeElement's tagname. * @param elementClassType a JSX-ElementClass type. This is a result of looking up ElementClass interface in the JSX global (imported from react.d.ts) * @return attributes type if able to resolve the type of node @@ -15092,14 +15392,15 @@ namespace ts { */ function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean, - elementType: Type = checkExpression(openingLikeElement.tagName), + sourceAttributesType: Type | undefined, + elementType: Type, elementClassType?: Type): Type { if (elementType.flags & TypeFlags.Union) { const types = (elementType as UnionType).types; return getUnionType(types.map(type => { - return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType); - }), /*subtypeReduction*/ true); + return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, sourceAttributesType, type, elementClassType); + }), UnionReduction.Subtype); } // If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type @@ -15129,7 +15430,7 @@ namespace ts { } // Get the element instance type (the result of newing or invoking this tag) - const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType); + const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType, sourceAttributesType); // If we should include all stateless attributes type, then get all attributes type from all stateless function signature. // Otherwise get only attributes type from the signature picked by choose-overload logic. @@ -15142,7 +15443,7 @@ namespace ts { } // Issue an error if this return type isn't assignable to JSX.ElementClass - if (elementClassType) { + if (elementClassType && sourceAttributesType) { checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } @@ -15225,14 +15526,20 @@ namespace ts { * @param node a custom JSX opening-like element * @param shouldIncludeAllStatelessAttributesType a boolean value used by language service to get all possible attributes type from an overload stateless function component */ - function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean): Type { - const links = getNodeLinks(node); - const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType"; - if (!links[linkLocation]) { - const elemClassType = getJsxGlobalElementClassType(); - return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, /*elementType*/ undefined, elemClassType); + function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, sourceAttributesType: Type, shouldIncludeAllStatelessAttributesType: boolean): Type { + if (!sourceAttributesType) { + // This ensures we cache non-inference uses of this calculation (ie, contextual types or services) + const links = getNodeLinks(node); + const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType"; + if (!links[linkLocation]) { + const elemClassType = getJsxGlobalElementClassType(); + return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), elemClassType); + } + return links[linkLocation]; + } + else { + return resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), getJsxGlobalElementClassType()); } - return links[linkLocation]; } /** @@ -15247,7 +15554,7 @@ namespace ts { else { // Because in language service, the given JSX opening-like element may be incomplete and therefore, // we can't resolve to exact signature if the element is a stateless function component so the best thing to do is return all attributes type from all overloads. - return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ true); + return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ true); } } @@ -15261,7 +15568,7 @@ namespace ts { return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); } else { - return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ false); + return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ false); } } @@ -15321,7 +15628,7 @@ namespace ts { } } - function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment, checkMode: CheckMode) { const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); if (isNodeOpeningLikeElement) { @@ -15346,7 +15653,7 @@ namespace ts { } if (isNodeOpeningLikeElement) { - checkJsxAttributesAssignableToTagNameAttributes(node); + checkJsxAttributesAssignableToTagNameAttributes(node, checkMode); } else { checkJsxChildren((node as JsxOpeningFragment).parent); @@ -15393,25 +15700,23 @@ namespace ts { * Check assignablity between given attributes property, "source attributes", and the "target attributes" * @param openingLikeElement an opening-like JSX element to check its JSXAttributes */ - function checkJsxAttributesAssignableToTagNameAttributes(openingLikeElement: JsxOpeningLikeElement) { + function checkJsxAttributesAssignableToTagNameAttributes(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode) { // The function involves following steps: // 1. Figure out expected attributes type by resolving tagName of the JSX opening-like element, targetAttributesType. // During these steps, we will try to resolve the tagName as intrinsic name, stateless function, stateful component (in the order) // 2. Solved JSX attributes type given by users, sourceAttributesType, which is by resolving "attributes" property of the JSX opening-like element. // 3. Check if the two are assignable to each other - // targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element. - const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? - getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : - getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false); // sourceAttributesType is a type of an attributes properties. // i.e
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". - const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, - attribute => { - return isUnhyphenatedJsxName(attribute.escapedName) || !!(getPropertyOfType(targetAttributesType, attribute.escapedName)); - }); + const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, checkMode); + + // targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element. + const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ? + getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) : + getCustomJsxElementAttributesType(openingLikeElement, sourceAttributesType, /*shouldIncludeAllStatelessAttributesType*/ false); // If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type. // but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass. @@ -15422,11 +15727,16 @@ namespace ts { // Check if sourceAttributesType assignable to targetAttributesType though this check will allow excess properties const isSourceAttributeTypeAssignableToTarget = checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement); // After we check for assignability, we will do another pass to check that all explicitly specified attributes have correct name corresponding in targetAttributeType. - // This will allow excess properties in spread type as it is very common pattern to spread outter attributes into React component in its render method. + // This will allow excess properties in spread type as it is very common pattern to spread outer attributes into React component in its render method. if (isSourceAttributeTypeAssignableToTarget && !isTypeAny(sourceAttributesType) && !isTypeAny(targetAttributesType)) { for (const attribute of openingLikeElement.attributes.properties) { - if (isJsxAttribute(attribute) && !isKnownProperty(targetAttributesType, attribute.name.escapedText, /*isComparingJsxAttributes*/ true)) { - error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, idText(attribute.name), typeToString(targetAttributesType)); + if (!isJsxAttribute(attribute)) { + continue; + } + const attrName = attribute.name as Identifier; + const isNotIgnoredJsxProperty = (isUnhyphenatedJsxName(idText(attrName)) || !!(getPropertyOfType(targetAttributesType, attrName.escapedText))); + if (isNotIgnoredJsxProperty && !isKnownProperty(targetAttributesType, attrName.escapedText, /*isComparingJsxAttributes*/ true)) { + error(attribute, Diagnostics.Property_0_does_not_exist_on_type_1, idText(attrName), typeToString(targetAttributesType)); // We break here so that errors won't be cascading break; } @@ -15572,17 +15882,35 @@ namespace ts { }); } - function checkNonNullExpression(node: Expression | QualifiedName) { - return checkNonNullType(checkExpression(node), node); + function checkNonNullExpression( + node: Expression | QualifiedName, + nullDiagnostic?: DiagnosticMessage, + undefinedDiagnostic?: DiagnosticMessage, + nullOrUndefinedDiagnostic?: DiagnosticMessage, + ) { + return checkNonNullType( + checkExpression(node), + node, + nullDiagnostic, + undefinedDiagnostic, + nullOrUndefinedDiagnostic + ); } - function checkNonNullType(type: Type, errorNode: Node): Type { + function checkNonNullType( + type: Type, + node: Node, + nullDiagnostic?: DiagnosticMessage, + undefinedDiagnostic?: DiagnosticMessage, + nullOrUndefinedDiagnostic?: DiagnosticMessage + ): Type { const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; if (kind) { - error(errorNode, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? - Diagnostics.Object_is_possibly_null_or_undefined : - Diagnostics.Object_is_possibly_undefined : - Diagnostics.Object_is_possibly_null); + error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? + (nullOrUndefinedDiagnostic || Diagnostics.Object_is_possibly_null_or_undefined) : + (undefinedDiagnostic || Diagnostics.Object_is_possibly_undefined) : + (nullDiagnostic || Diagnostics.Object_is_possibly_null) + ); const t = getNonNullableType(type); return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? unknownType : t; } @@ -15599,15 +15927,20 @@ namespace ts { function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { let propType: Type; - let leftSymbol = getNodeLinks(left) && getNodeLinks(left).resolvedSymbol; - const leftWasReferenced = leftSymbol && getSymbolLinks(leftSymbol).referenced; const leftType = checkNonNullExpression(left); + const parentSymbol = getNodeLinks(left).resolvedSymbol; const apparentType = getApparentType(getWidenedType(leftType)); if (isTypeAny(apparentType) || apparentType === silentNeverType) { + if (isIdentifier(left) && parentSymbol) { + markAliasReferenced(parentSymbol, node); + } return apparentType; } const assignmentKind = getAssignmentTargetKind(node); const prop = getPropertyOfType(apparentType, right.escapedText); + if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { + markAliasReferenced(parentSymbol, node); + } if (!prop) { const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String); if (!(indexInfo && indexInfo.type)) { @@ -15624,13 +15957,6 @@ namespace ts { else { checkPropertyNotUsedBeforeDeclaration(prop, node, right); markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); - // Reset the referenced-ness of the LHS expression if this access refers to a const enum or const enum only module - leftSymbol = getNodeLinks(left) && getNodeLinks(left).resolvedSymbol; - if (leftSymbol && !leftWasReferenced && getSymbolLinks(leftSymbol).referenced && - !(isNonLocalAlias(leftSymbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(prop)) - ) { - getSymbolLinks(leftSymbol).referenced = undefined; - } getNodeLinks(node).resolvedSymbol = prop; checkPropertyAccessibility(node, left, apparentType, prop); if (assignmentKind) { @@ -15710,6 +16036,9 @@ namespace ts { * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. */ function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { + if (!(prop.parent.flags & SymbolFlags.Class)) { + return false; + } let classType = getTypeOfSymbol(prop.parent) as InterfaceType; while (true) { classType = getSuperClass(classType); @@ -15759,7 +16088,7 @@ namespace ts { function getSuggestionForNonexistentSymbol(location: Node, outerName: __String, meaning: SymbolFlags): string { Debug.assert(outerName !== undefined, "outername should always be defined"); - const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, (symbols, name, meaning) => { + const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { Debug.assertEqual(outerName, name, "name should equal outerName"); const symbol = getSymbol(symbols, name, meaning); // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function @@ -15870,37 +16199,48 @@ namespace ts { } function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) { - if (prop && - noUnusedIdentifiers && - (prop.flags & SymbolFlags.ClassMember) && - prop.valueDeclaration && hasModifier(prop.valueDeclaration, ModifierFlags.Private) - && !(nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly))) { + if (!prop || !noUnusedIdentifiers || !(prop.flags & SymbolFlags.ClassMember) || !prop.valueDeclaration || !hasModifier(prop.valueDeclaration, ModifierFlags.Private)) { + return; + } + if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor))) { + return; + } - if (isThisAccess) { - // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). - const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); - if (containingMethod && containingMethod.symbol === prop) { - return; - } + if (isThisAccess) { + // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). + const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); + if (containingMethod && containingMethod.symbol === prop) { + return; } + } - if (getCheckFlags(prop) & CheckFlags.Instantiated) { - getSymbolLinks(prop).target.isReferenced = true; - } - else { - prop.isReferenced = true; - } + if (getCheckFlags(prop) & CheckFlags.Instantiated) { + getSymbolLinks(prop).target.isReferenced = true; + } + else { + prop.isReferenced = true; } } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: __String): boolean { - const left = node.kind === SyntaxKind.PropertyAccessExpression - ? (node).expression - : (node).left; - + const left = node.kind === SyntaxKind.PropertyAccessExpression ? node.expression : node.left; return isValidPropertyAccessWithType(node, left, propertyName, getWidenedType(checkExpression(left))); } + function isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean { + return isValidPropertyAccessWithType(node, node.expression, property.escapedName, type) + && (!(property.flags & ts.SymbolFlags.Method) || isValidMethodAccess(property, type)); + } + function isValidMethodAccess(method: Symbol, type: Type) { + const propType = getTypeOfFuncClassEnumModule(method); + const signatures = getSignaturesOfType(propType, SignatureKind.Call); + Debug.assert(signatures.length !== 0); + return signatures.some(sig => { + const thisType = getThisTypeOfSignature(sig); + return !thisType || isTypeAssignableTo(type, thisType); + }); + } + function isValidPropertyAccessWithType( node: PropertyAccessExpression | QualifiedName, left: LeftHandSideExpression | QualifiedName, @@ -16253,6 +16593,13 @@ namespace ts { return getSignatureInstantiation(signature, getInferredTypes(context), isInJavaScriptFile(contextualSignature.declaration)); } + function inferJsxTypeArguments(signature: Signature, sourceAttributesType: Type, context: InferenceContext): Type[] { + const paramType = getTypeAtPosition(signature, 0); + inferTypes(context.inferences, sourceAttributesType, paramType); + + return getInferredTypes(context); + } + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: ReadonlyArray, excludeArgument: boolean[], context: InferenceContext): Type[] { // Clear out all the inference results from the last time inferTypeArguments was called on this context for (const inference of context.inferences) { @@ -16351,7 +16698,7 @@ namespace ts { const constraint = getConstraintOfTypeParameter(typeParameters[i]); if (!constraint) continue; - const errorInfo = reportErrors && headMessage && chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + const errorInfo = reportErrors && headMessage && (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)); const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; if (!mapper) { mapper = createTypeMapper(typeParameters, typeArgumentTypes); @@ -17027,7 +17374,7 @@ namespace ts { } excludeCount--; if (excludeCount > 0) { - excludeArgument[indexOf(excludeArgument, /*value*/ true)] = false; + excludeArgument[excludeArgument.indexOf(/*value*/ true)] = false; } else { excludeArgument = undefined; @@ -17072,7 +17419,13 @@ namespace ts { return resolveUntypedCall(node); } - const funcType = checkNonNullExpression(node.expression); + const funcType = checkNonNullExpression( + node.expression, + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null, + Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined, + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined + ); + if (funcType === silentNeverType) { return silentNeverSignature; } @@ -17109,7 +17462,7 @@ namespace ts { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } else { - error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); + invocationError(node, apparentType, SignatureKind.Call); } return resolveErrorCall(node); } @@ -17199,7 +17552,7 @@ namespace ts { return signature; } - error(node, Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature); + invocationError(node, expressionType, SignatureKind.Construct); return resolveErrorCall(node); } @@ -17246,6 +17599,28 @@ namespace ts { return true; } + function invocationError(node: Node, apparentType: Type, kind: SignatureKind) { + error(node, kind === SignatureKind.Call + ? Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures + : Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature + , typeToString(apparentType)); + invocationErrorRecovery(apparentType, kind); + } + + function invocationErrorRecovery(apparentType: Type, kind: SignatureKind) { + if (!apparentType.symbol) { + return; + } + const importNode = getSymbolLinks(apparentType.symbol).originatingImport; + // Create a diagnostic on the originating import if possible onto which we can attach a quickfix + // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site + if (importNode && !isImportCall(importNode)) { + const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target), kind); + if (!sigs || !sigs.length) return; + error(importNode, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime); + } + } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[]): Signature { const tagType = checkExpression(node.tag); const apparentType = getApparentType(tagType); @@ -17263,7 +17638,7 @@ namespace ts { } if (!callSignatures.length) { - error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); + invocationError(node, apparentType, SignatureKind.Call); return resolveErrorCall(node); } @@ -17320,6 +17695,7 @@ namespace ts { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); errorInfo = chainDiagnosticMessages(errorInfo, headMessage); diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo)); + invocationErrorRecovery(apparentType, SignatureKind.Call); return resolveErrorCall(node); } @@ -17574,17 +17950,19 @@ namespace ts { if (moduleSymbol) { const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true); if (esModuleSymbol) { - return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol)); + return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); } } return createPromiseReturnType(node, anyType); } - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol): Type { + function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type { if (allowSyntheticDefaultImports && type && type !== unknownType) { const synthType = type as SyntheticDefaultModuleType; if (!synthType.syntheticType) { - if (!getPropertyOfType(type, InternalSymbolName.Default)) { + const file = find(originalSymbol.declarations, isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); + if (hasSyntheticDefault) { const memberTable = createSymbolTable(); const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); newSymbol.target = resolveSymbol(symbol); @@ -17592,7 +17970,7 @@ namespace ts { const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); anonymousSymbol.type = defaultContainingObject; - synthType.syntheticType = getIntersectionType([type, defaultContainingObject]); + synthType.syntheticType = (type.flags & TypeFlags.StructuredType && type.symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*propegatedFlags*/ 0) : defaultContainingObject; } else { synthType.syntheticType = type; @@ -17684,7 +18062,7 @@ namespace ts { const type = getTypeOfSymbol(symbol); if (strictNullChecks) { const declaration = symbol.valueDeclaration; - if (declaration && (declaration).initializer) { + if (declaration && hasInitializer(declaration)) { return getOptionalType(type); } } @@ -17803,7 +18181,6 @@ namespace ts { } function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); if (!func.body) { return unknownType; } @@ -17821,9 +18198,9 @@ namespace ts { } } else { - let types: Type[]; + let types = checkAndAggregateReturnExpressionTypes(func, checkMode); if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function - types = concatenate(checkAndAggregateYieldOperandTypes(func, checkMode), checkAndAggregateReturnExpressionTypes(func, checkMode)); + types = concatenate(checkAndAggregateYieldOperandTypes(func, checkMode), types); if (!types || types.length === 0) { const iterableIteratorAny = functionFlags & FunctionFlags.Async ? createAsyncIterableIteratorType(anyType) // AsyncGenerator function @@ -17836,7 +18213,6 @@ namespace ts { } } else { - types = checkAndAggregateReturnExpressionTypes(func, checkMode); if (!types) { // For an async function, the return type will not be never, but rather a Promise for never. return functionFlags & FunctionFlags.Async @@ -17852,9 +18228,10 @@ namespace ts { } // Return a union of the return expression types. - type = getUnionType(types, /*subtypeReduction*/ true); + type = getUnionType(types, UnionReduction.Subtype); } + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); if (!contextualSignature) { reportErrorsFromWidening(func, type); } @@ -17944,7 +18321,8 @@ namespace ts { return true; } - function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] { + /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means return `void`, `undefined` means return `never`. */ + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] | undefined { const functionFlags = getFunctionFlags(func); const aggregatedTypes: Type[] = []; let hasReturnWithNoExpression = functionHasImplicitReturn(func); @@ -17969,8 +18347,7 @@ namespace ts { hasReturnWithNoExpression = true; } }); - if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || - func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction)) { + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { return undefined; } if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression) { @@ -17978,6 +18355,17 @@ namespace ts { } return aggregatedTypes; } + function mayReturnNever(func: FunctionLikeDeclaration): boolean { + switch (func.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + case SyntaxKind.MethodDeclaration: + return func.parent.kind === SyntaxKind.ObjectLiteralExpression; + default: + return false; + } + } /** * TypeScript Specification 1.0 (6.3) - July 2014 @@ -18760,7 +19148,7 @@ namespace ts { leftType; case SyntaxKind.BarBarToken: return getTypeFacts(leftType) & TypeFacts.Falsy ? - getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) : + getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : leftType; case SyntaxKind.EqualsToken: checkAssignmentOperator(rightType); @@ -18920,7 +19308,7 @@ namespace ts { checkExpression(node.condition); const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); - return getUnionType([type1, type2], /*subtypeReduction*/ true); + return getUnionType([type1, type2], UnionReduction.Subtype); } function checkTemplateExpression(node: TemplateExpression): Type { @@ -18936,13 +19324,13 @@ namespace ts { return stringType; } - function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper): Type { + function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper | undefined): Type { const saveContextualType = node.contextualType; const saveContextualMapper = node.contextualMapper; node.contextualType = contextualType; node.contextualMapper = contextualMapper; const checkMode = contextualMapper === identityMapper ? CheckMode.SkipContextSensitive : - contextualMapper ? CheckMode.Inferential : CheckMode.Normal; + contextualMapper ? CheckMode.Inferential : CheckMode.Contextual; const result = checkExpression(node, checkMode); node.contextualType = saveContextualType; node.contextualMapper = saveContextualMapper; @@ -18952,6 +19340,9 @@ namespace ts { function checkExpressionCached(node: Expression, checkMode?: CheckMode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { + if (checkMode) { + return checkExpression(node, checkMode); + } // When computing a type that we're going to cache, we need to ignore any ongoing control flow // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart // to the top of the stack ensures all transient types are computed from a known point. @@ -18968,7 +19359,7 @@ namespace ts { return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression; } - function checkDeclarationInitializer(declaration: VariableLikeDeclaration) { + function checkDeclarationInitializer(declaration: HasExpressionInitializer) { const type = getTypeOfExpression(declaration.initializer, /*cache*/ true); return getCombinedNodeFlags(declaration) & NodeFlags.Const || (getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration)) || @@ -18977,7 +19368,7 @@ namespace ts { function isLiteralOfContextualType(candidateType: Type, contextualType: Type): boolean { if (contextualType) { - if (contextualType.flags & TypeFlags.Union && !(contextualType.flags & TypeFlags.Boolean)) { + if (contextualType.flags & TypeFlags.UnionOrIntersection && !(contextualType.flags & TypeFlags.Boolean)) { // If the contextual type is a union containing both of the 'true' and 'false' types we // don't consider it a literal context for boolean literals. const types = (contextualType).types; @@ -19120,10 +19511,11 @@ namespace ts { const ok = (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).expression === node) || (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).expression === node) || - ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node)); + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node) || + (node.parent.kind === SyntaxKind.TypeQuery && (node.parent).exprName === node)); if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment); + error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); } } return type; @@ -19217,11 +19609,11 @@ namespace ts { case SyntaxKind.JsxExpression: return checkJsxExpression(node, checkMode); case SyntaxKind.JsxElement: - return checkJsxElement(node); + return checkJsxElement(node, checkMode); case SyntaxKind.JsxSelfClosingElement: - return checkJsxSelfClosingElement(node); + return checkJsxSelfClosingElement(node, checkMode); case SyntaxKind.JsxFragment: - return checkJsxFragment(node); + return checkJsxFragment(node, checkMode); case SyntaxKind.JsxAttributes: return checkJsxAttributes(node, checkMode); case SyntaxKind.JsxOpeningElement: @@ -19275,7 +19667,7 @@ namespace ts { error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); } if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { - if (indexOf(func.parameters, node) !== 0) { + if (func.parameters.indexOf(node) !== 0) { error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); } if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { @@ -19310,7 +19702,7 @@ namespace ts { return; } - const typePredicate = getSignatureFromDeclaration(parent).typePredicate; + const typePredicate = getTypePredicateOfSignature(getSignatureFromDeclaration(parent)); if (!typePredicate) { return; } @@ -19328,7 +19720,7 @@ namespace ts { Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); } else { - const leadingError = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); + const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); checkTypeAssignableTo(typePredicate.type, getTypeOfNode(parent.parameters[typePredicate.parameterIndex]), node.type, @@ -19650,7 +20042,7 @@ namespace ts { } } - function checkPropertyDeclaration(node: PropertyDeclaration) { + function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); checkVariableLikeDeclaration(node); @@ -19696,24 +20088,6 @@ namespace ts { return; } - function containsSuperCallAsComputedPropertyName(n: Declaration): boolean { - const name = getNameOfDeclaration(n); - return name && containsSuperCall(name); - } - - function containsSuperCall(n: Node): boolean { - if (isSuperCall(n)) { - return true; - } - else if (isFunctionLike(n)) { - return false; - } - else if (isClassLike(n)) { - return forEach((n).members, containsSuperCallAsComputedPropertyName); - } - return forEachChild(n, containsSuperCall); - } - function isInstancePropertyWithInitializer(n: Node): boolean { return n.kind === SyntaxKind.PropertyDeclaration && !hasModifier(n, ModifierFlags.Static) && @@ -19953,6 +20327,11 @@ namespace ts { function checkMappedType(node: MappedTypeNode) { checkSourceElement(node.typeParameter); checkSourceElement(node.type); + + if (noImplicitAny && !node.type) { + reportImplicitAnyError(node, anyType); + } + const type = getTypeFromMappedTypeNode(node); const constraintType = getConstraintTypeFromMappedType(type); checkTypeAssignableTo(constraintType, stringType, node.typeParameter.constraint); @@ -20361,7 +20740,7 @@ namespace ts { return undefined; } - return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), /*subtypeReduction*/ true); + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); } /** @@ -20400,7 +20779,7 @@ namespace ts { const promisedType = getPromisedTypeOfPromise(type); if (promisedType) { - if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) { + if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) { // Verify that we don't have a bad actor in the form of a promise whose // promised type is the same as the promise type, or a mutually recursive // promise. If so, we return undefined as we cannot guess the shape. If this @@ -20633,7 +21012,7 @@ namespace ts { expectedReturnType, node, headMessage, - errorInfo); + () => errorInfo); } /** @@ -21035,20 +21414,33 @@ namespace ts { function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression): void { if (compilerOptions.noUnusedLocals && !(node.flags & NodeFlags.Ambient)) { - if (node.members) { - for (const member of node.members) { - if (member.kind === SyntaxKind.MethodDeclaration || member.kind === SyntaxKind.PropertyDeclaration) { - if (!member.symbol.isReferenced && hasModifier(member, ModifierFlags.Private)) { - error(member.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(member.symbol)); + for (const member of node.members) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { + // Already would have reported an error on the getter. + break; } - } - else if (member.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfNode(member); + if (!symbol.isReferenced && hasModifier(member, ModifierFlags.Private)) { + error(member.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol)); + } + break; + case SyntaxKind.Constructor: for (const parameter of (member).parameters) { if (!parameter.symbol.isReferenced && hasModifier(parameter, ModifierFlags.Private)) { error(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol)); } } - } + break; + case SyntaxKind.IndexSignature: + // Can't be private + break; + default: + Debug.fail(); } } } @@ -21331,7 +21723,7 @@ namespace ts { } // Check that a parameter initializer contains no references to parameters declared to the right of itself - function checkParameterInitializer(node: VariableLikeDeclaration): void { + function checkParameterInitializer(node: HasExpressionInitializer): void { if (getRootDeclaration(node).kind !== SyntaxKind.Parameter) { return; } @@ -21403,9 +21795,11 @@ namespace ts { } // Check variable, parameter, or property declaration - function checkVariableLikeDeclaration(node: VariableLikeDeclaration) { + function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { checkDecorators(node); - checkSourceElement(node.type); + if (!isBindingElement(node)) { + checkSourceElement(node.type); + } // JSDoc `function(string, string): string` syntax results in parameters with no name if (!node.name) { @@ -21416,7 +21810,7 @@ namespace ts { // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); + checkComputedPropertyName(node.name); if (node.initializer) { checkExpressionCached(node.initializer); } @@ -21428,11 +21822,11 @@ namespace ts { } // check computed properties inside property names of binding elements if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.propertyName); + checkComputedPropertyName(node.propertyName); } // check private/protected variable access - const parent = (node.parent).parent; + const parent = node.parent.parent; const parentType = getTypeForBindingElementParent(parent); const name = node.propertyName || node.name; const property = getPropertyOfType(parentType, getTextOfPropertyName(name)); @@ -21465,7 +21859,7 @@ namespace ts { return; } const symbol = getSymbolOfNode(node); - const type = convertAutoToAny(getTypeOfVariableOrParameterOrProperty(symbol)); + const type = convertAutoToAny(getTypeOfSymbol(symbol)); if (node === symbol.valueDeclaration) { // Node is the primary declaration of the symbol, just validate the initializer // Don't validate for-in initializer as it is already an error @@ -21693,7 +22087,7 @@ namespace ts { checkGrammarForInOrForOfStatement(node); const rightType = checkNonNullExpression(node.expression); - // TypeScript 1.0 spec (April 2014): 5.4 + // TypeScript 1.0 spec (April 2014): 5.4 // In a 'for-in' statement of the form // for (let VarDecl in Expr) Statement // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, @@ -21792,7 +22186,7 @@ namespace ts { const arrayTypes = (inputType).types; const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true); + arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); } } else if (arrayType.flags & TypeFlags.StringLike) { @@ -21842,7 +22236,7 @@ namespace ts { return stringType; } - return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true); + return getUnionType([arrayElementType, stringType], UnionReduction.Subtype); } return arrayElementType; @@ -21940,7 +22334,7 @@ namespace ts { return undefined; } - const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype); const iteratedType = getIteratedTypeOfIterator(returnType, errorNode, /*isAsyncIterator*/ !!asyncMethodType); if (checkAssignability && errorNode && iteratedType) { // If `checkAssignability` was specified, we were called from @@ -22015,7 +22409,7 @@ namespace ts { return undefined; } - let nextResult = getUnionType(map(nextMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + let nextResult = getUnionType(map(nextMethodSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); if (isTypeAny(nextResult)) { return undefined; } @@ -22079,57 +22473,58 @@ namespace ts { function checkReturnStatement(node: ReturnStatement) { // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - const functionBlock = getContainingFunction(node); - if (!functionBlock) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); - } + if (checkGrammarStatementInAmbientContext(node)) { + return; } const func = getContainingFunction(node); - if (func) { - const signature = getSignatureFromDeclaration(func); - const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(func); - if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function + if (!func) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); + return; + } + + const signature = getSignatureFromDeclaration(func); + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(func); + const isGenerator = functionFlags & FunctionFlags.Generator; + if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + if (isGenerator) { // AsyncGenerator function or Generator function // A generator does not need its return expressions checked against its return type. // Instead, the yield expressions are checked against the element type. - // TODO: Check return expressions of generators when return type tracking is added + // TODO: Check return types of generators when return type tracking is added // for generators. return; } - if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - if (func.kind === SyntaxKind.SetAccessor) { - if (node.expression) { - error(node, Diagnostics.Setters_cannot_return_a_value); - } - } - else if (func.kind === SyntaxKind.Constructor) { - if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) { - error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); - } - } - else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) { - if (functionFlags & FunctionFlags.Async) { // Async function - const promisedType = getPromisedTypeOfPromise(returnType); - const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (promisedType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - checkTypeAssignableTo(awaitedType, promisedType, node); - } - } - else { - checkTypeAssignableTo(exprType, returnType, node); - } + else if (func.kind === SyntaxKind.SetAccessor) { + if (node.expression) { + error(node, Diagnostics.Setters_cannot_return_a_value); } } - else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { - // The function has a return type, but the return statement doesn't have an expression. - error(node, Diagnostics.Not_all_code_paths_return_a_value); + else if (func.kind === SyntaxKind.Constructor) { + if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) { + error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); + } } + else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) { + if (functionFlags & FunctionFlags.Async) { // Async function + const promisedType = getPromisedTypeOfPromise(returnType); + const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (promisedType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + checkTypeAssignableTo(awaitedType, promisedType, node); + } + } + else { + checkTypeAssignableTo(exprType, returnType, node); + } + } + } + else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType) && !isGenerator) { + // The function has a return type, but the return statement doesn't have an expression. + error(node, Diagnostics.Not_all_code_paths_return_a_value); } } @@ -22454,9 +22849,11 @@ namespace ts { // type parameter at this position, we report an error. const sourceConstraint = source.constraint && getTypeFromTypeNode(source.constraint); const targetConstraint = getConstraintFromTypeParameter(target); - if ((sourceConstraint || targetConstraint) && - (!sourceConstraint || !targetConstraint || !isTypeIdenticalTo(sourceConstraint, targetConstraint))) { - return false; + if (sourceConstraint) { + // relax check if later interface augmentation has no constraint + if (!targetConstraint || !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { + return false; + } } // If the type parameter node has a default and it is not identical to the default @@ -22538,7 +22935,10 @@ namespace ts { } } } - checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name || node, Diagnostics.Class_0_incorrectly_extends_base_class_1); + const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); + } checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) { @@ -22570,7 +22970,13 @@ namespace ts { const t = getTypeFromTypeNode(typeRefNode); if (t !== unknownType) { if (isValidBaseType(t)) { - checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(t, type.thisType), node.name || node, Diagnostics.Class_0_incorrectly_implements_interface_1); + const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : + Diagnostics.Class_0_incorrectly_implements_interface_1; + const baseWithThis = getTypeWithThisArgument(t, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); + } } else { error(typeRefNode, Diagnostics.A_class_may_only_implement_another_class_or_interface); @@ -22587,6 +22993,37 @@ namespace ts { } } + function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { + // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible + let issuedMemberError = false; + for (const member of node.members) { + if (hasStaticModifier(member)) { + continue; + } + const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); + if (declaredProp) { + const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); + const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); + if (prop && baseProp) { + const rootChain = () => chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, + unescapeLeadingUnderscores(declaredProp.escapedName), + typeToString(typeWithThis), + typeToString(baseWithThis) + ); + if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { + issuedMemberError = true; + } + } + } + } + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + } + function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { const signatures = getSignaturesOfType(type, SignatureKind.Construct); if (signatures.length) { @@ -23569,7 +24006,7 @@ namespace ts { return checkParameter(node); case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: - return checkPropertyDeclaration(node); + return checkPropertyDeclaration(node); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: @@ -24277,6 +24714,7 @@ namespace ts { return undefined; case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: // 1). import x = require("./mo/*gotToDefinitionHere*/d") // 2). External module name in an import declaration // 3). Dynamic import call or require in javascript @@ -24377,7 +24815,7 @@ namespace ts { } if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true); + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true); } if (isInRightSideOfImportOrExportAssignment(node)) { @@ -24422,7 +24860,7 @@ namespace ts { const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr.parent); const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || unknownType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType; return checkArrayLiteralDestructuringElementAssignment(expr.parent, typeOfArrayLiteral, - indexOf((expr.parent).elements, expr), elementType || unknownType); + (expr.parent).elements.indexOf(expr), elementType || unknownType); } // Gets the property symbol corresponding to the property in destructuring assignment @@ -24475,36 +24913,28 @@ namespace ts { } function getRootSymbols(symbol: Symbol): Symbol[] { + const roots = getImmediateRootSymbols(symbol); + return roots ? flatMap(roots, getRootSymbols) : [symbol]; + } + function getImmediateRootSymbols(symbol: Symbol): ReadonlyArray | undefined { if (getCheckFlags(symbol) & CheckFlags.Synthetic) { - const symbols: Symbol[] = []; - const name = symbol.escapedName; - forEach(getSymbolLinks(symbol).containingType.types, t => { - const symbol = getPropertyOfType(t, name); - if (symbol) { - symbols.push(symbol); - } - }); - return symbols; + return mapDefined(getSymbolLinks(symbol).containingType.types, type => getPropertyOfType(type, symbol.escapedName)); } else if (symbol.flags & SymbolFlags.Transient) { - const transient = symbol as TransientSymbol; - if (transient.leftSpread) { - return [...getRootSymbols(transient.leftSpread), ...getRootSymbols(transient.rightSpread)]; - } - if (transient.syntheticOrigin) { - return getRootSymbols(transient.syntheticOrigin); - } - - let target: Symbol; - let next = symbol; - while (next = getSymbolLinks(next).target) { - target = next; - } - if (target) { - return [target]; - } + const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetAliasTarget(symbol)); } - return [symbol]; + return undefined; + } + function tryGetAliasTarget(symbol: Symbol): Symbol | undefined { + let target: Symbol | undefined; + let next = symbol; + while (next = getSymbolLinks(next).target) { + target = next; + } + return target; } // Emitter support @@ -24625,7 +25055,7 @@ namespace ts { // AND // - binding is not declared in loop, should be renamed to avoid name reuse across siblings // let a, b - // { let x = 1; a = () => x; } + // { let x = 1; a = () => x; } // { let x = 100; b = () => x; } // console.log(a()); // should print '1' // console.log(b()); // should print '100' @@ -25086,7 +25516,7 @@ namespace ts { // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope // external modules cannot define or contribute to type declaration files - let current = symbol; + let current = symbol; while (true) { const parent = getParentOfSymbol(current); if (parent) { @@ -25550,10 +25980,6 @@ namespace ts { } function checkGrammarTypeParameterList(typeParameters: NodeArray, file: SourceFile): boolean { - if (checkGrammarForDisallowedTrailingComma(typeParameters)) { - return true; - } - if (typeParameters && typeParameters.length === 0) { const start = typeParameters.pos - "<".length; const end = skipTrivia(file.text, typeParameters.end) + ">".length; @@ -25647,6 +26073,21 @@ namespace ts { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); } if (parameter.type.kind !== SyntaxKind.StringKeyword && parameter.type.kind !== SyntaxKind.NumberKeyword) { + const type = getTypeFromTypeNode(parameter.type); + + if (type.flags & TypeFlags.String || type.flags & TypeFlags.Number) { + return grammarErrorOnNode(parameter.name, + Diagnostics.An_index_signature_parameter_type_cannot_be_a_type_alias_Consider_writing_0_Colon_1_Colon_2_instead, + getTextOfNode(parameter.name), + typeToString(type), + typeToString(getTypeFromTypeNode(node.type))); + } + + if (allTypesAssignableToKind(type, TypeFlags.StringLiteral, /*strict*/ true)) { + return grammarErrorOnNode(parameter.name, + Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead); + } + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_or_number); } if (!node.type) { @@ -26358,7 +26799,7 @@ namespace ts { } } - function checkGrammarProperty(node: PropertyDeclaration) { + function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { if (isClassLike(node.parent)) { if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; @@ -26385,7 +26826,7 @@ namespace ts { return grammarErrorOnFirstToken(node.initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } - if (node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || + if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) { return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index da0ea4aee56..4813d4b4300 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -144,7 +144,9 @@ namespace ts { "es2017.string": "lib.es2017.string.d.ts", "es2017.intl": "lib.es2017.intl.d.ts", "es2017.typedarrays": "lib.es2017.typedarrays.d.ts", + "esnext.array": "lib.esnext.array.d.ts", "esnext.asynciterable": "lib.esnext.asynciterable.d.ts", + "esnext.promise": "lib.esnext.promise.d.ts", }), }, showInSimplifiedHelpView: true, @@ -400,6 +402,13 @@ namespace ts { category: Diagnostics.Module_Resolution_Options, description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking }, + { + name: "esModuleInterop", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports + }, { name: "preserveSymlinks", type: "boolean", @@ -702,7 +711,8 @@ namespace ts { export const defaultInitCompilerOptions: CompilerOptions = { module: ModuleKind.CommonJS, target: ScriptTarget.ES5, - strict: true + strict: true, + esModuleInterop: true }; let optionNameMapCache: OptionNameMap; @@ -1863,7 +1873,7 @@ namespace ts { return normalizeNonListOptionValue(option, basePath, value); } - function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { if (option.isFilePath) { value = normalizePath(combinePaths(basePath, value)); if (value === "") { @@ -1906,21 +1916,6 @@ namespace ts { */ const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; - /** - * Tests for a path with multiple recursive directory wildcards. - * Matches **\** and **\a\**, but not **\a**b. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * (^|\/) # matches either the beginning of the string or a directory separator. - * \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator. - * (.*\/)? # optionally matches any number of characters followed by a directory separator. - * \*\* # matches a recursive directory wildcard "**" - * ($|\/) # matches either the end of the string or a directory separator. - */ - const invalidMultipleRecursionPatterns = /(^|\/)\*\*\/(.*\/)?\*\*($|\/)/; - /** * Tests for a path where .. appears after a recursive directory wildcard. * Matches **\..\*, **\a\..\*, and **\.., but not ..\**\* @@ -2115,9 +2110,6 @@ namespace ts { if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; } - else if (invalidMultipleRecursionPatterns.test(spec)) { - return Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0; - } else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fe472017f8c..24c2153c582 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -191,6 +191,19 @@ namespace ts { return undefined; } + export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { + while (true) { + const { value, done } = iter.next(); + if (done) { + return undefined; + } + const result = callback(value); + if (result !== undefined) { + return result; + } + } + } + /** * 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. @@ -215,13 +228,27 @@ namespace ts { export function zipWith(arrayA: ReadonlyArray, arrayB: ReadonlyArray, callback: (a: T, b: U, index: number) => V): V[] { const result: V[] = []; - Debug.assert(arrayA.length === arrayB.length); + Debug.assertEqual(arrayA.length, arrayB.length); for (let i = 0; i < arrayA.length; i++) { result.push(callback(arrayA[i], arrayB[i], i)); } return result; } + export function zipToIterator(arrayA: ReadonlyArray, arrayB: ReadonlyArray): Iterator<[T, U]> { + Debug.assertEqual(arrayA.length, arrayB.length); + let i = 0; + return { + next() { + if (i === arrayA.length) { + return { value: undefined as never, done: true }; + } + i++; + return { value: [arrayA[i - 1], arrayB[i - 1]], done: false }; + } + }; + } + export function zipToMap(keys: ReadonlyArray, values: ReadonlyArray): Map { Debug.assert(keys.length === values.length); const map = createMap(); @@ -261,6 +288,8 @@ namespace ts { return undefined; } + export function findLast(array: ReadonlyArray, predicate: (element: T, index: number) => element is U): U | undefined; + export function findLast(array: ReadonlyArray, predicate: (element: T, index: number) => boolean): T | undefined; export function findLast(array: ReadonlyArray, predicate: (element: T, index: number) => boolean): T | undefined { for (let i = array.length - 1; i >= 0; i--) { const value = array[i]; @@ -306,17 +335,6 @@ namespace ts { return false; } - export function indexOf(array: ReadonlyArray, value: T): number { - if (array) { - for (let i = 0; i < array.length; i++) { - if (array[i] === value) { - return i; - } - } - } - return -1; - } - export function indexOfAnyCharCode(text: string, charCodes: ReadonlyArray, start?: number): number { for (let i = start || 0; i < text.length; i++) { if (contains(charCodes, text.charCodeAt(i))) { @@ -394,12 +412,14 @@ namespace ts { return result; } + export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { - return { next }; - function next(): { value: U, done: false } | { value: never, done: true } { - const iterRes = iter.next(); - return iterRes.done ? iterRes : { value: mapFn(iterRes.value), done: false }; - } + return { + next() { + const iterRes = iter.next(); + return iterRes.done ? iterRes : { value: mapFn(iterRes.value), done: false }; + } + }; } // Maps from T to T and avoids allocation if all elements map to themselves @@ -474,22 +494,32 @@ namespace ts { return result; } - export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[] { - const result: U[] = []; - while (true) { - const { value, done } = iter.next(); - if (done) break; - const res = mapfn(value); - if (res) { - if (isArray(res)) { - result.push(...res); + export function flatMapIterator(iter: Iterator, mapfn: (x: T) => U[] | Iterator | undefined): Iterator { + const first = iter.next(); + if (first.done) { + return emptyIterator; + } + let currentIter = getIterator(first.value); + return { + next() { + while (true) { + const currentRes = currentIter.next(); + if (!currentRes.done) { + return currentRes; + } + const iterRes = iter.next(); + if (iterRes.done) { + return iterRes; + } + currentIter = getIterator(iterRes.value); } - else { - result.push(res); - } - } + }, + }; + + function getIterator(x: T): Iterator { + const res = mapfn(x); + return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; } - return result; } /** @@ -523,12 +553,23 @@ namespace ts { return result || array; } + export function mapAllOrFail(array: ReadonlyArray, mapFn: (x: T, i: number) => U | undefined): U[] | undefined { + const result: U[] = []; + for (let i = 0; i < array.length; i++) { + const mapped = mapFn(array[i], i); + if (mapped === undefined) { + return undefined; + } + result.push(mapped); + } + return result; + } + export function mapDefined(array: ReadonlyArray | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { const result: U[] = []; if (array) { for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = mapFn(item, i); + const mapped = mapFn(array[i], i); if (mapped !== undefined) { result.push(mapped); } @@ -537,17 +578,34 @@ namespace ts { return result; } - export function mapDefinedIter(iter: Iterator, mapFn: (x: T) => U | undefined): U[] { - const result: U[] = []; - while (true) { - const { value, done } = iter.next(); - if (done) break; - const res = mapFn(value); - if (res !== undefined) { - result.push(res); + export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { + return { + next() { + while (true) { + const res = iter.next(); + if (res.done) { + return res; + } + const value = mapFn(res.value); + if (value !== undefined) { + return { value, done: false }; + } + } } - } - return result; + }; + } + + export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; + + export function singleIterator(value: T): Iterator { + let done = false; + return { + next() { + const wasDone = done; + done = true; + return wasDone ? { value: undefined as never, done: true } : { value, done: false }; + } + }; } /** @@ -1345,7 +1403,6 @@ namespace ts { this.set(key, values = [value]); } return values; - } function multiMapRemove(this: MultiMap, key: string, value: T) { const values = this.get(key); @@ -1360,12 +1417,12 @@ namespace ts { /** * Tests whether a value is an array. */ - export function isArray(value: any): value is ReadonlyArray { + export function isArray(value: any): value is ReadonlyArray<{}> { return Array.isArray ? Array.isArray(value) : value instanceof Array; } - export function toArray(value: T | ReadonlyArray): ReadonlyArray; export function toArray(value: T | T[]): T[]; + export function toArray(value: T | ReadonlyArray): ReadonlyArray; export function toArray(value: T | T[]): T[] { return isArray(value) ? value : [value]; } @@ -1939,11 +1996,6 @@ namespace ts { return /^\.\.?($|[\\/])/.test(path); } - /** @deprecated Use `!isExternalModuleNameRelative(moduleName)` instead. */ - export function moduleHasNonRelativeName(moduleName: string): boolean { - return !isExternalModuleNameRelative(moduleName); - } - export function getEmitScriptTarget(compilerOptions: CompilerOptions) { return compilerOptions.target || ScriptTarget.ES3; } @@ -1966,7 +2018,9 @@ namespace ts { const moduleKind = getEmitModuleKind(compilerOptions); return compilerOptions.allowSyntheticDefaultImports !== undefined ? compilerOptions.allowSyntheticDefaultImports - : moduleKind === ModuleKind.System; + : compilerOptions.esModuleInterop + ? moduleKind !== ModuleKind.None && moduleKind < ModuleKind.ES2015 + : moduleKind === ModuleKind.System; } export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictPropertyInitialization" | "alwaysStrict"; @@ -2030,7 +2084,7 @@ namespace ts { function getNormalizedPathComponentsOfUrl(url: string) { // Get root length of http://www.website.com/folder1/folder2/ - // In this example the root is: http://www.website.com/ + // In this example the root is: http://www.website.com/ // normalized path components should be ["http://www.website.com/", "folder1", "folder2"] const urlLength = url.length; @@ -2063,7 +2117,7 @@ namespace ts { } else { // Can't find the host assume the rest of the string as component - // but make sure we append "/" to it as root is not joined using "/" + // but make sure we append "/" to it as root is not joined using "/" // eg. if url passed in was http://website.com we want to use root as [http://website.com/] // so that other path manipulations will be correct and it can be merged with relative paths correctly return [url + directorySeparator]; @@ -2084,7 +2138,7 @@ namespace ts { const directoryComponents = getNormalizedPathOrUrlComponents(directoryPathOrUrl, currentDirectory); if (directoryComponents.length > 1 && lastOrUndefined(directoryComponents) === "") { // If the directory path given was of type test/cases/ then we really need components of directory to be only till its name - // that is ["test", "cases", ""] needs to be actually ["test", "cases"] + // that is ["test", "cases", ""] needs to be actually ["test", "cases"] directoryComponents.pop(); } @@ -2326,7 +2380,6 @@ namespace ts { function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", { singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter }: WildcardMatcher): string | undefined { let subpattern = ""; - let hasRecursiveDirectoryWildcard = false; let hasWrittenComponent = false; const components = getNormalizedPathComponents(spec, basePath); const lastComponent = lastOrUndefined(components); @@ -2345,12 +2398,7 @@ namespace ts { let optionalCount = 0; for (let component of components) { if (component === "**") { - if (hasRecursiveDirectoryWildcard) { - return undefined; - } - subpattern += doubleAsteriskRegexFragment; - hasRecursiveDirectoryWildcard = true; } else { if (usage === "directories") { @@ -3213,4 +3261,8 @@ namespace ts { cachedReadDirectoryResult.clear(); } } + + export function singleElementArray(t: T | undefined): T[] | undefined { + return t === undefined ? undefined : [t]; + } } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index dc76e0ddf50..b85c79e775c 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -64,7 +64,7 @@ namespace ts { let currentIdentifiers: Map; let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; - let errorNameNode: DeclarationName; + let errorNameNode: DeclarationName | QualifiedName; const emitJsDocComments = compilerOptions.removeComments ? noop : writeJsDocComments; const emit = compilerOptions.stripInternal ? stripInternal : emitNode; let needsDeclare = true; @@ -148,8 +148,8 @@ namespace ts { moduleElementDeclarationEmitInfo = []; } - if (!isBundledEmit && isExternalModule(sourceFile) && sourceFile.moduleAugmentations.length && !resultHasExternalModuleIndicator) { - // if file was external module with augmentations - this fact should be preserved in .d.ts as well. + if (!isBundledEmit && isExternalModule(sourceFile) && !resultHasExternalModuleIndicator) { + // if file was external module this fact should be preserved in .d.ts as well. // in case if we didn't write any external module specifiers in .d.ts we need to emit something // that will force compiler to think that this file is an external module - 'export {}' is a reasonable choice here. write("export {};"); @@ -651,6 +651,9 @@ namespace ts { } function emitExportAssignment(node: ExportAssignment) { + if (isSourceFile(node.parent)) { + resultHasExternalModuleIndicator = true; // Top-level exports are external module indicators + } if (node.expression.kind === SyntaxKind.Identifier) { write(node.isExportEquals ? "export = " : "export default "); writeTextOfNode(currentText, node.expression); @@ -745,6 +748,7 @@ namespace ts { const modifiers = getModifierFlags(node); // If the node is exported if (modifiers & ModifierFlags.Export) { + resultHasExternalModuleIndicator = true; // Top-level exports are external module indicators write("export "); } @@ -901,6 +905,7 @@ namespace ts { } function emitExportDeclaration(node: ExportDeclaration) { + resultHasExternalModuleIndicator = true; // Top-level exports are external module indicators emitJsDocComments(node); write("export "); if (node.exportClause) { @@ -1199,7 +1204,7 @@ namespace ts { write(">"); } } - else { + else { emitHeritageClause([baseTypeNode], /*isImplementsList*/ false); } } @@ -1372,7 +1377,7 @@ namespace ts { // if this is property of type literal, // or is parameter of method/call/construct/index signature of type literal // emit only if type is specified - if (node.type) { + if (hasType(node)) { write(": "); emitType(node.type); } @@ -1866,6 +1871,7 @@ namespace ts { // it allows emitSeparatedList to write separator appropriately) // Example: // original: function foo([, x, ,]) {} + // tslint:disable-next-line no-double-space // emit : function foo([ , x, , ]) {} write(" "); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index faf0acb1631..cf5ccb1397d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -939,6 +939,14 @@ "category": "Error", "code": 1335 }, + "An index signature parameter type cannot be a type alias. Consider writing '[{0}: {1}]: {2}' instead.": { + "category": "Error", + "code": 1336 + }, + "An index signature parameter type cannot be a union type. Consider using a mapped object type instead.": { + "category": "Error", + "code": 1337 + }, "Duplicate identifier '{0}'.": { "category": "Error", @@ -1396,6 +1404,10 @@ "category": "Error", "code": 2415 }, + "Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.": { + "category": "Error", + "code": 2416 + }, "Class static side '{0}' incorrectly extends base class static side '{1}'.": { "category": "Error", "code": 2417 @@ -1612,7 +1624,7 @@ "category": "Error", "code": 2474 }, - "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.": { + "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.": { "category": "Error", "code": 2475 }, @@ -2276,7 +2288,22 @@ "category": "Error", "code": 2719 }, - + "Class '{0}' incorrectly implements class '{1}'. Did you mean to extend '{1}' and inherit its members as a subclass?": { + "category": "Error", + "code": 2720 + }, + "Cannot invoke an object which is possibly 'null'.": { + "category": "Error", + "code": 2721 + }, + "Cannot invoke an object which is possibly 'undefined'.": { + "category": "Error", + "code": 2722 + }, + "Cannot invoke an object which is possibly 'null' or 'undefined'.": { + "category": "Error", + "code": 2723 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", "code": 4000 @@ -2623,10 +2650,6 @@ "category": "Error", "code": 5010 }, - "File specification cannot contain multiple recursive directory wildcards ('**'): '{0}'.": { - "category": "Error", - "code": 5011 - }, "Cannot read file '{0}': {1}.": { "category": "Error", "code": 5012 @@ -2828,6 +2851,10 @@ "category": "Message", "code": 6030 }, + "Starting compilation in watch mode...": { + "category": "Message", + "code": 6031 + }, "File change detected. Starting incremental compilation...": { "category": "Message", "code": 6032 @@ -3416,6 +3443,14 @@ "category": "Message", "code": 6187 }, + "Numeric separators are not allowed here.": { + "category": "Error", + "code": 6188 + }, + "Multiple consecutive numeric separators are not permitted.": { + "category": "Error", + "code": 6189 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 @@ -3528,7 +3563,18 @@ "category": "Error", "code": 7036 }, - + "Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.": { + "category": "Message", + "code": 7037 + }, + "A namespace-style import cannot be called or constructed, and will cause a failure at runtime.": { + "category": "Error", + "code": 7038 + }, + "Mapped object type implicitly has an 'any' template type.": { + "category": "Error", + "code": 7039 + }, "You cannot rename this element.": { "category": "Error", "code": 8000 @@ -3831,6 +3877,10 @@ "category": "Message", "code": 90028 }, + "Add async modifier to containing function": { + "category": "Message", + "code": 90029 + }, "Convert function to an ES2015 class": { "category": "Message", "code": 95001 @@ -3886,5 +3936,17 @@ "Install '{0}'": { "category": "Message", "code": 95014 + }, + "Replace import with '{0}'.": { + "category": "Message", + "code": 95015 + }, + "Use synthetic 'default' member.": { + "category": "Message", + "code": 95016 + }, + "Convert to ES6 module": { + "category": "Message", + "code": 95017 } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 7a1d88d3bfd..909dd0feede 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1194,27 +1194,15 @@ namespace ts { // function emitObjectBindingPattern(node: ObjectBindingPattern) { - const elements = node.elements; - if (elements.length === 0) { - write("{}"); - } - else { - write("{"); - emitList(node, elements, ListFormat.ObjectBindingPatternElements); - write("}"); - } + write("{"); + emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); + write("}"); } function emitArrayBindingPattern(node: ArrayBindingPattern) { - const elements = node.elements; - if (elements.length === 0) { - write("[]"); - } - else { - write("["); - emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - write("]"); - } + write("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + write("]"); } function emitBindingElement(node: BindingElement) { @@ -3167,8 +3155,8 @@ namespace ts { TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented, UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine, IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine, - ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings, - ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings, + ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, + ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty, ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index dfc22e31833..7ddc8a0cd5f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -71,11 +71,11 @@ namespace ts { // Literals /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ - export function createLiteral(value: string | StringLiteral | NumericLiteral | Identifier): StringLiteral; + export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; export function createLiteral(value: number): NumericLiteral; export function createLiteral(value: boolean): BooleanLiteral; export function createLiteral(value: string | number | boolean): PrimaryExpression; - export function createLiteral(value: string | number | boolean | StringLiteral | NumericLiteral | Identifier): PrimaryExpression { + export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): PrimaryExpression { if (typeof value === "number") { return createNumericLiteral(value + ""); } @@ -101,7 +101,7 @@ namespace ts { return node; } - function createLiteralFromNode(sourceNode: StringLiteral | NumericLiteral | Identifier): StringLiteral { + function createLiteralFromNode(sourceNode: StringLiteralLike | NumericLiteral | Identifier): StringLiteral { const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode)); node.textSourceNode = sourceNode; return node; @@ -3626,7 +3626,7 @@ namespace ts { return qualifiedName; } - export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean) { + export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean): Block { return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node); } @@ -3831,13 +3831,13 @@ namespace ts { if (isLeftSideOfBinary) { // No need to parenthesize the left operand when the binary operator is // left associative: - // (a*b)/x -> a*b/x - // (a**b)/x -> a**b/x + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x // // Parentheses are needed for the left operand when the binary operator is // right associative: - // (a/b)**x -> (a/b)**x - // (a**b)**x -> (a**b)**x + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x return binaryOperatorAssociativity === Associativity.Right; } else { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index b0633ef4d5a..1cc11acd32d 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -64,9 +64,9 @@ namespace ts { return { fileName: resolved.path, packageId: resolved.packageId }; } - function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { + function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, originalPath: string | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { return { - resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, + resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, failedLookupLocations }; } @@ -732,12 +732,12 @@ namespace ts { const result = jsOnly ? tryResolve(Extensions.JavaScript) : (tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript)); if (result && result.value) { - const { resolved, isExternalLibraryImport } = result.value; - return createResolvedModuleWithFailedLookupLocations(resolved, isExternalLibraryImport, failedLookupLocations); + const { resolved, originalPath, isExternalLibraryImport } = result.value; + return createResolvedModuleWithFailedLookupLocations(resolved, originalPath, isExternalLibraryImport, failedLookupLocations); } return { resolvedModule: undefined, failedLookupLocations }; - function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { + function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, originalPath?: string, isExternalLibraryImport: boolean }> { const loader: ResolutionKindSpecificLoader = (extensions, candidate, failedLookupLocations, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ true); const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state); if (resolved) { @@ -752,11 +752,17 @@ namespace ts { if (!resolved) return undefined; let resolvedValue = resolved.value; - if (!compilerOptions.preserveSymlinks) { - resolvedValue = resolvedValue && { ...resolved.value, path: realPath(resolved.value.path, host, traceEnabled), extension: resolved.value.extension }; + let originalPath: string | undefined; + if (!compilerOptions.preserveSymlinks && resolvedValue) { + originalPath = resolvedValue.path; + const path = realPath(resolved.value.path, host, traceEnabled); + if (path === originalPath) { + originalPath = undefined; + } + resolvedValue = { ...resolvedValue, path }; } // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. - return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; + return { value: resolvedValue && { resolved: resolvedValue, originalPath, isExternalLibraryImport: true } }; } else { const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); @@ -1115,7 +1121,8 @@ namespace ts { const containingDirectory = getDirectoryPath(containingFile); const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); - return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations); + // No originalPath because classic resolution doesn't resolve realPath + return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*originalPath*/ undefined, /*isExternalLibraryImport*/ false, failedLookupLocations); function tryResolve(extensions: Extensions): SearchResult { const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state); @@ -1162,7 +1169,7 @@ namespace ts { const state: ModuleResolutionState = { compilerOptions, host, traceEnabled }; const failedLookupLocations: string[] = []; const resolved = loadModuleFromNodeModulesOneLevel(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state); - return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations); + return createResolvedModuleWithFailedLookupLocations(resolved, /*originalPath*/ undefined, /*isExternalLibraryImport*/ true, failedLookupLocations); } /** diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cb28be4677a..f0685b7372e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -88,20 +88,48 @@ namespace ts { case SyntaxKind.SpreadAssignment: return visitNode(cbNode, (node).expression); case SyntaxKind.Parameter: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).dotDotDotToken) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); case SyntaxKind.PropertyDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).exclamationToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); case SyntaxKind.PropertySignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); case SyntaxKind.PropertyAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).initializer); case SyntaxKind.VariableDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).exclamationToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); case SyntaxKind.BindingElement: return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).propertyName) || - visitNode(cbNode, (node).dotDotDotToken) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).questionToken) || - visitNode(cbNode, (node).exclamationToken) || - visitNode(cbNode, (node).type) || - visitNode(cbNode, (node).initializer); + visitNode(cbNode, (node).propertyName) || + visitNode(cbNode, (node).dotDotDotToken) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).initializer); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: @@ -557,7 +585,7 @@ namespace ts { // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost // all nodes would need extra state on them to store this info. // - // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 + // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 // grammar specification. // // An important thing about these context concepts. By default they are effectively inherited @@ -673,7 +701,7 @@ namespace ts { function getLanguageVariant(scriptKind: ScriptKind) { // .tsx and .jsx files are treated as jsx language variant. - return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; + return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; } function initializeState(_sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, scriptKind: ScriptKind) { @@ -758,15 +786,7 @@ namespace ts { const comments = getJSDocCommentRanges(node, sourceFile.text); if (comments) { for (const comment of comments) { - const jsDoc = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos); - if (jsDoc) { - if (!node.jsDoc) { - node.jsDoc = [jsDoc]; - } - else { - node.jsDoc.push(jsDoc); - } - } + node.jsDoc = append(node.jsDoc, JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); } } @@ -1401,9 +1421,13 @@ namespace ts { return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isIdentifierOrPattern(); case ParsingContext.TypeParameters: return isIdentifier(); - case ParsingContext.ArgumentExpressions: case ParsingContext.ArrayLiteralMembers: - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); + if (token() === SyntaxKind.CommaToken) { + return true; + } + // falls through + case ParsingContext.ArgumentExpressions: + return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: return isStartOfParameter(); case ParsingContext.TypeArguments: @@ -1425,7 +1449,7 @@ namespace ts { function isValidHeritageClauseObjectLiteral() { Debug.assert(token() === SyntaxKind.OpenBraceToken); if (nextToken() === SyntaxKind.CloseBraceToken) { - // if we see "extends {}" then only treat the {} as what we're extending (and not + // if we see "extends {}" then only treat the {} as what we're extending (and not // the class body) if we have: // // extends {} { @@ -1521,7 +1545,7 @@ namespace ts { function isVariableDeclaratorListTerminator(): boolean { // If we can consume a semicolon (either explicitly, or with ASI), then consider us done - // with parsing the list of variable declarators. + // with parsing the list of variable declarators. if (canParseSemicolon()) { return true; } @@ -1647,6 +1671,11 @@ namespace ts { return undefined; } + if ((node as JSDocContainer).jsDocCache) { + // jsDocCache may include tags from parent nodes, which might have been modified. + (node as JSDocContainer).jsDocCache = undefined; + } + return node; } @@ -2241,7 +2270,7 @@ namespace ts { // // // - // We do *not* want to consume the > as we're consuming the expression for "". + // We do *not* want to consume the `>` as we're consuming the expression for "". node.expression = parseUnaryExpressionOrHigher(); } } @@ -3061,7 +3090,7 @@ namespace ts { // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". // // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is - // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done + // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done // with AssignmentExpression if we see one. const arrowExpression = tryParseParenthesizedArrowFunctionExpression() || tryParseAsyncSimpleArrowFunctionExpression(); if (arrowExpression) { @@ -3091,7 +3120,7 @@ namespace ts { // we're in '2' or '3'. Consume the assignment and return. // // Note: we call reScanGreaterToken so that we get an appropriately merged token - // for cases like > > = becoming >>= + // for cases like `> > =` becoming `>>=` if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher()); } @@ -3247,7 +3276,7 @@ namespace ts { if (first === SyntaxKind.OpenParenToken) { if (second === SyntaxKind.CloseParenToken) { - // Simple cases: "() =>", "(): ", and "() {". + // Simple cases: "() =>", "(): ", and "() {". // This is an arrow function with no parameters. // The last one is not actually an arrow function, // but this is probably what the user intended. @@ -3472,7 +3501,9 @@ namespace ts { node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher); node.colonToken = parseExpectedToken(SyntaxKind.ColonToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); - node.whenFalse = parseAssignmentExpressionOrHigher(); + node.whenFalse = nodeIsPresent(node.colonToken) + ? parseAssignmentExpressionOrHigher() + : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); return finishNode(node); } @@ -3867,7 +3898,8 @@ namespace ts { // We don't want to eagerly consume all import keyword as import call expression so we look a head to find "(" // For example: // var foo3 = require("subfolder - // import * as foo1 from "module-from-node -> we want this import to be a statement rather than import call expression + // import * as foo1 from "module-from-node + // We want this import to be a statement rather than import call expression sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; expression = parseTokenNode(); } @@ -3917,7 +3949,7 @@ namespace ts { // treated as the invocation of "new Foo". We disambiguate that in code (to match // the original grammar) by making sure that if we see an ObjectCreationExpression // we always consume arguments if they are there. So we treat "new Foo()" as an - // object creation only, and not at all as an invocation) Another way to think + // object creation only, and not at all as an invocation. Another way to think // about this is that for every "new" that we see, we will consume an argument list if // it is there as part of the *associated* object creation node. Any additional // argument lists we see, will become invocation expressions. @@ -4333,7 +4365,7 @@ namespace ts { const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); if (!parseExpected(SyntaxKind.GreaterThanToken)) { - // If it doesn't have the closing > then it's definitely not an type argument list. + // If it doesn't have the closing `>` then it's definitely not an type argument list. return undefined; } @@ -5366,8 +5398,8 @@ namespace ts { // off. The grammar would look something like this: // // MemberVariableDeclaration[Yield]: - // AccessibilityModifier_opt PropertyName TypeAnnotation_opt Initializer_opt[In]; - // AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initializer_opt[In, ?Yield]; + // AccessibilityModifier_opt PropertyName TypeAnnotation_opt Initializer_opt[In]; + // AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initializer_opt[In, ?Yield]; // // The checker may still error in the static case to explicitly disallow the yield expression. node.initializer = hasModifier(node, ModifierFlags.Static) @@ -5462,6 +5494,7 @@ namespace ts { switch (token()) { case SyntaxKind.OpenParenToken: // Method declaration case SyntaxKind.LessThanToken: // Generic Method declaration + case SyntaxKind.ExclamationToken: // Non-null assertion on property name case SyntaxKind.ColonToken: // Type Annotation for declaration case SyntaxKind.EqualsToken: // Initializer for declaration case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. @@ -6240,17 +6273,17 @@ namespace ts { indent += text.length; } - nextJSDocToken(); - while (token() === SyntaxKind.WhitespaceTrivia) { - nextJSDocToken(); + let t = nextJSDocToken(); + while (t === SyntaxKind.WhitespaceTrivia) { + t = nextJSDocToken(); } - if (token() === SyntaxKind.NewLineTrivia) { + if (t === SyntaxKind.NewLineTrivia) { state = JSDocState.BeginningOfLine; indent = 0; - nextJSDocToken(); + t = nextJSDocToken(); } - while (token() !== SyntaxKind.EndOfFileToken) { - switch (token()) { + loop: while (true) { + switch (t) { case SyntaxKind.AtToken: if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { removeTrailingNewlines(comments); @@ -6304,7 +6337,7 @@ namespace ts { indent += whitespace.length; break; case SyntaxKind.EndOfFileToken: - break; + break loop; default: // anything other than whitespace or asterisk at the beginning of the line starts the comment text state = JSDocState.SavingComments; @@ -6312,10 +6345,11 @@ namespace ts { break; } if (advanceToken) { - nextJSDocToken(); + t = nextJSDocToken(); } else { advanceToken = true; + t = currentToken as JsDocSyntaxKind; } } removeLeadingNewlines(comments); @@ -6426,8 +6460,9 @@ namespace ts { comments.push(text); indent += text.length; } - while (token() !== SyntaxKind.AtToken && token() !== SyntaxKind.EndOfFileToken) { - switch (token()) { + let tok = token() as JsDocSyntaxKind; + loop: while (true) { + switch (tok) { case SyntaxKind.NewLineTrivia: if (state >= JSDocState.SawAsterisk) { state = JSDocState.BeginningOfLine; @@ -6436,8 +6471,9 @@ namespace ts { indent = 0; break; case SyntaxKind.AtToken: + case SyntaxKind.EndOfFileToken: // Done - break; + break loop; case SyntaxKind.WhitespaceTrivia: if (state === JSDocState.SavingComments) { pushComment(scanner.getTokenText()); @@ -6465,11 +6501,7 @@ namespace ts { pushComment(scanner.getTokenText()); break; } - if (token() === SyntaxKind.AtToken) { - // Done - break; - } - nextJSDocToken(); + tok = nextJSDocToken(); } removeLeadingNewlines(comments); @@ -6571,10 +6603,7 @@ namespace ts { const start = scanner.getStartPos(); let children: JSDocParameterTag[]; while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Parameter, name))) { - if (!children) { - children = []; - } - children.push(child); + children = append(children, child); } if (children) { jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); @@ -6691,10 +6720,7 @@ namespace ts { } } else { - if (!jsdocTypeLiteral.jsDocPropertyTags) { - jsdocTypeLiteral.jsDocPropertyTags = [] as MutableNodeArray; - } - (jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray).push(child); + jsdocTypeLiteral.jsDocPropertyTags = append(jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray, child); } } if (jsdocTypeLiteral) { @@ -6747,8 +6773,7 @@ namespace ts { let canParseTag = true; let seenAsterisk = false; while (true) { - nextJSDocToken(); - switch (token()) { + switch (nextJSDocToken()) { case SyntaxKind.AtToken: if (canParseTag) { const child = tryParseChildTag(target); @@ -6844,7 +6869,7 @@ namespace ts { return result; } - function nextJSDocToken(): SyntaxKind { + function nextJSDocToken(): JsDocSyntaxKind { return currentToken = scanner.scanJSDocToken(); } @@ -7048,7 +7073,7 @@ namespace ts { // If the 'pos' is before the start of the change, then we don't need to touch it. // If it isn't, then the 'pos' must be inside the change. How we update it will - // depend if delta is positive or negative. If delta is positive then we have + // depend if delta is positive or negative. If delta is positive then we have // something like: // // -------------------AAA----------------- @@ -7073,7 +7098,7 @@ namespace ts { // If the 'end' is after the change range, then we always adjust it by the delta // amount. However, if the end is in the change range, then how we adjust it - // will depend on if delta is positive or negative. If delta is positive then we + // will depend on if delta is positive or negative. If delta is positive then we // have something like: // // -------------------AAA----------------- diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4088a6951a1..88a2f5ee8f1 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -6,7 +6,7 @@ namespace ts { const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string { + export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { return forEachAncestorDirectory(searchPath, ancestor => { const fileName = combinePaths(ancestor, configName); return fileExists(fileName) ? fileName : undefined; @@ -241,22 +241,28 @@ namespace ts { return errorMessage; } - const redForegroundEscapeSequence = "\u001b[91m"; - const yellowForegroundEscapeSequence = "\u001b[93m"; - const blueForegroundEscapeSequence = "\u001b[93m"; + /** @internal */ + export enum ForegroundColorEscapeSequences { + Grey = "\u001b[90m", + Red = "\u001b[91m", + Yellow = "\u001b[93m", + Blue = "\u001b[94m", + Cyan = "\u001b[96m" + } const gutterStyleSequence = "\u001b[30;47m"; const gutterSeparator = " "; const resetEscapeSequence = "\u001b[0m"; const ellipsis = "..."; function getCategoryFormat(category: DiagnosticCategory): string { switch (category) { - case DiagnosticCategory.Warning: return yellowForegroundEscapeSequence; - case DiagnosticCategory.Error: return redForegroundEscapeSequence; - case DiagnosticCategory.Message: return blueForegroundEscapeSequence; + case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; + case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; + case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; } } - function formatAndReset(text: string, formatStyle: string) { + /** @internal */ + export function formatColorAndReset(text: string, formatStyle: string) { return formatStyle + text + resetEscapeSequence; } @@ -289,7 +295,7 @@ namespace ts { // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, // so we'll skip ahead to the second-to-last line. if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { - context += formatAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); + context += formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); i = lastLine - 1; } @@ -300,12 +306,12 @@ namespace ts { lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces // Output the gutter and the actual contents of the line. - context += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; context += lineContent + host.getNewLine(); // Output the gutter and the error span for the line using tildes. - context += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += redForegroundEscapeSequence; + context += formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += ForegroundColorEscapeSequences.Red; if (i === firstLine) { // If we're on the last line, then limit it to the last character of the last line. // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. @@ -324,13 +330,19 @@ namespace ts { context += resetEscapeSequence; } - output += host.getNewLine(); - output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `; + output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan); + output += ":"; + output += formatColorAndReset(`${ firstLine + 1 }`, ForegroundColorEscapeSequences.Yellow); + output += ":"; + output += formatColorAndReset(`${ firstLineChar + 1 }`, ForegroundColorEscapeSequences.Yellow); + output += " - "; } const categoryColor = getCategoryFormat(diagnostic.category); const category = DiagnosticCategory[diagnostic.category].toLowerCase(); - output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()) }`; + output += formatColorAndReset(category, categoryColor); + output += formatColorAndReset(` TS${ diagnostic.code }: `, ForegroundColorEscapeSequences.Grey); + output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); if (diagnostic.file) { output += host.getNewLine(); @@ -339,7 +351,7 @@ namespace ts { output += host.getNewLine(); } - return output; + return output + host.getNewLine(); } export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string { @@ -704,7 +716,7 @@ namespace ts { interface OldProgramState { program: Program | undefined; - file: SourceFile; + oldSourceFile: SourceFile | undefined; /** The collection of paths modified *since* the old program. */ modifiedFilePaths: Path[]; } @@ -754,7 +766,6 @@ namespace ts { /** A transient placeholder used to mark predicted resolution in the result list. */ const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {}; - for (let i = 0; i < moduleNames.length; i++) { const moduleName = moduleNames[i]; // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions @@ -825,9 +836,13 @@ namespace ts { // If we change our policy of rechecking failed lookups on each program create, // we should adjust the value returned here. function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean { - const resolutionToFile = getResolvedModule(oldProgramState.file, moduleName); - if (resolutionToFile) { - // module used to be resolved to file - ignore it + const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile, moduleName); + const resolvedFile = resolutionToFile && oldProgramState.program && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName); + if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) { + // In the old program, we resolved to an ambient module that was in the same + // place as we expected to find an actual module file. + // We actually need to return 'false' here even though this seems like a 'true' case + // because the normal module resolution algorithm will find this anyway. return false; } const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName); @@ -1001,7 +1016,7 @@ namespace ts { const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory); if (resolveModuleNamesWorker) { const moduleNames = getModuleNames(newSourceFile); - const oldProgramState = { program: oldProgram, file: oldSourceFile, modifiedFilePaths }; + const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths }; const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState); // ensure that module resolution results are still correct const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); @@ -1586,6 +1601,7 @@ namespace ts { // synthesize 'import "tslib"' declaration const externalHelpersModuleReference = createLiteral(externalHelpersModuleNameText); const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined); + addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); externalHelpersModuleReference.parent = importDecl; importDecl.parent = file; imports = [externalHelpersModuleReference]; @@ -1945,7 +1961,7 @@ namespace ts { if (file.imports.length || file.moduleAugmentations.length) { // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. const moduleNames = getModuleNames(file); - const oldProgramState = { program: oldProgram, file, modifiedFilePaths }; + const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile: oldProgram && oldProgram.getSourceFile(file.fileName), modifiedFilePaths }; const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState); Debug.assert(resolutions.length === moduleNames.length); for (let i = 0; i < moduleNames.length; i++) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index f085c3ffc06..c8bb2ec0a25 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -35,7 +35,7 @@ namespace ts { scanJsxAttributeValue(): SyntaxKind; reScanJsxToken(): SyntaxKind; scanJsxToken(): SyntaxKind; - scanJSDocToken(): SyntaxKind; + scanJSDocToken(): JsDocSyntaxKind; scan(): SyntaxKind; getText(): string; // Sets the text for the scanner to scan. An optional subrange starting point and length @@ -193,7 +193,7 @@ namespace ts { /* As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers IdentifierStart :: - Can contain Unicode 3.0.0 categories: + Can contain Unicode 3.0.0 categories: Uppercase letter (Lu), Lowercase letter (Ll), Titlecase letter (Lt), @@ -201,7 +201,7 @@ namespace ts { Other letter (Lo), or Letter number (Nl). IdentifierPart :: = - Can contain IdentifierStart + Unicode 3.0.0 categories: + Can contain IdentifierStart + Unicode 3.0.0 categories: Non-spacing mark (Mn), Combining spacing mark (Mc), Decimal number (Nd), or @@ -216,7 +216,7 @@ namespace ts { /* As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers IdentifierStart :: - Can contain Unicode 6.2 categories: + Can contain Unicode 6.2 categories: Uppercase letter (Lu), Lowercase letter (Ll), Titlecase letter (Lt), @@ -224,7 +224,7 @@ namespace ts { Other letter (Lo), or Letter number (Nl). IdentifierPart :: - Can contain IdentifierStart + Unicode 6.2 categories: + Can contain IdentifierStart + Unicode 6.2 categories: Non-spacing mark (Mn), Combining spacing mark (Mc), Decimal number (Nd), @@ -561,9 +561,9 @@ namespace ts { return false; } - function scanConflictMarkerTrivia(text: string, pos: number, error?: ErrorCallback) { + function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { if (error) { - error(Diagnostics.Merge_conflict_marker_encountered, mergeConflictMarkerLength); + error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); } const ch = text.charCodeAt(pos); @@ -852,34 +852,92 @@ namespace ts { scanRange, }; - function error(message: DiagnosticMessage, length?: number): void { + function error(message: DiagnosticMessage): void; + function error(message: DiagnosticMessage, errPos: number, length: number): void; + function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { if (onError) { + const oldPos = pos; + pos = errPos; onError(message, length || 0); + pos = oldPos; } } + function scanNumberFragment(): string { + let start = pos; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + let result = ""; + while (true) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + result += text.substring(start, pos); + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + start = pos; + continue; + } + if (isDigit(ch)) { + allowSeparator = true; + isPreviousTokenSeparator = false; + pos++; + continue; + } + break; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return result + text.substring(start, pos); + } + function scanNumber(): string { const start = pos; - while (isDigit(text.charCodeAt(pos))) pos++; + const mainFragment = scanNumberFragment(); + let decimalFragment: string; + let scientificFragment: string; if (text.charCodeAt(pos) === CharacterCodes.dot) { pos++; - while (isDigit(text.charCodeAt(pos))) pos++; + decimalFragment = scanNumberFragment(); } let end = pos; if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { pos++; tokenFlags |= TokenFlags.Scientific; if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; - if (isDigit(text.charCodeAt(pos))) { - pos++; - while (isDigit(text.charCodeAt(pos))) pos++; - end = pos; - } - else { + const preNumericPart = pos; + const finalFragment = scanNumberFragment(); + if (!finalFragment) { error(Diagnostics.Digit_expected); } + else { + scientificFragment = text.substring(end, preNumericPart) + finalFragment; + end = pos; + } + } + if (tokenFlags & TokenFlags.ContainsSeparator) { + let result = mainFragment; + if (decimalFragment) { + result += "." + decimalFragment; + } + if (scientificFragment) { + result += scientificFragment; + } + return "" + +result; + } + else { + return "" + +(text.substring(start, end)); // No need to use all the fragments; no _ removal needed } - return "" + +(text.substring(start, end)); } function scanOctalDigits(): number { @@ -894,23 +952,41 @@ namespace ts { * Scans the given number of hexadecimal digits in the text, * returning -1 if the given number is unavailable. */ - function scanExactNumberOfHexDigits(count: number): number { - return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false); + function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); } /** * Scans as many hexadecimal digits as are available in the text, * returning -1 if the given number of digits was unavailable. */ - function scanMinimumNumberOfHexDigits(count: number): number { - return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true); + function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); } - function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean): number { + function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): number { let digits = 0; let value = 0; + let allowSeparator = false; + let isPreviousTokenSeparator = false; while (digits < minCount || scanAsManyAsPossible) { const ch = text.charCodeAt(pos); + if (canHaveSeparators && ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + continue; + } + allowSeparator = canHaveSeparators; if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; } @@ -925,10 +1001,14 @@ namespace ts { } pos++; digits++; + isPreviousTokenSeparator = false; } if (digits < minCount) { value = -1; } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } return value; } @@ -1097,7 +1177,7 @@ namespace ts { } function scanHexadecimalEscape(numDigits: number): string { - const escapedValue = scanExactNumberOfHexDigits(numDigits); + const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); if (escapedValue >= 0) { return String.fromCharCode(escapedValue); @@ -1109,7 +1189,7 @@ namespace ts { } function scanExtendedUnicodeEscape(): string { - const escapedValue = scanMinimumNumberOfHexDigits(1); + const escapedValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); let isInvalidExtendedEscape = false; // Validate the value of the digit @@ -1162,7 +1242,7 @@ namespace ts { if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { const start = pos; pos += 2; - const value = scanExactNumberOfHexDigits(4); + const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); pos = start; return value; } @@ -1218,8 +1298,27 @@ namespace ts { // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. let numberOfDigits = 0; + let separatorAllowed = false; + let isPreviousTokenSeparator = false; while (true) { const ch = text.charCodeAt(pos); + // Numeric seperators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (separatorAllowed) { + separatorAllowed = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + continue; + } + separatorAllowed = true; const valueOfCh = ch - CharacterCodes._0; if (!isDigit(ch) || valueOfCh >= base) { break; @@ -1227,11 +1326,17 @@ namespace ts { value = value * base + valueOfCh; pos++; numberOfDigits++; + isPreviousTokenSeparator = false; } // Invalid binaryIntegerLiteral or octalIntegerLiteral if (numberOfDigits === 0) { return -1; } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + // Literal ends with underscore - not allowed + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + return value; + } return value; } @@ -1435,7 +1540,7 @@ namespace ts { case CharacterCodes._0: if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { pos += 2; - let value = scanMinimumNumberOfHexDigits(1); + let value = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); if (value < 0) { error(Diagnostics.Hexadecimal_digit_expected); value = 0; @@ -1489,7 +1594,7 @@ namespace ts { return token = SyntaxKind.NumericLiteral; case CharacterCodes.colon: pos++; - return token = SyntaxKind.ColonToken; + return token = SyntaxKind.ColonToken; case CharacterCodes.semicolon: pos++; return token = SyntaxKind.SemicolonToken; @@ -1800,7 +1905,7 @@ namespace ts { break; } } - tokenValue += text.substr(firstCharPosition, pos - firstCharPosition); + tokenValue += text.substring(firstCharPosition, pos); } return token; } @@ -1819,7 +1924,7 @@ namespace ts { } } - function scanJSDocToken(): SyntaxKind { + function scanJSDocToken(): JsDocSyntaxKind { if (pos >= end) { return token = SyntaxKind.EndOfFileToken; } @@ -1828,6 +1933,7 @@ namespace ts { tokenPos = pos; const ch = text.charCodeAt(pos); + pos++; switch (ch) { case CharacterCodes.tab: case CharacterCodes.verticalTab: @@ -1838,56 +1944,31 @@ namespace ts { } return token = SyntaxKind.WhitespaceTrivia; case CharacterCodes.at: - pos++; return token = SyntaxKind.AtToken; case CharacterCodes.lineFeed: case CharacterCodes.carriageReturn: - pos++; return token = SyntaxKind.NewLineTrivia; case CharacterCodes.asterisk: - pos++; return token = SyntaxKind.AsteriskToken; case CharacterCodes.openBrace: - pos++; return token = SyntaxKind.OpenBraceToken; case CharacterCodes.closeBrace: - pos++; return token = SyntaxKind.CloseBraceToken; case CharacterCodes.openBracket: - pos++; return token = SyntaxKind.OpenBracketToken; case CharacterCodes.closeBracket: - pos++; return token = SyntaxKind.CloseBracketToken; case CharacterCodes.lessThan: - pos++; return token = SyntaxKind.LessThanToken; - case CharacterCodes.greaterThan: - pos++; - return token = SyntaxKind.GreaterThanToken; case CharacterCodes.equals: - pos++; return token = SyntaxKind.EqualsToken; case CharacterCodes.comma: - pos++; return token = SyntaxKind.CommaToken; case CharacterCodes.dot: - pos++; - if (text.substr(tokenPos, pos + 2) === "...") { - pos += 2; - return token = SyntaxKind.DotDotDotToken; - } return token = SyntaxKind.DotToken; - case CharacterCodes.exclamation: - pos++; - return token = SyntaxKind.ExclamationToken; - case CharacterCodes.question: - pos++; - return token = SyntaxKind.QuestionToken; } if (isIdentifierStart(ch, ScriptTarget.Latest)) { - pos++; while (isIdentifierPart(text.charCodeAt(pos), ScriptTarget.Latest) && pos < end) { pos++; } @@ -1895,7 +1976,7 @@ namespace ts { return token = SyntaxKind.Identifier; } else { - return pos += 1, token = SyntaxKind.Unknown; + return token = SyntaxKind.Unknown; } } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index b5cd1d9e322..828c5744bbd 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -420,7 +420,7 @@ namespace ts { host.getCanonicalFileName, /*isAbsolutePathAnUrl*/ true); - sourceMapSourceIndex = indexOf(sourceMapData.sourceMapSources, source); + sourceMapSourceIndex = sourceMapData.sourceMapSources.indexOf(source); if (sourceMapSourceIndex === -1) { sourceMapSourceIndex = sourceMapData.sourceMapSources.length; sourceMapData.sourceMapSources.push(source); diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index 4af313e3896..fed5f15f79f 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -2,6 +2,7 @@ namespace ts { export function createGetSymbolWalker( getRestTypeOfSignature: (sig: Signature) => Type, + getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, getReturnTypeOfSignature: (sig: Signature) => Type, getBaseTypes: (type: Type) => Type[], resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, @@ -117,8 +118,9 @@ namespace ts { } function visitSignature(signature: Signature): void { - if (signature.typePredicate) { - visitType(signature.typePredicate.type); + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + visitType(typePredicate.type); } forEach(signature.typeParameters, visitType); diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 65771410ea2..da0fcc73a98 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -859,7 +859,7 @@ namespace ts { statements.push( setTextRange( createStatement( - createExtendsHelper(context, getLocalName(node)) + createExtendsHelper(context, getInternalName(node)) ), /*location*/ extendsClauseElement ) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 5b4c12b4f23..5d46c8430d2 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -968,7 +968,7 @@ namespace ts { name: "typescript:asyncValues", scoped: false, text: ` - var __asyncValues = (this && this.__asyncIterator) || function (o) { + var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator]; return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index bd2a4ef554d..8df05e4f3ab 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -3190,8 +3190,8 @@ namespace ts { // `throw` methods that step through the generator when invoked. // // parameters: - // thisArg The value to use as the `this` binding for the transformed generator body. - // body A function that acts as the transformed generator body. + // @param thisArg The value to use as the `this` binding for the transformed generator body. + // @param body A function that acts as the transformed generator body. // // variables: // _ Persistent state for the generator that is shared between the helper and the diff --git a/src/compiler/transformers/module/es2015.ts b/src/compiler/transformers/module/es2015.ts index 3951d08109d..4f218e4fdcf 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -25,14 +25,14 @@ namespace ts { if (externalHelpersModuleName) { const statements: Statement[] = []; const statementOffset = addPrologue(statements, node.statements); - append(statements, - createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText) - ) + const tslibImport = createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), + createLiteral(externalHelpersModuleNameText) ); + addEmitFlags(tslibImport, EmitFlags.NeverApplyImportHelper); + append(statements, tslibImport); addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); return updateSourceFileNode( diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 0c8e2cda0f5..0d4e5b6864a 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -148,7 +148,7 @@ namespace ts { // Create an updated SourceFile: // // define(moduleName?, ["module1", "module2"], function ... - return updateSourceFileNode(node, + const updated = updateSourceFileNode(node, setTextRange( createNodeArray([ createStatement( @@ -192,6 +192,9 @@ namespace ts { /*location*/ node.statements ) ); + + addEmitHelpers(updated, context.readEmitHelpers()); + return updated; } /** @@ -296,7 +299,7 @@ namespace ts { // } // })(function ...) - return updateSourceFileNode( + const updated = updateSourceFileNode( node, setTextRange( createNodeArray([ @@ -328,6 +331,9 @@ namespace ts { /*location*/ node.statements ) ); + + addEmitHelpers(updated, context.readEmitHelpers()); + return updated; } /** @@ -385,6 +391,18 @@ namespace ts { return { aliasedModuleNames, unaliasedModuleNames, importAliasNames }; } + function getAMDImportExpressionForImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration) { + if (isImportEqualsDeclaration(node) || isExportDeclaration(node) || !getExternalModuleNameLiteral(node, currentSourceFile, host, resolver, compilerOptions)) { + return undefined; + } + const name = getLocalNameForExternalImport(node, currentSourceFile); + const expr = getHelperExpressionForImport(node, name); + if (expr === name) { + return undefined; + } + return createStatement(createAssignment(name, expr)); + } + /** * Transforms a SourceFile into an AMD or UMD module body. * @@ -402,6 +420,9 @@ namespace ts { // Visit each statement of the module body. append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement)); + if (moduleKind === ModuleKind.AMD) { + addRange(statements, mapDefined(currentModuleInfo.externalImports, getAMDImportExpressionForImport)); + } addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); // Append the 'export =' statement if provided. @@ -617,7 +638,12 @@ namespace ts { } } - return createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]); + const promise = createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]); + if (compilerOptions.esModuleInterop) { + context.requestEmitHelper(importStarHelper); + return createCall(createPropertyAccess(promise, createIdentifier("then")), /*typeArguments*/ undefined, [getHelperName("__importStar")]); + } + return promise; } function createImportCallExpressionCommonJS(arg: Expression | undefined, containsLexicalThis: boolean): Expression { @@ -627,7 +653,11 @@ namespace ts { // 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 const promiseResolveCall = createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []); - const requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []); + let requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []); + if (compilerOptions.esModuleInterop) { + context.requestEmitHelper(importStarHelper); + requireCall = createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [requireCall]); + } let func: FunctionExpression | ArrowFunction; if (languageVersion >= ScriptTarget.ES2015) { @@ -660,6 +690,22 @@ namespace ts { return createCall(createPropertyAccess(promiseResolveCall, "then"), /*typeArguments*/ undefined, [func]); } + + function getHelperExpressionForImport(node: ImportDeclaration, innerExpr: Expression) { + if (!compilerOptions.esModuleInterop || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) { + return innerExpr; + } + if (getNamespaceDeclarationNode(node)) { + context.requestEmitHelper(importStarHelper); + return createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]); + } + if (isDefaultImport(node)) { + context.requestEmitHelper(importDefaultHelper); + return createCall(getHelperName("__importDefault"), /*typeArguments*/ undefined, [innerExpr]); + } + return innerExpr; + } + /** * Visits an ImportDeclaration node. * @@ -681,7 +727,7 @@ namespace ts { createVariableDeclaration( getSynthesizedClone(namespaceDeclaration.name), /*type*/ undefined, - createRequireCall(node) + getHelperExpressionForImport(node, createRequireCall(node)) ) ); } @@ -694,7 +740,7 @@ namespace ts { createVariableDeclaration( getGeneratedNameForNode(node), /*type*/ undefined, - createRequireCall(node) + getHelperExpressionForImport(node, createRequireCall(node)) ) ); @@ -1671,4 +1717,28 @@ namespace ts { text: ` var __syncRequire = typeof module === "object" && typeof module.exports === "object";` }; + + // emit helper for `import * as Name from "foo"` + const importStarHelper: EmitHelper = { + name: "typescript:commonjsimportstar", + scoped: false, + text: ` +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}` + }; + + // emit helper for `import Name from "foo"` + const importDefaultHelper: EmitHelper = { + name: "typescript:commonjsimportdefault", + scoped: false, + text: ` +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}` + }; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 3c1bba280f0..cdb0d0667ad 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -81,7 +81,7 @@ namespace ts { let classAliases: Identifier[]; /** - * Keeps track of whether we are within any containing namespaces when performing + * Keeps track of whether we are within any containing namespaces when performing * just-in-time substitution while printing an expression identifier. */ let applicableSubstitutions: TypeScriptSubstitutionFlags; diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index a012c9be7db..15e91d755ca 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -143,6 +143,7 @@ namespace ts { createLiteral(externalHelpersModuleNameText)); if (externalHelpersImportDeclaration) { + addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); externalImports.unshift(externalHelpersImportDeclaration); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index af92654c0a5..23798635671 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -58,6 +58,23 @@ namespace ts { end: number; } + export type JsDocSyntaxKind = + | SyntaxKind.EndOfFileToken + | SyntaxKind.WhitespaceTrivia + | SyntaxKind.AtToken + | SyntaxKind.NewLineTrivia + | SyntaxKind.AsteriskToken + | SyntaxKind.OpenBraceToken + | SyntaxKind.CloseBraceToken + | SyntaxKind.LessThanToken + | SyntaxKind.OpenBracketToken + | SyntaxKind.CloseBracketToken + | SyntaxKind.EqualsToken + | SyntaxKind.CommaToken + | SyntaxKind.DotToken + | SyntaxKind.Identifier + | SyntaxKind.Unknown; + // token > SyntaxKind.Identifer => token is a keyword // Also, If you add a new SyntaxKind be sure to keep the `Markers` section at the bottom in sync export const enum SyntaxKind { @@ -438,24 +455,24 @@ namespace ts { } export const enum NodeFlags { - None = 0, - Let = 1 << 0, // Variable declaration - Const = 1 << 1, // Variable declaration - NestedNamespace = 1 << 2, // Namespace declaration - Synthesized = 1 << 3, // Node was synthesized during transformation - Namespace = 1 << 4, // Namespace declaration - ExportContext = 1 << 5, // Export context (initialized by binding) - ContainsThis = 1 << 6, // Interface contains references to "this" - HasImplicitReturn = 1 << 7, // If function implicitly returns on one of codepaths (initialized by binding) - HasExplicitReturn = 1 << 8, // If function has explicit reachable return on one of codepaths (initialized by binding) + None = 0, + Let = 1 << 0, // Variable declaration + Const = 1 << 1, // Variable declaration + NestedNamespace = 1 << 2, // Namespace declaration + Synthesized = 1 << 3, // Node was synthesized during transformation + Namespace = 1 << 4, // Namespace declaration + ExportContext = 1 << 5, // Export context (initialized by binding) + ContainsThis = 1 << 6, // Interface contains references to "this" + HasImplicitReturn = 1 << 7, // If function implicitly returns on one of codepaths (initialized by binding) + HasExplicitReturn = 1 << 8, // If function has explicit reachable return on one of codepaths (initialized by binding) GlobalAugmentation = 1 << 9, // Set if module declaration is an augmentation for the global scope - HasAsyncFunctions = 1 << 10, // If the file has async functions (initialized by binding) - DisallowInContext = 1 << 11, // If node was parsed in a context where 'in-expressions' are not allowed - YieldContext = 1 << 12, // If node was parsed in the 'yield' context created when parsing a generator - DecoratorContext = 1 << 13, // If node was parsed as part of a decorator - AwaitContext = 1 << 14, // If node was parsed in the 'await' context created when parsing an async function - ThisNodeHasError = 1 << 15, // If the parser encountered an error when parsing the code that created this node - JavaScriptFile = 1 << 16, // If node was parsed in a JavaScript + HasAsyncFunctions = 1 << 10, // If the file has async functions (initialized by binding) + DisallowInContext = 1 << 11, // If node was parsed in a context where 'in-expressions' are not allowed + YieldContext = 1 << 12, // If node was parsed in the 'yield' context created when parsing a generator + DecoratorContext = 1 << 13, // If node was parsed as part of a decorator + AwaitContext = 1 << 14, // If node was parsed in the 'await' context created when parsing an async function + ThisNodeHasError = 1 << 15, // If the parser encountered an error when parsing the code that created this node + JavaScriptFile = 1 << 16, // If node was parsed in a JavaScript ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node @@ -584,6 +601,40 @@ namespace ts { | JSDocFunctionType | EndOfFileToken; + export type HasType = + | SignatureDeclaration + | VariableDeclaration + | ParameterDeclaration + | PropertySignature + | PropertyDeclaration + | TypePredicateNode + | ParenthesizedTypeNode + | TypeOperatorNode + | MappedTypeNode + | AssertionExpression + | TypeAliasDeclaration + | JSDocTypeExpression + | JSDocNonNullableType + | JSDocNullableType + | JSDocOptionalType + | JSDocVariadicType; + + export type HasInitializer = + | HasExpressionInitializer + | ForStatement + | ForInStatement + | ForOfStatement + | JsxAttribute; + + export type HasExpressionInitializer = + | VariableDeclaration + | ParameterDeclaration + | BindingElement + | PropertySignature + | PropertyDeclaration + | PropertyAssignment + | EnumMember; + /* @internal */ export type MutableNodeArray = NodeArray & T[]; @@ -793,6 +844,9 @@ namespace ts { initializer?: Expression; // Optional initializer } + /*@internal*/ + export type BindingElementGrandparent = BindingElement["parent"]["parent"]; + export interface PropertySignature extends TypeElement, JSDocContainer { kind: SyntaxKind.PropertySignature; name: PropertyName; // Declared property name @@ -848,25 +902,18 @@ namespace ts { expression: Expression; } - // SyntaxKind.VariableDeclaration - // SyntaxKind.Parameter - // SyntaxKind.BindingElement - // SyntaxKind.Property - // SyntaxKind.PropertyAssignment - // SyntaxKind.JsxAttribute - // SyntaxKind.ShorthandPropertyAssignment - // SyntaxKind.EnumMember - // SyntaxKind.JSDocPropertyTag - // SyntaxKind.JSDocParameterTag - export interface VariableLikeDeclaration extends NamedDeclaration { - propertyName?: PropertyName; - dotDotDotToken?: DotDotDotToken; - name: DeclarationName; - questionToken?: QuestionToken; - exclamationToken?: ExclamationToken; - type?: TypeNode; - initializer?: Expression; - } + export type VariableLikeDeclaration = + | VariableDeclaration + | ParameterDeclaration + | BindingElement + | PropertyDeclaration + | PropertyAssignment + | PropertySignature + | JsxAttribute + | ShorthandPropertyAssignment + | EnumMember + | JSDocPropertyTag + | JSDocParameterTag; export interface PropertyLikeDeclaration extends NamedDeclaration { name: PropertyName; @@ -949,7 +996,7 @@ namespace ts { export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; /* @internal */ returnFlowNode?: FlowNode; } @@ -957,14 +1004,14 @@ namespace ts { /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ export interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } // See the comment on MethodDeclaration for the intuition behind GetAccessorDeclaration being a // ClassElement and an ObjectLiteralElement. export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } @@ -973,7 +1020,7 @@ namespace ts { // ClassElement and an ObjectLiteralElement. export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } @@ -982,7 +1029,7 @@ namespace ts { export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } export interface TypeNode extends Node { @@ -1102,11 +1149,13 @@ 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). + /* @internal */ textSourceNode?: Identifier | StringLiteralLike | 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; } + /* @internal */ export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral; + // Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing. // Consider 'Expression'. Without the brand, 'Expression' is actually no different // (structurally) than 'Node'. Because of this you can pass any Node to a function that @@ -1364,7 +1413,7 @@ namespace ts { export type BinaryOperatorToken = Token; - export interface BinaryExpression extends Expression, Declaration { + export interface BinaryExpression extends Expression, Declaration { kind: SyntaxKind.BinaryExpression; left: Expression; operatorToken: BinaryOperatorToken; @@ -1452,6 +1501,7 @@ namespace ts { kind: SyntaxKind.ArrowFunction; equalsGreaterThanToken: EqualsGreaterThanToken; body: ConciseBody; + name: never; } // The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral, @@ -1490,8 +1540,9 @@ namespace ts { HexSpecifier = 1 << 6, // e.g. `0x00000000` BinarySpecifier = 1 << 7, // e.g. `0b0110010000000000` OctalSpecifier = 1 << 8, // e.g. `0o777` + ContainsSeparator = 1 << 9, // e.g. `0b1100_0101` BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier, - NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinarySpecifier | OctalSpecifier + NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinarySpecifier | OctalSpecifier | ContainsSeparator } export interface NumericLiteral extends LiteralExpression { @@ -1986,7 +2037,7 @@ namespace ts { export interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } @@ -2108,6 +2159,7 @@ namespace ts { export interface ExportDeclaration extends DeclarationStatement { kind: SyntaxKind.ExportDeclaration; parent?: SourceFile | ModuleBlock; + /** Will not be assigned in the case of `export * from "foo";` */ exportClause?: NamedExports; /** If this is not a StringLiteral it will be a grammar error. */ moduleSpecifier?: Expression; @@ -2478,7 +2530,7 @@ namespace ts { // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined // It is used to resolve module names in the checker. // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead - /* @internal */ resolvedModules: Map; + /* @internal */ resolvedModules: Map; /* @internal */ resolvedTypeReferenceDirectiveNames: Map; /* @internal */ imports: ReadonlyArray; // Identifier only if `declare global` @@ -2509,7 +2561,7 @@ namespace ts { export interface ParseConfigHost { useCaseSensitiveFileNames: boolean; - readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, depth?: number): string[]; /** * Gets a value indicating whether the specified path exists and is a file. @@ -2738,6 +2790,8 @@ namespace ts { getAugmentedPropertiesOfType(type: Type): Symbol[]; getRootSymbols(symbol: Symbol): Symbol[]; getContextualType(node: Expression): Type | undefined; + /* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean; + /** * returns unknownSignature in the case of an error. * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. @@ -2752,6 +2806,8 @@ namespace ts { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; + /** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */ + /* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean; /** Follow all aliases to get the original symbol. */ getAliasedSymbol(symbol: Symbol): Symbol; /** Follow a *single* alias to get the immediately aliased symbol. */ @@ -2786,12 +2842,22 @@ namespace ts { /* @internal */ getNullType(): Type; /* @internal */ getESSymbolType(): Type; /* @internal */ getNeverType(): Type; - /* @internal */ getUnionType(types: Type[], subtypeReduction?: boolean): Type; + /* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): 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 */ createSignature( + declaration: SignatureDeclaration, + typeParameters: TypeParameter[], + thisParameter: Symbol | undefined, + parameters: Symbol[], + resolvedReturnType: Type, + typePredicate: TypePredicate | undefined, + 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; @@ -2816,7 +2882,7 @@ namespace ts { */ /* @internal */ isArrayLikeType(type: Type): boolean; /* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray): Symbol[]; - /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined; + /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(): string; /** @@ -2829,6 +2895,15 @@ namespace ts { * This should be called in a loop climbing parents of the symbol, so we'll get `N`. */ /* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined; + + /* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol; + } + + /* @internal */ + export const enum UnionReduction { + None = 0, + Literal, + Subtype } export enum NodeBuilderFlags { @@ -2925,9 +3000,9 @@ namespace ts { None = 0x00000000, // Write symbols's type argument if it is instantiated symbol - // eg. class C { p: T } <-- Show p as C.p here + // eg. class C { p: T } <-- Show p as C.p here // var a: C; - // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p + // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p WriteTypeParametersOrArguments = 0x00000001, // Use only external alias information to get the symbol name in the given context @@ -3177,6 +3252,7 @@ namespace ts { bindingElement?: BindingElement; // Binding element associated with property symbol exportsSomeValue?: boolean; // True if module exports some value (not just types) enumKind?: EnumKind; // Enum declaration classification + originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol` lateSymbol?: Symbol; // Late-bound symbol for a computed property } @@ -3199,6 +3275,7 @@ namespace ts { ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s) ContainsStatic = 1 << 9, // Synthetic property with static constituent(s) Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name + ReverseMapped = 1 << 11, // property of reverse-inferred homomorphic mapped type. Synthetic = SyntheticProperty | SyntheticMethod } @@ -3208,6 +3285,12 @@ namespace ts { isRestParameter?: boolean; } + /* @internal */ + export interface ReverseMappedSymbol extends TransientSymbol { + propertyType: Type; + mappedType: MappedType; + } + export const enum InternalSymbolName { Call = "__call", // Call signatures Constructor = "__constructor", // Constructor implementations @@ -3313,7 +3396,7 @@ namespace ts { resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt. - superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing + superCall?: SuperCall; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing switchTypes?: Type[]; // Cached array of switch case expression types } @@ -3442,6 +3525,7 @@ namespace ts { EvolvingArray = 1 << 8, // Evolving array type ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties ContainsSpread = 1 << 10, // Object literal contains spread operation + ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type ClassOrInterface = Class | Interface } @@ -3549,6 +3633,12 @@ namespace ts { finalArrayType?: Type; // Final array type of evolving array type } + /* @internal */ + export interface ReverseMappedType extends ObjectType { + source: Type; + mappedType: MappedType; + } + /* @internal */ // Resolved object, union, or intersection type export interface ResolvedType extends ObjectType, UnionOrIntersectionType { @@ -3562,7 +3652,7 @@ namespace ts { /* @internal */ // Object literals are initially marked fresh. Freshness disappears following an assignment, - // before a type assertion, or when an object literal's type is widened. The regular + // before a type assertion, or when an object literal's type is widened. The regular // version of a fresh type is identical except for the TypeFlags.FreshObjectLiteral flag. export interface FreshObjectLiteralType extends ResolvedType { regularType: ResolvedType; // Regular version of fresh type @@ -3638,7 +3728,13 @@ namespace ts { /* @internal */ thisParameter?: Symbol; // symbol of this-type parameter /* @internal */ - resolvedReturnType: Type; // Resolved return type + // See comment in `instantiateSignature` for why these are set lazily. + resolvedReturnType: Type | undefined; // Lazily set by `getReturnTypeOfSignature`. + /* @internal */ + // Lazily set by `getTypePredicateOfSignature`. + // `undefined` indicates a type predicate that has not yet been computed. + // Uses a special `noTypePredicate` sentinel value to indicate that there is no type predicate. This looks like a TypePredicate at runtime to avoid polymorphism. + resolvedTypePredicate: TypePredicate | undefined; /* @internal */ minArgumentCount: number; // Number of non-optional parameters /* @internal */ @@ -3658,8 +3754,6 @@ namespace ts { /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ - typePredicate?: TypePredicate; - /* @internal */ instantiations?: Map; // Generic signature instantiation cache } @@ -3889,6 +3983,7 @@ namespace ts { typeRoots?: string[]; /*@internal*/ version?: boolean; /*@internal*/ watch?: boolean; + esModuleInterop?: boolean; [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; } @@ -4229,6 +4324,8 @@ namespace ts { * If changing this, remember to change `moduleResolutionIsEqualTo`. */ export interface ResolvedModuleFull extends ResolvedModule { + /* @internal */ + readonly originalPath?: string; /** * Extension of resolvedFileName. This must match what's at the end of resolvedFileName. * This is optional for backwards-compatibility, but will be added if not provided. @@ -4308,11 +4405,11 @@ namespace ts { * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just * 'throw new Error("NotImplemented")' */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): (ResolvedModule | undefined)[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): (ResolvedTypeReferenceDirective | undefined)[]; getEnvironmentVariable?(name: string): string; /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; @@ -4454,6 +4551,7 @@ namespace ts { Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions. /*@internal*/ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform. + /*@internal*/ NeverApplyImportHelper = 1 << 26, // Indicates the node should never be wrapped with an import star helper (because, for example, it imports tslib itself) } export interface EmitHelper { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0e62c2d8cea..37b62fd5d85 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3,7 +3,9 @@ /* @internal */ namespace ts { export const emptyArray: never[] = [] as never[]; + export const resolvingEmptyArray: never[] = [] as never[]; export const emptyMap: ReadonlyMap = createMap(); + export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap = emptyMap as ReadonlyUnderscoreEscapedMap; export const externalHelpersModuleNameText = "tslib"; @@ -101,6 +103,7 @@ namespace ts { return oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport && oldResolution.extension === newResolution.extension && oldResolution.resolvedFileName === newResolution.resolvedFileName && + oldResolution.originalPath === newResolution.originalPath && packageIdIsEqual(oldResolution.packageId, newResolution.packageId); } @@ -345,7 +348,7 @@ namespace ts { export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile) { // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. - if (!nodeIsSynthesized(node) && node.parent) { + if (!nodeIsSynthesized(node) && node.parent && !(isNumericLiteral(node) && node.numericLiteralFlags & TokenFlags.ContainsSeparator)) { return getSourceTextOfNodeFromSourceFile(sourceFile, node); } @@ -547,7 +550,7 @@ namespace ts { // Return display name of an identifier // Computed property names will just be emitted as "[]", where is the source // text of the expression in the computed property. - export function declarationNameToString(name: DeclarationName) { + export function declarationNameToString(name: DeclarationName | QualifiedName) { return getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name); } @@ -783,7 +786,7 @@ namespace ts { case SyntaxKind.PropertySignature: case SyntaxKind.Parameter: case SyntaxKind.VariableDeclaration: - return node === (parent).type; + return node === (parent as HasType).type; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: @@ -801,7 +804,7 @@ namespace ts { return node === (parent).type; case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: - return (parent).typeArguments && indexOf((parent).typeArguments, node) >= 0; + return contains((parent).typeArguments, node); case SyntaxKind.TaggedTemplateExpression: // TODO (drosen): TaggedTemplateExpressions may eventually support type arguments. return false; @@ -1339,7 +1342,7 @@ namespace ts { case SyntaxKind.EnumMember: case SyntaxKind.PropertyAssignment: case SyntaxKind.BindingElement: - return (parent).initializer === node; + return (parent as HasInitializer).initializer === node; case SyntaxKind.ExpressionStatement: case SyntaxKind.IfStatement: case SyntaxKind.DoStatement: @@ -1417,6 +1420,8 @@ namespace ts { * exactly one argument (of the form 'require("name")'). * This function does not test if the node is in a JavaScript file or not. */ + export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: true): callExpression is CallExpression & { expression: Identifier, arguments: [StringLiteralLike] }; + export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression; export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression { if (callExpression.kind !== SyntaxKind.CallExpression) { return false; @@ -1454,7 +1459,7 @@ namespace ts { return false; } - export function getRightMostAssignedExpression(node: Node) { + export function getRightMostAssignedExpression(node: Expression): Expression { while (isAssignmentExpression(node, /*excludeCompoundAssignements*/ true)) { node = node.right; } @@ -1649,7 +1654,7 @@ namespace ts { result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration)); } - if (isVariableLike(node) && node.initializer && hasJSDocNodes(node.initializer)) { + if (isVariableLike(node) && hasInitializer(node) && hasJSDocNodes(node.initializer)) { result = addRange(result, node.initializer.jsDoc); } @@ -2117,6 +2122,10 @@ namespace ts { return "__@" + symbolName as __String; } + export function isKnownSymbol(symbol: Symbol): boolean { + return startsWith(symbol.escapedName as string, "__@"); + } + /** * Includes the word "Symbol" with unicode escapes */ @@ -2815,8 +2824,8 @@ 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, checkJSDoc?: boolean): TypeNode | undefined { - if (node.type) { + export function getEffectiveTypeAnnotationNode(node: Node, checkJSDoc?: boolean): TypeNode | undefined { + if (hasType(node)) { return node.type; } if (checkJSDoc || isInJavaScriptFile(node)) { @@ -3696,6 +3705,10 @@ namespace ts { export function typeHasCallOrConstructSignatures(type: Type, checker: TypeChecker) { return checker.getSignaturesOfType(type, SignatureKind.Call).length !== 0 || checker.getSignaturesOfType(type, SignatureKind.Construct).length !== 0; } + + export function forSomeAncestorDirectory(directory: string, callback: (directory: string) => boolean): boolean { + return !!forEachAncestorDirectory(directory, d => callback(d) ? true : undefined); + } } namespace ts { @@ -3911,8 +3924,8 @@ namespace ts { // // { // oldStart3: Min(oldStart1, oldStart2), - // oldEnd3 : Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), - // newEnd3 : Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) + // oldEnd3: Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), + // newEnd3: Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) // } const oldStart1 = oldStartN; @@ -4397,7 +4410,7 @@ namespace ts { return node.kind === SyntaxKind.RegularExpressionLiteral; } - export function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression { + export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; } @@ -5228,6 +5241,18 @@ namespace ts { return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor); } + /* @internal */ + export function isMethodOrAccessor(node: Node): node is MethodDeclaration | AccessorDeclaration { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return true; + default: + return false; + } + } + // Type members export function isTypeElement(node: Node): node is TypeElement { @@ -5807,4 +5832,37 @@ namespace ts { export function hasJSDocNodes(node: Node): node is HasJSDoc { return !!(node as JSDocContainer).jsDoc && (node as JSDocContainer).jsDoc.length > 0; } + + /** True if has type node attached to it. */ + /* @internal */ + export function hasType(node: Node): node is HasType { + return !!(node as HasType).type; + } + + /** True if has initializer node attached to it. */ + /* @internal */ + export function hasInitializer(node: Node): node is HasInitializer { + return !!(node as HasInitializer).initializer; + } + + /** True if has initializer node attached to it. */ + /* @internal */ + export function hasOnlyExpressionInitializer(node: Node): node is HasExpressionInitializer { + return hasInitializer(node) && !isForStatement(node) && !isForInStatement(node) && !isForOfStatement(node) && !isJsxAttribute(node); + } + + export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { + switch (node.kind) { + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return true; + default: + return false; + } + } } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 0b40e20a7b7..2d678eb0eff 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -622,7 +622,7 @@ namespace ts { case SyntaxKind.ForOfStatement: return updateForOf(node, - (node).awaitModifier, + visitNode((node).awaitModifier, visitor, isToken), visitNode((node).initializer, visitor, isForInitializer), visitNode((node).expression, visitor, isExpression), visitNode((node).statement, visitor, isStatement, liftToBlock)); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index a9e5fab2026..b4ed1c1ac1b 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -48,6 +48,15 @@ namespace ts { }; } + /** @internal */ + export function createWatchDiagnosticReporterWithColor(system = sys): DiagnosticReporter { + return diagnostic => { + let output = `[${ formatColorAndReset(new Date().toLocaleTimeString(), ForegroundColorEscapeSequences.Grey) }] `; + output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine + system.newLine}`; + system.write(output); + }; + } + export function reportDiagnostics(diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter): void { for (const diagnostic of diagnostics) { reportDiagnostic(diagnostic); @@ -131,7 +140,7 @@ namespace ts { reportWatchDiagnostic?: DiagnosticReporter ): WatchingSystemHost { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); - reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); + reportWatchDiagnostic = reportWatchDiagnostic || pretty ? createWatchDiagnosticReporterWithColor(system) : createWatchDiagnosticReporter(system); parseConfigFile = parseConfigFile || ts.parseConfigFile; return { system, @@ -302,6 +311,8 @@ namespace ts { // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder({ getCanonicalFileName, computeHash }); + clearHostScreen(); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); synchronizeProgram(); // Update the wild card directory watch @@ -492,10 +503,14 @@ namespace ts { scheduleProgramUpdate(); } - function updateProgram() { + function clearHostScreen() { if (watchingHost.system.clearScreen) { watchingHost.system.clearScreen(); } + } + + function updateProgram() { + clearHostScreen(); timerToUpdateProgram = undefined; reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); diff --git a/src/harness/externalCompileRunner.ts b/src/harness/externalCompileRunner.ts index 2a44badd813..c5e1c6ea9ff 100644 --- a/src/harness/externalCompileRunner.ts +++ b/src/harness/externalCompileRunner.ts @@ -42,21 +42,29 @@ abstract class ExternalCompileRunnerBase extends RunnerBase { const stdio = isWorker ? "pipe" : "inherit"; let types: string[]; if (fs.existsSync(path.join(cwd, "test.json"))) { - const update = cp.spawnSync("git", ["submodule", "update", "--remote"], { cwd, timeout, shell: true, stdio }); - if (update.status !== 0) throw new Error(`git submodule update for ${directoryName} failed!`); + const submoduleDir = path.join(cwd, directoryName); + const reset = cp.spawnSync("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir, timeout, shell: true, stdio }); + if (reset.status !== 0) throw new Error(`git reset for ${directoryName} failed: ${reset.stderr.toString()}`); + const clean = cp.spawnSync("git", ["clean", "-f"], { cwd: submoduleDir, timeout, shell: true, stdio }); + if (clean.status !== 0) throw new Error(`git clean for ${directoryName} failed: ${clean.stderr.toString()}`); + const update = cp.spawnSync("git", ["submodule", "update", "--remote", "."], { cwd: submoduleDir, timeout, shell: true, stdio }); + if (update.status !== 0) throw new Error(`git submodule update for ${directoryName} failed: ${update.stderr.toString()}`); const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); types = config.types; - cwd = path.join(cwd, directoryName); + cwd = submoduleDir; } if (fs.existsSync(path.join(cwd, "package.json"))) { if (fs.existsSync(path.join(cwd, "package-lock.json"))) { fs.unlinkSync(path.join(cwd, "package-lock.json")); } + if (fs.existsSync(path.join(cwd, "node_modules"))) { + require("del").sync(path.join(cwd, "node_modules")); + } const install = cp.spawnSync(`npm`, ["i"], { cwd, timeout, shell: true, stdio }); - if (install.status !== 0) throw new Error(`NPM Install for ${directoryName} failed!`); + if (install.status !== 0) throw new Error(`NPM Install for ${directoryName} failed: ${install.stderr.toString()}`); } const args = [path.join(__dirname, "tsc.js")]; if (types) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 1919a47873e..8c5570f730c 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -27,6 +27,7 @@ namespace FourSlash { // The contents of the file (with markers, etc stripped out) content: string; fileName: string; + symlinks?: string[]; version: number; // File-specific options (name/value pairs) fileOptions: Harness.TestCaseParser.CompilerSettings; @@ -106,15 +107,16 @@ namespace FourSlash { // Name of testcase metadata including ts.CompilerOptions properties that will be used by globalOptions // To add additional option, add property into the testOptMetadataNames, refer the property in either globalMetadataNames or fileMetadataNames // Add cases into convertGlobalOptionsToCompilationsSettings function for the compiler to acknowledge such option from meta data - const metadataOptionNames = { - baselineFile: "BaselineFile", - emitThisFile: "emitThisFile", // This flag is used for testing getEmitOutput feature. It allows test-cases to indicate what file to be output in multiple files project - fileName: "Filename", - resolveReference: "ResolveReference", // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file - }; + const enum MetadataOptionNames { + baselineFile = "BaselineFile", + emitThisFile = "emitThisFile", // This flag is used for testing getEmitOutput feature. It allows test-cases to indicate what file to be output in multiple files project + fileName = "Filename", + resolveReference = "ResolveReference", // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file + symlink = "Symlink", + } // List of allowed metadata names - const fileMetadataNames = [metadataOptionNames.fileName, metadataOptionNames.emitThisFile, metadataOptionNames.resolveReference]; + const fileMetadataNames = [MetadataOptionNames.fileName, MetadataOptionNames.emitThisFile, MetadataOptionNames.resolveReference, MetadataOptionNames.symlink]; function convertGlobalOptionsToCompilerOptions(globalOptions: Harness.TestCaseParser.CompilerSettings): ts.CompilerOptions { const settings: ts.CompilerOptions = { target: ts.ScriptTarget.ES5 }; @@ -263,7 +265,7 @@ namespace FourSlash { ts.forEach(testData.files, file => { // Create map between fileName and its content for easily looking up when resolveReference flag is specified this.inputFiles.set(file.fileName, file.content); - if (ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json") { + if (isTsconfig(file)) { const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content); if (configJson.config === undefined) { throw new Error(`Failed to parse test tsconfig.json: ${configJson.error.messageText}`); @@ -281,7 +283,7 @@ namespace FourSlash { configFileName = file.fileName; } - if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") { + if (!startResolveFileRef && file.fileOptions[MetadataOptionNames.resolveReference] === "true") { startResolveFileRef = file; } else if (startResolveFileRef) { @@ -295,7 +297,7 @@ namespace FourSlash { const host = new Utils.MockParseConfigHost(baseDir, /*ignoreCase*/ false, this.inputFiles); const configJsonObj = ts.parseConfigFileTextToJson(configFileName, this.inputFiles.get(configFileName)); - assert(configJsonObj.config !== undefined); + assert.isTrue(configJsonObj.config !== undefined); const { options, errors } = ts.parseJsonConfigFileContent(configJsonObj.config, host, baseDir); @@ -354,6 +356,10 @@ namespace FourSlash { Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false); } + for (const file of testData.files) { + ts.forEach(file.symlinks, link => this.languageServiceAdapterHost.addSymlink(link, file.fileName)); + } + this.formatCodeSettings = { baseIndentSize: 0, indentSize: 4, @@ -375,6 +381,7 @@ namespace FourSlash { insertSpaceAfterTypeAssertion: false, placeOpenBraceOnNewLineForFunctions: false, placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false }; // Open the first file by default @@ -435,20 +442,19 @@ namespace FourSlash { this.goToPosition(marker.position); } - public goToEachMarker(action: () => void) { - const markers = this.getMarkers(); - assert(markers.length !== 0); - for (const marker of markers) { - this.goToMarker(marker); - action(); + public goToEachMarker(markers: ReadonlyArray, action: (marker: FourSlash.Marker, index: number) => void) { + assert(markers.length); + for (let i = 0; i < markers.length; i++) { + this.goToMarker(markers[i]); + action(markers[i], i); } } public goToEachRange(action: () => void) { const ranges = this.getRanges(); - assert(ranges.length !== 0); + assert(ranges.length); for (const range of ranges) { - this.goToRangeStart(range); + this.selectRange(range); action(); } } @@ -476,6 +482,11 @@ namespace FourSlash { this.selectionEnd = end.position; } + public selectRange(range: Range): void { + this.goToRangeStart(range); + this.selectionEnd = range.end; + } + public moveCaretRight(count = 1) { this.currentCaretPosition += count; this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length); @@ -505,7 +516,7 @@ namespace FourSlash { } } - private raiseError(message: string) { + private raiseError(message: string): never { throw new Error(this.messageAtLastKnownMarker(message)); } @@ -653,7 +664,7 @@ namespace FourSlash { this.verifyGoToXPlain(arg0, endMarkerNames, getDefs); } else if (ts.isArray(arg0)) { - const pairs: ReadonlyArray<[string | string[], string | string[]]> = arg0; + const pairs = arg0 as ReadonlyArray<[string | string[], string | string[]]>; for (const [start, end] of pairs) { this.verifyGoToXPlain(start, end, getDefs); } @@ -793,7 +804,7 @@ namespace FourSlash { } const entries = this.getCompletionListAtCaret().entries; - assert(items.length <= entries.length, `Amount of expected items in completion list [ ${items.length} ] is greater than actual number of items in list [ ${entries.length} ]`); + assert.isTrue(items.length <= entries.length, `Amount of expected items in completion list [ ${items.length} ] is greater than actual number of items in list [ ${entries.length} ]`); ts.zipWith(entries, items, (entry, item) => { assert.equal(entry.name, item, `Unexpected item in completion list`); }); @@ -842,10 +853,10 @@ namespace FourSlash { } } - public verifyCompletionsAt(markerName: string, expected: string[], options?: FourSlashInterface.CompletionsAtOptions) { + public verifyCompletionsAt(markerName: string, expected: ReadonlyArray, options?: FourSlashInterface.CompletionsAtOptions) { this.goToMarker(markerName); - const actualCompletions = this.getCompletionListAtCaret(); + const actualCompletions = this.getCompletionListAtCaret(options); if (!actualCompletions) { this.raiseError(`No completions at position '${this.currentCaretPosition}'.`); } @@ -861,13 +872,24 @@ namespace FourSlash { } ts.zipWith(actual, expected, (completion, expectedCompletion, index) => { - if (completion.name !== expectedCompletion) { + const { name, insertText, replacementSpan } = typeof expectedCompletion === "string" ? { name: expectedCompletion, insertText: undefined, replacementSpan: undefined } : expectedCompletion; + if (completion.name !== name) { this.raiseError(`Expected completion at index ${index} to be ${expectedCompletion}, got ${completion.name}`); } + if (completion.insertText !== insertText) { + this.raiseError(`Expected completion insert text at index ${index} to be ${insertText}, got ${completion.insertText}`); + } + const convertedReplacementSpan = replacementSpan && textSpanFromRange(replacementSpan); + try { + assert.deepEqual(completion.replacementSpan, convertedReplacementSpan); + } + catch { + this.raiseError(`Expected completion replacementSpan at index ${index} to be ${stringify(convertedReplacementSpan)}, got ${stringify(completion.replacementSpan)}`); + } }); } - public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: FourSlashInterface.VerifyCompletionListContainsOptions) { + public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, options?: FourSlashInterface.VerifyCompletionListContainsOptions) { const completions = this.getCompletionListAtCaret(options); if (completions) { this.assertItemInCompletionList(completions.entries, entryId, text, documentation, kind, spanIndex, hasAction, options); @@ -888,7 +910,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(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number, options?: FourSlashInterface.CompletionsAtOptions) { + public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, options?: FourSlashInterface.CompletionsAtOptions) { let replacementSpan: ts.TextSpan; if (spanIndex !== undefined) { replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex); @@ -897,7 +919,7 @@ namespace FourSlash { const completions = this.getCompletionListAtCaret(options); if (completions) { let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source); - filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions; + filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind || (typeof expectedKind === "object" && e.kind === expectedKind.kind)) : filterCompletions; filterCompletions = filterCompletions.filter(entry => { const details = this.getCompletionEntryDetails(entry.name); const documentation = details && ts.displayPartsToString(details.documentation); @@ -947,7 +969,7 @@ namespace FourSlash { public verifyCompletionEntryDetails(entryName: string, expectedText: string, expectedDocumentation?: string, kind?: string, tags?: ts.JSDocTagInfo[]) { const details = this.getCompletionEntryDetails(entryName); - assert.isDefined(details, "no completion entry available"); + assert(details, "no completion entry available"); assert.equal(ts.displayPartsToString(details.displayParts), expectedText, this.assertionMessageAtLastKnownMarker("completion entry details text")); @@ -1082,7 +1104,7 @@ namespace FourSlash { public verifyRangesReferenceEachOther(ranges?: Range[]) { ranges = ranges || this.getRanges(); - assert(ranges.length !== 0); + assert(ranges.length); for (const range of ranges) { this.verifyReferencesOf(range, ranges); } @@ -1368,6 +1390,7 @@ Actual: ${stringify(fullActual)}`); public verifyCurrentParameterIsVariable(isVariable: boolean) { const signature = this.getActiveSignatureHelpItem(); + assert.isOk(signature); assert.equal(isVariable, signature.isVariadic); } @@ -1562,7 +1585,7 @@ Actual: ${stringify(fullActual)}`); } public baselineCurrentFileBreakpointLocations() { - let baselineFile = this.testData.globalOptions[metadataOptionNames.baselineFile]; + let baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile]; if (!baselineFile) { baselineFile = this.activeFile.fileName.replace(this.basePath + "/breakpointValidation", "bpSpan"); baselineFile = baselineFile.replace(ts.Extension.Ts, ".baseline"); @@ -1581,7 +1604,7 @@ Actual: ${stringify(fullActual)}`); const allFourSlashFiles = this.testData.files; for (const file of allFourSlashFiles) { - if (file.fileOptions[metadataOptionNames.emitThisFile] === "true") { + if (file.fileOptions[MetadataOptionNames.emitThisFile] === "true") { // Find a file with the flag emitThisFile turned on emitFiles.push(file); } @@ -1593,7 +1616,7 @@ Actual: ${stringify(fullActual)}`); } Harness.Baseline.runBaseline( - this.testData.globalOptions[metadataOptionNames.baselineFile], + this.testData.globalOptions[MetadataOptionNames.baselineFile], () => { let resultString = ""; // Loop through all the emittedFiles and emit them one by one @@ -1633,7 +1656,7 @@ Actual: ${stringify(fullActual)}`); } public baselineQuickInfo() { - let baselineFile = this.testData.globalOptions[metadataOptionNames.baselineFile]; + let baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile]; if (!baselineFile) { baselineFile = ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline"); } @@ -1801,7 +1824,7 @@ Actual: ${stringify(fullActual)}`); } else if (prevChar === " " && /A-Za-z_/.test(ch)) { /* Completions */ - this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, { includeExternalModuleExports: false }); + this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); } if (i % checkCadence === 0) { @@ -2018,7 +2041,7 @@ Actual: ${stringify(fullActual)}`); const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); if (negative) { - assert(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); + assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); } else { assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); @@ -2243,7 +2266,7 @@ Actual: ${stringify(fullActual)}`); public baselineCurrentFileNameOrDottedNameSpans() { Harness.Baseline.runBaseline( - this.testData.globalOptions[metadataOptionNames.baselineFile], + this.testData.globalOptions[MetadataOptionNames.baselineFile], () => { return this.baselineCurrentFileLocations(pos => this.getNameOrDottedNameSpan(pos)); @@ -2370,13 +2393,14 @@ Actual: ${stringify(fullActual)}`); */ public getAndApplyCodeActions(errorCode?: number, index?: number) { const fileName = this.activeFile.fileName; - this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index); + this.applyCodeActions(this.getCodeFixes(fileName, errorCode), index); } public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) { this.goToMarker(markerName); - const actualCompletion = this.getCompletionListAtCaret({ includeExternalModuleExports: true }).entries.find(e => e.name === options.name && e.source === options.source); + const actualCompletion = this.getCompletionListAtCaret({ includeExternalModuleExports: true, includeInsertTextCompletions: false }).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.`); @@ -2423,6 +2447,17 @@ Actual: ${stringify(fullActual)}`); this.verifyRangeIs(expectedText, includeWhiteSpace); } + public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void { + const { fixId, newFileContent } = options; + const fixIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId); + ts.Debug.assert(ts.contains(fixIds, fixId), "No available code fix has that group id.", () => `Expected '${fixId}'. Available action ids: ${fixIds}`); + const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings); + assert.deepEqual(commands, options.commands); + assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files"); + this.applyChanges(changes); + this.verifyCurrentFileContent(newFileContent); + } + /** * Applies fixes for the errors in fileName and compares the results to * expectedContents after all fixes have been applied. @@ -2435,7 +2470,7 @@ Actual: ${stringify(fullActual)}`); public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) { fileName = fileName ? fileName : this.activeFile.fileName; - this.applyCodeActions(this.getCodeFixActions(fileName)); + this.applyCodeActions(this.getCodeFixes(fileName)); const actualContents: string = this.getFileContent(fileName); if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) { @@ -2445,7 +2480,7 @@ Actual: ${stringify(fullActual)}`); public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) { const fileName = this.activeFile.fileName; - const actions = this.getCodeFixActions(fileName, options.errorCode); + const actions = this.getCodeFixes(fileName, options.errorCode); let index = options.index; if (index === undefined) { if (!(actions && actions.length === 1)) { @@ -2471,7 +2506,7 @@ Actual: ${stringify(fullActual)}`); } private verifyNewContent(options: FourSlashInterface.NewContentOptions) { - if (options.newFileContent) { + if (options.newFileContent !== undefined) { assert(!options.newRangeContent); this.verifyCurrentFileContent(options.newFileContent); } @@ -2484,7 +2519,7 @@ Actual: ${stringify(fullActual)}`); * Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found. * @param fileName Path to file where error should be retrieved from. */ - private getCodeFixActions(fileName: string, errorCode?: number): ts.CodeAction[] { + private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] { const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({ start: diagnostic.start, length: diagnostic.length, @@ -2500,7 +2535,7 @@ Actual: ${stringify(fullActual)}`); }); } - private applyCodeActions(actions: ts.CodeAction[], index?: number): void { + private applyCodeActions(actions: ReadonlyArray, index?: number): void { if (index === undefined) { if (!(actions && actions.length === 1)) { this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`); @@ -2513,8 +2548,10 @@ Actual: ${stringify(fullActual)}`); } } - const changes = actions[index].changes; + this.applyChanges(actions[index].changes); + } + private applyChanges(changes: ReadonlyArray): void { for (const change of changes) { this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); } @@ -2526,7 +2563,7 @@ Actual: ${stringify(fullActual)}`); this.raiseError("Exactly one range should be specified in the testfile."); } - const codeFixes = this.getCodeFixActions(this.activeFile.fileName, errorCode); + const codeFixes = this.getCodeFixes(this.activeFile.fileName, errorCode); if (codeFixes.length === 0) { if (expectedTextArray.length !== 0) { @@ -2865,7 +2902,7 @@ Actual: ${stringify(fullActual)}`); } public verifyCodeFixAvailable(negative: boolean, info: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined) { - const codeFixes = this.getCodeFixActions(this.activeFile.fileName); + const codeFixes = this.getCodeFixes(this.activeFile.fileName); if (negative) { if (codeFixes.length) { @@ -3032,7 +3069,7 @@ Actual: ${stringify(fullActual)}`); } public printAvailableCodeFixes() { - const codeFixes = this.getCodeFixActions(this.activeFile.fileName); + const codeFixes = this.getCodeFixes(this.activeFile.fileName); Harness.IO.log(stringify(codeFixes)); } @@ -3070,7 +3107,7 @@ Actual: ${stringify(fullActual)}`); entryId: ts.Completions.CompletionEntryIdentifier, text: string | undefined, documentation: string | undefined, - kind: string | undefined, + kind: string | undefined | { kind?: string, kindModifiers?: string }, spanIndex: number | undefined, hasAction: boolean | undefined, options: FourSlashInterface.VerifyCompletionListContainsOptions | undefined, @@ -3104,12 +3141,24 @@ Actual: ${stringify(fullActual)}`); } if (kind !== undefined) { - assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + if (typeof kind === "string") { + assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + } + else { + if (kind.kind) { + assert.equal(item.kind, kind.kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + } + if (kind.kindModifiers !== undefined) { + assert.equal(item.kindModifiers, kind.kindModifiers, this.assertionMessageAtLastKnownMarker("completion item kindModifiers for " + entryId)); + } + } } + + if (spanIndex !== undefined) { const span = this.getTextSpanForRangeAtIndex(spanIndex); - assert(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId)); + 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); @@ -3163,8 +3212,7 @@ Actual: ${stringify(fullActual)}`); private getTextSpanForRangeAtIndex(index: number): ts.TextSpan { const ranges = this.getRanges(); if (ranges && ranges.length > index) { - const range = ranges[index]; - return { start: range.start, length: range.end - range.start }; + return textSpanFromRange(ranges[index]); } else { this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length)); @@ -3194,6 +3242,10 @@ Actual: ${stringify(fullActual)}`); } } + function textSpanFromRange(range: FourSlash.Range): ts.TextSpan { + return ts.createTextSpanFromBounds(range.start, range.end); + } + export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) { const content = Harness.IO.readFile(fileName); runFourSlashTestContent(basePath, testType, content, fileName); @@ -3269,12 +3321,21 @@ ${code} // Stuff related to the subfile we're parsing let currentFileContent: string = undefined; let currentFileName = fileName; + let currentFileSymlinks: string[] | undefined; let currentFileOptions: { [s: string]: string } = {}; - function resetLocalData() { + function nextFile() { + const file = parseFileContent(currentFileContent, currentFileName, markerPositions, markers, ranges); + file.fileOptions = currentFileOptions; + file.symlinks = currentFileSymlinks; + + // Store result file + files.push(file); + currentFileContent = undefined; currentFileOptions = {}; currentFileName = fileName; + currentFileSymlinks = undefined; } for (let line of lines) { @@ -3303,8 +3364,7 @@ ${code} const match = optionRegex.exec(line.substr(2)); if (match) { const [key, value] = match.slice(1); - const fileMetadataNamesIndex = fileMetadataNames.indexOf(key); - if (fileMetadataNamesIndex === -1) { + if (!ts.contains(fileMetadataNames, key)) { // Check if the match is already existed in the global options if (globalOptions[key] !== undefined) { throw new Error(`Global option '${key}' already exists`); @@ -3312,24 +3372,22 @@ ${code} globalOptions[key] = value; } else { - if (fileMetadataNamesIndex === fileMetadataNames.indexOf(metadataOptionNames.fileName)) { - // Found an @FileName directive, if this is not the first then create a new subfile - if (currentFileContent) { - const file = parseFileContent(currentFileContent, currentFileName, markerPositions, markers, ranges); - file.fileOptions = currentFileOptions; + switch (key) { + case MetadataOptionNames.fileName: + // Found an @FileName directive, if this is not the first then create a new subfile + if (currentFileContent) { + nextFile(); + } - // Store result file - files.push(file); - - resetLocalData(); - } - - currentFileName = ts.isRootedDiskPath(value) ? value : basePath + "/" + value; - currentFileOptions[key] = value; - } - else { - // Add other fileMetadata flag - currentFileOptions[key] = value; + currentFileName = ts.isRootedDiskPath(value) ? value : basePath + "/" + value; + currentFileOptions[key] = value; + break; + case MetadataOptionNames.symlink: + currentFileSymlinks = ts.append(currentFileSymlinks, value); + break; + default: + // Add other fileMetadata flag + currentFileOptions[key] = value; } } } @@ -3341,19 +3399,13 @@ ${code} else { // Empty line or code line, terminate current subfile if there is one if (currentFileContent) { - const file = parseFileContent(currentFileContent, currentFileName, markerPositions, markers, ranges); - file.fileOptions = currentFileOptions; - - // Store result file - files.push(file); - - resetLocalData(); + nextFile(); } } } // @Filename is the only directive that can be used in a test that contains tsconfig.json file. - if (containTSConfigJson(files)) { + if (files.some(isTsconfig)) { let directive = getNonFileNameOptionInFileList(files); if (!directive) { directive = getNonFileNameOptionInObject(globalOptions); @@ -3372,8 +3424,8 @@ ${code} }; } - function containTSConfigJson(files: FourSlashFile[]): boolean { - return ts.forEach(files, f => f.fileOptions.Filename === "tsconfig.json"); + function isTsconfig(file: FourSlashFile): boolean { + return ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json"; } function getNonFileNameOptionInFileList(files: FourSlashFile[]): string { @@ -3382,7 +3434,7 @@ ${code} function getNonFileNameOptionInObject(optionObject: { [s: string]: string }): string { for (const option in optionObject) { - if (option !== metadataOptionNames.fileName) { + if (option !== MetadataOptionNames.fileName) { return option; } } @@ -3744,8 +3796,11 @@ namespace FourSlashInterface { this.state.goToMarker(name); } - public eachMarker(action: () => void) { - this.state.goToEachMarker(action); + public eachMarker(markers: ReadonlyArray, action: (marker: FourSlash.Marker, index: number) => void): void; + public eachMarker(action: (marker: FourSlash.Marker, index: number) => void): void; + public eachMarker(a: ReadonlyArray | ((marker: FourSlash.Marker, index: number) => void), b?: (marker: FourSlash.Marker, index: number) => void): void { + const markers = typeof a === "function" ? this.state.getMarkers() : a.map(m => this.state.getMarkerByName(m)); + this.state.goToEachMarker(markers, typeof a === "function" ? a : b); } public rangeStart(range: FourSlash.Range) { @@ -3785,6 +3840,10 @@ namespace FourSlashInterface { public select(startMarker: string, endMarker: string) { this.state.select(startMarker, endMarker); } + + public selectRange(range: FourSlash.Range): void { + this.state.selectRange(range); + } } export class VerifyNegatable { @@ -3820,7 +3879,7 @@ namespace FourSlashInterface { // Verifies the completion list contains the specified symbol. The // completion list is brought up if necessary - public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: VerifyCompletionListContainsOptions) { + public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, options?: VerifyCompletionListContainsOptions) { if (typeof entryId === "string") { entryId = { name: entryId, source: undefined }; } @@ -3932,7 +3991,7 @@ namespace FourSlashInterface { super(state); } - public completionsAt(markerName: string, completions: string[], options?: CompletionsAtOptions) { + public completionsAt(markerName: string, completions: ReadonlyArray, options?: CompletionsAtOptions) { this.state.verifyCompletionsAt(markerName, completions, options); } @@ -4143,6 +4202,10 @@ namespace FourSlashInterface { this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index); } + public codeFixAll(options: VerifyCodeFixAllOptions): void { + this.state.verifyCodeFixAll(options); + } + public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: ts.FormatCodeSettings): void { this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, actionName, formattingOptions); } @@ -4552,6 +4615,7 @@ namespace FourSlashInterface { newContent: string; } + export type ExpectedCompletionEntry = string | { name: string, insertText?: string, replacementSpan?: FourSlash.Range }; export interface CompletionsAtOptions extends ts.GetCompletionsAtPositionOptions { isNewIdentifierLocation?: boolean; } @@ -4578,6 +4642,12 @@ namespace FourSlashInterface { commands?: ts.CodeActionCommand[]; } + export interface VerifyCodeFixAllOptions { + fixId: string; + newFileContent: string; + commands: ReadonlyArray<{}>; + } + export interface VerifyRefactorOptions { name: string; actionName: string; diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 0b361f8db38..612224cb312 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -23,60 +23,39 @@ /// /// /// +/// + // Block scoped definitions work poorly for global variables, temporarily enable var /* tslint:disable:no-var-keyword */ -function assert(expr: boolean, msg?: string | (() => string)): void { - if (!expr) { - throw new Error(typeof msg === "string" ? msg : msg()); - } -} -namespace assert { - export function isFalse(expr: boolean, msg = "Expected value to be false."): void { - assert(!expr, msg); - } - export function equal(a: T, b: T, msg = "Expected values to be equal."): void { - assert(a === b, msg); - } - export function notEqual(a: T, b: T, msg = "Expected values to not be equal."): void { - assert(a !== b, msg); - } - export function isDefined(x: {} | null | undefined, msg = "Expected value to be defined."): void { - assert(x !== undefined && x !== null, msg); - } - export function isUndefined(x: {} | null | undefined, msg = "Expected value to be undefined."): void { - assert(x === undefined, msg); - } - export function deepEqual(a: T, b: T, msg?: string): void { - assert(isDeepEqual(a, b), msg || (() => `Expected values to be deeply equal:\nExpected:\n${JSON.stringify(a, undefined, 4)}\nActual:\n${JSON.stringify(b, undefined, 4)}`)); - } - export function lengthOf(a: ReadonlyArray<{}>, length: number, msg = "Expected length to match."): void { - assert(a.length === length, msg); - } - export function throws(cb: () => void, msg = "Expected callback to throw"): void { - let threw = false; - try { - cb(); - } - catch { - threw = true; - } - assert(threw, msg); - } +// 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); }; - function isDeepEqual(a: T, b: T): boolean { - if (a === b) { - return true; + const assertDeepImpl = assert.deepEqual; + assert.deepEqual = (a, b, msg) => { + if (ts.isArray(a) && ts.isArray(b)) { + assertDeepImpl(arrayExtraKeysObject(a), arrayExtraKeysObject(b), "Array extra keys differ"); } - if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) { - return false; + assertDeepImpl(a, b, msg); + + function arrayExtraKeysObject(a: ReadonlyArray<{} | null | undefined>): object { + const obj: { [key: string]: {} | null | undefined } = {}; + for (const key in a) { + if (Number.isNaN(Number(key))) { + obj[key] = a[key]; + } + } + return obj; } - const aKeys = Object.keys(a).sort(); - const bKeys = Object.keys(b).sort(); - return aKeys.length === bKeys.length && aKeys.every((key, i) => bKeys[i] === key && isDeepEqual((a as any)[key], (b as any)[key])); - } + }; } + declare var __dirname: string; // Node-specific var global: NodeJS.Global = Function("return this").call(undefined); @@ -84,7 +63,7 @@ declare var window: {}; declare var XMLHttpRequest: { new(): XMLHttpRequest; }; -interface XMLHttpRequest { +interface XMLHttpRequest { readonly readyState: number; readonly responseText: string; readonly status: number; @@ -389,8 +368,8 @@ namespace Utils { return; } - assert(!!array1, "array1"); - assert(!!array2, "array2"); + assert(array1, "array1"); + assert(array2, "array2"); assert.equal(array1.length, array2.length, "array1.length !== array2.length"); @@ -413,8 +392,8 @@ namespace Utils { return; } - assert(!!node1, "node1"); - assert(!!node2, "node2"); + assert(node1, "node1"); + assert(node2, "node2"); assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos"); assert.equal(node1.end, node2.end, "node1.end !== node2.end"); assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); @@ -444,8 +423,8 @@ namespace Utils { return; } - assert(!!array1, "array1"); - assert(!!array2, "array2"); + assert(array1, "array1"); + assert(array2, "array2"); assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos"); assert.equal(array1.end, array2.end, "array1.end !== array2.end"); assert.equal(array1.length, array2.length, "array1.length !== array2.length"); @@ -895,7 +874,7 @@ namespace Harness { // Cache of lib files from "built/local" let libFileNameSourceFileMap: ts.Map | undefined; - // Cache of lib files from "tests/lib/" + // Cache of lib files from "tests/lib/" const testLibFileNameSourceFileMap = ts.createMap(); const es6TestLibFileNameSourceFileMap = ts.createMap(); @@ -1301,7 +1280,7 @@ namespace Harness { function findResultCodeFile(fileName: string) { const sourceFile = result.program.getSourceFile(fileName); - assert.isDefined(sourceFile, "Program has no source file with name '" + fileName + "'"); + assert(sourceFile, "Program has no source file with name '" + fileName + "'"); // Is this file going to be emitted separately let sourceFileName: string; const outFile = options.outFile || options.out; @@ -1984,7 +1963,7 @@ namespace Harness { const data = testUnitData[i]; if (ts.getBaseFileName(data.name).toLowerCase() === "tsconfig.json") { const configJson = ts.parseJsonText(data.name, data.content); - assert(configJson.endOfFileToken !== undefined); + assert.isTrue(configJson.endOfFileToken !== undefined); let baseDir = ts.normalizePath(ts.getDirectoryPath(data.name)); if (rootDir) { baseDir = ts.getNormalizedAbsolutePath(baseDir, rootDir); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c81875139e5..d51517c159b 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -122,7 +122,7 @@ namespace Harness.LanguageService { getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; } - export class LanguageServiceAdapterHost { + export abstract class LanguageServiceAdapterHost { public typesRegistry: ts.Map | undefined; protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false); @@ -166,6 +166,8 @@ namespace Harness.LanguageService { throw new Error("No script with name '" + fileName + "'"); } + public abstract addSymlink(from: string, target: string): void; + public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ } /** @@ -174,13 +176,16 @@ namespace Harness.LanguageService { */ public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { const script: ScriptInfo = this.getScriptInfo(fileName); - assert(!!script); + assert.isOk(script); + return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); } } /// Native adapter - class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { + class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost { + symlinks = ts.createMap(); + isKnownTypesPackageName(name: string): boolean { return this.typesRegistry && this.typesRegistry.has(name); } @@ -211,13 +216,16 @@ namespace Harness.LanguageService { } directoryExists(dirName: string): boolean { + if (ts.forEachEntry(this.symlinks, (_, key) => ts.forSomeAncestorDirectory(key, ancestor => ancestor === dirName))) { + return true; + } + const fileEntry = this.virtualFileSystem.traversePath(dirName); return fileEntry && fileEntry.isDirectory(); } fileExists(fileName: string): boolean { - const script = this.getScriptSnapshot(fileName); - return script !== undefined; + return this.symlinks.has(fileName) || this.getScriptSnapshot(fileName) !== undefined; } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { return ts.matchFiles(path, extensions, exclude, include, @@ -227,9 +235,19 @@ namespace Harness.LanguageService { (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); } readFile(path: string): string | undefined { + const target = this.symlinks.get(path); + if (target !== undefined) { + return this.readFile(target); + } + const snapshot = this.getScriptSnapshot(path); return snapshot.getText(0, snapshot.getLength()); } + addSymlink(from: string, target: string) { this.symlinks.set(from, target); } + realpath(path: string): string { + const target = this.symlinks.get(path); + return target === undefined ? path : target; + } getTypeRootsVersion() { return 0; } @@ -245,7 +263,7 @@ namespace Harness.LanguageService { constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new NativeLanguageServiceHost(cancellationToken, options); } - getHost() { return this.host; } + getHost(): LanguageServiceAdapterHost { return this.host; } getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } getClassifier(): ts.Classifier { return ts.createClassifier(); } getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); } @@ -258,6 +276,8 @@ namespace Harness.LanguageService { public getModuleResolutionsForFile: (fileName: string) => string; public getTypeReferenceDirectiveResolutionsForFile: (fileName: string) => string; + addSymlink() { return ts.notImplemented(); } + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { super(cancellationToken, options); this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); @@ -359,7 +379,7 @@ namespace Harness.LanguageService { classification: parseInt(result[i + 1]) }; - assert(t.length > 0, "Result length should be greater than 0, got :" + t.length); + assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); position += t.length; } const finalLexState = parseInt(result[result.length - 1]); @@ -502,9 +522,10 @@ namespace Harness.LanguageService { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); } - getCodeFixesAtPosition(): ts.CodeAction[] { + getCodeFixesAtPosition(): never { throw new Error("Not supported on the shim."); } + getCombinedCodeFix = ts.notImplemented; applyCodeActionCommand = ts.notImplemented; getCodeFixDiagnostics(): ts.Diagnostic[] { throw new Error("Not supported on the shim."); diff --git a/src/harness/parallel/host.ts b/src/harness/parallel/host.ts index eca719628c7..59105cd5b08 100644 --- a/src/harness/parallel/host.ts +++ b/src/harness/parallel/host.ts @@ -9,6 +9,8 @@ namespace Harness.Parallel.Host { on(event: "error", listener: (err: Error) => void): this; on(event: "exit", listener: (code: number, signal: string) => void): this; on(event: "message", listener: (message: ParallelClientMessage) => void): this; + kill(signal?: string): void; + currentTasks?: {file: string}[]; // Custom monkeypatch onto child process handle } interface ProgressBarsOptions { @@ -134,6 +136,11 @@ namespace Harness.Parallel.Host { const newPerfData: {[testHash: string]: number} = {}; const workers: ChildProcessPartial[] = []; + const defaultTimeout = globalTimeout !== undefined + ? globalTimeout + : mocha && mocha.suite && mocha.suite._timeout + ? mocha.suite._timeout + : 20000; // 20 seconds 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 @@ -141,6 +148,14 @@ namespace Harness.Parallel.Host { const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); Harness.IO.writeFile(configPath, JSON.stringify(config)); const child = fork(__filename, [`--config="${configPath}"`]); + let currentTimeout = defaultTimeout; + const killChild = () => { + child.kill(); + console.error(`Worker exceeded ${currentTimeout}ms timeout ${child.currentTasks && child.currentTasks.length ? `while running test '${child.currentTasks[0].file}'.` : `during test setup.`}`); + return process.exit(2); + }; + let timer = setTimeout(killChild, currentTimeout); + const timeoutStack: number[] = []; child.on("error", err => { console.error("Unexpected error in child process:"); console.error(err); @@ -160,8 +175,25 @@ namespace Harness.Parallel.Host { Stack: ${data.payload.stack}`); return process.exit(2); } + case "timeout": { + clearTimeout(timer); + if (data.payload.duration === "reset") { + currentTimeout = timeoutStack.pop() || defaultTimeout; + } + else { + timeoutStack.push(currentTimeout); + currentTimeout = data.payload.duration; + } + timer = setTimeout(killChild, currentTimeout); // Reset timeout on timeout update, for when a timeout changes while a suite is executing + break; + } case "progress": case "result": { + clearTimeout(timer); + timer = setTimeout(killChild, currentTimeout); + if (child.currentTasks) { + child.currentTasks.shift(); + } totalPassing += data.payload.passing; if (data.payload.errors.length) { errorResults = errorResults.concat(data.payload.errors); @@ -195,6 +227,7 @@ namespace Harness.Parallel.Host { while (tasks.length && taskList.reduce((p, c) => p + c.size, 0) < chunkSize) { taskList.push(tasks.pop()); } + child.currentTasks = taskList; if (taskList.length === 1) { child.send({ type: "test", payload: taskList[0] }); } @@ -252,18 +285,22 @@ namespace Harness.Parallel.Host { for (const worker of workers) { const payload = batches.pop(); if (payload) { + worker.currentTasks = payload; worker.send({ type: "batch", payload }); } 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.currentTasks = [payload]; worker.send({ type: "test", payload }); } } } else { for (let i = 0; i < workerCount; i++) { - workers[i].send({ type: "test", payload: tasks.pop() }); + const task = tasks.pop(); + workers[i].currentTasks = [task]; + workers[i].send({ type: "test", payload: task }); } } diff --git a/src/harness/parallel/shared.ts b/src/harness/parallel/shared.ts index 2eb7777f828..26b03ac6234 100644 --- a/src/harness/parallel/shared.ts +++ b/src/harness/parallel/shared.ts @@ -10,5 +10,6 @@ namespace Harness.Parallel { export type ErrorInfo = ParallelErrorMessage["payload"] & { name: string[] }; 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; + export type ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: number | "reset" } } | never; + export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage | ParallelTimeoutChangeMessage; } \ No newline at end of file diff --git a/src/harness/parallel/worker.ts b/src/harness/parallel/worker.ts index 013992a8e35..1902ff70b19 100644 --- a/src/harness/parallel/worker.ts +++ b/src/harness/parallel/worker.ts @@ -36,11 +36,28 @@ namespace Harness.Parallel.Worker { }) as Mocha.ITestDefinition; } + function setTimeoutAndExecute(timeout: number | undefined, f: () => void) { + if (timeout !== undefined) { + const timeoutMsg: ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: timeout } }; + process.send(timeoutMsg); + } + f(); + if (timeout !== undefined) { + // Reset timeout + const timeoutMsg: ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: "reset" } }; + process.send(timeoutMsg); + } + } + function executeSuiteCallback(name: string, callback: MochaCallback) { + let timeout: number; const fakeContext: Mocha.ISuiteCallbackContext = { retries() { return this; }, slow() { return this; }, - timeout() { return this; }, + timeout(n) { + timeout = n as number; + return this; + }, }; namestack.push(name); let beforeFunc: Callable; @@ -71,7 +88,10 @@ namespace Harness.Parallel.Worker { finally { beforeFunc = undefined; } - testList.forEach(({ name, callback, kind }) => executeCallback(name, callback, kind)); + + setTimeoutAndExecute(timeout, () => { + testList.forEach(({ name, callback, kind }) => executeCallback(name, callback, kind)); + }); try { if (afterFunc) { @@ -103,9 +123,15 @@ namespace Harness.Parallel.Worker { } function executeTestCallback(name: string, callback: MochaCallback) { + let timeout: number; const fakeContext: Mocha.ITestCallbackContext = { skip() { return this; }, - timeout() { return this; }, + timeout(n) { + timeout = n as number; + const timeoutMsg: ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: timeout } }; + process.send(timeoutMsg); + return this; + }, retries() { return this; }, slow() { return this; }, }; @@ -131,6 +157,10 @@ namespace Harness.Parallel.Worker { } finally { namestack.pop(); + if (timeout !== undefined) { + const timeoutMsg: ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: "reset" } }; + process.send(timeoutMsg); + } } passing++; } @@ -157,6 +187,10 @@ namespace Harness.Parallel.Worker { } finally { namestack.pop(); + if (timeout !== undefined) { + const timeoutMsg: ParallelTimeoutChangeMessage = { type: "timeout", payload: { duration: "reset" } }; + process.send(timeoutMsg); + } } if (!completed) { errors.push({ error: "Test completes asynchronously, which is unsupported by the parallel harness", stack: "", name: [...namestack] }); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index c617cc7a0a3..f8d4d171f01 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -186,7 +186,7 @@ class ProjectRunner extends RunnerBase { getCanonicalFileName: Harness.Compiler.getCanonicalFileName, useCaseSensitiveFileNames: () => Harness.IO.useCaseSensitiveFileNames(), getNewLine: () => Harness.IO.newLine(), - fileExists: fileName => fileName === Harness.Compiler.defaultLibFileName || getSourceFileText(fileName) !== undefined, + fileExists: fileName => fileName === Harness.Compiler.defaultLibFileName || getSourceFileText(fileName) !== undefined, readFile: fileName => Harness.IO.readFile(fileName), getDirectories: path => Harness.IO.getDirectories(path) }; diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 2bd098b742d..76d65090f02 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -100,6 +100,7 @@ interface TestConfig { runners?: string[]; runUnitTests?: boolean; noColors?: boolean; + timeout?: number; } interface TaskSet { @@ -108,12 +109,16 @@ interface TaskSet { } let configOption: string; +let globalTimeout: number; function handleTestConfig() { if (testConfigContent !== "") { const testConfig = JSON.parse(testConfigContent); if (testConfig.light) { Harness.lightMode = true; } + if (testConfig.timeout) { + globalTimeout = testConfig.timeout; + } runUnitTests = testConfig.runUnitTests; if (testConfig.workerCount) { workerCount = +testConfig.workerCount; diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index 5ab8407067e..606d4e67acd 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -285,10 +285,10 @@ namespace Harness.SourceMapRecorder { } export function recordNewSourceFileSpan(sourceMapSpan: ts.SourceMapSpan, newSourceFileCode: string) { - assert(spansOnSingleLine.length === 0 || spansOnSingleLine[0].sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine, "new file source map span should be on new line. We currently handle only that scenario"); + assert.isTrue(spansOnSingleLine.length === 0 || spansOnSingleLine[0].sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine, "new file source map span should be on new line. We currently handle only that scenario"); recordSourceMapSpan(sourceMapSpan); - assert(spansOnSingleLine.length === 1); + assert.isTrue(spansOnSingleLine.length === 1); sourceMapRecorder.WriteLine("-------------------------------------------------------------------"); sourceMapRecorder.WriteLine("emittedFile:" + jsFile.fileName); sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]); @@ -331,7 +331,7 @@ namespace Harness.SourceMapRecorder { function getMarkerId(markerIndex: number) { let markerId = ""; if (spanMarkerContinues) { - assert(markerIndex === 0); + assert.isTrue(markerIndex === 0); markerId = "1->"; } else { diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 7353f3f7efc..25642ab5179 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -5,7 +5,7 @@ "outFile": "../../built/local/run.js", "declaration": false, "types": [ - "node", "mocha" + "node", "mocha", "chai" ], "lib": [ "es6", @@ -108,6 +108,7 @@ "./unittests/reuseProgramStructure.ts", "./unittests/moduleResolution.ts", "./unittests/tsconfigParsing.ts", + "./unittests/asserts.ts", "./unittests/builder.ts", "./unittests/commandLineParsing.ts", "./unittests/configurationExtension.ts", diff --git a/src/harness/unittests/asserts.ts b/src/harness/unittests/asserts.ts new file mode 100644 index 00000000000..ec274c44c22 --- /dev/null +++ b/src/harness/unittests/asserts.ts @@ -0,0 +1,11 @@ +/// + +namespace ts { + describe("assert", () => { + it("deepEqual", () => { + assert.throws(() => assert.deepEqual(createNodeArray([createIdentifier("A")]), createNodeArray([createIdentifier("B")]))); + assert.throws(() => assert.deepEqual(createNodeArray([], /*hasTrailingComma*/ true), createNodeArray([], /*hasTrailingComma*/ false))); + assert.deepEqual(createNodeArray([createIdentifier("A")], /*hasTrailingComma*/ true), createNodeArray([createIdentifier("A")], /*hasTrailingComma*/ true)); + }); + }); +} diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index bfdeb1a4067..a5d880fa551 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -43,7 +43,7 @@ namespace ts { }); }); - function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { + function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { const builder = createBuilder({ getCanonicalFileName: identity, computeHash: identity diff --git a/src/harness/unittests/commandLineParsing.ts b/src/harness/unittests/commandLineParsing.ts index 5f0cc2acc78..050178f63b3 100644 --- a/src/harness/unittests/commandLineParsing.ts +++ b/src/harness/unittests/commandLineParsing.ts @@ -12,7 +12,7 @@ namespace ts { const parsedErrors = parsed.errors; const expectedErrors = expectedParsedCommandLine.errors; - assert(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); + assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); for (let i = 0; i < parsedErrors.length; i++) { const parsedError = parsedErrors[i]; const expectedError = expectedErrors[i]; @@ -23,7 +23,7 @@ namespace ts { const parsedFileNames = parsed.fileNames; const expectedFileNames = expectedParsedCommandLine.fileNames; - assert(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); + assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); for (let i = 0; i < parsedFileNames.length; i++) { const parsedFileName = parsedFileNames[i]; const expectedFileName = expectedFileNames[i]; @@ -60,7 +60,7 @@ namespace ts { assertParseResult(["--lib", "es5,invalidOption", "0.ts"], { errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, @@ -263,7 +263,7 @@ namespace ts { assertParseResult(["--lib", "es5,", "es7", "0.ts"], { errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, @@ -283,7 +283,7 @@ namespace ts { assertParseResult(["--lib", "es5, ", "es7", "0.ts"], { errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 808e03a0689..a64d7bdd2a5 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -25,7 +25,7 @@ namespace ts.projectSystem { const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort(); - assert( + assert.isTrue( arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); } @@ -563,7 +563,7 @@ namespace ts.projectSystem { session.executeCommand(compileFileRequest); const expectedEmittedFileName = "/a/b/f1.js"; - assert(host.fileExists(expectedEmittedFileName)); + assert.isTrue(host.fileExists(expectedEmittedFileName)); assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); }); @@ -600,11 +600,11 @@ namespace ts.projectSystem { session.executeCommand(emitRequest); const expectedOutFileName = "/a/b/dist.js"; - assert(host.fileExists(expectedOutFileName)); + assert.isTrue(host.fileExists(expectedOutFileName)); const outFileContent = host.readFile(expectedOutFileName); - assert(outFileContent.indexOf(file1.content) !== -1); - assert(outFileContent.indexOf(file2.content) === -1); - assert(outFileContent.indexOf(file3.content) === -1); + assert.isTrue(outFileContent.indexOf(file1.content) !== -1); + 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", () => { @@ -634,19 +634,19 @@ namespace ts.projectSystem { // Verify js file const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; - assert(host.fileExists(expectedOutFileName)); + 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(host.fileExists(expectedMapFileName)); + assert.isTrue(host.fileExists(expectedMapFileName)); const mapFileContent = host.readFile(expectedMapFileName); verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); function verifyContentHasString(content: string, str: string) { - assert(stringContains(content, str), `Expected "${content}" to have "${str}"`); + assert.isTrue(stringContains(content, str), `Expected "${content}" to have "${str}"`); } }); }); diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index ebe5b631059..5a1b155fbcf 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -113,7 +113,7 @@ namespace ts { const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, testContents); function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) { - assert(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); + assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); for (let i = 0; i < actual.length; i++) { const actualError = actual[i]; const expectedError = expected[i]; diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index 9f5ca179efd..0bad2ba1e3a 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -16,7 +16,7 @@ namespace ts { assert.equal(parsedCompilerOptions, expectedCompilerOptions); const expectedErrors = expectedResult.errors; - assert(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); for (let i = 0; i < actualErrors.length; i++) { const actualError = actualErrors[i]; const expectedError = expectedErrors[i]; @@ -42,15 +42,15 @@ namespace ts { const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); const expectedErrors = expectedResult.errors; - assert(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); for (let i = 0; i < actualErrors.length; i++) { const actualError = actualErrors[i]; const expectedError = expectedErrors[i]; assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); - assert.isDefined(actualError.file); - assert(actualError.start > 0); - assert(actualError.length > 0); + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); } } @@ -266,7 +266,7 @@ namespace ts { file: undefined, start: 0, length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category }] @@ -297,7 +297,7 @@ namespace ts { file: undefined, start: 0, length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category }] @@ -328,7 +328,7 @@ namespace ts { file: undefined, start: 0, length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category }] @@ -359,7 +359,7 @@ namespace ts { file: undefined, start: 0, length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.asynciterable'.", + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'esnext.array', 'esnext.asynciterable', 'esnext.promise'.", code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category }] diff --git a/src/harness/unittests/convertTypeAcquisitionFromJson.ts b/src/harness/unittests/convertTypeAcquisitionFromJson.ts index ddeec6b9869..be1ada10a97 100644 --- a/src/harness/unittests/convertTypeAcquisitionFromJson.ts +++ b/src/harness/unittests/convertTypeAcquisitionFromJson.ts @@ -17,16 +17,16 @@ namespace ts { function verifyErrors(actualErrors: Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { const expectedErrors = expectedResult.errors; - assert(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); for (let i = 0; i < actualErrors.length; i++) { const actualError = actualErrors[i]; const expectedError = expectedErrors[i]; assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); if (hasLocation) { - assert.isDefined(actualError.file); - assert(actualError.start > 0); - assert(actualError.length > 0); + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); } } } diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index 61ffbd01a63..71b550cba8f 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -262,6 +262,18 @@ namespace N { // Force this test to be TS-only y = [#|this.x|]; } }`); + + // TODO (https://github.com/Microsoft/TypeScript/issues/20727): the extracted constant should have a type annotation. + testExtractConstant("extractConstant_ContextualType", ` +interface I { a: 1 | 2 | 3 } +let i: I = [#|{ a: 1 }|]; +`); + + testExtractConstant("extractConstant_ContextualType_Lambda", ` +const myObj: { member(x: number, y: string): void } = { + member: [#|(x, y) => x + y|], +} +`); }); function testExtractConstant(caption: string, text: string) { diff --git a/src/harness/unittests/extractRanges.ts b/src/harness/unittests/extractRanges.ts index 08bae357f0e..493c9639c3d 100644 --- a/src/harness/unittests/extractRanges.ts +++ b/src/harness/unittests/extractRanges.ts @@ -39,7 +39,7 @@ namespace ts { assert.equal(end, expectedRange.end, "incorrect end of range"); } else { - assert(!result.targetRange, `expected range to extract to be undefined`); + assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); } } diff --git a/src/harness/unittests/hostNewLineSupport.ts b/src/harness/unittests/hostNewLineSupport.ts index ac5031ed56b..95a05f101f7 100644 --- a/src/harness/unittests/hostNewLineSupport.ts +++ b/src/harness/unittests/hostNewLineSupport.ts @@ -47,7 +47,7 @@ namespace 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.isDefined(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 to find appropriate newlines"); assert(!result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); } diff --git a/src/harness/unittests/incrementalParser.ts b/src/harness/unittests/incrementalParser.ts index 2facc4aa152..c71b89d3da6 100644 --- a/src/harness/unittests/incrementalParser.ts +++ b/src/harness/unittests/incrementalParser.ts @@ -66,7 +66,7 @@ namespace ts { assertSameDiagnostics(newTree, incrementalNewTree); // There should be no reused nodes between two trees that are fully parsed. - assert(reusedElements(oldTree, newTree) === 0); + assert.isTrue(reusedElements(oldTree, newTree) === 0); assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName"); assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text"); diff --git a/src/harness/unittests/jsDocParsing.ts b/src/harness/unittests/jsDocParsing.ts index 51647c46368..b7215f5ea35 100644 --- a/src/harness/unittests/jsDocParsing.ts +++ b/src/harness/unittests/jsDocParsing.ts @@ -7,7 +7,7 @@ namespace ts { function parsesCorrectly(name: string, content: string) { it(name, () => { const typeAndDiagnostics = ts.parseJSDocTypeExpressionForTests(content); - assert(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); + assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json", () => Utils.sourceFileToJSON(typeAndDiagnostics.jsDocTypeExpression.type)); @@ -17,7 +17,7 @@ namespace ts { function parsesIncorrectly(name: string, content: string) { it(name, () => { const type = ts.parseJSDocTypeExpressionForTests(content); - assert(!type || type.diagnostics.length > 0); + assert.isTrue(!type || type.diagnostics.length > 0); }); } @@ -106,7 +106,7 @@ namespace ts { function parsesIncorrectly(name: string, content: string) { it(name, () => { const type = parseIsolatedJSDocComment(content); - assert(!type || type.diagnostics.length > 0); + assert.isTrue(!type || type.diagnostics.length > 0); }); } diff --git a/src/harness/unittests/languageService.ts b/src/harness/unittests/languageService.ts index a24a124e27c..f14b9daad82 100644 --- a/src/harness/unittests/languageService.ts +++ b/src/harness/unittests/languageService.ts @@ -40,12 +40,9 @@ export function Component(x: Config): any;` getDefaultLibFileName(options) { return ts.getDefaultLibFilePath(options); }, - fileExists: noop as any, - readFile: noop as any, - readDirectory: noop as any, }); const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position - assert.isDefined(definitions); + expect(definitions).to.exist; // tslint:disable-line no-unused-expression }); }); } \ No newline at end of file diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index f2c2369ef37..17a0db04fdf 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -50,6 +50,7 @@ namespace ts { "/dev/x/b.ts", "/dev/x/y/a.ts", "/dev/x/y/b.ts", + "/dev/q/a/c/b/d.ts", "/dev/js/a.js", "/dev/js/b.js", ]); @@ -1171,13 +1172,17 @@ namespace ts { }; const expected: ts.ParsedCommandLine = { options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 11, ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**/*"), - ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + errors: [], + fileNames: [ + "c:/dev/x/a.ts", + "c:/dev/x/aa.ts", + "c:/dev/x/b.ts", + "c:/dev/x/y/a.ts", + "c:/dev/x/y/b.ts", ], - fileNames: [], - wildcardDirectories: {} + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); @@ -1192,13 +1197,9 @@ namespace ts { }; const expected: ts.ParsedCommandLine = { options: {}, - errors: [ - createDiagnosticForConfigFile(json, 34, 9, ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**") - ], + errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", "c:/dev/z/a.ts" ], wildcardDirectories: { @@ -1426,5 +1427,60 @@ namespace ts { }); }); }); + + describe("exclude or include patterns which start with **", () => { + it("can exclude dirs whose pattern starts with **", () => { + const json = { + exclude: [ + "**/x" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/A.ts", + "/dev/B.ts", + "/dev/a.ts", + "/dev/b.ts", + "/dev/c.d.ts", + "/dev/q/a/c/b/d.ts", + "/dev/z/a.ts", + "/dev/z/aba.ts", + "/dev/z/abz.ts", + "/dev/z/b.ts", + "/dev/z/bba.ts", + "/dev/z/bbz.ts", + ], + wildcardDirectories: { + "/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + it("can include dirs whose pattern starts with **", () => { + const json = { + include: [ + "**/x", + "**/a/**/b" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/x/a.ts", + "/dev/x/b.ts", + "/dev/x/y/a.ts", + "/dev/x/y/b.ts", + "/dev/q/a/c/b/d.ts", + ], + wildcardDirectories: { + "/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + }); }); } diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index f77030aa183..0dd016b2f22 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -4,9 +4,9 @@ namespace ts { export function checkResolvedModule(expected: ResolvedModuleFull, actual: ResolvedModuleFull): boolean { if (!expected === !actual) { if (expected) { - assert(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); - assert(expected.extension === actual.extension, `'ext': expected '${expected.extension}' to be equal to '${actual.extension}'`); - assert(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); + assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); + assert.isTrue(expected.extension === actual.extension, `'ext': expected '${expected.extension}' to be equal to '${actual.extension}'`); + assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); } return true; } @@ -14,7 +14,7 @@ namespace ts { } export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void { - assert(actual.resolvedModule !== undefined, "module should be resolved"); + assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved"); checkResolvedModule(actual.resolvedModule, expectedResolvedModule); assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations); } @@ -58,7 +58,7 @@ namespace ts { realpath, directoryExists: path => directories.has(path), fileExists: path => { - assert(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); + assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); return map.has(path); } }; @@ -313,7 +313,7 @@ namespace ts { const host = createModuleResolutionHost(/*hasDirectoryExists*/ true, { name: realFileName, symlinks: [symlinkFileName] }); const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host); const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName; - checkResolvedModule(resolution.resolvedModule, { resolvedFileName, isExternalLibraryImport: true, extension: Extension.Dts }); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true)); }); } }); @@ -338,7 +338,7 @@ namespace ts { const path = normalizePath(combinePaths(currentDirectory, fileName)); return files.has(path); }, - readFile: notImplemented + readFile: notImplemented, }; const program = createProgram(rootFiles, options, host); @@ -351,7 +351,7 @@ namespace ts { // try to get file using a relative name for (const relativeFileName of relativeNamesToCheck) { - assert(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); + assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); } } @@ -426,7 +426,7 @@ export = C; const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); return files.has(path); }, - readFile: notImplemented + readFile: notImplemented, }; const program = createProgram(rootFiles, options, host); const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); @@ -441,7 +441,7 @@ export = C; "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }); - test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); + test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { @@ -449,7 +449,7 @@ export = C; "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }); - test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports)", () => { @@ -457,7 +457,7 @@ export = C; "/a/b/c.ts": `import {x} from "D"`, "/a/b/d.ts": "export var x" }); - test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { @@ -465,7 +465,7 @@ export = C; "moduleA.ts": `import {x} from "./ModuleB"`, "moduleB.ts": "export var x" }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); }); it("should fail when two files exist on disk that differs only in casing", () => { @@ -474,7 +474,7 @@ export = C; "/a/b/D.ts": "export var x", "/a/b/d.ts": "export var y" }); - test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); + test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); }); it("should fail when module name in 'require' calls has inconsistent casing", () => { @@ -483,7 +483,7 @@ export = C; "moduleB.ts": `import a = require("./moduleC")`, "moduleC.ts": "export var x" }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { @@ -496,7 +496,7 @@ import a = require("./moduleA"); import b = require("./moduleB"); ` }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { const files = createMapFromTemplate({ @@ -508,7 +508,7 @@ import a = require("./moduleA"); import b = require("./moduleB"); ` }); - test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); + test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); }); }); @@ -1067,14 +1067,14 @@ import b = require("./moduleB"); readFile: fileName => { const file = sourceFiles.get(fileName); return file && file.text; - } + }, }; const program1 = createProgram(names, {}, compilerHost); const diagnostics1 = program1.getFileProcessingDiagnostics().getDiagnostics(); assert.equal(diagnostics1.length, 1, "expected one diagnostic"); createProgram(names, {}, compilerHost, program1); - assert(program1.structureIsReused === StructureIsReused.Completely); + assert.isTrue(program1.structureIsReused === StructureIsReused.Completely); const diagnostics2 = program1.getFileProcessingDiagnostics().getDiagnostics(); assert.equal(diagnostics2.length, 1, "expected one diagnostic"); assert.equal(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 8d4e8a246b9..2a7f6d24ea7 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -6,10 +6,10 @@ namespace ts { const map = arrayToSet(expected) as Map; for (const missing of missingPaths) { const value = map.get(missing); - assert(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); + assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); map.set(missing, false); } - const notFound = mapDefinedIter(map.keys(), k => map.get(k) === true ? k : undefined); + const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.get(k) === true ? k : undefined)); assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); } diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index d146b3af080..dae465a3ef3 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -5,7 +5,7 @@ namespace ts.projectSystem { describe("Project errors", () => { function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: ReadonlyArray): void { - assert(projectFiles !== undefined, "missing project files"); + assert.isTrue(projectFiles !== undefined, "missing project files"); checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); } @@ -15,7 +15,7 @@ namespace ts.projectSystem { for (let i = 0; i < errors.length; i++) { const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n"); const expectedMessage = expectedErrors[i]; - assert(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); } } } @@ -24,7 +24,7 @@ namespace ts.projectSystem { assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); if (expectedErrors.length) { zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { - assert(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); }); } } @@ -137,13 +137,13 @@ namespace ts.projectSystem { { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); - assert(configuredProject !== undefined, "should find configured project"); + assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, [ "'{' expected." ]); - assert.isDefined(projectErrors[0].file); + assert.isNotNull(projectErrors[0].file); assert.equal(projectErrors[0].file.fileName, corruptedConfig.path); } // fix config and trigger watcher @@ -151,7 +151,7 @@ namespace ts.projectSystem { { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); - assert(configuredProject !== undefined, "should find configured project"); + assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, []); @@ -182,7 +182,7 @@ namespace ts.projectSystem { { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); - assert(configuredProject !== undefined, "should find configured project"); + assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, []); @@ -192,13 +192,13 @@ namespace ts.projectSystem { { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); - assert(configuredProject !== undefined, "should find configured project"); + assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, [ "'{' expected." ]); - assert.isDefined(projectErrors[0].file); + assert.isNotNull(projectErrors[0].file); assert.equal(projectErrors[0].file.fileName, corruptedConfig.path); } }); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 45fe7383c31..4a76a479b18 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -193,14 +193,14 @@ namespace ts { function checkCache(caption: string, program: Program, fileName: string, expectedContent: Map, getCache: (f: SourceFile) => Map, entryChecker: (expected: T, original: T) => boolean): void { const file = program.getSourceFile(fileName); - assert(file !== undefined, `cannot find file ${fileName}`); + assert.isTrue(file !== undefined, `cannot find file ${fileName}`); const cache = getCache(file); if (expectedContent === undefined) { - assert(cache === undefined, `expected ${caption} to be undefined`); + assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); } else { - assert(cache !== undefined, `expected ${caption} to be set`); - assert(mapsAreEqual(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); + assert.isTrue(cache !== undefined, `expected ${caption} to be set`); + assert.isTrue(mapsAreEqual(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); } } @@ -329,7 +329,7 @@ namespace ts { const options: CompilerOptions = { target, noLib: true }; const program1 = newProgram(files, ["a.ts"], options); - assert(program1.getMissingFilePaths().length !== 0); + assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); const program2 = updateProgram(program1, ["a.ts"], options, noop); assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); @@ -341,7 +341,7 @@ namespace ts { const options: CompilerOptions = { target, noLib: true }; const program1 = newProgram(files, ["a.ts"], options); - assert(program1.getMissingFilePaths().length !== 0); + assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts); @@ -389,6 +389,19 @@ namespace ts { checkResolvedModulesCache(program4, "a.ts", createMapFromTemplate({ b: createResolvedModule("b.ts"), c: undefined })); }); + it("set the resolvedImports after re-using an ambient external module declaration", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const options: CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = newProgram(files, ["/a.ts"], options); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateProgram('import * as aa from "a";'); + }); + assert.isDefined(program2.getSourceFile("/a.ts").resolvedModules.get("a"), "'a' is not an unresolved module after re-use"); + }); + it("resolved type directives cache follows type directives", () => { const files = [ { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, @@ -860,7 +873,7 @@ namespace ts { updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); }); assert.equal(program1.structureIsReused, StructureIsReused.Not); - assert.lengthOf(program2.getSemanticDiagnostics(), 0); + assert.deepEqual(program2.getSemanticDiagnostics(), []); }); }); }); @@ -888,7 +901,7 @@ namespace ts { /*hasInvalidatedResolution*/ returnFalse, /*hasChangedAutomaticTypeDirectiveNames*/ false ); - assert(actual); + assert.isTrue(actual); } function duplicate(options: CompilerOptions): CompilerOptions; diff --git a/src/harness/unittests/services/colorization.ts b/src/harness/unittests/services/colorization.ts index b03285d3333..17d4132e9e5 100644 --- a/src/harness/unittests/services/colorization.ts +++ b/src/harness/unittests/services/colorization.ts @@ -50,7 +50,7 @@ describe("Colorization", () => { const actualEntry = getEntryAtPosition(result, actualEntryPosition); - assert.isDefined(actualEntry, "Could not find classification entry for '" + expectedEntry.value + "' at position: " + actualEntryPosition); + assert(actualEntry, "Could not find classification entry for '" + expectedEntry.value + "' at position: " + actualEntryPosition); assert.equal(actualEntry.classification, expectedEntry.classification, "Classification class does not match expected. Expected: " + ts.TokenClass[expectedEntry.classification] + ", Actual: " + ts.TokenClass[actualEntry.classification]); assert.equal(actualEntry.length, expectedEntry.value.length, "Classification length does not match expected. Expected: " + ts.TokenClass[expectedEntry.value.length] + ", Actual: " + ts.TokenClass[actualEntry.length]); } diff --git a/src/harness/unittests/services/patternMatcher.ts b/src/harness/unittests/services/patternMatcher.ts index 62946afde37..fef382e8a3b 100644 --- a/src/harness/unittests/services/patternMatcher.ts +++ b/src/harness/unittests/services/patternMatcher.ts @@ -140,25 +140,25 @@ describe("PatternMatcher", () => { it("PreferCaseSensitiveCamelCaseMatchToLongPattern1", () => { const match = getFirstMatch("FogBar", "FBB"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("PreferCaseSensitiveCamelCaseMatchToLongPattern2", () => { const match = getFirstMatch("FogBar", "FoooB"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("CamelCaseMatchPartiallyUnmatched", () => { const match = getFirstMatch("FogBarBaz", "FZ"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("CamelCaseMatchCompletelyUnmatched", () => { const match = getFirstMatch("FogBarBaz", "ZZ"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("TwoUppercaseCharacters", () => { @@ -220,7 +220,7 @@ describe("PatternMatcher", () => { it("PreferCaseSensitiveMiddleUnderscore3", () => { const match = getFirstMatch("Fog_Bar", "F__B"); - assert(undefined === match); + assert.isTrue(undefined === match); }); it("PreferCaseSensitiveMiddleUnderscore4", () => { @@ -264,25 +264,25 @@ describe("PatternMatcher", () => { it("AllLowerPattern1", () => { const match = getFirstMatch("FogBarChangedEventArgs", "changedeventargs"); - assert(undefined !== match); + assert.isTrue(undefined !== match); }); it("AllLowerPattern2", () => { const match = getFirstMatch("FogBarChangedEventArgs", "changedeventarrrgh"); - assert(undefined === match); + assert.isTrue(undefined === match); }); it("AllLowerPattern3", () => { const match = getFirstMatch("ABCDEFGH", "bcd"); - assert(undefined !== match); + assert.isTrue(undefined !== match); }); it("AllLowerPattern4", () => { const match = getFirstMatch("AbcdefghijEfgHij", "efghij"); - assert(undefined === match); + assert.isTrue(undefined === match); }); }); @@ -370,13 +370,13 @@ describe("PatternMatcher", () => { it("BlankPattern", () => { const matches = getAllMatches("AddMetadataReference", ""); - assert(matches === undefined); + assert.isTrue(matches === undefined); }); it("WhitespaceOnlyPattern", () => { const matches = getAllMatches("AddMetadataReference", " "); - assert(matches === undefined); + assert.isTrue(matches === undefined); }); it("EachWordSeparately1", () => { @@ -403,13 +403,13 @@ describe("PatternMatcher", () => { it("MixedCasing", () => { const matches = getAllMatches("AddMetadataReference", "mEta"); - assert(matches === undefined); + assert.isTrue(matches === undefined); }); it("MixedCasing2", () => { const matches = getAllMatches("AddMetadataReference", "Data"); - assert(matches === undefined); + assert.isTrue(matches === undefined); }); it("AsteriskSplit", () => { @@ -421,7 +421,7 @@ describe("PatternMatcher", () => { it("LowercaseSubstring1", () => { const matches = getAllMatches("Operator", "a"); - assert(matches === undefined); + assert.isTrue(matches === undefined); }); it("LowercaseSubstring2", () => { @@ -441,7 +441,7 @@ describe("PatternMatcher", () => { it("DottedPattern2", () => { const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "C.Q"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("DottedPattern3", () => { @@ -464,13 +464,13 @@ describe("PatternMatcher", () => { it("DottedPattern6", () => { const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "F.F.B.B.Quux"); - assert(match === undefined); + assert.isTrue(match === undefined); }); it("DottedPattern7", () => { let match = getFirstMatch("UIElement", "UIElement"); match = getFirstMatch("GetKeyword", "UIElement"); - assert(match === undefined); + assert.isTrue(match === undefined); }); }); @@ -508,8 +508,8 @@ describe("PatternMatcher", () => { } function assertInRange(val: number, low: number, high: number) { - assert(val >= low); - assert(val <= high); + assert.isTrue(val >= low); + assert.isTrue(val <= high); } function verifyBreakIntoCharacterSpans(original: string, ...parts: string[]): void { @@ -521,6 +521,6 @@ describe("PatternMatcher", () => { } function assertContainsKind(kind: ts.PatternMatchKind, results: ts.PatternMatch[]) { - assert(ts.forEach(results, r => r.kind === kind)); + assert.isTrue(ts.forEach(results, r => r.kind === kind)); } }); diff --git a/src/harness/unittests/services/preProcessFile.ts b/src/harness/unittests/services/preProcessFile.ts index acbb8edd67c..1e13bc3e345 100644 --- a/src/harness/unittests/services/preProcessFile.ts +++ b/src/harness/unittests/services/preProcessFile.ts @@ -18,7 +18,7 @@ describe("PreProcessFile:", () => { return; } if (!expected) { - assert(false, `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + assert.isTrue(false, `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); } assert.equal(actual.length, expected.length, `[${kind}] Actual array's length does not match expected length. Expected files: ${JSON.stringify(expected)}, actual files: ${JSON.stringify(actual)}`); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 9912294d1d2..f46e173716c 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -1,5 +1,7 @@ /// +const expect: typeof _chai.expect = _chai.expect; + namespace ts.server { let lastWrittenToHost: string; const mockHost: ServerHost = { @@ -80,7 +82,7 @@ namespace ts.server { } }; - assert.throws(() => session.executeCommand(req)); + expect(() => session.executeCommand(req)).to.throw(); }); it("should output an error response when a command does not exist", () => { const req: protocol.Request = { @@ -99,7 +101,7 @@ namespace ts.server { request_seq: 0, success: false }; - assert.deepEqual(lastSent, expected); + expect(lastSent).to.deep.equal(expected); }); it("should return a tuple containing the response and if a response is required on success", () => { const req: protocol.ConfigureRequest = { @@ -114,18 +116,17 @@ namespace ts.server { } }; - assert.deepEqual(session.executeCommand(req), { + expect(session.executeCommand(req)).to.deep.equal({ responseRequired: false }); - const expected: protocol.Response = { + expect(lastSent).to.deep.equal({ command: CommandNames.Configure, type: "response", success: true, request_seq: 0, seq: 0, body: undefined - }; - assert.deepEqual(lastSent, expected); + }); }); it("should handle literal types in request", () => { const configureRequest: protocol.ConfigureRequest = { @@ -169,6 +170,19 @@ namespace ts.server { allowNonTsExtensions: true // injected by tsserver }); }); + + it("Status request gives ts.version", () => { + const req: protocol.StatusRequest = { + command: CommandNames.Status, + seq: 0, + type: "request" + }; + + const expected: protocol.StatusResponseBody = { + version: ts.version + }; + assert.deepEqual(session.executeCommand(req).response, expected); + }); }); describe("onMessage", () => { @@ -220,6 +234,7 @@ namespace ts.server { CommandNames.Saveto, CommandNames.SignatureHelp, CommandNames.SignatureHelpFull, + CommandNames.Status, CommandNames.TypeDefinition, CommandNames.ProjectInfo, CommandNames.ReloadProjects, @@ -296,15 +311,14 @@ namespace ts.server { session.onMessage(JSON.stringify(req)); - const expected: protocol.ConfigureResponse = { + expect(lastSent).to.deep.equal({ command: CommandNames.Configure, type: "response", success: true, request_seq: 0, seq: 0, body: undefined - }; - assert.deepEqual(lastSent, expected); + }); }); }); @@ -316,9 +330,9 @@ namespace ts.server { const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; session.send = Session.prototype.send; - assert.isDefined(session.send); - session.send(msg); - assert.equal(lastWrittenToHost, resultMsg); + assert(session.send); + expect(session.send(msg)).to.not.exist; // tslint:disable-line no-unused-expression + expect(lastWrittenToHost).to.equal(resultMsg); }); }); @@ -335,7 +349,11 @@ namespace ts.server { session.addProtocolHandler(command, () => result); - assert.deepEqual(session.executeCommand({ command, seq: 0, type: "request" }), result); + expect(session.executeCommand({ + command, + seq: 0, + type: "request" + })).to.deep.equal(result); }); it("throws when a duplicate handler is passed", () => { const respBody = { @@ -349,7 +367,8 @@ namespace ts.server { session.addProtocolHandler(command, () => resp); - assert.throws(() => session.addProtocolHandler(command, () => resp), `Protocol handler already exists for command "${command}"`); + expect(() => session.addProtocolHandler(command, () => resp)) + .to.throw(`Protocol handler already exists for command "${command}"`); }); }); @@ -362,13 +381,12 @@ namespace ts.server { session.event(info, evt); - const expected: protocol.Event = { + expect(lastSent).to.deep.equal({ type: "event", seq: 0, event: evt, body: info - }; - assert.deepEqual(lastSent, expected); + }); }); }); @@ -383,15 +401,14 @@ namespace ts.server { session.output(body, command, /*reqSeq*/ 0); - const expected: protocol.Response = { + expect(lastSent).to.deep.equal({ seq: 0, request_seq: 0, type: "response", command, body, success: true - }; - assert.deepEqual(lastSent, expected); + }); }); }); }); @@ -452,16 +469,14 @@ namespace ts.server { session.onMessage(JSON.stringify(request)); const lastSent = session.lastSent as protocol.Response; - assert.deepEqual({ ...lastSent, message: undefined }, { - request_seq: 0, + expect(lastSent).to.contain({ seq: 0, type: "response", command, - success: false, - message: undefined, + success: false }); - assert(ts.stringContains(lastSent.message, "myMessage") && ts.stringContains(lastSent.message, "f1")); + expect(lastSent.message).has.string("myMessage").and.has.string("f1"); }); }); @@ -501,24 +516,23 @@ namespace ts.server { session.output(body, command, /*reqSeq*/ 0); - const expected: protocol.Response = { + expect(session.lastSent).to.deep.equal({ seq: 0, request_seq: 0, type: "response", command, body, success: true - }; - assert.deepEqual(session.lastSent, expected); + }); }); it("can add and respond to new protocol handlers", () => { const session = new TestSession(); - assert.deepEqual(session.executeCommand({ + expect(session.executeCommand({ seq: 0, type: "request", command: session.customHandler - }), { + })).to.deep.equal({ response: undefined, responseRequired: true }); @@ -528,7 +542,8 @@ namespace ts.server { new class extends TestSession { constructor() { super(); - assert(this.projectService instanceof ProjectService); + assert(this.projectService); + expect(this.projectService).to.be.instanceOf(ProjectService); } }(); }); @@ -652,9 +667,9 @@ namespace ts.server { // Add an event handler cli.on("testevent", (eventinfo) => { - assert.equal(eventinfo, toEvent); + expect(eventinfo).to.equal(toEvent); responses++; - assert.equal(responses, 1); + expect(responses).to.equal(1); }); // Trigger said event from the server @@ -664,8 +679,8 @@ namespace ts.server { cli.execute("echo", toEcho, (resp) => { assert(resp.success, resp.message); responses++; - assert.equal(responses, 2); - assert.deepEqual(resp.body, toEcho); + expect(responses).to.equal(2); + expect(resp.body).to.deep.equal(toEcho); }); // Queue a configure command @@ -677,7 +692,7 @@ namespace ts.server { }, (resp) => { assert(resp.success, resp.message); responses++; - assert.equal(responses, 3); + expect(responses).to.equal(3); done(); }); diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index ed571b37399..934da4e3ba1 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -103,7 +103,7 @@ namespace M /*body */ createBlock(statements) ); - changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { suffix: newLineCharacter }); + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction); // replace statements with return statement const newStatement = createReturn( @@ -129,12 +129,11 @@ function bar() { changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); }); } - function findVariableStatementContaining(name: string, sourceFile: SourceFile) { - const varDecl = findChild(name, sourceFile); - assert.equal(varDecl.kind, SyntaxKind.VariableDeclaration); - const varStatement = varDecl.parent.parent; - assert.equal(varStatement.kind, SyntaxKind.VariableStatement); - return varStatement; + function findVariableStatementContaining(name: string, sourceFile: SourceFile): VariableStatement { + return cast(findVariableDeclarationContaining(name, sourceFile).parent.parent, isVariableStatement); + } + function findVariableDeclarationContaining(name: string, sourceFile: SourceFile): VariableDeclaration { + return cast(findChild(name, sourceFile), isVariableDeclaration); } { const text = ` @@ -306,11 +305,11 @@ var y; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("insertNodeAt1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter }); + runSingleFileTest("insertNodeBefore3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); }); - runSingleFileTest("insertNodeAt2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1")); + runSingleFileTest("insertNodeAfterVariableDeclaration", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableDeclarationContaining("y", sourceFile), createTestVariableDeclaration("z1")); }); } { @@ -325,23 +324,22 @@ namespace M { var a = 4; // comment 7 }`; runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); }); runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter }); + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass()); }); runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); }); runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass()); }); } - function findOpenBraceForConstructor(sourceFile: SourceFile) { + function findConstructor(sourceFile: SourceFile): ConstructorDeclaration { const classDecl = sourceFile.statements[0]; - const constructorDecl = forEach(classDecl.members, m => m.kind === SyntaxKind.Constructor && (m).body && m); - return constructorDecl.body.getFirstToken(); + return find(classDecl.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; } function createTestSuperCall() { const superCall = createCall( @@ -359,8 +357,8 @@ class A { } } `; - runSingleFileTest("insertNodeAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); + runSingleFileTest("insertNodeAtConstructorStart", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); }); const text2 = ` class A { @@ -370,7 +368,7 @@ class A { } `; runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall()); }); const text3 = ` class A { @@ -379,8 +377,8 @@ class A { } } `; - runSingleFileTest("insertNodeAfter3-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); + runSingleFileTest("insertNodeAtConstructorStart-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); }); } { @@ -638,7 +636,7 @@ class A { } const insertAfter = findChild("x", sourceFile); for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode); } }); } @@ -649,7 +647,7 @@ class A { } `; 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 }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); }); } { @@ -659,7 +657,7 @@ class A { } `; 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 }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); }); } { @@ -698,7 +696,7 @@ class A { /*questionToken*/ undefined, createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); }); } { @@ -716,7 +714,7 @@ class A { /*questionToken*/ undefined, createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); }); } { @@ -733,7 +731,7 @@ interface A { /*questionToken*/ undefined, createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); }); } { @@ -750,7 +748,7 @@ interface A { /*questionToken*/ undefined, createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); }); } { @@ -759,7 +757,7 @@ let x = foo `; runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { const newNode = createStatement(createParen(createLiteral(1))); - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode); }); } }); diff --git a/src/harness/unittests/textStorage.ts b/src/harness/unittests/textStorage.ts index 78a736487cc..aa8231aa31a 100644 --- a/src/harness/unittests/textStorage.ts +++ b/src/harness/unittests/textStorage.ts @@ -33,20 +33,20 @@ namespace ts.textStorage { for (let offset = 0; offset < end - start; offset++) { const pos1 = ts1.lineOffsetToPosition(line + 1, offset + 1); const pos2 = ts2.lineOffsetToPosition(line + 1, offset + 1); - assert(pos1 === pos2, `lineOffsetToPosition ${line + 1}-${offset + 1}: expected ${pos1} to equal ${pos2}`); + assert.isTrue(pos1 === pos2, `lineOffsetToPosition ${line + 1}-${offset + 1}: expected ${pos1} to equal ${pos2}`); } const {start: start1, length: length1 } = ts1.lineToTextSpan(line); const {start: start2, length: length2 } = ts2.lineToTextSpan(line); - assert(start1 === start2, `lineToTextSpan ${line}::start:: expected ${start1} to equal ${start2}`); - assert(length1 === length2, `lineToTextSpan ${line}::length:: expected ${length1} to equal ${length2}`); + assert.isTrue(start1 === start2, `lineToTextSpan ${line}::start:: expected ${start1} to equal ${start2}`); + assert.isTrue(length1 === length2, `lineToTextSpan ${line}::length:: expected ${length1} to equal ${length2}`); } for (let pos = 0; pos < f.content.length; pos++) { const { line: line1, offset: offset1 } = ts1.positionToLineOffset(pos); const { line: line2, offset: offset2 } = ts2.positionToLineOffset(pos); - assert(line1 === line2, `positionToLineOffset ${pos}::line:: expected ${line1} to equal ${line2}`); - assert(offset1 === offset2, `positionToLineOffset ${pos}::offset:: expected ${offset1} to equal ${offset2}`); + assert.isTrue(line1 === line2, `positionToLineOffset ${pos}::line:: expected ${line1} to equal ${line2}`); + assert.isTrue(offset1 === offset2, `positionToLineOffset ${pos}::offset:: expected ${offset1} to equal ${offset2}`); } }); @@ -55,16 +55,16 @@ namespace ts.textStorage { const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path)); ts1.getSnapshot(); - assert(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1"); + assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1"); ts1.edit(0, 5, " "); - assert(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 1"); + assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 1"); ts1.useText(); - assert(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2"); + assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2"); ts1.getLineInfo(0); - assert(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2"); + assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2"); }); }); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 00d7e17d60e..758a55b8f95 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -80,20 +80,47 @@ namespace ts.tscWatch { checkOutputDoesNotContain(host, expectedNonAffectedFiles); } - function checkOutputErrors(host: WatchedSystem, errors: ReadonlyArray, isInitial?: true, skipWaiting?: true) { + enum ExpectedOutputErrorsPosition { + BeforeCompilationStarts, + AfterCompilationStarting, + AfterFileChangeDetected + } + + function checkOutputErrors( + host: WatchedSystem, + errors: ReadonlyArray, + errorsPosition: ExpectedOutputErrorsPosition, + skipWaiting?: true + ) { const outputs = host.getOutput(); - const expectedOutputCount = (isInitial ? 0 : 1) + errors.length + (skipWaiting ? 0 : 1); + const expectedOutputCount = errors.length + (skipWaiting ? 0 : 1) + 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++; + let index: number; + + switch (errorsPosition) { + case ExpectedOutputErrorsPosition.AfterCompilationStarting: + assertWatchDiagnosticAt(host, 0, Diagnostics.Starting_compilation_in_watch_mode); + index = 1; + break; + case ExpectedOutputErrorsPosition.AfterFileChangeDetected: + assertWatchDiagnosticAt(host, 0, Diagnostics.File_change_detected_Starting_incremental_compilation); + index = 1; + break; + case ExpectedOutputErrorsPosition.BeforeCompilationStarts: + assertWatchDiagnosticAt(host, errors.length, Diagnostics.Starting_compilation_in_watch_mode); + index = 0; + break; } + forEach(errors, error => { assertDiagnosticAt(host, index, error); index++; }); if (!skipWaiting) { + if (errorsPosition === ExpectedOutputErrorsPosition.BeforeCompilationStarts) { + assertWatchDiagnosticAt(host, index, ts.Diagnostics.Starting_compilation_in_watch_mode); + index += 1; + } assertWatchDiagnosticAt(host, index, Diagnostics.Compilation_complete_Watching_for_file_changes); } host.clearOutput(); @@ -106,7 +133,7 @@ namespace ts.tscWatch { function assertWatchDiagnosticAt(host: WatchedSystem, outputAt: number, diagnosticMessage: DiagnosticMessage) { const output = host.getOutput()[outputAt]; - assert(endsWith(output, getWatchDiagnosticWithoutDate(host, diagnosticMessage)), "outputs[" + outputAt + "] is " + output); + assert.isTrue(endsWith(output, getWatchDiagnosticWithoutDate(host, diagnosticMessage)), "outputs[" + outputAt + "] is " + output); } function getWatchDiagnosticWithoutDate(host: WatchedSystem, diagnosticMessage: DiagnosticMessage) { @@ -333,13 +360,13 @@ namespace ts.tscWatch { 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); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); host.reloadFS([file1, commonFile2, libFile]); host.runQueuedTimeoutCallbacks(); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, libFile.path, commonFile2.path]); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("should reflect change in config file", () => { @@ -667,7 +694,7 @@ namespace ts.tscWatch { const watch = createWatchModeWithConfigFile(config.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); host.reloadFS([file1, file2, libFile]); host.checkTimeoutQueueLengthAndRun(1); @@ -675,7 +702,7 @@ namespace ts.tscWatch { assert.equal(host.exitCode, ExitStatus.DiagnosticsPresent_OutputsSkipped); checkOutputErrors(host, [ getDiagnosticWithoutFile(Diagnostics.File_0_not_found, config.path) - ], /*isInitial*/ undefined, /*skipWaiting*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected, /*skipWaiting*/ true); }); it("Proper errors: document is not contained in project", () => { @@ -778,7 +805,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([moduleFile, file1, libFile]); const watch = createWatchModeWithoutConfigFile([file1.path], host); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; @@ -787,12 +814,12 @@ namespace ts.tscWatch { host.runQueuedTimeoutCallbacks(); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile") - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1, libFile]); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("rename a module file and rename back should restore the states for configured projects", () => { @@ -810,7 +837,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); const watch = createWatchModeWithConfigFile(configFile.path, host); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; @@ -819,12 +846,12 @@ namespace ts.tscWatch { host.runQueuedTimeoutCallbacks(); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile") - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1, configFile, libFile]); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("types should load from config file path if config exists", () => { @@ -863,11 +890,11 @@ namespace ts.tscWatch { checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile") - ], /*isInitial*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); host.reloadFS([file1, moduleFile, libFile]); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("Configure file diagnostics events are generated when the config file has errors", () => { @@ -890,7 +917,7 @@ namespace ts.tscWatch { checkOutputErrors(host, [ getUnknownCompilerOption(watch(), configFile, "foo"), getUnknownCompilerOption(watch(), configFile, "allowJS") - ], /*isInitial*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.BeforeCompilationStarts); }); it("If config file doesnt have errors, they are not reported", () => { @@ -907,7 +934,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([file, configFile, libFile]); createWatchModeWithConfigFile(configFile.path, host); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); }); it("Reports errors when the config file changes", () => { @@ -924,7 +951,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([file, configFile, libFile]); const watch = createWatchModeWithConfigFile(configFile.path, host); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); configFile.content = `{ "compilerOptions": { @@ -935,14 +962,14 @@ namespace ts.tscWatch { host.runQueuedTimeoutCallbacks(); checkOutputErrors(host, [ getUnknownCompilerOption(watch(), configFile, "haha") - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); configFile.content = `{ "compilerOptions": {} }`; host.reloadFS([file, configFile, libFile]); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => { @@ -1030,13 +1057,13 @@ namespace ts.tscWatch { 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); + checkOutputErrors(host, intialErrors, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); configFile.content = configFileContentWithoutCommentLine; host.reloadFS(files); host.runQueuedTimeoutCallbacks(); const nowErrors = errors(); - checkOutputErrors(host, nowErrors); + checkOutputErrors(host, nowErrors, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length); assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length); }); @@ -1516,11 +1543,11 @@ namespace ts.tscWatch { function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) { for (const { path, content, shouldBeWritten } of emittedFiles) { if (shouldBeWritten) { - assert(host.fileExists(path), `Expected file ${path} to be present`); + assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`); assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`); } else { - assert(!host.fileExists(path), `Expected file ${path} to be absent`); + assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`); } } } @@ -1667,7 +1694,7 @@ namespace ts.tscWatch { const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); // ensure that imported file was found - checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*isInitial*/ true); + checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); const originalFileExists = host.fileExists; { @@ -1687,7 +1714,7 @@ namespace ts.tscWatch { 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 - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); } { let fileExistsIsCalled = false; @@ -1696,7 +1723,7 @@ namespace ts.tscWatch { return false; } fileExistsIsCalled = true; - assert(fileName.indexOf("/f2.") !== -1); + assert.isTrue(fileName.indexOf("/f2.") !== -1); return originalFileExists.call(host, fileName); }; @@ -1709,9 +1736,9 @@ namespace ts.tscWatch { // ensure file has correct number of errors after edit checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); - assert(fileExistsIsCalled); + assert.isTrue(fileExistsIsCalled); } { let fileExistsCalled = false; @@ -1720,7 +1747,7 @@ namespace ts.tscWatch { return false; } fileExistsCalled = true; - assert(fileName.indexOf("/f1.") !== -1); + assert.isTrue(fileName.indexOf("/f1.") !== -1); return originalFileExists.call(host, fileName); }; @@ -1730,8 +1757,8 @@ namespace ts.tscWatch { host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, [f1IsNotModule, cannotFindFoo]); - assert(fileExistsCalled); + checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); + assert.isTrue(fileExistsCalled); } }); @@ -1764,18 +1791,18 @@ namespace ts.tscWatch { const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); - assert(fileExistsCalledForBar, "'fileExists' should be called"); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ], /*isInitial*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); fileExistsCalledForBar = false; root.content = `import {y} from "bar"`; host.reloadFS(files.concat(imported)); host.runQueuedTimeoutCallbacks(); - assert(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); }); it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { @@ -1806,22 +1833,22 @@ namespace ts.tscWatch { const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); - assert(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrors(host, emptyArray, /*isInitial*/ true); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); fileExistsCalledForBar = false; host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - assert(fileExistsCalledForBar, "'fileExists' should be called."); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); fileExistsCalledForBar = false; host.reloadFS(filesWithImported); host.checkTimeoutQueueLengthAndRun(1); - assert(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); }); it("works when module resolution changes to ambient module", () => { @@ -1857,11 +1884,11 @@ declare module "fs" { checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ], /*isInitial*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); host.reloadFS(filesWithNodeType); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("works when included file with ambient module changes", () => { @@ -1899,12 +1926,12 @@ declare module "fs" { checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ], /*isInitial*/ true); + ], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); file.content += fileContentWithFS; host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); }); it("works when reusing program with files from external library", () => { @@ -1939,7 +1966,7 @@ declare module "fs" { 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); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting); const expectedFiles: ExpectedFile[] = [ createExpectedEmittedFile(file1), createExpectedEmittedFile(file2), @@ -1958,7 +1985,7 @@ declare module "fs" { host.reloadFS(programFiles.concat(configFile)); host.runQueuedTimeoutCallbacks(); checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrors(host, emptyArray); + checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected); verifyExpectedFiles(expectedFiles); @@ -2023,13 +2050,13 @@ declare module "fs" { checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js"); - assert(host.fileExists(outputFile1)); + assert.isTrue(host.fileExists(outputFile1)); assert.equal(host.readFile(outputFile1), file1.content + host.newLine); }); }); describe("tsc-watch console clearing", () => { - it("doesn't clear the console when it starts", () => { + it("clears the console when it starts", () => { const file = { path: "f.ts", content: "" @@ -2039,7 +2066,7 @@ declare module "fs" { createWatchModeWithoutConfigFile([file.path], host); host.runQueuedTimeoutCallbacks(); - host.checkScreenClears(0); + host.checkScreenClears(1); }); it("clears the console on recompile", () => { @@ -2057,7 +2084,7 @@ declare module "fs" { host.reloadFS([modifiedFile]); host.runQueuedTimeoutCallbacks(); - host.checkScreenClears(1); + host.checkScreenClears(2); }); }); } diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 0f03b5630b3..8d56360bba5 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -11,20 +11,20 @@ namespace ts { function assertParseError(jsonText: string) { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); assert.deepEqual(parsed.config, {}); - assert(undefined !== parsed.error); + assert.isTrue(undefined !== parsed.error); } function assertParseErrorWithExcludesKeyword(jsonText: string) { { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "tests/cases/unittests"); - assert(parsedCommand.errors && parsedCommand.errors.length === 1 && + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } { const parsed = ts.parseJsonText("/apath/tsconfig.json", jsonText); const parsedCommand = ts.parseJsonSourceFileConfigFileContent(parsed, ts.sys, "tests/cases/unittests"); - assert(parsedCommand.errors && parsedCommand.errors.length === 1 && + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } } @@ -44,26 +44,26 @@ namespace ts { function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { { const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } { const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } } function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { { const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert(parsed.errors.length >= 0); - assert(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); } { const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert(parsed.errors.length >= 0); - assert(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); if (!noLocation) { - assert(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); } } } @@ -241,7 +241,7 @@ namespace ts { }, files: ["file1.ts"] }; - assert(diagnostics.length === 2); + assert.isTrue(diagnostics.length === 2); assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); }); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 44924f4d63f..fe7a2095b86 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -468,12 +468,12 @@ namespace ts.projectSystem { assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); if (isMostRecent) { - assert.equal(events.length, index + 1, JSON.stringify(events)); - assert.equal(outputs.length, index + 1, JSON.stringify(outputs)); + assert.strictEqual(events.length, index + 1, JSON.stringify(events)); + assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); } } - describe("tsserverProjectSystem", () => { + describe("tsserverProjectSystem general functionality", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", content: "let x = 1" @@ -572,8 +572,8 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - assert.isDefined(configFileName, "should find config file"); - assert(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); checkNumberOfInferredProjects(projectService, 0); checkNumberOfConfiguredProjects(projectService, 1); @@ -612,8 +612,8 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - assert.isDefined(configFileName, "should find config file"); - assert(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); checkNumberOfInferredProjects(projectService, 0); checkNumberOfConfiguredProjects(projectService, 1); @@ -821,7 +821,7 @@ namespace ts.projectSystem { host.reloadFS([file1, commonFile2, libFile]); host.runQueuedTimeoutCallbacks(); checkNumberOfInferredProjects(projectService, 1); - assert.equal(projectService.inferredProjects[0], project, "Inferred project should be same"); + assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); checkProjectRootFiles(project, [file1.path]); checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; @@ -1024,11 +1024,11 @@ namespace ts.projectSystem { projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); const proj1 = projectService.findProject(proj1name); - assert(proj1.languageServiceEnabled); + assert.isTrue(proj1.languageServiceEnabled); projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); const proj2 = projectService.findProject(proj2name); - assert(proj2.languageServiceEnabled); + assert.isTrue(proj2.languageServiceEnabled); projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); const proj3 = projectService.findProject(proj3name); @@ -1100,18 +1100,18 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects.get(configFile.path); - assert(project.hasOpenRef()); // file1 + assert.isTrue(project.hasOpenRef()); // file1 projectService.closeClientFile(file1.path); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); assert.isFalse(project.hasOpenRef()); // No open files assert.isFalse(project.isClosed()); projectService.openClientFile(file2.path); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(configFile.path), project); - assert(project.hasOpenRef()); // file2 + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isTrue(project.hasOpenRef()); // file2 assert.isFalse(project.isClosed()); }); @@ -1134,18 +1134,18 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects.get(configFile.path); - assert(project.hasOpenRef()); // file1 + assert.isTrue(project.hasOpenRef()); // file1 projectService.closeClientFile(file1.path); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); assert.isFalse(project.hasOpenRef()); // No files assert.isFalse(project.isClosed()); projectService.openClientFile(libFile.path); checkNumberOfConfiguredProjects(projectService, 0); assert.isFalse(project.hasOpenRef()); // No files + project closed - assert(project.isClosed()); + assert.isTrue(project.isClosed()); }); it("should not close external project with no open files", () => { @@ -1184,6 +1184,24 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 0); }); + it("external project for dynamic file", () => { + const externalProjectName = "^ScriptDocument1 file1.ts"; + const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: externalFiles, + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + externalFiles[0].content = "let x =1;"; + projectService.applyChangesInOpenFiles(externalFiles, [], []); + }); + it("external project that included config files", () => { const file1 = { path: "/a/b/f1.ts", @@ -1233,28 +1251,28 @@ namespace ts.projectSystem { // open client file - should not lead to creation of inferred project projectService.openClientFile(file1.path, file1.content); checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert.equal(projectService.configuredProjects.get(config1.path), proj1); - assert.equal(projectService.configuredProjects.get(config2.path), proj2); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); projectService.openClientFile(file3.path, file3.content); checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config1.path), proj1); - assert.equal(projectService.configuredProjects.get(config2.path), proj2); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); projectService.closeExternalProject(externalProjectName); // open file 'file1' from configured project keeps project alive checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); assert.isUndefined(projectService.configuredProjects.get(config2.path)); projectService.closeClientFile(file3.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); assert.isUndefined(projectService.configuredProjects.get(config2.path)); projectService.closeClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); assert.isUndefined(projectService.configuredProjects.get(config2.path)); projectService.openClientFile(file2.path, file2.content); @@ -1284,16 +1302,16 @@ namespace ts.projectSystem { service.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); // should contain completions for string - assert(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); + assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); // should contain completions for string assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); - assert(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); + assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); }); it("clear mixed content file after closing", () => { @@ -1316,11 +1334,11 @@ namespace ts.projectSystem { service.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); - assert(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); + assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path); assert.equal(sf2.text, ""); @@ -1387,16 +1405,16 @@ namespace ts.projectSystem { }); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); projectService.closeExternalProject(externalProjectName); // configured project is alive since file is still open checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); projectService.closeClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { inferredProjects: 1 }); @@ -1910,7 +1928,7 @@ namespace ts.projectSystem { // The configured project should now be updated to include html file checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Open HTML file @@ -1925,7 +1943,7 @@ namespace ts.projectSystem { // Check identifiers defined in HTML content are available in .ts file const project = configuredProjectAt(projectService, 0); - let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false }); + let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`); // Close HTML file @@ -1939,7 +1957,7 @@ namespace ts.projectSystem { checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Check identifiers defined in HTML content are not available in .ts file - completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false }); + completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); }); @@ -2176,18 +2194,18 @@ namespace ts.projectSystem { projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); const project1 = projectService.configuredProjects.get(tsconfig1.path); - assert(project1.hasOpenRef(), "Has open ref count in project1 - 1"); // file2 + assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 1"); // file2 assert.equal(project1.getScriptInfo(file2.path).containingProjects.length, 1, "containing projects count"); assert.isFalse(project1.isClosed()); projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert(project1.hasOpenRef(), "Has open ref count in project1 - 2"); // file2 - assert.equal(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 2"); // file2 + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); assert.isFalse(project1.isClosed()); const project2 = projectService.configuredProjects.get(tsconfig2.path); - assert(project2.hasOpenRef(), "Has open ref count in project2 - 2"); // file1 + assert.isTrue(project2.hasOpenRef(), "Has open ref count in project2 - 2"); // file1 assert.isFalse(project2.isClosed()); assert.equal(project1.getScriptInfo(file1.path).containingProjects.length, 2, `${file1.path} containing projects count`); @@ -2196,9 +2214,9 @@ namespace ts.projectSystem { projectService.closeClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 2 }); assert.isFalse(project1.hasOpenRef(), "Has open ref count in project1 - 3"); // No files - assert(project2.hasOpenRef(), "Has open ref count in project2 - 3"); // file1 - assert.equal(projectService.configuredProjects.get(tsconfig1.path), project1); - assert.equal(projectService.configuredProjects.get(tsconfig2.path), project2); + assert.isTrue(project2.hasOpenRef(), "Has open ref count in project2 - 3"); // file1 + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2); assert.isFalse(project1.isClosed()); assert.isFalse(project2.isClosed()); @@ -2206,18 +2224,18 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 2 }); assert.isFalse(project1.hasOpenRef(), "Has open ref count in project1 - 4"); // No files assert.isFalse(project2.hasOpenRef(), "Has open ref count in project2 - 4"); // No files - assert.equal(projectService.configuredProjects.get(tsconfig1.path), project1); - assert.equal(projectService.configuredProjects.get(tsconfig2.path), project2); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2); assert.isFalse(project1.isClosed()); assert.isFalse(project2.isClosed()); projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); assert.isUndefined(projectService.configuredProjects.get(tsconfig2.path)); - assert(project1.hasOpenRef(), "Has open ref count in project1 - 5"); // file2 + assert.isTrue(project1.hasOpenRef(), "Has open ref count in project1 - 5"); // file2 assert.isFalse(project1.isClosed()); - assert(project2.isClosed()); + assert.isTrue(project2.isClosed()); }); it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { @@ -2255,7 +2273,7 @@ namespace ts.projectSystem { checkOpenFiles(projectService, files); checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); const configProject1 = projectService.configuredProjects.get(configFile.path); - assert(configProject1.hasOpenRef()); // file1 and file3 + assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]); const inferredProject1 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject1, [file2.path]); @@ -2272,7 +2290,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 1); const inferredProject3 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject3, [file4.path]); - assert.equal(inferredProject3, inferredProject2); + assert.strictEqual(inferredProject3, inferredProject2); projectService.closeClientFile(file1.path); projectService.closeClientFile(file2.path); @@ -2298,7 +2316,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 1); const inferredProject5 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject4, [file4.path]); - assert.equal(inferredProject5, inferredProject4); + assert.strictEqual(inferredProject5, inferredProject4); const file5: FileOrFolder = { path: "/file5.ts", @@ -2307,13 +2325,13 @@ namespace ts.projectSystem { host.reloadFS(files.concat(configFile, file5)); projectService.openClientFile(file5.path); verifyScriptInfosAreUndefined([file1, file2, file3]); - assert.equal(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); + assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path)); checkOpenFiles(projectService, [file4, file5]); checkNumberOfConfiguredProjects(projectService, 0); function verifyScriptInfos() { - infos.forEach(info => assert.equal(projectService.getScriptInfoForPath(info.path), info)); + infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info)); } function verifyScriptInfosAreUndefined(files: FileOrFolder[]) { @@ -2325,7 +2343,7 @@ namespace ts.projectSystem { function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean) { checkNumberOfConfiguredProjects(projectService, 1); const configProject2 = projectService.configuredProjects.get(configFile.path); - assert.equal(configProject2, configProject1); + assert.strictEqual(configProject2, configProject1); checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]); assert.equal(configProject2.hasOpenRef(), hasOpenRef); } @@ -2364,7 +2382,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); const configuredProject = projectService.configuredProjects.get(configFile.path); - assert(configuredProject.hasOpenRef()); // file1 and file3 + assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); const inferredProject1 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject1, [file2.path]); @@ -2376,23 +2394,23 @@ namespace ts.projectSystem { configFile.content = "{}"; host.reloadFS(files.concat(configFile)); // Time out is not yet run so there is project update pending - assert(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project projectService.openClientFile(file4.path); checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - assert.equal(projectService.configuredProjects.get(configFile.path), configuredProject); - assert(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project - assert.equal(projectService.inferredProjects[0], inferredProject1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); const inferredProject2 = projectService.inferredProjects[1]; checkProjectActualFiles(inferredProject2, [file4.path]); host.runQueuedTimeoutCallbacks(); checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), configuredProject); - assert(configuredProject.hasOpenRef()); // file2 + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // file2 checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); - assert.equal(projectService.inferredProjects[0], inferredProject2); + assert.strictEqual(projectService.inferredProjects[0], inferredProject2); checkProjectActualFiles(inferredProject2, [file4.path]); }); @@ -2427,7 +2445,7 @@ namespace ts.projectSystem { options: {} }); service.checkNumberOfProjects({ externalProjects: 1 }); - assert(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); + assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); service.openExternalProject({ projectFileName, @@ -2464,12 +2482,12 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); const project = projectService.configuredProjects.get(config.path); - assert(project.hasOpenRef()); // f1 + assert.isTrue(project.hasOpenRef()); // f1 assert.isFalse(project.isClosed()); projectService.closeClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config.path), project); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); assert.isFalse(project.hasOpenRef()); // No files assert.isFalse(project.isClosed()); @@ -2488,7 +2506,7 @@ namespace ts.projectSystem { projectService.openClientFile(f4.path); projectService.checkNumberOfProjects({ inferredProjects: 1 }); assert.isFalse(project.hasOpenRef()); // No files - assert(project.isClosed()); + assert.isTrue(project.isClosed()); for (const f of [f1, f2, f3]) { // All the script infos should not be present since the project is closed and orphan script infos are collected @@ -2540,7 +2558,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const project = configuredProjectAt(projectService, 0); assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert(!!lastEvent, "should receive event"); + assert.isTrue(!!lastEvent, "should receive event"); assert.equal(lastEvent.data.project, project, "project name"); assert.equal(lastEvent.data.project.getProjectName(), config.path, "config path"); assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state"); @@ -2548,9 +2566,9 @@ namespace ts.projectSystem { host.reloadFS([f1, f2, configWithExclude]); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert(project.languageServiceEnabled, "Language service enabled"); + assert.isTrue(project.languageServiceEnabled, "Language service enabled"); assert.equal(lastEvent.data.project, project, "project"); - assert(lastEvent.data.languageServiceEnabled, "Language service state"); + assert.isTrue(lastEvent.data.languageServiceEnabled, "Language service state"); }); it("syntactic features work even if language service is disabled", () => { @@ -2592,7 +2610,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const project = configuredProjectAt(projectService, 0); assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert(!!lastEvent, "should receive event"); + assert.isTrue(!!lastEvent, "should receive event"); assert.equal(lastEvent.data.project, project, "project name"); assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state"); @@ -2747,7 +2765,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); watchedRecursiveDirectories.pop(); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); checkWatchedDirectories(host, [], /*recursive*/ false); @@ -2756,7 +2774,7 @@ namespace ts.projectSystem { // On next file open the files file2a should be closed and not watched any more projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(configFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); checkWatchedFiles(host, [libFile.path, configFile.path]); checkWatchedDirectories(host, [], /*recursive*/ false); @@ -2806,7 +2824,7 @@ namespace ts.projectSystem { }); - describe("Proper errors", () => { + describe("tsserverProjectSystem Proper errors", () => { it("document is not contained in project", () => { const file1 = { path: "/a/b/app.ts", @@ -2960,7 +2978,7 @@ namespace ts.projectSystem { }); }); - describe("autoDiscovery", () => { + describe("tsserverProjectSystem autoDiscovery", () => { it("does not depend on extension", () => { const file1 = { path: "/a/b/app.html", @@ -2979,11 +2997,11 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); const typeAcquisition = projectService.externalProjects[0].getTypeAcquisition(); - assert(typeAcquisition.enable, "Typine acquisition should be enabled"); + assert.isTrue(typeAcquisition.enable, "Typine acquisition should be enabled"); }); }); - describe("extra resolution pass in lshost", () => { + describe("tsserverProjectSystem extra resolution pass in lshost", () => { it("can load typings that are proper modules", () => { const file1 = { path: "/a/b/app.js", @@ -3025,7 +3043,7 @@ namespace ts.projectSystem { }); }); - describe("navigate-to for javascript project", () => { + describe("tsserverProjectSystem navigate-to for javascript project", () => { function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; } @@ -3050,11 +3068,11 @@ namespace ts.projectSystem { const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; - assert(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); + assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); }); }); - describe("external projects", () => { + describe("tsserverProjectSystem external projects", () => { it("correctly handling add/remove tsconfig - 1", () => { const f1 = { path: "/a/b/app.ts", @@ -3264,23 +3282,23 @@ namespace ts.projectSystem { projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); const project = projectService.configuredProjects.get(config.path); - assert(project.hasOpenRef()); // f + assert.isTrue(project.hasOpenRef()); // f projectService.closeClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config.path), project); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); assert.isFalse(project.hasOpenRef()); // No files assert.isFalse(project.isClosed()); projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.equal(projectService.configuredProjects.get(config.path), project); - assert(project.hasOpenRef()); // f + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isTrue(project.hasOpenRef()); // f assert.isFalse(project.isClosed()); }); }); - describe("prefer typings to js", () => { + describe("tsserverProjectSystem prefer typings to js", () => { it("during second resolution pass", () => { const typingsCacheLocation = "/a/typings"; const f1 = { @@ -3308,7 +3326,7 @@ namespace ts.projectSystem { }); }); - describe("format settings", () => { + describe("tsserverProjectSystem format settings", () => { it("can be set globally", () => { const f1 = { path: "/a/b/app.ts", @@ -3349,7 +3367,7 @@ namespace ts.projectSystem { }); }); - describe("watching @types", () => { + describe("tsserverProjectSystem watching @types", () => { it("works correctly when typings are added or removed", () => { const f1 = { path: "/a/b/app.ts", @@ -3395,7 +3413,7 @@ namespace ts.projectSystem { }); }); - describe("Open-file", () => { + describe("tsserverProjectSystem Open-file", () => { it("can be reloaded with empty content", () => { const f = { path: "/a/b/app.ts", @@ -3470,7 +3488,7 @@ namespace ts.projectSystem { }); }); - describe("Language service", () => { + describe("tsserverProjectSystem Language service", () => { it("should work correctly on case-sensitive file systems", () => { const lib = { path: "/a/Lib/lib.d.ts", @@ -3488,7 +3506,7 @@ namespace ts.projectSystem { }); }); - describe("rename a module file and rename back", () => { + describe("tsserverProjectSystem rename a module file and rename back", () => { it("should restore the states for inferred projects", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -3623,7 +3641,7 @@ namespace ts.projectSystem { }); }); - describe("add the missing module file for inferred project", () => { + describe("tsserverProjectSystem add the missing module file for inferred project", () => { it("should remove the `module not found` error", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -3729,7 +3747,7 @@ namespace ts.projectSystem { }); }); - describe("Configure file diagnostics events", () => { + describe("tsserverProjectSystem Configure file diagnostics events", () => { it("are generated when the config file has errors", () => { const file = { @@ -3845,7 +3863,7 @@ namespace ts.projectSystem { }); }); - describe("skipLibCheck", () => { + describe("tsserverProjectSystem skipLibCheck", () => { it("should be turned on for js-only inferred projects", () => { const file1 = { path: "/a/b/file1.js", @@ -3872,16 +3890,16 @@ namespace ts.projectSystem { { file: file2.path } ); let errorResult = session.executeCommand(file2GetErrRequest).response; - assert(errorResult.length === 0); + assert.isTrue(errorResult.length === 0); const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); session.executeCommand(closeFileRequest); errorResult = session.executeCommand(file2GetErrRequest).response; - assert(errorResult.length !== 0); + assert.isTrue(errorResult.length !== 0); openFilesForSession([file1], session); errorResult = session.executeCommand(file2GetErrRequest).response; - assert(errorResult.length === 0); + assert.isTrue(errorResult.length === 0); }); it("should be turned on for js-only external projects", () => { @@ -3917,7 +3935,7 @@ namespace ts.projectSystem { { file: dTsFile.path } ); const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert(errorResult.length === 0); + assert.isTrue(errorResult.length === 0); }); it("should be turned on for js-only external projects with skipLibCheck=false", () => { @@ -3953,7 +3971,7 @@ namespace ts.projectSystem { { file: dTsFile.path } ); const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert(errorResult.length === 0); + assert.isTrue(errorResult.length === 0); }); it("should not report bind errors for declaration files with skipLibCheck=true", () => { @@ -3984,14 +4002,14 @@ namespace ts.projectSystem { { file: dTsFile1.path } ); const error1Result = session.executeCommand(dTsFile1GetErrRequest).response; - assert(error1Result.length === 0); + assert.isTrue(error1Result.length === 0); const dTsFile2GetErrRequest = makeSessionRequest( CommandNames.SemanticDiagnosticsSync, { file: dTsFile2.path } ); const error2Result = session.executeCommand(dTsFile2GetErrRequest).response; - assert(error2Result.length === 0); + assert.isTrue(error2Result.length === 0); }); it("should report semanitc errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => { @@ -4012,7 +4030,7 @@ namespace ts.projectSystem { { file: jsFile.path } ); const errorResult = session.executeCommand(getErrRequest).response; - assert(errorResult.length === 1); + assert.isTrue(errorResult.length === 1); assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code); }); @@ -4039,7 +4057,7 @@ namespace ts.projectSystem { { file: jsFile.path } ); const errorResult = session.executeCommand(getErrRequest).response; - assert(errorResult.length === 1); + assert.isTrue(errorResult.length === 1); assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code); }); @@ -4068,12 +4086,12 @@ namespace ts.projectSystem { { file: jsFile.path } ); const errorResult = session.executeCommand(getErrRequest).response; - assert(errorResult.length === 1); + assert.isTrue(errorResult.length === 1); assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code); }); }); - describe("non-existing directories listed in config file input array", () => { + describe("tsserverProjectSystem non-existing directories listed in config file input array", () => { it("should be tolerated without crashing the server", () => { const configFile = { path: "/a/b/tsconfig.json", @@ -4096,7 +4114,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 1); const inferredProject = projectService.inferredProjects[0]; - assert(inferredProject.containsFile(file1.path)); + assert.isTrue(inferredProject.containsFile(file1.path)); }); it("should be able to handle @types if input file list is empty", () => { @@ -4128,7 +4146,7 @@ namespace ts.projectSystem { }); }); - describe("reload", () => { + describe("tsserverProjectSystem reload", () => { it("should work with temp file", () => { const f1 = { path: "/a/b/app.ts", @@ -4236,7 +4254,7 @@ namespace ts.projectSystem { arguments: { file: f1.path } }); checkScriptInfoAndProjects(0, f1.content, "contents of closed file"); - assert.equal(info.getSnapshot(), snap); + assert.strictEqual(info.getSnapshot(), snap); // reload from temp file session.executeCommandSeq({ @@ -4244,7 +4262,7 @@ namespace ts.projectSystem { arguments: { file: f1.path, tmpfile: tmp.path } }); checkScriptInfoAndProjects(0, tmp.content, "contents of temp file"); - assert.notEqual(info.getSnapshot(), snap); + assert.notStrictEqual(info.getSnapshot(), snap); // reload from own file session.executeCommandSeq({ @@ -4252,11 +4270,11 @@ namespace ts.projectSystem { arguments: { file: f1.path } }); checkScriptInfoAndProjects(0, f1.content, "contents of closed file"); - assert.notEqual(info.getSnapshot(), snap); + assert.notStrictEqual(info.getSnapshot(), snap); function checkScriptInfoAndProjects(inferredProjects: number, contentsOfInfo: string, captionForContents: string) { checkNumberOfProjects(projectService, { inferredProjects }); - assert.equal(projectService.getScriptInfo(f1.path), info); + assert.strictEqual(projectService.getScriptInfo(f1.path), info); checkScriptInfoContents(contentsOfInfo, captionForContents); } @@ -4267,7 +4285,7 @@ namespace ts.projectSystem { }); }); - describe("Inferred projects", () => { + describe("tsserverProjectSystem Inferred projects", () => { it("should support files without extensions", () => { const f = { path: "/a/compile", @@ -4495,7 +4513,7 @@ namespace ts.projectSystem { }); }); - describe("No overwrite emit error", () => { + describe("tsserverProjectSystem No overwrite emit error", () => { it("for inferred project", () => { const f1 = { path: "/a/b/f1.js", @@ -4515,7 +4533,7 @@ namespace ts.projectSystem { seq: 2, arguments: { projectFileName: projectName } }).response as ReadonlyArray; - assert(diags.length === 0); + assert.isTrue(diags.length === 0); session.executeCommand({ type: "request", @@ -4529,7 +4547,7 @@ namespace ts.projectSystem { seq: 4, arguments: { projectFileName: projectName } }).response as ReadonlyArray; - assert(diagsAfterUpdate.length === 0); + assert.isTrue(diagsAfterUpdate.length === 0); }); it("for external project", () => { @@ -4556,7 +4574,7 @@ namespace ts.projectSystem { seq: 2, arguments: { projectFileName } }).response as ReadonlyArray; - assert(diags.length === 0); + assert.isTrue(diags.length === 0); session.executeCommand({ type: "request", @@ -4574,11 +4592,11 @@ namespace ts.projectSystem { seq: 4, arguments: { projectFileName } }).response as ReadonlyArray; - assert(diagsAfterUpdate.length === 0); + assert.isTrue(diagsAfterUpdate.length === 0); }); }); - describe("emit with outFile or out setting", () => { + describe("tsserverProjectSystem emit with outFile or out setting", () => { function test(opts: CompilerOptions, expectedUsesOutFile: boolean) { const f1 = { path: "/a/a.ts", @@ -4626,7 +4644,7 @@ namespace ts.projectSystem { }); }); - describe("import helpers", () => { + describe("tsserverProjectSystem import helpers", () => { it("should not crash in tsserver", () => { const f1 = { path: "/a/app.ts", @@ -4643,7 +4661,7 @@ namespace ts.projectSystem { }); }); - describe("searching for config file", () => { + describe("tsserverProjectSystem searching for config file", () => { it("should stop at projectRootPath if given", () => { const f1 = { path: "/a/file1.ts", @@ -4735,7 +4753,7 @@ namespace ts.projectSystem { }); }); - describe("cancellationToken", () => { + describe("tsserverProjectSystem cancellationToken", () => { // 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: ts.AnyFunction; before(() => { @@ -4758,7 +4776,7 @@ namespace ts.projectSystem { isCancellationRequested: () => false, setRequest: requestId => { if (expectedRequestId === undefined) { - assert(false, "unexpected call"); + assert.isTrue(false, "unexpected call"); } assert.equal(requestId, expectedRequestId); }, @@ -4998,7 +5016,7 @@ namespace ts.projectSystem { }); }); - describe("occurence highlight on string", () => { + describe("tsserverProjectSystem occurence highlight on string", () => { it("should be marked if only on string values", () => { const file1: FileOrFolder = { path: "/a/b/file1.ts", @@ -5017,7 +5035,7 @@ namespace ts.projectSystem { ); const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; const firstOccurence = highlightResponse[0]; - assert(firstOccurence.isInString, "Highlights should be marked with isInString"); + assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); } { @@ -5026,7 +5044,7 @@ namespace ts.projectSystem { { file: file1.path, line: 3, offset: 13 } ); const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert(highlightResponse.length === 2); + assert.isTrue(highlightResponse.length === 2); const firstOccurence = highlightResponse[0]; assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); } @@ -5037,14 +5055,14 @@ namespace ts.projectSystem { { file: file1.path, line: 4, offset: 14 } ); const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert(highlightResponse.length === 2); + assert.isTrue(highlightResponse.length === 2); const firstOccurence = highlightResponse[0]; assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); } }); }); - describe("maxNodeModuleJsDepth for inferred projects", () => { + describe("tsserverProjectSystem maxNodeModuleJsDepth for inferred projects", () => { it("should be set to 2 if the project has js root files", () => { const file1: FileOrFolder = { path: "/a/b/file1.js", @@ -5061,13 +5079,13 @@ namespace ts.projectSystem { let project = projectService.inferredProjects[0]; let options = project.getCompilationSettings(); - assert(options.maxNodeModuleJsDepth === 2); + assert.isTrue(options.maxNodeModuleJsDepth === 2); // Assert the option sticks projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); project = projectService.inferredProjects[0]; options = project.getCompilationSettings(); - assert(options.maxNodeModuleJsDepth === 2); + assert.isTrue(options.maxNodeModuleJsDepth === 2); }); it("should return to normal state when all js root files are removed from project", () => { @@ -5090,7 +5108,7 @@ namespace ts.projectSystem { projectService.openClientFile(file2.path); project = projectService.inferredProjects[0]; - assert(project.getCompilationSettings().maxNodeModuleJsDepth === 2); + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); projectService.closeClientFile(file2.path); project = projectService.inferredProjects[0]; @@ -5098,7 +5116,7 @@ namespace ts.projectSystem { }); }); - describe("Options Diagnostic locations reported correctly with changes in configFile contents", () => { + describe("tsserverProjectSystem Options Diagnostic locations reported correctly with changes in configFile contents", () => { it("when options change", () => { const file = { path: "/a/b/app.ts", @@ -5134,7 +5152,7 @@ namespace ts.projectSystem { seq: 2, arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } }).response as ReadonlyArray; - assert(diags.length === 2); + assert.isTrue(diags.length === 2); configFile.content = configFileContentWithoutCommentLine; host.reloadFS([file, configFile]); @@ -5145,7 +5163,7 @@ namespace ts.projectSystem { seq: 2, arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } }).response as ReadonlyArray; - assert(diagsAfterEdit.length === 2); + assert.isTrue(diagsAfterEdit.length === 2); verifyDiagnostic(diags[0], diagsAfterEdit[0]); verifyDiagnostic(diags[1], diagsAfterEdit[1]); @@ -5162,7 +5180,7 @@ namespace ts.projectSystem { }); }); - describe("refactors", () => { + describe("tsserverProjectSystem refactors", () => { it("use formatting options", () => { const file = { path: "/a.ts", @@ -5218,7 +5236,7 @@ namespace ts.projectSystem { }); }); - describe("CachingFileSystemInformation", () => { + describe("tsserverProjectSystem CachingFileSystemInformation", () => { enum CalledMapsWithSingleArg { fileExists = "fileExists", directoryExists = "directoryExists", @@ -5271,7 +5289,7 @@ namespace ts.projectSystem { function verifyCalledOn(callback: CalledMaps, name: string) { const calledMap = calledMaps[callback]; const result = calledMap.get(name); - assert(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); + assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); } function verifyNoCall(callback: CalledMaps) { @@ -5283,7 +5301,7 @@ namespace ts.projectSystem { const calledMap = calledMaps[callback]; ts.TestFSWithWatch.verifyMapSize(callback, calledMap, arrayFrom(expectedKeys.keys())); expectedKeys.forEach((called, name) => { - assert(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`); + assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`); assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`); }); } @@ -5356,10 +5374,10 @@ namespace ts.projectSystem { try { // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk verifyImportedDiagnostics(); - assert(false, `should not find file '${imported.path}'`); + assert.isTrue(false, `should not find file '${imported.path}'`); } catch (e) { - assert(e.message.indexOf(`Could not find file: '${imported.path}'.`) === 0); + assert.isTrue(e.message.indexOf(`Could not find file: '${imported.path}'.`) === 0); } const f2Lookups = getLocationsForModuleLookup("f2"); callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); @@ -5544,7 +5562,7 @@ namespace ts.projectSystem { callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(tsconfigFile.path), project); + assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); }); describe("WatchDirectories for config file with", () => { @@ -5631,7 +5649,7 @@ namespace ts.projectSystem { callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(canonicalConfigPath), project); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); verifyProjectAndWatchedDirectories(); callsTrackingHost.clear(); @@ -5640,7 +5658,7 @@ namespace ts.projectSystem { assert.equal(configFile2, configFileName); checkNumberOfConfiguredProjects(projectService, 1); - assert.equal(projectService.configuredProjects.get(canonicalConfigPath), project); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); verifyProjectAndWatchedDirectories(); callsTrackingHost.verifyNoHostCalls(); @@ -5835,14 +5853,14 @@ namespace ts.projectSystem { }); }); - describe("ProjectsChangedInBackground", () => { + describe("tsserverProjectSystem ProjectsChangedInBackground", () => { function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); const seen = createMap(); forEach(actual, f => { assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); seen.set(f, true); - assert(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); + assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); }); } @@ -6313,7 +6331,7 @@ namespace ts.projectSystem { else { // file2 addition wont be detected projectFiles.pop(); - assert(host.fileExists(file2.path)); + assert.isTrue(host.fileExists(file2.path)); } verifyProject(); @@ -6380,7 +6398,7 @@ namespace ts.projectSystem { assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); forEach(projectChangedEvents, (actualEvent, i) => { const expectedEvent = expectedEvents[i]; - assert.equal(actualEvent.eventName, expectedEvent.eventName); + assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); }); @@ -6431,7 +6449,7 @@ namespace ts.projectSystem { }); }); - describe("Watched recursive directories with windows style file system", () => { + describe("tsserverProjectSystem Watched recursive directories with windows style file system", () => { function verifyWatchedDirectories(useProjectAtRoot: boolean) { const root = useProjectAtRoot ? "c:/" : "c:/myfolder/allproject/"; const configFile: FileOrFolder = { @@ -6470,4 +6488,76 @@ namespace ts.projectSystem { verifyWatchedDirectories(/*useProjectAtRoot*/ false); }); }); + + describe("tsserverProjectSystem typingsInstaller on inferred Project", () => { + it("when projectRootPath is provided", () => { + const projects = "/users/username/projects"; + const projectRootPath = `${projects}/san2`; + const file: FileOrFolder = { + path: `${projectRootPath}/x.js`, + content: "const aaaaaaav = 1;" + }; + + const currentDirectory = `${projects}/anotherProject`; + const packageJsonInCurrentDirectory: FileOrFolder = { + path: `${currentDirectory}/package.json`, + content: JSON.stringify({ + devDependencies: { + pkgcurrentdirectory: "" + }, + }) + }; + const packageJsonOfPkgcurrentdirectory: FileOrFolder = { + path: `${currentDirectory}/node_modules/pkgcurrentdirectory/package.json`, + content: JSON.stringify({ + name: "pkgcurrentdirectory", + main: "index.js", + typings: "index.d.ts" + }) + }; + const indexOfPkgcurrentdirectory: FileOrFolder = { + path: `${currentDirectory}/node_modules/pkgcurrentdirectory/index.d.ts`, + content: "export function foo() { }" + }; + + const typingsCache = `/users/username/Library/Caches/typescript/2.7`; + const typingsCachePackageJson: FileOrFolder = { + path: `${typingsCache}/package.json`, + content: JSON.stringify({ + devDependencies: { + }, + }) + }; + + const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson]; + const host = createServerHost(files, { currentDirectory }); + + const typesRegistry = createMap(); + typesRegistry.set("pkgcurrentdirectory", void 0); + const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry); + + const projectService = createProjectService(host, { typingsInstaller }); + + projectService.setCompilerOptionsForInferredProjects({ + module: ModuleKind.CommonJS, + target: ScriptTarget.ES2016, + jsx: JsxEmit.Preserve, + experimentalDecorators: true, + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true + }); + + projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRootPath); + + const project = projectService.inferredProjects[0]; + assert.isDefined(project); + + // Ensure that we use result from types cache when getting ls + assert.isDefined(project.getLanguageService()); + + // Verify that the pkgcurrentdirectory from the current directory isnt picked up + checkProjectActualFiles(project, [file.path]); + }); + }); } diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index adf99094f76..a84520095a4 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -228,6 +228,37 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ externalProjects: 1 }); }); + it("external project - deduplicate from local @types packages", () => { + const appJs = { + path: "/a/b/app.js", + content: "" + }; + const nodeDts = { + path: "/node_modules/@types/node/index.d.ts", + content: "declare var node;" + }; + const host = createServerHost([appJs, nodeDts]); + const installer = new (class extends Installer { + constructor() { + super(host, { typesRegistry: createTypesRegistry("node") }); + } + installWorker() { + assert(false, "nothing should get installed"); + } + })(); + + const projectFileName = "/a/app/test.csproj"; + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: [toExternalFile(appJs.path)], + typeAcquisition: { enable: true, include: ["node"] } + }); + installer.checkPendingCommands(/*expectedCount*/ 0); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + }); + it("external project - no auto in typing acquisition, no .d.ts/js files", () => { const file1 = { path: "/a/b/app.ts", @@ -292,7 +323,7 @@ namespace ts.projectSystem { typeAcquisition: { enable: true, include: ["jquery"] } }); - assert(enqueueIsCalled, "expected enqueueIsCalled to be true"); + assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true"); installer.installAll(/*expectedCount*/ 1); // auto is set in type acquisition - use it even if project contains only .ts files @@ -598,7 +629,7 @@ namespace ts.projectSystem { installer.executePendingCommands(); // expected all typings file to exist for (const f of typingFiles) { - assert(host.fileExists(f.path), `expected file ${f.path} to exist`); + assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`); } host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); @@ -934,8 +965,8 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/1); - assert(host.fileExists(node.path), "typings for 'node' should be created"); - assert(host.fileExists(commander.path), "typings for 'commander' should be created"); + assert.isTrue(host.fileExists(node.path), "typings for 'node' should be created"); + assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created"); checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path]); }); @@ -978,7 +1009,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); }); - it("cached unresolved typings are not recomputed if program structure did not change", () => { + it("should recompute resolutions after typings are installed", () => { const host = createServerHost([]); const session = createSession(host); const f = { @@ -1020,7 +1051,7 @@ namespace ts.projectSystem { session.executeCommand(changeRequest); host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); - assert.equal(version1, version2, "set of unresolved imports should not change"); + assert.notEqual(version1, version2, "set of unresolved imports should change"); }); }); @@ -1076,7 +1107,7 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); installer.checkPendingCommands(/*expectedCount*/ 0); - assert(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name"); + assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name"); }); }); @@ -1225,7 +1256,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); - assert(seenTelemetryEvent); + assert.isTrue(seenTelemetryEvent); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); @@ -1276,10 +1307,10 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); - assert(!!beginEvent); - assert(!!endEvent); - assert(beginEvent.eventId === endEvent.eventId); - assert(endEvent.installSuccess); + assert.isTrue(!!beginEvent); + assert.isTrue(!!endEvent); + assert.isTrue(beginEvent.eventId === endEvent.eventId); + assert.isTrue(endEvent.installSuccess); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); @@ -1322,9 +1353,9 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); - assert(!!beginEvent); - assert(!!endEvent); - assert(beginEvent.eventId === endEvent.eventId); + assert.isTrue(!!beginEvent); + assert.isTrue(!!endEvent); + assert.isTrue(beginEvent.eventId === endEvent.eventId); assert.isFalse(endEvent.installSuccess); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]); diff --git a/src/harness/unittests/versionCache.ts b/src/harness/unittests/versionCache.ts index 60b478d4869..bbd23f25dac 100644 --- a/src/harness/unittests/versionCache.ts +++ b/src/harness/unittests/versionCache.ts @@ -34,7 +34,7 @@ var p:Point=new Point(); var q:Point=p;`; const { lines } = server.LineIndex.linesFromText(testContent); - assert(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); + assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); const lineIndex = new server.LineIndex(); lineIndex.load(lines); @@ -94,7 +94,7 @@ that was purple at the tips and grew 1cm per day`; ({ lines, lineMap } = server.LineIndex.linesFromText(testContent)); - assert(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); + assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); const lineIndex = new server.LineIndex(); lineIndex.load(lines); @@ -203,10 +203,10 @@ and grew 1cm per day`; const testFileName = "src/compiler/scanner.ts"; testContent = Harness.IO.readFile(testFileName); const totalChars = testContent.length; - assert(totalChars > 0, "Failed to read test file."); + assert.isTrue(totalChars > 0, "Failed to read test file."); ({ lines, lineMap } = server.LineIndex.linesFromText(testContent)); - assert(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); + assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line"); lineIndex = new server.LineIndex(); lineIndex.load(lines); diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 8accd6f621e..54572814d8e 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -162,7 +162,7 @@ namespace Utils { /** * Reads the directory at the given path and retrieves a list of file names and a list * of directory names within it. Suitable for use with ts.matchFiles() - * @param path The path to the directory to be read + * @param path The path to the directory to be read */ getAccessibleFileSystemEntries(path: string) { const entry = this.traversePath(path); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index d410fec9206..921d4674231 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -139,7 +139,7 @@ interface Array {}` function checkMapKeys(caption: string, map: Map, expectedKeys: ReadonlyArray) { verifyMapSize(caption, map, expectedKeys); for (const name of expectedKeys) { - assert(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); + assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } } @@ -168,7 +168,7 @@ interface Array {}` mapSeen.set(f, true); } } - assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`); + assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(arrayFrom(mapExpected.keys()))} in ${JSON.stringify(host.getOutput())}`); } export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray) { @@ -241,7 +241,7 @@ interface Array {}` ignoreWatchInvokedWithTriggerAsFileCreate: boolean; } - export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost { + export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost { args: string[] = []; private readonly output: string[] = []; @@ -547,7 +547,7 @@ interface Array {}` } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { + return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const directories: string[] = []; const files: string[] = []; const dirEntry = this.fs.get(this.toPath(dir)); diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index 001c63b44a1..951d34377a6 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -761,7 +761,7 @@ interface ProgressEventInit extends EventInit { } interface PushSubscriptionOptionsInit { - applicationServerKey?: any; + applicationServerKey?: BufferSource | null; userVisibleOnly?: boolean; } @@ -770,7 +770,8 @@ interface RegistrationOptions { } interface RequestInit { - body?: any; + signal?: AbortSignal; + body?: Blob | BufferSource | FormData | string | null; cache?: RequestCache; credentials?: RequestCredentials; headers?: HeadersInit; @@ -1088,7 +1089,7 @@ interface RTCTransportStats extends RTCStats { } interface ScopedCredentialDescriptor { - id: any; + id: BufferSource; transports?: Transport[]; type: ScopedCredentialType; } @@ -3596,11 +3597,11 @@ interface Element extends Node, GlobalEventHandlers, ElementTraversal, NodeSelec slot: string; readonly shadowRoot: ShadowRoot | null; getAttribute(name: string): string | null; - getAttributeNode(name: string): Attr; - getAttributeNodeNS(namespaceURI: string, localName: string): Attr; + getAttributeNode(name: string): Attr | null; + getAttributeNodeNS(namespaceURI: string, localName: string): Attr | null; getAttributeNS(namespaceURI: string, localName: string): string; - getBoundingClientRect(): ClientRect; - getClientRects(): ClientRectList; + getBoundingClientRect(): ClientRect | DOMRect; + getClientRects(): ClientRectList | DOMRectList; getElementsByTagName(name: K): NodeListOf; getElementsByTagName(name: K): NodeListOf; getElementsByTagName(name: string): NodeListOf; @@ -3757,9 +3758,10 @@ declare var External: { }; interface File extends Blob { - readonly lastModifiedDate: any; + readonly lastModifiedDate: Date; readonly name: string; readonly webkitRelativePath: string; + readonly lastModified: number; } declare var File: { @@ -4897,6 +4899,7 @@ interface HTMLFormElement extends HTMLElement { */ submit(): void; reportValidity(): boolean; + reportValidity(): boolean; addEventListener(type: K, listener: (this: HTMLFormElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: K, listener: (this: HTMLFormElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; @@ -5200,6 +5203,10 @@ interface HTMLIFrameElement extends HTMLElement, GetSVGDocument { * Sets or retrieves the width of the object. */ width: string; + /** + * Sets or retrives the content of the page that is to contain. + */ + srcdoc: string; addEventListener(type: K, listener: (this: HTMLIFrameElement, ev: HTMLIFrameElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: K, listener: (this: HTMLIFrameElement, ev: HTMLIFrameElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; @@ -5495,8 +5502,9 @@ interface HTMLInputElement extends HTMLElement { * Sets the start and end positions of a selection in a text field. * @param start The offset into the text field for the start of the selection. * @param end The offset into the text field for the end of the selection. + * @param direction The direction in which the selection is performed. */ - setSelectionRange(start?: number, end?: number, direction?: string): void; + setSelectionRange(start: number, end: number, direction?: "forward" | "backward" | "none"): void; /** * Decrements a range input control's value by the value given by the Step attribute. If the optional parameter is used, it will decrement the input control's step value multiplied by the parameter's value. * @param n Value to decrement the value by. @@ -6057,6 +6065,7 @@ interface HTMLObjectElement extends HTMLElement, GetSVGDocument { * Returns whether an element will successfully validate based on forms validation rules and constraints. */ readonly willValidate: boolean; + typemustmatch: boolean; /** * Returns whether a form will validate when it is submitted, without having to submit it. */ @@ -6958,8 +6967,9 @@ interface HTMLTextAreaElement extends HTMLElement { * Sets the start and end positions of a selection in a text field. * @param start The offset into the text field for the start of the selection. * @param end The offset into the text field for the end of the selection. + * @param direction The direction in which the selection is performed. */ - setSelectionRange(start: number, end: number): void; + setSelectionRange(start: number, end: number, direction?: "forward" | "backward" | "none"): void; addEventListener(type: K, listener: (this: HTMLTextAreaElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: K, listener: (this: HTMLTextAreaElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; @@ -7362,10 +7372,10 @@ declare var IntersectionObserver: { }; interface IntersectionObserverEntry { - readonly boundingClientRect: ClientRect; + readonly boundingClientRect: ClientRect | DOMRect; readonly intersectionRatio: number; - readonly intersectionRect: ClientRect; - readonly rootBounds: ClientRect; + readonly intersectionRect: ClientRect | DOMRect; + readonly rootBounds: ClientRect | DOMRect; readonly target: Element; readonly time: number; readonly isIntersecting: boolean; @@ -7534,7 +7544,7 @@ declare var MediaKeyMessageEvent: { interface MediaKeys { createSession(sessionType?: MediaKeySessionType): MediaKeySession; - setServerCertificate(serverCertificate: any): Promise; + setServerCertificate(serverCertificate: BufferSource): Promise; } declare var MediaKeys: { @@ -7548,10 +7558,10 @@ interface MediaKeySession extends EventTarget { readonly keyStatuses: MediaKeyStatusMap; readonly sessionId: string; close(): Promise; - generateRequest(initDataType: string, initData: any): Promise; + generateRequest(initDataType: string, initData: BufferSource): Promise; load(sessionId: string): Promise; remove(): Promise; - update(response: any): Promise; + update(response: BufferSource): Promise; } declare var MediaKeySession: { @@ -7562,8 +7572,8 @@ declare var MediaKeySession: { interface MediaKeyStatusMap { readonly size: number; forEach(callback: ForEachCallback): void; - get(keyId: any): MediaKeyStatus; - has(keyId: any): boolean; + get(keyId: BufferSource): MediaKeyStatus; + has(keyId: BufferSource): boolean; } declare var MediaKeyStatusMap: { @@ -9163,8 +9173,8 @@ interface Range { detach(): void; expand(Unit: ExpandGranularity): boolean; extractContents(): DocumentFragment; - getBoundingClientRect(): ClientRect; - getClientRects(): ClientRectList; + getBoundingClientRect(): ClientRect | DOMRect; + getClientRects(): ClientRectList | DOMRectList; insertNode(newNode: Node): void; selectNode(refNode: Node): void; selectNodeContents(refNode: Node): void; @@ -9227,6 +9237,7 @@ interface Request extends Object, Body { readonly referrerPolicy: ReferrerPolicy; readonly type: RequestType; readonly url: string; + readonly signal: AbortSignal; clone(): Request; } @@ -12213,7 +12224,7 @@ interface URL { declare var URL: { prototype: URL; - new(url: string, base?: string): URL; + new(url: string, base?: string | URL): URL; createObjectURL(object: any, options?: ObjectURLOptions): string; revokeObjectURL(url: string): void; }; @@ -12301,8 +12312,8 @@ declare var WaveShaperNode: { }; interface WebAuthentication { - getAssertion(assertionChallenge: any, options?: AssertionOptions): Promise; - makeCredential(accountInformation: Account, cryptoParameters: ScopedCredentialParameters[], attestationChallenge: any, options?: ScopedCredentialOptions): Promise; + getAssertion(assertionChallenge: BufferSource, options?: AssertionOptions): Promise; + makeCredential(accountInformation: Account, cryptoParameters: ScopedCredentialParameters[], attestationChallenge: BufferSource, options?: ScopedCredentialOptions): Promise; } declare var WebAuthentication: { @@ -14657,6 +14668,23 @@ declare var HTMLSummaryElement: { new(): HTMLSummaryElement; }; +interface DOMRectReadOnly { + readonly bottom: number; + readonly height: number; + readonly left: number; + readonly right: number; + readonly top: number; + readonly width: number; + readonly x: number; + readonly y: number; +} + +declare var DOMRectReadOnly: { + prototype: DOMRectReadOnly; + new (x?: number, y?: number, width?: number, height?: number): DOMRectReadOnly; + fromRect(rectangle?: DOMRectInit): DOMRectReadOnly; +}; + interface EXT_blend_minmax { readonly MIN_EXT: number; readonly MAX_EXT: number; @@ -14675,6 +14703,25 @@ interface EXT_sRGB { readonly FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT: number; } +interface DOMRect extends DOMRectReadOnly { + height: number; + width: number; + x: number; + y: number; +} + +declare var DOMRect: { + prototype: DOMRect; + new (x?: number, y?: number, width?: number, height?: number): DOMRect; + fromRect(rectangle?: DOMRectInit): DOMRect; +}; + +interface DOMRectList { + readonly length: number; + item(index: number): DOMRect | null; + [index: number]: DOMRect; +} + interface OES_vertex_array_object { readonly VERTEX_ARRAY_BINDING_OES: number; createVertexArrayOES(): WebGLVertexArrayObjectOES; @@ -14779,6 +14826,43 @@ interface WEBGL_lose_context { restoreContext(): void; } +interface AbortController { + readonly signal: AbortSignal; + abort(): void; +} + +declare var AbortController: { + prototype: AbortController; + new(): AbortController; +}; + +interface AbortSignal extends EventTarget { + readonly aborted: boolean; + onabort: (ev: Event) => any; +} + +interface EventSource extends EventTarget { + readonly url: string; + readonly withCredentials: boolean; + readonly CONNECTING: number; + readonly OPEN: number; + readonly CLOSED: number; + readonly readyState: number; + onopen: (evt: MessageEvent) => any; + onmessage: (evt: MessageEvent) => any; + onerror: (evt: MessageEvent) => any; + close(): void; +} + +declare var EventSource: { + prototype: EventSource; + new(url: string, eventSourceInitDict?: EventSourceInit): EventSource; +}; + +interface EventSourceInit { + readonly withCredentials: boolean; +} + declare type EventListenerOrEventListenerObject = EventListener | EventListenerObject; interface DecodeErrorCallback { @@ -14791,7 +14875,7 @@ interface ErrorEventHandler { (message: string, filename?: string, lineno?: number, colno?: number, error?: Error): void; } interface ForEachCallback { - (keyId: any, status: MediaKeyStatus): void; + (keyId: BufferSource, status: MediaKeyStatus): void; } interface FrameRequestCallback { (time: number): void; @@ -15029,6 +15113,7 @@ interface SVGElementTagNameMap { "view": SVGViewElement; } +/** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */ interface ElementTagNameMap extends HTMLElementTagNameMap, SVGElementTagNameMap { } declare var Audio: { new(src?: string): HTMLAudioElement; }; @@ -15246,7 +15331,7 @@ declare function removeEventListener(type: K, li declare function removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; type AAGUID = string; type AlgorithmIdentifier = string | Algorithm; -type BodyInit = any; +type BodyInit = Blob | BufferSource | FormData | string; type ByteString = string; type ConstrainBoolean = boolean | ConstrainBooleanParameters; type ConstrainDOMString = string | string[] | ConstrainDOMStringParameters; diff --git a/src/lib/es2015.collection.d.ts b/src/lib/es2015.collection.d.ts index 9b35fcbeffb..74759107851 100644 --- a/src/lib/es2015.collection.d.ts +++ b/src/lib/es2015.collection.d.ts @@ -10,7 +10,7 @@ interface Map { interface MapConstructor { new (): Map; - new (entries?: [K, V][]): Map; + new (entries?: ReadonlyArray<[K, V]>): Map; readonly prototype: Map; } declare var Map: MapConstructor; @@ -31,7 +31,7 @@ interface WeakMap { interface WeakMapConstructor { new (): WeakMap; - new (entries?: [K, V][]): WeakMap; + new (entries?: ReadonlyArray<[K, V]>): WeakMap; readonly prototype: WeakMap; } declare var WeakMap: WeakMapConstructor; @@ -47,7 +47,7 @@ interface Set { interface SetConstructor { new (): Set; - new (values?: T[]): Set; + new (values?: ReadonlyArray): Set; readonly prototype: Set; } declare var Set: SetConstructor; @@ -58,7 +58,7 @@ interface ReadonlySet { readonly size: number; } -interface WeakSet { +interface WeakSet { add(value: T): this; delete(value: T): boolean; has(value: T): boolean; @@ -66,7 +66,7 @@ interface WeakSet { interface WeakSetConstructor { new (): WeakSet; - new (values?: T[]): WeakSet; + new (values?: ReadonlyArray): WeakSet; readonly prototype: WeakSet; } declare var WeakSet: WeakSetConstructor; diff --git a/src/lib/es2015.core.d.ts b/src/lib/es2015.core.d.ts index ccff68968f1..eef20591a84 100644 --- a/src/lib/es2015.core.d.ts +++ b/src/lib/es2015.core.d.ts @@ -118,8 +118,9 @@ interface Math { log1p(x: number): number; /** - * Returns the result of (e^x - 1) of x (e raised to the power of x, where e is the base of - * the natural logarithms). + * Returns the result of (e^x - 1), which is an implementation-dependent approximation to + * subtracting 1 from the exponential function of x (e raised to the power of x, where e + * is the base of the natural logarithms). * @param x A numeric expression. */ expm1(x: number): number; diff --git a/src/lib/es2015.iterable.d.ts b/src/lib/es2015.iterable.d.ts index f3024bc334f..ccb7df6be69 100644 --- a/src/lib/es2015.iterable.d.ts +++ b/src/lib/es2015.iterable.d.ts @@ -52,7 +52,7 @@ interface ArrayConstructor { * Creates an array from an iterable object. * @param iterable An iterable object to convert to an array. */ - from(iterable: Iterable): T[]; + from(iterable: Iterable | ArrayLike): T[]; /** * Creates an array from an iterable object. @@ -60,7 +60,7 @@ interface ArrayConstructor { * @param mapfn A mapping function to call on every element of the array. * @param thisArg Value of 'this' used to invoke the mapfn. */ - from(iterable: Iterable, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; + from(iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } interface ReadonlyArray { @@ -180,7 +180,7 @@ interface SetConstructor { new (iterable: Iterable): Set; } -interface WeakSet { } +interface WeakSet { } interface WeakSetConstructor { new (iterable: Iterable): WeakSet; diff --git a/src/lib/es2015.symbol.wellknown.d.ts b/src/lib/es2015.symbol.wellknown.d.ts index 23d836c6515..681b6e8edfa 100644 --- a/src/lib/es2015.symbol.wellknown.d.ts +++ b/src/lib/es2015.symbol.wellknown.d.ts @@ -118,7 +118,7 @@ interface Set { readonly [Symbol.toStringTag]: "Set"; } -interface WeakSet { +interface WeakSet { readonly [Symbol.toStringTag]: "WeakSet"; } diff --git a/src/lib/es2017.object.d.ts b/src/lib/es2017.object.d.ts index 4014e8c2927..775aaece7f6 100644 --- a/src/lib/es2017.object.d.ts +++ b/src/lib/es2017.object.d.ts @@ -9,7 +9,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(o: any): any[]; + values(o: {}): any[]; /** * Returns an array of key/values of the enumerable properties of an object @@ -21,7 +21,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(o: any): [string, any][]; + entries(o: {}): [string, any][]; /** * Returns an object containing all own property descriptors of an object diff --git a/src/lib/es2018.d.ts b/src/lib/es2018.d.ts index 71d6e190b15..90f6d4931f4 100644 --- a/src/lib/es2018.d.ts +++ b/src/lib/es2018.d.ts @@ -1 +1 @@ -/// \ No newline at end of file +/// diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index aaefeec7d1a..0f1a68de4c2 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -550,7 +550,7 @@ interface Math { */ atan2(y: number, x: number): number; /** - * Returns the smallest number greater than or equal to its numeric argument. + * Returns the smallest integer greater than or equal to its numeric argument. * @param x A numeric expression. */ ceil(x: number): number; @@ -565,7 +565,7 @@ interface Math { */ exp(x: number): number; /** - * Returns the greatest number less than or equal to its numeric argument. + * Returns the greatest integer less than or equal to its numeric argument. * @param x A numeric expression. */ floor(x: number): number; @@ -992,12 +992,12 @@ interface ReadonlyArray { * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: ReadonlyArray[]): T[]; + concat(...items: (T[] | ReadonlyArray)[]): T[]; /** * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: (T | ReadonlyArray)[]): T[]; + concat(...items: (T | T[] | ReadonlyArray)[]): T[]; /** * Adds all the elements of an array separated by the specified separator string. * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma. @@ -1113,12 +1113,12 @@ interface Array { * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: ReadonlyArray[]): T[]; + concat(...items: (T[] | ReadonlyArray)[]): T[]; /** * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: (T | ReadonlyArray)[]): T[]; + concat(...items: (T | T[] | ReadonlyArray)[]): T[]; /** * Adds all the elements of an array separated by the specified separator string. * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma. diff --git a/src/lib/esnext.array.d.ts b/src/lib/esnext.array.d.ts new file mode 100644 index 00000000000..ddba47badaa --- /dev/null +++ b/src/lib/esnext.array.d.ts @@ -0,0 +1,203 @@ +interface ReadonlyArray { + + /** + * Calls a defined callback function on each element of an array. Then, flattens the result into + * a new array. + * This is identical to a map followed by a flatten of depth 1. + * + * @param callback A function that accepts up to three arguments. The flatMap method calls the + * callback function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callback function. If + * thisArg is omitted, undefined is used as the this value. + */ + flatMap ( + callback: (this: This, value: T, index: number, array: T[]) => U|U[], + thisArg?: This + ): U[] + + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: + ReadonlyArray | + + ReadonlyArray> | + ReadonlyArray[]> | + ReadonlyArray[][]> | + ReadonlyArray[][][]> | + + ReadonlyArray>> | + ReadonlyArray[][]>> | + ReadonlyArray>[][]> | + ReadonlyArray[]>[]> | + ReadonlyArray>[]> | + ReadonlyArray[]>> | + + ReadonlyArray>>> | + ReadonlyArray[]>>> | + ReadonlyArray>[]>> | + ReadonlyArray>>[]> | + + ReadonlyArray>>>>, + depth: 4): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: + ReadonlyArray | + + ReadonlyArray[][]> | + ReadonlyArray[]> | + ReadonlyArray> | + + ReadonlyArray>> | + ReadonlyArray[]>> | + ReadonlyArray>[]> | + + ReadonlyArray>>>, + depth: 3): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: + ReadonlyArray | + + ReadonlyArray> | + ReadonlyArray[]> | + + ReadonlyArray>>, + depth: 2): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: + ReadonlyArray | + ReadonlyArray>, + depth?: 1 + ): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: + ReadonlyArray, + depth: 0 + ): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. If no depth is provided, flatten method defaults to the depth of 1. + * + * @param depth The maximum recursion depth + */ + flatten(depth?: number): any[]; + } + +interface Array { + + /** + * Calls a defined callback function on each element of an array. Then, flattens the result into + * a new array. + * This is identical to a map followed by a flatten of depth 1. + * + * @param callback A function that accepts up to three arguments. The flatMap method calls the + * callback function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callback function. If + * thisArg is omitted, undefined is used as the this value. + */ + flatMap ( + callback: (this: This, value: T, index: number, array: T[]) => U|U[], + thisArg?: This + ): U[] + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][][][][][][], depth: 7): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][][][][][], depth: 6): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][][][][], depth: 5): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][][][], depth: 4): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][][], depth: 3): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][][], depth: 2): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[][], depth?: 1): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flatten(this: U[], depth: 0): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. If no depth is provided, flatten method defaults to the depth of 1. + * + * @param depth The maximum recursion depth + */ + flatten(depth?: number): any[]; +} diff --git a/src/lib/esnext.d.ts b/src/lib/esnext.d.ts index 71fab82a866..bbfb9535aa7 100644 --- a/src/lib/esnext.d.ts +++ b/src/lib/esnext.d.ts @@ -1,2 +1,4 @@ -/// +/// /// +/// +/// diff --git a/src/lib/esnext.promise.d.ts b/src/lib/esnext.promise.d.ts new file mode 100644 index 00000000000..28f903870b6 --- /dev/null +++ b/src/lib/esnext.promise.d.ts @@ -0,0 +1,12 @@ +/** + * Represents the completion of an asynchronous operation + */ +interface Promise { + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): Promise +} diff --git a/src/lib/webworker.generated.d.ts b/src/lib/webworker.generated.d.ts index 6eb17c33c5e..1b0ce074f00 100644 --- a/src/lib/webworker.generated.d.ts +++ b/src/lib/webworker.generated.d.ts @@ -66,12 +66,13 @@ interface ObjectURLOptions { } interface PushSubscriptionOptionsInit { - applicationServerKey?: any; + applicationServerKey?: BufferSource | null; userVisibleOnly?: boolean; } interface RequestInit { - body?: any; + signal?: AbortSignal; + body?: Blob | BufferSource | FormData | string | null; cache?: RequestCache; credentials?: RequestCredentials; headers?: HeadersInit; @@ -119,7 +120,7 @@ interface NotificationEventInit extends ExtendableEventInit { } interface PushEventInit extends ExtendableEventInit { - data?: any; + data?: BufferSource | USVString; } interface SyncEventInit extends ExtendableEventInit { @@ -414,9 +415,10 @@ declare var EventTarget: { }; interface File extends Blob { - readonly lastModifiedDate: any; + readonly lastModifiedDate: Date; readonly name: string; readonly webkitRelativePath: string; + readonly lastModified: number; } declare var File: { @@ -959,6 +961,7 @@ interface Request extends Object, Body { readonly referrerPolicy: ReferrerPolicy; readonly type: RequestType; readonly url: string; + readonly signal: AbortSignal; clone(): Request; } @@ -1061,7 +1064,7 @@ interface URL { declare var URL: { prototype: URL; - new(url: string, base?: string): URL; + new(url: string, base?: string | URL): URL; createObjectURL(object: any, options?: ObjectURLOptions): string; revokeObjectURL(url: string): void; }; @@ -1821,6 +1824,43 @@ interface AddEventListenerOptions extends EventListenerOptions { once?: boolean; } +interface AbortController { + readonly signal: AbortSignal; + abort(): void; +} + +declare var AbortController: { + prototype: AbortController; + new(): AbortController; +}; + +interface AbortSignal extends EventTarget { + readonly aborted: boolean; + onabort: (ev: Event) => any; +} + +interface EventSource extends EventTarget { + readonly url: string; + readonly withCredentials: boolean; + readonly CONNECTING: number; + readonly OPEN: number; + readonly CLOSED: number; + readonly readyState: number; + onopen: (evt: MessageEvent) => any; + onmessage: (evt: MessageEvent) => any; + onerror: (evt: MessageEvent) => any; + close(): void; +} + +declare var EventSource: { + prototype: EventSource; + new(url: string, eventSourceInitDict?: EventSourceInit): EventSource; +}; + +interface EventSourceInit { + readonly withCredentials: boolean; +} + declare type EventListenerOrEventListenerObject = EventListener | EventListenerObject; interface DecodeErrorCallback { @@ -1833,7 +1873,7 @@ interface ErrorEventHandler { (message: string, filename?: string, lineno?: number, colno?: number, error?: Error): void; } interface ForEachCallback { - (keyId: any, status: MediaKeyStatus): void; + (keyId: BufferSource, status: MediaKeyStatus): void; } interface FunctionStringCallback { (data: string): void; @@ -1883,7 +1923,7 @@ declare function addEventListener(type: string, listener: EventListenerOrEventLi declare function removeEventListener(type: K, listener: (this: DedicatedWorkerGlobalScope, ev: DedicatedWorkerGlobalScopeEventMap[K]) => any, options?: boolean | EventListenerOptions): void; declare function removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; type AlgorithmIdentifier = string | Algorithm; -type BodyInit = any; +type BodyInit = Blob | BufferSource | FormData | string; type IDBKeyPath = string; type RequestInfo = Request | string; type USVString = string; diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7ed37669f34..b01830dcac5 100644 --- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -145,7 +145,7 @@ - + @@ -154,7 +154,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -796,7 +796,7 @@ - + @@ -866,52 +866,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1378,7 +1384,7 @@ - + @@ -1541,10 +1547,13 @@ - + - + + + + @@ -1955,34 +1964,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2058,6 +2070,15 @@ + + + + + + + + + @@ -2354,37 +2375,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2462,10 +2495,13 @@ - + - + + + + @@ -3429,15 +3465,6 @@ - - - - - - - - - @@ -3731,31 +3758,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3770,10 +3803,13 @@ - + - + + + + @@ -3920,37 +3956,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4481,10 +4529,13 @@ - + - + + + + @@ -4701,6 +4752,15 @@ + + + + + + + + + @@ -4791,6 +4851,15 @@ + + + + + + + + + @@ -5396,10 +5465,13 @@ - + - + + + + @@ -5849,10 +5921,13 @@ - + - + + + + @@ -6275,10 +6350,13 @@ - + - + + + + @@ -6390,15 +6468,12 @@ - + - + - + - - - @@ -6504,6 +6579,15 @@ + + + + + + + + + @@ -6582,6 +6666,9 @@ + + + @@ -7350,7 +7437,7 @@ - + @@ -8665,7 +8752,7 @@ - + @@ -8866,7 +8953,7 @@ - + @@ -8875,7 +8962,7 @@ - + @@ -8884,7 +8971,7 @@ - + diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl index ea8a9bd9838..921ca600aa6 100644 --- a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -145,7 +145,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -866,52 +866,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - + - - - @@ -1541,10 +1547,13 @@ - + - + + + + @@ -1955,34 +1964,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2058,6 +2070,15 @@ + + + + + + + + + @@ -2354,37 +2375,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2462,10 +2495,13 @@ - + - + + + + @@ -3220,7 +3256,7 @@ - + @@ -3429,15 +3465,6 @@ - - - - - - - - - @@ -3731,31 +3758,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3770,10 +3803,13 @@ - + - + + + + @@ -3920,37 +3956,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4481,10 +4529,13 @@ - + - + + + + @@ -4701,6 +4752,15 @@ + + + + + + + + + @@ -4791,6 +4851,15 @@ + + + + + + + + + @@ -5396,10 +5465,13 @@ - + - + + + + @@ -5443,7 +5515,7 @@ - + @@ -5849,10 +5921,13 @@ - + - + + + + @@ -6275,10 +6350,13 @@ - + - + + + + @@ -6390,15 +6468,12 @@ - + - + - + - - - @@ -6504,6 +6579,15 @@ + + + + + + + + + @@ -6571,7 +6655,7 @@ - + @@ -6582,6 +6666,9 @@ + + + @@ -7350,7 +7437,7 @@ - + @@ -8374,7 +8461,7 @@ - + @@ -8383,7 +8470,7 @@ - + @@ -8413,7 +8500,7 @@ - + diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl index 1f5a92cdf4a..30850814f26 100644 --- a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -154,7 +154,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -190,7 +190,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -550,7 +550,7 @@ - + @@ -805,7 +805,7 @@ - + @@ -875,52 +875,58 @@ - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1550,10 +1556,13 @@ - + - + + + + @@ -1964,34 +1973,37 @@ - + - + - + - + - + - + + + + @@ -2067,6 +2079,15 @@ + + + + + + + + + @@ -2089,7 +2110,7 @@ - + @@ -2363,37 +2384,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2471,10 +2504,13 @@ - + + + + @@ -2836,7 +2872,7 @@ - + @@ -3438,15 +3474,6 @@ - - - - - - - - - @@ -3740,31 +3767,37 @@ - + + + + - + - + - + - + - + + + + @@ -3779,10 +3812,13 @@ - + - + + + + @@ -3929,37 +3965,49 @@ - + - + + + + - + - + + + + - + + + + - + + + + @@ -4177,7 +4225,7 @@ - + @@ -4186,7 +4234,7 @@ - + @@ -4490,10 +4538,13 @@ - + + + + @@ -4710,6 +4761,15 @@ + + + + + + + + + @@ -4800,6 +4860,15 @@ + + + + + + + + + @@ -5405,10 +5474,13 @@ - + + + + @@ -5858,10 +5930,13 @@ - + - + + + + @@ -6284,10 +6359,13 @@ - + - + + + + @@ -6358,7 +6436,7 @@ - + @@ -6399,15 +6477,12 @@ - + - + - + - - - @@ -6513,6 +6588,15 @@ + + + + + + + + + @@ -6591,6 +6675,9 @@ + + + @@ -7359,7 +7446,7 @@ - + @@ -8638,7 +8725,7 @@ - + @@ -8875,7 +8962,7 @@ - + @@ -8884,7 +8971,7 @@ - + @@ -8893,7 +8980,7 @@ - + diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl index 6a88538b293..922edbca2de 100644 --- a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -864,8 +864,8 @@ - - + + @@ -876,8 +876,8 @@ - - + + @@ -888,8 +888,8 @@ - - + + @@ -900,8 +900,8 @@ - - + + @@ -912,6 +912,9 @@ + + + @@ -1542,8 +1545,8 @@ - - + + @@ -1959,8 +1962,8 @@ - - + + @@ -1983,8 +1986,8 @@ - - + + @@ -2064,6 +2067,15 @@ + + + + + + + + + @@ -2188,7 +2200,7 @@ - + @@ -2361,8 +2373,8 @@ - - + + @@ -2373,8 +2385,8 @@ - - + + @@ -2385,8 +2397,8 @@ - - + + @@ -2397,8 +2409,8 @@ - - + + @@ -2481,8 +2493,8 @@ - - + + @@ -3450,15 +3462,6 @@ - - - - - - - - - @@ -3753,8 +3756,8 @@ - - + + @@ -3777,7 +3780,7 @@ - + @@ -3798,8 +3801,8 @@ - - + + @@ -3951,8 +3954,8 @@ - - + + @@ -3963,8 +3966,8 @@ - - + + @@ -3975,8 +3978,8 @@ - - + + @@ -3987,8 +3990,8 @@ - - + + @@ -4524,8 +4527,8 @@ - - + + @@ -4746,6 +4749,15 @@ + + + + + + + + + @@ -4836,6 +4848,15 @@ + + + + + + + + + @@ -5439,8 +5460,8 @@ - - + + @@ -5895,7 +5916,7 @@ - + @@ -6321,8 +6342,8 @@ - - + + @@ -6441,6 +6462,9 @@ + + + @@ -6546,6 +6570,15 @@ + + + + + + + + + @@ -6624,6 +6657,9 @@ + + + @@ -7395,6 +7431,9 @@ + + + diff --git a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 5f702fd6c58..c2febd1ee9b 100644 --- a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -427,7 +427,7 @@ - + @@ -436,7 +436,7 @@ - + @@ -523,7 +523,7 @@ - + @@ -875,52 +875,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1063,7 +1069,7 @@ - + @@ -1414,7 +1420,7 @@ - + @@ -1426,7 +1432,7 @@ - + @@ -1550,10 +1556,13 @@ - + - + + + + @@ -1964,34 +1973,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2067,6 +2079,15 @@ + + + + + + + + + @@ -2363,37 +2384,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2471,10 +2504,13 @@ - + - + + + + @@ -2908,7 +2944,7 @@ - + @@ -2947,7 +2983,7 @@ - + @@ -3438,15 +3474,6 @@ - - - - - - - - - @@ -3740,31 +3767,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3779,10 +3812,13 @@ - + - + + + + @@ -3868,7 +3904,7 @@ - + @@ -3929,37 +3965,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4490,10 +4538,13 @@ - + - + + + + @@ -4710,6 +4761,15 @@ + + + + + + + + + @@ -4800,6 +4860,15 @@ + + + + + + + + + @@ -5405,10 +5474,13 @@ - + - + + + + @@ -5858,10 +5930,13 @@ - + - + + + + @@ -6284,10 +6359,13 @@ - + - + + + + @@ -6399,15 +6477,12 @@ - + - + - + - - - @@ -6513,6 +6588,15 @@ + + + + + + + + + @@ -6591,6 +6675,9 @@ + + + @@ -6883,7 +6970,7 @@ - + @@ -7021,7 +7108,7 @@ - + @@ -7359,7 +7446,7 @@ - + diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index ce91987a422..efd7b273f5d 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -154,7 +154,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -190,7 +190,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -550,7 +550,7 @@ - + @@ -805,7 +805,7 @@ - + @@ -875,52 +875,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1550,10 +1556,13 @@ - + - + + + + @@ -1964,34 +1973,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2067,6 +2079,15 @@ + + + + + + + + + @@ -2363,37 +2384,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2471,10 +2504,13 @@ - + - + + + + @@ -3438,15 +3474,6 @@ - - - - - - - - - @@ -3740,31 +3767,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3779,10 +3812,13 @@ - + - + + + + @@ -3929,37 +3965,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4490,10 +4538,13 @@ - + - + + + + @@ -4710,6 +4761,15 @@ + + + + + + + + + @@ -4800,6 +4860,15 @@ + + + + + + + + + @@ -4876,7 +4945,7 @@ - + @@ -5405,10 +5474,13 @@ - + - + + + + @@ -5858,10 +5930,13 @@ - + - + + + + @@ -6284,10 +6359,13 @@ - + - + + + + @@ -6399,15 +6477,12 @@ - + - + - + - - - @@ -6513,6 +6588,15 @@ + + + + + + + + + @@ -6591,6 +6675,9 @@ + + + @@ -7359,11 +7446,11 @@ - + - + @@ -8875,7 +8962,7 @@ - + @@ -8884,7 +8971,7 @@ - + @@ -8893,7 +8980,7 @@ - + diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index ffa39896b4c..8f52506d6b9 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -866,52 +866,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1054,7 +1060,7 @@ - + @@ -1099,7 +1105,7 @@ - + @@ -1541,10 +1547,13 @@ - + - + + + + @@ -1955,34 +1964,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2058,6 +2070,15 @@ + + + + + + + + + @@ -2146,7 +2167,7 @@ - + @@ -2354,37 +2375,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2462,10 +2495,13 @@ - + - + + + + @@ -2554,7 +2590,7 @@ - + @@ -2800,7 +2836,7 @@ - + @@ -2827,7 +2863,7 @@ - + @@ -2899,7 +2935,7 @@ - + @@ -2908,7 +2944,7 @@ - + @@ -2917,7 +2953,7 @@ - + @@ -3001,7 +3037,7 @@ - + @@ -3019,7 +3055,7 @@ - + @@ -3429,15 +3465,6 @@ - - - - - - - - - @@ -3731,31 +3758,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3770,10 +3803,13 @@ - + - + + + + @@ -3859,7 +3895,7 @@ - + @@ -3868,7 +3904,7 @@ - + @@ -3877,7 +3913,7 @@ - + @@ -3920,37 +3956,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4481,10 +4529,13 @@ - + - + + + + @@ -4701,6 +4752,15 @@ + + + + + + + + + @@ -4791,6 +4851,15 @@ + + + + + + + + + @@ -5396,10 +5465,13 @@ - + - + + + + @@ -5849,10 +5921,13 @@ - + - + + + + @@ -6223,7 +6298,7 @@ - + @@ -6275,10 +6350,13 @@ - + - + + + + @@ -6390,15 +6468,12 @@ - + - + - + - - - @@ -6504,6 +6579,15 @@ + + + + + + + + + @@ -7066,7 +7150,7 @@ - + @@ -7353,7 +7437,7 @@ - + @@ -8398,7 +8482,7 @@ - + @@ -8407,7 +8491,7 @@ - + diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 6067b27a513..76488c39e76 100644 --- a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -145,7 +145,7 @@ - + @@ -154,7 +154,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -418,7 +418,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -796,7 +796,7 @@ - + @@ -866,52 +866,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1099,7 +1105,7 @@ - + @@ -1541,10 +1547,13 @@ - + - + + + + @@ -1955,34 +1964,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2058,6 +2070,15 @@ + + + + + + + + + @@ -2173,7 +2194,7 @@ - + @@ -2182,7 +2203,7 @@ - + @@ -2354,37 +2375,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2410,7 +2443,7 @@ - + @@ -2462,10 +2495,13 @@ - + - + + + + @@ -2554,7 +2590,7 @@ - + @@ -2818,7 +2854,7 @@ - + @@ -2899,7 +2935,7 @@ - + @@ -2917,7 +2953,7 @@ - + @@ -3148,7 +3184,7 @@ - + @@ -3157,7 +3193,7 @@ - + @@ -3166,7 +3202,7 @@ - + @@ -3429,15 +3465,6 @@ - - - - - - - - - @@ -3731,31 +3758,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3763,17 +3796,20 @@ - + - + - + + + + @@ -3868,7 +3904,7 @@ - + @@ -3920,37 +3956,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4481,10 +4529,13 @@ - + - + + + + @@ -4537,7 +4588,7 @@ - + @@ -4546,7 +4597,7 @@ - + @@ -4701,6 +4752,15 @@ + + + + + + + + + @@ -4791,6 +4851,15 @@ + + + + + + + + + @@ -4867,7 +4936,7 @@ - + @@ -5140,7 +5209,7 @@ - + @@ -5149,7 +5218,7 @@ - + @@ -5158,7 +5227,7 @@ - + @@ -5167,7 +5236,7 @@ - + @@ -5176,7 +5245,7 @@ - + @@ -5185,7 +5254,7 @@ - + @@ -5194,7 +5263,7 @@ - + @@ -5203,7 +5272,7 @@ - + @@ -5212,7 +5281,7 @@ - + @@ -5221,7 +5290,7 @@ - + @@ -5248,7 +5317,7 @@ - + @@ -5257,7 +5326,7 @@ - + @@ -5266,7 +5335,7 @@ - + @@ -5275,7 +5344,7 @@ - + @@ -5284,7 +5353,7 @@ - + @@ -5293,7 +5362,7 @@ - + @@ -5302,7 +5371,7 @@ - + @@ -5311,7 +5380,7 @@ - + @@ -5338,7 +5407,7 @@ - + @@ -5347,7 +5416,7 @@ - + @@ -5356,7 +5425,7 @@ - + @@ -5365,7 +5434,7 @@ - + @@ -5396,10 +5465,13 @@ - + - + + + + @@ -5443,7 +5515,7 @@ - + @@ -5635,7 +5707,7 @@ - + @@ -5644,7 +5716,7 @@ - + @@ -5716,7 +5788,7 @@ - + @@ -5725,7 +5797,7 @@ - + @@ -5734,7 +5806,7 @@ - + @@ -5743,7 +5815,7 @@ - + @@ -5752,7 +5824,7 @@ - + @@ -5761,7 +5833,7 @@ - + @@ -5770,7 +5842,7 @@ - + @@ -5779,7 +5851,7 @@ - + @@ -5788,7 +5860,7 @@ - + @@ -5797,7 +5869,7 @@ - + @@ -5806,7 +5878,7 @@ - + @@ -5815,7 +5887,7 @@ - + @@ -5849,10 +5921,13 @@ - + - + + + + @@ -6040,7 +6115,7 @@ - + @@ -6049,7 +6124,7 @@ - + @@ -6058,7 +6133,7 @@ - + @@ -6067,7 +6142,7 @@ - + @@ -6088,7 +6163,7 @@ - + @@ -6097,7 +6172,7 @@ - + @@ -6106,7 +6181,7 @@ - + @@ -6115,7 +6190,7 @@ - + @@ -6124,7 +6199,7 @@ - + @@ -6133,7 +6208,7 @@ - + @@ -6142,7 +6217,7 @@ - + @@ -6151,7 +6226,7 @@ - + @@ -6160,7 +6235,7 @@ - + @@ -6169,7 +6244,7 @@ - + @@ -6178,7 +6253,7 @@ - + @@ -6187,7 +6262,7 @@ - + @@ -6196,7 +6271,7 @@ - + @@ -6205,7 +6280,7 @@ - + @@ -6214,7 +6289,7 @@ - + @@ -6223,7 +6298,7 @@ - + @@ -6232,7 +6307,7 @@ - + @@ -6241,7 +6316,7 @@ - + @@ -6250,7 +6325,7 @@ - + @@ -6275,10 +6350,13 @@ - + - + + + + @@ -6390,15 +6468,12 @@ - + - + - + - - - @@ -6504,6 +6579,15 @@ + + + + + + + + + @@ -6582,6 +6666,9 @@ + + + @@ -6754,7 +6841,7 @@ - + @@ -7350,7 +7437,7 @@ - + @@ -7549,7 +7636,7 @@ - + @@ -7558,7 +7645,7 @@ - + @@ -7567,7 +7654,7 @@ - + @@ -7576,7 +7663,7 @@ - + @@ -7585,7 +7672,7 @@ - + @@ -7603,7 +7690,7 @@ - + @@ -7612,7 +7699,7 @@ - + @@ -7621,7 +7708,7 @@ - + @@ -8395,7 +8482,7 @@ - + @@ -8404,7 +8491,7 @@ - + @@ -8431,7 +8518,7 @@ - + @@ -8476,7 +8563,7 @@ - + @@ -8485,7 +8572,7 @@ - + @@ -8866,7 +8953,7 @@ - + @@ -8875,7 +8962,7 @@ - + @@ -8884,7 +8971,7 @@ - + diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index 35fa1147eac..2418583bb98 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -145,7 +145,7 @@ - + @@ -154,7 +154,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -337,7 +337,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -796,7 +796,7 @@ - + @@ -866,52 +866,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1541,10 +1547,13 @@ - + - + + + + @@ -1955,34 +1964,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2058,6 +2070,15 @@ + + + + + + + + + @@ -2354,37 +2375,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2462,10 +2495,13 @@ - + - + + + + @@ -2764,7 +2800,7 @@ - + @@ -2836,7 +2872,7 @@ - + @@ -3082,7 +3118,7 @@ - + @@ -3175,7 +3211,7 @@ - + @@ -3429,15 +3465,6 @@ - - - - - - - - - @@ -3731,31 +3758,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3770,10 +3803,13 @@ - + - + + + + @@ -3920,37 +3956,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4441,7 +4489,7 @@ - + @@ -4481,10 +4529,13 @@ - + - + + + + @@ -4701,6 +4752,15 @@ + + + + + + + + + @@ -4791,6 +4851,15 @@ + + + + + + + + + @@ -5396,10 +5465,13 @@ - + - + + + + @@ -5849,10 +5921,13 @@ - + - + + + + @@ -6275,10 +6350,13 @@ - + - + + + + @@ -6390,15 +6468,12 @@ - + - + - + - - - @@ -6504,6 +6579,15 @@ + + + + + + + + + @@ -6582,6 +6666,9 @@ + + + @@ -7350,7 +7437,7 @@ - + @@ -8866,7 +8953,7 @@ - + @@ -8875,7 +8962,7 @@ - + @@ -8884,7 +8971,7 @@ - + diff --git a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 23d85fb19e7..42685f7a45e 100644 --- a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -138,7 +138,7 @@ - + @@ -147,7 +147,7 @@ - + @@ -156,7 +156,7 @@ - + @@ -165,7 +165,7 @@ - + @@ -174,7 +174,7 @@ - + @@ -525,7 +525,7 @@ - + @@ -534,7 +534,7 @@ - + @@ -786,7 +786,7 @@ - + @@ -857,8 +857,8 @@ - - + + @@ -869,8 +869,8 @@ - - + + @@ -881,8 +881,8 @@ - - + + @@ -893,8 +893,8 @@ - - + + @@ -905,6 +905,9 @@ + + + @@ -1371,7 +1374,7 @@ - + @@ -1380,7 +1383,7 @@ - + @@ -1535,8 +1538,8 @@ - - + + @@ -1952,8 +1955,8 @@ - - + + @@ -1964,8 +1967,8 @@ - - + + @@ -1976,8 +1979,8 @@ - - + + @@ -2057,6 +2060,15 @@ + + + + + + + + + @@ -2354,8 +2366,8 @@ - - + + @@ -2366,8 +2378,8 @@ - - + + @@ -2378,8 +2390,8 @@ - - + + @@ -2390,8 +2402,8 @@ - - + + @@ -2474,8 +2486,8 @@ - - + + @@ -3443,15 +3455,6 @@ - - - - - - - - - @@ -3746,8 +3749,8 @@ - - + + @@ -3758,8 +3761,8 @@ - - + + @@ -3770,8 +3773,8 @@ - - + + @@ -3791,8 +3794,8 @@ - - + + @@ -3944,8 +3947,8 @@ - - + + @@ -3956,8 +3959,8 @@ - - + + @@ -3968,8 +3971,8 @@ - - + + @@ -3980,8 +3983,8 @@ - - + + @@ -4517,8 +4520,8 @@ - - + + @@ -4739,6 +4742,15 @@ + + + + + + + + + @@ -4829,6 +4841,15 @@ + + + + + + + + + @@ -5432,8 +5453,8 @@ - - + + @@ -5888,8 +5909,8 @@ - - + + @@ -6314,8 +6335,8 @@ - - + + @@ -6434,6 +6455,9 @@ + + + @@ -6539,6 +6563,15 @@ + + + + + + + + + @@ -7391,6 +7424,9 @@ + + + @@ -8901,7 +8937,7 @@ - + @@ -8910,7 +8946,7 @@ - + @@ -8919,7 +8955,7 @@ - + diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl index e80dfb3e455..bcace1bd162 100644 --- a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -138,7 +138,7 @@ - + @@ -147,7 +147,7 @@ - + @@ -156,7 +156,7 @@ - + @@ -165,7 +165,7 @@ - + @@ -174,7 +174,7 @@ - + @@ -210,7 +210,7 @@ - + @@ -276,7 +276,7 @@ - + @@ -411,7 +411,7 @@ - + @@ -525,7 +525,7 @@ - + @@ -534,7 +534,7 @@ - + @@ -786,7 +786,7 @@ - + @@ -856,48 +856,57 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - + @@ -1041,7 +1050,7 @@ - + @@ -1086,7 +1095,7 @@ - + @@ -1528,10 +1537,13 @@ - + - + + + + @@ -1942,31 +1954,37 @@ - + - + - + - + - + + + + - + - + + + + @@ -2042,6 +2060,15 @@ + + + + + + + + + @@ -2166,7 +2193,7 @@ - + @@ -2338,37 +2365,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2446,10 +2485,13 @@ - + - + + + + @@ -2874,7 +2916,7 @@ - + @@ -2883,7 +2925,7 @@ - + @@ -2892,7 +2934,7 @@ - + @@ -2901,7 +2943,7 @@ - + @@ -2910,7 +2952,7 @@ - + @@ -2922,7 +2964,7 @@ - + @@ -3413,15 +3455,6 @@ - - - - - - - - - @@ -3715,28 +3748,37 @@ - + - + + + + - + - + + + + - + - + + + + @@ -3751,10 +3793,13 @@ - + - + + + + @@ -3840,7 +3885,7 @@ - + @@ -3849,7 +3894,7 @@ - + @@ -3858,7 +3903,7 @@ - + @@ -3901,37 +3946,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4462,10 +4519,13 @@ - + - + + + + @@ -4682,6 +4742,15 @@ + + + + + + + + + @@ -4772,6 +4841,15 @@ + + + + + + + + + @@ -5374,10 +5452,13 @@ - + - + + + + @@ -5421,7 +5502,7 @@ - + @@ -5827,10 +5908,13 @@ - + - + + + + @@ -6250,10 +6334,13 @@ - + - + + + + @@ -6365,11 +6452,11 @@ - + - + - + @@ -6476,6 +6563,15 @@ + + + + + + + + + @@ -6554,6 +6650,9 @@ + + + @@ -6846,7 +6945,7 @@ - + @@ -6984,7 +7083,7 @@ - + @@ -7322,11 +7421,11 @@ - + - + @@ -8346,7 +8445,7 @@ - + @@ -8355,7 +8454,7 @@ - + @@ -8367,7 +8466,7 @@ - + @@ -8376,7 +8475,7 @@ - + @@ -8385,7 +8484,7 @@ - + @@ -8838,7 +8937,7 @@ - + @@ -8847,7 +8946,7 @@ - + @@ -8856,7 +8955,7 @@ - + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index 2b490cc372c..ca1dcf2d4cb 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -144,7 +144,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -162,7 +162,7 @@ - + @@ -171,7 +171,7 @@ - + @@ -180,7 +180,7 @@ - + @@ -531,7 +531,7 @@ - + @@ -540,7 +540,7 @@ - + @@ -795,7 +795,7 @@ - + @@ -865,52 +865,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -1540,10 +1546,13 @@ - + - + + + + @@ -1954,34 +1963,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2057,6 +2069,15 @@ + + + + + + + + + @@ -2353,37 +2374,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2461,10 +2494,13 @@ - + - + + + + @@ -3009,7 +3045,7 @@ - + @@ -3428,15 +3464,6 @@ - - - - - - - - - @@ -3730,31 +3757,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3769,10 +3802,13 @@ - + - + + + + @@ -3919,37 +3955,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4480,10 +4528,13 @@ - + - + + + + @@ -4700,6 +4751,15 @@ + + + + + + + + + @@ -4790,6 +4850,15 @@ + + + + + + + + + @@ -5395,10 +5464,13 @@ - + - + + + + @@ -5848,10 +5920,13 @@ - + - + + + + @@ -6274,10 +6349,13 @@ - + - + + + + @@ -6389,15 +6467,12 @@ - + - + - + - - - @@ -6503,6 +6578,15 @@ + + + + + + + + + @@ -6581,6 +6665,9 @@ + + + @@ -7349,7 +7436,7 @@ - + @@ -8865,7 +8952,7 @@ - + @@ -8874,7 +8961,7 @@ - + @@ -8883,7 +8970,7 @@ - + diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 43e951d8504..34cf0f01ff6 100644 --- a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -411,7 +411,7 @@ - + @@ -859,52 +859,58 @@ - + - + - + - + - + + + + - + - + + + + - + - + + + + - + - - - @@ -975,7 +981,7 @@ - + @@ -1047,7 +1053,7 @@ - + @@ -1092,7 +1098,7 @@ - + @@ -1534,10 +1540,13 @@ - + - + + + + @@ -1948,34 +1957,37 @@ - + - + - + - + - + - + - + - + + + + @@ -2051,6 +2063,15 @@ + + + + + + + + + @@ -2166,7 +2187,7 @@ - + @@ -2347,37 +2368,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -2455,10 +2488,13 @@ - + - + + + + @@ -2547,7 +2583,7 @@ - + @@ -2892,7 +2928,7 @@ - + @@ -2901,7 +2937,7 @@ - + @@ -2910,7 +2946,7 @@ - + @@ -2958,7 +2994,7 @@ - + @@ -2985,7 +3021,7 @@ - + @@ -2994,7 +3030,7 @@ - + @@ -3141,7 +3177,7 @@ - + @@ -3150,7 +3186,7 @@ - + @@ -3422,15 +3458,6 @@ - - - - - - - - - @@ -3724,31 +3751,37 @@ - + - + + + + - + - + - + - + - + + + + @@ -3763,10 +3796,13 @@ - + - + + + + @@ -3852,7 +3888,7 @@ - + @@ -3861,7 +3897,7 @@ - + @@ -3870,7 +3906,7 @@ - + @@ -3913,37 +3949,49 @@ - + - + + + + - + - + + + + - + - + + + + - + - + + + + @@ -4474,10 +4522,13 @@ - + - + + + + @@ -4641,7 +4692,7 @@ - + @@ -4694,6 +4745,15 @@ + + + + + + + + + @@ -4784,6 +4844,15 @@ + + + + + + + + + @@ -5151,7 +5220,7 @@ - + @@ -5196,7 +5265,7 @@ - + @@ -5259,7 +5328,7 @@ - + @@ -5286,7 +5355,7 @@ - + @@ -5331,7 +5400,7 @@ - + @@ -5340,7 +5409,7 @@ - + @@ -5349,7 +5418,7 @@ - + @@ -5358,7 +5427,7 @@ - + @@ -5389,10 +5458,13 @@ - + - + + + + @@ -5436,7 +5508,7 @@ - + @@ -5736,7 +5808,7 @@ - + @@ -5763,7 +5835,7 @@ - + @@ -5790,7 +5862,7 @@ - + @@ -5842,10 +5914,13 @@ - + - + + + + @@ -6144,7 +6219,7 @@ - + @@ -6153,7 +6228,7 @@ - + @@ -6162,7 +6237,7 @@ - + @@ -6198,7 +6273,7 @@ - + @@ -6207,7 +6282,7 @@ - + @@ -6216,7 +6291,7 @@ - + @@ -6252,7 +6327,7 @@ - + @@ -6268,10 +6343,13 @@ - + - + + + + @@ -6383,15 +6461,12 @@ - + - + - + - - - @@ -6492,7 +6567,16 @@ - + + + + + + + + + + @@ -6575,6 +6659,9 @@ + + + @@ -6867,7 +6954,7 @@ - + @@ -7005,7 +7092,7 @@ - + @@ -7343,7 +7430,7 @@ - + @@ -8388,7 +8475,7 @@ - + @@ -8397,7 +8484,7 @@ - + @@ -8406,7 +8493,7 @@ - + @@ -8478,7 +8565,7 @@ - + diff --git a/src/server/cancellationToken/cancellationToken.ts b/src/server/cancellationToken/cancellationToken.ts index 6f2f4a8897c..b7243ccd871 100644 --- a/src/server/cancellationToken/cancellationToken.ts +++ b/src/server/cancellationToken/cancellationToken.ts @@ -46,7 +46,7 @@ function createCancellationToken(args: string[]): ServerCancellationToken { let perRequestPipeName: string; let currentRequestId: number; return { - isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName), + isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName), setRequest(requestId: number) { currentRequestId = requestId; perRequestPipeName = namePrefix + requestId; diff --git a/src/server/client.ts b/src/server/client.ts index 4439e972c17..f34516ac4fd 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -200,7 +200,7 @@ namespace ts.server { const response = this.processResponse(request); Debug.assert(response.body.length === 1, "Unexpected length of completion details response body."); - const convertedCodeActions = map(response.body[0].codeActions, codeAction => this.convertCodeActions(codeAction, fileName)); + const convertedCodeActions = map(response.body[0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); return { ...response.body[0], codeActions: convertedCodeActions }; } @@ -553,15 +553,18 @@ namespace ts.server { return notImplemented(); } - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] { + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: ReadonlyArray): ReadonlyArray { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - return response.body.map(entry => this.convertCodeActions(entry, file)); + // TODO: GH#20538 shouldn't need cast + return (response.body as ReadonlyArray).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } + getCombinedCodeFix = notImplemented; + applyCodeActionCommand = notImplemented; private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { @@ -638,14 +641,11 @@ namespace ts.server { }); } - convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction { - return { - description: entry.description, - changes: entry.changes.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) - })) - }; + private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { + return changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) + })); } convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..664938a4cdc 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -547,9 +547,11 @@ namespace ts.server { } switch (response.kind) { case ActionSet: + project.resolutionCache.clear(); this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings); break; case ActionInvalidate: + project.resolutionCache.clear(); this.typingsCache.deleteTypingsForProject(response.projectName); break; } @@ -1774,9 +1776,10 @@ namespace ts.server { const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { - Debug.assert(isRootedDiskPath(fileName) || openedByClient, "Script info with relative file name can only be open script info"); - Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "Open script files with non rooted disk path opened with current directory context cannot have same canonical names"); const isDynamic = isDynamicFileName(fileName); + Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "Script info with non-dynamic relative file name can only be open script info"); + Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "Open script files with non rooted disk path opened with current directory context cannot have same canonical names"); + Debug.assert(!isDynamic || this.currentDirectory === currentDirectory, "Dynamic files must always have current directory context since containing external project name will always match the script info name."); // If the file is not opened by client and the file doesnot exist on the disk, return if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) { return; diff --git a/src/server/project.ts b/src/server/project.ts index ce750c78e23..ebc93ae3e1e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -121,6 +121,7 @@ namespace ts.server { private program: Program; private externalFiles: SortedReadonlyArray; private missingFilesMap: Map; + private plugins: PluginModule[] = []; private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap(); private lastCachedUnresolvedImportsList: SortedReadonlyArray; @@ -506,8 +507,27 @@ namespace ts.server { } abstract getTypeAcquisition(): TypeAcquisition; + protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition { + if (!newTypeAcquisition || !newTypeAcquisition.include) { + // Nothing to filter out, so just return as-is + return newTypeAcquisition; + } + return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) }; + } + getExternalFiles(): SortedReadonlyArray { - return emptyArray as SortedReadonlyArray; + return toSortedArray(flatMap(this.plugins, plugin => { + if (typeof plugin.getExternalFiles !== "function") return; + try { + return plugin.getExternalFiles(this); + } + catch (e) { + this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`); + if (e.stack) { + this.projectService.logger.info(e.stack); + } + } + })); } getSourceFile(path: Path) { @@ -718,7 +738,8 @@ namespace ts.server { this.projectStateVersion++; } - private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push) { + /* @internal */ + private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push, ambientModules: string[]) { const cached = this.cachedUnresolvedImportsPerFile.get(file.path); if (cached) { // found cached result - use it and return @@ -731,7 +752,7 @@ namespace ts.server { if (file.resolvedModules) { file.resolvedModules.forEach((resolvedModule, name) => { // pick unresolved non-relative names - if (!resolvedModule && !isExternalModuleNameRelative(name)) { + if (!resolvedModule && !isExternalModuleNameRelative(name) && !isAmbientlyDeclaredModule(name)) { // for non-scoped names extract part up-to the first slash // for scoped names - extract up to the second slash let trimmed = name.trim(); @@ -748,6 +769,10 @@ namespace ts.server { }); } this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray); + + function isAmbientlyDeclaredModule(name: string) { + return ambientModules.some(m => m === name); + } } /** @@ -777,8 +802,9 @@ namespace ts.server { // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch if (hasChanges || changedFiles.length) { const result: string[] = []; + const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); for (const sourceFile of this.program.getSourceFiles()) { - this.extractUnresolvedImportsFromSourceFile(sourceFile, result); + this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules); } this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result); } @@ -804,6 +830,13 @@ namespace ts.server { return !hasChanges; } + + + protected removeExistingTypings(include: string[]): string[] { + const existing = ts.getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost); + return include.filter(i => existing.indexOf(i) < 0); + } + private setTypings(typings: SortedReadonlyArray): boolean { if (arrayIsEqualTo(this.typingFiles, typings)) { return false; @@ -1008,6 +1041,84 @@ namespace ts.server { orderedRemoveItem(this.rootFiles, info); this.rootFilesMap.delete(info.path); } + + protected enableGlobalPlugins() { + const host = this.projectService.host; + const options = this.getCompilationSettings(); + + if (!host.require) { + this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded"); + return; + } + + // 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(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations]; + + if (this.projectService.globalPlugins) { + // Enable global plugins with synthetic configuration entries + for (const globalPluginName of this.projectService.globalPlugins) { + // Skip empty names from odd commandline parses + if (!globalPluginName) continue; + + // Skip already-locally-loaded plugins + if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue; + + // Provide global: true so plugins can detect why they can't find their config + this.projectService.logger.info(`Loading global plugin ${globalPluginName}`); + this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths); + } + } + } + + protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) { + this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`); + + const log = (message: string) => { + this.projectService.logger.info(message); + }; + + for (const searchPath of searchPaths) { + const resolvedModule = Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log); + if (resolvedModule) { + this.enableProxy(resolvedModule, pluginConfigEntry); + return; + } + } + this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`); + } + + private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) { + try { + if (typeof pluginModuleFactory !== "function") { + this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`); + return; + } + + const info: PluginCreateInfo = { + config: configEntry, + project: this, + languageService: this.languageService, + languageServiceHost: this, + serverHost: this.projectService.host + }; + + const pluginModule = pluginModuleFactory({ typescript: ts }); + const newLS = pluginModule.create(info); + for (const k of Object.keys(this.languageService)) { + if (!(k in newLS)) { + this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`); + (newLS as any)[k] = (this.languageService as any)[k]; + } + } + this.projectService.logger.info(`Plugin validation succeded`); + this.languageService = newLS; + this.plugins.push(pluginModule); + } + catch (e) { + this.projectService.logger.info(`Plugin activation failed: ${e}`); + } + } } /** @@ -1071,6 +1182,7 @@ namespace ts.server { projectService.host, currentDirectory); this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath); + this.enableGlobalPlugins(); } addRoot(info: ScriptInfo) { @@ -1132,8 +1244,6 @@ namespace ts.server { /*@internal*/ configFileSpecs: ConfigFileSpecs; - private plugins: PluginModule[] = []; - /** Ref count to the project when opened from external project */ private externalProjectRefCount = 0; @@ -1215,69 +1325,7 @@ namespace ts.server { } } - if (this.projectService.globalPlugins) { - // Enable global plugins with synthetic configuration entries - for (const globalPluginName of this.projectService.globalPlugins) { - // Skip empty names from odd commandline parses - if (!globalPluginName) continue; - - // Skip already-locally-loaded plugins - if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue; - - // Provide global: true so plugins can detect why they can't find their config - this.projectService.logger.info(`Loading global plugin ${globalPluginName}`); - this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths); - } - } - } - - private enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) { - this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`); - - const log = (message: string) => { - this.projectService.logger.info(message); - }; - - for (const searchPath of searchPaths) { - const resolvedModule = Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log); - if (resolvedModule) { - this.enableProxy(resolvedModule, pluginConfigEntry); - return; - } - } - this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`); - } - - private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) { - try { - if (typeof pluginModuleFactory !== "function") { - this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`); - return; - } - - const info: PluginCreateInfo = { - config: configEntry, - project: this, - languageService: this.languageService, - languageServiceHost: this, - serverHost: this.projectService.host - }; - - const pluginModule = pluginModuleFactory({ typescript: ts }); - const newLS = pluginModule.create(info); - for (const k of Object.keys(this.languageService)) { - if (!(k in newLS)) { - this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`); - (newLS as any)[k] = (this.languageService as any)[k]; - } - } - this.projectService.logger.info(`Plugin validation succeded`); - this.languageService = newLS; - this.plugins.push(pluginModule); - } - catch (e) { - this.projectService.logger.info(`Plugin activation failed: ${e}`); - } + this.enableGlobalPlugins(); } /** @@ -1299,28 +1347,13 @@ namespace ts.server { } setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void { - this.typeAcquisition = newTypeAcquisition; + this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); } getTypeAcquisition() { return this.typeAcquisition; } - getExternalFiles(): SortedReadonlyArray { - return toSortedArray(flatMap(this.plugins, plugin => { - if (typeof plugin.getExternalFiles !== "function") return; - try { - return plugin.getExternalFiles(this); - } - catch (e) { - this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`); - if (e.stack) { - this.projectService.logger.info(e.stack); - } - } - })); - } - /*@internal*/ watchWildcards(wildcardDirectories: Map) { updateWatchingWildcardDirectories( @@ -1445,7 +1478,7 @@ namespace ts.server { Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined"); Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined"); Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined"); - this.typeAcquisition = newTypeAcquisition; + this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); } } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6ba968dd114..b3cbba27a20 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -71,6 +71,7 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", + Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", ReloadProjects = "reloadProjects", @@ -99,9 +100,14 @@ namespace ts.server.protocol { BreakpointStatement = "breakpointStatement", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", - ApplyCodeActionCommand = "applyCodeActionCommand", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", + // TODO: GH#20538 + /* @internal */ + GetCombinedCodeFix = "getCombinedCodeFix", + /* @internal */ + GetCombinedCodeFixFull = "getCombinedCodeFix-full", + ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -216,6 +222,24 @@ namespace ts.server.protocol { projectFileName?: string; } + export interface StatusRequest extends Request { + command: CommandTypes.Status; + } + + export interface StatusResponseBody { + /** + * The TypeScript version (`ts.version`). + */ + version: string; + } + + /** + * Response to StatusRequest + */ + export interface StatusResponse extends Response { + body: StatusResponseBody; + } + /** * Requests a JS Doc comment template for a given position */ @@ -533,6 +557,19 @@ namespace ts.server.protocol { arguments: CodeFixRequestArgs; } + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixRequest extends Request { + command: CommandTypes.GetCombinedCodeFix; + arguments: GetCombinedCodeFixRequestArgs; + } + + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixResponse extends Response { + body: CombinedCodeActions; + } + export interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -582,7 +619,21 @@ namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[]; + errorCodes?: ReadonlyArray; + } + + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; + fixId: {}; + } + + // TODO: GH#20538 + /* @internal */ + export interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; } export interface ApplyCodeActionCommandRequestArgs { @@ -1568,7 +1619,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[] } export interface CodeAction { @@ -1580,6 +1631,23 @@ namespace ts.server.protocol { commands?: {}[]; } + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeActions { + changes: ReadonlyArray; + commands?: ReadonlyArray<{}>; + } + + // TODO: GH#20538 + /* @internal */ + export interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + /** * Format and format on key response message. */ @@ -1625,6 +1693,11 @@ namespace ts.server.protocol { * This affects lone identifier completions but not completions on the right hand side of `obj.`. */ includeExternalModuleExports: boolean; + /** + * If enabled, the completion list will include completions with invalid identifier names. + * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. + */ + includeInsertTextCompletions: boolean; } /** @@ -1700,6 +1773,12 @@ namespace ts.server.protocol { * is often the same as the name but may be different in certain circumstances. */ sortText: string; + /** + * Text to insert instead of `name`. + * This is used to support bracketed completions; If `name` might be "a-b" but `insertText` would be `["a-b"]`, + * coupled with `replacementSpan` to replace a dotted access with a bracket access. + */ + insertText?: string; /** * An optional span that indicates the text to be replaced by this completion item. * If present, this span should be used instead of the default one. @@ -2488,6 +2567,7 @@ namespace ts.server.protocol { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface CompilerOptions { diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index fe71040b4f6..9650130634e 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -257,7 +257,7 @@ namespace ts.server { export class ScriptVersionCache { private changes: TextChange[] = []; private readonly versions: LineIndexSnapshot[] = new Array(ScriptVersionCache.maxVersions); - private minVersion = 0; // no versions earlier than min version will maintain change history + private minVersion = 0; // no versions earlier than min version will maintain change history private currentVersion = 0; diff --git a/src/server/server.ts b/src/server/server.ts index 86349fa2be5..8e53c4d5109 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -979,6 +979,10 @@ namespace ts.server { allowLocalPluginLoads }; + logger.info(`Starting TS Server`); + logger.info(`Version: ${versionMajorMinor}`); + logger.info(`Arguments: ${process.argv.join(" ")}`); + const ioSession = new IOSession(options); process.on("uncaughtException", err => { ioSession.logError(err, "unknown"); diff --git a/src/server/session.ts b/src/server/session.ts index e09701617b8..693d1651c91 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1207,10 +1207,10 @@ namespace ts.server { if (simplifiedResult) { return mapDefined(completions && completions.entries, entry => { if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) { - const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; + const { name, kind, kindModifiers, sortText, insertText, replacementSpan, hasAction, source, isRecommended } = entry; const convertedSpan = replacementSpan ? this.toLocationTextSpan(replacementSpan, scriptInfo) : undefined; // Use `hasAction || undefined` to avoid serializing `false`. - return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended }; + return { name, kind, kindModifiers, sortText, insertText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended }; } }).sort((a, b) => compareStringsCaseSensitiveUI(a.name, b.name)); } @@ -1230,7 +1230,7 @@ namespace ts.server { return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source); }); return simplifiedResult - ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(action, scriptInfo)) })) + ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(project, action)) })) : result; } @@ -1538,18 +1538,14 @@ namespace ts.server { const oldText = snapshot.getText(0, snapshot.getLength()); mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits); } - return { - renameLocation: mappedRenameLocation, - renameFilename, - edits: edits.map(change => this.mapTextChangesToCodeEdits(project, change)) - }; + return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(project, edits) }; } else { return result; } } - private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] { + private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { if (args.errorCodes.length === 0) { return undefined; } @@ -1564,22 +1560,35 @@ namespace ts.server { return undefined; } if (simplifiedResult) { - return codeActions.map(codeAction => this.mapCodeAction(codeAction, scriptInfo)); + return codeActions.map(codeAction => this.mapCodeAction(project, codeAction)); } else { return codeActions; } } - private applyCodeActionCommand(commandName: string, requestSeq: number, args: protocol.ApplyCodeActionCommandRequestArgs): void { + private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { + Debug.assert(scope.type === "file"); + const { file, project } = this.getFileAndProject(scope.args); + const formatOptions = this.projectService.getFormatCodeOptions(file); + const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, formatOptions); + if (simplifiedResult) { + return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; + } + else { + return res; + } + } + + private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} { const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them. for (const command of toArray(commands)) { const { project } = this.getFileAndProject(command); - const output = (success: boolean, message: string) => this.doOutput({}, commandName, requestSeq, success, message); project.getLanguageService().applyCodeActionCommand(command).then( - result => { output(/*success*/ true, result.successMessage); }, - error => { output(/*success*/ false, error); }); + _result => { /* TODO: GH#20447 report success message? */ }, + _error => { /* TODO: GH#20447 report errors */ }); } + return {}; } private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) { @@ -1604,16 +1613,16 @@ namespace ts.server { return { startPosition, endPosition }; } - 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)) - })); + private mapCodeAction(project: Project, { description, changes: unmappedChanges, commands }: CodeAction): protocol.CodeAction { + const changes = unmappedChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName)))); return { description, changes, commands }; } - private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits { - const scriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(textChanges.fileName)); + private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray): protocol.FileCodeEdits[] { + return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName)))); + } + + private mapTextChangesToCodeEditsUsingScriptinfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo): protocol.FileCodeEdits { return { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) @@ -1703,19 +1712,23 @@ namespace ts.server { } private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({ + [CommandNames.Status]: () => { + const response: protocol.StatusResponseBody = { version }; + return this.requiredResponse(response); + }, [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => { this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false); - // TODO: report errors + // TODO: GH#20447 report errors return this.requiredResponse(/*response*/ true); }, [CommandNames.OpenExternalProjects]: (request: protocol.OpenExternalProjectsRequest) => { this.projectService.openExternalProjects(request.arguments.projects); - // TODO: report errors + // TODO: GH#20447 report errors return this.requiredResponse(/*response*/ true); }, [CommandNames.CloseExternalProject]: (request: protocol.CloseExternalProjectRequest) => { this.projectService.closeExternalProject(request.arguments.projectFileName); - // TODO: report errors + // TODO: GH#20447 report errors return this.requiredResponse(/*response*/ true); }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { @@ -1956,9 +1969,14 @@ namespace ts.server { [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => { return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.GetCombinedCodeFix]: (request: protocol.GetCombinedCodeFixRequest) => { + return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.GetCombinedCodeFixFull]: (request: protocol.GetCombinedCodeFixRequest) => { + return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => { - this.applyCodeActionCommand(request.command, request.seq, request.arguments); - return this.notRequired(); // Response will come asynchronously. + return this.requiredResponse(this.applyCodeActionCommand(request.arguments)); }, [CommandNames.GetSupportedCodeFixes]: () => { return this.requiredResponse(this.getSupportedCodeFixes()); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index d76ff1bf6d0..c44419f8cf3 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -34,19 +34,6 @@ namespace ts.server { export type Types = Msg; } - function getProjectRootPath(project: Project): Path { - switch (project.projectKind) { - case ProjectKind.Configured: - return getDirectoryPath(project.getProjectName()); - case ProjectKind.Inferred: - // TODO: fixme - return ""; - case ProjectKind.External: - const projectName = normalizeSlashes(project.getProjectName()); - return getDirectoryPath(projectName); - } - } - export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings { return { projectName: project.getProjectName(), @@ -54,7 +41,7 @@ namespace ts.server { compilerOptions: project.getCompilationSettings(), typeAcquisition, unresolvedImports, - projectRootPath: getProjectRootPath(project), + projectRootPath: project.getCurrentDirectory() as Path, cachePath, kind: "discover" }; diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 57dbc5711e7..26df417f879 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -262,8 +262,8 @@ namespace ts.BreakpointResolver { } // Set breakpoint on identifier element of destructuring pattern - // a or ...c or d: x from - // [a, b, ...c] or { a, b } or { d: x } from destructuring pattern + // `a` or `...c` or `d: x` from + // `[a, b, ...c]` or `{ a, b }` or `{ d: x }` from destructuring pattern if ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.SpreadElement || node.kind === SyntaxKind.PropertyAssignment || @@ -427,8 +427,9 @@ namespace ts.BreakpointResolver { } else { const functionDeclaration = parameter.parent; - const indexOfParameter = indexOf(functionDeclaration.parameters, parameter); - if (indexOfParameter) { + const indexOfParameter = functionDeclaration.parameters.indexOf(parameter); + Debug.assert(indexOfParameter !== -1); + if (indexOfParameter !== 0) { // Not a first parameter, go to previous parameter return spanInParameterDeclaration(functionDeclaration.parameters[indexOfParameter - 1]); } diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 32b3dac2cca..3e4c6058a36 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -837,7 +837,7 @@ namespace ts { return ClassificationType.keyword; } - // Special case < and > If they appear in a generic context they are punctuation, + // Special case `<` and `>`: If they appear in a generic context they are punctuation, // not operators. if (tokenKind === SyntaxKind.LessThanToken || tokenKind === SyntaxKind.GreaterThanToken) { // If the node owning the token has a type argument list or type parameter list, then diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index ad9ab520dab..aa1f2fb6885 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -1,40 +1,56 @@ /* @internal */ namespace ts { - export interface CodeFix { + export interface CodeFixRegistration { errorCodes: number[]; - getCodeActions(context: CodeFixContext): CodeAction[] | undefined; + getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; + fixIds?: string[]; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } - export interface CodeFixContext extends textChanges.TextChangesContext { - errorCode: number; + export interface CodeFixContextBase extends textChanges.TextChangesContext { sourceFile: SourceFile; - span: TextSpan; program: Program; host: LanguageServiceHost; cancellationToken: CancellationToken; } - export namespace codefix { - const codeFixes: CodeFix[][] = []; + export interface CodeFixAllContext extends CodeFixContextBase { + fixId: {}; + } - export function registerCodeFix(codeFix: CodeFix) { - forEach(codeFix.errorCodes, error => { - let fixes = codeFixes[error]; - if (!fixes) { - fixes = []; - codeFixes[error] = fixes; + export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; + } + + export namespace codefix { + const codeFixRegistrations: CodeFixRegistration[][] = []; + const fixIdToRegistration = createMap(); + + export function registerCodeFix(reg: CodeFixRegistration) { + for (const error of reg.errorCodes) { + let registrations = codeFixRegistrations[error]; + if (!registrations) { + registrations = []; + codeFixRegistrations[error] = registrations; } - fixes.push(codeFix); - }); + registrations.push(reg); + } + if (reg.fixIds) { + for (const fixId of reg.fixIds) { + Debug.assert(!fixIdToRegistration.has(fixId)); + fixIdToRegistration.set(fixId, reg); + } + } } export function getSupportedErrorCodes() { - return Object.keys(codeFixes); + return Object.keys(codeFixRegistrations); } - export function getFixes(context: CodeFixContext): CodeAction[] { - const fixes = codeFixes[context.errorCode]; - const allActions: CodeAction[] = []; + export function getFixes(context: CodeFixContext): CodeFixAction[] { + const fixes = codeFixRegistrations[context.errorCode]; + const allActions: CodeFixAction[] = []; forEach(fixes, f => { const actions = f.getCodeActions(context); @@ -52,5 +68,40 @@ namespace ts { return allActions; } + + export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { + // Currently fixId is always a string. + return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context); + } + + function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { + return { changes, commands }; + } + + export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges { + return { fileName, textChanges }; + } + + export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CombinedCodeActions { + const commands: CodeActionCommand[] = []; + const changes = textChanges.ChangeTracker.with(context, t => + eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); + return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); + } + + export function codeFixAllWithTextChanges(context: CodeFixAllContext, errorCodes: number[], use: (changes: Push, error: Diagnostic) => void): CombinedCodeActions { + const changes: TextChange[] = []; + eachDiagnostic(context, errorCodes, diag => use(changes, diag)); + changes.sort((a, b) => b.span.start - a.span.start); + return createCombinedCodeActions([createFileTextChanges(context.sourceFile.fileName, changes)]); + } + + function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: Diagnostic) => void): void { + for (const diag of program.getSemanticDiagnostics(sourceFile)) { + if (contains(errorCodes, diag.code)) { + cb(diag); + } + } + } } } diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index 7f17aab6db2..d063df87be4 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -1,20 +1,22 @@ /* @internal */ namespace ts.codefix { + const fixId = "addMissingInvocationForDecorator"; + const errorCodes = [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code]; registerCodeFix({ - errorCodes: [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - const decorator = getAncestor(token, SyntaxKind.Decorator) as Decorator; - Debug.assert(!!decorator, "Expected position to be owned by a decorator."); - const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined); - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, decorator.expression, replacement); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), - changes: changeTracker.getChanges() - }]; - } + errorCodes, + getCodeActions: (context) => { + const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)), }); + + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const decorator = findAncestor(token, isDecorator)!; + Debug.assert(!!decorator, "Expected position to be owned by a decorator."); + const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined); + changeTracker.replaceNode(sourceFile, decorator.expression, replacement); + } } diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index e18190d6f6a..de4498035be 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -1,27 +1,36 @@ /* @internal */ namespace ts.codefix { + const fixId = "correctQualifiedNameToIndexedAccessType"; + const errorCodes = [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - const qualifiedName = getAncestor(token, SyntaxKind.QualifiedName) as QualifiedName; - Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name."); - if (!isIdentifier(qualifiedName.left)) { - return undefined; + errorCodes, + getCodeActions(context) { + const qualifiedName = getQualifiedName(context.sourceFile, context.span.start); + if (!qualifiedName) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); + return [{ description, changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: (context) => codeFixAll(context, errorCodes, (changes, diag) => { + const q = getQualifiedName(diag.file, diag.start); + if (q) { + doChange(changes, diag.file, q); } - const leftText = qualifiedName.left.getText(sourceFile); - const rightText = qualifiedName.right.getText(sourceFile); - const replacement = createIndexedAccessTypeNode( - createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined), - createLiteralTypeNode(createLiteral(rightText))); - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, qualifiedName, replacement); - - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${leftText}["${rightText}"]`]), - changes: changeTracker.getChanges() - }]; - } + }), }); + + function getQualifiedName(sourceFile: SourceFile, pos: number): QualifiedName & { left: Identifier } | undefined { + const qualifiedName = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ true), isQualifiedName)!; + Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name."); + return isIdentifier(qualifiedName.left) ? qualifiedName as QualifiedName & { left: Identifier } : undefined; + } + + function doChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, qualifiedName: QualifiedName): void { + const rightText = qualifiedName.right.text; + const replacement = createIndexedAccessTypeNode( + createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined), + createLiteralTypeNode(createLiteral(rightText))); + changeTracker.replaceNode(sourceFile, qualifiedName, replacement); + } } diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 291dc61c32f..6a59e61d52d 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -1,18 +1,47 @@ /* @internal */ namespace ts.codefix { - registerCodeFix({ - errorCodes: getApplicableDiagnosticCodes(), - getCodeActions: getDisableJsDiagnosticsCodeActions + const fixId = "disableJsDiagnostics"; + const errorCodes = mapDefined(Object.keys(Diagnostics), key => { + const diag = (Diagnostics as MapLike)[key]; + return diag.category === DiagnosticCategory.Error ? diag.code : undefined; }); - function getApplicableDiagnosticCodes(): number[] { - const allDiagnostcs = >Diagnostics; - return Object.keys(allDiagnostcs) - .filter(d => allDiagnostcs[d] && allDiagnostcs[d].category === DiagnosticCategory.Error) - .map(d => allDiagnostcs[d].code); - } + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const { sourceFile, program, newLineCharacter, span } = context; - function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string) { + if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { + return undefined; + } + + return [{ + description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), + changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], + fixId, + }, + { + description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), + changes: [createFileTextChanges(sourceFile.fileName, [{ + span: { + start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0, + length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0 + }, + newText: `// @ts-nocheck${newLineCharacter}` + }])], + // fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. + fixId: undefined, + }]; + }, + fixIds: [fixId], // No point applying as a group, doing it once will fix all errors + getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + if (err.start !== undefined) { + changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); + } + }), + }); + + function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): TextChange { const { line } = getLineAndCharacterOfPosition(sourceFile, position); const lineStartPosition = getStartPositionOfLine(line, sourceFile); const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); @@ -38,33 +67,4 @@ namespace ts.codefix { newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}` }; } - - function getDisableJsDiagnosticsCodeActions(context: CodeFixContext): CodeAction[] | undefined { - const { sourceFile, program, newLineCharacter, span } = context; - - if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { - return undefined; - } - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)] - }] - }, - { - description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { - start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0, - length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0 - }, - newText: `// @ts-nocheck${newLineCharacter}` - }] - }] - }]; - } } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 19bd592b7a7..0fc6a430e19 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,209 +1,194 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [ + Diagnostics.Property_0_does_not_exist_on_type_1.code, + Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, + ]; + const fixId = "addMissingMember"; registerCodeFix({ - errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code, - Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code], - getCodeActions: getActionsForAddMissingMember + errorCodes, + getCodeActions(context) { + const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker()); + if (!info) return undefined; + const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info; + const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs); + const addMember = inJs ? + singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) : + getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic); + return concatenate(singleElementArray(methodCodeAction), addMember); + }, + fixIds: [fixId], + getAllCodeActions: context => { + const seenNames = createMap(); + return codeFixAll(context, errorCodes, (changes, diag) => { + const { program } = context; + const info = getInfo(diag.file!, diag.start!, program.getTypeChecker()); + if (!info) return; + const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info; + if (!addToSeen(seenNames, token.text)) { + return; + } + + // Always prefer to add a method declaration if possible. + if (call) { + addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs); + } + else { + if (inJs) { + addMissingMemberInJs(changes, classDeclarationSourceFile, classDeclaration, token.text, makeStatic); + } + else { + const typeNode = getTypeNode(program.getTypeChecker(), classDeclaration, token); + addPropertyDeclaration(changes, classDeclarationSourceFile, classDeclaration, token.text, typeNode, makeStatic); + } + } + }); + }, }); - function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined { - - const tokenSourceFile = context.sourceFile; - const start = context.span.start; + interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression; } + function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined { // The identifier of the missing property. eg: // this.missing = 1; // ^^^^^^^ - const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false); - - if (token.kind !== SyntaxKind.Identifier) { + const token = getTokenAtPosition(tokenSourceFile, tokenPos, /*includeJsDocComment*/ false); + if (!isIdentifier(token)) { return undefined; } - if (!isPropertyAccessExpression(token.parent)) { + const classAndMakeStatic = getClassAndMakeStatic(token, checker); + if (!classAndMakeStatic) { + return undefined; + } + const { classDeclaration, makeStatic } = classAndMakeStatic; + const classDeclarationSourceFile = classDeclaration.getSourceFile(); + const inJs = isInJavaScriptFile(classDeclarationSourceFile); + const call = tryCast(token.parent.parent, isCallExpression); + + return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call }; + } + + function getClassAndMakeStatic(token: Node, checker: TypeChecker): { readonly classDeclaration: ClassLikeDeclaration, readonly makeStatic: boolean } | undefined { + const { parent } = token; + if (!isPropertyAccessExpression(parent)) { return undefined; } - const tokenName = token.getText(tokenSourceFile); - - let makeStatic = false; - let classDeclaration: ClassLikeDeclaration; - - if (token.parent.expression.kind === SyntaxKind.ThisKeyword) { + if (parent.expression.kind === SyntaxKind.ThisKeyword) { const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); if (!isClassElement(containingClassMemberDeclaration)) { return undefined; } - - classDeclaration = containingClassMemberDeclaration.parent; - + const classDeclaration = containingClassMemberDeclaration.parent; // Property accesses on `this` in a static method are accesses of a static member. - makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static); + return isClassLike(classDeclaration) ? { classDeclaration, makeStatic: hasModifier(containingClassMemberDeclaration, ModifierFlags.Static) } : undefined; } else { - - const checker = context.program.getTypeChecker(); - const leftExpression = token.parent.expression; - const leftExpressionType = checker.getTypeAtLocation(leftExpression); - - if (leftExpressionType.flags & TypeFlags.Object) { - const symbol = leftExpressionType.symbol; - if (symbol.flags & SymbolFlags.Class) { - classDeclaration = symbol.declarations && symbol.declarations[0]; - if (leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol)) { - // The expression is a class symbol but the type is not the instance-side. - makeStatic = true; - } - } - } - } - - if (!classDeclaration || !isClassLike(classDeclaration)) { - return undefined; - } - - const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration); - const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile); - - return isInJavaScriptFile(classDeclarationSourceFile) ? - getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) : - getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic); - - function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { - let actions: CodeAction[]; - - const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ false); - if (methodCodeAction) { - actions = [methodCodeAction]; - } - - if (makeStatic) { - if (classDeclaration.kind === SyntaxKind.ClassExpression) { - return actions; - } - - const className = classDeclaration.name.getText(); - - const staticInitialization = createStatement(createAssignment( - createPropertyAccess(createIdentifier(className), tokenName), - createIdentifier("undefined"))); - - const staticInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context); - staticInitializationChangeTracker.insertNodeAfter( - classDeclarationSourceFile, - classDeclaration, - staticInitialization, - { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); - const initializeStaticAction = { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [tokenName]), - changes: staticInitializationChangeTracker.getChanges() - }; - - (actions || (actions = [])).push(initializeStaticAction); - return actions; - } - else { - const classConstructor = getFirstConstructorWithBody(classDeclaration); - if (!classConstructor) { - return actions; - } - - const propertyInitialization = createStatement(createAssignment( - createPropertyAccess(createThis(), tokenName), - createIdentifier("undefined"))); - - const propertyInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context); - propertyInitializationChangeTracker.insertNodeBefore( - classDeclarationSourceFile, - classConstructor.body.getLastToken(), - propertyInitialization, - { suffix: context.newLineCharacter }); - - const initializeAction = { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]), - changes: propertyInitializationChangeTracker.getChanges() - }; - - (actions || (actions = [])).push(initializeAction); - return actions; - } - } - - function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { - let actions: CodeAction[]; - - const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ true); - if (methodCodeAction) { - actions = [methodCodeAction]; - } - - let typeNode: TypeNode; - if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { - const binaryExpression = token.parent.parent as BinaryExpression; - const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left; - const checker = context.program.getTypeChecker(); - const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression))); - typeNode = checker.typeToTypeNode(widenedType, classDeclaration); - } - typeNode = typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); - - const property = createProperty( - /*decorators*/undefined, - /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, - tokenName, - /*questionToken*/ undefined, - typeNode, - /*initializer*/ undefined); - const propertyChangeTracker = textChanges.ChangeTracker.fromContext(context); - propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter }); - - const diag = makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0; - actions = append(actions, { - description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), - changes: propertyChangeTracker.getChanges() - }); - - if (!makeStatic) { - // Index signatures cannot have the static modifier. - const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); - const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "x", - /*questionToken*/ undefined, - stringTypeNode, - /*initializer*/ undefined); - const indexSignature = createIndexSignature( - /*decorators*/ undefined, - /*modifiers*/ undefined, - [indexingParameter], - typeNode); - - const indexSignatureChangeTracker = textChanges.ChangeTracker.fromContext(context); - indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter }); - - actions.push({ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), - changes: indexSignatureChangeTracker.getChanges() - }); - } - - return actions; - } - - function getActionForMethodDeclaration(includeTypeScriptSyntax: boolean): CodeAction | undefined { - if (token.parent.parent.kind === SyntaxKind.CallExpression) { - const callExpression = token.parent.parent; - const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, includeTypeScriptSyntax, makeStatic); - - const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromContext(context); - methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter }); - const diag = makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0; - return { - description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), - changes: methodDeclarationChangeTracker.getChanges() - }; + const leftExpressionType = checker.getTypeAtLocation(parent.expression); + const { symbol } = leftExpressionType; + if (!(leftExpressionType.flags & TypeFlags.Object && symbol.flags & SymbolFlags.Class)) { + return undefined; } + const classDeclaration = cast(first(symbol.declarations), isClassLike); + // The expression is a class symbol but the type is not the instance-side. + return { classDeclaration, makeStatic: leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol) }; } } + + function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { + const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); + if (changes.length === 0) return undefined; + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); + return { description, changes, fixId }; + } + + function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void { + if (makeStatic) { + if (classDeclaration.kind === SyntaxKind.ClassExpression) { + return; + } + const className = classDeclaration.name.getText(); + const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName); + changeTracker.insertNodeAfter(classDeclarationSourceFile, classDeclaration, staticInitialization); + } + else { + const classConstructor = getFirstConstructorWithBody(classDeclaration); + if (!classConstructor) { + return; + } + const propertyInitialization = initializePropertyToUndefined(createThis(), tokenName); + changeTracker.insertNodeAtConstructorEnd(classDeclarationSourceFile, classConstructor, propertyInitialization); + } + } + + function initializePropertyToUndefined(obj: Expression, propertyName: string) { + return createStatement(createAssignment(createPropertyAccess(obj, propertyName), createIdentifier("undefined"))); + } + + function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, makeStatic: boolean): CodeFixAction[] | undefined { + const typeNode = getTypeNode(context.program.getTypeChecker(), classDeclaration, token); + const addProp = createAddPropertyDeclarationAction(context, classDeclarationSourceFile, classDeclaration, makeStatic, token.text, typeNode); + return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, classDeclarationSourceFile, classDeclaration, token.text, typeNode)]; + } + + function getTypeNode(checker: TypeChecker, classDeclaration: ClassLikeDeclaration, token: Node) { + let typeNode: TypeNode; + if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { + const binaryExpression = token.parent.parent as BinaryExpression; + const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left; + const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression))); + typeNode = checker.typeToTypeNode(widenedType, classDeclaration); + } + return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + function createAddPropertyDeclarationAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); + const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic)); + return { description, changes, fixId }; + } + + function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode, makeStatic: boolean): void { + const property = createProperty( + /*decorators*/ undefined, + /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, + tokenName, + /*questionToken*/ undefined, + typeNode, + /*initializer*/ undefined); + changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property); + } + + function createAddIndexSignatureAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { + // Index signatures cannot have the static modifier. + const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); + const indexingParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "x", + /*questionToken*/ undefined, + stringTypeNode, + /*initializer*/ undefined); + const indexSignature = createIndexSignature( + /*decorators*/ undefined, + /*modifiers*/ undefined, + [indexingParameter], + typeNode); + + const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature)); + // No fixId here because code-fix-all currently only works on adding individual named properties. + return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined }; + } + + function getActionForMethodDeclaration(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { + const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); + const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs)); + return { description, changes, fixId }; + } + + function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean) { + const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic); + changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration); + } } diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts new file mode 100644 index 00000000000..883993e7b51 --- /dev/null +++ b/src/services/codefixes/fixAwaitInSyncFunction.ts @@ -0,0 +1,74 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "fixAwaitInSyncFunction"; + const errorCodes = [ + Diagnostics.await_expression_is_only_allowed_within_an_async_function.code, + Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator.code, + ]; + registerCodeFix({ + errorCodes, + getCodeActions(context) { + const { sourceFile, span } = context; + const nodes = getNodes(sourceFile, span.start); + if (!nodes) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const nodes = getNodes(diag.file, diag.start); + if (!nodes) return; + doChange(changes, context.sourceFile, nodes); + }), + }); + + function getReturnType(expr: FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction) { + if (expr.type) { + return expr.type; + } + if (isVariableDeclaration(expr.parent) && + expr.parent.type && + isFunctionTypeNode(expr.parent.type)) { + return expr.parent.type.type; + } + } + + function getNodes(sourceFile: SourceFile, start: number): { insertBefore: Node, returnType: TypeNode | undefined } | undefined { + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); + const containingFunction = getContainingFunction(token); + let insertBefore: Node | undefined; + switch (containingFunction.kind) { + case SyntaxKind.MethodDeclaration: + insertBefore = containingFunction.name; + break; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + insertBefore = findChildOfKind(containingFunction, SyntaxKind.FunctionKeyword, sourceFile); + break; + case SyntaxKind.ArrowFunction: + insertBefore = findChildOfKind(containingFunction, SyntaxKind.OpenParenToken, sourceFile) || first(containingFunction.parameters); + break; + default: + return; + } + + return { + insertBefore, + returnType: getReturnType(containingFunction) + }; + } + + function doChange( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + { insertBefore, returnType }: { insertBefore: Node | undefined, returnType: TypeNode | undefined }): void { + + if (returnType) { + const entityName = getEntityNameFromTypeNode(returnType); + if (!entityName || entityName.kind !== SyntaxKind.Identifier || entityName.text !== "Promise") { + changes.replaceNode(sourceFile, returnType, createTypeReferenceNode("Promise", createNodeArray([returnType]))); + } + } + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, insertBefore); + } +} diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index 9796bf2fa22..a28a46caf1b 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -1,34 +1,42 @@ /* @internal */ namespace ts.codefix { + const fixId = "fixCannotFindModule"; + const errorCodes = [Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code]; registerCodeFix({ - errorCodes: [ - Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code, - ], + errorCodes, getCodeActions: context => { - const { sourceFile, span: { start } } = context; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - if (!isStringLiteral(token)) { - throw Debug.fail(); // These errors should only happen on the module name. - } - - const action = tryGetCodeActionForInstallPackageTypes(context.host, sourceFile.fileName, token.text); - return action && [action]; + const codeAction = tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)); + return codeAction && [{ fixId, ...codeAction }]; }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => { + const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start)); + if (pkg) { + commands.push(getCommand(diag.file.fileName, pkg)); + } + }), }); - export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined { + function getModuleName(sourceFile: SourceFile, pos: number): string { + return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isStringLiteral).text; + } + + function getCommand(fileName: string, packageName: string): InstallPackageAction { + return { type: "install package", file: fileName, packageName }; + } + + function getTypesPackageNameToInstall(host: LanguageServiceHost, moduleName: string): string | undefined { const { packageName } = getPackageName(moduleName); + // If !registry, registry not available yet, can't do anything. + return host.isKnownTypesPackageName(packageName) ? getTypesPackageName(packageName) : undefined; + } - if (!host.isKnownTypesPackageName(packageName)) { - // If !registry, registry not available yet, can't do anything. - return undefined; - } - - const typesPackageName = getTypesPackageName(packageName); - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [typesPackageName]), + export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined { + const packageName = getTypesPackageNameToInstall(host, moduleName); + return packageName === undefined ? undefined : { + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [packageName]), changes: [], - commands: [{ type: "install package", file: fileName, packageName: typesPackageName }], + commands: [getCommand(fileName, packageName)], }; } } diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 768eaf752b7..da3685339ab 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -1,52 +1,48 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [ + Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code, + Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code, + ]; + const fixId = "fixClassDoesntImplementInheritedAbstractMember"; registerCodeFix({ - errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code], - getCodeActions: getActionForClassLikeMissingAbstractMember + errorCodes, + getCodeActions(context) { + const { program, sourceFile, span } = context; + const changes = textChanges.ChangeTracker.with(context, t => + addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t)); + return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), changes); + }), }); - registerCodeFix({ - errorCodes: [Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code], - getCodeActions: getActionForClassLikeMissingAbstractMember - }); - - function getActionForClassLikeMissingAbstractMember(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const start = context.span.start; + function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { // This is the identifier in the case of a class declaration // or the class keyword token in the case of a class expression. - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const checker = context.program.getTypeChecker(); + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const classDeclaration = token.parent; + Debug.assert(isClassLike(classDeclaration)); + return classDeclaration as ClassLikeDeclaration; + } - if (isClassLike(token.parent)) { - const classDeclaration = token.parent as ClassLikeDeclaration; + function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, changeTracker: textChanges.ChangeTracker): void { + const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); + const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); - const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); - const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); - - // Note that this is ultimately derived from a map indexed by symbol names, - // so duplicates cannot occur. - const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType); - const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember); - - const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker); - const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context); - if (changes && changes.length > 0) { - return [{ - description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), - changes - }]; - } - } - - return undefined; + // Note that this is ultimately derived from a map indexed by symbol names, + // so duplicates cannot occur. + const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember); + createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member)); } function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean { - const decls = symbol.getDeclarations(); - Debug.assert(!!(decls && decls.length > 0)); - const flags = getModifierFlags(decls[0]); + // See `codeFixClassExtendAbstractProtectedProperty.ts` in https://github.com/Microsoft/TypeScript/pull/11547/files + // (now named `codeFixClassExtendAbstractPrivateProperty.ts`) + const flags = getModifierFlags(first(symbol.getDeclarations())); return !(flags & ModifierFlags.Private) && !!(flags & ModifierFlags.Abstract); } -} \ No newline at end of file +} diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 1a0cf17bbd6..0236b0c79b7 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -1,64 +1,70 @@ /* @internal */ namespace ts.codefix { + const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code, + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code]; + const fixId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? registerCodeFix({ - errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code], - getCodeActions: getActionForClassLikeIncorrectImplementsInterface + errorCodes, + getCodeActions(context) { + const { program, sourceFile, span } = context; + const classDeclaration = getClass(sourceFile, span.start); + const checker = program.getTypeChecker(); + return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { + const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t)); + if (changes.length === 0) return undefined; + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); + return { description, changes, fixId }; + }); + }, + fixIds: [fixId], + getAllCodeActions(context) { + const seenClassDeclarations = createMap(); + return codeFixAll(context, errorCodes, (changes, diag) => { + const classDeclaration = getClass(diag.file!, diag.start!); + if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { + for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) { + addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, changes); + } + } + }); + }, }); - function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const checker = context.program.getTypeChecker(); + function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { + const classDeclaration = getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false)); + Debug.assert(!!classDeclaration); + return classDeclaration!; + } - const classDeclaration = getContainingClass(token); - if (!classDeclaration) { - return undefined; + function addMissingDeclarations( + checker: TypeChecker, + implementedTypeNode: ExpressionWithTypeArguments, + sourceFile: SourceFile, + classDeclaration: ClassLikeDeclaration, + changeTracker: textChanges.ChangeTracker + ): void { + // Note that this is ultimately derived from a map indexed by symbol names, + // so duplicates cannot occur. + const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; + const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); + const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); + + const classType = checker.getTypeAtLocation(classDeclaration); + + if (!checker.getIndexTypeOfType(classType, IndexKind.Number)) { + createMissingIndexSignatureDeclaration(implementedType, IndexKind.Number); + } + if (!checker.getIndexTypeOfType(classType, IndexKind.String)) { + createMissingIndexSignatureDeclaration(implementedType, IndexKind.String); } - const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); - const classType = checker.getTypeAtLocation(classDeclaration) as InterfaceType; - const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDeclaration); - - const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number); - const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String); - - const result: CodeAction[] = []; - for (const implementedTypeNode of implementedTypeNodes) { - // Note that this is ultimately derived from a map indexed by symbol names, - // so duplicates cannot occur. - const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; - const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); - const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); - - let newNodes: Node[] = []; - createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes); - createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes); - newNodes = newNodes.concat(createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker)); - const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); - if (newNodes.length > 0) { - pushAction(result, newNodes, message); - } - } - - return result; - - function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void { - if (hasIndexSigOfKind) { - return; - } + createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member)); + function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); - - if (!indexInfoOfKind) { - return; + if (indexInfoOfKind) { + changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration)); } - const newIndexSignatureDeclaration = checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration); - newNodes.push(newIndexSignatureDeclaration); - } - - function pushAction(result: CodeAction[], newNodes: Node[], description: string): void { - result.push({ description, changes: newNodesToChanges(newNodes, openBrace, context) }); } } -} \ No newline at end of file +} diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index e5c4266684d..1595bbf3c13 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -1,49 +1,52 @@ /* @internal */ namespace ts.codefix { + const fixId = "classSuperMustPrecedeThisAccess"; + const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code]; registerCodeFix({ - errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - if (token.kind !== SyntaxKind.ThisKeyword) { - return undefined; - } - - const constructor = getContainingFunction(token); - const superCall = findSuperCall((constructor).body); - if (!superCall) { - return undefined; - } - - // figure out if the `this` access is actually inside the supercall - // i.e. super(this.a), since in that case we won't suggest a fix - if (superCall.expression && superCall.expression.kind === SyntaxKind.CallExpression) { - const expressionArguments = (superCall.expression).arguments; - for (const arg of expressionArguments) { - if ((arg).expression === token) { - return undefined; - } + errorCodes, + getCodeActions(context) { + const { sourceFile, span } = context; + const nodes = getNodes(sourceFile, span.start); + if (!nodes) return undefined; + const { constructor, superCall } = nodes; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions(context) { + const { sourceFile } = context; + const seenClasses = createMap(); // Ensure we only do this once per class. + return codeFixAll(context, errorCodes, (changes, diag) => { + const nodes = getNodes(diag.file!, diag.start!); + if (!nodes) return; + const { constructor, superCall } = nodes; + if (addToSeen(seenClasses, getNodeId(constructor.parent))) { + doChange(changes, sourceFile, constructor, superCall); } - } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { suffix: context.newLineCharacter }); - changeTracker.deleteNode(sourceFile, superCall); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), - changes: changeTracker.getChanges() - }]; - - function findSuperCall(n: Node): ExpressionStatement { - if (n.kind === SyntaxKind.ExpressionStatement && isSuperCall((n).expression)) { - return n; - } - if (isFunctionLike(n)) { - return undefined; - } - return forEachChild(n, findSuperCall); - } - } + }); + }, }); + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, constructor: ConstructorDeclaration, superCall: ExpressionStatement): void { + changes.insertNodeAtConstructorStart(sourceFile, constructor, superCall); + changes.deleteNode(sourceFile, superCall); + } + + function getNodes(sourceFile: SourceFile, pos: number): { readonly constructor: ConstructorDeclaration, readonly superCall: ExpressionStatement } { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + Debug.assert(token.kind === SyntaxKind.ThisKeyword); + const constructor = getContainingFunction(token) as ConstructorDeclaration; + const superCall = findSuperCall(constructor.body); + // figure out if the `this` access is actually inside the supercall + // i.e. super(this.a), since in that case we won't suggest a fix + return superCall && !superCall.expression.arguments.some(arg => isPropertyAccessExpression(arg) && arg.expression === token) ? { constructor, superCall } : undefined; + } + + function findSuperCall(n: Node): ExpressionStatement & { expression: CallExpression } | undefined { + return isExpressionStatement(n) && isSuperCall(n.expression) + ? n as ExpressionStatement & { expression: CallExpression } + : isFunctionLike(n) + ? undefined + : forEachChild(n, findSuperCall); + } } diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 24f44a877b3..8fe2e8458a8 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -1,23 +1,28 @@ /* @internal */ namespace ts.codefix { + const fixId = "constructorForDerivedNeedSuperCall"; + const errorCodes = [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code]; registerCodeFix({ - errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - - if (token.kind !== SyntaxKind.ConstructorKeyword) { - return undefined; - } - - const changeTracker = textChanges.ChangeTracker.fromContext(context); - const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { suffix: context.newLineCharacter }); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), - changes: changeTracker.getChanges() - }]; - } + errorCodes, + getCodeActions(context) { + const { sourceFile, span } = context; + const ctr = getNode(sourceFile, span.start); + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => + doChange(changes, context.sourceFile, getNode(diag.file, diag.start!))), }); -} \ No newline at end of file + + function getNode(sourceFile: SourceFile, pos: number): ConstructorDeclaration { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + Debug.assert(token.kind === SyntaxKind.ConstructorKeyword); + return token.parent as ConstructorDeclaration; + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, ctr: ConstructorDeclaration) { + const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); + changes.insertNodeAtConstructorStart(sourceFile, ctr, superCall); + } +} diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index 57bf2dd2795..d98ca556f47 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -1,43 +1,52 @@ /* @internal */ namespace ts.codefix { + const fixId = "extendsInterfaceBecomesImplements"; + const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - const classDeclNode = getContainingClass(token); - if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) { - return undefined; - } - - const heritageClauses = classDeclNode.heritageClauses; - if (!(heritageClauses && heritageClauses.length > 0)) { - return undefined; - } - - const extendsToken = heritageClauses[0].getFirstToken(); - if (!(extendsToken && extendsToken.kind === SyntaxKind.ExtendsKeyword)) { - return undefined; - } - - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword)); - - // We replace existing keywords with commas. - for (let i = 1; i < heritageClauses.length; i++) { - const keywordToken = heritageClauses[i].getFirstToken(); - if (keywordToken) { - changeTracker.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken)); - } - } - - const result = [{ - description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), - changes: changeTracker.getChanges() - }]; - - return result; - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const nodes = getNodes(sourceFile, context.span.start); + if (!nodes) return undefined; + const { extendsToken, heritageClauses } = nodes; + const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const nodes = getNodes(diag.file, diag.start!); + if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses); + }), }); + + function getNodes(sourceFile: SourceFile, pos: number) { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + const heritageClauses = getContainingClass(token)!.heritageClauses; + const extendsToken = heritageClauses[0].getFirstToken(); + return extendsToken.kind === SyntaxKind.ExtendsKeyword ? { extendsToken, heritageClauses } : undefined; + } + + function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray): void { + changes.replaceRange(sourceFile, { pos: extendsToken.getStart(), end: extendsToken.end }, createToken(SyntaxKind.ImplementsKeyword)); + + // If there is already an implements clause, replace the implements keyword with a comma. + if (heritageClauses.length === 2 && + heritageClauses[0].token === SyntaxKind.ExtendsKeyword && + heritageClauses[1].token === SyntaxKind.ImplementsKeyword) { + + const implementsToken = heritageClauses[1].getFirstToken()!; + const implementsFullStart = implementsToken.getFullStart(); + changes.replaceRange(sourceFile, { pos: implementsFullStart, end: implementsFullStart }, createToken(SyntaxKind.CommaToken)); + + // Rough heuristic: delete trailing whitespace after keyword so that it's not excessive. + // (Trailing because leading might be indentation, which is more sensitive.) + const text = sourceFile.text; + let end = implementsToken.end; + while (end < text.length && ts.isWhiteSpaceSingleLine(text.charCodeAt(end))) { + end++; + } + + changes.deleteRange(sourceFile, { pos: implementsToken.getStart(), end }); + } + } } diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 5ac4f035f73..19610da0b15 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -1,20 +1,26 @@ /* @internal */ namespace ts.codefix { + const fixId = "forgottenThisPropertyAccess"; + const errorCodes = [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code]; registerCodeFix({ - errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); - if (token.kind !== SyntaxKind.Identifier) { - return undefined; - } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token)); - - return [{ - description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), - changes: changeTracker.getChanges() - }]; - } + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const token = getNode(sourceFile, context.span.start); + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + doChange(changes, context.sourceFile, getNode(diag.file, diag.start!)); + }), }); + + function getNode(sourceFile: SourceFile, pos: number): Identifier { + return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isIdentifier); + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Identifier): void { + changes.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token)); + } } \ No newline at end of file diff --git a/src/services/codefixes/fixInvalidImportSyntax.ts b/src/services/codefixes/fixInvalidImportSyntax.ts new file mode 100644 index 00000000000..f98f1eaea7c --- /dev/null +++ b/src/services/codefixes/fixInvalidImportSyntax.ts @@ -0,0 +1,98 @@ +/* @internal */ +namespace ts.codefix { + registerCodeFix({ + errorCodes: [Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime.code], + getCodeActions: getActionsForInvalidImport + }); + + function getActionsForInvalidImport(context: CodeFixContext): CodeAction[] | undefined { + const sourceFile = context.sourceFile; + + // This is the whole import statement, eg: + // import * as Bluebird from 'bluebird'; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false).parent as ImportDeclaration; + if (!isImportDeclaration(node)) { + // No import quick fix for import calls + return []; + } + return getCodeFixesForImportDeclaration(context, node); + } + + function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportDeclaration) { + const sourceFile = getSourceFileOfNode(node); + const namespace = getNamespaceDeclarationNode(node) as NamespaceImport; + const opts = context.program.getCompilerOptions(); + const variations: CodeAction[] = []; + + // import Bluebird from "bluebird"; + const replacement = createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(namespace.name, /*namedBindings*/ undefined), + node.moduleSpecifier + ); + const changeTracker = textChanges.ChangeTracker.fromContext(context); + changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); + const changes = changeTracker.getChanges(); + variations.push({ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Replace_import_with_0), [changes[0].textChanges[0].newText]), + changes + }); + + if (getEmitModuleKind(opts) === ModuleKind.CommonJS) { + // import Bluebird = require("bluebird"); + const replacement = createImportEqualsDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + namespace.name, + createExternalModuleReference(node.moduleSpecifier) + ); + const changeTracker = textChanges.ChangeTracker.fromContext(context); + changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); + const changes = changeTracker.getChanges(); + variations.push({ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Replace_import_with_0), [changes[0].textChanges[0].newText]), + changes + }); + } + + return variations; + } + + registerCodeFix({ + errorCodes: [ + Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code, + Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature.code, + ], + getCodeActions: getActionsForUsageOfInvalidImport + }); + + function getActionsForUsageOfInvalidImport(context: CodeFixContext): CodeAction[] | undefined { + const sourceFile = context.sourceFile; + const targetKind = Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression; + const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false), a => a.kind === targetKind && a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length)) as CallExpression | NewExpression; + if (!node) { + return []; + } + const expr = node.expression; + const type = context.program.getTypeChecker().getTypeAtLocation(expr); + if (!(type.symbol && (type.symbol as TransientSymbol).originatingImport)) { + return []; + } + const fixes: CodeAction[] = []; + const relatedImport = (type.symbol as TransientSymbol).originatingImport; + if (!isImportCall(relatedImport)) { + addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport)); + } + const propertyAccess = createPropertyAccess(expr, "default"); + const changeTracker = textChanges.ChangeTracker.fromContext(context); + changeTracker.replaceNode(sourceFile, expr, propertyAccess, {}); + const changes = changeTracker.getChanges(); + fixes.push({ + description: getLocaleSpecificMessage(Diagnostics.Use_synthetic_default_member), + changes + }); + return fixes; + } +} diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts index 8d5cd562497..8c43ed0cc7f 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -1,61 +1,91 @@ /* @internal */ namespace ts.codefix { + const fixIdPlain = "fixJSDocTypes_plain"; + const fixIdNullable = "fixJSDocTypes_nullable"; + const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code]; registerCodeFix({ - errorCodes: [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code], - getCodeActions: getActionsForJSDocTypes + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const checker = context.program.getTypeChecker(); + const info = getInfo(sourceFile, context.span.start, checker); + if (!info) return undefined; + const { typeNode, type } = info; + const original = typeNode.getText(sourceFile); + const actions = [fix(type, fixIdPlain)]; + if (typeNode.kind === SyntaxKind.JSDocNullableType) { + // for nullable types, suggest the flow-compatible `T | null | undefined` + // in addition to the jsdoc/closure-compatible `T | null` + actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable)); + } + return actions; + + function fix(type: Type, fixId: string): CodeFixAction { + const newText = typeString(type, checker); + return { + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]), + changes: [createFileTextChanges(sourceFile.fileName, [createChange(typeNode, sourceFile, newText)])], + fixId, + }; + } + }, + fixIds: [fixIdPlain, fixIdNullable], + getAllCodeActions(context) { + const { fixId, program, sourceFile } = context; + const checker = program.getTypeChecker(); + return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + const info = getInfo(err.file, err.start!, checker); + if (!info) return; + const { typeNode, type } = info; + const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && fixId === fixIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; + changes.push(createChange(typeNode, sourceFile, typeString(fixedType, checker))); + }); + } }); - function getActionsForJSDocTypes(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); + function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { readonly typeNode: TypeNode, type: Type } { + const decl = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isTypeContainer); + const typeNode = decl && decl.type; + return typeNode && { typeNode, type: checker.getTypeFromTypeNode(typeNode) }; + } + function createChange(declaration: TypeNode, sourceFile: SourceFile, newText: string): TextChange { + return { span: createTextSpanFromBounds(declaration.getStart(sourceFile), declaration.getEnd()), newText }; + } + + function typeString(type: Type, checker: TypeChecker): string { + return checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation); + } + + // TODO: GH#19856 Node & { type: TypeNode } + type TypeContainer = + | AsExpression | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration + | GetAccessorDeclaration | IndexSignatureDeclaration | MappedTypeNode | MethodDeclaration + | MethodSignature | ParameterDeclaration | PropertyDeclaration | PropertySignature | SetAccessorDeclaration + | TypeAliasDeclaration | TypeAssertion | VariableDeclaration; + function isTypeContainer(node: Node): node is TypeContainer { // NOTE: Some locations are not handled yet: // MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments - const decl = ts.findAncestor(node, - n => - n.kind === SyntaxKind.AsExpression || - n.kind === SyntaxKind.CallSignature || - n.kind === SyntaxKind.ConstructSignature || - n.kind === SyntaxKind.FunctionDeclaration || - n.kind === SyntaxKind.GetAccessor || - n.kind === SyntaxKind.IndexSignature || - n.kind === SyntaxKind.MappedType || - n.kind === SyntaxKind.MethodDeclaration || - n.kind === SyntaxKind.MethodSignature || - n.kind === SyntaxKind.Parameter || - n.kind === SyntaxKind.PropertyDeclaration || - n.kind === SyntaxKind.PropertySignature || - n.kind === SyntaxKind.SetAccessor || - n.kind === SyntaxKind.TypeAliasDeclaration || - n.kind === SyntaxKind.TypeAssertionExpression || - n.kind === SyntaxKind.VariableDeclaration); - if (!decl) return; - const checker = context.program.getTypeChecker(); - - const jsdocType = (decl as VariableDeclaration).type; - if (!jsdocType) return; - const original = getTextOfNode(jsdocType); - const type = checker.getTypeFromTypeNode(jsdocType); - const actions = [createAction(jsdocType, sourceFile.fileName, original, checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation))]; - if (jsdocType.kind === SyntaxKind.JSDocNullableType) { - // for nullable types, suggest the flow-compatible `T | null | undefined` - // in addition to the jsdoc/closure-compatible `T | null` - const replacementWithUndefined = checker.typeToString(checker.getNullableType(type, TypeFlags.Undefined), /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation); - actions.push(createAction(jsdocType, sourceFile.fileName, original, replacementWithUndefined)); + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.IndexSignature: + case SyntaxKind.MappedType: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.SetAccessor: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.VariableDeclaration: + return true; + default: + return false; } - return actions; - } - - function createAction(declaration: TypeNode, fileName: string, original: string, replacement: string): CodeAction { - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, replacement]), - changes: [{ - fileName, - textChanges: [{ - span: { start: declaration.getStart(), length: declaration.getWidth() }, - newText: replacement - }] - }], - }; } } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 2546a92a32f..729f14e9ef9 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -1,19 +1,34 @@ /* @internal */ namespace ts.codefix { + const fixId = "fixSpelling"; + const errorCodes = [ + Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, + Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, + ]; registerCodeFix({ - errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, - Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], - getCodeActions: getActionsForCorrectSpelling + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker()); + if (!info) return undefined; + const { node, suggestion } = info; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); + return [{ description, changes, fixId }]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker()); + if (info) doChange(changes, context.sourceFile, info.node, info.suggestion); + }), }); - function getActionsForCorrectSpelling(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; - + function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { node: Node, suggestion: string } | undefined { // This is the identifier of the misspelled word. eg: // this.speling = 1; // ^^^^^^^ - const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); // TODO: GH#15852 - const checker = context.program.getTypeChecker(); + const node = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); // TODO: GH#15852 + let suggestion: string; if (isPropertyAccessExpression(node.parent) && node.parent.name === node) { Debug.assert(node.kind === SyntaxKind.Identifier); @@ -26,18 +41,12 @@ namespace ts.codefix { Debug.assert(name !== undefined, "name should be defined"); suggestion = checker.getSuggestionForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning)); } - if (suggestion) { - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: node.getStart(), length: node.getWidth() }, - newText: suggestion - }], - }], - }]; - } + + return suggestion === undefined ? undefined : { node, suggestion }; + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestion: string) { + changes.replaceNode(sourceFile, node, createIdentifier(suggestion)); } function convertSemanticMeaningToSymbolFlags(meaning: SemanticMeaning): SymbolFlags { diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 473c4748550..b7d948b3deb 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -1,207 +1,235 @@ /* @internal */ namespace ts.codefix { + const fixIdPrefix = "unusedIdentifier_prefix"; + const fixIdDelete = "unusedIdentifier_delete"; + const errorCodes = [ + Diagnostics._0_is_declared_but_its_value_is_never_read.code, + Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code, + ]; registerCodeFix({ - errorCodes: [ - Diagnostics._0_is_declared_but_its_value_is_never_read.code, - Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code - ], - getCodeActions: (context: CodeFixContext) => { - const sourceFile = context.sourceFile; - const start = context.span.start; + errorCodes, + getCodeActions(context) { + const { sourceFile } = context; + const token = getToken(sourceFile, context.span.start); + const result: CodeFixAction[] = []; - let token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - - // this handles var ["computed"] = 12; - if (token.kind === SyntaxKind.OpenBracketToken) { - token = getTokenAtPosition(sourceFile, start + 1, /*includeJsDocComment*/ false); + const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); + if (deletion.length) { + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); + result.push({ description, changes: deletion, fixId: fixIdDelete }); } - switch (token.kind) { - case ts.SyntaxKind.Identifier: - return deleteIdentifierOrPrefixWithUnderscore(token, context.errorCode); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.NamespaceImport: - return [deleteNode(token.parent)]; - - default: - return deleteDefault(); + const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); + if (prefix.length) { + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); + result.push({ description, changes: prefix, fixId: fixIdPrefix }); } - function deleteDefault(): CodeAction[] | undefined { - if (isDeclarationName(token)) { - return [deleteNode(token.parent)]; - } - else if (isLiteralComputedPropertyDeclarationName(token)) { - return [deleteNode(token.parent.parent)]; - } - else { - return undefined; - } - } - - function prefixIdentifierWithUnderscore(identifier: Identifier): CodeAction { - const startPosition = identifier.getStart(sourceFile, /*includeJsDocComment*/ false); - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), { 0: token.getText() }), - changes: [{ - fileName: sourceFile.path, - textChanges: [{ - span: { start: startPosition, length: 0 }, - newText: "_" - }] - }] - }; - } - - function deleteIdentifierOrPrefixWithUnderscore(identifier: Identifier, errorCode: number): CodeAction[] | undefined { - const parent = identifier.parent; - switch (parent.kind) { - case ts.SyntaxKind.VariableDeclaration: - return deleteVariableDeclarationOrPrefixWithUnderscore(identifier, parent); - - case SyntaxKind.TypeParameter: - const typeParameters = (parent.parent).typeParameters; - if (typeParameters.length === 1) { - const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); - const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); - Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); - Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); - - return [deleteNodeRange(previousToken, nextToken)]; - } - else { - return [deleteNodeInList(parent)]; - } - - case ts.SyntaxKind.Parameter: - const functionDeclaration = parent.parent; - const deleteAction = functionDeclaration.parameters.length === 1 ? deleteNode(parent) : deleteNodeInList(parent); - return errorCode === Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code - ? [deleteAction] - : [deleteAction, prefixIdentifierWithUnderscore(identifier)]; - - // handle case where 'import a = A;' - case SyntaxKind.ImportEqualsDeclaration: - const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration); - return [deleteNode(importEquals)]; - - case SyntaxKind.ImportSpecifier: - const namedImports = parent.parent; - if (namedImports.elements.length === 1) { - return deleteNamedImportBinding(namedImports); - } - else { - // delete import specifier - return [deleteNodeInList(parent)]; - } - - case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' - const importClause = parent; - if (!importClause.namedBindings) { // |import d from './file'| - const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); - return [deleteNode(importDecl)]; - } - else { - // import |d,| * as ns from './file' - const start = importClause.name.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false); - if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { - // shift first non-whitespace position after comma to the start position of the node - return [deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) })]; - } - else { - return [deleteNode(importClause.name)]; - } - } - - case SyntaxKind.NamespaceImport: - return deleteNamedImportBinding(parent); - - default: - return deleteDefault(); - } - } - - function deleteNamedImportBinding(namedBindings: NamedImportBindings): CodeAction[] | undefined { - if ((namedBindings.parent).name) { - // Delete named imports while preserving the default import - // import d|, * as ns| from './file' - // import d|, { a }| from './file' - const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false); - if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - return [deleteRange({ pos: previousToken.getStart(), end: namedBindings.end })]; + return result; + }, + fixIds: [fixIdPrefix, fixIdDelete], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const { sourceFile } = context; + const token = getToken(diag.file!, diag.start!); + switch (context.fixId) { + case fixIdPrefix: + if (isIdentifier(token) && canPrefix(token)) { + tryPrefixDeclaration(changes, diag.code, sourceFile, token); } - return undefined; - } - else { - // Delete the entire import declaration - // |import * as ns from './file'| - // |import { a } from './file'| - const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration); - return [deleteNode(importDecl)]; - } + break; + case fixIdDelete: + tryDeleteDeclaration(changes, sourceFile, token); + break; + default: + Debug.fail(JSON.stringify(context.fixId)); } + }), + }); - // token.parent is a variableDeclaration - function deleteVariableDeclarationOrPrefixWithUnderscore(identifier: Identifier, varDecl: ts.VariableDeclaration): CodeAction[] | undefined { + function getToken(sourceFile: SourceFile, pos: number): Node { + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); + // this handles var ["computed"] = 12; + return token.kind === SyntaxKind.OpenBracketToken ? getTokenAtPosition(sourceFile, pos + 1, /*includeJsDocComment*/ false) : token; + } + + function tryPrefixDeclaration(changes: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, token: Node): void { + // Don't offer to prefix a property. + if (errorCode !== Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code && isIdentifier(token) && canPrefix(token)) { + changes.replaceNode(sourceFile, token, createIdentifier(`_${token.text}`)); + } + } + + function canPrefix(token: Identifier): boolean { + switch (token.parent.kind) { + case SyntaxKind.Parameter: + return true; + case SyntaxKind.VariableDeclaration: { + const varDecl = token.parent as VariableDeclaration; switch (varDecl.parent.parent.kind) { - case SyntaxKind.ForStatement: - const forStatement = varDecl.parent.parent; - const forInitializer = forStatement.initializer; - return [forInitializer.declarations.length === 1 ? deleteNode(forInitializer) : deleteNodeInList(varDecl)]; - case SyntaxKind.ForOfStatement: - const forOfStatement = varDecl.parent.parent; - Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList); - const forOfInitializer = forOfStatement.initializer; - return [ - replaceNode(forOfInitializer.declarations[0], createObjectLiteral()), - prefixIdentifierWithUnderscore(identifier) - ]; - case SyntaxKind.ForInStatement: - // There is no valid fix in the case of: - // for .. in - return [prefixIdentifierWithUnderscore(identifier)]; - - default: - const variableStatement = varDecl.parent.parent; - if (variableStatement.declarationList.declarations.length === 1) { - return [deleteNode(variableStatement)]; - } - else { - return [deleteNodeInList(varDecl)]; - } + return true; } } - - function deleteNode(n: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNode(sourceFile, n)); - } - - function deleteRange(range: TextRange) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteRange(sourceFile, range)); - } - - function deleteNodeInList(n: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeInList(sourceFile, n)); - } - - function deleteNodeRange(start: Node, end: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeRange(sourceFile, start, end)); - } - - function replaceNode(n: Node, newNode: Node) { - return makeChange(textChanges.ChangeTracker.fromContext(context).replaceNode(sourceFile, n, newNode)); - } - - function makeChange(changeTracker: textChanges.ChangeTracker): CodeAction { - return { - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }), - changes: changeTracker.getChanges() - }; - } } - }); + return false; + } + + function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void { + switch (token.kind) { + case SyntaxKind.Identifier: + tryDeleteIdentifier(changes, sourceFile, token); + break; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.NamespaceImport: + changes.deleteNode(sourceFile, token.parent); + break; + default: + tryDeleteDefault(changes, sourceFile, token); + } + } + + function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void { + if (isDeclarationName(token)) { + changes.deleteNode(sourceFile, token.parent); + } + else if (isLiteralComputedPropertyDeclarationName(token)) { + changes.deleteNode(sourceFile, token.parent.parent); + } + } + + function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier): void { + const parent = identifier.parent; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + tryDeleteVariableDeclaration(changes, sourceFile, parent); + break; + + case SyntaxKind.TypeParameter: + const typeParameters = (parent.parent).typeParameters; + if (typeParameters.length === 1) { + const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); + const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); + Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); + Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); + + changes.deleteNodeRange(sourceFile, previousToken, nextToken); + } + else { + changes.deleteNodeInList(sourceFile, parent); + } + break; + + case SyntaxKind.Parameter: + const functionDeclaration = parent.parent; + if (functionDeclaration.parameters.length === 1) { + changes.deleteNode(sourceFile, parent); + } + else { + changes.deleteNodeInList(sourceFile, parent); + } + break; + + // handle case where 'import a = A;' + case SyntaxKind.ImportEqualsDeclaration: + const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration); + changes.deleteNode(sourceFile, importEquals); + break; + + case SyntaxKind.ImportSpecifier: + const namedImports = parent.parent; + if (namedImports.elements.length === 1) { + tryDeleteNamedImportBinding(changes, sourceFile, namedImports); + } + else { + // delete import specifier + changes.deleteNodeInList(sourceFile, parent); + } + break; + + case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' + const importClause = parent; + if (!importClause.namedBindings) { // |import d from './file'| + changes.deleteNode(sourceFile, getAncestor(importClause, SyntaxKind.ImportDeclaration)!); + } + else { + // import |d,| * as ns from './file' + const start = importClause.name.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); + changes.deleteRange(sourceFile, { pos: start, end }); + } + else { + changes.deleteNode(sourceFile, importClause.name); + } + } + break; + + case SyntaxKind.NamespaceImport: + tryDeleteNamedImportBinding(changes, sourceFile, parent); + break; + + default: + tryDeleteDefault(changes, sourceFile, identifier); + break; + } + } + + function tryDeleteNamedImportBinding(changes: textChanges.ChangeTracker, sourceFile: SourceFile, namedBindings: NamedImportBindings): void { + if ((namedBindings.parent).name) { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false); + if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { + changes.deleteRange(sourceFile, { pos: previousToken.getStart(), end: namedBindings.end }); + } + } + else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration); + changes.deleteNode(sourceFile, importDecl); + } + } + + // token.parent is a variableDeclaration + function tryDeleteVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, varDecl: VariableDeclaration): void { + switch (varDecl.parent.parent.kind) { + case SyntaxKind.ForStatement: { + const forStatement = varDecl.parent.parent; + const forInitializer = forStatement.initializer; + if (forInitializer.declarations.length === 1) { + changes.deleteNode(sourceFile, forInitializer); + } + else { + changes.deleteNodeInList(sourceFile, varDecl); + } + break; + } + + case SyntaxKind.ForOfStatement: + const forOfStatement = varDecl.parent.parent; + Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList); + const forOfInitializer = forOfStatement.initializer; + changes.replaceNode(sourceFile, forOfInitializer.declarations[0], createObjectLiteral()); + break; + + case SyntaxKind.ForInStatement: + case SyntaxKind.TryStatement: + break; + + default: + const variableStatement = varDecl.parent.parent; + if (variableStatement.declarationList.declarations.length === 1) { + changes.deleteNode(sourceFile, variableStatement); + } + else { + changes.deleteNodeInList(sourceFile, varDecl); + } + } + } } \ No newline at end of file diff --git a/src/services/codefixes/fixes.ts b/src/services/codefixes/fixes.ts index 5b1ee8ec857..317c65b15ee 100644 --- a/src/services/codefixes/fixes.ts +++ b/src/services/codefixes/fixes.ts @@ -11,7 +11,10 @@ /// /// /// +/// /// /// /// /// +/// + diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 206a3864ac0..47e23e717d9 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -1,61 +1,24 @@ /* @internal */ namespace ts.codefix { - - export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { - const sourceFile = context.sourceFile; - - const changeTracker = textChanges.ChangeTracker.fromContext(context); - - for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); - } - - const changes = changeTracker.getChanges(); - if (!some(changes)) { - return changes; - } - - Debug.assert(changes.length === 1); - const consolidatedChanges: FileTextChanges[] = [{ - fileName: changes[0].fileName, - textChanges: [{ - span: changes[0].textChanges[0].span, - newText: changes[0].textChanges.reduce((prev, cur) => prev + cur.newText, "") - }] - - }]; - return consolidatedChanges; - } - /** * Finds members of the resolved type that are missing in the class pointed to by class decl * and generates source code for the missing members. * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for. * @returns Empty string iff there are no member insertions. */ - export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker): Node[] { + export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray, checker: TypeChecker, out: (node: ClassElement) => void): void { const classMembers = classDeclaration.symbol.members; - const missingMembers = possiblyMissingSymbols.filter(symbol => !classMembers.has(symbol.escapedName)); - - let newNodes: Node[] = []; - for (const symbol of missingMembers) { - const newNode = createNewNodeForMemberSymbol(symbol, classDeclaration, checker); - if (newNode) { - if (Array.isArray(newNode)) { - newNodes = newNodes.concat(newNode); - } - else { - newNodes.push(newNode); - } + for (const symbol of possiblyMissingSymbols) { + if (!classMembers.has(symbol.escapedName)) { + addNewNodeForMemberSymbol(symbol, classDeclaration, checker, out); } } - return newNodes; } /** * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`. */ - function createNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { + function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, out: (node: Node) => void): void { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; @@ -63,7 +26,7 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; // Clone name to remove leading trivia. - const name = getSynthesizedClone(getNameOfDeclaration(declaration)) as PropertyName; + const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration)) as PropertyName; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); @@ -75,14 +38,14 @@ namespace ts.codefix { case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: const typeNode = checker.typeToTypeNode(type, enclosingDeclaration); - const property = createProperty( + out(createProperty( /*decorators*/undefined, modifiers, name, optional ? createToken(SyntaxKind.QuestionToken) : undefined, typeNode, - /*initializer*/ undefined); - return property; + /*initializer*/ undefined)); + break; case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: // The signature for the implementation appears as an entry in `signatures` iff @@ -94,81 +57,71 @@ namespace ts.codefix { // correspondence of declarations and signatures. const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); if (!some(signatures)) { - return undefined; + break; } if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - return signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + outputMethod(signature, modifiers, name, createStubbedMethodBody()); + break; } - const signatureDeclarations: MethodDeclaration[] = []; for (const signature of signatures) { - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + // Need to ensure nodes are fresh each time so they can have different positions. + outputMethod(signature, getSynthesizedDeepClones(modifiers), getSynthesizedDeepClone(name)); } if (declarations.length > signatures.length) { const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); - if (methodDeclaration) { - signatureDeclarations.push(methodDeclaration); - } + outputMethod(signature, modifiers, name, createStubbedMethodBody()); } else { Debug.assert(declarations.length === signatures.length); - const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, optional, modifiers); - signatureDeclarations.push(methodImplementingSignatures); + out(createMethodImplementingSignatures(signatures, name, optional, modifiers)); } - return signatureDeclarations; - default: - return undefined; + break; } - function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); - if (signatureDeclaration) { - signatureDeclaration.decorators = undefined; - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclaration.body = body; - } - return signatureDeclaration; + function outputMethod(signature: Signature, modifiers: NodeArray, name: PropertyName, body?: Block): void { + const method = signatureToMethodDeclaration(checker, signature, enclosingDeclaration, modifiers, name, optional, body); + if (method) out(method); } } - export function createMethodFromCallExpression(callExpression: CallExpression, methodName: string, includeTypeScriptSyntax: boolean, makeStatic: boolean): MethodDeclaration { - const parameters = createDummyParameters(callExpression.arguments.length, /*names*/ undefined, /*minArgumentCount*/ undefined, includeTypeScriptSyntax); - - let typeParameters: TypeParameterDeclaration[]; - if (includeTypeScriptSyntax) { - const typeArgCount = length(callExpression.typeArguments); - for (let i = 0; i < typeArgCount; i++) { - const name = typeArgCount < 8 ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`; - const typeParameter = createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined); - (typeParameters ? typeParameters : typeParameters = []).push(typeParameter); - } + function signatureToMethodDeclaration(checker: TypeChecker, signature: Signature, enclosingDeclaration: ClassLikeDeclaration, modifiers: NodeArray, name: PropertyName, optional: boolean, body: Block | undefined) { + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); + if (!signatureDeclaration) { + return undefined; } - const newMethod = createMethod( + signatureDeclaration.decorators = undefined; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = body; + return signatureDeclaration; + } + + function getSynthesizedDeepClones(nodes: NodeArray | undefined): NodeArray | undefined { + return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone)); + } + + export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration { + return createMethod( /*decorators*/ undefined, /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, /*asteriskToken*/ undefined, methodName, /*questionToken*/ undefined, - typeParameters, - parameters, - /*type*/ includeTypeScriptSyntax ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined, - createStubbedMethodBody() - ); - return newMethod; + /*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) => + createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)), + /*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs), + /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), + createStubbedMethodBody()); } - function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, addAnyType: boolean) { + function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] { const parameters: ParameterDeclaration[] = []; for (let i = 0; i < argCount; i++) { const newParameter = createParameter( @@ -177,11 +130,10 @@ namespace ts.codefix { /*dotDotDotToken*/ undefined, /*name*/ names && names[i] || `arg${i}`, /*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, - /*type*/ addAnyType ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined, + /*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined); parameters.push(newParameter); } - return parameters; } @@ -205,7 +157,7 @@ namespace ts.codefix { const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name); - const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*addAnyType*/ true); + const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*inJs*/ false); if (someSigHasRestParameter) { const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); @@ -229,7 +181,7 @@ namespace ts.codefix { /*returnType*/ undefined); } - export function createStubbedMethod( + function createStubbedMethod( modifiers: ReadonlyArray, name: PropertyName, optional: boolean, @@ -258,7 +210,7 @@ namespace ts.codefix { /*multiline*/ true); } - function createVisibilityModifier(flags: ModifierFlags) { + function createVisibilityModifier(flags: ModifierFlags): Modifier | undefined { if (flags & ModifierFlags.Public) { return createToken(SyntaxKind.PublicKeyword); } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 7bf057a03d5..8f69cd95c11 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -9,14 +9,17 @@ namespace ts.codefix { Diagnostics.Cannot_find_namespace_0.code, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code ], - getCodeActions: getImportCodeActions + getCodeActions: getImportCodeActions, + // TODO: GH#20315 + fixIds: [], + getAllCodeActions: notImplemented, }); type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport"; // Map from module Id to an array of import declarations in that module. type ImportDeclarationMap = AnyImportSyntax[][]; - interface ImportCodeAction extends CodeAction { + interface ImportCodeAction extends CodeFixAction { kind: ImportCodeActionKind; moduleSpecifier?: string; } @@ -27,11 +30,12 @@ namespace ts.codefix { } interface SymbolAndTokenContext extends SymbolContext { - symbolToken: Node | undefined; + symbolToken: Identifier | undefined; } interface ImportCodeFixContext extends SymbolAndTokenContext { host: LanguageServiceHost; + program: Program; checker: TypeChecker; compilerOptions: CompilerOptions; getCanonicalFileName: GetCanonicalFileName; @@ -154,6 +158,8 @@ namespace ts.codefix { return { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), changes, + // TODO: GH#20315 + fixId: undefined, kind, moduleSpecifier }; @@ -161,15 +167,18 @@ namespace ts.codefix { function convertToImportCodeFixContext(context: CodeFixContext): ImportCodeFixContext { const useCaseSensitiveFileNames = context.host.useCaseSensitiveFileNames ? context.host.useCaseSensitiveFileNames() : false; - const checker = context.program.getTypeChecker(); - const symbolToken = getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false); + const { program } = context; + const checker = program.getTypeChecker(); + // This will always be an Identifier, since the diagnostics we fix only fail on identifiers. + const symbolToken = cast(getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false), isIdentifier); return { host: context.host, newLineCharacter: context.newLineCharacter, formatContext: context.formatContext, sourceFile: context.sourceFile, + program, checker, - compilerOptions: context.program.getCompilerOptions(), + compilerOptions: program.getCompilerOptions(), cachedImportDeclarations: [], getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames), symbolName: symbolToken.getText(), @@ -205,14 +214,17 @@ namespace ts.codefix { for (const declaration of declarations) { const namespace = getNamespaceImportName(declaration); if (namespace) { - actions.push(getCodeActionForUseExistingNamespaceImport(namespace.text, context, context.symbolToken)); + const moduleSymbol = context.checker.getAliasedSymbol(context.checker.getSymbolAtLocation(namespace)); + if (moduleSymbol && moduleSymbol.exports.has(escapeLeadingUnderscores(context.symbolName))) { + actions.push(getCodeActionForUseExistingNamespaceImport(namespace.text, context, context.symbolToken)); + } } } } return [...actions, ...getCodeActionsForAddImport(moduleSymbols, context, declarations)]; } - function getNamespaceImportName(declaration: AnyImportSyntax): Identifier { + function getNamespaceImportName(declaration: AnyImportSyntax): Identifier | undefined { if (declaration.kind === SyntaxKind.ImportDeclaration) { const namedBindings = declaration.importClause && isImportClause(declaration.importClause) && declaration.importClause.namedBindings; return namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport ? namedBindings.name : undefined; @@ -249,7 +261,7 @@ namespace ts.codefix { } function getCodeActionForNewImport(context: SymbolContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction { - const { kind, sourceFile, newLineCharacter, symbolName } = context; + const { kind, sourceFile, symbolName } = context; const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax); const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier); @@ -268,10 +280,10 @@ namespace ts.codefix { const changes = ChangeTracker.with(context, changeTracker => { if (lastImportDeclaration) { - changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: newLineCharacter }); + changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl); } else { - changeTracker.insertNodeAt(sourceFile, getSourceFileImportLocation(sourceFile), importDecl, { suffix: `${newLineCharacter}${newLineCharacter}` }); + changeTracker.insertNodeAtTopOfFile(sourceFile, importDecl, /*blankLineBetween*/ true); } }); @@ -294,6 +306,10 @@ namespace ts.codefix { return literal; } + function usesJsExtensionOnImports(sourceFile: SourceFile): boolean { + return firstDefined(sourceFile.imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false; + } + function createImportClauseOfKind(kind: ImportKind.Default | ImportKind.Named | ImportKind.Namespace, symbolName: string) { const id = createIdentifier(symbolName); switch (kind) { @@ -309,6 +325,7 @@ namespace ts.codefix { } export function getModuleSpecifiersForNewImport( + program: Program, sourceFile: SourceFile, moduleSymbols: ReadonlyArray, options: CompilerOptions, @@ -316,68 +333,84 @@ namespace ts.codefix { host: LanguageServiceHost, ): string[] { const { baseUrl, paths, rootDirs } = options; - const choicesForEachExportingModule = mapIterator(arrayIterator(moduleSymbols), moduleSymbol => { - const moduleFileName = moduleSymbol.valueDeclaration.getSourceFile().fileName; - const sourceDirectory = getDirectoryPath(sourceFile.fileName); - const global = tryGetModuleNameFromAmbientModule(moduleSymbol) - || tryGetModuleNameFromTypeRoots(options, host, getCanonicalFileName, moduleFileName) - || tryGetModuleNameAsNodeModule(options, moduleFileName, host, getCanonicalFileName, sourceDirectory) - || rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName); - if (global) { - return [global]; - } - - const relativePath = removeExtensionAndIndexPostFix(getRelativePath(moduleFileName, sourceDirectory, getCanonicalFileName), options); - if (!baseUrl) { - return [relativePath]; - } - - const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName); - if (!relativeToBaseUrl) { - return [relativePath]; - } - - const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, options); - if (paths) { - const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); - if (fromPaths) { - return [fromPaths]; + const addJsExtension = usesJsExtensionOnImports(sourceFile); + const choicesForEachExportingModule = flatMap(moduleSymbols, moduleSymbol => + getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile()).map(moduleFileName => { + const sourceDirectory = getDirectoryPath(sourceFile.fileName); + const global = tryGetModuleNameFromAmbientModule(moduleSymbol) + || tryGetModuleNameFromTypeRoots(options, host, getCanonicalFileName, moduleFileName, addJsExtension) + || tryGetModuleNameAsNodeModule(options, moduleFileName, host, getCanonicalFileName, sourceDirectory) + || rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName); + if (global) { + return [global]; } - } - /* - Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl. + const relativePath = removeExtensionAndIndexPostFix(getRelativePath(moduleFileName, sourceDirectory, getCanonicalFileName), options, addJsExtension); + if (!baseUrl) { + return [relativePath]; + } - Suppose we have: - baseUrl = /base - sourceDirectory = /base/a/b - moduleFileName = /base/foo/bar - Then: - relativePath = ../../foo/bar - getRelativePathNParents(relativePath) = 2 - pathFromSourceToBaseUrl = ../../ - getRelativePathNParents(pathFromSourceToBaseUrl) = 2 - 2 < 2 = false - In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar". + const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName); + if (!relativeToBaseUrl) { + return [relativePath]; + } - Suppose we have: - baseUrl = /base - sourceDirectory = /base/foo/a - moduleFileName = /base/foo/bar - Then: - relativePath = ../a - getRelativePathNParents(relativePath) = 1 - pathFromSourceToBaseUrl = ../../ - getRelativePathNParents(pathFromSourceToBaseUrl) = 2 - 1 < 2 = true - In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a". - */ - const pathFromSourceToBaseUrl = getRelativePath(baseUrl, sourceDirectory, getCanonicalFileName); - const relativeFirst = getRelativePathNParents(pathFromSourceToBaseUrl) < getRelativePathNParents(relativePath); - return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath]; - }); + const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, options, addJsExtension); + if (paths) { + const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); + if (fromPaths) { + return [fromPaths]; + } + } + + if (isPathRelativeToParent(relativeToBaseUrl)) { + return [relativePath]; + } + + /* + Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl. + + Suppose we have: + baseUrl = /base + sourceDirectory = /base/a/b + moduleFileName = /base/foo/bar + Then: + relativePath = ../../foo/bar + getRelativePathNParents(relativePath) = 2 + pathFromSourceToBaseUrl = ../../ + getRelativePathNParents(pathFromSourceToBaseUrl) = 2 + 2 < 2 = false + In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar". + + Suppose we have: + baseUrl = /base + sourceDirectory = /base/foo/a + moduleFileName = /base/foo/bar + Then: + relativePath = ../a + getRelativePathNParents(relativePath) = 1 + pathFromSourceToBaseUrl = ../../ + getRelativePathNParents(pathFromSourceToBaseUrl) = 2 + 1 < 2 = true + In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a". + */ + const pathFromSourceToBaseUrl = getRelativePath(baseUrl, sourceDirectory, getCanonicalFileName); + const relativeFirst = getRelativePathNParents(pathFromSourceToBaseUrl) < getRelativePathNParents(relativePath); + return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath]; + })); // Only return results for the re-export with the shortest possible path (and also give the other path even if that's long.) - return best(choicesForEachExportingModule, (a, b) => a[0].length < b[0].length); + return best(arrayIterator(choicesForEachExportingModule), (a, b) => a[0].length < b[0].length); + } + + /** + * Looks for a existing imports that use symlinks to this module. + * Only if no symlink is available, the real path will be used. + */ + function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray { + const symlinks = mapDefined(program.getSourceFiles(), sf => + sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res => + res && res.resolvedFileName === fileName ? res.originalPath : undefined)); + return symlinks.length === 0 ? [fileName] : symlinks; } function getRelativePathNParents(relativePath: string): number { @@ -395,9 +428,10 @@ namespace ts.codefix { } } - function tryGetModuleNameFromPaths(relativeNameWithIndex: string, relativeName: string, paths: MapLike>): string | undefined { + function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike>): string | undefined { for (const key in paths) { - for (const pattern of paths[key]) { + for (const patternText of paths[key]) { + const pattern = removeFileExtension(normalizePath(patternText)); const indexOfStar = pattern.indexOf("*"); if (indexOfStar === 0 && pattern.length === 1) { continue; @@ -405,14 +439,14 @@ namespace ts.codefix { else if (indexOfStar !== -1) { const prefix = pattern.substr(0, indexOfStar); const suffix = pattern.substr(indexOfStar + 1); - if (relativeName.length >= prefix.length + suffix.length && - startsWith(relativeName, prefix) && - endsWith(relativeName, suffix)) { - const matchedStar = relativeName.substr(prefix.length, relativeName.length - suffix.length); - return key.replace("\*", matchedStar); + if (relativeToBaseUrl.length >= prefix.length + suffix.length && + startsWith(relativeToBaseUrl, prefix) && + endsWith(relativeToBaseUrl, suffix)) { + const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length); + return key.replace("*", matchedStar); } } - else if (pattern === relativeName || pattern === relativeNameWithIndex) { + else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) { return key; } } @@ -435,12 +469,13 @@ namespace ts.codefix { host: GetEffectiveTypeRootsHost, getCanonicalFileName: (file: string) => string, moduleFileName: string, + addJsExtension: boolean, ): string | undefined { const roots = getEffectiveTypeRoots(options, host); return roots && firstDefined(roots, unNormalizedTypeRoot => { const typeRoot = toPath(unNormalizedTypeRoot, /*basePath*/ undefined, getCanonicalFileName); if (startsWith(moduleFileName, typeRoot)) { - return removeExtensionAndIndexPostFix(moduleFileName.substring(typeRoot.length + 1), options); + return removeExtensionAndIndexPostFix(moduleFileName.substring(typeRoot.length + 1), options, addJsExtension); } }); } @@ -571,17 +606,28 @@ namespace ts.codefix { } function getPathRelativeToRootDirs(path: string, rootDirs: ReadonlyArray, getCanonicalFileName: GetCanonicalFileName): string | undefined { - return firstDefined(rootDirs, rootDir => getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)); + return firstDefined(rootDirs, rootDir => { + const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); + return isPathRelativeToParent(relativePath) ? undefined : relativePath; + }); } - function removeExtensionAndIndexPostFix(fileName: string, options: CompilerOptions): string { + function removeExtensionAndIndexPostFix(fileName: string, options: CompilerOptions, addJsExtension: boolean): string { const noExtension = removeFileExtension(fileName); - return getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeJs ? removeSuffix(noExtension, "/index") : noExtension; + return addJsExtension + ? noExtension + ".js" + : getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeJs + ? removeSuffix(noExtension, "/index") + : noExtension; } function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - return isRootedDiskPath(relativePath) || startsWith(relativePath, "..") ? undefined : relativePath; + return isRootedDiskPath(relativePath) ? undefined : relativePath; + } + + function isPathRelativeToParent(path: string): boolean { + return startsWith(path, ".."); } function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) { @@ -613,7 +659,7 @@ namespace ts.codefix { } const existingDeclaration = firstDefined(declarations, moduleSpecifierFromAnyImport); - const moduleSpecifiers = existingDeclaration ? [existingDeclaration] : getModuleSpecifiersForNewImport(ctx.sourceFile, moduleSymbols, ctx.compilerOptions, ctx.getCanonicalFileName, ctx.host); + const moduleSpecifiers = existingDeclaration ? [existingDeclaration] : getModuleSpecifiersForNewImport(ctx.program, ctx.sourceFile, moduleSymbols, ctx.compilerOptions, ctx.getCanonicalFileName, ctx.host); return moduleSpecifiers.map(spec => getCodeActionForNewImport(ctx, spec)); } @@ -663,7 +709,7 @@ namespace ts.codefix { } } - function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext, symbolToken: Node): ImportCodeAction { + function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext, symbolToken: Identifier): ImportCodeAction { const { symbolName, sourceFile } = context; /** @@ -676,13 +722,10 @@ namespace ts.codefix { * namespace instead of altering the import declaration. For example, "foo" would * become "ns.foo" */ - return createCodeAction( - Diagnostics.Change_0_to_1, - [symbolName, `${namespacePrefix}.${symbolName}`], - ChangeTracker.with(context, tracker => - tracker.replaceNode(sourceFile, symbolToken, createPropertyAccess(createIdentifier(namespacePrefix), symbolName))), - "CodeChange", - /*moduleSpecifier*/ undefined); + // Prefix the node instead of it replacing it, because this may be used for import completions and we don't want the text changes to overlap with the identifier being completed. + const changes = ChangeTracker.with(context, tracker => + tracker.changeIdentifierToPropertyAccess(sourceFile, namespacePrefix, symbolToken)); + return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes, "CodeChange", /*moduleSpecifier*/ undefined); } function getImportCodeActions(context: CodeFixContext): ImportCodeAction[] { @@ -703,7 +746,7 @@ namespace ts.codefix { } else if (isJsxOpeningLikeElement(symbolToken.parent) && symbolToken.parent.tagName === symbolToken) { // The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`. - symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value)); + symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value, /*excludeGlobals*/ false)); symbolName = symbol.name; } else { @@ -746,11 +789,14 @@ namespace ts.codefix { forEachExternalModuleToImportFrom(checker, sourceFile, allSourceFiles, moduleSymbol => { cancellationToken.throwIfCancellationRequested(); // check the default export - const defaultExport = checker.tryGetMemberInModuleExports("default", moduleSymbol); + const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol); if (defaultExport) { const localSymbol = getLocalSymbolForExportDefault(defaultExport); - if ((localSymbol && localSymbol.escapedName === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, context.compilerOptions.target) === symbolName) - && checkSymbolHasMeaning(localSymbol || defaultExport, currentTokenMeaning)) { + if (( + localSymbol && localSymbol.escapedName === symbolName || + getEscapedNameForExportDefault(defaultExport) === symbolName || + moduleSymbolToValidIdentifier(moduleSymbol, context.compilerOptions.target) === symbolName + ) && checkSymbolHasMeaning(localSymbol || defaultExport, currentTokenMeaning)) { // check if this symbol is already used const symbolId = getUniqueSymbolId(localSymbol || defaultExport, checker); symbolIdActionMap.addActions(symbolId, getCodeActionForImport(moduleSymbol, { ...context, kind: ImportKind.Default })); @@ -763,6 +809,22 @@ namespace ts.codefix { const symbolId = getUniqueSymbolId(exportSymbolWithIdenticalName, checker); symbolIdActionMap.addActions(symbolId, getCodeActionForImport(moduleSymbol, { ...context, kind: ImportKind.Named })); } + + function getEscapedNameForExportDefault(symbol: Symbol): __String | undefined { + return firstDefined(symbol.declarations, declaration => { + if (isExportAssignment(declaration)) { + if (isIdentifier(declaration.expression)) { + return declaration.expression.escapedText; + } + } + else if (isExportSpecifier(declaration)) { + Debug.assert(declaration.name.escapedText === InternalSymbolName.Default); + if (declaration.propertyName) { + return declaration.propertyName.escapedText; + } + } + }); + } }); return symbolIdActionMap.getAllActions(); @@ -805,7 +867,7 @@ namespace ts.codefix { return moduleSpecifierToValidIdentifier(removeFileExtension(getBaseFileName(moduleSymbol.name)), target); } - function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget): string { + export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget): string { let res = ""; let lastCharWasValid = true; const firstCharCode = moduleSpecifier.charCodeAt(0); diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 3f03180360d..95d85bc5aa0 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1,270 +1,268 @@ /* @internal */ namespace ts.codefix { + const fixId = "inferFromUsage"; + const errorCodes = [ + // Variable declarations + Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, + + // Variable uses + Diagnostics.Variable_0_implicitly_has_an_1_type.code, + + // Parameter declarations + Diagnostics.Parameter_0_implicitly_has_an_1_type.code, + Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code, + + // Get Accessor declarations + Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code, + Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code, + + // Set Accessor declarations + Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code, + + // Property declarations + Diagnostics.Member_0_implicitly_has_an_1_type.code, + ]; registerCodeFix({ - errorCodes: [ - // Variable declarations - Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, + errorCodes, + getCodeActions({ sourceFile, program, span: { start }, errorCode, cancellationToken }) { + if (isSourceFileJavaScript(sourceFile)) { + return undefined; // TODO: GH#20113 + } - // Variable uses - Diagnostics.Variable_0_implicitly_has_an_1_type.code, + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); + const fix = getFix(sourceFile, token, errorCode, program, cancellationToken); + if (!fix) return undefined; - // Parameter declarations - Diagnostics.Parameter_0_implicitly_has_an_1_type.code, - Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code, - - // Get Accessor declarations - Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code, - Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code, - - // Set Accessor declarations - Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code, - - // Property declarations - Diagnostics.Member_0_implicitly_has_an_1_type.code, - ], - getCodeActions: getActionsForAddExplicitTypeAnnotation + const { declaration, textChanges } = fix; + const name = getNameOfDeclaration(declaration); + const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name.getText()]); + return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], fixId }]; + }, + fixIds: [fixId], + getAllCodeActions(context) { + const { sourceFile, program, cancellationToken } = context; + const seenFunctions = createMap(); + return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + const fix = getFix(sourceFile, getTokenAtPosition(err.file!, err.start!, /*includeJsDocComment*/ false), err.code, program, cancellationToken, seenFunctions); + if (fix) changes.push(...fix.textChanges); + }); + }, }); - function getActionsForAddExplicitTypeAnnotation({ sourceFile, program, span: { start }, errorCode, cancellationToken }: CodeFixContext): CodeAction[] | undefined { - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); - let writer: StringSymbolWriter; + function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage { + switch (errorCode) { + case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: + return isSetAccessor(getContainingFunction(token)) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; + case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: + return Diagnostics.Infer_parameter_types_from_usage; + default: + return Diagnostics.Infer_type_of_0_from_usage; + } + } - if (isInJavaScriptFile(token)) { + interface Fix { + readonly declaration: Declaration; + readonly textChanges: TextChange[]; + } + + function getFix(sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: Map): Fix | undefined { + if (!isAllowedTokenKind(token.kind)) { return undefined; } - switch (token.kind) { + switch (errorCode) { + // Variable and Property declarations + case Diagnostics.Member_0_implicitly_has_an_1_type.code: + case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: + return getCodeActionForVariableDeclaration(token.parent, sourceFile, program, cancellationToken); + + case Diagnostics.Variable_0_implicitly_has_an_1_type.code: { + const symbol = program.getTypeChecker().getSymbolAtLocation(token); + return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(symbol.valueDeclaration, sourceFile, program, cancellationToken); + } + } + + const containingFunction = getContainingFunction(token); + if (containingFunction === undefined) { + return undefined; + } + switch (errorCode) { + + // Parameter declarations + case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: + if (isSetAccessor(containingFunction)) { + return getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken); + } + // falls through + case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: + return !seenFunctions || addToSeen(seenFunctions, getNodeId(containingFunction)) + ? getCodeActionForParameters(token.parent, containingFunction, sourceFile, program, cancellationToken) + : undefined; + + // Get Accessor declarations + case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code: + case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code: + return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined; + + // Set Accessor declarations + case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: + return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined; + + default: + throw Debug.fail(String(errorCode)); + } + } + + function isAllowedTokenKind(kind: SyntaxKind): boolean { + switch (kind) { case SyntaxKind.Identifier: case SyntaxKind.DotDotDotToken: case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.ReadonlyKeyword: - // Allowed - break; + return true; default: - return undefined; + return false; } + } - const containingFunction = getContainingFunction(token); - const checker = program.getTypeChecker(); + function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(declaration.name)) return undefined; + const type = inferTypeForVariableFromUsage(declaration.name, sourceFile, program, cancellationToken); + return makeFix(declaration, declaration.name.getEnd(), type, program); + } - switch (errorCode) { - // Variable and Property declarations - case Diagnostics.Member_0_implicitly_has_an_1_type.code: - case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: - return getCodeActionForVariableDeclaration(token.parent); - case Diagnostics.Variable_0_implicitly_has_an_1_type.code: - return getCodeActionForVariableUsage(token); - - // Parameter declarations - case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: - if (isSetAccessor(containingFunction)) { - return getCodeActionForSetAccessor(containingFunction); - } - // falls through - case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: - return getCodeActionForParameters(token.parent); - - // Get Accessor declarations - case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code: - case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code: - return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction) : undefined; - - // Set Accessor declarations - case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: - return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction) : undefined; + function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration { + switch (declaration.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + return true; + case SyntaxKind.FunctionExpression: + return !!(declaration as FunctionExpression).name; } + return false; + } - return undefined; - - function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature) { - if (!isIdentifier(declaration.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(declaration.name); - const typeString = type && typeToString(type, declaration); - - if (!typeString) { - return undefined; - } - - return createCodeActions(declaration.name.getText(), declaration.name.getEnd(), `: ${typeString}`); - } - - function getCodeActionForVariableUsage(token: Identifier) { - const symbol = checker.getSymbolAtLocation(token); - return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(symbol.valueDeclaration); - } - - function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration { - switch (declaration.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - return true; - case SyntaxKind.FunctionExpression: - return !!(declaration as FunctionExpression).name; - } - return false; - } - - function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration): CodeAction[] { - if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { - return undefined; - } - - const types = inferTypeForParametersFromUsage(containingFunction) || - map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name)); - - if (!types) { - return undefined; - } - - const textChanges: TextChange[] = zipWith(containingFunction.parameters, types, (parameter, type) => { - if (type && !parameter.type && !parameter.initializer) { - const typeString = typeToString(type, containingFunction); - return typeString ? { - span: { start: parameter.end, length: 0 }, - newText: `: ${typeString}` - } : undefined; - } - }).filter(c => !!c); - - return textChanges.length ? [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_parameter_types_from_usage), [parameterDeclaration.name.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges - }] - }] : undefined; - } - - function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration) { - const setAccessorParameter = setAccessorDeclaration.parameters[0]; - if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name) || - inferTypeForVariableFromUsage(setAccessorParameter.name); - const typeString = type && typeToString(type, containingFunction); - if (!typeString) { - return undefined; - } - - return createCodeActions(setAccessorDeclaration.name.getText(), setAccessorParameter.name.getEnd(), `: ${typeString}`); - } - - function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration) { - if (!isIdentifier(getAccessorDeclaration.name)) { - return undefined; - } - - const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name); - const typeString = type && typeToString(type, containingFunction); - if (!typeString) { - return undefined; - } - - const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken); - return createCodeActions(getAccessorDeclaration.name.getText(), closeParenToken.getEnd(), `: ${typeString}`); - } - - function createCodeActions(name: string, start: number, typeString: string) { - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_type_of_0_from_usage), [name]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start, length: 0 }, - newText: typeString - }] - }] - }]; - } - - function getReferences(token: PropertyName | Token) { - const references = FindAllReferences.findReferencedSymbols( - program, - cancellationToken, - program.getSourceFiles(), - token.getSourceFile(), - token.getStart()); - - Debug.assert(!!references, "Found no references!"); - Debug.assert(references.length === 1, "Found more references than expected"); - - return map(references[0].references, r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); - } - - function inferTypeForVariableFromUsage(token: Identifier) { - return InferFromReference.inferTypeFromReferences(getReferences(token), checker, cancellationToken); - } - - function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration) { - switch (containingFunction.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - const isConstructor = containingFunction.kind === SyntaxKind.Constructor; - const searchToken = isConstructor ? - >getFirstChildOfKind(containingFunction, sourceFile, SyntaxKind.ConstructorKeyword) : - containingFunction.name; - if (searchToken) { - return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken), containingFunction, checker, cancellationToken); - } - } - } - - function getTypeAccessiblityWriter() { - if (!writer) { - let str = ""; - let typeIsAccessible = true; - - const writeText: (text: string) => void = text => str += text; - writer = { - string: () => typeIsAccessible ? str : undefined, - writeKeyword: writeText, - writeOperator: writeText, - writePunctuation: writeText, - writeSpace: writeText, - writeStringLiteral: writeText, - writeParameter: writeText, - writeProperty: writeText, - writeSymbol: writeText, - writeLine: () => str += " ", - increaseIndent: noop, - decreaseIndent: noop, - clear: () => { str = ""; typeIsAccessible = true; }, - trackSymbol: (symbol, declaration, meaning) => { - if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { - typeIsAccessible = false; - } - }, - reportInaccessibleThisError: () => { typeIsAccessible = false; }, - reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; }, - reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; } - }; - } - writer.clear(); - return writer; - } - - function typeToString(type: Type, enclosingDeclaration: Declaration) { - const writer = getTypeAccessiblityWriter(); - checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); - return writer.string(); - } - - function getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) { - for (const child of node.getChildren(sourcefile)) { - if (child.kind === kind) return child; - } + function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) { return undefined; } + + const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || + containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, sourceFile, program, cancellationToken) : undefined); + if (!types) return undefined; + + // We didn't actually find a set of type inference positions matching each parameter position + if (containingFunction.parameters.length !== types.length) { + return undefined; + } + + const textChanges = arrayFrom(mapDefinedIterator(zipToIterator(containingFunction.parameters, types), ([parameter, type]) => + type && !parameter.type && !parameter.initializer ? makeChange(containingFunction, parameter.end, type, program) : undefined)); + return textChanges.length ? { declaration: parameterDeclaration, textChanges } : undefined; + } + + function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + const setAccessorParameter = setAccessorDeclaration.parameters[0]; + if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) { + return undefined; + } + + const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, sourceFile, program, cancellationToken) || + inferTypeForVariableFromUsage(setAccessorParameter.name, sourceFile, program, cancellationToken); + return makeFix(setAccessorParameter, setAccessorParameter.name.getEnd(), type, program); + } + + function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined { + if (!isIdentifier(getAccessorDeclaration.name)) { + return undefined; + } + + const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name, sourceFile, program, cancellationToken); + const closeParenToken = findChildOfKind(getAccessorDeclaration, SyntaxKind.CloseParenToken, sourceFile); + return makeFix(getAccessorDeclaration, closeParenToken.getEnd(), type, program); + } + + function makeFix(declaration: Declaration, start: number, type: Type | undefined, program: Program): Fix | undefined { + return type && { declaration, textChanges: [makeChange(declaration, start, type, program)] }; + } + + function makeChange(declaration: Declaration, start: number, type: Type | undefined, program: Program): TextChange | undefined { + const typeString = type && typeToString(type, declaration, program.getTypeChecker()); + return typeString === undefined ? undefined : { span: createTextSpan(start, 0), newText: `: ${typeString}` }; + } + + function getReferences(token: PropertyName | Token, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Identifier[] { + const references = FindAllReferences.findReferencedSymbols( + program, + cancellationToken, + program.getSourceFiles(), + sourceFile, + token.getStart(sourceFile)); + + if (!references || references.length !== 1) { + return []; + } + + return references[0].references.map(r => getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false)); + } + + function inferTypeForVariableFromUsage(token: Identifier, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Type | undefined { + return InferFromReference.inferTypeFromReferences(getReferences(token, sourceFile, program, cancellationToken), program.getTypeChecker(), cancellationToken); + } + + function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | undefined { + switch (containingFunction.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + const isConstructor = containingFunction.kind === SyntaxKind.Constructor; + const searchToken = isConstructor ? + findChildOfKind>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile) : + containingFunction.name; + if (searchToken) { + return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, sourceFile, program, cancellationToken), containingFunction, program.getTypeChecker(), cancellationToken); + } + } + } + + function getTypeAccessiblityWriter(checker: TypeChecker): StringSymbolWriter { + let str = ""; + let typeIsAccessible = true; + + const writeText: (text: string) => void = text => str += text; + return { + string: () => typeIsAccessible ? str : undefined, + writeKeyword: writeText, + writeOperator: writeText, + writePunctuation: writeText, + writeSpace: writeText, + writeStringLiteral: writeText, + writeParameter: writeText, + writeProperty: writeText, + writeSymbol: writeText, + writeLine: () => writeText(" "), + increaseIndent: noop, + decreaseIndent: noop, + clear: () => { str = ""; typeIsAccessible = true; }, + trackSymbol: (symbol, declaration, meaning) => { + if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + typeIsAccessible = false; + } + }, + reportInaccessibleThisError: () => { typeIsAccessible = false; }, + reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; }, + reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; } + }; + } + + function typeToString(type: Type, enclosingDeclaration: Declaration, checker: TypeChecker): string { + const writer = getTypeAccessiblityWriter(checker); + checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); + return writer.string(); } namespace InferFromReference { @@ -295,6 +293,10 @@ namespace ts.codefix { } export function inferTypeForParametersFromReferences(references: Identifier[], declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): (Type | undefined)[] | undefined { + if (references.length === 0) { + return undefined; + } + if (declaration.parameters) { const usageContext: UsageContext = {}; for (const reference of references) { @@ -319,7 +321,7 @@ namespace ts.codefix { } } if (types.length) { - const type = checker.getWidenedType(checker.getUnionType(types, /*subtypeReduction*/ true)); + const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype)); paramTypes[parameterIndex] = isRestParameter ? checker.createArrayType(type) : type; } } @@ -556,12 +558,12 @@ namespace ts.codefix { return checker.getStringType(); } else if (usageContext.candidateTypes) { - return checker.getWidenedType(checker.getUnionType(map(usageContext.candidateTypes, t => checker.getBaseTypeOfLiteralType(t)), /*subtypeReduction*/ true)); + return checker.getWidenedType(checker.getUnionType(map(usageContext.candidateTypes, t => checker.getBaseTypeOfLiteralType(t)), UnionReduction.Subtype)); } else if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) { const paramType = getParameterTypeFromCallContexts(0, usageContext.properties.get("then" as __String).callContexts, /*isRestParameter*/ false, checker); const types = paramType.getCallSignatures().map(c => c.getReturnType()); - return checker.createPromiseType(types.length ? checker.getUnionType(types, /*subtypeReduction*/ true) : checker.getAnyType()); + return checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType()); } else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) { return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String).callContexts, /*isRestParameter*/ false, checker)); @@ -624,7 +626,7 @@ namespace ts.codefix { } if (types.length) { - const type = checker.getWidenedType(checker.getUnionType(types, /*subtypeReduction*/ true)); + const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype)); return isRestParameter ? checker.createArrayType(type) : type; } return undefined; diff --git a/src/services/completions.ts b/src/services/completions.ts index 28837bcce40..bdd5e4a86a2 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -39,44 +39,60 @@ namespace ts.Completions { return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log); } + const contextToken = findPrecedingToken(position, sourceFile); + if (contextToken && isBreakOrContinueStatement(contextToken.parent) + && (contextToken.kind === SyntaxKind.BreakKeyword || contextToken.kind === SyntaxKind.ContinueKeyword || contextToken.kind === SyntaxKind.Identifier)) { + return getLabelCompletionAtPosition(contextToken.parent); + } + const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, options, compilerOptions.target); if (!completionData) { return undefined; } - const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData; + switch (completionData.kind) { + case CompletionDataKind.Data: + return completionInfoFromData(sourceFile, typeChecker, compilerOptions, log, completionData, options.includeInsertTextCompletions); + case CompletionDataKind.JsDocTagName: + // If the current position is a jsDoc tag name, only tag names should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); + case CompletionDataKind.JsDocTag: + // If the current position is a jsDoc tag, only tags should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); + case CompletionDataKind.JsDocParameterName: + return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); + default: + throw Debug.assertNever(completionData); + } + } - if (sourceFile.languageVariant === LanguageVariant.JSX && - location && location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) { + function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + } + + function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, includeInsertTextCompletions: boolean): CompletionInfo { + const { symbols, completionKind, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData; + + if (sourceFile.languageVariant === LanguageVariant.JSX && location && location.parent && isJsxClosingElement(location.parent)) { // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. // For example: - // var x =
completion list at "1" will contain "div" with type any - const tagName = (location.parent.parent).openingElement.tagName; + // var x =
+ // The completion list at "1" will contain "div" with type any + const tagName = location.parent.parent.openingElement.tagName; return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, entries: [{ - name: (tagName).getFullText(), + name: tagName.getFullText(), kind: ScriptElementKind.classElement, kindModifiers: undefined, sortText: "0", }]}; } - if (request) { - const entries = request.kind === "JsDocTagName" - // If the current position is a jsDoc tag name, only tag names should be provided for completion - ? JsDoc.getJSDocTagNameCompletions() - : request.kind === "JsDocTag" - // If the current position is a jsDoc tag, only tags should be provided for completion - ? JsDoc.getJSDocTagCompletions() - : JsDoc.getJSDocParameterNameCompletions(request.tag); - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; - } - const entries: CompletionEntry[] = []; if (isSourceFileJavaScript(sourceFile)) { - const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap); + const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, recommendedCompletion, symbolToOriginInfoMap); getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries); } else { @@ -84,7 +100,7 @@ namespace ts.Completions { return undefined; } - getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap); + getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, recommendedCompletion, symbolToOriginInfoMap); } // TODO add filter for keyword based on type/value/namespace and also location @@ -92,17 +108,29 @@ namespace ts.Completions { // Add all keywords if // - this is not a member completion list (all the keywords) // - other filters are enabled in required scenario so add those keywords + const isMemberCompletion = isMemberCompletionKind(completionKind); if (keywordFilters !== KeywordCompletionFilters.None || !isMemberCompletion) { addRange(entries, getKeywordCompletions(keywordFilters)); } - return { isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, entries }; + return { isGlobalCompletion: completionKind === CompletionKind.Global, isMemberCompletion, isNewIdentifierLocation, entries }; + } + + function isMemberCompletionKind(kind: CompletionKind): boolean { + switch (kind) { + case CompletionKind.ObjectPropertyDeclaration: + case CompletionKind.MemberLike: + case CompletionKind.PropertyAccess: + return true; + default: + return false; + } } function getJavaScriptCompletionEntries( sourceFile: SourceFile, position: number, - uniqueNames: Map<{}>, + uniqueNames: Map, target: ScriptTarget, entries: Push): void { getNameTable(sourceFile).forEach((pos, name) => { @@ -111,16 +139,9 @@ namespace ts.Completions { return; } const realName = unescapeLeadingUnderscores(name); - - if (uniqueNames.has(realName) || isStringANonContextualKeyword(realName)) { - return; - } - - uniqueNames.set(realName, true); - const displayName = getCompletionEntryDisplayName(realName, target, /*performCharacterChecks*/ true, /*allowStringLiteral*/ false); - if (displayName) { + if (addToSeen(uniqueNames, realName) && isIdentifierText(realName, target) && !isStringANonContextualKeyword(realName)) { entries.push({ - name: displayName, + name: realName, kind: ScriptElementKind.warning, kindModifiers: "", sortText: "1" @@ -132,18 +153,22 @@ namespace ts.Completions { function createCompletionEntry( symbol: Symbol, location: Node, - performCharacterChecks: boolean, + sourceFile: SourceFile, typeChecker: TypeChecker, target: ScriptTarget, - allowStringLiteral: boolean, + kind: CompletionKind, origin: SymbolOriginInfo | undefined, recommendedCompletion: Symbol | undefined, + propertyAccessToConvert: PropertyAccessExpression | undefined, + includeInsertTextCompletions: boolean, ): CompletionEntry | undefined { - // Try to get a valid display name for this symbol, if we could not find one, then ignore it. - // We would like to only show things that can be added after a dot, so for instance numeric properties can - // not be accessed with a dot (a.1 <- invalid) - const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks, allowStringLiteral, origin); - if (!displayName) { + const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind); + if (!info) { + return undefined; + } + const { name, needsConvertPropertyAccess } = info; + Debug.assert(!(needsConvertPropertyAccess && !propertyAccessToConvert)); + if (needsConvertPropertyAccess && !includeInsertTextCompletions) { return undefined; } @@ -156,16 +181,22 @@ namespace ts.Completions { // Use a 'sortText' of 0' so that all symbol completion entries come before any other // entries (like JavaScript identifier entries). return { - name: displayName, + name, kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), kindModifiers: SymbolDisplay.getSymbolModifiers(symbol), sortText: "0", source: getSourceFromOrigin(origin), - hasAction: trueOrUndefined(origin !== undefined), + // TODO: GH#20619 Use configured quote style + insertText: needsConvertPropertyAccess ? `["${name}"]` : undefined, + replacementSpan: needsConvertPropertyAccess + ? createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert.name.end) + : undefined, + hasAction: trueOrUndefined(needsConvertPropertyAccess || origin !== undefined), isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), }; } + function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol, checker: TypeChecker): boolean { return localSymbol === recommendedCompletion || !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; @@ -183,11 +214,13 @@ namespace ts.Completions { symbols: ReadonlyArray, entries: Push, location: Node, - performCharacterChecks: boolean, + sourceFile: SourceFile, typeChecker: TypeChecker, target: ScriptTarget, log: Log, - allowStringLiteral: boolean, + kind: CompletionKind, + includeInsertTextCompletions?: boolean, + propertyAccessToConvert?: PropertyAccessExpression | undefined, recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, ): Map { @@ -197,35 +230,40 @@ namespace ts.Completions { // Based on the order we add things we will always see locals first, then globals, then module exports. // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. const uniques = createMap(); - if (symbols) { - for (const symbol of symbols) { - const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined; - const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral, origin, recommendedCompletion); - if (!entry) { - continue; - } - - const { name } = entry; - if (uniques.has(name)) { - continue; - } - - // Latter case tests whether this is a global variable. - if (!origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile()))) { - uniques.set(name, true); - } - - entries.push(entry); + for (const symbol of symbols) { + const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined; + const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, includeInsertTextCompletions); + if (!entry) { + continue; } + + const { name } = entry; + if (uniques.has(name)) { + continue; + } + + // Latter case tests whether this is a global variable. + if (!origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile()))) { + uniques.set(name, true); + } + + entries.push(entry); } log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); return uniques; } + function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { + const entries = getLabelStatementCompletions(node); + if (entries.length) { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + } + } + function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined { const node = findPrecedingToken(position, sourceFile); - if (!node || node.kind !== SyntaxKind.StringLiteral) { + if (!node || !isStringLiteral(node) && !isNoSubstitutionTemplateLiteral(node)) { return undefined; } @@ -244,7 +282,7 @@ namespace ts.Completions { // foo({ // '/*completion position*/' // }); - return getStringLiteralCompletionEntriesFromPropertyAssignment(node.parent, typeChecker, compilerOptions.target, log); + return getStringLiteralCompletionEntriesFromPropertyAssignment(node.parent, sourceFile, typeChecker, compilerOptions.target, log); } else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { // Get all names of properties on the expression @@ -253,7 +291,8 @@ namespace ts.Completions { // } // let a: A; // a['/*completion position*/'] - return getStringLiteralCompletionEntriesFromElementAccess(node.parent, typeChecker, compilerOptions.target, log); + const type = typeChecker.getTypeAtLocation(node.parent.expression); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); } else if (node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration || isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) || isImportCall(node.parent) @@ -264,20 +303,18 @@ namespace ts.Completions { // import x = require("/*completion position*/"); // var y = require("/*completion position*/"); // export * from "/*completion position*/"; - const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(node, compilerOptions, host, typeChecker); + const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker); return pathCompletionsInfo(entries); } - else if (isEqualityExpression(node.parent)) { - // Get completions from the type of the other operand - // i.e. switch (a) { - // case '/*completion position*/' + else if (isIndexedAccessTypeNode(node.parent.parent)) { + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; // } - return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation(node.parent.left === node ? node.parent.right : node.parent.left), typeChecker); - } - else if (isCaseOrDefaultClause(node.parent)) { - // Get completions from the type of the switch expression - // i.e. x === '/*completion position' - return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation((node.parent.parent.parent).expression), typeChecker); + // let x: Foo["/*completion position*/"] + const type = typeChecker.getTypeFromTypeNode(node.parent.parent.objectType); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); } else { const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); @@ -290,7 +327,7 @@ namespace ts.Completions { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" - return getStringLiteralCompletionEntriesFromType(typeChecker.getContextualType(node), typeChecker); + return getStringLiteralCompletionEntriesFromType(getContextualTypeFromParent(node, typeChecker), typeChecker); } } @@ -306,11 +343,11 @@ namespace ts.Completions { }; } - function getStringLiteralCompletionEntriesFromPropertyAssignment(element: ObjectLiteralElement, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { + function getStringLiteralCompletionEntriesFromPropertyAssignment(element: ObjectLiteralElement, sourceFile: SourceFile, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { const type = typeChecker.getContextualType((element.parent)); const entries: CompletionEntry[] = []; if (type) { - getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true); + getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, sourceFile, typeChecker, target, log, CompletionKind.String); if (entries.length) { return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } @@ -335,11 +372,10 @@ namespace ts.Completions { return undefined; } - function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { - const type = typeChecker.getTypeAtLocation(node.expression); + function getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(stringLiteralNode: StringLiteral | NoSubstitutionTemplateLiteral, sourceFile: SourceFile, type: Type, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { const entries: CompletionEntry[] = []; if (type) { - getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true); + getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, stringLiteralNode, sourceFile, typeChecker, target, log, CompletionKind.String); if (entries.length) { return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } @@ -358,6 +394,32 @@ namespace ts.Completions { return undefined; } + function getLabelStatementCompletions(node: Node): CompletionEntry[] { + const entries: CompletionEntry[] = []; + const uniques = createMap(); + let current = node; + + while (current) { + if (isFunctionLike(current)) { + break; + } + if (isLabeledStatement(current)) { + const name = current.label.text; + if (!uniques.has(name)) { + uniques.set(name, true); + entries.push({ + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.label, + sortText: "0" + }); + } + } + current = current.parent; + } + return entries; + } + function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker, uniques = createMap()): void { if (type && type.flags & TypeFlags.TypeParameter) { type = typeChecker.getBaseConstraintOfType(type); @@ -384,6 +446,13 @@ namespace ts.Completions { } } + interface SymbolCompletion { + type: "symbol"; + symbol: Symbol; + location: Node; + symbolToOriginInfoMap: SymbolOriginInfoMap; + previousToken: Node; + } function getSymbolCompletionFromEntryId( typeChecker: TypeChecker, log: (message: string) => void, @@ -392,31 +461,34 @@ namespace ts.Completions { position: number, { name, source }: CompletionEntryIdentifier, allSourceFiles: ReadonlyArray, - ): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } { - const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true }, compilerOptions.target); + ): SymbolCompletion | { type: "request", request: Request } | { type: "none" } { + const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true, includeInsertTextCompletions: true }, compilerOptions.target); if (!completionData) { return { type: "none" }; } - - const { symbols, location, allowStringLiteral, symbolToOriginInfoMap, request } = completionData; - if (request) { - return { type: "request", request }; + if (completionData.kind !== CompletionDataKind.Data) { + return { type: "request", request: completionData }; } + const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken } = completionData; + // Find the symbol with the matching entry name. // We don't need to perform character checks here because we're only comparing the // name against 'entryName' (which is known to be good), not building a new // completion entry. - const symbol = find(symbols, s => { - const origin = symbolToOriginInfoMap[getSymbolId(s)]; - return getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral, origin) === name - && getSourceFromOrigin(origin) === source; - }); - return symbol ? { type: "symbol", symbol, location, symbolToOriginInfoMap } : { type: "none" }; + return firstDefined(symbols, (symbol): SymbolCompletion => { // TODO: Shouldn't need return type annotation (GH#12632) + const origin = symbolToOriginInfoMap[getSymbolId(symbol)]; + const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target, origin, completionKind); + return info && info.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, location, symbolToOriginInfoMap, previousToken } : undefined; + }) || { type: "none" }; } function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string { - return origin && origin.isDefaultExport && symbol.name === "default" ? codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) : symbol.name; + return origin && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default + // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. + ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined) + || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) + : symbol.name; } export interface CompletionEntryIdentifier { @@ -425,7 +497,7 @@ namespace ts.Completions { } export function getCompletionEntryDetails( - typeChecker: TypeChecker, + program: Program, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, @@ -436,6 +508,7 @@ namespace ts.Completions { formatContext: formatting.FormatContext, getCanonicalFileName: GetCanonicalFileName, ): CompletionEntryDetails { + const typeChecker = program.getTypeChecker(); const { name } = entryId; // Compute all the completion symbols again. const symbolCompletion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles); @@ -443,19 +516,19 @@ namespace ts.Completions { case "request": { const { request } = symbolCompletion; switch (request.kind) { - case "JsDocTagName": + case CompletionDataKind.JsDocTagName: return JsDoc.getJSDocTagNameCompletionDetails(name); - case "JsDocTag": + case CompletionDataKind.JsDocTag: return JsDoc.getJSDocTagCompletionDetails(name); - case "JsDocParameterName": + case CompletionDataKind.JsDocParameterName: return JsDoc.getJSDocParameterNameCompletionDetails(name); default: return Debug.assertNever(request); } } case "symbol": { - const { symbol, location, symbolToOriginInfoMap } = symbolCompletion; - const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, formatContext, getCanonicalFileName, allSourceFiles); + const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion; + const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles); const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol); const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay }; @@ -479,30 +552,51 @@ namespace ts.Completions { } } + interface CodeActionsAndSourceDisplay { + readonly codeActions: CodeAction[] | undefined; + readonly sourceDisplay: SymbolDisplayPart[] | undefined; + } function getCompletionEntryCodeActionsAndSourceDisplay( symbolToOriginInfoMap: SymbolOriginInfoMap, symbol: Symbol, + program: Program, checker: TypeChecker, host: LanguageServiceHost, compilerOptions: CompilerOptions, sourceFile: SourceFile, + previousToken: Node, formatContext: formatting.FormatContext, getCanonicalFileName: GetCanonicalFileName, allSourceFiles: ReadonlyArray, - ): { codeActions: CodeAction[] | undefined, sourceDisplay: SymbolDisplayPart[] | undefined } { + ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; - if (!symbolOriginInfo) { - return { codeActions: undefined, sourceDisplay: undefined }; - } + return symbolOriginInfo + ? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles) + : { codeActions: undefined, sourceDisplay: undefined }; + } + function getCodeActionsAndSourceDisplayForImport( + symbolOriginInfo: SymbolOriginInfo, + symbol: Symbol, + program: Program, + checker: TypeChecker, + host: LanguageServiceHost, + compilerOptions: CompilerOptions, + sourceFile: SourceFile, + previousToken: Node, + formatContext: formatting.FormatContext, + getCanonicalFileName: GetCanonicalFileName, + allSourceFiles: ReadonlyArray + ): CodeActionsAndSourceDisplay { const { moduleSymbol, isDefaultExport } = symbolOriginInfo; const exportedSymbol = skipAlias(symbol.exportSymbol || symbol, checker); const moduleSymbols = getAllReExportingModules(exportedSymbol, checker, allSourceFiles); Debug.assert(contains(moduleSymbols, moduleSymbol)); - const sourceDisplay = [textPart(first(codefix.getModuleSpecifiersForNewImport(sourceFile, moduleSymbols, compilerOptions, getCanonicalFileName, host)))]; + const sourceDisplay = [textPart(first(codefix.getModuleSpecifiersForNewImport(program, sourceFile, moduleSymbols, compilerOptions, getCanonicalFileName, host)))]; const codeActions = codefix.getCodeActionForImport(moduleSymbols, { host, + program, checker, newLineCharacter: host.getNewLine(), compilerOptions, @@ -510,9 +604,9 @@ namespace ts.Completions { formatContext, symbolName: getSymbolName(symbol, symbolOriginInfo, compilerOptions.target), getCanonicalFileName, - symbolToken: undefined, + symbolToken: tryCast(previousToken, isIdentifier), kind: isDefaultExport ? codefix.ImportKind.Default : codefix.ImportKind.Named, - }); + }).slice(0, 1); // Only take the first code action return { sourceDisplay, codeActions }; } @@ -541,23 +635,33 @@ namespace ts.Completions { return completion.type === "symbol" ? completion.symbol : undefined; } + const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName } interface CompletionData { - symbols: ReadonlyArray; - isGlobalCompletion: boolean; - isMemberCompletion: boolean; - allowStringLiteral: boolean; - isNewIdentifierLocation: boolean; - location: Node | undefined; - isRightOfDot: boolean; - request?: Request; - keywordFilters: KeywordCompletionFilters; - symbolToOriginInfoMap: SymbolOriginInfoMap; - recommendedCompletion: Symbol | undefined; + readonly kind: CompletionDataKind.Data; + readonly symbols: ReadonlyArray; + readonly completionKind: CompletionKind; + /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ + readonly propertyAccessToConvert: PropertyAccessExpression | undefined; + readonly isNewIdentifierLocation: boolean; + readonly location: Node | undefined; + readonly keywordFilters: KeywordCompletionFilters; + readonly symbolToOriginInfoMap: SymbolOriginInfoMap; + readonly recommendedCompletion: Symbol | undefined; + readonly previousToken: Node | undefined; } - type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag }; + type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; - function getRecommendedCompletion(currentToken: Node, checker: TypeChecker/*, symbolToOriginInfoMap: SymbolOriginInfoMap*/): Symbol | undefined { - const ty = checker.getContextualType(currentToken as Expression); + const enum CompletionKind { + ObjectPropertyDeclaration, + Global, + PropertyAccess, + MemberLike, + String, + None, + } + + function getRecommendedCompletion(currentToken: Node, checker: TypeChecker): Symbol | undefined { + const ty = getContextualType(currentToken, checker); const symbol = ty && ty.symbol; // Don't include make a recommended completion for an abstract class return symbol && (symbol.flags & SymbolFlags.Enum || symbol.flags & SymbolFlags.Class && !isAbstractConstructorSymbol(symbol)) @@ -565,6 +669,48 @@ namespace ts.Completions { : undefined; } + function getContextualType(currentToken: Node, checker: ts.TypeChecker): Type | undefined { + const { parent } = currentToken; + switch (currentToken.kind) { + case ts.SyntaxKind.Identifier: + return getContextualTypeFromParent(currentToken as ts.Identifier, checker); + case ts.SyntaxKind.EqualsToken: + return ts.isVariableDeclaration(parent) ? checker.getContextualType(parent.initializer) : + ts.isBinaryExpression(parent) ? checker.getTypeAtLocation(parent.left) : undefined; + case ts.SyntaxKind.NewKeyword: + return checker.getContextualType(parent as ts.Expression); + case ts.SyntaxKind.CaseKeyword: + return getSwitchedType(cast(currentToken.parent, isCaseClause), checker); + default: + return isEqualityOperatorKind(currentToken.kind) && ts.isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) + // completion at `x ===/**/` should be for the right side + ? checker.getTypeAtLocation(parent.left) + : checker.getContextualType(currentToken as ts.Expression); + } + } + + function getContextualTypeFromParent(node: ts.Expression, checker: ts.TypeChecker): Type | undefined { + const { parent } = node; + switch (parent.kind) { + case ts.SyntaxKind.NewExpression: + return checker.getContextualType(parent as ts.NewExpression); + case ts.SyntaxKind.BinaryExpression: { + const { left, operatorToken, right } = parent as ts.BinaryExpression; + return isEqualityOperatorKind(operatorToken.kind) + ? checker.getTypeAtLocation(node === right ? left : right) + : checker.getContextualType(node); + } + case ts.SyntaxKind.CaseClause: + return (parent as ts.CaseClause).expression === node ? getSwitchedType(parent as ts.CaseClause, checker) : undefined; + default: + return checker.getContextualType(node); + } + } + + function getSwitchedType(caseClause: ts.CaseClause, checker: ts.TypeChecker): ts.Type { + return checker.getTypeAtLocation(caseClause.parent.parent.expression); + } + function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); if (chain) return first(chain); @@ -583,9 +729,7 @@ namespace ts.Completions { allSourceFiles: ReadonlyArray, options: GetCompletionsAtPositionOptions, target: ScriptTarget, - ): CompletionData | undefined { - let request: Request | undefined; - + ): CompletionData | Request | undefined { let start = timestamp(); let currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) @@ -603,7 +747,7 @@ namespace ts.Completions { if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { // The current position is next to the '@' sign, when no tag name being provided yet. // Provide a full list of tag names - request = { kind: "JsDocTagName" }; + return { kind: CompletionDataKind.JsDocTagName }; } else { // When completion is requested without "@", we will have check to make sure that @@ -624,7 +768,7 @@ namespace ts.Completions { // */ const lineStart = getLineStartPositionForPosition(position, sourceFile); if (!(sourceFile.text.substring(lineStart, position).match(/[^\*|\s|(/\*\*)]/))) { - request = { kind: "JsDocTag" }; + return { kind: CompletionDataKind.JsDocTag }; } } } @@ -635,7 +779,7 @@ namespace ts.Completions { const tag = getJsDocTagAtPosition(currentToken, position); if (tag) { if (tag.tagName.pos <= position && position <= tag.tagName.end) { - request = { kind: "JsDocTagName" }; + return { kind: CompletionDataKind.JsDocTagName }; } if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ true); @@ -648,26 +792,10 @@ namespace ts.Completions { } } if (isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { - request = { kind: "JsDocParameterName", tag }; + return { kind: CompletionDataKind.JsDocParameterName, tag }; } } - if (request) { - return { - symbols: emptyArray, - isGlobalCompletion: false, - isMemberCompletion: false, - allowStringLiteral: false, - isNewIdentifierLocation: false, - location: undefined, - isRightOfDot: false, - request, - keywordFilters: KeywordCompletionFilters.None, - symbolToOriginInfoMap: undefined, - recommendedCompletion: undefined, - }; - } - if (!insideJsDocTagTypeExpression) { // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal // comment or the plain text part of a jsDoc comment, so no completion should be available @@ -696,6 +824,7 @@ namespace ts.Completions { // Also determine whether we are trying to complete with members of that node // or attributes of a JSX tag. let node = currentToken; + let propertyAccessToConvert: PropertyAccessExpression | undefined; let isRightOfDot = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; @@ -710,18 +839,19 @@ namespace ts.Completions { let parent = contextToken.parent; if (contextToken.kind === SyntaxKind.DotToken) { - if (parent.kind === SyntaxKind.PropertyAccessExpression) { - node = (contextToken.parent).expression; - isRightOfDot = true; - } - else if (parent.kind === SyntaxKind.QualifiedName) { - node = (contextToken.parent).left; - isRightOfDot = true; - } - else { - // There is nothing that precedes the dot, so this likely just a stray character - // or leading into a '...' token. Just bail out instead. - return undefined; + isRightOfDot = true; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + propertyAccessToConvert = parent as PropertyAccessExpression; + node = propertyAccessToConvert.expression; + break; + case SyntaxKind.QualifiedName: + node = (parent as QualifiedName).left; + break; + default: + // There is nothing that precedes the dot, so this likely just a stray character + // or leading into a '...' token. Just bail out instead. + return undefined; } } else if (sourceFile.languageVariant === LanguageVariant.JSX) { @@ -761,10 +891,8 @@ namespace ts.Completions { } const semanticStart = timestamp(); - let isGlobalCompletion = false; - let isMemberCompletion: boolean; - let allowStringLiteral = false; - let isNewIdentifierLocation: boolean; + let completionKind = CompletionKind.None; + let isNewIdentifierLocation = false; let keywordFilters = KeywordCompletionFilters.None; let symbols: Symbol[] = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; @@ -780,8 +908,7 @@ namespace ts.Completions { else { symbols = tagSymbols; } - isMemberCompletion = true; - isNewIdentifierLocation = false; + completionKind = CompletionKind.MemberLike; } else if (isStartingCloseTag) { const tagName = (contextToken.parent.parent).openingElement.tagName; @@ -790,8 +917,7 @@ namespace ts.Completions { if (!typeChecker.isUnknownSymbol(tagSymbol)) { symbols = [tagSymbol]; } - isMemberCompletion = true; - isNewIdentifierLocation = false; + completionKind = CompletionKind.MemberLike; } else { // For JavaScript or TypeScript, if we're not after a dot, then just try to get the @@ -804,8 +930,8 @@ namespace ts.Completions { log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - const recommendedCompletion = getRecommendedCompletion(previousToken, typeChecker); - return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion }; + const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker); + return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -822,9 +948,7 @@ namespace ts.Completions { function getTypeScriptMemberSymbols(): void { // Right of dot member completion list - isGlobalCompletion = false; - isMemberCompletion = true; - isNewIdentifierLocation = false; + completionKind = CompletionKind.PropertyAccess; // Since this is qualified name check its a type node location const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent); @@ -874,9 +998,8 @@ namespace ts.Completions { symbols.push(...getPropertiesForCompletion(type, typeChecker, /*isForAccess*/ true)); } else { - // Filter private properties for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccess((node.parent), symbol.name)) { + if (typeChecker.isValidPropertyAccessForCompletions((node.parent), type, symbol)) { symbols.push(symbol); } } @@ -901,7 +1024,7 @@ namespace ts.Completions { if (tryGetConstructorLikeCompletionContainer(contextToken)) { // no members, only keywords - isMemberCompletion = false; + completionKind = CompletionKind.None; // Declaring new property/method/accessor isNewIdentifierLocation = true; // Has keywords for constructor parameter @@ -923,7 +1046,7 @@ namespace ts.Completions { if (attrsType) { symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (jsxContainer).attributes.properties); - isMemberCompletion = true; + completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; return true; } @@ -931,7 +1054,7 @@ namespace ts.Completions { } // Get all entities in the current scope. - isMemberCompletion = false; + completionKind = CompletionKind.None; isNewIdentifierLocation = isNewIdentifierDefinitionLocation(contextToken); if (previousToken !== contextToken) { @@ -967,13 +1090,8 @@ namespace ts.Completions { position; const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; - if (scopeNode) { - isGlobalCompletion = - scopeNode.kind === SyntaxKind.SourceFile || - scopeNode.kind === SyntaxKind.TemplateExpression || - scopeNode.kind === SyntaxKind.JsxExpression || - scopeNode.kind === SyntaxKind.Block || // Some blocks aren't statements, but all get global completions - isStatement(scopeNode); + if (isGlobalCompletionScope(scopeNode)) { + completionKind = CompletionKind.Global; } const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; @@ -987,6 +1105,18 @@ namespace ts.Completions { return true; } + function isGlobalCompletionScope(scopeNode: Node): boolean { + switch (scopeNode.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.TemplateExpression: + case SyntaxKind.JsxExpression: + case SyntaxKind.Block: + return true; + default: + return isStatement(scopeNode); + } + } + function filterGlobalCompletion(symbols: Symbol[]): void { filterMutate(symbols, symbol => { if (!isSourceFile(location)) { @@ -1067,28 +1197,21 @@ namespace ts.Completions { codefix.forEachExternalModuleToImportFrom(typeChecker, sourceFile, allSourceFiles, moduleSymbol => { for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) { - let { name } = symbol; - // Don't add a completion for a re-export, only for the original. - // If `symbol.parent !== moduleSymbol`, this comes from an `export * from "foo"` re-export. Those don't create new symbols. + // If `symbol.parent !== ...`, this comes from an `export * from "foo"` re-export. Those don't create new symbols. // If `some(...)`, this comes from an `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). - if (symbol.parent !== moduleSymbol || some(symbol.declarations, d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) { + if (symbol.parent !== typeChecker.resolveExternalModuleSymbol(moduleSymbol) + || some(symbol.declarations, d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) { continue; } - const isDefaultExport = name === "default"; + const isDefaultExport = symbol.name === InternalSymbolName.Default; if (isDefaultExport) { - const localSymbol = getLocalSymbolForExportDefault(symbol); - if (localSymbol) { - symbol = localSymbol; - name = localSymbol.name; - } - else { - name = codefix.moduleSymbolToValidIdentifier(moduleSymbol, target); - } + symbol = getLocalSymbolForExportDefault(symbol) || symbol; } - if (stringContainsCharactersInOrder(name.toLowerCase(), tokenTextLowerCase)) { + const origin: SymbolOriginInfo = { moduleSymbol, isDefaultExport }; + if (stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { symbols.push(symbol); symbolToOriginInfoMap[getSymbolId(symbol)] = { moduleSymbol, isDefaultExport }; } @@ -1253,8 +1376,7 @@ namespace ts.Completions { */ function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | ObjectBindingPattern): boolean { // We're looking up possible property names from contextual/inferred/declared type. - isMemberCompletion = true; - allowStringLiteral = true; + completionKind = CompletionKind.ObjectPropertyDeclaration; let typeMembers: Symbol[]; let existingMembers: ReadonlyArray; @@ -1281,7 +1403,7 @@ namespace ts.Completions { // through type declaration or inference. // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - // type of parameter will flow in from the contextual type of the function - let canGetType = rootDeclaration.initializer || rootDeclaration.type || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; + let canGetType = hasInitializer(rootDeclaration) || hasType(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { if (isExpression(rootDeclaration.parent)) { canGetType = !!typeChecker.getContextualType(rootDeclaration.parent); @@ -1332,7 +1454,7 @@ namespace ts.Completions { return false; } - isMemberCompletion = true; + completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); @@ -1352,7 +1474,7 @@ namespace ts.Completions { */ function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) { // We're looking up possible property names from parent type. - isMemberCompletion = true; + completionKind = CompletionKind.MemberLike; // Declaring new property/method/accessor isNewIdentifierLocation = true; // Has keywords for class elements @@ -1515,7 +1637,7 @@ namespace ts.Completions { switch (contextToken.kind) { case SyntaxKind.OpenParenToken: case SyntaxKind.CommaToken: - return isConstructorDeclaration(contextToken.parent) && contextToken.parent; + return isConstructorDeclaration(contextToken.parent) && contextToken.parent; default: if (isConstructorParameterCompletion(contextToken)) { @@ -1717,7 +1839,10 @@ namespace ts.Completions { return true; } - return isDeclarationName(contextToken) && !isJsxAttribute(contextToken.parent); + return isDeclarationName(contextToken) + && !isJsxAttribute(contextToken.parent) + // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. + && !(isClassLike(contextToken.parent) && position > previousToken.end); } function isFunctionLikeButNotConstructor(kind: SyntaxKind) { @@ -1756,10 +1881,10 @@ namespace ts.Completions { } if (existingImportsOrExports.size === 0) { - return filter(exportsOfModule, e => e.escapedName !== "default"); + return filter(exportsOfModule, e => e.escapedName !== InternalSymbolName.Default); } - return filter(exportsOfModule, e => e.escapedName !== "default" && !existingImportsOrExports.get(e.escapedName)); + return filter(exportsOfModule, e => e.escapedName !== InternalSymbolName.Default && !existingImportsOrExports.get(e.escapedName)); } /** @@ -1903,55 +2028,44 @@ namespace ts.Completions { } } - /** - * Get the name to be display in completion from a given symbol. - * - * @return undefined if the name is of external module - */ - function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean, origin: SymbolOriginInfo | undefined): string | undefined { - const name = getSymbolName(symbol, origin, target); - if (!name) return undefined; - - // First check of the displayName is not external module; if it is an external module, it is not valid entry - if (symbol.flags & SymbolFlags.Namespace) { - const firstCharCode = name.charCodeAt(0); - if (isSingleOrDoubleQuote(firstCharCode)) { - // If the symbol is external module, don't show it in the completion list - // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) - return undefined; - } - } - - // If the symbol is for a member of an object type and is the internal name of an ES - // symbol, it is not a valid entry. Internal names for ES symbols start with "__@" - if (symbol.flags & SymbolFlags.ClassMember) { - const escapedName = symbol.escapedName as string; - if (escapedName.length >= 3 && - escapedName.charCodeAt(0) === CharacterCodes._ && - escapedName.charCodeAt(1) === CharacterCodes._ && - escapedName.charCodeAt(2) === CharacterCodes.at) { - return undefined; - } - } - - return getCompletionEntryDisplayName(name, target, performCharacterChecks, allowStringLiteral); + interface CompletionEntryDisplayNameForSymbol { + readonly name: string; + readonly needsConvertPropertyAccess: boolean; } - - /** - * Get a displayName from a given for completion list, performing any necessary quotes stripping - * and checking whether the name is valid identifier name. - */ - function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean): string { - // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an - // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name. - // e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid. - // We, thus, need to check if whatever was inside the quotes is actually a valid identifier name. - if (performCharacterChecks && !isIdentifierText(name, target)) { - // TODO: GH#18169 - return allowStringLiteral ? JSON.stringify(name) : undefined; + function getCompletionEntryDisplayNameForSymbol( + symbol: Symbol, + target: ScriptTarget, + origin: SymbolOriginInfo | undefined, + kind: CompletionKind, + ): CompletionEntryDisplayNameForSymbol | undefined { + const name = getSymbolName(symbol, origin, target); + if (name === undefined + // If the symbol is external module, don't show it in the completion list + // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) + || symbol.flags & SymbolFlags.Module && startsWithQuote(name) + // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" + || isKnownSymbol(symbol)) { + return undefined; } - return name; + const validIdentiferResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; + if (isIdentifierText(name, target)) return validIdentiferResult; + switch (kind) { + case CompletionKind.None: + case CompletionKind.Global: + case CompletionKind.MemberLike: + return undefined; + case CompletionKind.ObjectPropertyDeclaration: + // TODO: GH#18169 + return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; + case CompletionKind.PropertyAccess: + // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 + return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; + case CompletionKind.String: + return validIdentiferResult; + default: + Debug.assertNever(kind); + } } // A cache of completion entries for keywords, these do not change between sessions @@ -1964,7 +2078,7 @@ namespace ts.Completions { return _keywordCompletions[keywordFilter] = generateKeywordCompletions(keywordFilter); type FilterKeywordCompletions = (entryName: string) => boolean; - function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters) { + function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] { switch (keywordFilter) { case KeywordCompletionFilters.None: return getAllKeywordCompletions(); @@ -1972,6 +2086,8 @@ namespace ts.Completions { return getFilteredKeywordCompletions(isClassMemberCompletionKeywordText); case KeywordCompletionFilters.ConstructorParameterKeywords: return getFilteredKeywordCompletions(isConstructorParameterCompletionKeywordText); + default: + Debug.assertNever(keywordFilter); } } @@ -2032,15 +2148,16 @@ namespace ts.Completions { return isConstructorParameterCompletionKeyword(stringToToken(text)); } - function isEqualityExpression(node: Node): node is BinaryExpression { - return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); - } - - function isEqualityOperatorKind(kind: SyntaxKind) { - return kind === SyntaxKind.EqualsEqualsToken || - kind === SyntaxKind.ExclamationEqualsToken || - kind === SyntaxKind.EqualsEqualsEqualsToken || - kind === SyntaxKind.ExclamationEqualsEqualsToken; + function isEqualityOperatorKind(kind: ts.SyntaxKind): kind is EqualityOperator { + switch (kind) { + case ts.SyntaxKind.EqualsEqualsEqualsToken: + case ts.SyntaxKind.EqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsToken: + return true; + default: + return false; + } } /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ @@ -2075,8 +2192,7 @@ namespace ts.Completions { /** * Gets all properties on a type, but if that type is a union of several types, - * tries to only include those types which declare properties, not methods. - * This ensures that we don't try providing completions for all the methods on e.g. Array. + * excludes array-like types or callable/constructable types. */ function getPropertiesForCompletion(type: Type, checker: TypeChecker, isForAccess: boolean): Symbol[] { if (!(type.flags & TypeFlags.Union)) { diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index d9a9a03c38b..5c1e7116af4 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -2,19 +2,15 @@ namespace ts.DocumentHighlights { export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] | undefined { const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ true); - // Note that getTouchingWord indicates failure by returning the sourceFile node. - if (node === sourceFile) return undefined; - Debug.assert(node.parent !== undefined); - - if (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent)) { + if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { // For a JSX element, just highlight the matching tag, not all references. const { openingElement, closingElement } = node.parent.parent; const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); return [{ fileName: sourceFile.fileName, highlightSpans }]; } - return getSemanticDocumentHighlights(node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); + return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); } function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { @@ -25,8 +21,8 @@ namespace ts.DocumentHighlights { }; } - function getSemanticDocumentHighlights(node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(node, program, sourceFilesToSearch, cancellationToken); + function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken); return referenceEntries && convertReferencedSymbols(referenceEntries); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 86db21e3225..8b7473357fe 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -43,32 +43,21 @@ namespace ts.FindAllReferences { export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const referencedSymbols = findAllReferencedSymbols(program, cancellationToken, sourceFiles, sourceFile, position); - - if (!referencedSymbols || !referencedSymbols.length) { - return undefined; - } - - const out: ReferencedSymbol[] = []; const checker = program.getTypeChecker(); - for (const { definition, references } of referencedSymbols) { + return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => // Only include referenced symbols that have a valid definition. - if (definition) { - out.push({ definition: definitionToReferencedSymbolDefinitionInfo(definition, checker), references: references.map(toReferenceEntry) }); - } - } - - return out; + definition && { definition: definitionToReferencedSymbolDefinitionInfo(definition, checker), references: references.map(toReferenceEntry) }); } export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number): ImplementationLocation[] { // A node in a JSDoc comment can't have an implementation anyway. const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false); - const referenceEntries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node); + const referenceEntries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); const checker = program.getTypeChecker(); return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } - function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, node: Node): Entry[] | undefined { + function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, node: Node, position: number): Entry[] | undefined { if (node.kind === SyntaxKind.SourceFile) { return undefined; } @@ -89,7 +78,7 @@ namespace ts.FindAllReferences { } else { // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(node, program, sourceFiles, cancellationToken, { implementations: true }); + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true }); } } @@ -98,13 +87,13 @@ namespace ts.FindAllReferences { return map(x, toReferenceEntry); } - export function getReferenceEntriesForNode(node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined { - return flattenEntries(Core.getReferencedSymbolsForNode(node, program, sourceFiles, cancellationToken, options)); + export function getReferenceEntriesForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined { + return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)); } function findAllReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number, options?: Options): SymbolAndEntries[] | undefined { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); - return Core.getReferencedSymbolsForNode(node, program, sourceFiles, cancellationToken, options); + return Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options); } function flattenEntries(referenceSymbols: SymbolAndEntries[]): Entry[] { @@ -253,9 +242,10 @@ namespace ts.FindAllReferences { /* @internal */ namespace ts.FindAllReferences.Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined { - if (node.kind === ts.SyntaxKind.SourceFile) { - return undefined; + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined { + if (isSourceFile(node)) { + const reference = GoToDefinition.getReferenceAtPosition(node, position, program); + return reference && getReferencedSymbolsForModule(program, program.getTypeChecker().getMergedSymbol(reference.file.symbol), sourceFiles); } if (!options.implementations) { @@ -271,11 +261,7 @@ namespace ts.FindAllReferences.Core { // Could not find a symbol e.g. unknown identifier if (!symbol) { // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. - if (!options.implementations && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles, cancellationToken); - } - // Can't have references to something that we have no symbol for. - return undefined; + return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined; } if (symbol.flags & SymbolFlags.Module && isModuleReferenceLocation(node)) { @@ -286,7 +272,7 @@ namespace ts.FindAllReferences.Core { } function isModuleReferenceLocation(node: ts.Node): boolean { - if (node.kind !== SyntaxKind.StringLiteral) { + if (node.kind !== SyntaxKind.StringLiteral && node.kind !== SyntaxKind.NoSubstitutionTemplateLiteral) { return false; } switch (node.parent.kind) { @@ -376,7 +362,7 @@ namespace ts.FindAllReferences.Core { const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.declarations); const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, /*isForConstructor*/ node.kind === SyntaxKind.ConstructorKeyword, checker, cancellationToken, searchMeaning, options, result); + const state = new State(sourceFiles, getSpecialSearchKind(node), checker, cancellationToken, searchMeaning, options, result); if (node.kind === SyntaxKind.DefaultKeyword) { addReference(node, symbol, node, state); @@ -403,6 +389,21 @@ namespace ts.FindAllReferences.Core { return result; } + function getSpecialSearchKind(node: Node): SpecialSearchKind { + switch (node.kind) { + case SyntaxKind.ConstructorKeyword: + return SpecialSearchKind.Constructor; + case SyntaxKind.Identifier: + if (isClassLike(node.parent)) { + Debug.assert(node.parent.name === node); + return SpecialSearchKind.Class; + } + // falls through + default: + return SpecialSearchKind.None; + } + } + /** Handle a few special cases relating to export/import specifiers. */ function skipPastExportOrImportSpecifier(symbol: Symbol, node: Node, checker: TypeChecker): Symbol { const { parent } = node; @@ -439,6 +440,12 @@ namespace ts.FindAllReferences.Core { includes(symbol: Symbol): boolean; } + const enum SpecialSearchKind { + None, + Constructor, + Class, + } + /** * Holds all state needed for the finding references. * Unlike `Search`, there is only one `State`. @@ -472,7 +479,7 @@ namespace ts.FindAllReferences.Core { constructor( readonly sourceFiles: ReadonlyArray, /** True if we're searching for constructor references. */ - readonly isForConstructor: boolean, + readonly specialSearchKind: SpecialSearchKind, readonly checker: TypeChecker, readonly cancellationToken: CancellationToken, readonly searchMeaning: SemanticMeaning, @@ -489,6 +496,9 @@ namespace ts.FindAllReferences.Core { /** @param allSearchSymbols set of additinal symbols for use by `includes`. */ createSearch(location: Node, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { // Note: if this is an external module symbol, the name doesn't include quotes. + // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. + // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form + // here appears to be intentional). const { text = stripQuotes(unescapeLeadingUnderscores((getLocalSymbolForExportDefault(symbol) || symbol).escapedName)), allSearchSymbols = undefined, @@ -845,11 +855,18 @@ namespace ts.FindAllReferences.Core { return; } - if (state.isForConstructor) { - findConstructorReferences(referenceLocation, sourceFile, search, state); - } - else { - addReference(referenceLocation, relatedSymbol, search.location, state); + switch (state.specialSearchKind) { + case SpecialSearchKind.None: + addReference(referenceLocation, relatedSymbol, search.location, state); + break; + case SpecialSearchKind.Constructor: + addConstructorReferences(referenceLocation, sourceFile, search, state); + break; + case SpecialSearchKind.Class: + addClassStaticThisReferences(referenceLocation, search, state); + break; + default: + Debug.assertNever(state.specialSearchKind); } getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); @@ -961,27 +978,52 @@ namespace ts.FindAllReferences.Core { } /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ - function findConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { + function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { if (isNewExpressionTarget(referenceLocation)) { addReference(referenceLocation, search.symbol, search.location, state); } - const pusher = state.referenceAdder(search.symbol, search.location); + const pusher = () => state.referenceAdder(search.symbol, search.location); if (isClassLike(referenceLocation.parent)) { Debug.assert(referenceLocation.parent.name === referenceLocation); // This is the class declaration containing the constructor. - findOwnConstructorReferences(search.symbol, sourceFile, pusher); + findOwnConstructorReferences(search.symbol, sourceFile, pusher()); } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); - if (classExtending && isClassLike(classExtending)) { - findSuperConstructorAccesses(classExtending, pusher); + if (classExtending) { + findSuperConstructorAccesses(classExtending, pusher()); } } } + function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { + addReference(referenceLocation, search.symbol, search.location, state); + if (isClassLike(referenceLocation.parent)) { + Debug.assert(referenceLocation.parent.name === referenceLocation); + // This is the class declaration. + addStaticThisReferences(referenceLocation.parent, state.referenceAdder(search.symbol, search.location)); + } + } + + function addStaticThisReferences(classLike: ClassLikeDeclaration, pusher: (node: Node) => void): void { + for (const member of classLike.members) { + if (!(isMethodOrAccessor(member) && hasModifier(member, ModifierFlags.Static))) { + continue; + } + member.body.forEachChild(function cb(node) { + if (node.kind === SyntaxKind.ThisKeyword) { + pusher(node); + } + else if (!isFunctionLike(node)) { + node.forEachChild(cb); + } + }); + } + } + function getPropertyAccessExpressionFromRightHandSide(node: Node): PropertyAccessExpression { return isRightSideOfPropertyAccess(node) && node.parent; } @@ -992,7 +1034,7 @@ namespace ts.FindAllReferences.Core { */ function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { for (const decl of classSymbol.members.get(InternalSymbolName.Constructor).declarations) { - const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)!; + const ctrKeyword = findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)!; Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); addNode(ctrKeyword); } @@ -1060,7 +1102,7 @@ namespace ts.FindAllReferences.Core { const containingTypeReference = getContainingTypeReference(refNode); if (containingTypeReference && state.markSeenContainingTypeReference(containingTypeReference)) { const parent = containingTypeReference.parent; - if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { + if (hasType(parent) && parent.type === containingTypeReference && hasInitializer(parent) && isImplementationExpression(parent.initializer)) { addReference(parent.initializer); } else if (isFunctionLike(parent) && parent.type === containingTypeReference && (parent as FunctionLikeDeclaration).body) { @@ -1385,7 +1427,7 @@ namespace ts.FindAllReferences.Core { // This is not needed when searching for re-exports. function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, implementations: boolean): Symbol[] { // The search set contains at least the current symbol - const result = [symbol]; + const result: Symbol[] = []; const containingObjectLiteralElement = getContainingObjectLiteralElement(location); if (containingObjectLiteralElement) { @@ -1402,9 +1444,9 @@ namespace ts.FindAllReferences.Core { // If the location is in a context sensitive location (i.e. in an object literal) try // to get a contextual type for it, and add the property symbol from the contextual // type to the search set - forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), contextualSymbol => { - addRange(result, checker.getRootSymbols(contextualSymbol)); - }); + for (const contextualSymbol of getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker)) { + addRootSymbols(contextualSymbol); + } /* Because in short-hand property assignment, location has two meaning : property name and as value of the property * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of @@ -1445,9 +1487,7 @@ namespace ts.FindAllReferences.Core { // If this is a union property, add all the symbols from all its source symbols in all unioned types. // If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list for (const rootSymbol of checker.getRootSymbols(sym)) { - if (rootSymbol !== sym) { - result.push(rootSymbol); - } + result.push(rootSymbol); // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { @@ -1471,7 +1511,7 @@ namespace ts.FindAllReferences.Core { * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. * The value of previousIterationSymbol is undefined when the function is first called. */ - function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[], previousIterationSymbolsCache: SymbolTable, checker: TypeChecker): void { + function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Push, previousIterationSymbolsCache: SymbolTable, checker: TypeChecker): void { if (!symbol) { return; } @@ -1540,9 +1580,7 @@ namespace ts.FindAllReferences.Core { // compare to our searchSymbol const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation); if (containingObjectLiteralElement) { - const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), contextualSymbol => - find(checker.getRootSymbols(contextualSymbol), search.includes)); - + const contextualSymbol = firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), findRootSymbol); if (contextualSymbol) { return contextualSymbol; } @@ -1571,7 +1609,7 @@ namespace ts.FindAllReferences.Core { function findRootSymbol(sym: Symbol): Symbol | undefined { // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) // Or a union property, use its underlying unioned symbols - return forEach(state.checker.getRootSymbols(sym), rootSymbol => { + return firstDefined(checker.getRootSymbols(sym), rootSymbol => { // if it is in the list, then we are done if (search.includes(rootSymbol)) { return rootSymbol; @@ -1582,12 +1620,12 @@ namespace ts.FindAllReferences.Core { // parent symbol if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { // Parents will only be defined if implementations is true - if (search.parents && !some(search.parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, state.inheritsFromCache, state.checker))) { + if (search.parents && !some(search.parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, state.inheritsFromCache, checker))) { return undefined; } const result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, result, /*previousIterationSymbolsCache*/ createSymbolTable(), state.checker); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, result, /*previousIterationSymbolsCache*/ createSymbolTable(), checker); return find(result, search.includes); } @@ -1609,28 +1647,12 @@ namespace ts.FindAllReferences.Core { } /** Gets all symbols for one property. Does not get symbols for every property. */ - function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, checker: TypeChecker): Symbol[] | undefined { - const objectLiteral = node.parent; - const contextualType = checker.getContextualType(objectLiteral); + function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, checker: TypeChecker): ReadonlyArray { + const contextualType = checker.getContextualType(node.parent); const name = getNameFromObjectLiteralElement(node); - if (name && contextualType) { - const result: Symbol[] = []; - const symbol = contextualType.getProperty(name); - if (symbol) { - result.push(symbol); - } - - if (contextualType.flags & TypeFlags.Union) { - forEach((contextualType).types, t => { - const symbol = t.getProperty(name); - if (symbol) { - result.push(symbol); - } - }); - } - return result; - } - return undefined; + const symbol = contextualType && name && contextualType.getProperty(name); + return symbol ? [symbol] : + contextualType && contextualType.flags & TypeFlags.Union ? mapDefined((contextualType).types, t => t.getProperty(name)) : emptyArray; } /** @@ -1670,14 +1692,12 @@ namespace ts.FindAllReferences.Core { if (!node) { return false; } - else if (isVariableLike(node)) { - if (node.initializer) { - return true; - } - else if (node.kind === SyntaxKind.VariableDeclaration) { - const parentStatement = getParentStatementOfVariableDeclaration(node); - return parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient); - } + else if (isVariableLike(node) && hasInitializer(node)) { + return true; + } + else if (node.kind === SyntaxKind.VariableDeclaration) { + const parentStatement = getParentStatementOfVariableDeclaration(node); + return parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient); } else if (isFunctionLike(node)) { return !!(node as FunctionLikeDeclaration).body || hasModifier(node, ModifierFlags.Ambient); diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 8214ffd6f2d..16270eda479 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -11,8 +11,8 @@ namespace ts.formatting { for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { allTokens.push(token); } - function anyTokenExcept(token: SyntaxKind): TokenRange { - return { tokens: allTokens.filter(t => t !== token), isSpecific: false }; + function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { + return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; } const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; @@ -47,7 +47,7 @@ namespace ts.formatting { rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.Ignore), rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.Ignore), - rule("NoSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.Delete), rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Space), rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), // insert space after '?' only when it is used in conditional operator @@ -69,10 +69,10 @@ namespace ts.formatting { rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), // More unary operator special-casing. - // DevDiv 181814: Be careful when removing leading whitespace + // DevDiv 181814: Be careful when removing leading whitespace // around unary operators. Examples: - // 1 - -2 --X--> 1--2 - // a + ++b --X--> a+++b + // 1 - -2 --X--> 1--2 + // a + ++b --X--> a+++b rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), @@ -80,7 +80,7 @@ namespace ts.formatting { rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.Delete), // For functions and control block place } on a new line [multi-line rule] rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.NewLine), @@ -198,7 +198,7 @@ namespace ts.formatting { RuleAction.Delete), // decorators - rule("SpaceBeforeAt", anyToken, SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.Space), rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), // Insert space after @ in decorator rule("SpaceAfterDecorator", @@ -231,8 +231,8 @@ namespace ts.formatting { rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.Space), rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementContext, isNextTokenNotCloseBracket], RuleAction.Space), - rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementContext], RuleAction.Delete), + rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket], RuleAction.Space), + rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.Delete), // Insert space after function keyword for anonymous functions rule("SpaceAfterAnonymousFunctionKeyword", SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.Space), @@ -300,9 +300,12 @@ namespace ts.formatting { rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Space), rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Delete), + + rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Space), + rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Delete), ]; - // These rules are lower in priority than user-configurable + // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. const lowPriorityCommonRules = [ // Space after keyword but not before ; or : or ? rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), @@ -312,8 +315,9 @@ namespace ts.formatting { rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.Space, RuleFlags.CanDeleteNewLines), rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - // No space before and after indexer - rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + + // No space before and after indexer `x[]` + rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.Delete), rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), @@ -323,7 +327,7 @@ namespace ts.formatting { "SpaceBetweenStatements", [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], anyToken, - [isNonJsxSameLineTokenContext, isNonJsxElementContext, isNotForContext], + [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], RuleAction.Space), // This low-pri rule takes care of "try {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. rule("SpaceAfterTryFinally", [SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.Space), @@ -440,6 +444,19 @@ namespace ts.formatting { return !isBinaryOpContext(context); } + function isNotTypeAnnotationContext(context: FormattingContext): boolean { + return !isTypeAnnotationContext(context); + } + + function isTypeAnnotationContext(context: FormattingContext): boolean { + const contextKind = context.contextNode.kind; + return contextKind === SyntaxKind.PropertyDeclaration || + contextKind === SyntaxKind.PropertySignature || + contextKind === SyntaxKind.Parameter || + contextKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(contextKind); + } + function isConditionalOperatorContext(context: FormattingContext): boolean { return context.contextNode.kind === SyntaxKind.ConditionalExpression; } @@ -613,12 +630,12 @@ namespace ts.formatting { return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; } - function isNonJsxElementContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxElement; + function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment; } function isJsxExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxExpression; + return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute; } function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 0a444a7280a..1e6323be9fa 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -367,12 +367,13 @@ namespace ts.formatting { function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings): number { const containingList = getContainingList(node, sourceFile); - return containingList ? getActualIndentationFromList(containingList) : Value.Unknown; - - function getActualIndentationFromList(list: ReadonlyArray): number { - const index = indexOf(list, node); - return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : Value.Unknown; + if (containingList) { + const index = containingList.indexOf(node); + if (index !== -1) { + return deriveActualIndentationFromList(containingList, index, sourceFile, options); + } } + return Value.Unknown; } function getLineIndentationWhenExpressionIsInMultiLine(node: Node, sourceFile: SourceFile, options: EditorSettings): number { @@ -508,6 +509,7 @@ namespace ts.formatting { case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxExpression: case SyntaxKind.MethodSignature: @@ -554,6 +556,8 @@ namespace ts.formatting { (!!(child).namedBindings && (child).namedBindings.kind !== SyntaxKind.NamedImports); case SyntaxKind.JsxElement: return childKind !== SyntaxKind.JsxClosingElement; + case SyntaxKind.JsxFragment: + return childKind !== SyntaxKind.JsxClosingFragment; } // No explicit rule for given nodes so the result will follow the default value argument return indentByDefault; diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 35f4d079e29..5f17eccf9c5 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -1,22 +1,9 @@ /* @internal */ namespace ts.GoToDefinition { export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] { - /// Triple slash reference comments - const comment = findReferenceInPosition(sourceFile.referencedFiles, position); - if (comment) { - const referenceFile = tryResolveScriptReference(program, sourceFile, comment); - if (referenceFile) { - return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)]; - } - // Might still be on jsdoc, so keep looking. - } - - // Type reference directives - const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); - if (typeReferenceDirective) { - const referenceFile = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName); - return referenceFile && referenceFile.resolvedFileName && - [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; + const reference = getReferenceAtPosition(sourceFile, position, program); + if (reference) { + return [getDefinitionInfoForFileReference(reference.fileName, reference.file.fileName)]; } const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); @@ -115,6 +102,23 @@ namespace ts.GoToDefinition { return getDefinitionFromSymbol(typeChecker, symbol, node); } + export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { fileName: string, file: SourceFile } | undefined { + const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position); + if (referencePath) { + const file = tryResolveScriptReference(program, sourceFile, referencePath); + return file && { fileName: referencePath.fileName, file }; + } + + const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); + if (typeReferenceDirective) { + const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName); + const file = reference && program.getSourceFile(reference.resolvedFileName); + return file && { fileName: typeReferenceDirective.fileName, file }; + } + + return undefined; + } + /// Goto type export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); @@ -301,7 +305,7 @@ namespace ts.GoToDefinition { return createDefinitionInfo(decl, symbolKind, symbolName, containerName); } - function findReferenceInPosition(refs: ReadonlyArray, pos: number): FileReference { + export function findReferenceInPosition(refs: ReadonlyArray, pos: number): FileReference { for (const ref of refs) { if (ref.pos <= pos && pos <= ref.end) { return ref; diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index f12df4613c0..d2ce802f177 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -290,7 +290,7 @@ namespace ts.FindAllReferences { function isNameMatch(name: __String): boolean { // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports - return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === "default"; + return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; } } @@ -534,7 +534,7 @@ namespace ts.FindAllReferences { // If `importedName` is undefined, do continue searching as the export is anonymous. // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) const importedName = symbolName(importedSymbol); - if (importedName === undefined || importedName === "default" || importedName === symbol.escapedName) { + if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { return { kind: ImportExport.Import, symbol: importedSymbol, ...isImport }; } } @@ -604,7 +604,7 @@ namespace ts.FindAllReferences { } function symbolName(symbol: Symbol): __String | undefined { - if (symbol.escapedName !== "default") { + if (symbol.escapedName !== InternalSymbolName.Default) { return symbol.escapedName; } @@ -616,7 +616,7 @@ namespace ts.FindAllReferences { /** If at an export specifier, go to the symbol it refers to. */ function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { - // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. + // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. if (symbol.declarations) { for (const declaration of symbol.declarations) { if (isExportSpecifier(declaration) && !(declaration as ExportSpecifier).propertyName && !(declaration as ExportSpecifier).parent.parent.moduleSpecifier) { diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 79b08780226..46f2458bba9 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -1,6 +1,5 @@ /* @internal */ namespace ts.JsDoc { - const singleLineTemplate = { newText: "/** */", caretOffset: 3 }; const jsDocTagNames = [ "augments", "author", @@ -113,7 +112,7 @@ namespace ts.JsDoc { function forEachUnique(array: T[], callback: (element: T, index: number) => U): U { if (array) { for (let i = 0; i < array.length; i++) { - if (indexOf(array, array[i]) === i) { + if (array.indexOf(array[i]) === i) { const result = callback(array[i], i); if (result) { return result; @@ -197,9 +196,16 @@ namespace ts.JsDoc { /** * Checks if position points to a valid position to add JSDoc comments, and if so, * returns the appropriate template. Otherwise returns an empty string. - * Invalid positions are - * - within comments, strings (including template literals and regex), and JSXText - * - within a token + * Valid positions are + * - outside of comments, statements, and expressions, and + * - preceding a: + * - function/constructor/method declaration + * - class declarations + * - variable statements + * - namespace declarations + * - interface declarations + * - method signatures + * - type alias declarations * * Hosts should ideally check that: * - The line is all whitespace up to 'position' before performing the insertion. @@ -225,19 +231,17 @@ namespace ts.JsDoc { const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos); if (!commentOwnerInfo) { - // if climbing the tree did not find a declaration with parameters, complete to a single line comment - return singleLineTemplate; + return undefined; } const { commentOwner, parameters } = commentOwnerInfo; - - if (commentOwner.kind === SyntaxKind.JsxText) { + if (commentOwner.getStart() < position) { return undefined; } - if (commentOwner.getStart() < position || parameters.length === 0) { - // if climbing the tree found a declaration with parameters but the request was made inside it - // or if there are no parameters, complete to a single line comment - return singleLineTemplate; + if (!parameters || parameters.length === 0) { + // if there are no parameters, just complete to a single line JSDoc comment + const singleLineResult = "/** */"; + return { newText: singleLineResult, caretOffset: 3 }; } const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position); @@ -247,11 +251,19 @@ namespace ts.JsDoc { const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character).replace(/\S/i, () => " "); const isJavaScriptFile = hasJavaScriptFileExtension(sourceFile.fileName); - const docParams = parameters.map(({name}, i) => { - const nameText = isIdentifier(name) ? name.text : `param${i}`; - const type = isJavaScriptFile ? "{any} " : ""; - return `${indentationStr} * @param ${type}${nameText}${newLine}`; - }).join(""); + let docParams = ""; + for (let i = 0; i < parameters.length; i++) { + const currentName = parameters[i].name; + const paramName = currentName.kind === SyntaxKind.Identifier ? + (currentName).escapedText : + "param" + i; + if (isJavaScriptFile) { + docParams += `${indentationStr} * @param {any} ${paramName}${newLine}`; + } + else { + docParams += `${indentationStr} * @param ${paramName}${newLine}`; + } + } // A doc comment consists of the following // * The opening comment line @@ -273,7 +285,7 @@ namespace ts.JsDoc { interface CommentOwnerInfo { readonly commentOwner: Node; - readonly parameters: ReadonlyArray; + readonly parameters?: ReadonlyArray; } function getCommentOwnerInfo(tokenAtPos: Node): CommentOwnerInfo | undefined { for (let commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) { @@ -285,18 +297,32 @@ namespace ts.JsDoc { const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; return { commentOwner, parameters }; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAliasDeclaration: + return { commentOwner }; + case SyntaxKind.VariableStatement: { const varStatement = commentOwner; const varDeclarations = varStatement.declarationList.declarations; const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer ? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer) : undefined; - return parameters ? { commentOwner, parameters } : undefined; + return { commentOwner, parameters }; } case SyntaxKind.SourceFile: return undefined; + case SyntaxKind.ModuleDeclaration: + // If in walking up the tree, we hit a a nested namespace declaration, + // then we must be somewhere within a dotted namespace name; however we don't + // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. + return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; + case SyntaxKind.BinaryExpression: { const be = commentOwner as BinaryExpression; if (getSpecialPropertyAssignmentKind(be) === ts.SpecialPropertyAssignmentKind.None) { @@ -305,11 +331,6 @@ namespace ts.JsDoc { const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray; return { commentOwner, parameters }; } - - case SyntaxKind.JsxText: { - const parameters: ReadonlyArray = emptyArray; - return { commentOwner, parameters }; - } } } } diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 762726adb77..8449805ee71 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -46,7 +46,7 @@ namespace ts.NavigateTo { continue; } - // It was a match! If the pattern has dots in it, then also see if the + // It was a match! If the pattern has dots in it, then also see if the // declaration container matches as well. let containerMatches = matches; if (patternMatcher.patternContainsDots) { diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 15c1ff99758..65d288e7f8d 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -1,12 +1,12 @@ /* @internal */ namespace ts.Completions.PathCompletions { - export function getStringLiteralCompletionsFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] { + export function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] { const literalValue = normalizeSlashes(node.text); const scriptPath = node.getSourceFile().path; const scriptDirectory = getDirectoryPath(scriptPath); - const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); + const span = getDirectoryFragmentTextSpan((node).text, node.getStart(sourceFile) + 1); if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { const extensions = getSupportedExtensions(compilerOptions); if (compilerOptions.rootDirs) { @@ -94,7 +94,7 @@ namespace ts.Completions.PathCompletions { * * both foo.ts and foo.tsx become foo */ - const foundFiles = createMap(); + const foundFiles = createMap(); for (let filePath of files) { filePath = normalizePath(filePath); if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { @@ -103,7 +103,7 @@ namespace ts.Completions.PathCompletions { const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); - if (!foundFiles.get(foundFileName)) { + if (!foundFiles.has(foundFileName)) { foundFiles.set(foundFileName, true); } } @@ -147,25 +147,15 @@ namespace ts.Completions.PathCompletions { getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, span, host, /*exclude*/ undefined, result); for (const path in paths) { - if (!paths.hasOwnProperty(path)) continue; const patterns = paths[path]; - if (!patterns) continue; - - if (path === "*") { - for (const pattern of patterns) { - for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions, host)) { - // Path mappings may provide a duplicate way to get to something we've already added, so don't add again. - if (result.some(entry => entry.name === match)) continue; - result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); + if (paths.hasOwnProperty(path) && patterns) { + for (const pathCompletion of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, fileExtensions, host)) { + // Path mappings may provide a duplicate way to get to something we've already added, so don't add again. + if (!result.some(entry => entry.name === pathCompletion)) { + result.push(createCompletionEntryForModule(pathCompletion, ScriptElementKind.externalModuleName, span)); } } } - else if (startsWith(path, fragment)) { - if (patterns.length === 1) { - if (result.some(entry => entry.name === path)) continue; - result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); - } - } } } @@ -187,52 +177,67 @@ namespace ts.Completions.PathCompletions { return result; } - function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: ReadonlyArray, host: LanguageServiceHost): string[] { - if (host.readDirectory) { - const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; - if (parsed) { - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a - // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); - const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); - const normalizedPrefixBase = getBaseFileName(normalizedPrefix); - - const fragmentHasPath = stringContains(fragment, directorySeparator); - - // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; - - const normalizedSuffix = normalizePath(parsed.suffix); - const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); - const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; - - // If we have a suffix, then we need to read the directory all the way down. We could create a glob - // that encodes the suffix, but we would have to escape the character "?" which readDirectory - // doesn't support. For now, this is safer but slower - const includeGlob = normalizedSuffix ? "**/*" : "./*"; - - const matches = tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]); - if (matches) { - const result: string[] = []; - - // Trim away prefix and suffix - for (const match of matches) { - const normalizedMatch = normalizePath(match); - if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { - continue; - } - - const start = completePrefix.length; - const length = normalizedMatch.length - start - normalizedSuffix.length; - - result.push(removeFileExtension(normalizedMatch.substr(start, length))); - } - return result; - } - } + function getCompletionsForPathMapping( + path: string, patterns: ReadonlyArray, fragment: string, baseUrl: string, fileExtensions: ReadonlyArray, host: LanguageServiceHost, + ): string[] { + if (!endsWith(path, "*")) { + // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion. + return !stringContains(path, "*") && startsWith(path, fragment) ? [path] : emptyArray; } - return undefined; + const pathPrefix = path.slice(0, path.length - 1); + if (!startsWith(fragment, pathPrefix)) { + return emptyArray; + } + + const remainingFragment = fragment.slice(pathPrefix.length); + return flatMap(patterns, pattern => getModulesForPathsPattern(remainingFragment, baseUrl, pattern, fileExtensions, host)); + } + + function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: ReadonlyArray, host: LanguageServiceHost): string[] | undefined { + if (!host.readDirectory) { + return undefined; + } + + const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; + if (!parsed) { + return undefined; + } + + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); + const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = getBaseFileName(normalizedPrefix); + + const fragmentHasPath = stringContains(fragment, directorySeparator); + + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + + const normalizedSuffix = normalizePath(parsed.suffix); + // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b". + const baseDirectory = normalizePath(combinePaths(baseUrl, expandedPrefixDirectory)); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*"; + + const matches = tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]); + const directories = tryGetDirectories(host, baseDirectory); + // Trim away prefix and suffix + return mapDefined(concatenate(matches, directories), match => { + const normalizedMatch = normalizePath(match); + if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { + return; + } + + const start = completePrefix.length; + const length = normalizedMatch.length - start - normalizedSuffix.length; + return removeFileExtension(normalizedMatch.substr(start, length)); + }); } function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] { @@ -464,7 +469,7 @@ namespace ts.Completions.PathCompletions { return tryIOAndConsumeErrors(host, host.getDirectories, directoryName); } - function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray): string[] { + function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray): string[] | undefined { return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include); } diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index 8f6a468be64..a99683d45ef 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -295,7 +295,7 @@ namespace ts { // import "mod"; // import d from "mod" // import {a as A } from "mod"; - // import * as NS from "mod" + // import * as NS from "mod" // import d, {a, b as B} from "mod" // import i = require("mod"); // import("mod"); diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index e0a19247939..85ef9113bda 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -33,8 +33,8 @@ namespace ts { } export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { - return flatMapIter(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context)); + return arrayFrom(flatMapIterator(refactors.values(), refactor => + context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context))); } export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index b66d14ee449..cddf40ae017 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -50,7 +50,6 @@ namespace ts.refactor.convertFunctionToES6Class { const { file: sourceFile } = context; const ctorSymbol = getConstructorSymbol(context); - const newLine = context.formatContext.options.newLineCharacter; const deletedNodes: Node[] = []; const deletes: (() => any)[] = []; @@ -88,7 +87,7 @@ namespace ts.refactor.convertFunctionToES6Class { } // Because the preceding node could be touched, we need to insert nodes before delete nodes. - changeTracker.insertNodeAfter(sourceFile, precedingNode, newClassDeclaration, { suffix: newLine }); + changeTracker.insertNodeAfter(sourceFile, precedingNode, newClassDeclaration); for (const deleteCallback of deletes) { deleteCallback(); } diff --git a/src/services/refactors/convertToEs6Module.ts b/src/services/refactors/convertToEs6Module.ts new file mode 100644 index 00000000000..1046bf90aa6 --- /dev/null +++ b/src/services/refactors/convertToEs6Module.ts @@ -0,0 +1,582 @@ +/* @internal */ +namespace ts.refactor { + const actionName = "Convert to ES6 module"; + + const convertToEs6Module: Refactor = { + name: actionName, + description: getLocaleSpecificMessage(Diagnostics.Convert_to_ES6_module), + getEditsForAction, + getAvailableActions, + }; + + registerRefactor(convertToEs6Module); + + function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { + const { file, startPosition } = context; + if (!isSourceFileJavaScript(file) || !file.commonJsModuleIndicator) { + return undefined; + } + + const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); + return !isAtTriggerLocation(file, node) ? undefined : [ + { + name: convertToEs6Module.name, + description: convertToEs6Module.description, + actions: [ + { + description: convertToEs6Module.description, + name: actionName, + }, + ], + }, + ]; + } + + function isAtTriggerLocation(sourceFile: SourceFile, node: Node, onSecondTry = false): boolean { + switch (node.kind) { + case SyntaxKind.CallExpression: + return isAtTopLevelRequire(node as CallExpression); + case SyntaxKind.PropertyAccessExpression: + return isExportsOrModuleExportsOrAlias(sourceFile, node as PropertyAccessExpression) + || isExportsOrModuleExportsOrAlias(sourceFile, (node as PropertyAccessExpression).expression); + case SyntaxKind.VariableDeclarationList: + const decl = (node as VariableDeclarationList).declarations[0]; + return isExportsOrModuleExportsOrAlias(sourceFile, decl.initializer); + case SyntaxKind.VariableDeclaration: + return isExportsOrModuleExportsOrAlias(sourceFile, (node as VariableDeclaration).initializer); + default: + return isExpression(node) && isExportsOrModuleExportsOrAlias(sourceFile, node) + || !onSecondTry && isAtTriggerLocation(sourceFile, node.parent, /*onSecondTry*/ true); + } + } + + function isAtTopLevelRequire(call: CallExpression): boolean { + if (!isRequireCall(call, /*checkArgumentIsStringLiteral*/ true)) { + return false; + } + const { parent: propAccess } = call; + const varDecl = isPropertyAccessExpression(propAccess) ? propAccess.parent : propAccess; + if (isExpressionStatement(varDecl) && isSourceFile(varDecl.parent)) { // `require("x");` as a statement + return true; + } + if (!isVariableDeclaration(varDecl)) { + return false; + } + const { parent: varDeclList } = varDecl; + if (varDeclList.kind !== SyntaxKind.VariableDeclarationList) { + return false; + } + const { parent: varStatement } = varDeclList; + return varStatement.kind === SyntaxKind.VariableStatement && varStatement.parent.kind === SyntaxKind.SourceFile; + } + + function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined { + Debug.assertEqual(actionName, _actionName); + const { file, program } = context; + Debug.assert(isSourceFileJavaScript(file)); + const edits = textChanges.ChangeTracker.with(context, changes => { + const moduleExportsChangedToDefault = convertFileToEs6Module(file, program.getTypeChecker(), changes, program.getCompilerOptions().target); + if (moduleExportsChangedToDefault) { + for (const importingFile of program.getSourceFiles()) { + fixImportOfModuleExports(importingFile, file, changes); + } + } + }); + return { edits, renameFilename: undefined, renameLocation: undefined }; + } + + function fixImportOfModuleExports(importingFile: ts.SourceFile, exportingFile: ts.SourceFile, changes: textChanges.ChangeTracker) { + for (const moduleSpecifier of importingFile.imports) { + const imported = getResolvedModule(importingFile, moduleSpecifier.text); + if (!imported || imported.resolvedFileName !== exportingFile.fileName) { + continue; + } + + const { parent } = moduleSpecifier; + switch (parent.kind) { + case SyntaxKind.ExternalModuleReference: { + const importEq = (parent as ExternalModuleReference).parent; + changes.replaceNode(importingFile, importEq, makeImport(importEq.name, /*namedImports*/ undefined, moduleSpecifier.text)); + break; + } + case SyntaxKind.CallExpression: { + const call = parent as CallExpression; + if (isRequireCall(call, /*checkArgumentIsStringLiteral*/ false)) { + changes.replaceNode(importingFile, parent, createPropertyAccess(getSynthesizedDeepClone(call), "default")); + } + break; + } + } + } + } + + /** @returns Whether we converted a `module.exports =` to a default export. */ + function convertFileToEs6Module(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, target: ScriptTarget): ModuleExportsChanged { + const identifiers: Identifiers = { original: collectFreeIdentifiers(sourceFile), additional: createMap() }; + const exports = collectExportRenames(sourceFile, checker, identifiers); + convertExportsAccesses(sourceFile, exports, changes); + let moduleExportsChangedToDefault = false; + for (const statement of sourceFile.statements) { + const moduleExportsChanged = convertStatement(sourceFile, statement, checker, changes, identifiers, target, exports); + moduleExportsChangedToDefault = moduleExportsChangedToDefault || moduleExportsChanged; + } + return moduleExportsChangedToDefault; + } + + /** + * Contains an entry for each renamed export. + * This is necessary because `exports.x = 0;` does not declare a local variable. + * Converting this to `export const x = 0;` would declare a local, so we must be careful to avoid shadowing. + * If there would be shadowing at either the declaration or at any reference to `exports.x` (now just `x`), we must convert to: + * const _x = 0; + * export { _x as x }; + * This conversion also must place if the exported name is not a valid identifier, e.g. `exports.class = 0;`. + */ + type ExportRenames = ReadonlyMap; + + function collectExportRenames(sourceFile: SourceFile, checker: TypeChecker, identifiers: Identifiers): ExportRenames { + const res = createMap(); + forEachExportReference(sourceFile, node => { + const { text, originalKeywordKind } = node.name; + if (!res.has(text) && (originalKeywordKind !== undefined && isNonContextualKeyword(originalKeywordKind) + || checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ true))) { + // Unconditionally add an underscore in case `text` is a keyword. + res.set(text, makeUniqueName(`_${text}`, identifiers)); + } + }); + return res; + } + + function convertExportsAccesses(sourceFile: SourceFile, exports: ExportRenames, changes: textChanges.ChangeTracker): void { + forEachExportReference(sourceFile, (node, isAssignmentLhs) => { + if (isAssignmentLhs) { + return; + } + const { text } = node.name; + changes.replaceNode(sourceFile, node, createIdentifier(exports.get(text) || text)); + }); + } + + function forEachExportReference(sourceFile: SourceFile, cb: (node: PropertyAccessExpression, isAssignmentLhs: boolean) => void): void { + sourceFile.forEachChild(function recur(node) { + if (isPropertyAccessExpression(node) && isExportsOrModuleExportsOrAlias(sourceFile, node.expression)) { + const { parent } = node; + cb(node, isBinaryExpression(parent) && parent.left === node && parent.operatorToken.kind === SyntaxKind.EqualsToken); + } + node.forEachChild(recur); + }); + } + + /** Whether `module.exports =` was changed to `export default` */ + type ModuleExportsChanged = boolean; + + function convertStatement(sourceFile: SourceFile, statement: Statement, checker: TypeChecker, changes: textChanges.ChangeTracker, identifiers: Identifiers, target: ScriptTarget, exports: ExportRenames): ModuleExportsChanged { + switch (statement.kind) { + case SyntaxKind.VariableStatement: + convertVariableStatement(sourceFile, statement as VariableStatement, changes, checker, identifiers, target); + return false; + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + switch (expression.kind) { + case SyntaxKind.CallExpression: { + if (isRequireCall(expression, /*checkArgumentIsStringLiteral*/ true)) { + // For side-effecting require() call, just make a side-effecting import. + changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0].text)); + } + return false; + } + case SyntaxKind.BinaryExpression: { + const { left, operatorToken, right } = expression as BinaryExpression; + return operatorToken.kind === SyntaxKind.EqualsToken && convertAssignment(sourceFile, checker, statement as ExpressionStatement, left, right, changes, exports); + } + } + } + // falls through + default: + return false; + } + } + + function convertVariableStatement(sourceFile: SourceFile, statement: VariableStatement, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, target: ScriptTarget): void { + const { declarationList } = statement as VariableStatement; + let foundImport = false; + const newNodes = flatMap(declarationList.declarations, decl => { + const { name, initializer } = decl; + if (isExportsOrModuleExportsOrAlias(sourceFile, initializer)) { + // `const alias = module.exports;` can be removed. + foundImport = true; + return []; + } + if (isRequireCall(initializer, /*checkArgumentIsStringLiteral*/ true)) { + foundImport = true; + return convertSingleImport(sourceFile, name, initializer.arguments[0].text, changes, checker, identifiers, target); + } + else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteral*/ true)) { + foundImport = true; + return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0].text, identifiers); + } + else { + // Move it out to its own variable statement. + return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([decl], declarationList.flags)); + } + }); + if (foundImport) { + // useNonAdjustedEndPosition to ensure we don't eat the newline after the statement. + changes.replaceNodeWithNodes(sourceFile, statement, newNodes); + } + } + + /** Converts `const name = require("moduleSpecifier").propertyName` */ + function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: string, identifiers: Identifiers): ReadonlyArray { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: { + // `const [a, b] = require("c").d` --> `import { d } from "c"; const [a, b] = d;` + const tmp = makeUniqueName(propertyName, identifiers); + return [ + makeSingleImport(tmp, propertyName, moduleSpecifier), + makeConst(/*modifiers*/ undefined, name, createIdentifier(tmp)), + ]; + } + case SyntaxKind.Identifier: + // `const a = require("b").c` --> `import { c as a } from "./b"; + return [makeSingleImport(name.text, propertyName, moduleSpecifier)]; + default: + Debug.assertNever(name); + } + } + + function convertAssignment( + sourceFile: SourceFile, + checker: TypeChecker, + statement: ExpressionStatement, + left: Expression, + right: Expression, + changes: textChanges.ChangeTracker, + exports: ExportRenames, + ): ModuleExportsChanged { + if (!isPropertyAccessExpression(left)) { + return false; + } + + if (isExportsOrModuleExportsOrAlias(sourceFile, left)) { + if (isExportsOrModuleExportsOrAlias(sourceFile, right)) { + // `const alias = module.exports;` or `module.exports = alias;` can be removed. + changes.deleteNode(sourceFile, statement); + } + else { + let newNodes = isObjectLiteralExpression(right) ? tryChangeModuleExportsObject(right) : undefined; + let changedToDefaultExport = false; + if (!newNodes) { + ([newNodes, changedToDefaultExport] = convertModuleExportsToExportDefault(right, checker)); + } + changes.replaceNodeWithNodes(sourceFile, statement, newNodes); + return changedToDefaultExport; + } + } + else if (isExportsOrModuleExportsOrAlias(sourceFile, left.expression)) { + convertNamedExport(sourceFile, statement, left.name, right, changes, exports); + } + + return false; + } + + /** + * Convert `module.exports = { ... }` to individual exports.. + * We can't always do this if the module has interesting members -- then it will be a default export instead. + */ + function tryChangeModuleExportsObject(object: ObjectLiteralExpression): ReadonlyArray | undefined { + return mapAllOrFail(object.properties, prop => { + switch (prop.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // TODO: Maybe we should handle this? See fourslash test `refactorConvertToEs6Module_export_object_shorthand.ts`. + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: + return undefined; + case SyntaxKind.PropertyAssignment: { + const { name, initializer } = prop as PropertyAssignment; + return !isIdentifier(name) ? undefined : convertExportsDotXEquals(name.text, initializer); + } + case SyntaxKind.MethodDeclaration: { + const m = prop as MethodDeclaration; + return !isIdentifier(m.name) ? undefined : functionExpressionToDeclaration(m.name.text, [createToken(SyntaxKind.ExportKeyword)], m); + } + default: + Debug.assertNever(prop); + } + }); + } + + function convertNamedExport( + sourceFile: SourceFile, + statement: Statement, + propertyName: Identifier, + right: Expression, + changes: textChanges.ChangeTracker, + exports: ExportRenames, + ): void { + // If "originalKeywordKind" was set, this is e.g. `exports. + const { text } = propertyName; + const rename = exports.get(text); + if (rename !== undefined) { + /* + const _class = 0; + export { _class as class }; + */ + const newNodes = [ + makeConst(/*modifiers*/ undefined, rename, right), + makeExportDeclaration([createExportSpecifier(rename, text)]), + ]; + changes.replaceNodeWithNodes(sourceFile, statement, newNodes); + } + else { + changes.replaceNode(sourceFile, statement, convertExportsDotXEquals(text, right), { useNonAdjustedEndPosition: true }); + } + } + + function convertModuleExportsToExportDefault(exported: Expression, checker: TypeChecker): [ReadonlyArray, ModuleExportsChanged] { + const modifiers = [createToken(SyntaxKind.ExportKeyword), createToken(SyntaxKind.DefaultKeyword)]; + switch (exported.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: { + // `module.exports = function f() {}` --> `export default function f() {}` + const fn = exported as FunctionExpression | ArrowFunction; + return [[functionExpressionToDeclaration(fn.name && fn.name.text, modifiers, fn)], true]; + } + case SyntaxKind.ClassExpression: { + // `module.exports = class C {}` --> `export default class C {}` + const cls = exported as ClassExpression; + return [[classExpressionToDeclaration(cls.name && cls.name.text, modifiers, cls)], true]; + } + case SyntaxKind.CallExpression: + if (isRequireCall(exported, /*checkArgumentIsStringLiteral*/ true)) { + return convertReExportAll(exported.arguments[0], checker); + } + // falls through + default: + // `module.exports = 0;` --> `export default 0;` + return [[createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ false, exported)], true]; + } + } + + function convertReExportAll(reExported: StringLiteralLike, checker: TypeChecker): [ReadonlyArray, ModuleExportsChanged] { + // `module.exports = require("x");` ==> `export * from "x"; export { default } from "x";` + const moduleSpecifier = reExported.text; + const moduleSymbol = checker.getSymbolAtLocation(reExported); + const exports = moduleSymbol ? moduleSymbol.exports : emptyUnderscoreEscapedMap; + return exports.has("export=" as __String) + ? [[reExportDefault(moduleSpecifier)], true] + : !exports.has("default" as __String) + ? [[reExportStar(moduleSpecifier)], false] + // If there's some non-default export, must include both `export *` and `export default`. + : exports.size > 1 ? [[reExportStar(moduleSpecifier), reExportDefault(moduleSpecifier)], true] : [[reExportDefault(moduleSpecifier)], true]; + } + function reExportStar(moduleSpecifier: string): ExportDeclaration { + return makeExportDeclaration(/*exportClause*/ undefined, moduleSpecifier); + } + function reExportDefault(moduleSpecifier: string): ExportDeclaration { + return makeExportDeclaration([createExportSpecifier(/*propertyName*/ undefined, "default")], moduleSpecifier); + } + + function convertExportsDotXEquals(name: string | undefined, exported: Expression): Statement { + const modifiers = [createToken(SyntaxKind.ExportKeyword)]; + switch (exported.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + // `exports.f = function() {}` --> `export function f() {}` + return functionExpressionToDeclaration(name, modifiers, exported as FunctionExpression | ArrowFunction); + case SyntaxKind.ClassExpression: + // `exports.C = class {}` --> `export class C {}` + return classExpressionToDeclaration(name, modifiers, exported as ClassExpression); + default: + // `exports.x = 0;` --> `export const x = 0;` + return makeConst(modifiers, createIdentifier(name), exported); + } + } + + /** + * Converts `const <> = require("x");`. + * Returns nodes that will replace the variable declaration for the commonjs import. + * May also make use `changes` to remove qualifiers at the use sites of imports, to change `mod.x` to `x`. + */ + function convertSingleImport( + file: SourceFile, + name: BindingName, + moduleSpecifier: string, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + identifiers: Identifiers, + target: ScriptTarget, + ): ReadonlyArray { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: { + const importSpecifiers = mapAllOrFail(name.elements, e => + e.dotDotDotToken || e.initializer || e.propertyName && !isIdentifier(e.propertyName) || !isIdentifier(e.name) + ? undefined + : makeImportSpecifier(e.propertyName && (e.propertyName as Identifier).text, e.name.text)); + if (importSpecifiers) { + return [makeImport(/*name*/ undefined, importSpecifiers, moduleSpecifier)]; + } + } + // falls through -- object destructuring has an interesting pattern and must be a variable declaration + case SyntaxKind.ArrayBindingPattern: { + /* + import x from "x"; + const [a, b, c] = x; + */ + const tmp = makeUniqueName(codefix.moduleSpecifierToValidIdentifier(moduleSpecifier, target), identifiers); + return [ + makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier), + makeConst(/*modifiers*/ undefined, getSynthesizedDeepClone(name), createIdentifier(tmp)), + ]; + } + case SyntaxKind.Identifier: + return convertSingleIdentifierImport(file, name, moduleSpecifier, changes, checker, identifiers); + default: + Debug.assertNever(name); + } + } + + /** + * Convert `import x = require("x").` + * Also converts uses like `x.y()` to `y()` and uses a named import. + */ + function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: string, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers): ReadonlyArray { + const nameSymbol = checker.getSymbolAtLocation(name); + // Maps from module property name to name actually used. (The same if there isn't shadowing.) + const namedBindingsNames = createMap(); + // True if there is some non-property use like `x()` or `f(x)`. + let needDefaultImport = false; + + for (const use of identifiers.original.get(name.text)) { + if (checker.getSymbolAtLocation(use) !== nameSymbol || use === name) { + // This was a use of a different symbol with the same name, due to shadowing. Ignore. + continue; + } + + const { parent } = use; + if (isPropertyAccessExpression(parent)) { + const { expression, name: { text: propertyName } } = parent; + Debug.assert(expression === use); // Else shouldn't have been in `collectIdentifiers` + let idName = namedBindingsNames.get(propertyName); + if (idName === undefined) { + idName = makeUniqueName(propertyName, identifiers); + namedBindingsNames.set(propertyName, idName); + } + changes.replaceNode(file, parent, createIdentifier(idName)); + } + else { + needDefaultImport = true; + } + } + + const namedBindings = namedBindingsNames.size === 0 ? undefined : arrayFrom(mapIterator(namedBindingsNames.entries(), ([propertyName, idName]) => + createImportSpecifier(propertyName === idName ? undefined : createIdentifier(propertyName), createIdentifier(idName)))); + if (!namedBindings) { + // If it was unused, ensure that we at least import *something*. + needDefaultImport = true; + } + return [makeImport(needDefaultImport ? getSynthesizedDeepClone(name) : undefined, namedBindings, moduleSpecifier)]; + } + + // Identifiers helpers + + function makeUniqueName(name: string, identifiers: Identifiers): string { + while (identifiers.original.has(name) || identifiers.additional.has(name)) { + name = `_${name}`; + } + identifiers.additional.set(name, true); + return name; + } + + /** + * Helps us create unique identifiers. + * `original` refers to the local variable names in the original source file. + * `additional` is any new unique identifiers we've generated. (e.g., we'll generate `_x`, then `__x`.) + */ + interface Identifiers { + readonly original: FreeIdentifiers; + // Additional identifiers we've added. Mutable! + readonly additional: Map; + } + + type FreeIdentifiers = ReadonlyMap>; + function collectFreeIdentifiers(file: SourceFile): FreeIdentifiers { + const map = createMultiMap(); + file.forEachChild(function recur(node) { + if (isIdentifier(node) && isFreeIdentifier(node)) { + map.add(node.text, node); + } + node.forEachChild(recur); + }); + return map; + } + + function isFreeIdentifier(node: Identifier): boolean { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + return (parent as PropertyAccessExpression).name !== node; + case SyntaxKind.BindingElement: + return (parent as BindingElement).propertyName !== node; + default: + return true; + } + } + + // Node helpers + + function functionExpressionToDeclaration(name: string | undefined, additionalModifiers: ReadonlyArray, fn: FunctionExpression | ArrowFunction | MethodDeclaration): FunctionDeclaration { + return createFunctionDeclaration( + getSynthesizedDeepClones(fn.decorators), // TODO: GH#19915 Don't think this is even legal. + concatenate(additionalModifiers, getSynthesizedDeepClones(fn.modifiers)), + getSynthesizedDeepClone(fn.asteriskToken), + name, + getSynthesizedDeepClones(fn.typeParameters), + getSynthesizedDeepClones(fn.parameters), + getSynthesizedDeepClone(fn.type), + convertToFunctionBody(getSynthesizedDeepClone(fn.body))); + } + + function classExpressionToDeclaration(name: string | undefined, additionalModifiers: ReadonlyArray, cls: ClassExpression): ClassDeclaration { + return createClassDeclaration( + getSynthesizedDeepClones(cls.decorators), // TODO: GH#19915 Don't think this is even legal. + concatenate(additionalModifiers, getSynthesizedDeepClones(cls.modifiers)), + name, + getSynthesizedDeepClones(cls.typeParameters), + getSynthesizedDeepClones(cls.heritageClauses), + getSynthesizedDeepClones(cls.members)); + } + + function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: string): ImportDeclaration { + return propertyName === "default" + ? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier) + : makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier); + } + + function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray, moduleSpecifier: string): ImportDeclaration { + const importClause = (name || namedImports) && createImportClause(name, namedImports && createNamedImports(namedImports)); + return createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifier)); + } + + function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier { + return createImportSpecifier(propertyName !== undefined && propertyName !== name ? createIdentifier(propertyName) : undefined, createIdentifier(name)); + } + + function makeConst(modifiers: ReadonlyArray | undefined, name: string | BindingName, init: Expression): VariableStatement { + return createVariableStatement( + modifiers, + createVariableDeclarationList( + [createVariableDeclaration(name, /*type*/ undefined, init)], + NodeFlags.Const)); + } + + function makeExportDeclaration(exportSpecifiers: ExportSpecifier[] | undefined, moduleSpecifier?: string): ExportDeclaration { + return createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + exportSpecifiers && createNamedExports(exportSpecifiers), + moduleSpecifier === undefined ? undefined : createLiteral(moduleSpecifier)); + } +} diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index f1a461b0088..b3110a0ca36 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -658,11 +658,10 @@ namespace ts.refactor.extractSymbol { case SyntaxKind.Constructor: return "constructor"; case SyntaxKind.FunctionExpression: - return scope.name - ? `function expression '${scope.name.text}'` - : "anonymous function expression"; case SyntaxKind.FunctionDeclaration: - return `function '${scope.name.text}'`; + return scope.name + ? `function '${scope.name.text}'` + : "anonymous function"; case SyntaxKind.ArrowFunction: return "arrow function"; case SyntaxKind.MethodDeclaration: @@ -811,13 +810,10 @@ namespace ts.refactor.extractSymbol { const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); if (nodeToInsertBefore) { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, { suffix: context.newLineCharacter + context.newLineCharacter }); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true); } else { - changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { - prefix: isLineBreak(file.text.charCodeAt(scope.getLastToken().pos)) ? context.newLineCharacter : context.newLineCharacter + context.newLineCharacter, - suffix: context.newLineCharacter - }); + changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction); } const newNodes: Node[] = []; @@ -960,10 +956,12 @@ namespace ts.refactor.extractSymbol { } } - const replacementRange = isReadonlyArray(range.range) - ? { pos: first(range.range).getStart(), end: last(range.range).end } - : { pos: range.range.getStart(), end: range.range.end }; - changeTracker.replaceRangeWithNodes(context.file, replacementRange, newNodes, { nodeSeparator: context.newLineCharacter }); + if (isReadonlyArray(range.range)) { + changeTracker.replaceNodesWithNodes(context.file, range.range, newNodes); + } + else { + changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes); + } const edits = changeTracker.getChanges(); const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range; @@ -1006,7 +1004,7 @@ namespace ts.refactor.extractSymbol { const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file.text); const isJS = isInJavaScriptFile(scope); - const variableType = isJS + const variableType = isJS || !checker.isContextSensitive(node) ? undefined : checker.typeToTypeNode(checker.getContextualType(node), scope, NodeBuilderFlags.NoTruncation); @@ -1041,7 +1039,7 @@ namespace ts.refactor.extractSymbol { // Declare const maxInsertionPos = node.pos; const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter }); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true); // Consume changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference); @@ -1056,8 +1054,8 @@ namespace ts.refactor.extractSymbol { const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); if (oldVariableDeclaration) { // Declare - // CONSIDER: could detect that each is on a separate line - changeTracker.insertNodeAt(context.file, oldVariableDeclaration.getStart(), newVariableDeclaration, { suffix: ", " }); + // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`) + changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration); // Consume const localReference = createIdentifier(localNameText); @@ -1079,17 +1077,10 @@ namespace ts.refactor.extractSymbol { // Declare const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); if (nodeToInsertBefore.pos === 0) { - // If we're at the beginning of the file, we need to take care not to insert before header comments - // (e.g. copyright, triple-slash references). Fortunately, this problem has already been solved - // for imports. - const insertionPos = getSourceFileImportLocation(file); - changeTracker.insertNodeAt(context.file, insertionPos, newVariableStatement, { - prefix: insertionPos === 0 ? undefined : context.newLineCharacter, - suffix: isLineBreak(file.text.charCodeAt(insertionPos)) ? context.newLineCharacter : context.newLineCharacter + context.newLineCharacter - }); + changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false); } else { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter }); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false); } // Consume @@ -1286,12 +1277,12 @@ namespace ts.refactor.extractSymbol { * If `scope` contains a function after `minPos`, then return the first such function. * Otherwise, return `undefined`. */ - function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Node | undefined { + function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined { return find(getStatementsOrClassElements(scope), child => child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); } - function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): Node { + function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement { const members = scope.members; Debug.assert(members.length > 0); // There must be at least one child, since we extracted from one. @@ -1317,7 +1308,7 @@ namespace ts.refactor.extractSymbol { return prevMember; } - function getNodeToInsertConstantBefore(node: Node, scope: Scope): Node { + function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement { Debug.assert(!isClassLike(scope)); let prevScope: Scope | undefined = undefined; @@ -1701,7 +1692,7 @@ namespace ts.refactor.extractSymbol { } for (let i = 0; i < scopes.length; i++) { const scope = scopes[i]; - const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags); + const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false); if (resolvedSymbol === symbol) { continue; } diff --git a/src/services/refactors/refactors.ts b/src/services/refactors/refactors.ts index 3858b198743..8b4561700d5 100644 --- a/src/services/refactors/refactors.ts +++ b/src/services/refactors/refactors.ts @@ -1,5 +1,6 @@ /// /// +/// /// /// /// diff --git a/src/services/refactors/useDefaultImport.ts b/src/services/refactors/useDefaultImport.ts index 56faf082a49..a103168f67b 100644 --- a/src/services/refactors/useDefaultImport.ts +++ b/src/services/refactors/useDefaultImport.ts @@ -23,7 +23,7 @@ namespace ts.refactor.installTypesForPackage { return undefined; } - const module = ts.getResolvedModule(file, importInfo.moduleSpecifier.text); + const module = getResolvedModule(file, importInfo.moduleSpecifier.text); const resolvedFile = program.getSourceFile(module.resolvedFileName); if (!(resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals)) { return undefined; @@ -52,7 +52,7 @@ namespace ts.refactor.installTypesForPackage { } const { importStatement, name, moduleSpecifier } = importInfo; const newImportClause = createImportClause(name, /*namedBindings*/ undefined); - const newImportStatement = ts.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier); + const newImportStatement = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier); return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, importStatement, newImportStatement)), renameFilename: undefined, diff --git a/src/services/rename.ts b/src/services/rename.ts index cfe79679fba..ca46a93f3c9 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -38,9 +38,17 @@ namespace ts.Rename { return undefined; } - const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); - return kind ? getRenameInfoSuccess(displayName, typeChecker.getFullyQualifiedName(symbol), kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile) : undefined; + if (!kind) { + return undefined; + } + + const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteral(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) + ? stripQuotes(getTextOfIdentifierOrLiteral(node)) + : undefined; + const displayName = specifierName || typeChecker.symbolToString(symbol); + const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol); + return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile); } } else if (node.kind === SyntaxKind.StringLiteral) { diff --git a/src/services/services.ts b/src/services/services.ts index 24679e6a167..eb5e3ae5743 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -488,6 +488,7 @@ namespace ts { parameters: Symbol[]; thisParameter: Symbol; resolvedReturnType: Type; + resolvedTypePredicate: TypePredicate | undefined; minTypeArgumentCount: number; minArgumentCount: number; hasRestParameter: boolean; @@ -1268,6 +1269,7 @@ namespace ts { } return host.readFile && host.readFile(fileName); }, + realpath: host.realpath && (path => host.realpath(path)), directoryExists: directoryName => { return directoryProbablyExists(directoryName, host); }, @@ -1430,7 +1432,7 @@ namespace ts { return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; } - function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = { includeExternalModuleExports: false }): CompletionInfo { + function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = { includeExternalModuleExports: false, includeInsertTextCompletions: false }): CompletionInfo { synchronizeHostData(); return Completions.getCompletionsAtPosition( host, @@ -1446,7 +1448,7 @@ namespace ts { function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails { synchronizeHostData(); return Completions.getCompletionEntryDetails( - program.getTypeChecker(), + program, log, program.getCompilerOptions(), getValidSourceFile(fileName), @@ -1881,7 +1883,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); @@ -1894,6 +1896,16 @@ namespace ts { }); } + function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions { + synchronizeHostData(); + Debug.assert(scope.type === "file"); + const sourceFile = getValidSourceFile(scope.fileName); + const newLineCharacter = getNewLineOrDefaultFromHost(host); + const formatContext = formatting.getFormatContext(formatOptions); + + return codefix.getAllFixes({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + } + function applyCodeActionCommand(action: CodeActionCommand): Promise; function applyCodeActionCommand(action: CodeActionCommand[]): Promise; function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -2041,7 +2053,7 @@ namespace ts { } function getTodoCommentsRegExp(): RegExp { - // NOTE: ?: means 'non-capture group'. It allows us to have groups without having to + // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to // filter them out later in the final result array. // TODO comments can appear in one of the following forms: @@ -2188,6 +2200,7 @@ namespace ts { isValidBraceCompletionAtPosition, getSpanOfEnclosingComment, getCodeFixesAtPosition, + getCombinedCodeFix, applyCodeActionCommand, getEmitOutput, getNonBoundSourceFile, @@ -2238,20 +2251,6 @@ namespace ts { isLiteralComputedPropertyDeclarationName(node); } - function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { - switch (node.kind) { - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return true; - } - return false; - } - /** * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } */ diff --git a/src/services/shims.ts b/src/services/shims.ts index 7c932361c10..fda698785b3 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -141,7 +141,7 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, start: number, length: number): string; getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): string; - getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/, source: string | undefined): string; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined): string; getQuickInfoAtPosition(fileName: string, position: number): string; @@ -330,7 +330,7 @@ namespace ts { // if shimHost is a COM object then property check will become method call with no arguments. // 'in' does not have this effect. if ("getModuleResolutionsForFile" in this.shimHost) { - this.resolveModuleNames = (moduleNames: string[], containingFile: string) => { + this.resolveModuleNames = (moduleNames: string[], containingFile: string): ResolvedModuleFull[] => { const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); return map(moduleNames, name => { const result = getProperty(resolutionsInFile, name); @@ -906,11 +906,11 @@ namespace ts { } /** Get a string based representation of a completion list entry details */ - public getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/, source: string | undefined) { + public getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined) { return this.forwardJSONCall( `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, () => { - const localOptions: ts.FormatCodeOptions = JSON.parse(options); + const localOptions: ts.FormatCodeOptions = options === undefined ? undefined : JSON.parse(options); return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source); } ); diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 36c83f5f4af..f5338db954b 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -197,7 +197,7 @@ namespace ts.SignatureHelp { function getArgumentIndex(argumentsList: Node, node: Node) { // The list we got back can include commas. In the presence of errors it may // also just have nodes without commas. For example "Foo(a b c)" will have 3 - // args without commas. We want to find what index we're at. So we count + // args without commas. We want to find what index we're at. So we count // forward until we hit ourselves, only incrementing the index if it isn't a // comma. // @@ -224,12 +224,12 @@ namespace ts.SignatureHelp { // The argument count for a list is normally the number of non-comma children it has. // For example, if you have "Foo(a,b)" then there will be three children of the arg // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there - // is a small subtlety. If you have "Foo(a,)", then the child list will just have + // is a small subtlety. If you have "Foo(a,)", then the child list will just have // 'a' ''. So, in the case where the last child is a comma, we increase the // arg count by one to compensate. // - // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then - // we'll have: 'a' '' '' + // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then + // we'll have: 'a' '' '' // That will give us 2 non-commas. We then add one for the last comma, giving us an // arg count of 3. const listChildren = argumentsList.getChildren(); @@ -253,9 +253,11 @@ namespace ts.SignatureHelp { // not enough to put us in the substitution expression; we should consider ourselves part of // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). // + // tslint:disable no-double-space // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` // ^ ^ ^ ^ ^ ^ ^ ^ ^ // Case: 1 1 3 2 1 3 2 2 1 + // tslint:enable no-double-space Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); if (isTemplateLiteralKind(node.kind)) { if (isInsideTemplateLiteral(node, position)) { @@ -307,9 +309,8 @@ namespace ts.SignatureHelp { // Otherwise, we will not show signature help past the expression. // For example, // - // ` ${ 1 + 1 foo(10) - // | | - // + // ` ${ 1 + 1 foo(10) + // | | // This is because a Missing node has no width. However, what we actually want is to include trivia // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. if (template.kind === SyntaxKind.TemplateExpression) { diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index fe04342b8f7..6f979f1721b 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -91,9 +91,14 @@ namespace ts.SymbolDisplay { } export function getSymbolModifiers(symbol: Symbol): string { - return symbol && symbol.declarations && symbol.declarations.length > 0 + const nodeModifiers = symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) : ScriptElementKindModifier.none; + + const symbolModifiers = symbol && symbol.flags & SymbolFlags.Optional ? + ScriptElementKindModifier.optionalModifier + : ScriptElementKindModifier.none; + return nodeModifiers && symbolModifiers ? nodeModifiers + "," + symbolModifiers : nodeModifiers || symbolModifiers; } interface SymbolDisplayPartsDocumentationAndSymbolKind { @@ -105,7 +110,7 @@ namespace ts.SymbolDisplay { // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node, - location: Node, semanticMeaning = getMeaningFromLocation(location)): SymbolDisplayPartsDocumentationAndSymbolKind { + location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[]; @@ -115,6 +120,7 @@ namespace ts.SymbolDisplay { let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); let type: Type; + let documentationFromAlias: SymbolDisplayPart[]; // Class at constructor site need to be shown as constructor apart from property,method, vars if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { @@ -243,6 +249,7 @@ namespace ts.SymbolDisplay { } } if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { + addAliasPrefixIfNecessary(); if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) { // Special case for class expressions because we would like to indicate that // the class name is local to the class body (similar to function expression) @@ -258,14 +265,14 @@ namespace ts.SymbolDisplay { writeTypeParametersOfSymbol(symbol, sourceFile); } if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol); writeTypeParametersOfSymbol(symbol, sourceFile); } if (symbolFlags & SymbolFlags.TypeAlias) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol); @@ -276,7 +283,7 @@ namespace ts.SymbolDisplay { addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias)); } if (symbolFlags & SymbolFlags.Enum) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); if (forEach(symbol.declarations, isConstEnumDeclaration)) { displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); displayParts.push(spacePart()); @@ -286,7 +293,7 @@ namespace ts.SymbolDisplay { addFullSymbolName(symbol); } if (symbolFlags & SymbolFlags.Module) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration); const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); @@ -294,7 +301,7 @@ namespace ts.SymbolDisplay { addFullSymbolName(symbol); } if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(textPart("type parameter")); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); @@ -328,7 +335,7 @@ namespace ts.SymbolDisplay { else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) { // Type alias type parameter // For example - // type list = T[]; // Both T will go through same code path + // type list = T[]; // Both T will go through same code path addInPrefix(); displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); @@ -354,7 +361,32 @@ namespace ts.SymbolDisplay { } } if (symbolFlags & SymbolFlags.Alias) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); + if (!hasAddedSymbolInfo) { + const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); + if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { + const resolvedNode = resolvedSymbol.declarations[0]; + const declarationName = ts.getNameOfDeclaration(resolvedNode); + if (declarationName) { + const isExternalModuleDeclaration = + ts.isModuleWithStringLiteralName(resolvedNode) && + ts.hasModifier(resolvedNode, ModifierFlags.Ambient); + const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; + const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKind( + typeChecker, + resolvedSymbol, + ts.getSourceFileOfNode(resolvedNode), + resolvedNode, + declarationName, + semanticMeaning, + shouldUseAliasName ? symbol : resolvedSymbol); + displayParts.push(...resolvedInfo.displayParts); + displayParts.push(lineBreakPart()); + documentationFromAlias = resolvedInfo.documentation; + } + } + } + switch (symbol.declarations[0].kind) { case SyntaxKind.NamespaceExportDeclaration: displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); @@ -400,7 +432,7 @@ namespace ts.SymbolDisplay { if (symbolKind !== ScriptElementKind.unknown) { if (type) { if (isThisExpression) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); } else { @@ -472,12 +504,24 @@ namespace ts.SymbolDisplay { } } + if (documentation.length === 0 && documentationFromAlias) { + documentation = documentationFromAlias; + } + return { displayParts, documentation, symbolKind, tags }; - function addNewLineIfDisplayPartsExist() { + function prefixNextMeaning() { if (displayParts.length) { displayParts.push(lineBreakPart()); } + addAliasPrefixIfNecessary(); + } + + function addAliasPrefixIfNecessary() { + if (alias) { + pushTypePart(ScriptElementKind.alias); + displayParts.push(spacePart()); + } } function addInPrefix() { @@ -486,17 +530,20 @@ namespace ts.SymbolDisplay { displayParts.push(spacePart()); } - function addFullSymbolName(symbol: Symbol, enclosingDeclaration?: Node) { - const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbol, enclosingDeclaration || sourceFile, /*meaning*/ undefined, + function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { + if (alias && symbolToDisplay === symbol) { + symbolToDisplay = alias; + } + const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing); addRange(displayParts, fullSymbolDisplayParts); } function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { - addNewLineIfDisplayPartsExist(); + prefixNextMeaning(); if (symbolKind) { pushTypePart(symbolKind); - if (!some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { + if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { displayParts.push(spacePart()); addFullSymbolName(symbol); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 7c4e25537ef..b0efef44642 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -117,7 +117,7 @@ namespace ts.textChanges { readonly options?: never; } - export interface ChangeMultipleNodesOptions extends ChangeNodeOptions { + interface ChangeMultipleNodesOptions extends ChangeNodeOptions { nodeSeparator: string; } interface ReplaceWithMultipleNodes extends BaseChange { @@ -192,8 +192,11 @@ namespace ts.textChanges { } export class ChangeTracker { - private changes: Change[] = []; + private readonly changes: Change[] = []; private readonly newLineCharacter: string; + private readonly deletedNodesInLists: true[] = []; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. + // Map from class id to nodes to insert at the start + private readonly nodesInsertedAtClassStarts = createMap<{ sourceFile: SourceFile, cls: ClassLikeDeclaration, members: ClassElement[] }>(); public static fromContext(context: TextChangesContext): ChangeTracker { return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext); @@ -220,14 +223,14 @@ namespace ts.textChanges { public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) { const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, node, options); - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } }); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); return this; } public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } }); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); return this; } @@ -245,6 +248,9 @@ namespace ts.textChanges { this.deleteNode(sourceFile, node); return this; } + const id = getNodeId(node); + Debug.assert(!this.deletedNodesInLists[id], "Deleting a node twice"); + this.deletedNodesInLists[id] = true; if (index !== containingList.length - 1) { const nextToken = getTokenAtPosition(sourceFile, node.end, /*includeJsDocComment*/ false); if (nextToken && isSeparator(node, nextToken)) { @@ -258,9 +264,17 @@ namespace ts.textChanges { } } else { - const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false); - if (previousToken && isSeparator(node, previousToken)) { - this.deleteNodeRange(sourceFile, previousToken, node); + const prev = containingList[index - 1]; + if (this.deletedNodesInLists[getNodeId(prev)]) { + const pos = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const end = getAdjustedEndPosition(sourceFile, node, {}); + this.deleteRange(sourceFile, { pos, end }); + } + else { + const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false); + if (previousToken && isSeparator(node, previousToken)) { + this.deleteNodeRange(sourceFile, previousToken, node); + } } } return this; @@ -305,40 +319,104 @@ namespace ts.textChanges { return this; } - public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: ReadonlyArray, options: ChangeMultipleNodesOptions) { - const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start); - const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options); - return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options); + public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: ReadonlyArray): void { + this.replaceWithMultiple(sourceFile, oldNode.getStart(sourceFile), oldNode.getEnd(), newNodes, { nodeSeparator: this.newLineCharacter }); } - public replaceNodesWithNodes(sourceFile: SourceFile, oldNodes: ReadonlyArray, newNodes: ReadonlyArray, options: ChangeMultipleNodesOptions) { - const startPosition = getAdjustedStartPosition(sourceFile, oldNodes[0], options, Position.Start); - const endPosition = getAdjustedEndPosition(sourceFile, lastOrUndefined(oldNodes), options); - return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options); + public replaceNodesWithNodes(sourceFile: SourceFile, oldNodes: ReadonlyArray, newNodes: ReadonlyArray): void { + this.replaceWithMultiple(sourceFile, first(oldNodes).getStart(sourceFile), last(oldNodes).getEnd(), newNodes, { nodeSeparator: this.newLineCharacter }); } - public replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: ReadonlyArray, options: ChangeMultipleNodesOptions) { - return this.replaceWithMultiple(sourceFile, range.pos, range.end, newNodes, options); - } - - public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: ReadonlyArray, options: ChangeMultipleNodesOptions) { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start); - const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options); - } - - public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) { + private insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) { this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, options, node: newNode, range: { pos, end: pos } }); return this; } - public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, before, options, Position.Start); - return this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, options); + public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void { + const pos = getInsertionPositionAtSourceFileTop(sourceFile); + this.insertNodeAt(sourceFile, pos, newNode, { + prefix: pos === 0 ? undefined : this.newLineCharacter, + suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""), + }); } - public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) { - if ((isStatementButNotDeclaration(after)) || + public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false) { + const startPosition = getAdjustedStartPosition(sourceFile, before, {}, Position.Start); + return this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween)); + } + + public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { + const pos = before.getStart(sourceFile); + this.replaceWithSingle(sourceFile, pos, pos, createToken(modifier), { suffix: " " }); + } + + public changeIdentifierToPropertyAccess(sourceFile: SourceFile, prefix: string, node: Identifier): void { + const startPosition = getAdjustedStartPosition(sourceFile, node, {}, Position.Start); + this.replaceWithSingle(sourceFile, startPosition, startPosition, createPropertyAccess(createIdentifier(prefix), ""), {}); + } + + private getOptionsForInsertNodeBefore(before: Node, doubleNewlines: boolean): ChangeNodeOptions { + if (isStatement(before) || isClassElement(before)) { + return { suffix: doubleNewlines ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter }; + } + else if (isVariableDeclaration(before)) { // insert `x = 1, ` into `const x = 1, y = 2; + return { suffix: ", " }; + } + throw Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it + } + + public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const firstStatement = firstOrUndefined(ctr.body.statements); + if (!firstStatement || !ctr.body.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [newStatement, ...ctr.body.statements]); + } + else { + this.insertNodeBefore(sourceFile, firstStatement, newStatement); + } + } + + public insertNodeAtConstructorEnd(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const lastStatement = lastOrUndefined(ctr.body.statements); + if (!lastStatement || !ctr.body.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [...ctr.body.statements, newStatement]); + } + else { + this.insertNodeAfter(sourceFile, lastStatement, newStatement); + } + } + + private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: ReadonlyArray): void { + this.replaceNode(sourceFile, ctr.body, createBlock(statements, /*multiLine*/ true), { useNonAdjustedEndPosition: true }); + } + + public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { + const startPosition = getAdjustedStartPosition(sourceFile, scope.getLastToken(), {}, Position.Start); + this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, { + prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken().pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, + suffix: this.newLineCharacter + }); + } + + public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement): void { + const firstMember = firstOrUndefined(cls.members); + if (!firstMember) { + const id = getNodeId(cls).toString(); + const newMembers = this.nodesInsertedAtClassStarts.get(id); + if (newMembers) { + Debug.assert(newMembers.sourceFile === sourceFile && newMembers.cls === cls); + newMembers.members.push(newElement); + } + else { + this.nodesInsertedAtClassStarts.set(id, { sourceFile, cls, members: [newElement] }); + } + } + else { + this.insertNodeBefore(sourceFile, firstMember, newElement); + } + } + + public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): this { + if (isStatementButNotDeclaration(after) || after.kind === SyntaxKind.PropertyDeclaration || after.kind === SyntaxKind.PropertySignature || after.kind === SyntaxKind.MethodSignature) { @@ -354,8 +432,21 @@ namespace ts.textChanges { }); } } - const endPosition = getAdjustedEndPosition(sourceFile, after, options); - return this.replaceWithSingle(sourceFile, endPosition, endPosition, newNode, options); + const endPosition = getAdjustedEndPosition(sourceFile, after, {}); + return this.replaceWithSingle(sourceFile, endPosition, endPosition, newNode, this.getInsertNodeAfterOptions(after)); + } + + private getInsertNodeAfterOptions(node: Node): InsertNodeOptions { + if (isClassDeclaration(node) || isModuleDeclaration(node)) { + return { prefix: this.newLineCharacter, suffix: this.newLineCharacter }; + } + else if (isStatement(node) || isClassElement(node) || isTypeElement(node)) { + return { suffix: this.newLineCharacter }; + } + else if (isVariableDeclaration(node)) { + return { prefix: ", " }; + } + throw Debug.failBadSyntaxKind(node); // We haven't handled this kind of node yet -- add it } /** @@ -502,7 +593,18 @@ namespace ts.textChanges { return this; } + private finishInsertNodeAtClassStart(): void { + this.nodesInsertedAtClassStarts.forEach(({ sourceFile, cls, members }) => { + const newCls = cls.kind === SyntaxKind.ClassDeclaration + ? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members) + : updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members); + this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true }); + }); + } + public getChanges(): FileTextChanges[] { + this.finishInsertNodeAtClassStart(); + const changesPerFile = createMap(); // group changes per file for (const c of this.changes) { @@ -751,4 +853,46 @@ namespace ts.textChanges { this.lastNonTriviaPosition = 0; } } + + function getInsertionPositionAtSourceFileTop({ text }: SourceFile): number { + const shebang = getShebang(text); + let position = 0; + if (shebang !== undefined) { + position = shebang.length; + advancePastLineBreak(); + } + + // For a source file, it is possible there are detached comments we should not skip + let ranges = getLeadingCommentRanges(text, position); + if (!ranges) return position; + // However we should still skip a pinned comment at the top + if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) { + position = ranges[0].end; + advancePastLineBreak(); + ranges = ranges.slice(1); + } + // As well as any triple slash references + for (const range of ranges) { + if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(text, range.pos, range.end)) { + position = range.end; + advancePastLineBreak(); + continue; + } + break; + } + return position; + + function advancePastLineBreak() { + if (position < text.length) { + const charCode = text.charCodeAt(position); + if (isLineBreak(charCode)) { + position++; + + if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { + position++; + } + } + } + } + } } diff --git a/src/services/types.ts b/src/services/types.ts index 7224fdbdda8..597709a7c79 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -181,6 +181,7 @@ namespace ts { */ readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + realpath?(path: string): string; fileExists?(path: string): boolean; /* @@ -294,7 +295,11 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + // TODO: GH#20538 return `ReadonlyArray` + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + // TODO: GH#20538 + /* @internal */ + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -322,8 +327,13 @@ namespace ts { dispose(): void; } + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeFixScope { type: "file"; fileName: string; } + export interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; + includeInsertTextCompletions: boolean; } export interface ApplyCodeActionCommandResult { @@ -409,6 +419,23 @@ namespace ts { commands?: CodeActionCommand[]; } + // TODO: GH#20538 + /* @internal */ + export interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + + // TODO: GH#20538 + /* @internal */ + export interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; + } + // Publicly, this type is just `{}`. Internally it is a union of all the actions we use. // See `commands?: {}[]` in protocol.ts export type CodeActionCommand = InstallPackageAction; @@ -571,6 +598,7 @@ namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface FormatCodeSettings extends EditorSettings { @@ -589,6 +617,7 @@ namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface DefinitionInfo { @@ -720,6 +749,7 @@ namespace ts { kind: ScriptElementKind; kindModifiers: string; // see ScriptElementKindModifier, comma separated sortText: string; + insertText?: string; /** * An optional span that indicates the text to be replaced by this completion item. * If present, this span should be used instead of the default one. @@ -928,6 +958,7 @@ namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional" } export const enum ClassificationTypeNames { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e76fcd93492..8fe12d29313 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -445,7 +445,7 @@ namespace ts { return position < candidate.end || !isCompletedNode(candidate, sourceFile); } - export function isCompletedNode(n: Node, sourceFile: SourceFile): boolean { + function isCompletedNode(n: Node, sourceFile: SourceFile): boolean { if (nodeIsMissing(n)) { return false; } @@ -512,7 +512,7 @@ namespace ts { case SyntaxKind.ExpressionStatement: return isCompletedNode((n).expression, sourceFile) || - hasChildOfKind(n, SyntaxKind.SemicolonToken); + hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ArrayBindingPattern: @@ -540,11 +540,9 @@ namespace ts { return isCompletedNode((n).statement, sourceFile); case SyntaxKind.DoStatement: // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; - const hasWhileKeyword = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile); - if (hasWhileKeyword) { - return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); - } - return isCompletedNode((n).statement, sourceFile); + return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) + ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) + : isCompletedNode((n).statement, sourceFile); case SyntaxKind.TypeQuery: return isCompletedNode((n).exprName, sourceFile); @@ -619,12 +617,12 @@ namespace ts { }; } - export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFile): boolean { + export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { return !!findChildOfKind(n, kind, sourceFile); } - export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFileLike): Node | undefined { - return forEach(n.getChildren(sourceFile), c => c.kind === kind && c); + export function findChildOfKind(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { + return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); } export function findContainingList(node: Node): SyntaxList | undefined { @@ -1099,6 +1097,16 @@ namespace ts { return !seen[id] && (seen[id] = true); }; } + + /** Add a value to a set, and return true if it wasn't already present. */ + export function addToSeen(seen: Map, key: string | number): boolean { + key = String(key); + if (seen.has(key)) { + return false; + } + seen.set(key, true); + return true; + } } // Display-part writer helpers @@ -1266,19 +1274,6 @@ namespace ts { }); } - export function getDeclaredName(typeChecker: TypeChecker, symbol: Symbol, location: Node): string { - // If this is an export or import specifier it could have been renamed using the 'as' syntax. - // If so we want to search for whatever is under the cursor. - if (isImportOrExportSpecifierName(location) || isStringOrNumericLiteral(location) && location.parent.kind === SyntaxKind.ComputedPropertyName) { - return getTextOfIdentifierOrLiteral(location); - } - - // Try to get the local symbol if we're dealing with an 'export default' - // since that symbol has the "true" name. - const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol); - return typeChecker.symbolToString(localExportDefaultSymbol || symbol); - } - export function isImportOrExportSpecifierName(location: Node): location is Identifier { return location.parent && (location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) && @@ -1292,12 +1287,16 @@ namespace ts { */ export function stripQuotes(name: string) { const length = name.length; - if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isSingleOrDoubleQuote(name.charCodeAt(0))) { + if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && startsWithQuote(name)) { return name.substring(1, length - 1); } return name; } + export function startsWithQuote(name: string): boolean { + return isSingleOrDoubleQuote(name.charCodeAt(0)); + } + export function scriptKindIs(fileName: string, host: LanguageServiceHost, ...scriptKinds: ScriptKind[]): boolean { const scriptKind = getScriptKind(fileName, host); return forEach(scriptKinds, k => k === scriptKind); @@ -1320,57 +1319,6 @@ namespace ts { return position; } - export function getOpenBrace(constructor: ConstructorDeclaration, sourceFile: SourceFile) { - // First token is the open curly, this is where we want to put the 'super' call. - return constructor.body.getFirstToken(sourceFile); - } - - export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { - return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false); - } - - export function getSourceFileImportLocation({ text }: SourceFile) { - const shebang = getShebang(text); - let position = 0; - if (shebang !== undefined) { - position = shebang.length; - advancePastLineBreak(); - } - - // For a source file, it is possible there are detached comments we should not skip - let ranges = getLeadingCommentRanges(text, position); - if (!ranges) return position; - // However we should still skip a pinned comment at the top - if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) { - position = ranges[0].end; - advancePastLineBreak(); - ranges = ranges.slice(1); - } - // As well as any triple slash references - for (const range of ranges) { - if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(text, range.pos, range.end)) { - position = range.end; - advancePastLineBreak(); - continue; - } - break; - } - return position; - - function advancePastLineBreak() { - if (position < text.length) { - const charCode = text.charCodeAt(position); - if (isLineBreak(charCode)) { - position++; - - if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { - position++; - } - } - } - } - } - /** * Creates a deep, memberwise clone of a node with no source map location. * @@ -1406,6 +1354,10 @@ namespace ts { return visited; } + export function getSynthesizedDeepClones(nodes: NodeArray | undefined): NodeArray | undefined { + return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone), nodes.hasTrailingComma); + } + /** * Sets EmitFlags to suppress leading and trailing trivia on the node. */ diff --git a/tests/baselines/reference/ES5For-of31.types b/tests/baselines/reference/ES5For-of31.types index ee12f3894a8..309c8d1cab8 100644 --- a/tests/baselines/reference/ES5For-of31.types +++ b/tests/baselines/reference/ES5For-of31.types @@ -5,11 +5,11 @@ var a: string, b: number; for ({ a: b = 1, b: a = ""} of []) { >{ a: b = 1, b: a = ""} : { a?: number; b?: string; } ->a : undefined +>a : number >b = 1 : 1 >b : number >1 : 1 ->b : undefined +>b : string >a = "" : "" >a : string >"" : "" diff --git a/tests/baselines/reference/abstractPropertyNegative.errors.txt b/tests/baselines/reference/abstractPropertyNegative.errors.txt index 314ab842ded..be1c85374fb 100644 --- a/tests/baselines/reference/abstractPropertyNegative.errors.txt +++ b/tests/baselines/reference/abstractPropertyNegative.errors.txt @@ -7,15 +7,12 @@ tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstra tests/cases/compiler/abstractPropertyNegative.ts(15,5): error TS1244: Abstract methods can only appear within an abstract class. tests/cases/compiler/abstractPropertyNegative.ts(16,37): error TS1005: '{' expected. tests/cases/compiler/abstractPropertyNegative.ts(19,3): error TS2540: Cannot assign to 'ro' because it is a constant or a read-only property. -tests/cases/compiler/abstractPropertyNegative.ts(24,7): error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'. - Types of property 'num' are incompatible. - Type 'string' is not assignable to type 'number'. -tests/cases/compiler/abstractPropertyNegative.ts(30,7): error TS2415: Class 'WrongTypeAccessorImpl' incorrectly extends base class 'WrongTypeAccessor'. - Types of property 'num' are incompatible. - Type 'string' is not assignable to type 'number'. -tests/cases/compiler/abstractPropertyNegative.ts(33,7): error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'. - Types of property 'num' are incompatible. - Type 'string' is not assignable to type 'number'. +tests/cases/compiler/abstractPropertyNegative.ts(25,5): error TS2416: Property 'num' in type 'WrongTypePropertyImpl' is not assignable to the same property in base type 'WrongTypeProperty'. + Type 'string' is not assignable to type 'number'. +tests/cases/compiler/abstractPropertyNegative.ts(31,9): error TS2416: Property 'num' in type 'WrongTypeAccessorImpl' is not assignable to the same property in base type 'WrongTypeAccessor'. + Type 'string' is not assignable to type 'number'. +tests/cases/compiler/abstractPropertyNegative.ts(34,5): error TS2416: Property 'num' in type 'WrongTypeAccessorImpl2' is not assignable to the same property in base type 'WrongTypeAccessor'. + Type 'string' is not assignable to type 'number'. tests/cases/compiler/abstractPropertyNegative.ts(38,18): error TS2676: Accessors must both be abstract or non-abstract. tests/cases/compiler/abstractPropertyNegative.ts(39,9): error TS2676: Accessors must both be abstract or non-abstract. tests/cases/compiler/abstractPropertyNegative.ts(40,9): error TS2676: Accessors must both be abstract or non-abstract. @@ -65,28 +62,25 @@ tests/cases/compiler/abstractPropertyNegative.ts(41,18): error TS2676: Accessors abstract num: number; } class WrongTypePropertyImpl extends WrongTypeProperty { - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'. -!!! error TS2415: Types of property 'num' are incompatible. -!!! error TS2415: Type 'string' is not assignable to type 'number'. num = "nope, wrong"; + ~~~ +!!! error TS2416: Property 'num' in type 'WrongTypePropertyImpl' is not assignable to the same property in base type 'WrongTypeProperty'. +!!! error TS2416: Type 'string' is not assignable to type 'number'. } abstract class WrongTypeAccessor { abstract get num(): number; } class WrongTypeAccessorImpl extends WrongTypeAccessor { - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2415: Class 'WrongTypeAccessorImpl' incorrectly extends base class 'WrongTypeAccessor'. -!!! error TS2415: Types of property 'num' are incompatible. -!!! error TS2415: Type 'string' is not assignable to type 'number'. get num() { return "nope, wrong"; } + ~~~ +!!! error TS2416: Property 'num' in type 'WrongTypeAccessorImpl' is not assignable to the same property in base type 'WrongTypeAccessor'. +!!! error TS2416: Type 'string' is not assignable to type 'number'. } class WrongTypeAccessorImpl2 extends WrongTypeAccessor { - ~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'. -!!! error TS2415: Types of property 'num' are incompatible. -!!! error TS2415: Type 'string' is not assignable to type 'number'. num = "nope, wrong"; + ~~~ +!!! error TS2416: Property 'num' in type 'WrongTypeAccessorImpl2' is not assignable to the same property in base type 'WrongTypeAccessor'. +!!! error TS2416: Type 'string' is not assignable to type 'number'. } abstract class AbstractAccessorMismatch { diff --git a/tests/baselines/reference/allowSyntheticDefaultImports1.js b/tests/baselines/reference/allowSyntheticDefaultImports1.js index fee1d137706..8c5565a1fb7 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports1.js +++ b/tests/baselines/reference/allowSyntheticDefaultImports1.js @@ -4,21 +4,12 @@ import Namespace from "./b"; export var x = new Namespace.Foo(); -//// [b.ts] +//// [b.d.ts] export class Foo { member: string; } -//// [b.js] -"use strict"; -exports.__esModule = true; -var Foo = /** @class */ (function () { - function Foo() { - } - return Foo; -}()); -exports.Foo = Foo; //// [a.js] "use strict"; exports.__esModule = true; diff --git a/tests/baselines/reference/allowSyntheticDefaultImports1.symbols b/tests/baselines/reference/allowSyntheticDefaultImports1.symbols index ecbe9e4c205..5ee2812ab59 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports1.symbols +++ b/tests/baselines/reference/allowSyntheticDefaultImports1.symbols @@ -4,15 +4,15 @@ import Namespace from "./b"; export var x = new Namespace.Foo(); >x : Symbol(x, Decl(a.ts, 1, 10)) ->Namespace.Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0)) +>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0)) >Namespace : Symbol(Namespace, Decl(a.ts, 0, 6)) ->Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0)) +>Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0)) -=== tests/cases/compiler/b.ts === +=== tests/cases/compiler/b.d.ts === export class Foo { ->Foo : Symbol(Foo, Decl(b.ts, 0, 0)) +>Foo : Symbol(Foo, Decl(b.d.ts, 0, 0)) member: string; ->member : Symbol(Foo.member, Decl(b.ts, 0, 18)) +>member : Symbol(Foo.member, Decl(b.d.ts, 0, 18)) } diff --git a/tests/baselines/reference/allowSyntheticDefaultImports1.types b/tests/baselines/reference/allowSyntheticDefaultImports1.types index c2265d7611b..c8092331a47 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports1.types +++ b/tests/baselines/reference/allowSyntheticDefaultImports1.types @@ -9,7 +9,7 @@ export var x = new Namespace.Foo(); >Namespace : typeof Namespace >Foo : typeof Namespace.Foo -=== tests/cases/compiler/b.ts === +=== tests/cases/compiler/b.d.ts === export class Foo { >Foo : Foo diff --git a/tests/baselines/reference/allowSyntheticDefaultImports2.js b/tests/baselines/reference/allowSyntheticDefaultImports2.js index 5dc8472d58b..c1e43a4fc81 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports2.js +++ b/tests/baselines/reference/allowSyntheticDefaultImports2.js @@ -4,28 +4,11 @@ import Namespace from "./b"; export var x = new Namespace.Foo(); -//// [b.ts] +//// [b.d.ts] export class Foo { member: string; } -//// [b.js] -System.register([], function (exports_1, context_1) { - "use strict"; - var __moduleName = context_1 && context_1.id; - var Foo; - return { - setters: [], - execute: function () { - Foo = /** @class */ (function () { - function Foo() { - } - return Foo; - }()); - exports_1("Foo", Foo); - } - }; -}); //// [a.js] System.register(["./b"], function (exports_1, context_1) { "use strict"; diff --git a/tests/baselines/reference/allowSyntheticDefaultImports2.symbols b/tests/baselines/reference/allowSyntheticDefaultImports2.symbols index 615b1095c66..a2b33c94d82 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports2.symbols +++ b/tests/baselines/reference/allowSyntheticDefaultImports2.symbols @@ -4,14 +4,14 @@ import Namespace from "./b"; export var x = new Namespace.Foo(); >x : Symbol(x, Decl(a.ts, 1, 10)) ->Namespace.Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0)) +>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0)) >Namespace : Symbol(Namespace, Decl(a.ts, 0, 6)) ->Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0)) +>Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0)) -=== tests/cases/compiler/b.ts === +=== tests/cases/compiler/b.d.ts === export class Foo { ->Foo : Symbol(Foo, Decl(b.ts, 0, 0)) +>Foo : Symbol(Foo, Decl(b.d.ts, 0, 0)) member: string; ->member : Symbol(Foo.member, Decl(b.ts, 0, 18)) +>member : Symbol(Foo.member, Decl(b.d.ts, 0, 18)) } diff --git a/tests/baselines/reference/allowSyntheticDefaultImports2.types b/tests/baselines/reference/allowSyntheticDefaultImports2.types index c40fbc7e988..420c19b7c1d 100644 --- a/tests/baselines/reference/allowSyntheticDefaultImports2.types +++ b/tests/baselines/reference/allowSyntheticDefaultImports2.types @@ -9,7 +9,7 @@ export var x = new Namespace.Foo(); >Namespace : typeof Namespace >Foo : typeof Namespace.Foo -=== tests/cases/compiler/b.ts === +=== tests/cases/compiler/b.d.ts === export class Foo { >Foo : Foo diff --git a/tests/baselines/reference/anyMappedTypesError.errors.txt b/tests/baselines/reference/anyMappedTypesError.errors.txt new file mode 100644 index 00000000000..e1442c15723 --- /dev/null +++ b/tests/baselines/reference/anyMappedTypesError.errors.txt @@ -0,0 +1,7 @@ +tests/cases/compiler/anyMappedTypesError.ts(1,12): error TS7039: Mapped object type implicitly has an 'any' template type. + + +==== tests/cases/compiler/anyMappedTypesError.ts (1 errors) ==== + type Foo = {[P in "bar"]}; + ~~~~~~~~~~~~~~ +!!! error TS7039: Mapped object type implicitly has an 'any' template type. \ No newline at end of file diff --git a/tests/baselines/reference/anyMappedTypesError.js b/tests/baselines/reference/anyMappedTypesError.js new file mode 100644 index 00000000000..8797d2cfc0e --- /dev/null +++ b/tests/baselines/reference/anyMappedTypesError.js @@ -0,0 +1,4 @@ +//// [anyMappedTypesError.ts] +type Foo = {[P in "bar"]}; + +//// [anyMappedTypesError.js] diff --git a/tests/baselines/reference/anyMappedTypesError.symbols b/tests/baselines/reference/anyMappedTypesError.symbols new file mode 100644 index 00000000000..0e9a425aad3 --- /dev/null +++ b/tests/baselines/reference/anyMappedTypesError.symbols @@ -0,0 +1,5 @@ +=== tests/cases/compiler/anyMappedTypesError.ts === +type Foo = {[P in "bar"]}; +>Foo : Symbol(Foo, Decl(anyMappedTypesError.ts, 0, 0)) +>P : Symbol(P, Decl(anyMappedTypesError.ts, 0, 13)) + diff --git a/tests/baselines/reference/anyMappedTypesError.types b/tests/baselines/reference/anyMappedTypesError.types new file mode 100644 index 00000000000..290ea6883b1 --- /dev/null +++ b/tests/baselines/reference/anyMappedTypesError.types @@ -0,0 +1,5 @@ +=== tests/cases/compiler/anyMappedTypesError.ts === +type Foo = {[P in "bar"]}; +>Foo : Foo +>P : P + diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c127232c099..3eabea4435a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -59,6 +59,7 @@ declare namespace ts { pos: number; end: number; } + type JsDocSyntaxKind = SyntaxKind.EndOfFileToken | SyntaxKind.WhitespaceTrivia | SyntaxKind.AtToken | SyntaxKind.NewLineTrivia | SyntaxKind.AsteriskToken | SyntaxKind.OpenBraceToken | SyntaxKind.CloseBraceToken | SyntaxKind.LessThanToken | SyntaxKind.OpenBracketToken | SyntaxKind.CloseBracketToken | SyntaxKind.EqualsToken | SyntaxKind.CommaToken | SyntaxKind.DotToken | SyntaxKind.Identifier | SyntaxKind.Unknown; enum SyntaxKind { Unknown = 0, EndOfFileToken = 1, @@ -453,6 +454,9 @@ declare namespace ts { interface JSDocContainer { } type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; + type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; + type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; interface NodeArray extends ReadonlyArray, TextRange { hasTrailingComma?: boolean; } @@ -604,15 +608,7 @@ declare namespace ts { kind: SyntaxKind.SpreadAssignment; expression: Expression; } - interface VariableLikeDeclaration extends NamedDeclaration { - propertyName?: PropertyName; - dotDotDotToken?: DotDotDotToken; - name: DeclarationName; - questionToken?: QuestionToken; - exclamationToken?: ExclamationToken; - type?: TypeNode; - initializer?: Expression; - } + type VariableLikeDeclaration = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyDeclaration | PropertyAssignment | PropertySignature | JsxAttribute | ShorthandPropertyAssignment | EnumMember | JSDocPropertyTag | JSDocParameterTag; interface PropertyLikeDeclaration extends NamedDeclaration { name: PropertyName; } @@ -660,30 +656,30 @@ declare namespace ts { } interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -905,6 +901,7 @@ declare namespace ts { kind: SyntaxKind.ArrowFunction; equalsGreaterThanToken: EqualsGreaterThanToken; body: ConciseBody; + name: never; } interface LiteralLikeNode extends Node { text: string; @@ -1279,7 +1276,7 @@ declare namespace ts { } interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } @@ -1366,6 +1363,7 @@ declare namespace ts { interface ExportDeclaration extends DeclarationStatement { kind: SyntaxKind.ExportDeclaration; parent?: SourceFile | ModuleBlock; + /** Will not be assigned in the case of `export * from "foo";` */ exportClause?: NamedExports; /** If this is not a StringLiteral it will be a grammar error. */ moduleSpecifier?: Expression; @@ -1620,7 +1618,7 @@ declare namespace ts { } interface ParseConfigHost { useCaseSensitiveFileNames: boolean; - readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, depth?: number): string[]; /** * Gets a value indicating whether the specified path exists and is a file. * @param path The path to test. @@ -2071,6 +2069,7 @@ declare namespace ts { EvolvingArray = 256, ObjectLiteralPatternWithComputedProperties = 512, ContainsSpread = 1024, + ReverseMapped = 2048, ClassOrInterface = 3, } interface ObjectType extends Type { @@ -2299,6 +2298,7 @@ declare namespace ts { types?: string[]; /** Paths used to compute primary types search locations */ typeRoots?: string[]; + esModuleInterop?: boolean; [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; } interface TypeAcquisition { @@ -2472,11 +2472,11 @@ declare namespace ts { getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): (ResolvedModule | undefined)[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): (ResolvedTypeReferenceDirective | undefined)[]; getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { @@ -2916,7 +2916,7 @@ declare namespace ts { function isStringLiteral(node: Node): node is StringLiteral; function isJsxText(node: Node): node is JsxText; function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; - function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral; function isTemplateHead(node: Node): node is TemplateHead; function isTemplateMiddle(node: Node): node is TemplateMiddle; function isTemplateTail(node: Node): node is TemplateTail; @@ -3108,6 +3108,7 @@ declare namespace ts { function isJSDocCommentContainingNode(node: Node): boolean; function isSetAccessor(node: Node): node is SetAccessorDeclaration; function isGetAccessor(node: Node): node is GetAccessorDeclaration; + function isObjectLiteralElement(node: Node): node is ObjectLiteralElement; } declare namespace ts { type ErrorCallback = (message: DiagnosticMessage, length: number) => void; @@ -3130,7 +3131,7 @@ declare namespace ts { scanJsxAttributeValue(): SyntaxKind; reScanJsxToken(): SyntaxKind; scanJsxToken(): SyntaxKind; - scanJSDocToken(): SyntaxKind; + scanJSDocToken(): JsDocSyntaxKind; scan(): SyntaxKind; getText(): string; setText(text: string, start?: number, length?: number): void; @@ -3289,7 +3290,7 @@ declare namespace ts { declare namespace ts { function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ - function createLiteral(value: string | StringLiteral | NumericLiteral | Identifier): StringLiteral; + function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; function createLiteral(value: number): NumericLiteral; function createLiteral(value: boolean): BooleanLiteral; function createLiteral(value: string | number | boolean): PrimaryExpression; @@ -3775,7 +3776,7 @@ declare namespace ts { } } declare namespace ts { - function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined; function resolveTripleslashReference(moduleName: string, containingFile: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; @@ -3925,6 +3926,7 @@ declare namespace ts { useCaseSensitiveFileNames?(): boolean; readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -3983,7 +3985,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4001,6 +4003,7 @@ declare namespace ts { } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; + includeInsertTextCompletions: boolean; } interface ApplyCodeActionCommandResult { successMessage: string; @@ -4211,6 +4214,7 @@ declare namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface FormatCodeSettings extends EditorSettings { insertSpaceAfterCommaDelimiter?: boolean; @@ -4228,6 +4232,7 @@ declare namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface DefinitionInfo { fileName: string; @@ -4343,6 +4348,7 @@ declare namespace ts { kind: ScriptElementKind; kindModifiers: string; sortText: string; + insertText?: string; /** * An optional span that indicates the text to be replaced by this completion item. * If present, this span should be used instead of the default one. @@ -4509,6 +4515,7 @@ declare namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional", } enum ClassificationTypeNames { comment = "comment", @@ -4904,6 +4911,7 @@ declare namespace ts.server.protocol { Rename = "rename", Saveto = "saveto", SignatureHelp = "signatureHelp", + Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", ReloadProjects = "reloadProjects", @@ -5005,6 +5013,21 @@ declare namespace ts.server.protocol { file: string; projectFileName?: string; } + interface StatusRequest extends Request { + command: CommandTypes.Status; + } + interface StatusResponseBody { + /** + * The TypeScript version (`ts.version`). + */ + version: string; + } + /** + * Response to StatusRequest + */ + interface StatusResponse extends Response { + body: StatusResponseBody; + } /** * Requests a JS Doc comment template for a given position */ @@ -5294,7 +5317,7 @@ declare namespace ts.server.protocol { /** * Errorcodes we want to get the fixes for. */ - errorCodes?: number[]; + errorCodes?: ReadonlyArray; } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ @@ -6089,6 +6112,11 @@ declare namespace ts.server.protocol { * This affects lone identifier completions but not completions on the right hand side of `obj.`. */ includeExternalModuleExports: boolean; + /** + * If enabled, the completion list will include completions with invalid identifier names. + * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. + */ + includeInsertTextCompletions: boolean; } /** * Completions request; value of command field is "completions". @@ -6157,6 +6185,12 @@ declare namespace ts.server.protocol { * is often the same as the name but may be different in certain circumstances. */ sortText: string; + /** + * Text to insert instead of `name`. + * This is used to support bracketed completions; If `name` might be "a-b" but `insertText` would be `["a-b"]`, + * coupled with `replacementSpan` to replace a dotted access with a bracket access. + */ + insertText?: string; /** * An optional span that indicates the text to be replaced by this completion item. * If present, this span should be used instead of the default one. @@ -6830,6 +6864,7 @@ declare namespace ts.server.protocol { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface CompilerOptions { allowJs?: boolean; @@ -7072,10 +7107,12 @@ declare namespace ts.server { private getApplicableRefactors(args); private getEditsForRefactor(args, simplifiedResult); private getCodeFixes(args, simplifiedResult); - private applyCodeActionCommand(commandName, requestSeq, args); + private getCombinedCodeFix({scope, fixId}, simplifiedResult); + private applyCodeActionCommand(args); private getStartAndEndPosition(args, scriptInfo); - private mapCodeAction({description, changes: unmappedChanges, commands}, scriptInfo); + private mapCodeAction(project, {description, changes: unmappedChanges, commands}); private mapTextChangesToCodeEdits(project, textChanges); + private mapTextChangesToCodeEditsUsingScriptinfo(textChanges, scriptInfo); private convertTextChangeToCodeEdit(change, scriptInfo); private getBraceMatching(args, simplifiedResult); private getDiagnosticsForProject(next, delay, fileName); @@ -7216,6 +7253,7 @@ declare namespace ts.server { private program; private externalFiles; private missingFilesMap; + private plugins; private cachedUnresolvedImportsPerFile; private lastCachedUnresolvedImportsList; protected languageService: LanguageService; @@ -7294,6 +7332,7 @@ declare namespace ts.server { disableLanguageService(): void; getProjectName(): string; abstract getTypeAcquisition(): TypeAcquisition; + protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition; getExternalFiles(): SortedReadonlyArray; getSourceFile(path: Path): SourceFile; close(): void; @@ -7314,12 +7353,12 @@ declare namespace ts.server { removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean): void; registerFileUpdate(fileName: string): void; markAsDirty(): void; - private extractUnresolvedImportsFromSourceFile(file, result); /** * Updates set of files that contribute to this project * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean; + protected removeExistingTypings(include: string[]): string[]; private setTypings(typings); private updateGraphWorker(); private detachScriptInfoFromProject(uncheckedFileName); @@ -7330,6 +7369,9 @@ declare namespace ts.server { filesToString(writeProjectFileNames: boolean): string; setCompilerOptions(compilerOptions: CompilerOptions): void; protected removeRoot(info: ScriptInfo): void; + protected enableGlobalPlugins(): void; + protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]): void; + private enableProxy(pluginModuleFactory, configEntry); } /** * If a file is opened and no tsconfig (or jsconfig) is found, @@ -7358,7 +7400,6 @@ declare namespace ts.server { private typeAcquisition; private directoriesWatchedForWildcards; readonly canonicalConfigFilePath: NormalizedPath; - private plugins; /** Ref count to the project when opened from external project */ private externalProjectRefCount; private projectErrors; @@ -7369,8 +7410,6 @@ declare namespace ts.server { updateGraph(): boolean; getConfigFilePath(): NormalizedPath; enablePlugins(): void; - private enablePlugin(pluginConfigEntry, searchPaths); - private enableProxy(pluginModuleFactory, configEntry); /** * Get the errors that dont have any file name associated */ @@ -7382,7 +7421,6 @@ declare namespace ts.server { setProjectErrors(projectErrors: Diagnostic[]): void; setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; getTypeAcquisition(): TypeAcquisition; - getExternalFiles(): SortedReadonlyArray; close(): void; getEffectiveTypeRoots(): string[]; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 62a3880827c..630b7a08a28 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -59,6 +59,7 @@ declare namespace ts { pos: number; end: number; } + type JsDocSyntaxKind = SyntaxKind.EndOfFileToken | SyntaxKind.WhitespaceTrivia | SyntaxKind.AtToken | SyntaxKind.NewLineTrivia | SyntaxKind.AsteriskToken | SyntaxKind.OpenBraceToken | SyntaxKind.CloseBraceToken | SyntaxKind.LessThanToken | SyntaxKind.OpenBracketToken | SyntaxKind.CloseBracketToken | SyntaxKind.EqualsToken | SyntaxKind.CommaToken | SyntaxKind.DotToken | SyntaxKind.Identifier | SyntaxKind.Unknown; enum SyntaxKind { Unknown = 0, EndOfFileToken = 1, @@ -453,6 +454,9 @@ declare namespace ts { interface JSDocContainer { } type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; + type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; + type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; interface NodeArray extends ReadonlyArray, TextRange { hasTrailingComma?: boolean; } @@ -604,15 +608,7 @@ declare namespace ts { kind: SyntaxKind.SpreadAssignment; expression: Expression; } - interface VariableLikeDeclaration extends NamedDeclaration { - propertyName?: PropertyName; - dotDotDotToken?: DotDotDotToken; - name: DeclarationName; - questionToken?: QuestionToken; - exclamationToken?: ExclamationToken; - type?: TypeNode; - initializer?: Expression; - } + type VariableLikeDeclaration = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyDeclaration | PropertyAssignment | PropertySignature | JsxAttribute | ShorthandPropertyAssignment | EnumMember | JSDocPropertyTag | JSDocParameterTag; interface PropertyLikeDeclaration extends NamedDeclaration { name: PropertyName; } @@ -660,30 +656,30 @@ declare namespace ts { } interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; body?: FunctionBody; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; - parent?: ClassDeclaration | ClassExpression; + parent?: ClassLikeDeclaration; } interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; - parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + parent?: ClassLikeDeclaration | ObjectLiteralExpression; name: PropertyName; body?: FunctionBody; } type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -905,6 +901,7 @@ declare namespace ts { kind: SyntaxKind.ArrowFunction; equalsGreaterThanToken: EqualsGreaterThanToken; body: ConciseBody; + name: never; } interface LiteralLikeNode extends Node { text: string; @@ -1279,7 +1276,7 @@ declare namespace ts { } interface HeritageClause extends Node { kind: SyntaxKind.HeritageClause; - parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + parent?: InterfaceDeclaration | ClassLikeDeclaration; token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; types: NodeArray; } @@ -1366,6 +1363,7 @@ declare namespace ts { interface ExportDeclaration extends DeclarationStatement { kind: SyntaxKind.ExportDeclaration; parent?: SourceFile | ModuleBlock; + /** Will not be assigned in the case of `export * from "foo";` */ exportClause?: NamedExports; /** If this is not a StringLiteral it will be a grammar error. */ moduleSpecifier?: Expression; @@ -1620,7 +1618,7 @@ declare namespace ts { } interface ParseConfigHost { useCaseSensitiveFileNames: boolean; - readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, depth?: number): string[]; /** * Gets a value indicating whether the specified path exists and is a file. * @param path The path to test. @@ -2071,6 +2069,7 @@ declare namespace ts { EvolvingArray = 256, ObjectLiteralPatternWithComputedProperties = 512, ContainsSpread = 1024, + ReverseMapped = 2048, ClassOrInterface = 3, } interface ObjectType extends Type { @@ -2299,6 +2298,7 @@ declare namespace ts { types?: string[]; /** Paths used to compute primary types search locations */ typeRoots?: string[]; + esModuleInterop?: boolean; [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; } interface TypeAcquisition { @@ -2472,11 +2472,11 @@ declare namespace ts { getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): (ResolvedModule | undefined)[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): (ResolvedTypeReferenceDirective | undefined)[]; getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { @@ -2792,7 +2792,7 @@ declare namespace ts { scanJsxAttributeValue(): SyntaxKind; reScanJsxToken(): SyntaxKind; scanJsxToken(): SyntaxKind; - scanJSDocToken(): SyntaxKind; + scanJSDocToken(): JsDocSyntaxKind; scan(): SyntaxKind; getText(): string; setText(text: string, start?: number, length?: number): void; @@ -2969,7 +2969,7 @@ declare namespace ts { function isStringLiteral(node: Node): node is StringLiteral; function isJsxText(node: Node): node is JsxText; function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; - function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral; function isTemplateHead(node: Node): node is TemplateHead; function isTemplateMiddle(node: Node): node is TemplateMiddle; function isTemplateTail(node: Node): node is TemplateTail; @@ -3161,6 +3161,7 @@ declare namespace ts { function isJSDocCommentContainingNode(node: Node): boolean; function isSetAccessor(node: Node): node is SetAccessorDeclaration; function isGetAccessor(node: Node): node is GetAccessorDeclaration; + function isObjectLiteralElement(node: Node): node is ObjectLiteralElement; } declare namespace ts { function createNode(kind: SyntaxKind, pos?: number, end?: number): Node; @@ -3236,7 +3237,7 @@ declare namespace ts { declare namespace ts { function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ - function createLiteral(value: string | StringLiteral | NumericLiteral | Identifier): StringLiteral; + function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; function createLiteral(value: number): NumericLiteral; function createLiteral(value: boolean): BooleanLiteral; function createLiteral(value: string | number | boolean): PrimaryExpression; @@ -3722,7 +3723,7 @@ declare namespace ts { } } declare namespace ts { - function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined; function resolveTripleslashReference(moduleName: string, containingFile: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; @@ -3925,6 +3926,7 @@ declare namespace ts { useCaseSensitiveFileNames?(): boolean; readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -3983,7 +3985,7 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4001,6 +4003,7 @@ declare namespace ts { } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; + includeInsertTextCompletions: boolean; } interface ApplyCodeActionCommandResult { successMessage: string; @@ -4211,6 +4214,7 @@ declare namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface FormatCodeSettings extends EditorSettings { insertSpaceAfterCommaDelimiter?: boolean; @@ -4228,6 +4232,7 @@ declare namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface DefinitionInfo { fileName: string; @@ -4343,6 +4348,7 @@ declare namespace ts { kind: ScriptElementKind; kindModifiers: string; sortText: string; + insertText?: string; /** * An optional span that indicates the text to be replaced by this completion item. * If present, this span should be used instead of the default one. @@ -4509,6 +4515,7 @@ declare namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional", } enum ClassificationTypeNames { comment = "comment", diff --git a/tests/baselines/reference/apparentTypeSubtyping.errors.txt b/tests/baselines/reference/apparentTypeSubtyping.errors.txt index 01fffeb3824..ca2f0765d62 100644 --- a/tests/baselines/reference/apparentTypeSubtyping.errors.txt +++ b/tests/baselines/reference/apparentTypeSubtyping.errors.txt @@ -1,7 +1,6 @@ -tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSubtyping.ts(9,7): error TS2415: Class 'Derived' incorrectly extends base class 'Base'. - Types of property 'x' are incompatible. - Type 'String' is not assignable to type 'string'. - 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. +tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSubtyping.ts(10,5): error TS2416: Property 'x' in type 'Derived' is not assignable to the same property in base type 'Base'. + Type 'String' is not assignable to type 'string'. + 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. ==== tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSubtyping.ts (1 errors) ==== @@ -14,12 +13,11 @@ tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSubtypi // is String (S) a subtype of U extends String (T)? Would only be true if we used the apparent type of U (T) class Derived extends Base { // error - ~~~~~~~ -!!! error TS2415: Class 'Derived' incorrectly extends base class 'Base'. -!!! error TS2415: Types of property 'x' are incompatible. -!!! error TS2415: Type 'String' is not assignable to type 'string'. -!!! error TS2415: 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. x: String; + ~ +!!! error TS2416: Property 'x' in type 'Derived' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type 'String' is not assignable to type 'string'. +!!! error TS2416: 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. } class Base2 { diff --git a/tests/baselines/reference/apparentTypeSupertype.errors.txt b/tests/baselines/reference/apparentTypeSupertype.errors.txt index c7610a80f2f..a4a8ccc0a8b 100644 --- a/tests/baselines/reference/apparentTypeSupertype.errors.txt +++ b/tests/baselines/reference/apparentTypeSupertype.errors.txt @@ -1,8 +1,7 @@ -tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSupertype.ts(9,7): error TS2415: Class 'Derived' incorrectly extends base class 'Base'. - Types of property 'x' are incompatible. - Type 'U' is not assignable to type 'string'. - Type 'String' is not assignable to type 'string'. - 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. +tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSupertype.ts(10,5): error TS2416: Property 'x' in type 'Derived' is not assignable to the same property in base type 'Base'. + Type 'U' is not assignable to type 'string'. + Type 'String' is not assignable to type 'string'. + 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. ==== tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSupertype.ts (1 errors) ==== @@ -15,11 +14,10 @@ tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSuperty // is String (S) a subtype of U extends String (T)? Would only be true if we used the apparent type of U (T) class Derived extends Base { // error - ~~~~~~~ -!!! error TS2415: Class 'Derived' incorrectly extends base class 'Base'. -!!! error TS2415: Types of property 'x' are incompatible. -!!! error TS2415: Type 'U' is not assignable to type 'string'. -!!! error TS2415: Type 'String' is not assignable to type 'string'. -!!! error TS2415: 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. x: U; + ~ +!!! error TS2416: Property 'x' in type 'Derived' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type 'U' is not assignable to type 'string'. +!!! error TS2416: Type 'String' is not assignable to type 'string'. +!!! error TS2416: 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. } \ No newline at end of file diff --git a/tests/baselines/reference/arrayConcat2.types b/tests/baselines/reference/arrayConcat2.types index b63f33eb7b1..2e33d4cd023 100644 --- a/tests/baselines/reference/arrayConcat2.types +++ b/tests/baselines/reference/arrayConcat2.types @@ -5,17 +5,17 @@ var a: string[] = []; a.concat("hello", 'world'); >a.concat("hello", 'world') : string[] ->a.concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>a.concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >a : string[] ->concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >"hello" : "hello" >'world' : "world" a.concat('Hello'); >a.concat('Hello') : string[] ->a.concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>a.concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >a : string[] ->concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >'Hello' : "Hello" var b = new Array(); @@ -25,8 +25,8 @@ var b = new Array(); b.concat('hello'); >b.concat('hello') : string[] ->b.concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>b.concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >b : string[] ->concat : { (...items: ReadonlyArray[]): string[]; (...items: (string | ReadonlyArray)[]): string[]; } +>concat : { (...items: (string[] | ReadonlyArray)[]): string[]; (...items: (string | string[] | ReadonlyArray)[]): string[]; } >'hello' : "hello" diff --git a/tests/baselines/reference/arrayConcat3.js b/tests/baselines/reference/arrayConcat3.js new file mode 100644 index 00000000000..90226797706 --- /dev/null +++ b/tests/baselines/reference/arrayConcat3.js @@ -0,0 +1,12 @@ +//// [arrayConcat3.ts] +// TODO: remove lib hack when https://github.com/Microsoft/TypeScript/issues/20454 is fixed +type Fn = (subj: U) => U +function doStuff(a: Array>, b: Array>) { + b.concat(a); +} + + +//// [arrayConcat3.js] +function doStuff(a, b) { + b.concat(a); +} diff --git a/tests/baselines/reference/arrayConcat3.symbols b/tests/baselines/reference/arrayConcat3.symbols new file mode 100644 index 00000000000..373c8d7041b --- /dev/null +++ b/tests/baselines/reference/arrayConcat3.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/arrayConcat3.ts === +// TODO: remove lib hack when https://github.com/Microsoft/TypeScript/issues/20454 is fixed +type Fn = (subj: U) => U +>Fn : Symbol(Fn, Decl(arrayConcat3.ts, 0, 0)) +>T : Symbol(T, Decl(arrayConcat3.ts, 1, 8)) +>U : Symbol(U, Decl(arrayConcat3.ts, 1, 29)) +>T : Symbol(T, Decl(arrayConcat3.ts, 1, 8)) +>subj : Symbol(subj, Decl(arrayConcat3.ts, 1, 42)) +>U : Symbol(U, Decl(arrayConcat3.ts, 1, 29)) +>U : Symbol(U, Decl(arrayConcat3.ts, 1, 29)) + +function doStuff(a: Array>, b: Array>) { +>doStuff : Symbol(doStuff, Decl(arrayConcat3.ts, 1, 55)) +>T : Symbol(T, Decl(arrayConcat3.ts, 2, 17)) +>T1 : Symbol(T1, Decl(arrayConcat3.ts, 2, 34)) +>T : Symbol(T, Decl(arrayConcat3.ts, 2, 17)) +>a : Symbol(a, Decl(arrayConcat3.ts, 2, 49)) +>Array : Symbol(Array, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Fn : Symbol(Fn, Decl(arrayConcat3.ts, 0, 0)) +>T : Symbol(T, Decl(arrayConcat3.ts, 2, 17)) +>b : Symbol(b, Decl(arrayConcat3.ts, 2, 65)) +>Array : Symbol(Array, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Fn : Symbol(Fn, Decl(arrayConcat3.ts, 0, 0)) +>T1 : Symbol(T1, Decl(arrayConcat3.ts, 2, 34)) + + b.concat(a); +>b.concat : Symbol(Array.concat, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>b : Symbol(b, Decl(arrayConcat3.ts, 2, 65)) +>concat : Symbol(Array.concat, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(arrayConcat3.ts, 2, 49)) +} + diff --git a/tests/baselines/reference/arrayConcat3.types b/tests/baselines/reference/arrayConcat3.types new file mode 100644 index 00000000000..2bfbe09a176 --- /dev/null +++ b/tests/baselines/reference/arrayConcat3.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/arrayConcat3.ts === +// TODO: remove lib hack when https://github.com/Microsoft/TypeScript/issues/20454 is fixed +type Fn = (subj: U) => U +>Fn : Fn +>T : T +>U : U +>T : T +>subj : U +>U : U +>U : U + +function doStuff(a: Array>, b: Array>) { +>doStuff : (a: Fn[], b: Fn[]) => void +>T : T +>T1 : T1 +>T : T +>a : Fn[] +>Array : T[] +>Fn : Fn +>T : T +>b : Fn[] +>Array : T[] +>Fn : Fn +>T1 : T1 + + b.concat(a); +>b.concat(a) : Fn[] +>b.concat : { (...items: (Fn[] | ReadonlyArray>)[]): Fn[]; (...items: (Fn | Fn[] | ReadonlyArray>)[]): Fn[]; } +>b : Fn[] +>concat : { (...items: (Fn[] | ReadonlyArray>)[]): Fn[]; (...items: (Fn | Fn[] | ReadonlyArray>)[]): Fn[]; } +>a : Fn[] +} + diff --git a/tests/baselines/reference/arrayConcatMap.types b/tests/baselines/reference/arrayConcatMap.types index db5b3774db0..cbb30b8304a 100644 --- a/tests/baselines/reference/arrayConcatMap.types +++ b/tests/baselines/reference/arrayConcatMap.types @@ -4,9 +4,9 @@ var x = [].concat([{ a: 1 }], [{ a: 2 }]) >[].concat([{ a: 1 }], [{ a: 2 }]) .map(b => b.a) : any[] >[].concat([{ a: 1 }], [{ a: 2 }]) .map : (callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any) => U[] >[].concat([{ a: 1 }], [{ a: 2 }]) : any[] ->[].concat : { (...items: ReadonlyArray[]): any[]; (...items: any[]): any[]; } +>[].concat : { (...items: (any[] | ReadonlyArray)[]): any[]; (...items: any[]): any[]; } >[] : undefined[] ->concat : { (...items: ReadonlyArray[]): any[]; (...items: any[]): any[]; } +>concat : { (...items: (any[] | ReadonlyArray)[]): any[]; (...items: any[]): any[]; } >[{ a: 1 }] : { a: number; }[] >{ a: 1 } : { a: number; } >a : number diff --git a/tests/baselines/reference/arrayFrom.errors.txt b/tests/baselines/reference/arrayFrom.errors.txt new file mode 100644 index 00000000000..896125ee1ce --- /dev/null +++ b/tests/baselines/reference/arrayFrom.errors.txt @@ -0,0 +1,46 @@ +tests/cases/compiler/arrayFrom.ts(19,7): error TS2322: Type 'A[]' is not assignable to type 'B[]'. + Type 'A' is not assignable to type 'B'. + Property 'b' is missing in type 'A'. +tests/cases/compiler/arrayFrom.ts(22,7): error TS2322: Type 'A[]' is not assignable to type 'B[]'. + + +==== tests/cases/compiler/arrayFrom.ts (2 errors) ==== + // Tests fix for #20432, ensures Array.from accepts all valid inputs + // Also tests for #19682 + + interface A { + a: string; + } + + interface B { + b: string; + } + + const inputA: A[] = []; + const inputB: B[] = []; + const inputALike: ArrayLike = { length: 0 }; + const inputARand = getEither(inputA, inputALike); + + const result1: A[] = Array.from(inputA); + const result2: A[] = Array.from(inputA.values()); + const result3: B[] = Array.from(inputA.values()); // expect error + ~~~~~~~ +!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. +!!! error TS2322: Type 'A' is not assignable to type 'B'. +!!! error TS2322: Property 'b' is missing in type 'A'. + const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b })); + const result5: A[] = Array.from(inputALike); + const result6: B[] = Array.from(inputALike); // expect error + ~~~~~~~ +!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. + const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a })); + const result8: A[] = Array.from(inputARand); + const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a })); + + // if this is written inline, the compiler seems to infer + // the ?: as always taking the false branch, narrowing to ArrayLike, + // even when the type is written as : Iterable|ArrayLike + function getEither (in1: Iterable, in2: ArrayLike) { + return Math.random() > 0.5 ? in1 : in2; + } + \ No newline at end of file diff --git a/tests/baselines/reference/arrayFrom.js b/tests/baselines/reference/arrayFrom.js new file mode 100644 index 00000000000..b9b7ff75268 --- /dev/null +++ b/tests/baselines/reference/arrayFrom.js @@ -0,0 +1,66 @@ +//// [arrayFrom.ts] +// Tests fix for #20432, ensures Array.from accepts all valid inputs +// Also tests for #19682 + +interface A { + a: string; +} + +interface B { + b: string; +} + +const inputA: A[] = []; +const inputB: B[] = []; +const inputALike: ArrayLike = { length: 0 }; +const inputARand = getEither(inputA, inputALike); + +const result1: A[] = Array.from(inputA); +const result2: A[] = Array.from(inputA.values()); +const result3: B[] = Array.from(inputA.values()); // expect error +const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b })); +const result5: A[] = Array.from(inputALike); +const result6: B[] = Array.from(inputALike); // expect error +const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a })); +const result8: A[] = Array.from(inputARand); +const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a })); + +// if this is written inline, the compiler seems to infer +// the ?: as always taking the false branch, narrowing to ArrayLike, +// even when the type is written as : Iterable|ArrayLike +function getEither (in1: Iterable, in2: ArrayLike) { + return Math.random() > 0.5 ? in1 : in2; +} + + +//// [arrayFrom.js] +// Tests fix for #20432, ensures Array.from accepts all valid inputs +// Also tests for #19682 +var inputA = []; +var inputB = []; +var inputALike = { length: 0 }; +var inputARand = getEither(inputA, inputALike); +var result1 = Array.from(inputA); +var result2 = Array.from(inputA.values()); +var result3 = Array.from(inputA.values()); // expect error +var result4 = Array.from(inputB, function (_a) { + var b = _a.b; + return ({ a: b }); +}); +var result5 = Array.from(inputALike); +var result6 = Array.from(inputALike); // expect error +var result7 = Array.from(inputALike, function (_a) { + var a = _a.a; + return ({ b: a }); +}); +var result8 = Array.from(inputARand); +var result9 = Array.from(inputARand, function (_a) { + var a = _a.a; + return ({ b: a }); +}); +// if this is written inline, the compiler seems to infer +// the ?: as always taking the false branch, narrowing to ArrayLike, +// even when the type is written as : Iterable|ArrayLike +function getEither(in1, in2) { + return Math.random() > 0.5 ? in1 : in2; +} diff --git a/tests/baselines/reference/arrayFrom.symbols b/tests/baselines/reference/arrayFrom.symbols new file mode 100644 index 00000000000..4a68122739f --- /dev/null +++ b/tests/baselines/reference/arrayFrom.symbols @@ -0,0 +1,147 @@ +=== tests/cases/compiler/arrayFrom.ts === +// Tests fix for #20432, ensures Array.from accepts all valid inputs +// Also tests for #19682 + +interface A { +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) + + a: string; +>a : Symbol(A.a, Decl(arrayFrom.ts, 3, 13)) +} + +interface B { +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) + + b: string; +>b : Symbol(B.b, Decl(arrayFrom.ts, 7, 13)) +} + +const inputA: A[] = []; +>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) + +const inputB: B[] = []; +>inputB : Symbol(inputB, Decl(arrayFrom.ts, 12, 5)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) + +const inputALike: ArrayLike = { length: 0 }; +>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5)) +>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>length : Symbol(length, Decl(arrayFrom.ts, 13, 34)) + +const inputARand = getEither(inputA, inputALike); +>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5)) +>getEither : Symbol(getEither, Decl(arrayFrom.ts, 24, 70)) +>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5)) +>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5)) + +const result1: A[] = Array.from(inputA); +>result1 : Symbol(result1, Decl(arrayFrom.ts, 16, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5)) + +const result2: A[] = Array.from(inputA.values()); +>result2 : Symbol(result2, Decl(arrayFrom.ts, 17, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputA.values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --)) +>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5)) +>values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --)) + +const result3: B[] = Array.from(inputA.values()); // expect error +>result3 : Symbol(result3, Decl(arrayFrom.ts, 18, 5)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputA.values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --)) +>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5)) +>values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --)) + +const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b })); +>result4 : Symbol(result4, Decl(arrayFrom.ts, 19, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputB : Symbol(inputB, Decl(arrayFrom.ts, 12, 5)) +>b : Symbol(b, Decl(arrayFrom.ts, 19, 42)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>a : Symbol(a, Decl(arrayFrom.ts, 19, 56)) +>b : Symbol(b, Decl(arrayFrom.ts, 19, 42)) + +const result5: A[] = Array.from(inputALike); +>result5 : Symbol(result5, Decl(arrayFrom.ts, 20, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5)) + +const result6: B[] = Array.from(inputALike); // expect error +>result6 : Symbol(result6, Decl(arrayFrom.ts, 21, 5)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5)) + +const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a })); +>result7 : Symbol(result7, Decl(arrayFrom.ts, 22, 5)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5)) +>a : Symbol(a, Decl(arrayFrom.ts, 22, 46)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>b : Symbol(b, Decl(arrayFrom.ts, 22, 60)) +>a : Symbol(a, Decl(arrayFrom.ts, 22, 46)) + +const result8: A[] = Array.from(inputARand); +>result8 : Symbol(result8, Decl(arrayFrom.ts, 23, 5)) +>A : Symbol(A, Decl(arrayFrom.ts, 0, 0)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5)) + +const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a })); +>result9 : Symbol(result9, Decl(arrayFrom.ts, 24, 5)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5)) +>a : Symbol(a, Decl(arrayFrom.ts, 24, 46)) +>B : Symbol(B, Decl(arrayFrom.ts, 5, 1)) +>b : Symbol(b, Decl(arrayFrom.ts, 24, 60)) +>a : Symbol(a, Decl(arrayFrom.ts, 24, 46)) + +// if this is written inline, the compiler seems to infer +// the ?: as always taking the false branch, narrowing to ArrayLike, +// even when the type is written as : Iterable|ArrayLike +function getEither (in1: Iterable, in2: ArrayLike) { +>getEither : Symbol(getEither, Decl(arrayFrom.ts, 24, 70)) +>T : Symbol(T, Decl(arrayFrom.ts, 29, 19)) +>in1 : Symbol(in1, Decl(arrayFrom.ts, 29, 23)) +>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --)) +>T : Symbol(T, Decl(arrayFrom.ts, 29, 19)) +>in2 : Symbol(in2, Decl(arrayFrom.ts, 29, 40)) +>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(arrayFrom.ts, 29, 19)) + + return Math.random() > 0.5 ? in1 : in2; +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>in1 : Symbol(in1, Decl(arrayFrom.ts, 29, 23)) +>in2 : Symbol(in2, Decl(arrayFrom.ts, 29, 40)) +} + diff --git a/tests/baselines/reference/arrayFrom.types b/tests/baselines/reference/arrayFrom.types new file mode 100644 index 00000000000..f6a04dc4423 --- /dev/null +++ b/tests/baselines/reference/arrayFrom.types @@ -0,0 +1,176 @@ +=== tests/cases/compiler/arrayFrom.ts === +// Tests fix for #20432, ensures Array.from accepts all valid inputs +// Also tests for #19682 + +interface A { +>A : A + + a: string; +>a : string +} + +interface B { +>B : B + + b: string; +>b : string +} + +const inputA: A[] = []; +>inputA : A[] +>A : A +>[] : undefined[] + +const inputB: B[] = []; +>inputB : B[] +>B : B +>[] : undefined[] + +const inputALike: ArrayLike = { length: 0 }; +>inputALike : ArrayLike +>ArrayLike : ArrayLike +>A : A +>{ length: 0 } : { length: number; } +>length : number +>0 : 0 + +const inputARand = getEither(inputA, inputALike); +>inputARand : ArrayLike | Iterable +>getEither(inputA, inputALike) : ArrayLike | Iterable +>getEither : (in1: Iterable, in2: ArrayLike) => Iterable | ArrayLike +>inputA : A[] +>inputALike : ArrayLike + +const result1: A[] = Array.from(inputA); +>result1 : A[] +>A : A +>Array.from(inputA) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputA : A[] + +const result2: A[] = Array.from(inputA.values()); +>result2 : A[] +>A : A +>Array.from(inputA.values()) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputA.values() : IterableIterator +>inputA.values : () => IterableIterator +>inputA : A[] +>values : () => IterableIterator + +const result3: B[] = Array.from(inputA.values()); // expect error +>result3 : B[] +>B : B +>Array.from(inputA.values()) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputA.values() : IterableIterator +>inputA.values : () => IterableIterator +>inputA : A[] +>values : () => IterableIterator + +const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b })); +>result4 : A[] +>A : A +>Array.from(inputB, ({ b }): A => ({ a: b })) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputB : B[] +>({ b }): A => ({ a: b }) : ({ b }: B) => A +>b : string +>A : A +>({ a: b }) : { a: string; } +>{ a: b } : { a: string; } +>a : string +>b : string + +const result5: A[] = Array.from(inputALike); +>result5 : A[] +>A : A +>Array.from(inputALike) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputALike : ArrayLike + +const result6: B[] = Array.from(inputALike); // expect error +>result6 : B[] +>B : B +>Array.from(inputALike) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputALike : ArrayLike + +const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a })); +>result7 : B[] +>B : B +>Array.from(inputALike, ({ a }): B => ({ b: a })) : B[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputALike : ArrayLike +>({ a }): B => ({ b: a }) : ({ a }: A) => B +>a : string +>B : B +>({ b: a }) : { b: string; } +>{ b: a } : { b: string; } +>b : string +>a : string + +const result8: A[] = Array.from(inputARand); +>result8 : A[] +>A : A +>Array.from(inputARand) : A[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputARand : ArrayLike | Iterable + +const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a })); +>result9 : B[] +>B : B +>Array.from(inputARand, ({ a }): B => ({ b: a })) : B[] +>Array.from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>Array : ArrayConstructor +>from : { (iterable: Iterable | ArrayLike): T[]; (iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; (arrayLike: ArrayLike): T[]; (arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +>inputARand : ArrayLike | Iterable +>({ a }): B => ({ b: a }) : ({ a }: A) => B +>a : string +>B : B +>({ b: a }) : { b: string; } +>{ b: a } : { b: string; } +>b : string +>a : string + +// if this is written inline, the compiler seems to infer +// the ?: as always taking the false branch, narrowing to ArrayLike, +// even when the type is written as : Iterable|ArrayLike +function getEither (in1: Iterable, in2: ArrayLike) { +>getEither : (in1: Iterable, in2: ArrayLike) => Iterable | ArrayLike +>T : T +>in1 : Iterable +>Iterable : Iterable +>T : T +>in2 : ArrayLike +>ArrayLike : ArrayLike +>T : T + + return Math.random() > 0.5 ? in1 : in2; +>Math.random() > 0.5 ? in1 : in2 : Iterable | ArrayLike +>Math.random() > 0.5 : boolean +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>0.5 : 0.5 +>in1 : Iterable +>in2 : ArrayLike +} + diff --git a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt index 7ffe8317d42..87c8b59ee1e 100644 --- a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt +++ b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt @@ -1,12 +1,12 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(13,1): error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray'. Types of property 'concat' are incompatible. - Type '{ (...items: ReadonlyArray[]): A[]; (...items: (A | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: ReadonlyArray[]): B[]; (...items: (B | ReadonlyArray)[]): B[]; }'. + Type '{ (...items: (A[] | ReadonlyArray)[]): A[]; (...items: (A | A[] | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: (B[] | ReadonlyArray)[]): B[]; (...items: (B | B[] | ReadonlyArray)[]): B[]; }'. Type 'A[]' is not assignable to type 'B[]'. Type 'A' is not assignable to type 'B'. Property 'b' is missing in type 'A'. tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error TS2322: Type 'C' is not assignable to type 'ReadonlyArray'. Types of property 'concat' are incompatible. - Type '{ (...items: ReadonlyArray[]): A[]; (...items: (A | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: ReadonlyArray[]): B[]; (...items: (B | ReadonlyArray)[]): B[]; }'. + Type '{ (...items: (A[] | ReadonlyArray)[]): A[]; (...items: (A | A[] | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: (B[] | ReadonlyArray)[]): B[]; (...items: (B | B[] | ReadonlyArray)[]): B[]; }'. Type 'A[]' is not assignable to type 'B[]'. @@ -27,7 +27,7 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T ~~~ !!! error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray'. !!! error TS2322: Types of property 'concat' are incompatible. -!!! error TS2322: Type '{ (...items: ReadonlyArray[]): A[]; (...items: (A | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: ReadonlyArray[]): B[]; (...items: (B | ReadonlyArray)[]): B[]; }'. +!!! error TS2322: Type '{ (...items: (A[] | ReadonlyArray)[]): A[]; (...items: (A | A[] | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: (B[] | ReadonlyArray)[]): B[]; (...items: (B | B[] | ReadonlyArray)[]): B[]; }'. !!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. !!! error TS2322: Type 'A' is not assignable to type 'B'. !!! error TS2322: Property 'b' is missing in type 'A'. @@ -39,6 +39,6 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T ~~~ !!! error TS2322: Type 'C' is not assignable to type 'ReadonlyArray'. !!! error TS2322: Types of property 'concat' are incompatible. -!!! error TS2322: Type '{ (...items: ReadonlyArray[]): A[]; (...items: (A | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: ReadonlyArray[]): B[]; (...items: (B | ReadonlyArray)[]): B[]; }'. +!!! error TS2322: Type '{ (...items: (A[] | ReadonlyArray)[]): A[]; (...items: (A | A[] | ReadonlyArray)[]): A[]; }' is not assignable to type '{ (...items: (B[] | ReadonlyArray)[]): B[]; (...items: (B | B[] | ReadonlyArray)[]): B[]; }'. !!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. \ No newline at end of file diff --git a/tests/baselines/reference/baseClassImprovedMismatchErrors.errors.txt b/tests/baselines/reference/baseClassImprovedMismatchErrors.errors.txt new file mode 100644 index 00000000000..031fe183754 --- /dev/null +++ b/tests/baselines/reference/baseClassImprovedMismatchErrors.errors.txt @@ -0,0 +1,73 @@ +tests/cases/compiler/baseClassImprovedMismatchErrors.ts(8,5): error TS2416: Property 'n' in type 'Derived' is not assignable to the same property in base type 'Base'. + Type 'string | Derived' is not assignable to type 'string | Base'. + Type 'Derived' is not assignable to type 'string | Base'. + Type 'Derived' is not assignable to type 'Base'. + Types of property 'n' are incompatible. + Type 'string | Derived' is not assignable to type 'string | Base'. + Type 'Derived' is not assignable to type 'string | Base'. + Type 'Derived' is not assignable to type 'Base'. +tests/cases/compiler/baseClassImprovedMismatchErrors.ts(9,5): error TS2416: Property 'fn' in type 'Derived' is not assignable to the same property in base type 'Base'. + Type '() => string | number' is not assignable to type '() => number'. + Type 'string | number' is not assignable to type 'number'. + Type 'string' is not assignable to type 'number'. +tests/cases/compiler/baseClassImprovedMismatchErrors.ts(14,5): error TS2416: Property 'n' in type 'DerivedInterface' is not assignable to the same property in base type 'Base'. + Type 'string | DerivedInterface' is not assignable to type 'string | Base'. + Type 'DerivedInterface' is not assignable to type 'string | Base'. + Type 'DerivedInterface' is not assignable to type 'Base'. + Types of property 'n' are incompatible. + Type 'string | DerivedInterface' is not assignable to type 'string | Base'. + Type 'DerivedInterface' is not assignable to type 'string | Base'. + Type 'DerivedInterface' is not assignable to type 'Base'. +tests/cases/compiler/baseClassImprovedMismatchErrors.ts(15,5): error TS2416: Property 'fn' in type 'DerivedInterface' is not assignable to the same property in base type 'Base'. + Type '() => string | number' is not assignable to type '() => number'. + Type 'string | number' is not assignable to type 'number'. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/compiler/baseClassImprovedMismatchErrors.ts (4 errors) ==== + class Base { + n: Base | string; + fn() { + return 10; + } + } + class Derived extends Base { + n: Derived | string; + ~ +!!! error TS2416: Property 'n' in type 'Derived' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type 'string | Derived' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'Derived' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'Derived' is not assignable to type 'Base'. +!!! error TS2416: Types of property 'n' are incompatible. +!!! error TS2416: Type 'string | Derived' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'Derived' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'Derived' is not assignable to type 'Base'. + fn() { + ~~ +!!! error TS2416: Property 'fn' in type 'Derived' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type '() => string | number' is not assignable to type '() => number'. +!!! error TS2416: Type 'string | number' is not assignable to type 'number'. +!!! error TS2416: Type 'string' is not assignable to type 'number'. + return 10 as number | string; + } + } + class DerivedInterface implements Base { + n: DerivedInterface | string; + ~ +!!! error TS2416: Property 'n' in type 'DerivedInterface' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type 'string | DerivedInterface' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'DerivedInterface' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'DerivedInterface' is not assignable to type 'Base'. +!!! error TS2416: Types of property 'n' are incompatible. +!!! error TS2416: Type 'string | DerivedInterface' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'DerivedInterface' is not assignable to type 'string | Base'. +!!! error TS2416: Type 'DerivedInterface' is not assignable to type 'Base'. + fn() { + ~~ +!!! error TS2416: Property 'fn' in type 'DerivedInterface' is not assignable to the same property in base type 'Base'. +!!! error TS2416: Type '() => string | number' is not assignable to type '() => number'. +!!! error TS2416: Type 'string | number' is not assignable to type 'number'. +!!! error TS2416: Type 'string' is not assignable to type 'number'. + return 10 as number | string; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/baseClassImprovedMismatchErrors.js b/tests/baselines/reference/baseClassImprovedMismatchErrors.js new file mode 100644 index 00000000000..296f9e6eb1c --- /dev/null +++ b/tests/baselines/reference/baseClassImprovedMismatchErrors.js @@ -0,0 +1,57 @@ +//// [baseClassImprovedMismatchErrors.ts] +class Base { + n: Base | string; + fn() { + return 10; + } +} +class Derived extends Base { + n: Derived | string; + fn() { + return 10 as number | string; + } +} +class DerivedInterface implements Base { + n: DerivedInterface | string; + fn() { + return 10 as number | string; + } +} + +//// [baseClassImprovedMismatchErrors.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Base = /** @class */ (function () { + function Base() { + } + Base.prototype.fn = function () { + return 10; + }; + return Base; +}()); +var Derived = /** @class */ (function (_super) { + __extends(Derived, _super); + function Derived() { + return _super !== null && _super.apply(this, arguments) || this; + } + Derived.prototype.fn = function () { + return 10; + }; + return Derived; +}(Base)); +var DerivedInterface = /** @class */ (function () { + function DerivedInterface() { + } + DerivedInterface.prototype.fn = function () { + return 10; + }; + return DerivedInterface; +}()); diff --git a/tests/baselines/reference/baseClassImprovedMismatchErrors.symbols b/tests/baselines/reference/baseClassImprovedMismatchErrors.symbols new file mode 100644 index 00000000000..19ac124a2e0 --- /dev/null +++ b/tests/baselines/reference/baseClassImprovedMismatchErrors.symbols @@ -0,0 +1,42 @@ +=== tests/cases/compiler/baseClassImprovedMismatchErrors.ts === +class Base { +>Base : Symbol(Base, Decl(baseClassImprovedMismatchErrors.ts, 0, 0)) + + n: Base | string; +>n : Symbol(Base.n, Decl(baseClassImprovedMismatchErrors.ts, 0, 12)) +>Base : Symbol(Base, Decl(baseClassImprovedMismatchErrors.ts, 0, 0)) + + fn() { +>fn : Symbol(Base.fn, Decl(baseClassImprovedMismatchErrors.ts, 1, 21)) + + return 10; + } +} +class Derived extends Base { +>Derived : Symbol(Derived, Decl(baseClassImprovedMismatchErrors.ts, 5, 1)) +>Base : Symbol(Base, Decl(baseClassImprovedMismatchErrors.ts, 0, 0)) + + n: Derived | string; +>n : Symbol(Derived.n, Decl(baseClassImprovedMismatchErrors.ts, 6, 28)) +>Derived : Symbol(Derived, Decl(baseClassImprovedMismatchErrors.ts, 5, 1)) + + fn() { +>fn : Symbol(Derived.fn, Decl(baseClassImprovedMismatchErrors.ts, 7, 24)) + + return 10 as number | string; + } +} +class DerivedInterface implements Base { +>DerivedInterface : Symbol(DerivedInterface, Decl(baseClassImprovedMismatchErrors.ts, 11, 1)) +>Base : Symbol(Base, Decl(baseClassImprovedMismatchErrors.ts, 0, 0)) + + n: DerivedInterface | string; +>n : Symbol(DerivedInterface.n, Decl(baseClassImprovedMismatchErrors.ts, 12, 40)) +>DerivedInterface : Symbol(DerivedInterface, Decl(baseClassImprovedMismatchErrors.ts, 11, 1)) + + fn() { +>fn : Symbol(DerivedInterface.fn, Decl(baseClassImprovedMismatchErrors.ts, 13, 33)) + + return 10 as number | string; + } +} diff --git a/tests/baselines/reference/baseClassImprovedMismatchErrors.types b/tests/baselines/reference/baseClassImprovedMismatchErrors.types new file mode 100644 index 00000000000..0ca00a02248 --- /dev/null +++ b/tests/baselines/reference/baseClassImprovedMismatchErrors.types @@ -0,0 +1,47 @@ +=== tests/cases/compiler/baseClassImprovedMismatchErrors.ts === +class Base { +>Base : Base + + n: Base | string; +>n : string | Base +>Base : Base + + fn() { +>fn : () => number + + return 10; +>10 : 10 + } +} +class Derived extends Base { +>Derived : Derived +>Base : Base + + n: Derived | string; +>n : string | Derived +>Derived : Derived + + fn() { +>fn : () => string | number + + return 10 as number | string; +>10 as number | string : string | number +>10 : 10 + } +} +class DerivedInterface implements Base { +>DerivedInterface : DerivedInterface +>Base : Base + + n: DerivedInterface | string; +>n : string | DerivedInterface +>DerivedInterface : DerivedInterface + + fn() { +>fn : () => string | number + + return 10 as number | string; +>10 as number | string : string | number +>10 : 10 + } +} diff --git a/tests/baselines/reference/bindingPatternOmittedExpressionNesting.js b/tests/baselines/reference/bindingPatternOmittedExpressionNesting.js index e2dff308879..0a8542ee637 100644 --- a/tests/baselines/reference/bindingPatternOmittedExpressionNesting.js +++ b/tests/baselines/reference/bindingPatternOmittedExpressionNesting.js @@ -9,3 +9,4 @@ var _b, _c, _d, _e; //// [bindingPatternOmittedExpressionNesting.d.ts] +export {}; diff --git a/tests/baselines/reference/checkJsxChildrenProperty14.errors.txt b/tests/baselines/reference/checkJsxChildrenProperty14.errors.txt index ecae349e19e..e01fce65781 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty14.errors.txt +++ b/tests/baselines/reference/checkJsxChildrenProperty14.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/jsx/file.tsx(42,27): error TS2322: Type '{ a: 10; b: "hi"; children: Element[]; }' is not assignable to type 'IntrinsicAttributes & SingleChildProp'. - Type '{ a: 10; b: "hi"; children: Element[]; }' is not assignable to type 'SingleChildProp'. +tests/cases/conformance/jsx/file.tsx(42,27): error TS2322: Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & SingleChildProp'. + Type '{ children: Element[]; a: number; b: string; }' is not assignable to type 'SingleChildProp'. Types of property 'children' are incompatible. Type 'Element[]' is not assignable to type 'Element'. Property 'type' is missing in type 'Element[]'. @@ -49,8 +49,8 @@ tests/cases/conformance/jsx/file.tsx(42,27): error TS2322: Type '{ a: 10; b: "hi // Error let k5 = <>