diff --git a/Gulpfile.ts b/Gulpfile.ts index d677b042250..1b902562832 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -17,7 +17,7 @@ declare module "gulp-typescript" { stripInternal?: boolean; types?: string[]; } - interface CompileStream extends NodeJS.ReadWriteStream {} // Either gulp or gulp-typescript has some odd typings which don't reflect reality, making this required + interface CompileStream extends NodeJS.ReadWriteStream { } // Either gulp or gulp-typescript has some odd typings which don't reflect reality, making this required } import * as insert from "gulp-insert"; import * as sourcemaps from "gulp-sourcemaps"; @@ -34,7 +34,6 @@ import through2 = require("through2"); import merge2 = require("merge2"); import intoStream = require("into-stream"); import * as os from "os"; -import Linter = require("tslint"); import fold = require("travis-fold"); const gulp = helpMaker(originalGulp); const mochaParallel = require("./scripts/mocha-parallel.js"); @@ -66,7 +65,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), { } }); -function exec(cmd: string, args: string[], complete: () => void = (() => {}), error: (e: any, status: number) => void = (() => {})) { +function exec(cmd: string, args: string[], complete: () => void = (() => { }), error: (e: any, status: number) => void = (() => { })) { console.log(`${cmd} ${args.join(" ")}`); // TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition const subshellFlag = isWin ? "/c" : "-c"; @@ -117,12 +116,12 @@ const es2015LibrarySources = [ ]; const es2015LibrarySourceMap = es2015LibrarySources.map(function(source) { - return { target: "lib." + source, sources: ["header.d.ts", source] }; + return { target: "lib." + source, sources: ["header.d.ts", source] }; }); -const es2016LibrarySource = [ "es2016.array.include.d.ts" ]; +const es2016LibrarySource = ["es2016.array.include.d.ts"]; -const es2016LibrarySourceMap = es2016LibrarySource.map(function (source) { +const es2016LibrarySourceMap = es2016LibrarySource.map(function(source) { return { target: "lib." + source, sources: ["header.d.ts", source] }; }); @@ -131,38 +130,38 @@ const es2017LibrarySource = [ "es2017.sharedmemory.d.ts" ]; -const es2017LibrarySourceMap = es2017LibrarySource.map(function (source) { +const es2017LibrarySourceMap = es2017LibrarySource.map(function(source) { return { target: "lib." + source, sources: ["header.d.ts", source] }; }); const hostsLibrarySources = ["dom.generated.d.ts", "webworker.importscripts.d.ts", "scripthost.d.ts"]; const librarySourceMap = [ - // Host library - { target: "lib.dom.d.ts", sources: ["header.d.ts", "dom.generated.d.ts"] }, - { target: "lib.dom.iterable.d.ts", sources: ["header.d.ts", "dom.iterable.d.ts"] }, - { target: "lib.webworker.d.ts", sources: ["header.d.ts", "webworker.generated.d.ts"] }, - { target: "lib.scripthost.d.ts", sources: ["header.d.ts", "scripthost.d.ts"] }, + // Host library + { target: "lib.dom.d.ts", sources: ["header.d.ts", "dom.generated.d.ts"] }, + { target: "lib.dom.iterable.d.ts", sources: ["header.d.ts", "dom.iterable.d.ts"] }, + { target: "lib.webworker.d.ts", sources: ["header.d.ts", "webworker.generated.d.ts"] }, + { target: "lib.scripthost.d.ts", sources: ["header.d.ts", "scripthost.d.ts"] }, - // JavaScript library - { target: "lib.es5.d.ts", sources: ["header.d.ts", "es5.d.ts"] }, - { target: "lib.es2015.d.ts", sources: ["header.d.ts", "es2015.d.ts"] }, - { target: "lib.es2016.d.ts", sources: ["header.d.ts", "es2016.d.ts"] }, - { target: "lib.es2017.d.ts", sources: ["header.d.ts", "es2017.d.ts"] }, + // JavaScript library + { target: "lib.es5.d.ts", sources: ["header.d.ts", "es5.d.ts"] }, + { target: "lib.es2015.d.ts", sources: ["header.d.ts", "es2015.d.ts"] }, + { target: "lib.es2016.d.ts", sources: ["header.d.ts", "es2016.d.ts"] }, + { target: "lib.es2017.d.ts", sources: ["header.d.ts", "es2017.d.ts"] }, - // JavaScript + all host library - { target: "lib.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(hostsLibrarySources) }, - { target: "lib.es6.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") } + // JavaScript + all host library + { target: "lib.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(hostsLibrarySources) }, + { target: "lib.es6.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") } ].concat(es2015LibrarySourceMap, es2016LibrarySourceMap, es2017LibrarySourceMap); -const libraryTargets = librarySourceMap.map(function (f) { +const libraryTargets = librarySourceMap.map(function(f) { return path.join(builtLocalDirectory, f.target); }); for (const i in libraryTargets) { const entry = librarySourceMap[i]; const target = libraryTargets[i]; - const sources = [copyright].concat(entry.sources.map(function (s) { + const sources = [copyright].concat(entry.sources.map(function(s) { return path.join(libraryDirectory, s); })); gulp.task(target, false, [], function() { @@ -392,7 +391,7 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => { .pipe(sourcemaps.init()) .pipe(tsc(servicesProject)); const completedJs = js.pipe(prependCopyright()) - .pipe(sourcemaps.write(".")); + .pipe(sourcemaps.write(".")); const completedDts = dts.pipe(prependCopyright(/*outputCopyright*/true)) .pipe(insert.transform((contents, file) => { file.path = standaloneDefinitionsFile; @@ -435,17 +434,17 @@ const tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverli gulp.task(tsserverLibraryFile, false, [servicesFile], (done) => { const serverLibraryProject = tsc.createProject("src/server/tsconfig.library.json", getCompilerSettings({}, /*useBuiltCompiler*/ true)); - const {js, dts}: {js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream} = serverLibraryProject.src() + const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src() .pipe(sourcemaps.init()) .pipe(newer(tsserverLibraryFile)) .pipe(tsc(serverLibraryProject)); return merge2([ js.pipe(prependCopyright()) - .pipe(sourcemaps.write(".")) - .pipe(gulp.dest(builtLocalDirectory)), + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(builtLocalDirectory)), dts.pipe(prependCopyright()) - .pipe(gulp.dest(builtLocalDirectory)) + .pipe(gulp.dest(builtLocalDirectory)) ]); }); @@ -477,7 +476,7 @@ gulp.task(specMd, false, [word2mdJs], (done) => { const specMDFullPath = path.resolve(specMd); const cmd = "cscript //nologo " + word2mdJs + " \"" + specWordFullPath + "\" " + "\"" + specMDFullPath + "\""; console.log(cmd); - cp.exec(cmd, function () { + cp.exec(cmd, function() { done(); }); }); @@ -493,12 +492,12 @@ gulp.task("dontUseDebugMode", false, [], (done) => { useDebugMode = false; done( gulp.task("VerifyLKG", false, [], () => { const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile].concat(libraryTargets); - const missingFiles = expectedFiles.filter(function (f) { + const missingFiles = expectedFiles.filter(function(f) { return !fs.existsSync(f); }); if (missingFiles.length > 0) { throw new Error("Cannot replace the LKG unless all built targets are present in directory " + builtLocalDirectory + - ". The following files are missing:\n" + missingFiles.join("\n")); + ". The following files are missing:\n" + missingFiles.join("\n")); } // Copy all the targets into the LKG directory return gulp.src(expectedFiles).pipe(gulp.dest(LKGDirectory)); @@ -532,8 +531,6 @@ const localRwcBaseline = path.join(internalTests, "baselines/rwc/local"); const refRwcBaseline = path.join(internalTests, "baselines/rwc/reference"); const localTest262Baseline = path.join(internalTests, "baselines/test262/local"); -const refTest262Baseline = path.join(internalTests, "baselines/test262/reference"); - gulp.task("tests", "Builds the test infrastructure using the built compiler", [run]); gulp.task("tests-debug", "Builds the test sources and automation in debug mode", () => { @@ -628,7 +625,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: } args.push(run); setNodeEnvToDevelopment(); - runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function (err) { + runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function(err) { // last worker clean everything and runs linter in case if there were no errors del(taskConfigsFolder).then(() => { if (!err) { @@ -680,7 +677,7 @@ gulp.task("runtests", ["build-rules", "tests"], (done) => { runConsoleTests("mocha-fivemat-progress-reporter", /*runInParallel*/ false, done); -}); + }); const nodeServerOutFile = "tests/webTestServer.js"; const nodeServerInFile = "tests/webTestServer.ts"; @@ -812,32 +809,36 @@ gulp.task("diff-rwc", "Diffs the RWC baselines using the diff tool specified by exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], done, done); }); +gulp.task("baseline-accept", "Makes the most recent test results the new baseline, overwriting the old baseline", () => { + return baselineAccept(""); +}); + +function baselineAccept(subfolder = "") { + return merge2(baselineCopy(subfolder), baselineDelete(subfolder)); +} + +function baselineCopy(subfolder = "") { + return gulp.src([`tests/baselines/local/${subfolder}/**`, `!tests/baselines/local/${subfolder}/**/*.delete`]) + .pipe(gulp.dest(refBaseline)); +} + +function baselineDelete(subfolder = "") { + return gulp.src(["tests/baselines/local/**/*.delete"]) + .pipe(insert.transform((content, fileObj) => { + const target = path.join(refBaseline, fileObj.relative.substr(0, fileObj.relative.length - ".delete".length)); + del.sync(target); + del.sync(fileObj.path); + return ""; + })); +} -gulp.task("baseline-accept", "Makes the most recent test results the new baseline, overwriting the old baseline", (done) => { - const softAccept = cmdLineOptions["soft"]; - if (!softAccept) { - del(refBaseline).then(() => { - fs.renameSync(localBaseline, refBaseline); - done(); - }, done); - } - else { - gulp.src(localBaseline) - .pipe(gulp.dest(refBaseline)) - .on("end", () => { - del(path.join(refBaseline, "local")).then(() => done(), done); - }); - } -}); gulp.task("baseline-accept-rwc", "Makes the most recent rwc test results the new baseline, overwriting the old baseline", () => { - return del(refRwcBaseline).then(() => { - fs.renameSync(localRwcBaseline, refRwcBaseline); - }); + return baselineAccept("rwc"); }); + + gulp.task("baseline-accept-test262", "Makes the most recent test262 test results the new baseline, overwriting the old baseline", () => { - return del(refTest262Baseline).then(() => { - fs.renameSync(localTest262Baseline, refTest262Baseline); - }); + return baselineAccept("test262"); }); @@ -929,26 +930,6 @@ gulp.task("build-rules", "Compiles tslint rules to js", () => { .pipe(gulp.dest(dest)); }); -function getLinterOptions() { - return { - configuration: require("./tslint.json"), - formatter: "prose", - formattersDirectory: undefined, - rulesDirectory: "built/local/tslint" - }; -} - -function lintFileContents(options, path, contents) { - const ll = new Linter(path, contents, options); - console.log("Linting '" + path + "'."); - return ll.lint(); -} - -function lintFile(options, path) { - const contents = fs.readFileSync(path, "utf8"); - return lintFileContents(options, path, contents); -} - const lintTargets = [ "Gulpfile.ts", "src/compiler/**/*.ts", @@ -960,29 +941,72 @@ const lintTargets = [ "tests/*.ts", "tests/webhost/*.ts" // Note: does *not* descend recursively ]; +function sendNextFile(files: {path: string}[], child: cp.ChildProcess, callback: (failures: number) => void, failures: number) { + const file = files.pop(); + if (file) { + console.log(`Linting '${file.path}'.`); + child.send({ kind: "file", name: file.path }); + } + else { + child.send({ kind: "close" }); + callback(failures); + } +} + +function spawnLintWorker(files: {path: string}[], callback: (failures: number) => void) { + const child = cp.fork("./scripts/parallel-lint"); + let failures = 0; + child.on("message", function(data) { + switch (data.kind) { + case "result": + if (data.failures > 0) { + failures += data.failures; + console.log(data.output); + } + sendNextFile(files, child, callback, failures); + break; + case "error": + console.error(data.error); + failures++; + sendNextFile(files, child, callback, failures); + break; + } + }); + sendNextFile(files, child, callback, failures); +} gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", ["build-rules"], () => { const fileMatcher = RegExp(cmdLineOptions["files"]); - const lintOptions = getLinterOptions(); - let failed = 0; if (fold.isTravis()) console.log(fold.start("lint")); - return gulp.src(lintTargets) - .pipe(insert.transform((contents, file) => { - if (!fileMatcher.test(file.path)) return contents; - const result = lintFile(lintOptions, file.path); - if (result.failureCount > 0) { - console.log(result.output); - failed += result.failureCount; + + let files: {stat: fs.Stats, path: string}[] = []; + return gulp.src(lintTargets, { read: false }) + .pipe(through2.obj((chunk, enc, cb) => { + files.push(chunk); + cb(); + }, (cb) => { + files = files.filter(file => fileMatcher.test(file.path)).sort((filea, fileb) => filea.stat.size - fileb.stat.size); + const workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length; + for (let i = 0; i < workerCount; i++) { + spawnLintWorker(files, finished); } - return contents; // TODO (weswig): Automatically apply fixes? :3 - })) - .on("end", () => { - if (fold.isTravis()) console.log(fold.end("lint")); - if (failed > 0) { - console.error("Linter errors."); - process.exit(1); + + let completed = 0; + let failures = 0; + function finished(fails) { + completed++; + failures += fails; + if (completed === workerCount) { + if (fold.isTravis()) console.log(fold.end("lint")); + if (failures > 0) { + throw new Error(`Linter errors: ${failures}`); + } + else { + cb(); + } + } } - }); + })); }); diff --git a/Jakefile.js b/Jakefile.js index 0624c92f162..992839a8661 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -4,7 +4,6 @@ var fs = require("fs"); var os = require("os"); var path = require("path"); var child_process = require("child_process"); -var Linter = require("tslint"); var fold = require("travis-fold"); var runTestsInParallel = require("./scripts/mocha-parallel").runTestsInParallel; @@ -33,6 +32,28 @@ if (process.env.path !== undefined) { process.env.PATH = nodeModulesPathPrefix + process.env.PATH; } +function toNs(diff) { + return diff[0] * 1e9 + diff[1]; +} + +function mark() { + if (!fold.isTravis()) return; + var stamp = process.hrtime(); + var id = Math.floor(Math.random() * 0xFFFFFFFF).toString(16); + console.log("travis_time:start:" + id + "\r"); + return { + stamp: stamp, + id: id + }; +} + +function measure(marker) { + if (!fold.isTravis()) return; + var diff = process.hrtime(marker.stamp); + var total = [marker.stamp[0] + diff[0], marker.stamp[1] + diff[1]]; + console.log("travis_time:end:" + marker.id + ":start=" + toNs(marker.stamp) + ",finish=" + toNs(total) + ",duration=" + toNs(diff) + "\r"); +} + var compilerSources = [ "core.ts", "performance.ts", @@ -286,6 +307,7 @@ var builtLocalCompiler = path.join(builtLocalDirectory, compilerFilename); */ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts, callback) { file(outFile, prereqs, function() { + var startCompileTime = mark(); opts = opts || {}; var compilerPath = useBuiltCompiler ? builtLocalCompiler : LKGCompiler; var options = "--noImplicitAny --noImplicitThis --noEmitOnError --types " @@ -362,11 +384,13 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts callback(); } + measure(startCompileTime); complete(); }); ex.addListener("error", function() { fs.unlinkSync(outFile); fail("Compilation of " + outFile + " unsuccessful"); + measure(startCompileTime); }); ex.run(); }, {async: true}); @@ -769,6 +793,7 @@ function runConsoleTests(defaultReporter, runInParallel) { // timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer if(!runInParallel) { + var startTime = mark(); tests = tests ? ' -g "' + tests + '"' : ''; var cmd = "mocha" + (debug ? " --debug-brk" : "") + " -R " + reporter + tests + colors + bail + ' -t ' + testTimeout + ' ' + run; console.log(cmd); @@ -777,10 +802,12 @@ function runConsoleTests(defaultReporter, runInParallel) { process.env.NODE_ENV = "development"; exec(cmd, function () { process.env.NODE_ENV = savedNodeEnv; + measure(startTime); runLinter(); finish(); }, function(e, status) { process.env.NODE_ENV = savedNodeEnv; + measure(startTime); finish(status); }); @@ -788,9 +815,10 @@ function runConsoleTests(defaultReporter, runInParallel) { else { var savedNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = "development"; + var startTime = mark(); runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function (err) { process.env.NODE_ENV = savedNodeEnv; - + measure(startTime); // last worker clean everything and runs linter in case if there were no errors deleteTemporaryProjectOutput(); jake.rmRf(taskConfigsFolder); @@ -909,16 +937,16 @@ task("tests-debug", ["setDebugMode", "tests"]); // Makes the test results the new baseline desc("Makes the most recent test results the new baseline, overwriting the old baseline"); task("baseline-accept", function(hardOrSoft) { - if (!hardOrSoft || hardOrSoft === "hard") { - jake.rmRf(refBaseline); - fs.renameSync(localBaseline, refBaseline); - } - else if (hardOrSoft === "soft") { - var files = jake.readdirR(localBaseline); - for (var i in files) { + var files = jake.readdirR(localBaseline); + var deleteEnding = '.delete'; + for (var i in files) { + if (files[i].substr(files[i].length - deleteEnding.length) === deleteEnding) { + var filename = path.basename(files[i]); + filename = filename.substr(0, filename.length - deleteEnding.length); + fs.unlink(path.join(refBaseline, filename)); + } else { jake.cpR(files[i], refBaseline); } - jake.rmRf(path.join(refBaseline, "local")); } }); @@ -1025,36 +1053,6 @@ task("build-rules-end", [] , function() { if (fold.isTravis()) console.log(fold.end("build-rules")); }); -function getLinterOptions() { - return { - configuration: require("./tslint.json"), - formatter: "prose", - formattersDirectory: undefined, - rulesDirectory: "built/local/tslint" - }; -} - -function lintFileContents(options, path, contents) { - var ll = new Linter(path, contents, options); - console.log("Linting '" + path + "'."); - return ll.lint(); -} - -function lintFile(options, path) { - var contents = fs.readFileSync(path, "utf8"); - return lintFileContents(options, path, contents); -} - -function lintFileAsync(options, path, cb) { - fs.readFile(path, "utf8", function(err, contents) { - if (err) { - return cb(err); - } - var result = lintFileContents(options, path, contents); - cb(undefined, result); - }); -} - var lintTargets = compilerSources .concat(harnessSources) // Other harness sources @@ -1065,73 +1063,78 @@ var lintTargets = compilerSources .concat(["Gulpfile.ts"]) .concat([nodeServerInFile, perftscPath, "tests/perfsys.ts", webhostPath]); +function sendNextFile(files, child, callback, failures) { + var file = files.pop(); + if (file) { + console.log("Linting '" + file + "'."); + child.send({kind: "file", name: file}); + } + else { + child.send({kind: "close"}); + callback(failures); + } +} + +function spawnLintWorker(files, callback) { + var child = child_process.fork("./scripts/parallel-lint"); + var failures = 0; + child.on("message", function(data) { + switch (data.kind) { + case "result": + if (data.failures > 0) { + failures += data.failures; + console.log(data.output); + } + sendNextFile(files, child, callback, failures); + break; + case "error": + console.error(data.error); + failures++; + sendNextFile(files, child, callback, failures); + break; + } + }); + sendNextFile(files, child, callback, failures); +} desc("Runs tslint on the compiler sources. Optional arguments are: f[iles]=regex"); task("lint", ["build-rules"], function() { if (fold.isTravis()) console.log(fold.start("lint")); - var lintOptions = getLinterOptions(); + var startTime = mark(); var failed = 0; var fileMatcher = RegExp(process.env.f || process.env.file || process.env.files || ""); var done = {}; for (var i in lintTargets) { var target = lintTargets[i]; if (!done[target] && fileMatcher.test(target)) { - var result = lintFile(lintOptions, target); - if (result.failureCount > 0) { - console.log(result.output); - failed += result.failureCount; - } - done[target] = true; + done[target] = fs.statSync(target).size; } } - if (fold.isTravis()) console.log(fold.end("lint")); - if (failed > 0) { - fail('Linter errors.', failed); - } -}); -/** - * This is required because file watches on Windows get fires _twice_ - * when a file changes on some node/windows version configuations - * (node v4 and win 10, for example). By not running a lint for a file - * which already has a pending lint, we avoid duplicating our work. - * (And avoid printing duplicate results!) - */ -var lintSemaphores = {}; + var workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length; -function lintWatchFile(filename) { - fs.watch(filename, {persistent: true}, function(event) { - if (event !== "change") { - return; - } - - if (!lintSemaphores[filename]) { - lintSemaphores[filename] = true; - lintFileAsync(getLinterOptions(), filename, function(err, result) { - delete lintSemaphores[filename]; - if (err) { - console.log(err); - return; - } - if (result.failureCount > 0) { - console.log("***Lint failure***"); - for (var i = 0; i < result.failures.length; i++) { - var failure = result.failures[i]; - var start = failure.startPosition.lineAndCharacter; - var end = failure.endPosition.lineAndCharacter; - console.log("warning " + filename + " (" + (start.line + 1) + "," + (start.character + 1) + "," + (end.line + 1) + "," + (end.character + 1) + "): " + failure.failure); - } - console.log("*** Total " + result.failureCount + " failures."); - } - }); - } + var names = Object.keys(done).sort(function(namea, nameb) { + return done[namea] - done[nameb]; }); -} -desc("Watches files for changes to rerun a lint pass"); -task("lint-server", ["build-rules"], function() { - console.log("Watching ./src for changes to linted files"); - for (var i = 0; i < lintTargets.length; i++) { - lintWatchFile(lintTargets[i]); + for (var i = 0; i < workerCount; i++) { + spawnLintWorker(names, finished); } -}); + + var completed = 0; + var failures = 0; + function finished(fails) { + completed++; + failures += fails; + if (completed === workerCount) { + measure(startTime); + if (fold.isTravis()) console.log(fold.end("lint")); + if (failures > 0) { + fail('Linter errors.', failed); + } + else { + complete(); + } + } + } +}, {async: true}); diff --git a/README.md b/README.md index fca2890bc77..d16bc363b26 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Join the chat at https://gitter.im/Microsoft/TypeScript](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Microsoft/TypeScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[TypeScript](http://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types, classes, and modules to JavaScript. TypeScript supports tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](http://www.typescriptlang.org/Playground), and stay up to date via [our blog](http://blogs.msdn.com/typescript) and [Twitter account](https://twitter.com/typescriptlang). +[TypeScript](http://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types, classes, and modules to JavaScript. TypeScript supports tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](http://www.typescriptlang.org/Playground), and stay up to date via [our blog](https://blogs.msdn.microsoft.com/typescript) and [Twitter account](https://twitter.com/typescriptlang). ## Installing diff --git a/package.json b/package.json index 25d8f0c42ea..eb34b6bffbb 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "mkdirp": "latest", "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", + "q": "latest", "run-sequence": "latest", "sorcery": "latest", "through2": "latest", diff --git a/scripts/parallel-lint.js b/scripts/parallel-lint.js new file mode 100644 index 00000000000..a9aec06c2df --- /dev/null +++ b/scripts/parallel-lint.js @@ -0,0 +1,45 @@ +var Linter = require("tslint"); +var fs = require("fs"); + +function getLinterOptions() { + return { + configuration: require("../tslint.json"), + formatter: "prose", + formattersDirectory: undefined, + rulesDirectory: "built/local/tslint" + }; +} + +function lintFileContents(options, path, contents) { + var ll = new Linter(path, contents, options); + return ll.lint(); +} + +function lintFileAsync(options, path, cb) { + fs.readFile(path, "utf8", function (err, contents) { + if (err) { + return cb(err); + } + var result = lintFileContents(options, path, contents); + cb(undefined, result); + }); +} + +process.on("message", function (data) { + switch (data.kind) { + case "file": + var target = data.name; + var lintOptions = getLinterOptions(); + lintFileAsync(lintOptions, target, function (err, result) { + if (err) { + process.send({ kind: "error", error: err.toString() }); + return; + } + process.send({ kind: "result", failures: result.failureCount, output: result.output }); + }); + break; + case "close": + process.exit(0); + break; + } +}); \ No newline at end of file diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index 26632ba6bab..431cf460180 100644 --- a/scripts/processDiagnosticMessages.ts +++ b/scripts/processDiagnosticMessages.ts @@ -69,7 +69,7 @@ function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosti } function buildUniqueNameMap(names: string[]): ts.Map { - var nameMap: ts.Map = {}; + var nameMap = ts.createMap(); var uniqueNames = NameGenerator.ensureUniqueness(names, /* isCaseSensitive */ false, /* isFixed */ undefined); diff --git a/scripts/tslint/preferConstRule.ts b/scripts/tslint/preferConstRule.ts index aaa1b0e53d5..1d316692468 100644 --- a/scripts/tslint/preferConstRule.ts +++ b/scripts/tslint/preferConstRule.ts @@ -1,7 +1,6 @@ import * as Lint from "tslint/lib/lint"; import * as ts from "typescript"; - export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_FACTORY = (identifier: string) => `Identifier '${identifier}' never appears on the LHS of an assignment - use const instead of let for its declaration.`; @@ -64,7 +63,7 @@ interface DeclarationUsages { } class PreferConstWalker extends Lint.RuleWalker { - private inScopeLetDeclarations: ts.Map[] = []; + private inScopeLetDeclarations: ts.MapLike[] = []; private errors: Lint.RuleFailure[] = []; private markAssignment(identifier: ts.Identifier) { const name = identifier.text; @@ -172,7 +171,7 @@ class PreferConstWalker extends Lint.RuleWalker { } private visitAnyForStatement(node: ts.ForOfStatement | ts.ForInStatement) { - const names: ts.Map = {}; + const names: ts.MapLike = {}; if (isLet(node.initializer)) { if (node.initializer.kind === ts.SyntaxKind.VariableDeclarationList) { this.collectLetIdentifiers(node.initializer as ts.VariableDeclarationList, names); @@ -194,7 +193,7 @@ class PreferConstWalker extends Lint.RuleWalker { } visitBlock(node: ts.Block) { - const names: ts.Map = {}; + const names: ts.MapLike = {}; for (const statement of node.statements) { if (statement.kind === ts.SyntaxKind.VariableStatement) { this.collectLetIdentifiers((statement as ts.VariableStatement).declarationList, names); @@ -205,7 +204,7 @@ class PreferConstWalker extends Lint.RuleWalker { this.popDeclarations(); } - private collectLetIdentifiers(list: ts.VariableDeclarationList, ret: ts.Map) { + private collectLetIdentifiers(list: ts.VariableDeclarationList, ret: ts.MapLike) { for (const node of list.declarations) { if (isLet(node) && !isExported(node)) { this.collectNameIdentifiers(node, node.name, ret); @@ -213,7 +212,7 @@ class PreferConstWalker extends Lint.RuleWalker { } } - private collectNameIdentifiers(declaration: ts.VariableDeclaration, node: ts.Identifier | ts.BindingPattern, table: ts.Map) { + private collectNameIdentifiers(declaration: ts.VariableDeclaration, node: ts.Identifier | ts.BindingPattern, table: ts.MapLike) { if (node.kind === ts.SyntaxKind.Identifier) { table[(node as ts.Identifier).text] = { declaration, usages: 0 }; } @@ -222,7 +221,7 @@ class PreferConstWalker extends Lint.RuleWalker { } } - private collectBindingPatternIdentifiers(value: ts.VariableDeclaration, pattern: ts.BindingPattern, table: ts.Map) { + private collectBindingPatternIdentifiers(value: ts.VariableDeclaration, pattern: ts.BindingPattern, table: ts.MapLike) { for (const element of pattern.elements) { this.collectNameIdentifiers(value, element.name, table); } diff --git a/scripts/types/ambient.d.ts b/scripts/types/ambient.d.ts index e77e3fe8c5a..4f4b118c432 100644 --- a/scripts/types/ambient.d.ts +++ b/scripts/types/ambient.d.ts @@ -10,7 +10,7 @@ declare module "gulp-insert" { export function append(text: string | Buffer): NodeJS.ReadWriteStream; export function prepend(text: string | Buffer): NodeJS.ReadWriteStream; export function wrap(text: string | Buffer, tail: string | Buffer): NodeJS.ReadWriteStream; - export function transform(cb: (contents: string, file: {path: string}) => string): NodeJS.ReadWriteStream; // file is a vinyl file + export function transform(cb: (contents: string, file: {path: string, relative: string}) => string): NodeJS.ReadWriteStream; // file is a vinyl file } declare module "into-stream" { @@ -22,3 +22,4 @@ declare module "into-stream" { } declare module "sorcery"; +declare module "travis-fold"; diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c3dab267d02..45dbab15732 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -89,9 +89,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - const start = performance.mark(); + performance.mark("beforeBind"); binder(file, options); - performance.measure("Bind", start); + performance.mark("afterBind"); + performance.measure("Bind", "beforeBind", "afterBind"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { @@ -135,7 +136,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; - classifiableNames = {}; + classifiableNames = createMap(); symbolCount = 0; Symbol = objectAllocator.getSymbolConstructor(); @@ -183,11 +184,11 @@ namespace ts { symbol.declarations.push(node); if (symbolFlags & SymbolFlags.HasExports && !symbol.exports) { - symbol.exports = {}; + symbol.exports = createMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = {}; + symbol.members = createMap(); } if (symbolFlags & SymbolFlags.Value) { @@ -298,8 +299,10 @@ namespace ts { const name = isDefaultExport && parent ? "default" : getDeclarationName(node); let symbol: Symbol; - if (name !== undefined) { - + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, "__missing"); + } + else { // Check and see if the symbol table already has a symbol with this name. If not, // create a new symbol with this name and add it to the table. Note that we don't // give the new symbol any flags *yet*. This ensures that it will not conflict @@ -311,6 +314,11 @@ namespace ts { // declaration we have for this symbol, and then create a new symbol for this // declaration. // + // Note that when properties declared in Javascript constructors + // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. + // Always. This allows the common Javascript pattern of overwriting a prototype method + // with an bound instance method of the same type: `this.method = this.method.bind(this)` + // // If we created a new symbol, either because we didn't have a symbol with this name // in the symbol table, or we conflicted with an existing symbol, then just add this // node as the sole declaration of the new symbol. @@ -318,55 +326,57 @@ namespace ts { // Otherwise, we'll be merging into a compatible existing symbol (for example when // you have multiple 'vars' with the same name in the same container). In this case // just add this node into the declarations list of the symbol. - symbol = hasProperty(symbolTable, name) - ? symbolTable[name] - : (symbolTable[name] = createSymbol(SymbolFlags.None, name)); + symbol = symbolTable[name] || (symbolTable[name] = createSymbol(SymbolFlags.None, name)); if (name && (includes & SymbolFlags.Classifiable)) { classifiableNames[name] = name; } if (symbol.flags & excludes) { - if (node.name) { - node.name.parent = node; - } - - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - - // If the current node is a default export of some sort, then check if - // there are any other default exports that we need to error on. - // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. - if (isDefaultExport && symbol.declarations) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; + if (symbol.isReplaceableByMethod) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. + symbol = symbolTable[name] = createSymbol(SymbolFlags.None, name); } else { - // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. + if (node.name) { + node.name.parent = node; + } + + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + + // If the current node is a default export of some sort, then check if + // there are any other default exports that we need to error on. + // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. + if (isDefaultExport && symbol.declarations) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; + } + else { + // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. + forEach(symbol.declarations, declaration => { + // Error on multiple export default in the following case: + // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default + // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) + if ((declaration.flags & NodeFlags.Default) || + (declaration.kind === SyntaxKind.ExportAssignment && !(node).isExportEquals)) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; + } + }); + } + forEach(symbol.declarations, declaration => { - // Error on multiple export default in the following case: - // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default - // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) - if ((declaration.flags & NodeFlags.Default) || - (declaration.kind === SyntaxKind.ExportAssignment && !(node).isExportEquals)) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; - } + file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); }); + file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); + + symbol = createSymbol(SymbolFlags.None, name); } - - forEach(symbol.declarations, declaration => { - file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); - }); - file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); - - symbol = createSymbol(SymbolFlags.None, name); } } - else { - symbol = createSymbol(SymbolFlags.None, "__missing"); - } addDeclarationToSymbol(symbol, node, includes); symbol.parent = parent; @@ -447,7 +457,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = {}; + container.locals = createMap(); } addToContainerChain(container); } @@ -1412,7 +1422,8 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = { [symbol.name]: symbol }; + typeLiteralSymbol.members = createMap(); + typeLiteralSymbol.members[symbol.name] = symbol; } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1422,7 +1433,7 @@ namespace ts { } if (inStrictMode) { - const seen: Map = {}; + const seen = createMap(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1478,7 +1489,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = {}; + blockScopeContainer.locals = createMap(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -1940,7 +1951,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || {}; + file.symbol.globalExports = file.symbol.globalExports || createMap(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -1982,20 +1993,25 @@ namespace ts { } function bindThisPropertyAssignment(node: BinaryExpression) { - // Declare a 'member' in case it turns out the container was an ES5 class or ES6 constructor - let assignee: Node; + Debug.assert(isInJavaScriptFile(node)); + // Declare a 'member' if the container is an ES5 class or ES6 constructor if (container.kind === SyntaxKind.FunctionDeclaration || container.kind === SyntaxKind.FunctionExpression) { - assignee = container; + container.symbol.members = container.symbol.members || createMap(); + // It's acceptable for multiple 'this' assignments of the same identifier to occur + declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } else if (container.kind === SyntaxKind.Constructor) { - assignee = container.parent; + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const saveContainer = container; + container = container.parent; + const symbol = bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); + if (symbol) { + // constructor-declared symbols can be overwritten by subsequent method declarations + (symbol as Symbol).isReplaceableByMethod = true; + } + container = saveContainer; } - else { - return; - } - assignee.symbol.members = assignee.symbol.members || {}; - // It's acceptable for multiple 'this' assignments of the same identifier to occur - declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } function bindPrototypePropertyAssignment(node: BinaryExpression) { @@ -2019,7 +2035,7 @@ namespace ts { // Set up the members collection if it doesn't exist already if (!funcSymbol.members) { - funcSymbol.members = {}; + funcSymbol.members = createMap(); } // Declare the method/property @@ -2068,7 +2084,7 @@ namespace ts { // module might have an exported variable called 'prototype'. We can't allow that as // that would clash with the built-in 'prototype' for the class. const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype"); - if (hasProperty(symbol.exports, prototypeSymbol.name)) { + if (symbol.exports[prototypeSymbol.name]) { if (node.name) { node.name.parent = node; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 71fdefdc4c9..0117eadec6c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44,7 +44,7 @@ namespace ts { let symbolCount = 0; const emptyArray: any[] = []; - const emptySymbols: SymbolTable = {}; + const emptySymbols = createMap(); const compilerOptions = host.getCompilerOptions(); const languageVersion = compilerOptions.target || ScriptTarget.ES3; @@ -106,11 +106,11 @@ namespace ts { isOptionalParameter }; - const tupleTypes: Map = {}; - const unionTypes: Map = {}; - const intersectionTypes: Map = {}; - const stringLiteralTypes: Map = {}; - const numericLiteralTypes: Map = {}; + const tupleTypes = createMap(); + const unionTypes = createMap(); + const intersectionTypes = createMap(); + const stringLiteralTypes = createMap(); + const numericLiteralTypes = createMap(); const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); @@ -132,7 +132,7 @@ namespace ts { const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = {}; + emptyGenericType.instantiations = createMap(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -146,7 +146,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const globals: SymbolTable = {}; + const globals = createMap(); /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -245,7 +245,8 @@ namespace ts { NEUndefinedOrNull = 1 << 19, // x != undefined / x != null Truthy = 1 << 20, // x Falsy = 1 << 21, // !x - All = (1 << 22) - 1, + Discriminatable = 1 << 22, // May have discriminant property + All = (1 << 23) - 1, // The following members encode facts about particular kinds of types for use in the getTypeFacts function. // The presence of a particular fact means that the given test is true for some (and possibly all) values // of that kind of type. @@ -275,15 +276,15 @@ namespace ts { TrueFacts = BaseBooleanFacts | Truthy, SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts: Map = { + const typeofEQFacts = createMap({ "string": TypeFacts.TypeofEQString, "number": TypeFacts.TypeofEQNumber, "boolean": TypeFacts.TypeofEQBoolean, @@ -291,9 +292,9 @@ namespace ts { "undefined": TypeFacts.EQUndefined, "object": TypeFacts.TypeofEQObject, "function": TypeFacts.TypeofEQFunction - }; + }); - const typeofNEFacts: Map = { + const typeofNEFacts = createMap({ "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -301,19 +302,19 @@ namespace ts { "undefined": TypeFacts.NEUndefined, "object": TypeFacts.TypeofNEObject, "function": TypeFacts.TypeofNEFunction - }; + }); - const typeofTypesByName: Map = { + const typeofTypesByName = createMap({ "string": stringType, "number": numberType, "boolean": booleanType, "symbol": esSymbolType, "undefined": undefinedType - }; + }); let jsxElementType: ObjectType; /** Things we lazy load from the JSX namespace */ - const jsxTypes: Map = {}; + const jsxTypes = createMap(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -324,10 +325,10 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation: Map = {}; - const assignableRelation: Map = {}; - const comparableRelation: Map = {}; - const identityRelation: Map = {}; + const subtypeRelation = createMap(); + const assignableRelation = createMap(); + const comparableRelation = createMap(); + const identityRelation = createMap(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -341,9 +342,8 @@ namespace ts { ResolvedReturnType } - const builtinGlobals: SymbolTable = { - [undefinedSymbol.name]: undefinedSymbol - }; + const builtinGlobals = createMap(); + builtinGlobals[undefinedSymbol.name] = undefinedSymbol; initializeTypeChecker(); @@ -403,8 +403,8 @@ namespace ts { result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneSymbolTable(symbol.members); - if (symbol.exports) result.exports = cloneSymbolTable(symbol.exports); + if (symbol.members) result.members = cloneMap(symbol.members); + if (symbol.exports) result.exports = cloneMap(symbol.exports); recordMergedSymbol(result, symbol); return result; } @@ -426,11 +426,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = {}; + if (!target.members) target.members = createMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = {}; + if (!target.exports) target.exports = createMap(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -447,29 +447,17 @@ namespace ts { } } - function cloneSymbolTable(symbolTable: SymbolTable): SymbolTable { - const result: SymbolTable = {}; - for (const id in symbolTable) { - if (hasProperty(symbolTable, id)) { - result[id] = symbolTable[id]; - } - } - return result; - } - function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { for (const id in source) { - if (hasProperty(source, id)) { - if (!hasProperty(target, id)) { - target[id] = source[id]; - } - else { - let symbol = target[id]; - if (!(symbol.flags & SymbolFlags.Merged)) { - target[id] = symbol = cloneSymbol(symbol); - } - mergeSymbol(symbol, source[id]); + let targetSymbol = target[id]; + if (!targetSymbol) { + target[id] = source[id]; + } + else { + if (!(targetSymbol.flags & SymbolFlags.Merged)) { + target[id] = targetSymbol = cloneSymbol(targetSymbol); } + mergeSymbol(targetSymbol, source[id]); } } } @@ -513,14 +501,12 @@ namespace ts { function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { for (const id in source) { - if (hasProperty(source, id)) { - if (hasProperty(target, id)) { - // Error on redeclarations - forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); - } - else { - target[id] = source[id]; - } + if (target[id]) { + // Error on redeclarations + forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); + } + else { + target[id] = source[id]; } } @@ -537,7 +523,7 @@ namespace ts { function getNodeLinks(node: Node): NodeLinks { const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = {}); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 }); } function isGlobalSourceFile(node: Node) { @@ -545,18 +531,20 @@ namespace ts { } function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { - if (meaning && hasProperty(symbols, name)) { + if (meaning) { const symbol = symbols[name]; - Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { - return symbol; - } - if (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors - if (target === unknownSymbol || target.flags & meaning) { + if (symbol) { + Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { return symbol; } + if (symbol.flags & SymbolFlags.Alias) { + const target = resolveAlias(symbol); + // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors + if (target === unknownSymbol || target.flags & meaning) { + return symbol; + } + } } } // return undefined if we can't find a symbol. @@ -744,7 +732,7 @@ namespace ts { // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, // which is not the desired behavior. - if (hasProperty(moduleExports, name) && + if (moduleExports[name] && moduleExports[name].flags === SymbolFlags.Alias && getDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier)) { break; @@ -1104,9 +1092,9 @@ namespace ts { function getExportOfModule(symbol: Symbol, name: string): Symbol { if (symbol.flags & SymbolFlags.Module) { - const exports = getExportsOfSymbol(symbol); - if (hasProperty(exports, name)) { - return resolveSymbol(exports[name]); + const exportedSymbol = getExportsOfSymbol(symbol)[name]; + if (exportedSymbol) { + return resolveSymbol(exportedSymbol); } } } @@ -1424,7 +1412,7 @@ namespace ts { */ function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: Map, exportNode?: ExportDeclaration) { for (const id in source) { - if (id !== "default" && !hasProperty(target, id)) { + if (id !== "default" && !target[id]) { target[id] = source[id]; if (lookupTable && exportNode) { lookupTable[id] = { @@ -1432,7 +1420,7 @@ namespace ts { } as ExportCollisionTracker; } } - else if (lookupTable && exportNode && id !== "default" && hasProperty(target, id) && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { + else if (lookupTable && exportNode && id !== "default" && target[id] && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { if (!lookupTable[id].exportsWithDuplicate) { lookupTable[id].exportsWithDuplicate = [exportNode]; } @@ -1454,12 +1442,12 @@ namespace ts { return; } visitedSymbols.push(symbol); - const symbols = cloneSymbolTable(symbol.exports); + const symbols = cloneMap(symbol.exports); // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports["__export"]; if (exportStars) { - const nestedSymbols: SymbolTable = {}; - const lookupTable: Map = {}; + const nestedSymbols = createMap(); + const lookupTable = createMap(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1473,7 +1461,7 @@ namespace ts { for (const id in lookupTable) { const { exportsWithDuplicate } = lookupTable[id]; // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || hasProperty(symbols, id)) { + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols[id]) { continue; } for (const node of exportsWithDuplicate) { @@ -1579,13 +1567,11 @@ namespace ts { function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[]; for (const id in members) { - if (hasProperty(members, id)) { - if (!isReservedMemberName(id)) { - if (!result) result = []; - const symbol = members[id]; - if (symbolIsValue(symbol)) { - result.push(symbol); - } + if (!isReservedMemberName(id)) { + if (!result) result = []; + const symbol = members[id]; + if (symbolIsValue(symbol)) { + result.push(symbol); } } } @@ -1661,12 +1647,12 @@ namespace ts { } // If symbol is directly available by its name in the symbol table - if (isAccessible(lookUp(symbols, symbol.name))) { + if (isAccessible(symbols[symbol.name])) { return [symbol]; } // Check if symbol is any of the alias - return forEachValue(symbols, symbolFromSymbolTable => { + return forEachProperty(symbols, symbolFromSymbolTable => { if (symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.name !== "export=" && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) { @@ -1701,12 +1687,12 @@ namespace ts { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { // If symbol of this name is not available in the symbol table we are ok - if (!hasProperty(symbolTable, symbol.name)) { + let symbolFromSymbolTable = symbolTable[symbol.name]; + if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; } // If the symbol with this name is present it should refer to the symbol - let symbolFromSymbolTable = symbolTable[symbol.name]; if (symbolFromSymbolTable === symbol) { // No need to qualify return true; @@ -3124,7 +3110,7 @@ namespace ts { // Return the type implied by an object binding pattern function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type { - const members: SymbolTable = {}; + const members = createMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3254,6 +3240,13 @@ namespace ts { // * className.prototype.method = expr if (declaration.kind === SyntaxKind.BinaryExpression || declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) { + // Use JS Doc type if present on parent expression statement + if (declaration.flags & NodeFlags.JavaScriptFile) { + const typeTag = getJSDocTypeTag(declaration.parent); + if (typeTag && typeTag.typeExpression) { + return links.type = getTypeFromTypeNode(typeTag.typeExpression.type); + } + } const declaredTypes = map(symbol.declarations, decl => decl.kind === SyntaxKind.BinaryExpression ? checkExpressionCached((decl).right) : @@ -3712,7 +3705,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = {}; + (type).instantiations = createMap(); (type).instantiations[getTypeListId(type.typeParameters)] = type; (type).target = type; (type).typeArguments = type.typeParameters; @@ -3754,7 +3747,7 @@ namespace ts { if (typeParameters) { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. - links.instantiations = {}; + links.instantiations = createMap(); links.instantiations[getTypeListId(links.typeParameters)] = type; } } @@ -3775,7 +3768,7 @@ namespace ts { return expr.kind === SyntaxKind.NumericLiteral || expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.NumericLiteral || - expr.kind === SyntaxKind.Identifier && hasProperty(symbol.exports, (expr).text); + expr.kind === SyntaxKind.Identifier && !!symbol.exports[(expr).text]; } function enumHasLiteralMembers(symbol: Symbol) { @@ -3798,7 +3791,7 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes: Map = {}; + const memberTypes = createMap(); for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); @@ -3961,7 +3954,7 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result: SymbolTable = {}; + const result = createMap(); for (const symbol of symbols) { result[symbol.name] = symbol; } @@ -3971,7 +3964,7 @@ namespace ts { // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result: SymbolTable = {}; + const result = createMap(); for (const symbol of symbols) { result[symbol.name] = mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper); } @@ -3980,7 +3973,7 @@ namespace ts { function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const s of baseSymbols) { - if (!hasProperty(symbols, s.name)) { + if (!symbols[s.name]) { symbols[s.name] = s; } } @@ -4097,7 +4090,7 @@ namespace ts { } function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { - const members: SymbolTable = {}; + const members = createMap(); for (let i = 0; i < memberTypes.length; i++) { const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); symbol.type = memberTypes[i]; @@ -4320,11 +4313,9 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members[name]; + if (symbol && symbolIsValue(symbol)) { + return symbol; } } } @@ -4453,13 +4444,13 @@ namespace ts { } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: string): Symbol { - const properties = type.resolvedProperties || (type.resolvedProperties = {}); - if (hasProperty(properties, name)) { - return properties[name]; - } - const property = createUnionOrIntersectionProperty(type, name); - if (property) { - properties[name] = property; + const properties = type.resolvedProperties || (type.resolvedProperties = createMap()); + let property = properties[name]; + if (!property) { + property = createUnionOrIntersectionProperty(type, name); + if (property) { + properties[name] = property; + } } return property; } @@ -4476,11 +4467,9 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members[name]; + if (symbol && symbolIsValue(symbol)) { + return symbol; } if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { const symbol = getPropertyOfObjectType(globalFunctionType, name); @@ -4948,24 +4937,27 @@ namespace ts { } function getTypeListId(types: Type[]) { + let result = ""; if (types) { - switch (types.length) { - case 1: - return "" + types[0].id; - case 2: - return types[0].id + "," + types[1].id; - default: - let result = ""; - for (let i = 0; i < types.length; i++) { - if (i > 0) { - result += ","; - } - result += types[i].id; - } - return result; + const length = types.length; + let i = 0; + while (i < length) { + const startId = types[i].id; + let count = 1; + while (i + count < length && types[i + count].id === startId + count) { + count++; + } + if (result.length) { + result += ","; + } + result += startId; + if (count > 1) { + result += ":" + count; + } + i += count; } } - return ""; + return result; } // This function is used to propagate certain flags when creating new object type references and union types. @@ -5348,12 +5340,12 @@ namespace ts { } } - // We deduplicate the constituent types based on object identity. If the subtypeReduction flag is - // specified we also reduce the constituent type set to only include types that aren't subtypes of - // other types. Subtype reduction is expensive for large union types and is possible only when union + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction + // flag is specified we also reduce the constituent type set to only include types that aren't subtypes + // of other types. Subtype reduction is expensive for large union types and is possible only when union // types are known not to circularly reference themselves (as is the case with union types created by // expression constructs such as array literals and the || and ?: operators). Named types can - // circularly reference themselves and therefore cannot be deduplicated during their declaration. + // 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 { if (types.length === 0) { @@ -5375,15 +5367,23 @@ namespace ts { typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType : neverType; } - else if (typeSet.length === 1) { - return typeSet[0]; + return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments); + } + + // This function assumes the constituent type list is sorted and deduplicated. + function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + if (types.length === 0) { + return neverType; } - const id = getTypeListId(typeSet); + if (types.length === 1) { + return types[0]; + } + const id = getTypeListId(types); let type = unionTypes[id]; if (!type) { - const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); + const propagatedFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); - type.types = typeSet; + type.types = types; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; } @@ -5475,7 +5475,7 @@ namespace ts { function getLiteralTypeForText(flags: TypeFlags, text: string) { const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes; - return hasProperty(map, text) ? map[text] : map[text] = createLiteralType(flags, text); + return map[text] || (map[text] = createLiteralType(flags, text)); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -5552,6 +5552,8 @@ namespace ts { return getTypeFromThisTypeNode(node); case SyntaxKind.LiteralType: return getTypeFromLiteralTypeNode(node); + case SyntaxKind.JSDocLiteralType: + return getTypeFromLiteralTypeNode((node).literal); case SyntaxKind.TypeReference: case SyntaxKind.JSDocTypeReference: return getTypeFromTypeReference(node); @@ -6590,7 +6592,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = {}; + maybeStack[depth] = createMap(); maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; const saveExpandingFlags = expandingFlags; @@ -6621,7 +6623,7 @@ namespace ts { const maybeCache = maybeStack[depth]; // If result is definitely true, copy assumptions to global cache, else copy to next level up const destinationCache = (result === Ternary.True || depth === 0) ? relation : maybeStack[depth - 1]; - copyMap(maybeCache, destinationCache); + copyProperties(maybeCache, destinationCache); } else { // A false result goes straight into global cache (when something is false under assumptions it @@ -7231,7 +7233,7 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members: SymbolTable = {}; + const members = createMap(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); @@ -7455,7 +7457,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited: Map = {}; + const visited = createMap(); inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -7589,7 +7591,7 @@ namespace ts { return; } const key = source.id + "," + target.id; - if (hasProperty(visited, key)) { + if (visited[key]) { return; } visited[key] = true; @@ -7845,18 +7847,25 @@ namespace ts { } function isDiscriminantProperty(type: Type, name: string) { - if (type) { - const nonNullType = getNonNullableType(type); - if (nonNullType.flags & TypeFlags.Union) { - const prop = getPropertyOfType(nonNullType, name); - if (prop && prop.flags & SymbolFlags.SyntheticProperty) { - if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = !(prop).hasCommonType && - isUnitUnionType(getTypeOfSymbol(prop)); - } - return (prop).isDiscriminantProperty; + if (type && type.flags & TypeFlags.Union) { + let prop = getPropertyOfType(type, name); + if (!prop) { + // The type may be a union that includes nullable or primitive types. If filtering + // those out produces a different type, get the property from that type instead. + // Effectively, we're checking if this *could* be a discriminant property once nullable + // and primitive types are removed by other type guards. + const filteredType = getTypeWithFacts(type, TypeFacts.Discriminatable); + if (filteredType !== type && filteredType.flags & TypeFlags.Union) { + prop = getPropertyOfType(filteredType, name); } } + if (prop && prop.flags & SymbolFlags.SyntheticProperty) { + if ((prop).isDiscriminantProperty === undefined) { + (prop).isDiscriminantProperty = !(prop).hasCommonType && + isUnitUnionType(getTypeOfSymbol(prop)); + } + return (prop).isDiscriminantProperty; + } } return false; } @@ -7904,10 +7913,10 @@ namespace ts { // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType !== assignedType && declaredType.flags & TypeFlags.Union) { - const reducedTypes = filter(declaredType.types, t => typeMaybeAssignableTo(assignedType, t)); - if (reducedTypes.length) { - return reducedTypes.length === 1 ? reducedTypes[0] : getUnionType(reducedTypes); + if (declaredType !== assignedType) { + const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + if (reducedType !== neverType) { + return reducedType; } } return declaredType; @@ -7921,6 +7930,14 @@ namespace ts { return result; } + function isFunctionObjectType(type: ObjectType): boolean { + // We do a quick check for a "bind" property before performing the more expensive subtype + // check. This gives us a quicker out in the common case where an object type is not a function. + const resolved = resolveStructuredTypeMembers(type); + return !!(resolved.callSignatures.length || resolved.constructSignatures.length || + resolved.members["bind"] && isTypeSubtypeOf(type, globalFunctionType)); + } + function getTypeFacts(type: Type): TypeFacts { const flags = type.flags; if (flags & TypeFlags.String) { @@ -7949,8 +7966,7 @@ namespace ts { type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } if (flags & TypeFlags.ObjectType) { - const resolved = resolveStructuredTypeMembers(type); - return resolved.callSignatures.length || resolved.constructSignatures.length || isTypeSubtypeOf(type, globalFunctionType) ? + return isFunctionObjectType(type) ? strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } @@ -7974,26 +7990,7 @@ namespace ts { } function getTypeWithFacts(type: Type, include: TypeFacts) { - if (!(type.flags & TypeFlags.Union)) { - return getTypeFacts(type) & include ? type : neverType; - } - let firstType: Type; - let types: Type[]; - for (const t of (type as UnionType).types) { - if (getTypeFacts(t) & include) { - if (!firstType) { - firstType = t; - } - else { - if (!types) { - types = [firstType]; - } - types.push(t); - } - } - } - return types ? getUnionType(types) : - firstType ? firstType : neverType; + return filterType(type, t => (getTypeFacts(t) & include) !== 0); } function getTypeWithDefault(type: Type, defaultExpression: Expression) { @@ -8169,9 +8166,12 @@ namespace ts { } function filterType(type: Type, f: (t: Type) => boolean): Type { - return type.flags & TypeFlags.Union ? - getUnionType(filter((type).types, f)) : - f(type) ? type : neverType; + if (type.flags & TypeFlags.Union) { + const types = (type).types; + const filtered = filter(types, f); + return filtered === types ? type : getUnionTypeFromSortedList(filtered); + } + return f(type) ? type : neverType; } function isIncomplete(flowType: FlowType) { @@ -8186,7 +8186,7 @@ namespace ts { return incomplete ? { flags: 0, type } : type; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { let key: string; if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; @@ -8238,7 +8238,7 @@ namespace ts { else if (flow.flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).container; - if (container && includeOuterFunctions) { + if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression) { flow = container.flowNode; continue; } @@ -8346,7 +8346,7 @@ namespace ts { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {}); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); if (!key) { key = getFlowCacheKey(reference); } @@ -8509,16 +8509,16 @@ namespace ts { } if (assumeTrue && !(type.flags & TypeFlags.Union)) { // We narrow a non-union type to an exact primitive type if the non-union type - // is a supertype of that primtive type. For example, type 'any' can be narrowed + // is a supertype of that primitive type. For example, type 'any' can be narrowed // to one of the primitive types. - const targetType = getProperty(typeofTypesByName, literal.text); + const targetType = typeofTypesByName[literal.text]; if (targetType && isTypeSubtypeOf(targetType, type)) { return targetType; } } const facts = assumeTrue ? - getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject : - getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject; + typeofEQFacts[literal.text] || TypeFacts.TypeofEQHostObject : + typeofNEFacts[literal.text] || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } @@ -8549,10 +8549,6 @@ namespace ts { } return type; } - // We never narrow type any in an instanceof guard - if (isTypeAny(type)) { - return type; - } // Check that right operand is a function type with a prototype property const rightType = checkExpression(expr.right); @@ -8570,6 +8566,11 @@ namespace ts { } } + // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { + return type; + } + if (!targetType) { // Target type is type of construct signature let constructSignatures: Signature[]; @@ -8598,9 +8599,9 @@ namespace ts { // If the current type is a union type, remove all constituents that couldn't be instances of // the candidate type. If one or more constituents remain, return a union of those. if (type.flags & TypeFlags.Union) { - const assignableConstituents = filter((type).types, t => isTypeInstanceOf(t, candidate)); - if (assignableConstituents.length) { - return getUnionType(assignableConstituents); + const assignableType = filterType(type, t => isTypeInstanceOf(t, candidate)); + if (assignableType !== neverType) { + return assignableType; } } // If the candidate type is a subtype of the target type, narrow to the candidate type. @@ -8616,7 +8617,7 @@ namespace ts { } function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { - if (type.flags & TypeFlags.Any || !hasMatchingArgument(callExpression, reference)) { + if (!hasMatchingArgument(callExpression, reference)) { return type; } const signature = getResolvedSignature(callExpression); @@ -8624,6 +8625,12 @@ namespace ts { if (!predicate) { return type; } + + // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) { + return type; + } + if (isIdentifierTypePredicate(predicate)) { const predicateArgument = callExpression.arguments[predicate.parameterIndex]; if (predicateArgument) { @@ -8709,21 +8716,52 @@ namespace ts { function getControlFlowContainer(node: Node): Node { while (true) { node = node.parent; - if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration) { + if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration) { return node; } } } - function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) { - const declarationContainer = getControlFlowContainer(declaration); - let container = getControlFlowContainer(reference); - while (container !== declarationContainer && - (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) && - (includeOuterFunctions || getImmediatelyInvokedFunctionExpression(container))) { - container = getControlFlowContainer(container); + // Check if a parameter is assigned anywhere within its declaring function. + function isParameterAssigned(symbol: Symbol) { + const func = getRootDeclaration(symbol.valueDeclaration).parent; + const links = getNodeLinks(func); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(func)) { + markParameterAssignments(func); + } + } + return symbol.isAssigned || false; + } + + function hasParentWithAssignmentsMarked(node: Node) { + while (true) { + node = node.parent; + if (!node) { + return false; + } + if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) { + return true; + } + } + } + + function markParameterAssignments(node: Node) { + if (node.kind === SyntaxKind.Identifier) { + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node); + if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { + symbol.isAssigned = true; + } + } + } + else { + forEachChild(node, markParameterAssignments); } - return container === declarationContainer; } function checkIdentifier(node: Identifier): Type { @@ -8778,15 +8816,35 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); const type = getTypeOfSymbol(localOrExportSymbol); - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) { + const declaration = localOrExportSymbol.valueDeclaration; + // We only narrow variables and parameters occurring in a non-assignment position. For all other + // entities we simply return the declared type. + if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node) || !declaration) { return type; } - const declaration = localOrExportSymbol.valueDeclaration; - const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol); - const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration || - getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) || - !isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions); - const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions); + // The declaration container is the innermost function that encloses the declaration of the variable + // or parameter. The flow container is the innermost function starting with which we analyze the control + // flow graph to determine the control flow based type. + const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; + const declarationContainer = getControlFlowContainer(declaration); + let flowContainer = getControlFlowContainer(node); + // 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. + while (flowContainer !== declarationContainer && + (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) && + (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + flowContainer = getControlFlowContainer(flowContainer); + } + // 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 = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter || + flowContainer !== declarationContainer || isInAmbientContext(declaration); + const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); + // A variable is considered uninitialized when it is possible to analyze the entire control flow graph + // from declaration to use, and when the variable's declared type doesn't include undefined but the + // control flow based type does include undefined. if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors @@ -9039,7 +9097,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true); + return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer*/ undefined); } if (isInJavaScriptFile(node)) { @@ -9450,6 +9508,11 @@ namespace ts { const binaryExpression = node.parent; const operator = binaryExpression.operatorToken.kind; if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + // Don't do this for special property assignments to avoid circularity + if (getSpecialPropertyAssignmentKind(binaryExpression) !== SpecialPropertyAssignmentKind.None) { + return undefined; + } + // In an assignment expression, the right operand is contextually typed by the type of the left operand. if (node === binaryExpression.right) { return checkExpression(binaryExpression.left); @@ -9941,7 +10004,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable: SymbolTable = {}; + const propertiesTable = createMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10032,7 +10095,7 @@ namespace ts { // type with those properties for which the binding pattern specifies a default value. if (contextualTypeHasPattern) { for (const prop of getPropertiesOfType(contextualType)) { - if (!hasProperty(propertiesTable, prop.name)) { + if (!propertiesTable[prop.name]) { if (!(prop.flags & SymbolFlags.Optional)) { error(prop.valueDeclaration || (prop).bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); @@ -10122,10 +10185,9 @@ namespace ts { const correspondingPropSymbol = getPropertyOfType(elementAttributesType, node.name.text); correspondingPropType = correspondingPropSymbol && getTypeOfSymbol(correspondingPropSymbol); if (isUnhyphenatedJsxName(node.name.text)) { - // Maybe there's a string indexer? - const indexerType = getIndexTypeOfType(elementAttributesType, IndexKind.String); - if (indexerType) { - correspondingPropType = indexerType; + const attributeType = getTypeOfPropertyOfType(elementAttributesType, getTextOfPropertyName(node.name)) || getIndexTypeOfType(elementAttributesType, IndexKind.String); + if (attributeType) { + correspondingPropType = attributeType; } else { // If there's no corresponding property with this name, error @@ -10160,7 +10222,7 @@ namespace ts { for (const prop of props) { // Is there a corresponding property in the element attributes type? Skip checking of properties // that have already been assigned to, as these are not actually pushed into the resulting type - if (!hasProperty(nameTable, prop.name)) { + if (!nameTable[prop.name]) { const targetPropSym = getPropertyOfType(elementAttributesType, prop.name); if (targetPropSym) { const msg = chainDiagnosticMessages(undefined, Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property, prop.name); @@ -10482,7 +10544,7 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable: Map = {}; + const nameTable = createMap(); // Process this array in right-to-left order so we know which // attributes (mostly from spreads) are being overwritten and // thus should have their types ignored @@ -10506,7 +10568,7 @@ namespace ts { const targetProperties = getPropertiesOfType(targetAttributesType); for (let i = 0; i < targetProperties.length; i++) { if (!(targetProperties[i].flags & SymbolFlags.Optional) && - !hasProperty(nameTable, targetProperties[i].name)) { + !nameTable[targetProperties[i].name]) { error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); } @@ -10700,7 +10762,7 @@ namespace ts { !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false); + return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined); } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { @@ -10825,10 +10887,11 @@ namespace ts { } // Check for compatible indexer types. - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) { + const allowedNullableFlags = strictNullChecks ? 0 : TypeFlags.Nullable; + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol | allowedNullableFlags)) { // Try to use a number indexer. - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | allowedNullableFlags) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { const numberIndexInfo = getIndexInfoOfType(objectType, IndexKind.Number); if (numberIndexInfo) { getNodeLinks(node).resolvedIndexInfo = numberIndexInfo; @@ -13788,15 +13851,19 @@ namespace ts { } function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const getter = 1, setter = 2, property = getter | setter; + const enum Accessor { + Getter = 1, + Setter = 2, + Property = Getter | Setter + } - const instanceNames: Map = {}; - const staticNames: Map = {}; + const instanceNames = createMap(); + const staticNames = createMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { if (isParameterPropertyDeclaration(param)) { - addName(instanceNames, param.name, (param.name as Identifier).text, property); + addName(instanceNames, param.name, (param.name as Identifier).text, Accessor.Property); } } } @@ -13808,24 +13875,24 @@ namespace ts { if (memberName) { switch (member.kind) { case SyntaxKind.GetAccessor: - addName(names, member.name, memberName, getter); + addName(names, member.name, memberName, Accessor.Getter); break; case SyntaxKind.SetAccessor: - addName(names, member.name, memberName, setter); + addName(names, member.name, memberName, Accessor.Setter); break; case SyntaxKind.PropertyDeclaration: - addName(names, member.name, memberName, property); + addName(names, member.name, memberName, Accessor.Property); break; } } } } - function addName(names: Map, location: Node, name: string, meaning: number) { - if (hasProperty(names, name)) { - const prev = names[name]; + function addName(names: Map, location: Node, name: string, meaning: Accessor) { + const prev = names[name]; + if (prev) { if (prev & meaning) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } @@ -13840,7 +13907,7 @@ namespace ts { } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names: Map = {}; + const names = createMap(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -13854,7 +13921,7 @@ namespace ts { continue; } - if (hasProperty(names, memberName)) { + if (names[memberName]) { error(member.symbol.valueDeclaration.name, Diagnostics.Duplicate_identifier_0, memberName); error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } @@ -15066,22 +15133,20 @@ namespace ts { function checkUnusedLocalsAndParameters(node: Node): void { if (node.parent.kind !== SyntaxKind.InterfaceDeclaration && noUnusedIdentifiers && !isInAmbientContext(node)) { for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced) { - if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { - const parameter = local.valueDeclaration; - if (compilerOptions.noUnusedParameters && - !isParameterPropertyDeclaration(parameter) && - !parameterIsThisKeyword(parameter) && - !parameterNameStartsWithUnderscore(parameter)) { - error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } - } - else if (compilerOptions.noUnusedLocals) { - forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + const local = node.locals[key]; + if (!local.isReferenced) { + if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { + const parameter = local.valueDeclaration; + if (compilerOptions.noUnusedParameters && + !isParameterPropertyDeclaration(parameter) && + !parameterIsThisKeyword(parameter) && + !parameterNameStartsWithUnderscore(parameter)) { + error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } + else if (compilerOptions.noUnusedLocals) { + forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + } } } } @@ -15107,7 +15172,7 @@ namespace ts { else if (member.kind === SyntaxKind.Constructor) { for (const parameter of (member).parameters) { if (!parameter.symbol.isReferenced && parameter.flags & NodeFlags.Private) { - error(parameter.name, Diagnostics._0_is_declared_but_never_used, parameter.symbol.name); + error(parameter.name, Diagnostics.Property_0_is_declared_but_never_used, parameter.symbol.name); } } } @@ -15138,13 +15203,11 @@ namespace ts { function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile): void { if (compilerOptions.noUnusedLocals && !isInAmbientContext(node)) { for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced && !local.exportSymbol) { - for (const declaration of local.declarations) { - if (!isAmbientModule(declaration)) { - error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } + const local = node.locals[key]; + if (!local.isReferenced && !local.exportSymbol) { + for (const declaration of local.declarations) { + if (!isAmbientModule(declaration)) { + error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } } @@ -16153,7 +16216,7 @@ namespace ts { else { const identifierName = (catchClause.variableDeclaration.name).text; const locals = catchClause.block.locals; - if (locals && hasProperty(locals, identifierName)) { + if (locals) { const localSymbol = locals[identifierName]; if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName); @@ -16574,18 +16637,18 @@ namespace ts { return true; } - const seen: Map<{ prop: Symbol; containingType: Type }> = {}; + const seen = createMap<{ prop: Symbol; containingType: Type }>(); forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { - if (!hasProperty(seen, prop.name)) { + const existing = seen[prop.name]; + if (!existing) { seen[prop.name] = { prop: prop, containingType: base }; } else { - const existing = seen[prop.name]; const isInheritedProperty = existing.containingType !== type; if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { ok = false; @@ -17032,11 +17095,6 @@ namespace ts { } } - if (compilerOptions.noImplicitAny && !node.body) { - // Ambient shorthand module is an implicit any - reportImplicitAnyError(node, anyType); - } - if (node.body) { checkSourceElement(node.body); if (!isGlobalScopeAugmentation(node)) { @@ -17540,11 +17598,10 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - const start = performance.mark(); - + performance.mark("beforeCheck"); checkSourceFileWorker(node); - - performance.measure("Check", start); + performance.mark("afterCheck"); + performance.measure("Check", "beforeCheck", "afterCheck"); } // Fully type check a source file and collect the relevant diagnostics. @@ -17644,7 +17701,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols: SymbolTable = {}; + const symbols = createMap(); let memberFlags: NodeFlags = 0; if (isInsideWithStatementBody(location)) { @@ -17722,7 +17779,7 @@ namespace ts { // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and // it will not copy symbol with reserved name to the array - if (!hasProperty(symbols, id)) { + if (!symbols[id]) { symbols[id] = symbol; } } @@ -18141,7 +18198,7 @@ namespace ts { const propsByName = createSymbolTable(getPropertiesOfType(type)); if (getSignaturesOfType(type, SignatureKind.Call).length || getSignaturesOfType(type, SignatureKind.Construct).length) { forEach(getPropertiesOfType(globalFunctionType), p => { - if (!hasProperty(propsByName, p.name)) { + if (!propsByName[p.name]) { propsByName[p.name] = p; } }); @@ -18198,7 +18255,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachValue(getExportsOfModule(moduleSymbol), isValue); + : forEachProperty(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; @@ -18489,7 +18546,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return hasProperty(globals, name); + return !!globals[name]; } function getReferencedValueSymbol(reference: Identifier): Symbol { @@ -18513,9 +18570,6 @@ namespace ts { // populate reverse mapping: file path -> type reference directive that was resolved to this file fileToDirective = createFileMap(); for (const key in resolvedTypeReferenceDirectives) { - if (!hasProperty(resolvedTypeReferenceDirectives, key)) { - continue; - } const resolvedDirective = resolvedTypeReferenceDirectives[key]; if (!resolvedDirective) { continue; @@ -19304,7 +19358,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen: Map = {}; + const seen = createMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -19366,7 +19420,7 @@ namespace ts { continue; } - if (!hasProperty(seen, effectiveName)) { + if (!seen[effectiveName]) { seen[effectiveName] = currentKind; } else { @@ -19390,7 +19444,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen: Map = {}; + const seen = createMap(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -19398,7 +19452,7 @@ namespace ts { const jsxAttr = (attr); const name = jsxAttr.name; - if (!hasProperty(seen, name.text)) { + if (!seen[name.text]) { seen[name.text] = true; } else { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d33..616bf5e70c0 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -61,10 +61,10 @@ namespace ts { }, { name: "jsx", - type: { + type: createMap({ "preserve": JsxEmit.Preserve, "react": JsxEmit.React - }, + }), paramType: Diagnostics.KIND, description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, }, @@ -91,7 +91,7 @@ namespace ts { { name: "module", shortName: "m", - type: { + type: createMap({ "none": ModuleKind.None, "commonjs": ModuleKind.CommonJS, "amd": ModuleKind.AMD, @@ -99,16 +99,16 @@ namespace ts { "umd": ModuleKind.UMD, "es6": ModuleKind.ES6, "es2015": ModuleKind.ES2015, - }, + }), description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, paramType: Diagnostics.KIND, }, { name: "newLine", - type: { + type: createMap({ "crlf": NewLineKind.CarriageReturnLineFeed, "lf": NewLineKind.LineFeed - }, + }), description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, paramType: Diagnostics.NEWLINE, }, @@ -250,12 +250,12 @@ namespace ts { { name: "target", shortName: "t", - type: { + type: createMap({ "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5, "es6": ScriptTarget.ES6, "es2015": ScriptTarget.ES2015, - }, + }), description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, paramType: Diagnostics.VERSION, }, @@ -284,10 +284,10 @@ namespace ts { }, { name: "moduleResolution", - type: { + type: createMap({ "node": ModuleResolutionKind.NodeJs, "classic": ModuleResolutionKind.Classic, - }, + }), description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, }, { @@ -392,7 +392,7 @@ namespace ts { type: "list", element: { name: "lib", - type: { + type: createMap({ // JavaScript only "es5": "lib.es5.d.ts", "es6": "lib.es2015.d.ts", @@ -417,7 +417,7 @@ namespace ts { "es2016.array.include": "lib.es2016.array.include.d.ts", "es2017.object": "lib.es2017.object.d.ts", "es2017.sharedmemory": "lib.es2017.sharedmemory.d.ts" - }, + }), }, description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon }, @@ -470,8 +470,8 @@ namespace ts { return optionNameMapCache; } - const optionNameMap: Map = {}; - const shortOptionNames: Map = {}; + const optionNameMap = createMap(); + const shortOptionNames = createMap(); forEach(optionDeclarations, option => { optionNameMap[option.name.toLowerCase()] = option; if (option.shortName) { @@ -486,10 +486,9 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; - forEachKey(opt.type, key => { + for (const key in opt.type) { namesOfType.push(` '${key}'`); - }); - + } return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } @@ -497,7 +496,7 @@ namespace ts { export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = trimString((value || "")).toLowerCase(); const map = opt.type; - if (hasProperty(map, key)) { + if (key in map) { return map[key]; } else { @@ -551,11 +550,11 @@ namespace ts { s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); // Try to translate short option names to their full equivalents. - if (hasProperty(shortOptionNames, s)) { + if (s in shortOptionNames) { s = shortOptionNames[s]; } - if (hasProperty(optionNameMap, s)) { + if (s in optionNameMap) { const opt = optionNameMap[s]; if (opt.isTSConfigOnly) { @@ -811,7 +810,7 @@ namespace ts { const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); for (const id in jsonOptions) { - if (hasProperty(optionNameMap, id)) { + if (id in optionNameMap) { const opt = optionNameMap[id]; defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } @@ -848,7 +847,7 @@ namespace ts { function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = value.toLowerCase(); - if (hasProperty(opt.type, key)) { + if (key in opt.type) { return opt.type[key]; } else { @@ -958,12 +957,12 @@ namespace ts { // Literal file names (provided via the "files" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map later when when including // wildcard paths. - const literalFileMap: Map = {}; + const literalFileMap = createMap(); // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard, and to handle extension priority. - const wildcardFileMap: Map = {}; + const wildcardFileMap = createMap(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -1011,7 +1010,7 @@ namespace ts { removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); const key = keyMapper(file); - if (!hasProperty(literalFileMap, key) && !hasProperty(wildcardFileMap, key)) { + if (!(key in literalFileMap) && !(key in wildcardFileMap)) { wildcardFileMap[key] = file; } } @@ -1063,7 +1062,7 @@ namespace ts { // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: Map = {}; + const wildcardDirectories = createMap(); if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { @@ -1076,7 +1075,7 @@ namespace ts { if (match) { const key = useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(); const flags = watchRecursivePattern.test(name) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None; - const existingFlags = getProperty(wildcardDirectories, key); + const existingFlags = wildcardDirectories[key]; if (existingFlags === undefined || existingFlags < flags) { wildcardDirectories[key] = flags; if (flags === WatchDirectoryFlags.Recursive) { @@ -1088,11 +1087,9 @@ namespace ts { // Remove any subpaths under an existing recursively watched directory. for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } @@ -1115,7 +1112,7 @@ namespace ts { for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { const higherPriorityExtension = extensions[i]; const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); - if (hasProperty(literalFiles, higherPriorityPath) || hasProperty(wildcardFiles, higherPriorityPath)) { + if (higherPriorityPath in literalFiles || higherPriorityPath in wildcardFiles) { return true; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 549410159b0..40341929c02 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -19,8 +19,28 @@ namespace ts { True = -1 } + const createObject = Object.create; + + export function createMap(template?: MapLike): Map { + const map: Map = createObject(null); // tslint:disable-line:no-null-keyword + + // Using 'delete' on an object causes V8 to put the object in dictionary mode. + // This disables creation of hidden classes, which are expensive when an object is + // constantly changing shape. + map["__"] = undefined; + delete map["__"]; + + // Copies keys/values from template. Note that for..in will not throw if + // template is undefined, and instead will just exit the loop. + for (const key in template) if (hasOwnProperty.call(template, key)) { + map[key] = template[key]; + } + + return map; + } + export function createFileMap(keyMapper?: (key: string) => string): FileMap { - let files: Map = {}; + let files = createMap(); return { get, set, @@ -46,7 +66,7 @@ namespace ts { } function contains(path: Path) { - return hasProperty(files, toKey(path)); + return toKey(path) in files; } function remove(path: Path) { @@ -55,7 +75,7 @@ namespace ts { } function clear() { - files = {}; + files = createMap(); } function toKey(path: Path): string { @@ -147,17 +167,44 @@ namespace ts { return count; } + /** + * Filters an array by a predicate function. Returns the same array instance if the predicate is + * true for all elements, otherwise returns a new array instance containing the filtered subset. + */ export function filter(array: T[], f: (x: T) => boolean): T[] { - let result: T[]; if (array) { - result = []; - for (const item of array) { - if (f(item)) { - result.push(item); + const len = array.length; + let i = 0; + while (i < len && f(array[i])) i++; + if (i < len) { + const result = array.slice(0, i); + i++; + while (i < len) { + const item = array[i]; + if (f(item)) { + result.push(item); + } + i++; } + return result; } } - return result; + return array; + } + + export function removeWhere(array: T[], f: (x: T) => boolean): boolean { + let outIndex = 0; + for (const item of array) { + if (!f(item)) { + array[outIndex] = item; + outIndex++; + } + } + if (outIndex !== array.length) { + array.length = outIndex; + return true; + } + return false; } export function filterMutate(array: T[], f: (x: T) => boolean): void { @@ -322,82 +369,142 @@ namespace ts { const hasOwnProperty = Object.prototype.hasOwnProperty; - export function hasProperty(map: Map, key: string): boolean { + /** + * Indicates whether a map-like contains an own property with the specified key. + * + * NOTE: This is intended for use only with MapLike objects. For Map objects, use + * the 'in' operator. + * + * @param map A map-like. + * @param key A property key. + */ + export function hasProperty(map: MapLike, key: string): boolean { return hasOwnProperty.call(map, key); } - export function getKeys(map: Map): string[] { + /** + * Gets the value of an owned property in a map-like. + * + * NOTE: This is intended for use only with MapLike objects. For Map objects, use + * an indexer. + * + * @param map A map-like. + * @param key A property key. + */ + export function getProperty(map: MapLike, key: string): T | undefined { + return hasOwnProperty.call(map, key) ? map[key] : undefined; + } + + /** + * Gets the owned, enumerable property keys of a map-like. + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * Object.keys instead as it offers better performance. + * + * @param map A map-like. + */ + export function getOwnKeys(map: MapLike): string[] { const keys: string[] = []; - for (const key in map) { + for (const key in map) if (hasOwnProperty.call(map, key)) { keys.push(key); } return keys; } - export function getProperty(map: Map, key: string): T | undefined { - return hasProperty(map, key) ? map[key] : undefined; + /** + * Enumerates the properties of a Map, invoking a callback and returning the first truthy result. + * + * @param map A map for which properties should be enumerated. + * @param callback A callback to invoke for each property. + */ + export function forEachProperty(map: Map, callback: (value: T, key: string) => U): U { + let result: U; + for (const key in map) { + if (result = callback(map[key], key)) break; + } + return result; } - export function getOrUpdateProperty(map: Map, key: string, makeValue: () => T): T { - return hasProperty(map, key) ? map[key] : map[key] = makeValue(); + /** + * Returns true if a Map has some matching property. + * + * @param map A map whose properties should be tested. + * @param predicate An optional callback used to test each property. + */ + export function someProperties(map: Map, predicate?: (value: T, key: string) => boolean) { + for (const key in map) { + if (!predicate || predicate(map[key], key)) return true; + } + return false; } - export function isEmpty(map: Map) { - for (const id in map) { - if (hasProperty(map, id)) { - return false; - } + /** + * Performs a shallow copy of the properties from a source Map to a target MapLike + * + * @param source A map from which properties should be copied. + * @param target A map to which properties should be copied. + */ + export function copyProperties(source: Map, target: MapLike): void { + for (const key in source) { + target[key] = source[key]; + } + } + + /** + * Reduce the properties of a map. + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * reduceOwnProperties instead as it offers better runtime safety. + * + * @param map The map to reduce + * @param callback An aggregation function that is called for each entry in the map + * @param initial The initial value for the reduction. + */ + export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + let result = initial; + for (const key in map) { + result = callback(result, map[key], String(key)); + } + return result; + } + + /** + * Reduce the properties defined on a map-like (but not from its prototype chain). + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * reduceProperties instead as it offers better performance. + * + * @param map The map-like to reduce + * @param callback An aggregation function that is called for each entry in the map + * @param initial The initial value for the reduction. + */ + export function reduceOwnProperties(map: MapLike, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + let result = initial; + for (const key in map) if (hasOwnProperty.call(map, key)) { + result = callback(result, map[key], String(key)); + } + return result; + } + + /** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @param left A map-like whose properties should be compared. + * @param right A map-like whose properties should be compared. + */ + export function equalOwnProperties(left: MapLike, right: MapLike, equalityComparer?: (left: T, right: T) => boolean) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) if (hasOwnProperty.call(left, key)) { + if (!hasOwnProperty.call(right, key) === undefined) return false; + if (equalityComparer ? !equalityComparer(left[key], right[key]) : left[key] !== right[key]) return false; + } + for (const key in right) if (hasOwnProperty.call(right, key)) { + if (!hasOwnProperty.call(left, key)) return false; } return true; } - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - result[id] = (object)[id]; - } - return result; - } - - export function extend, T2 extends Map<{}>>(first: T1 , second: T2): T1 & T2 { - const result: T1 & T2 = {}; - for (const id in first) { - (result as any)[id] = first[id]; - } - for (const id in second) { - if (!hasProperty(result, id)) { - (result as any)[id] = second[id]; - } - } - return result; - } - - export function forEachValue(map: Map, callback: (value: T) => U): U { - let result: U; - for (const id in map) { - if (result = callback(map[id])) break; - } - return result; - } - - export function forEachKey(map: Map, callback: (key: string) => U): U { - let result: U; - for (const id in map) { - if (result = callback(id)) break; - } - return result; - } - - export function lookUp(map: Map, key: string): T { - return hasProperty(map, key) ? map[key] : undefined; - } - - export function copyMap(source: Map, target: Map): void { - for (const p in source) { - target[p] = source[p]; - } - } - /** * Creates a map from the elements of an array. * @@ -408,33 +515,40 @@ namespace ts { * the same key with the given 'makeKey' function, then the element with the higher * index in the array will be the one associated with the produced key. */ - export function arrayToMap(array: T[], makeKey: (value: T) => string): Map { - const result: Map = {}; - - forEach(array, value => { - result[makeKey(value)] = value; - }); - + export function arrayToMap(array: T[], makeKey: (value: T) => string): Map; + export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue: (value: T) => U): Map; + export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue?: (value: T) => U): Map { + const result = createMap(); + for (const value of array) { + result[makeKey(value)] = makeValue ? makeValue(value) : value; + } return result; } - /** - * Reduce the properties of a map. - * - * @param map The map to reduce - * @param callback An aggregation function that is called for each entry in the map - * @param initial The initial value for the reduction. - */ - export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { - let result = initial; - if (map) { - for (const key in map) { - if (hasProperty(map, key)) { - result = callback(result, map[key], String(key)); - } + export function cloneMap(map: Map) { + const clone = createMap(); + copyProperties(map, clone); + return clone; + } + + export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + if (hasOwnProperty.call(object, id)) { + result[id] = (object)[id]; } } + return result; + } + export function extend(first: T1 , second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in second) if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[id]; + } + for (const id in first) if (hasOwnProperty.call(first, id)) { + (result as any)[id] = (first as any)[id]; + } return result; } @@ -465,9 +579,7 @@ namespace ts { export let localizedDiagnosticMessages: Map = undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { - return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] - ? localizedDiagnosticMessages[message.key] - : message.message; + return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] || message.message; } export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: any[]): Diagnostic; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index c93784cd9e0..3642ee7f10a 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -157,9 +157,7 @@ namespace ts { if (usedTypeDirectiveReferences) { for (const directive in usedTypeDirectiveReferences) { - if (hasProperty(usedTypeDirectiveReferences, directive)) { - referencesOutput += `/// ${newLine}`; - } + referencesOutput += `/// ${newLine}`; } } @@ -269,10 +267,10 @@ namespace ts { } if (!usedTypeDirectiveReferences) { - usedTypeDirectiveReferences = {}; + usedTypeDirectiveReferences = createMap(); } for (const directive of typeReferenceDirectives) { - if (!hasProperty(usedTypeDirectiveReferences, directive)) { + if (!(directive in usedTypeDirectiveReferences)) { usedTypeDirectiveReferences[directive] = directive; } } @@ -537,14 +535,14 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!hasProperty(currentIdentifiers, baseName)) { + if (!(baseName in currentIdentifiers)) { return baseName; } let count = 0; while (true) { count++; const name = baseName + "_" + count; - if (!hasProperty(currentIdentifiers, name)) { + if (!(name in currentIdentifiers)) { return name; } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8126d5c605e..c6a3698ea16 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2231,7 +2231,7 @@ "category": "Error", "code": 4082 }, - "Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to the 'typings' folder to resolve this conflict.": { + "Conflicting definitions for '{0}' found at '{1}' and '{2}'. Consider installing a specific version of this library to resolve the conflict.": { "category": "Message", "code": 4090 }, @@ -2828,6 +2828,10 @@ "category": "Message", "code": 6137 }, + "Property '{0}' is declared but never used.": { + "category": "Error", + "code": 6138 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c8bd8072b24..869f2decce7 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -24,7 +24,7 @@ namespace ts { Return = 1 << 3 } - const entities: Map = { + const entities = createMap({ "quot": 0x0022, "amp": 0x0026, "apos": 0x0027, @@ -278,7 +278,7 @@ namespace ts { "clubs": 0x2663, "hearts": 0x2665, "diams": 0x2666 - }; + }); // Flags enum to track count of temp variables and a few dedicated names const enum TempFlags { @@ -407,7 +407,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { - if (node.locals && hasProperty(node.locals, name)) { + if (node.locals && name in node.locals) { // We conservatively include alias symbols to cover cases where they're emitted as locals if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { return false; @@ -489,13 +489,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void { if (isBreak) { if (!state.labeledNonLocalBreaks) { - state.labeledNonLocalBreaks = {}; + state.labeledNonLocalBreaks = createMap(); } state.labeledNonLocalBreaks[labelText] = labelMarker; } else { if (!state.labeledNonLocalContinues) { - state.labeledNonLocalContinues = {}; + state.labeledNonLocalContinues = createMap(); } state.labeledNonLocalContinues[labelText] = labelMarker; } @@ -577,27 +577,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge const setSourceMapWriterEmit = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? changeSourceMapEmit : function (writer: SourceMapWriter) { }; - const moduleEmitDelegates: Map<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const moduleEmitDelegates = createMap<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void>({ [ModuleKind.ES6]: emitES6Module, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, [ModuleKind.UMD]: emitUMDModule, [ModuleKind.CommonJS]: emitCommonJSModule, - }; + }); - const bundleEmitDelegates: Map<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const bundleEmitDelegates = createMap<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void>({ [ModuleKind.ES6]() {}, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, [ModuleKind.UMD]() {}, [ModuleKind.CommonJS]() {}, - }; + }); return doEmit; function doEmit(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); - generatedNameSet = {}; + generatedNameSet = createMap(); nodeToGeneratedName = []; decoratedClassAliases = []; isOwnFileEmit = !isBundledEmit; @@ -669,8 +669,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueName(name: string): boolean { return !resolver.hasGlobalName(name) && - !hasProperty(currentFileIdentifiers, name) && - !hasProperty(generatedNameSet, name); + !(name in currentFileIdentifiers) && + !(name in generatedNameSet); } // Return the next available name in the pattern _a ... _z, _0, _1, ... @@ -2646,7 +2646,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return false; } - return !exportEquals && exportSpecifiers && hasProperty(exportSpecifiers, (node).text); + return !exportEquals && exportSpecifiers && (node).text in exportSpecifiers; } function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { @@ -3257,13 +3257,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // Don't initialize seen unless we have at least one element. // Emit a comma to separate for all but the first element. if (!seen) { - seen = {}; + seen = createMap(); } else { write(", "); } - if (!hasProperty(seen, id.text)) { + if (!(id.text in seen)) { emit(id); seen[id.text] = id.text; } @@ -3856,7 +3856,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (convertedLoopState) { if (!convertedLoopState.labels) { - convertedLoopState.labels = {}; + convertedLoopState.labels = createMap(); } convertedLoopState.labels[node.label.text] = node.label.text; } @@ -3970,7 +3970,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return; } - if (!exportEquals && exportSpecifiers && hasProperty(exportSpecifiers, name.text)) { + if (!exportEquals && exportSpecifiers && name.text in exportSpecifiers) { for (const specifier of exportSpecifiers[name.text]) { writeLine(); emitStart(specifier.name); @@ -5311,18 +5311,7 @@ const _super = (function (geti, seti) { emitSignatureParameters(ctor); } else { - // Based on EcmaScript6 section 14.5.14: Runtime Semantics: ClassDefinitionEvaluation. - // If constructor is empty, then, - // If ClassHeritageopt is present, then - // Let constructor be the result of parsing the String "constructor(... args){ super (...args);}" using the syntactic grammar with the goal symbol MethodDefinition. - // Else, - // Let constructor be the result of parsing the String "constructor( ){ }" using the syntactic grammar with the goal symbol MethodDefinition - if (baseTypeElement) { - write("(...args)"); - } - else { - write("()"); - } + write("()"); } } @@ -5360,7 +5349,7 @@ const _super = (function (geti, seti) { write("_super.apply(this, arguments);"); } else { - write("super(...args);"); + write("super(...arguments);"); } emitEnd(baseTypeElement); } @@ -6461,7 +6450,7 @@ const _super = (function (geti, seti) { * Here we check if alternative name was provided for a given moduleName and return it if possible. */ function tryRenameExternalModule(moduleName: LiteralExpression): string { - if (renamedDependencies && hasProperty(renamedDependencies, moduleName.text)) { + if (renamedDependencies && moduleName.text in renamedDependencies) { return `"${renamedDependencies[moduleName.text]}"`; } return undefined; @@ -6803,7 +6792,7 @@ const _super = (function (geti, seti) { function collectExternalModuleInfo(sourceFile: SourceFile) { externalImports = []; - exportSpecifiers = {}; + exportSpecifiers = createMap(); exportEquals = undefined; hasExportStarsToExportValues = false; for (const node of sourceFile.statements) { @@ -6842,7 +6831,7 @@ const _super = (function (geti, seti) { // export { x, y } for (const specifier of (node).exportClause.elements) { const name = (specifier.propertyName || specifier.name).text; - getOrUpdateProperty(exportSpecifiers, name, () => []).push(specifier); + (exportSpecifiers[name] || (exportSpecifiers[name] = [])).push(specifier); } } break; @@ -6941,7 +6930,7 @@ const _super = (function (geti, seti) { } // local names set should only be added if we have anything exported - if (!exportedDeclarations && isEmpty(exportSpecifiers)) { + if (!exportedDeclarations && !someProperties(exportSpecifiers)) { // no exported declarations (export var ...) or export specifiers (export {x}) // check if we have any non star export declarations. let hasExportDeclarationWithExportClause = false; @@ -7081,7 +7070,7 @@ const _super = (function (geti, seti) { if (hoistedVars) { writeLine(); write("var "); - const seen: Map = {}; + const seen = createMap(); for (let i = 0; i < hoistedVars.length; i++) { const local = hoistedVars[i]; const name = local.kind === SyntaxKind.Identifier @@ -7091,7 +7080,7 @@ const _super = (function (geti, seti) { if (name) { // do not emit duplicate entries (in case of declaration merging) in the list of hoisted variables const text = unescapeIdentifier(name.text); - if (hasProperty(seen, text)) { + if (text in seen) { continue; } else { @@ -7447,7 +7436,7 @@ const _super = (function (geti, seti) { writeModuleName(node, emitRelativePathAsModuleName); write("["); - const groupIndices: Map = {}; + const groupIndices = createMap(); const dependencyGroups: DependencyGroup[] = []; for (let i = 0; i < externalImports.length; i++) { @@ -7460,7 +7449,7 @@ const _super = (function (geti, seti) { // for deduplication purposes in key remove leading and trailing quotes so 'a' and "a" will be considered the same const key = text.substr(1, text.length - 2); - if (hasProperty(groupIndices, key)) { + if (key in groupIndices) { // deduplicate/group entries in dependency list by the dependency name const groupIndex = groupIndices[key]; dependencyGroups[groupIndex].push(externalImports[i]); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 014d0926213..b05451340d1 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -417,14 +417,16 @@ namespace ts { case SyntaxKind.JSDocPropertyTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); + case SyntaxKind.JSDocLiteralType: + return visitNode(cbNode, (node).literal); } } export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - const start = performance.mark(); + performance.mark("beforeParse"); const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); - - performance.measure("Parse", start); + performance.mark("afterParse"); + performance.measure("Parse", "beforeParse", "afterParse"); return result; } @@ -595,7 +597,7 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = {}; + identifiers = createMap(); identifierCount = 0; nodeCount = 0; @@ -1084,7 +1086,7 @@ namespace ts { function internIdentifier(text: string): string { text = escapeIdentifier(text); - return hasProperty(identifiers, text) ? identifiers[text] : (identifiers[text] = text); + return identifiers[text] || (identifiers[text] = text); } // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues @@ -5889,9 +5891,13 @@ namespace ts { case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: return parseTokenNode(); + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return parseJSDocLiteralType(); } - // TODO (drosen): Parse string literal types in JSDoc as well. return parseJSDocTypeReference(); } @@ -6070,6 +6076,12 @@ namespace ts { return finishNode(result); } + function parseJSDocLiteralType(): JSDocLiteralType { + const result = createNode(SyntaxKind.JSDocLiteralType); + result.literal = parseLiteralTypeNode(); + return finishNode(result); + } + function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { const pos = scanner.getStartPos(); // skip the ? diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 89db876ae5e..e8f064e81cd 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -6,71 +6,57 @@ namespace ts { } /*@internal*/ +/** Performance measurements for the compiler. */ namespace ts.performance { - /** Performance measurements for the compiler. */ declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; - let profilerEvent: (markName: string) => void; - let counters: Map; + + const profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true + ? onProfilerEvent + : (markName: string) => { }; + + let enabled = false; + let profilerStart = 0; + let counts: Map; + let marks: Map; let measures: Map; /** - * Emit a performance event if ts-profiler is connected. This is primarily used - * to generate heap snapshots. + * Marks a performance event. * - * @param eventName A name for the event. + * @param markName The name of the mark. */ - export function emit(eventName: string) { - if (profilerEvent) { - profilerEvent(eventName); + export function mark(markName: string) { + if (enabled) { + marks[markName] = timestamp(); + counts[markName] = (counts[markName] || 0) + 1; + profilerEvent(markName); } } - /** - * Increments a counter with the specified name. - * - * @param counterName The name of the counter. - */ - export function increment(counterName: string) { - if (counters) { - counters[counterName] = (getProperty(counters, counterName) || 0) + 1; - } - } - - /** - * Gets the value of the counter with the specified name. - * - * @param counterName The name of the counter. - */ - export function getCount(counterName: string) { - return counters && getProperty(counters, counterName) || 0; - } - - /** - * Marks the start of a performance measurement. - */ - export function mark() { - return measures ? timestamp() : 0; - } - /** * Adds a performance measurement with the specified name. * * @param measureName The name of the performance measurement. - * @param marker The timestamp of the starting mark. + * @param startMarkName The name of the starting mark. If not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. */ - export function measure(measureName: string, marker: number) { - if (measures) { - measures[measureName] = (getProperty(measures, measureName) || 0) + (timestamp() - marker); + export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + if (enabled) { + const end = endMarkName && marks[endMarkName] || timestamp(); + const start = startMarkName && marks[startMarkName] || profilerStart; + measures[measureName] = (measures[measureName] || 0) + (end - start); } } /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - return forEachKey(measures, key => cb(key, measures[key])); + export function getCount(markName: string) { + return counts && counts[markName] || 0; } /** @@ -79,31 +65,31 @@ namespace ts.performance { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return measures && getProperty(measures, measureName) || 0; + return measures && measures[measureName] || 0; + } + + /** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + */ + export function forEachMeasure(cb: (measureName: string, duration: number) => void) { + for (const key in measures) { + cb(key, measures[key]); + } } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counters = { }; - measures = { - "I/O Read": 0, - "I/O Write": 0, - "Program": 0, - "Parse": 0, - "Bind": 0, - "Check": 0, - "Emit": 0, - }; - - profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true - ? onProfilerEvent - : undefined; + counts = createMap(); + marks = createMap(); + measures = createMap(); + enabled = true; + profilerStart = timestamp(); } - /** Disables (and clears) performance measurements for the compiler. */ + /** Disables performance measurements for the compiler. */ export function disable() { - counters = undefined; - measures = undefined; - profilerEvent = undefined; + enabled = false; } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d40b2f3219..1c491aeeaac 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -119,49 +119,31 @@ namespace ts { } function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string { - let jsonContent: { typings?: string, types?: string, main?: string }; - try { - const jsonText = state.host.readFile(packageJsonPath); - jsonContent = jsonText ? <{ typings?: string, types?: string, main?: string }>JSON.parse(jsonText) : {}; - } - catch (e) { - // gracefully handle if readFile fails or returns not JSON - jsonContent = {}; + const jsonContent = readJson(packageJsonPath, state.host); + + function tryReadFromField(fieldName: string) { + if (hasProperty(jsonContent, fieldName)) { + const typesFile = (jsonContent)[fieldName]; + if (typeof typesFile === "string") { + const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); + } + return typesFilePath; + } + else { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof typesFile); + } + } + } } - let typesFile: string; - let fieldName: string; - // first try to read content of 'typings' section (backward compatibility) - if (jsonContent.typings) { - if (typeof jsonContent.typings === "string") { - fieldName = "typings"; - typesFile = jsonContent.typings; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings); - } - } - } - // then read 'types' - if (!typesFile && jsonContent.types) { - if (typeof jsonContent.types === "string") { - fieldName = "types"; - typesFile = jsonContent.types; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types); - } - } - } - if (typesFile) { - const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); - } + const typesFilePath = tryReadFromField("typings") || tryReadFromField("types"); + if (typesFilePath) { return typesFilePath; } + // Use the main module for inferring types if no types package specified and the allowJs is set if (state.compilerOptions.allowJs && jsonContent.main && typeof jsonContent.main === "string") { if (state.traceEnabled) { @@ -173,6 +155,17 @@ namespace ts { return undefined; } + function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } { + try { + const jsonText = host.readFile(path); + return jsonText ? JSON.parse(jsonText) : {}; + } + catch (e) { + // gracefully handle if readFile fails or returns not JSON + return {}; + } + } + const typeReferenceExtensions = [".d.ts"]; function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost) { @@ -508,7 +501,7 @@ namespace ts { if (state.traceEnabled) { trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } - matchedPattern = matchPatternOrExact(getKeys(state.compilerOptions.paths), moduleName); + matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName); } if (matchedPattern) { @@ -717,7 +710,7 @@ namespace ts { } function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { - const packageJsonPath = combinePaths(candidate, "package.json"); + const packageJsonPath = pathToPackageJson(candidate); const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host); if (directoryExists && state.host.fileExists(packageJsonPath)) { if (state.traceEnabled) { @@ -747,6 +740,10 @@ namespace ts { return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state); } + function pathToPackageJson(directory: string): string { + return combinePaths(directory, "package.json"); + } + function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); @@ -846,7 +843,7 @@ namespace ts { } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - const existingDirectories: Map = {}; + const existingDirectories = createMap(); function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. @@ -860,9 +857,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - const start = performance.mark(); + performance.mark("beforeIORead"); text = sys.readFile(fileName, options.charset); - performance.measure("I/O Read", start); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); } catch (e) { if (onError) { @@ -877,7 +875,7 @@ namespace ts { } function directoryExists(directoryPath: string): boolean { - if (hasProperty(existingDirectories, directoryPath)) { + if (directoryPath in existingDirectories) { return true; } if (sys.directoryExists(directoryPath)) { @@ -899,13 +897,13 @@ namespace ts { function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { - outputFingerprints = {}; + outputFingerprints = createMap(); } const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); - if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { + if (mtimeBefore && fileName in outputFingerprints) { const fingerprint = outputFingerprints[fileName]; // If output has not been changed, and the file has no external modification @@ -929,7 +927,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - const start = performance.mark(); + performance.mark("beforeIOWrite"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -939,7 +937,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - performance.measure("I/O Write", start); + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); } catch (e) { if (onError) { @@ -1040,16 +1039,11 @@ namespace ts { return []; } const resolutions: T[] = []; - const cache: Map = {}; + const cache = createMap(); for (const name of names) { - let result: T; - if (hasProperty(cache, name)) { - result = cache[name]; - } - else { - result = loader(name, containingFile); - cache[name] = result; - } + const result = name in cache + ? cache[name] + : cache[name] = loader(name, containingFile); resolutions.push(result); } return resolutions; @@ -1070,15 +1064,21 @@ namespace ts { } // Walk the primary type lookup locations - let result: string[] = []; + const result: string[] = []; if (host.directoryExists && host.getDirectories) { const typeRoots = getEffectiveTypeRoots(options, host); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { for (const typeDirectivePath of host.getDirectories(root)) { - // Return just the type directive names - result = result.concat(getBaseFileName(normalizePath(typeDirectivePath))); + const normalized = normalizePath(typeDirectivePath); + const packageJsonPath = pathToPackageJson(combinePaths(root, normalized)); + // tslint:disable-next-line:no-null-keyword + const isNotNeededPackage = host.fileExists(packageJsonPath) && readJson(packageJsonPath, host).typings === null; + if (!isNotNeededPackage) { + // Return just the type directive names + result.push(getBaseFileName(normalized)); + } } } } @@ -1095,7 +1095,7 @@ namespace ts { let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; - let resolvedTypeReferenceDirectives: Map = {}; + let resolvedTypeReferenceDirectives = createMap(); let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -1110,12 +1110,12 @@ namespace ts { // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports: Map = {}; + const modulesWithElidedImports = createMap(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules: Map = {}; + const sourceFilesFoundSearchingNodeModules = createMap(); - const start = performance.mark(); + performance.mark("beforeProgram"); host = host || createCompilerHost(options); @@ -1213,8 +1213,8 @@ namespace ts { }; verifyCompilerOptions(); - - performance.measure("Program", start); + performance.mark("afterProgram"); + performance.measure("Program", "beforeProgram", "afterProgram"); return program; @@ -1241,10 +1241,10 @@ namespace ts { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); - classifiableNames = {}; + classifiableNames = createMap(); for (const sourceFile of files) { - copyMap(sourceFile.classifiableNames, classifiableNames); + copyProperties(sourceFile.classifiableNames, classifiableNames); } } @@ -1272,7 +1272,7 @@ namespace ts { (oldOptions.maxNodeModuleJsDepth !== options.maxNodeModuleJsDepth) || !arrayIsEqualTo(oldOptions.typeRoots, oldOptions.typeRoots) || !arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) || - !mapIsEqualTo(oldOptions.paths, options.paths)) { + !equalOwnProperties(oldOptions.paths, options.paths)) { return false; } @@ -1394,7 +1394,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - isSourceFileFromExternalLibrary: (file: SourceFile) => !!lookUp(sourceFilesFoundSearchingNodeModules, file.path), + isSourceFileFromExternalLibrary: (file: SourceFile) => !!sourceFilesFoundSearchingNodeModules[file.path], writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -1457,14 +1457,15 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - const start = performance.mark(); + performance.mark("beforeEmit"); const emitResult = emitFiles( emitResolver, getEmitHost(writeFileCallback), sourceFile); - performance.measure("Emit", start); + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); return emitResult; } @@ -1931,7 +1932,7 @@ namespace ts { // If the file was previously found via a node_modules search, but is now being processed as a root file, // then everything it sucks in may also be marked incorrectly, and needs to be checked again. - if (file && lookUp(sourceFilesFoundSearchingNodeModules, file.path) && currentNodeModulesDepth == 0) { + if (file && sourceFilesFoundSearchingNodeModules[file.path] && currentNodeModulesDepth == 0) { sourceFilesFoundSearchingNodeModules[file.path] = false; if (!options.noResolve) { processReferencedFiles(file, getDirectoryPath(fileName), isDefaultLib); @@ -1942,7 +1943,7 @@ namespace ts { processImportedModules(file, getDirectoryPath(fileName)); } // See if we need to reprocess the imports due to prior skipped imports - else if (file && lookUp(modulesWithElidedImports, file.path)) { + else if (file && modulesWithElidedImports[file.path]) { if (currentNodeModulesDepth < maxNodeModulesJsDepth) { modulesWithElidedImports[file.path] = false; processImportedModules(file, getDirectoryPath(fileName)); @@ -2009,15 +2010,17 @@ namespace ts { } function processTypeReferenceDirectives(file: SourceFile) { - const typeDirectives = map(file.typeReferenceDirectives, l => l.fileName); + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName); for (let i = 0; i < typeDirectives.length; i++) { const ref = file.typeReferenceDirectives[i]; const resolvedTypeReferenceDirective = resolutions[i]; // store resolved type directive on the file - setResolvedTypeReferenceDirective(file, ref.fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective(ref.fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); + const fileName = ref.fileName.toLocaleLowerCase(); + setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); + processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); } } @@ -2042,7 +2045,7 @@ namespace ts { const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName); if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) { fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, - Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_the_typings_folder_to_resolve_this_conflict, + Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName @@ -2082,7 +2085,7 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { - file.resolvedModules = {}; + file.resolvedModules = createMap(); const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); for (let i = 0; i < moduleNames.length; i++) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6dd84298008..c1431ca23ed 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -55,7 +55,7 @@ namespace ts { tryScan(callback: () => T): T; } - const textToToken: Map = { + const textToToken = createMap({ "abstract": SyntaxKind.AbstractKeyword, "any": SyntaxKind.AnyKeyword, "as": SyntaxKind.AsKeyword, @@ -179,7 +179,7 @@ namespace ts { "|=": SyntaxKind.BarEqualsToken, "^=": SyntaxKind.CaretEqualsToken, "@": SyntaxKind.AtToken, - }; + }); /* As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers @@ -274,9 +274,7 @@ namespace ts { function makeReverseMap(source: Map): string[] { const result: string[] = []; for (const name in source) { - if (source.hasOwnProperty(name)) { - result[source[name]] = name; - } + result[source[name]] = name; } return result; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 2d8c36a3d02..4046867faf1 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -46,6 +46,7 @@ namespace ts { export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { const compilerOptions = host.getCompilerOptions(); + const extendedDiagnostics = compilerOptions.extendedDiagnostics; let currentSourceFile: SourceFile; let sourceMapDir: string; // The directory in which sourcemap will be let stopOverridingSpan = false; @@ -240,7 +241,9 @@ namespace ts { return; } - const start = performance.mark(); + if (extendedDiagnostics) { + performance.mark("beforeSourcemap"); + } const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); @@ -282,7 +285,10 @@ namespace ts { updateLastEncodedAndRecordedSpans(); - performance.measure("Source Map", start); + if (extendedDiagnostics) { + performance.mark("afterSourcemap"); + performance.measure("Source Map", "beforeSourcemap", "afterSourcemap"); + } } function getStartPos(range: TextRange) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 29ae2c60af1..350d75429b7 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -233,15 +233,15 @@ namespace ts { const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"]; function createWatchedFileSet() { - const dirWatchers: Map = {}; + const dirWatchers = createMap(); // One file can have multiple watchers - const fileWatcherCallbacks: Map = {}; + const fileWatcherCallbacks = createMap(); return { addFile, removeFile }; function reduceDirWatcherRefCountForFile(fileName: string) { const dirName = getDirectoryPath(fileName); - if (hasProperty(dirWatchers, dirName)) { - const watcher = dirWatchers[dirName]; + const watcher = dirWatchers[dirName]; + if (watcher) { watcher.referenceCount -= 1; if (watcher.referenceCount <= 0) { watcher.close(); @@ -251,13 +251,12 @@ namespace ts { } function addDirWatcher(dirPath: string): void { - if (hasProperty(dirWatchers, dirPath)) { - const watcher = dirWatchers[dirPath]; + let watcher = dirWatchers[dirPath]; + if (watcher) { watcher.referenceCount += 1; return; } - - const watcher: DirectoryWatcher = _fs.watch( + watcher = _fs.watch( dirPath, { persistent: true }, (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) @@ -268,12 +267,7 @@ namespace ts { } function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void { - if (hasProperty(fileWatcherCallbacks, filePath)) { - fileWatcherCallbacks[filePath].push(callback); - } - else { - fileWatcherCallbacks[filePath] = [callback]; - } + (fileWatcherCallbacks[filePath] || (fileWatcherCallbacks[filePath] = [])).push(callback); } function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile { @@ -289,8 +283,9 @@ namespace ts { } function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) { - if (hasProperty(fileWatcherCallbacks, filePath)) { - const newCallbacks = copyListRemovingItem(callback, fileWatcherCallbacks[filePath]); + const callbacks = fileWatcherCallbacks[filePath]; + if (callbacks) { + const newCallbacks = copyListRemovingItem(callback, callbacks); if (newCallbacks.length === 0) { delete fileWatcherCallbacks[filePath]; } @@ -306,7 +301,7 @@ namespace ts { ? undefined : ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath); // Some applications save a working file via rename operations - if ((eventName === "change" || eventName === "rename") && hasProperty(fileWatcherCallbacks, fileName)) { + if ((eventName === "change" || eventName === "rename") && fileWatcherCallbacks[fileName]) { for (const fileCallback of fileWatcherCallbacks[fileName]) { fileCallback(fileName); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 10538d0c009..8b445bcb626 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -122,11 +122,11 @@ namespace ts { const gutterSeparator = " "; const resetEscapeSequence = "\u001b[0m"; const ellipsis = "..."; - const categoryFormatMap: Map = { + const categoryFormatMap = createMap({ [DiagnosticCategory.Warning]: yellowForegroundEscapeSequence, [DiagnosticCategory.Error]: redForegroundEscapeSequence, [DiagnosticCategory.Message]: blueForegroundEscapeSequence, - }; + }); function formatAndReset(text: string, formatStyle: string) { return formatStyle + text + resetEscapeSequence; @@ -432,7 +432,7 @@ namespace ts { } // reset the cache of existing files - cachedExistingFiles = {}; + cachedExistingFiles = createMap(); const compileResult = compile(rootFileNames, compilerOptions, compilerHost); @@ -445,10 +445,9 @@ namespace ts { } function cachedFileExists(fileName: string): boolean { - if (hasProperty(cachedExistingFiles, fileName)) { - return cachedExistingFiles[fileName]; - } - return cachedExistingFiles[fileName] = hostFileExists(fileName); + return fileName in cachedExistingFiles + ? cachedExistingFiles[fileName] + : cachedExistingFiles[fileName] = hostFileExists(fileName); } function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) { @@ -676,7 +675,7 @@ namespace ts { const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. const descriptionColumn: string[] = []; - const optionsDescriptionMap: Map = {}; // Map between option.description and list of option.type if it is a kind + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind for (let i = 0; i < optsList.length; i++) { const option = optsList[i]; @@ -704,9 +703,10 @@ namespace ts { description = getDiagnosticText(option.description); const options: string[] = []; const element = (option).element; - forEachKey(>element.type, key => { + const typeMap = >element.type; + for (const key in typeMap) { options.push(`'${key}'`); - }); + } optionsDescriptionMap[description] = options; } else { @@ -786,7 +786,7 @@ namespace ts { return; function serializeCompilerOptions(options: CompilerOptions): Map { - const result: Map = {}; + const result = createMap(); const optionsNameMap = getOptionNameMap().optionNameMap; for (const name in options) { @@ -812,9 +812,8 @@ namespace ts { // Enum const typeMap = >optionDefinition.type; for (const key in typeMap) { - if (hasProperty(typeMap, key)) { - if (typeMap[key] === value) - result[name] = key; + if (typeMap[key] === value) { + result[name] = key; } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 74a47f3dbc2..0bbbd99bdd5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,9 +1,13 @@ - namespace ts { - export interface Map { + + export interface MapLike { [index: string]: T; } + export interface Map extends MapLike { + __mapBrand: any; + } + // branded string type used to store absolute, normalized and canonicalized paths // arbitrary file name can be converted to Path via toPath function export type Path = string & { __pathBrand: any }; @@ -346,6 +350,7 @@ namespace ts { JSDocTypedefTag, JSDocPropertyTag, JSDocTypeLiteral, + JSDocLiteralType, // Synthesized list SyntaxList, @@ -376,9 +381,9 @@ namespace ts { LastBinaryOperator = CaretEqualsToken, FirstNode = QualifiedName, FirstJSDocNode = JSDocTypeExpression, - LastJSDocNode = JSDocTypeLiteral, + LastJSDocNode = JSDocLiteralType, FirstJSDocTagNode = JSDocComment, - LastJSDocTagNode = JSDocTypeLiteral + LastJSDocTagNode = JSDocLiteralType } export const enum NodeFlags { @@ -1498,6 +1503,10 @@ namespace ts { type: JSDocType; } + export interface JSDocLiteralType extends JSDocType { + literal: LiteralTypeNode; + } + export type JSDocTypeReferencingNode = JSDocThisType | JSDocConstructorType | JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; // @kind(SyntaxKind.JSDocRecordMember) @@ -2153,6 +2162,8 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere + /* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol? + /* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments } /* @internal */ @@ -2178,9 +2189,7 @@ namespace ts { /* @internal */ export interface TransientSymbol extends Symbol, SymbolLinks { } - export interface SymbolTable { - [index: string]: Symbol; - } + export type SymbolTable = Map; /** Represents a "prefix*suffix" pattern. */ /* @internal */ @@ -2207,23 +2216,24 @@ namespace ts { AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'. AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'. CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions) - EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. - LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. - LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure - CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function - BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement - ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body. - BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. - NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop + EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. + LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. + LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure + CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function + BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement + ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body. + BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. + NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop + AssignmentsMarked = 0x00400000, // Parameter assignments have been marked } /* @internal */ export interface NodeLinks { + flags?: NodeCheckFlags; // Set of flags specific to Node resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result - flags?: NodeCheckFlags; // Set of flags specific to Node enumMemberValue?: number; // Constant value of enum member isVisible?: boolean; // Is this node visible hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context @@ -2564,7 +2574,7 @@ namespace ts { } export type RootPaths = string[]; - export type PathSubstitutions = Map; + export type PathSubstitutions = MapLike; export type TsConfigOnlyOptions = RootPaths | PathSubstitutions; export type CompilerOptionsValue = string | number | boolean | (string | number)[] | TsConfigOnlyOptions; @@ -2724,7 +2734,7 @@ namespace ts { fileNames: string[]; raw?: any; errors: Diagnostic[]; - wildcardDirectories?: Map; + wildcardDirectories?: MapLike; } export const enum WatchDirectoryFlags { @@ -2734,7 +2744,7 @@ namespace ts { export interface ExpandResult { fileNames: string[]; - wildcardDirectories: Map; + wildcardDirectories: MapLike; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index df9f4f95252..0a1f43203ce 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -87,25 +87,6 @@ namespace ts { return node.end - node.pos; } - export function mapIsEqualTo(map1: Map, map2: Map): boolean { - if (!map1 || !map2) { - return map1 === map2; - } - return containsAll(map1, map2) && containsAll(map2, map1); - } - - function containsAll(map: Map, other: Map): boolean { - for (const key in map) { - if (!hasProperty(map, key)) { - continue; - } - if (!hasProperty(other, key) || map[key] !== other[key]) { - return false; - } - } - return true; - } - export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean { if (!array1 || !array2) { return array1 === array2; @@ -126,7 +107,7 @@ namespace ts { } export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean { - return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleNameText); + return !!(sourceFile.resolvedModules && sourceFile.resolvedModules[moduleNameText]); } export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModule { @@ -135,7 +116,7 @@ namespace ts { export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModule): void { if (!sourceFile.resolvedModules) { - sourceFile.resolvedModules = {}; + sourceFile.resolvedModules = createMap(); } sourceFile.resolvedModules[moduleNameText] = resolvedModule; @@ -143,7 +124,7 @@ namespace ts { export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames = {}; + sourceFile.resolvedTypeReferenceDirectiveNames = createMap(); } sourceFile.resolvedTypeReferenceDirectiveNames[typeReferenceDirectiveName] = resolvedTypeReferenceDirective; @@ -166,7 +147,7 @@ namespace ts { } for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; - const oldResolution = oldResolutions && hasProperty(oldResolutions, names[i]) ? oldResolutions[names[i]] : undefined; + const oldResolution = oldResolutions && oldResolutions[names[i]]; const changed = oldResolution ? !newResolution || !comparer(oldResolution, newResolution) @@ -1970,7 +1951,7 @@ namespace ts { export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; - const fileDiagnostics: Map = {}; + const fileDiagnostics = createMap(); let diagnosticsModified = false; let modificationCount = 0; @@ -1988,12 +1969,11 @@ namespace ts { } function reattachFileDiagnostics(newFile: SourceFile): void { - if (!hasProperty(fileDiagnostics, newFile.fileName)) { - return; - } - - for (const diagnostic of fileDiagnostics[newFile.fileName]) { - diagnostic.file = newFile; + const diagnostics = fileDiagnostics[newFile.fileName]; + if (diagnostics) { + for (const diagnostic of diagnostics) { + diagnostic.file = newFile; + } } } @@ -2034,9 +2014,7 @@ namespace ts { forEach(nonFileDiagnostics, pushDiagnostic); for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - forEach(fileDiagnostics[key], pushDiagnostic); - } + forEach(fileDiagnostics[key], pushDiagnostic); } return sortAndDeduplicateDiagnostics(allDiagnostics); @@ -2051,9 +2029,7 @@ namespace ts { nonFileDiagnostics = sortAndDeduplicateDiagnostics(nonFileDiagnostics); for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]); - } + fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]); } } } @@ -2064,7 +2040,7 @@ namespace ts { // the map below must be updated. Note that this regexp *does not* include the 'delete' character. // There is no reason for this other than that JSON.stringify does not handle it either. const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - const escapedCharsMap: Map = { + const escapedCharsMap = createMap({ "\0": "\\0", "\t": "\\t", "\v": "\\v", @@ -2077,7 +2053,7 @@ namespace ts { "\u2028": "\\u2028", // lineSeparator "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine - }; + }); /** @@ -2797,7 +2773,7 @@ namespace ts { } function stringifyObject(value: any) { - return `{${reduceProperties(value, stringifyProperty, "")}}`; + return `{${reduceOwnProperties(value, stringifyProperty, "")}}`; } function stringifyProperty(memo: string, value: any, key: string) { diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index ecdc18394b0..66396293dc2 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -52,7 +52,7 @@ class CompilerBaselineRunner extends RunnerBase { private makeUnitName(name: string, root: string) { const path = ts.toPath(name, root, (fileName) => Harness.Compiler.getCanonicalFileName(fileName)); const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", (fileName) => Harness.Compiler.getCanonicalFileName(fileName)); - return path.replace(pathStart, "/"); + return pathStart ? path.replace(pathStart, "/") : path; }; public checkTestCodeOutput(fileName: string) { @@ -291,8 +291,8 @@ class CompilerBaselineRunner extends RunnerBase { const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); - const fullResults: ts.Map = {}; - const pullResults: ts.Map = {}; + const fullResults = ts.createMap(); + const pullResults = ts.createMap(); for (const sourceFile of allFiles) { fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc609..b5fa53763cb 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -95,14 +95,14 @@ namespace FourSlash { export import IndentStyle = ts.IndentStyle; - const entityMap: ts.Map = { + const entityMap = ts.createMap({ "&": "&", "\"": """, "'": "'", "/": "/", "<": "<", ">": ">" - }; + }); export function escapeXmlAttributeValue(s: string) { return s.replace(/[&<>"'\/]/g, ch => entityMap[ch]); @@ -204,7 +204,7 @@ namespace FourSlash { public formatCodeOptions: ts.FormatCodeOptions; - private inputFiles: ts.Map = {}; // Map between inputFile's fileName and its content for easily looking up when resolving references + private inputFiles = ts.createMap(); // Map between inputFile's fileName and its content for easily looking up when resolving references // Add input file which has matched file name with the given reference-file path. // This is necessary when resolveReference flag is specified @@ -249,6 +249,7 @@ namespace FourSlash { if (compilationOptions.typeRoots) { compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); } + compilationOptions.skipDefaultLibCheck = true; const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); this.languageServiceAdapterHost = languageServiceAdapter.getHost(); @@ -300,11 +301,11 @@ namespace FourSlash { } else { // resolveReference file-option is not specified then do not resolve any files and include all inputFiles - ts.forEachKey(this.inputFiles, fileName => { + for (const fileName in this.inputFiles) { if (!Harness.isDefaultLibraryFile(fileName)) { this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName], /*isRootFile*/ true); } - }); + } this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false); } @@ -376,7 +377,7 @@ namespace FourSlash { public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) { const startMarker = this.getMarkerByName(startMarkerName); const endMarker = this.getMarkerByName(endMarkerName); - const predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + const predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar === startPos) && (errorLimChar === endPos)) ? true : false; }; @@ -428,12 +429,12 @@ namespace FourSlash { let predicate: (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) => boolean; if (after) { - predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar >= startPos) && (errorLimChar >= startPos)) ? true : false; }; } else { - predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar <= startPos) && (errorLimChar <= startPos)) ? true : false; }; } @@ -458,7 +459,7 @@ namespace FourSlash { endPos = endMarker.position; } - errors.forEach(function (error: ts.Diagnostic) { + errors.forEach(function(error: ts.Diagnostic) { if (predicate(error.start, error.start + error.length, startPos, endPos)) { exists = true; } @@ -475,7 +476,7 @@ namespace FourSlash { Harness.IO.log("Unexpected error(s) found. Error list is:"); } - errors.forEach(function (error: ts.Diagnostic) { + errors.forEach(function(error: ts.Diagnostic) { Harness.IO.log(" minChar: " + error.start + ", limChar: " + (error.start + error.length) + ", message: " + ts.flattenDiagnosticMessageText(error.messageText, Harness.IO.newLine()) + "\n"); @@ -593,9 +594,9 @@ namespace FourSlash { public noItemsWithSameNameButDifferentKind(): void { const completions = this.getCompletionListAtCaret(); - const uniqueItems: ts.Map = {}; + const uniqueItems = ts.createMap(); for (const item of completions.entries) { - if (!ts.hasProperty(uniqueItems, item.name)) { + if (!(item.name in uniqueItems)) { uniqueItems[item.name] = item.kind; } else { @@ -773,7 +774,7 @@ namespace FourSlash { } public verifyRangesWithSameTextReferenceEachOther() { - ts.forEachValue(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); + ts.forEachProperty(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); } private verifyReferencesWorker(references: ts.ReferenceEntry[], fileName: string, start: number, end: number, isWriteAccess?: boolean, isDefinition?: boolean) { @@ -1348,14 +1349,7 @@ namespace FourSlash { } // Enters lines of text at the current caret position - public type(text: string) { - return this.typeHighFidelity(text); - } - - // Enters lines of text at the current caret position, invoking - // language service APIs to mimic Visual Studio's behavior - // as much as possible - private typeHighFidelity(text: string) { + public type(text: string, highFidelity = false) { let offset = this.currentCaretPosition; const prevChar = " "; const checkCadence = (text.length >> 2) + 1; @@ -1364,24 +1358,26 @@ namespace FourSlash { // Make the edit const ch = text.charAt(i); this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, ch); - this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); + if (highFidelity) { + this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); + } this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch); offset++; - if (ch === "(" || ch === ",") { - /* Signature help*/ - this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset); - } - else if (prevChar === " " && /A-Za-z_/.test(ch)) { - /* Completions */ - this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); - } + if (highFidelity) { + if (ch === "(" || ch === ",") { + /* Signature help*/ + this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset); + } + else if (prevChar === " " && /A-Za-z_/.test(ch)) { + /* Completions */ + this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); + } - if (i % checkCadence === 0) { - this.checkPostEditInvariants(); - // this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); - // this.languageService.getSemanticDiagnostics(this.activeFile.fileName); + if (i % checkCadence === 0) { + this.checkPostEditInvariants(); + } } // Handle post-keystroke formatting @@ -1389,14 +1385,12 @@ namespace FourSlash { const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); - // this.checkPostEditInvariants(); } } } // Move the caret to wherever we ended up this.currentCaretPosition = offset; - this.fixCaretPosition(); this.checkPostEditInvariants(); } @@ -1415,7 +1409,6 @@ namespace FourSlash { const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); - this.checkPostEditInvariants(); } } @@ -1639,10 +1632,11 @@ namespace FourSlash { } public rangesByText(): ts.Map { - const result: ts.Map = {}; + const result = ts.createMap(); for (const range of this.getRanges()) { const text = this.rangeText(range); - (ts.getProperty(result, text) || (result[text] = [])).push(range); + const ranges = result[text] || (result[text] = []); + ranges.push(range); } return result; } @@ -1897,7 +1891,7 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap: ts.Map = { + const openBraceMap = ts.createMap({ "(": ts.CharacterCodes.openParen, "{": ts.CharacterCodes.openBrace, "[": ts.CharacterCodes.openBracket, @@ -1905,7 +1899,7 @@ namespace FourSlash { '"': ts.CharacterCodes.doubleQuote, "`": ts.CharacterCodes.backtick, "<": ts.CharacterCodes.lessThan - }; + }); const charCode = openBraceMap[openingBrace]; @@ -2269,40 +2263,12 @@ namespace FourSlash { export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void { // Parse out the files and their metadata const testData = parseTestData(basePath, content, fileName); - const state = new TestState(basePath, testType, testData); - - let result = ""; - const fourslashFile: Harness.Compiler.TestFile = { - unitName: Harness.Compiler.fourslashFileName, - content: undefined, - }; - const testFile: Harness.Compiler.TestFile = { - unitName: fileName, - content: content - }; - - const host = Harness.Compiler.createCompilerHost( - [fourslashFile, testFile], - (fn, contents) => result = contents, - ts.ScriptTarget.Latest, - Harness.IO.useCaseSensitiveFileNames(), - Harness.IO.getCurrentDirectory()); - - const program = ts.createProgram([Harness.Compiler.fourslashFileName, fileName], { outFile: "fourslashTestOutput.js", noResolve: true, target: ts.ScriptTarget.ES3 }, host); - - const sourceFile = host.getSourceFile(fileName, ts.ScriptTarget.ES3); - - const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile); - if (diagnostics.length > 0) { - throw new Error(`Error compiling ${fileName}: ` + - diagnostics.map(e => ts.flattenDiagnosticMessageText(e.messageText, Harness.IO.newLine())).join("\r\n")); + const output = ts.transpileModule(content, { reportDiagnostics: true }); + if (output.diagnostics.length > 0) { + throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`); } - - program.emit(sourceFile); - - ts.Debug.assert(!!result); - runCode(result, state); + runCode(output.outputText, state); } function runCode(code: string, state: TestState): void { @@ -2395,13 +2361,14 @@ ${code} // Comment line, check for global/file @options and record them const match = optionRegex.exec(line.substr(2)); if (match) { - const fileMetadataNamesIndex = fileMetadataNames.indexOf(match[1]); + const [key, value] = match.slice(1); + const fileMetadataNamesIndex = fileMetadataNames.indexOf(key); if (fileMetadataNamesIndex === -1) { // Check if the match is already existed in the global options - if (globalOptions[match[1]] !== undefined) { - throw new Error("Global Option : '" + match[1] + "' is already existed"); + if (globalOptions[key] !== undefined) { + throw new Error(`Global option '${key}' already exists`); } - globalOptions[match[1]] = match[2]; + globalOptions[key] = value; } else { if (fileMetadataNamesIndex === fileMetadataNames.indexOf(metadataOptionNames.fileName)) { @@ -2416,12 +2383,12 @@ ${code} resetLocalData(); } - currentFileName = basePath + "/" + match[2]; - currentFileOptions[match[1]] = match[2]; + currentFileName = basePath + "/" + value; + currentFileOptions[key] = value; } else { // Add other fileMetadata flag - currentFileOptions[match[1]] = match[2]; + currentFileOptions[key] = value; } } } @@ -2509,7 +2476,7 @@ ${code} } const marker: Marker = { - fileName: fileName, + fileName, position: location.position, data: markerValue }; @@ -2526,7 +2493,7 @@ ${code} function recordMarker(fileName: string, location: LocationInformation, name: string, markerMap: MarkerMap, markers: Marker[]): Marker { const marker: Marker = { - fileName: fileName, + fileName, position: location.position }; diff --git a/src/harness/harness.ts b/src/harness/harness.ts index e8e0a11f615..9ed817eae49 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -848,9 +848,9 @@ namespace Harness { export const defaultLibFileName = "lib.d.ts"; export const es2015DefaultLibFileName = "lib.es2015.d.ts"; - const libFileNameSourceFileMap: ts.Map = { + const libFileNameSourceFileMap= ts.createMap({ [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest) - }; + }); export function getDefaultLibrarySourceFile(fileName = defaultLibFileName): ts.SourceFile { if (!isDefaultLibraryFile(fileName)) { @@ -1005,13 +1005,13 @@ namespace Harness { let optionsIndex: ts.Map; function getCommandLineOption(name: string): ts.CommandLineOption { if (!optionsIndex) { - optionsIndex = {}; + optionsIndex = ts.createMap(); const optionDeclarations = harnessOptionDeclarations.concat(ts.optionDeclarations); for (const option of optionDeclarations) { optionsIndex[option.name.toLowerCase()] = option; } } - return ts.lookUp(optionsIndex, name.toLowerCase()); + return optionsIndex[name.toLowerCase()]; } export function setCompilerOptionsFromHarnessSetting(settings: Harness.TestCaseParser.CompilerSettings, options: ts.CompilerOptions & HarnessOptions): void { @@ -1569,6 +1569,7 @@ namespace Harness { /** Support class for baseline files */ export namespace Baseline { + const NoContent = ""; export interface BaselineOptions { Subfolder?: string; @@ -1635,14 +1636,6 @@ namespace Harness { throw new Error("The generated content was \"undefined\". Return \"null\" if no baselining is required.\""); } - // Store the content in the 'local' folder so we - // can accept it later (manually) - /* tslint:disable:no-null-keyword */ - if (actual !== null) { - /* tslint:enable:no-null-keyword */ - IO.writeFile(actualFileName, actual); - } - return actual; } @@ -1659,7 +1652,7 @@ namespace Harness { /* tslint:disable:no-null-keyword */ if (actual === null) { /* tslint:enable:no-null-keyword */ - actual = ""; + actual = NoContent; } let expected = ""; @@ -1672,7 +1665,13 @@ namespace Harness { function writeComparison(expected: string, actual: string, relativeFileName: string, actualFileName: string, descriptionForDescribe: string) { const encoded_actual = Utils.encodeString(actual); - if (expected != encoded_actual) { + if (expected !== encoded_actual) { + if (actual === NoContent) { + IO.writeFile(relativeFileName + ".delete", ""); + } + else { + IO.writeFile(relativeFileName, actual); + } // Overwrite & issue error const errMsg = "The baseline file " + relativeFileName + " has changed."; throw new Error(errMsg); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d7ed04b627f..94124432780 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.Map = {}; + protected fileNameToScript = ts.createMap(); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -135,7 +135,7 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachValue(this.fileNameToScript, (scriptInfo) => { + ts.forEachProperty(this.fileNameToScript, (scriptInfo) => { if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. @@ -146,7 +146,7 @@ namespace Harness.LanguageService { } public getScriptInfo(fileName: string): ScriptInfo { - return ts.lookUp(this.fileNameToScript, fileName); + return this.fileNameToScript[fileName]; } public addScript(fileName: string, content: string, isRootFile: boolean): void { @@ -235,7 +235,7 @@ namespace Harness.LanguageService { this.getModuleResolutionsForFile = (fileName) => { const scriptInfo = this.getScriptInfo(fileName); const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); - const imports: ts.Map = {}; + const imports = ts.createMap(); for (const module of preprocessInfo.importedFiles) { const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); if (resolutionInfo.resolvedModule) { @@ -248,7 +248,7 @@ namespace Harness.LanguageService { const scriptInfo = this.getScriptInfo(fileName); if (scriptInfo) { const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); - const resolutions: ts.Map = {}; + const resolutions = ts.createMap(); const settings = this.nativeHost.getCompilationSettings(); for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 038b2dbe359..76f042834bb 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -253,18 +253,15 @@ class ProjectRunner extends RunnerBase { moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future }; // Set the values specified using json - const optionNameMap: ts.Map = {}; - ts.forEach(ts.optionDeclarations, option => { - optionNameMap[option.name] = option; - }); + const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); for (const name in testCase) { - if (name !== "mapRoot" && name !== "sourceRoot" && ts.hasProperty(optionNameMap, name)) { + if (name !== "mapRoot" && name !== "sourceRoot" && name in optionNameMap) { const option = optionNameMap[name]; const optType = option.type; let value = testCase[name]; if (typeof optType !== "string") { const key = value.toLowerCase(); - if (ts.hasProperty(optType, key)) { + if (key in optType) { value = optType[key]; } } diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index e0ce09391fb..2885b310605 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -7,16 +7,16 @@ namespace ts { } function createDefaultServerHost(fileMap: Map): server.ServerHost { - const existingDirectories: Map = {}; - forEachValue(fileMap, v => { - let dir = getDirectoryPath(v.name); + const existingDirectories = createMap(); + for (const name in fileMap) { + let dir = getDirectoryPath(name); let previous: string; do { existingDirectories[dir] = true; previous = dir; dir = getDirectoryPath(dir); } while (dir !== previous); - }); + } return { args: [], newLine: "\r\n", @@ -24,7 +24,7 @@ namespace ts { write: (s: string) => { }, readFile: (path: string, encoding?: string): string => { - return hasProperty(fileMap, path) && fileMap[path].content; + return path in fileMap ? fileMap[path].content : undefined; }, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => { throw new Error("NYI"); @@ -33,10 +33,10 @@ namespace ts { throw new Error("NYI"); }, fileExists: (path: string): boolean => { - return hasProperty(fileMap, path); + return path in fileMap; }, directoryExists: (path: string): boolean => { - return hasProperty(existingDirectories, path); + return existingDirectories[path] || false; }, createDirectory: (path: string) => { }, @@ -101,7 +101,7 @@ namespace ts { content: `foo()` }; - const serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported }); + const serverHost = createDefaultServerHost(createMap({ [root.name]: root, [imported.name]: imported })); const { project, rootScriptInfo } = createProject(root.name, serverHost); // ensure that imported file was found @@ -193,7 +193,7 @@ namespace ts { content: `export var y = 1` }; - const fileMap: Map = { [root.name]: root }; + const fileMap = createMap({ [root.name]: root }); const serverHost = createDefaultServerHost(fileMap); const originalFileExists = serverHost.fileExists; diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 5f63889b084..09febd2300a 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -10,7 +10,7 @@ namespace ts { const map = arrayToMap(files, f => f.name); if (hasDirectoryExists) { - const directories: Map = {}; + const directories = createMap(); for (const f of files) { let name = getDirectoryPath(f.name); while (true) { @@ -25,19 +25,19 @@ namespace ts { return { readFile, directoryExists: path => { - return hasProperty(directories, path); + return path in directories; }, fileExists: path => { - assert.isTrue(hasProperty(directories, getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); - return hasProperty(map, path); + assert.isTrue(getDirectoryPath(path) in directories, `'fileExists' '${path}' request in non-existing directory`); + return path in map; } }; } else { - return { readFile, fileExists: path => hasProperty(map, path), }; + return { readFile, fileExists: path => path in map, }; } function readFile(path: string): string { - return hasProperty(map, path) ? map[path].content : undefined; + return path in map ? map[path].content : undefined; } } @@ -287,7 +287,7 @@ namespace ts { const host: CompilerHost = { getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + return path in files ? createSourceFile(fileName, files[path], languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -298,7 +298,7 @@ namespace ts { useCaseSensitiveFileNames: () => false, fileExists: fileName => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path); + return path in files; }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -318,7 +318,7 @@ namespace ts { } it("should find all modules", () => { - const files: Map = { + const files = createMap({ "/a/b/c/first/shared.ts": ` class A {} export = A`, @@ -332,23 +332,23 @@ import Shared = require('../first/shared'); class C {} export = C; ` - }; + }); test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); }); it("should find modules in node_modules", () => { - const files: Map = { + const files = createMap({ "/parent/node_modules/mod/index.d.ts": "export var x", "/parent/app/myapp.ts": `import {x} from "mod"` - }; + }); test(files, "/parent/app", ["myapp.ts"], 2, []); }); it("should find file referenced via absolute and relative names", () => { - const files: Map = { + const files = createMap({ "/a/b/c.ts": `/// `, "/a/b/b.ts": "var x" - }; + }); test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); }); }); @@ -358,11 +358,7 @@ export = C; function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { - const f: Map = {}; - for (const fileName in files) { - f[getCanonicalFileName(fileName)] = files[fileName]; - } - files = f; + files = reduceProperties(files, (files, file, fileName) => (files[getCanonicalFileName(fileName)] = file, files), createMap()); } const host: CompilerHost = { @@ -371,7 +367,7 @@ export = C; return library; } const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + return path in files ? createSourceFile(fileName, files[path], languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -382,7 +378,7 @@ export = C; useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, fileExists: fileName => { const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path); + return path in files; }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -395,57 +391,57 @@ export = C; } it("should succeed when the same file is referenced using absolute and relative names", () => { - const files: Map = { + const files = createMap({ "/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"], []); }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files: Map = { + const files = createMap({ "/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]); }); it("should fail when two files used in program differ only in casing (imports)", () => { - const files: Map = { + const files = createMap({ "/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]); }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files: Map = { + const files = createMap({ "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]); }); it("should fail when two files exist on disk that differs only in casing", () => { - const files: Map = { + const files = createMap({ "/a/b/c.ts": `import {x} from "D"`, "/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]); }); it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files: Map = { + const files = createMap({ "moduleA.ts": `import a = require("./ModuleC")`, "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]); }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files: Map = { + const files = createMap({ "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -453,11 +449,11 @@ export = C; import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); ` - }; + }); 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: Map = { + const files = createMap({ "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -465,7 +461,7 @@ import b = require("./moduleB.ts"); import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); ` - }; + }); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); }); }); @@ -1023,7 +1019,7 @@ import b = require("./moduleB.ts"); const names = map(files, f => f.name); const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES6)), f => f.fileName); const compilerHost: CompilerHost = { - fileExists : fileName => hasProperty(sourceFiles, fileName), + fileExists : fileName => fileName in sourceFiles, getSourceFile: fileName => sourceFiles[fileName], getDefaultLibFileName: () => "lib.d.ts", writeFile(file, text) { @@ -1034,7 +1030,7 @@ import b = require("./moduleB.ts"); getCanonicalFileName: f => f.toLowerCase(), getNewLine: () => "\r\n", useCaseSensitiveFileNames: () => false, - readFile: fileName => hasProperty(sourceFiles, fileName) ? sourceFiles[fileName].text : undefined + readFile: fileName => fileName in sourceFiles ? sourceFiles[fileName].text : undefined }; const program1 = createProgram(names, {}, compilerHost); const diagnostics1 = program1.getFileProcessingDiagnostics().getDiagnostics(); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 8b2b15c15b3..60687d34c55 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -95,13 +95,14 @@ namespace ts { } } + function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { + const file = createSourceFile(fileName, sourceText.getFullText(), target); + file.sourceText = sourceText; + return file; + } + function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - const files: Map = {}; - for (const t of texts) { - const file = createSourceFile(t.name, t.text.getFullText(), target); - file.sourceText = t.text; - files[t.name] = file; - } + const files = arrayToMap(texts, t => t.name, t => createSourceFileWithText(t.name, t.text, target)); return { getSourceFile(fileName): SourceFile { @@ -128,10 +129,9 @@ namespace ts { getNewLine(): string { return sys ? sys.newLine : newLine; }, - fileExists: fileName => hasProperty(files, fileName), + fileExists: fileName => fileName in files, readFile: fileName => { - const file = lookUp(files, fileName); - return file && file.text; + return fileName in files ? files[fileName].text : undefined; } }; } @@ -152,29 +152,29 @@ namespace ts { return program; } - function getSizeOfMap(map: Map): number { - let size = 0; - for (const id in map) { - if (hasProperty(map, id)) { - size++; + function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): boolean { + if (!expected === !actual) { + if (expected) { + assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); + assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); } + return true; } - return size; + return false; } - function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): void { - assert.isTrue(actual !== undefined); - assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); - assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); + function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): boolean { + if (!expected === !actual) { + if (expected) { + assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); + assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); + } + return true; + } + return false; } - function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): void { - assert.isTrue(actual !== undefined); - assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); - assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); - } - - function checkCache(caption: string, program: Program, fileName: string, expectedContent: Map, getCache: (f: SourceFile) => Map, entryChecker: (expected: T, original: T) => void): void { + 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.isTrue(file !== undefined, `cannot find file ${fileName}`); const cache = getCache(file); @@ -183,23 +183,7 @@ namespace ts { } else { assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - const actualCacheSize = getSizeOfMap(cache); - const expectedSize = getSizeOfMap(expectedContent); - assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); - - for (const id in expectedContent) { - if (hasProperty(expectedContent, id)) { - - if (expectedContent[id]) { - const expected = expectedContent[id]; - const actual = cache[id]; - entryChecker(expected, actual); - } - } - else { - assert.isTrue(cache[id] === undefined); - } - } + assert.isTrue(equalOwnProperties(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); } } @@ -313,6 +297,8 @@ namespace ts { }); it("resolution cache follows imports", () => { + (Error).stackTraceLimit = Infinity; + const files = [ { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, @@ -320,7 +306,7 @@ namespace ts { const options: CompilerOptions = { target }; const program_1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" } })); checkResolvedModulesCache(program_1, "b.ts", undefined); const program_2 = updateProgram(program_1, ["a.ts"], options, files => { @@ -329,7 +315,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" } })); checkResolvedModulesCache(program_1, "b.ts", undefined); // imports has changed - program is not reused @@ -346,7 +332,7 @@ namespace ts { files[0].text = files[0].text.updateImportsAndExports(newImports); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedModulesCache(program_4, "a.ts", { "b": { resolvedFileName: "b.ts" }, "c": undefined }); + checkResolvedModulesCache(program_4, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" }, "c": undefined })); }); it("resolved type directives cache follows type directives", () => { @@ -357,7 +343,7 @@ namespace ts { const options: CompilerOptions = { target, typeRoots: ["/types"] }; const program_1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); const program_2 = updateProgram(program_1, ["/a.ts"], options, files => { @@ -366,7 +352,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); // type reference directives has changed - program is not reused @@ -384,7 +370,7 @@ namespace ts { files[0].text = files[0].text.updateReferences(newReferences); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); }); }); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index d5e6daa5d60..cf7486acb40 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -362,13 +362,13 @@ namespace ts.server { class InProcClient { private server: InProcSession; private seq = 0; - private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; - private eventHandlers: ts.Map<(args: any) => void> = {}; + private callbacks = createMap<(resp: protocol.Response) => void>(); + private eventHandlers = createMap<(args: any) => void>(); handle(msg: protocol.Message): void { if (msg.type === "response") { const response = msg; - if (this.callbacks[response.request_seq]) { + if (response.request_seq in this.callbacks) { this.callbacks[response.request_seq](response); delete this.callbacks[response.request_seq]; } @@ -380,7 +380,7 @@ namespace ts.server { } emit(name: string, args: any): void { - if (this.eventHandlers[name]) { + if (name in this.eventHandlers) { this.eventHandlers[name](args); } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7a375c50b8a..49f033b9def 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -68,20 +68,10 @@ namespace ts { return entry; } - function sizeOfMap(map: Map): number { - let n = 0; - for (const name in map) { - if (hasProperty(map, name)) { - n++; - } - } - return n; - } - function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(sizeOfMap(map), expectedKeys.length, `${caption}: incorrect size of map`); + assert.equal(reduceProperties(map, count => count + 1, 0), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { - assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getKeys(map)}`); + assert.isTrue(name in map, `${caption} is expected to contain ${name}, actual keys: ${Object.keys(map)}`); } } @@ -126,8 +116,8 @@ namespace ts { private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private callbackQueue: TimeOutCallback[] = []; - readonly watchedDirectories: Map<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {}; - readonly watchedFiles: Map = {}; + readonly watchedDirectories = createMap<{ cb: DirectoryWatcherCallback, recursive: boolean }[]>(); + readonly watchedFiles = createMap(); constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[]) { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -208,7 +198,7 @@ namespace ts { watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path) || (this.watchedDirectories[path] = []); + const callbacks = this.watchedDirectories[path] || (this.watchedDirectories[path] = []); callbacks.push({ cb: callback, recursive }); return { referenceCount: 0, @@ -229,7 +219,7 @@ namespace ts { triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path); + const callbacks = this.watchedDirectories[path]; if (callbacks) { for (const callback of callbacks) { callback.cb(fileName); @@ -239,7 +229,7 @@ namespace ts { triggerFileWatcherCallback(fileName: string, removed?: boolean): void { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path); + const callbacks = this.watchedFiles[path]; if (callbacks) { for (const callback of callbacks) { callback(path, removed); @@ -249,7 +239,7 @@ namespace ts { watchFile(fileName: string, callback: FileWatcherCallback) { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path) || (this.watchedFiles[path] = []); + const callbacks = this.watchedFiles[path] || (this.watchedFiles[path] = []); callbacks.push(callback); return { close: () => { @@ -594,7 +584,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; @@ -621,7 +611,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; diff --git a/src/lib/es2015.core.d.ts b/src/lib/es2015.core.d.ts index 980f610ca7d..15fbb1b77c5 100644 --- a/src/lib/es2015.core.d.ts +++ b/src/lib/es2015.core.d.ts @@ -68,6 +68,10 @@ interface ArrayConstructor { of(...items: T[]): Array; } +interface DateConstructor { + new (value: Date): Date; +} + interface Function { /** * Returns the name of the function. Function names are read-only and can not be changed. diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 496df578c19..e4cbe323c68 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1006,7 +1006,12 @@ interface ReadonlyArray { * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: T[]): T[]; + concat(...items: T[][]): T[]; + /** + * Combines two or more arrays. + * @param items Additional items to add to the end of array1. + */ + concat(...items: (T | T[])[]): 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/server/client.ts b/src/server/client.ts index f04dbd8dc02..88177e91fad 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -21,7 +21,7 @@ namespace ts.server { export class SessionClient implements LanguageService { private sequence: number = 0; - private lineMaps: ts.Map = {}; + private lineMaps: ts.Map = ts.createMap(); private messages: string[] = []; private lastRenameEntry: RenameEntry; @@ -37,7 +37,7 @@ namespace ts.server { } private getLineMap(fileName: string): number[] { - let lineMap = ts.lookUp(this.lineMaps, fileName); + let lineMap = this.lineMaps[fileName]; if (!lineMap) { const scriptSnapshot = this.host.getScriptSnapshot(fileName); lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6e9adad7f47..db9bdd5043b 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -130,15 +130,15 @@ namespace ts.server { const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); const currentResolutionsInFile = cache.get(path); - const newResolutions: Map = {}; + const newResolutions = createMap(); const resolvedModules: R[] = []; const compilerOptions = this.getCompilationSettings(); for (const name of names) { // check if this is a duplicate entry in the list - let resolution = lookUp(newResolutions, name); + let resolution = newResolutions[name]; if (!resolution) { - const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name); + const existingResolution = currentResolutionsInFile && currentResolutionsInFile[name]; if (moduleResolutionIsValid(existingResolution)) { // ok, it is safe to use existing name resolution results resolution = existingResolution; @@ -378,7 +378,7 @@ namespace ts.server { export interface ProjectOptions { // these fields can be present in the project file files?: string[]; - wildcardDirectories?: ts.Map; + wildcardDirectories?: ts.MapLike; compilerOptions?: ts.CompilerOptions; } @@ -391,7 +391,7 @@ namespace ts.server { // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; program: ts.Program; - filenameToSourceFile: ts.Map = {}; + filenameToSourceFile = ts.createMap(); updateGraphSeq = 0; /** Used for configured projects which may have multiple open roots */ openRefCount = 0; @@ -504,7 +504,7 @@ namespace ts.server { return; } - this.filenameToSourceFile = {}; + this.filenameToSourceFile = createMap(); const sourceFiles = this.program.getSourceFiles(); for (let i = 0, len = sourceFiles.length; i < len; i++) { const normFilename = ts.normalizePath(sourceFiles[i].fileName); @@ -563,7 +563,7 @@ namespace ts.server { } let strBuilder = ""; - ts.forEachValue(this.filenameToSourceFile, + ts.forEachProperty(this.filenameToSourceFile, sourceFile => { strBuilder += sourceFile.fileName + "\n"; }); return strBuilder; } @@ -613,7 +613,7 @@ namespace ts.server { } export class ProjectService { - filenameToScriptInfo: ts.Map = {}; + filenameToScriptInfo = ts.createMap(); // open, non-configured root files openFileRoots: ScriptInfo[] = []; // projects built from openFileRoots @@ -625,12 +625,12 @@ namespace ts.server { // open files that are roots of a configured project openFileRootsConfigured: ScriptInfo[] = []; // a path to directory watcher map that detects added tsconfig files - directoryWatchersForTsconfig: ts.Map = {}; + directoryWatchersForTsconfig = ts.createMap(); // count of how many projects are using the directory watcher. If the // number becomes 0 for a watcher, then we should close it. - directoryWatchersRefCount: ts.Map = {}; + directoryWatchersRefCount = ts.createMap(); hostConfiguration: HostConfiguration; - timerForDetectingProjectFileListChanges: Map = {}; + timerForDetectingProjectFileListChanges = createMap(); constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { // ts.disableIncrementalParsing = true; @@ -857,7 +857,7 @@ namespace ts.server { if (project.isConfiguredProject()) { project.projectFileWatcher.close(); project.directoryWatcher.close(); - forEachValue(project.directoriesWatchedForWildcards, watcher => { watcher.close(); }); + forEachProperty(project.directoriesWatchedForWildcards, watcher => { watcher.close(); }); delete project.directoriesWatchedForWildcards; this.configuredProjects = copyListRemovingItem(project, this.configuredProjects); } @@ -1124,7 +1124,7 @@ namespace ts.server { getScriptInfo(filename: string) { filename = ts.normalizePath(filename); - return ts.lookUp(this.filenameToScriptInfo, filename); + return this.filenameToScriptInfo[filename]; } /** @@ -1133,7 +1133,7 @@ namespace ts.server { */ openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { fileName = ts.normalizePath(fileName); - let info = ts.lookUp(this.filenameToScriptInfo, fileName); + let info = this.filenameToScriptInfo[fileName]; if (!info) { let content: string; if (this.host.fileExists(fileName)) { @@ -1246,7 +1246,7 @@ namespace ts.server { * @param filename is absolute pathname */ closeClientFile(filename: string) { - const info = ts.lookUp(this.filenameToScriptInfo, filename); + const info = this.filenameToScriptInfo[filename]; if (info) { this.closeOpenFile(info); info.isOpen = false; @@ -1255,14 +1255,14 @@ namespace ts.server { } getProjectForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = this.filenameToScriptInfo[filename]; if (scriptInfo) { return scriptInfo.defaultProject; } } printProjectsForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = this.filenameToScriptInfo[filename]; if (scriptInfo) { this.psLogger.startGroup(); this.psLogger.info("Projects for " + filename); @@ -1419,7 +1419,7 @@ namespace ts.server { /*recursive*/ true ); - project.directoriesWatchedForWildcards = reduceProperties(projectOptions.wildcardDirectories, (watchers, flag, directory) => { + project.directoriesWatchedForWildcards = reduceProperties(createMap(projectOptions.wildcardDirectories), (watchers, flag, directory) => { if (comparePaths(configDirectoryPath, directory, ".", !this.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; this.log(`Add ${ recursive ? "recursive " : ""}watcher for: ${directory}`); diff --git a/src/server/session.ts b/src/server/session.ts index 7e1ca81e2af..9383a54bd48 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1061,7 +1061,7 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { + private handlers = createMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({ [CommandNames.Exit]: () => { this.exit(); return { responseRequired: false }; @@ -1198,9 +1198,10 @@ namespace ts.server { this.reloadProjects(); return { responseRequired: false }; } - }; + }); + public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) { - if (this.handlers[command]) { + if (command in this.handlers) { throw new Error(`Protocol handler already exists for command "${command}"`); } this.handlers[command] = handler; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 3b013a4a924..4f0e06244d2 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -47,7 +47,7 @@ namespace ts.JsTyping { { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { // A typing name to typing file path mapping - const inferredTypings: Map = {}; + const inferredTypings = createMap(); if (!typingOptions || !typingOptions.enableAutoDiscovery) { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; @@ -58,12 +58,7 @@ namespace ts.JsTyping { if (!safeList) { const result = readConfigFile(safeListPath, (path: string) => host.readFile(path)); - if (result.config) { - safeList = result.config; - } - else { - safeList = {}; - }; + safeList = createMap(result.config); } const filesToWatch: string[] = []; @@ -93,7 +88,7 @@ namespace ts.JsTyping { // Add the cached typing locations for inferred typings that are already installed for (const name in packageNameToTypingLocation) { - if (hasProperty(inferredTypings, name) && !inferredTypings[name]) { + if (name in inferredTypings && !inferredTypings[name]) { inferredTypings[name] = packageNameToTypingLocation[name]; } } @@ -124,7 +119,7 @@ namespace ts.JsTyping { } for (const typing of typingNames) { - if (!hasProperty(inferredTypings, typing)) { + if (!(typing in inferredTypings)) { inferredTypings[typing] = undefined; } } @@ -139,16 +134,16 @@ namespace ts.JsTyping { const jsonConfig: PackageJson = result.config; filesToWatch.push(jsonPath); if (jsonConfig.dependencies) { - mergeTypings(getKeys(jsonConfig.dependencies)); + mergeTypings(getOwnKeys(jsonConfig.dependencies)); } if (jsonConfig.devDependencies) { - mergeTypings(getKeys(jsonConfig.devDependencies)); + mergeTypings(getOwnKeys(jsonConfig.devDependencies)); } if (jsonConfig.optionalDependencies) { - mergeTypings(getKeys(jsonConfig.optionalDependencies)); + mergeTypings(getOwnKeys(jsonConfig.optionalDependencies)); } if (jsonConfig.peerDependencies) { - mergeTypings(getKeys(jsonConfig.peerDependencies)); + mergeTypings(getOwnKeys(jsonConfig.peerDependencies)); } } } @@ -167,7 +162,7 @@ namespace ts.JsTyping { mergeTypings(cleanedTypingNames); } else { - mergeTypings(filter(cleanedTypingNames, f => hasProperty(safeList, f))); + mergeTypings(filter(cleanedTypingNames, f => f in safeList)); } const hasJsxFile = forEach(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index e9afb6925b5..ccded188e61 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -15,7 +15,7 @@ namespace ts.NavigateTo { const nameToDeclarations = sourceFile.getNamedDeclarations(); for (const name in nameToDeclarations) { - const declarations = getProperty(nameToDeclarations, name); + const declarations = nameToDeclarations[name]; if (declarations) { // First do a quick check to see if the name of the declaration matches the // last portion of the (possibly) dotted name they're searching for. diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f105a9fa6e2..dda9ef485d2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -234,7 +234,7 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[]): void { - const nameToItems: Map = {}; + const nameToItems = createMap(); filterMutate(children, child => { const decl = child.node; const name = decl.name && nodeText(decl.name); @@ -243,7 +243,7 @@ namespace ts.NavigationBar { return true; } - const itemsWithSameName = getProperty(nameToItems, name); + const itemsWithSameName = nameToItems[name]; if (!itemsWithSameName) { nameToItems[name] = child; return true; diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 3d20337eca7..b4f67ca056d 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -113,7 +113,7 @@ namespace ts { // we see the name of a module that is used everywhere, or the name of an overload). As // such, we cache the information we compute about the candidate for the life of this // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans: Map = {}; + const stringToWordSpans = createMap(); pattern = pattern.trim(); @@ -188,7 +188,7 @@ namespace ts { } function getWordSpans(word: string): TextSpan[] { - if (!hasProperty(stringToWordSpans, word)) { + if (!(word in stringToWordSpans)) { stringToWordSpans[word] = breakIntoWordSpans(word); } diff --git a/src/services/services.ts b/src/services/services.ts index aea4d5f872e..148d3af427c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -975,7 +975,7 @@ namespace ts { } private computeNamedDeclarations(): Map { - const result: Map = {}; + const result = createMap(); forEachChild(this, visit); @@ -990,7 +990,7 @@ namespace ts { } function getDeclarations(name: string) { - return getProperty(result, name) || (result[name] = []); + return result[name] || (result[name] = []); } function getDeclarationName(declaration: Declaration) { @@ -2025,7 +2025,7 @@ namespace ts { fileName?: string; reportDiagnostics?: boolean; moduleName?: string; - renamedDependencies?: Map; + renamedDependencies?: MapLike; } export interface TranspileOutput { @@ -2042,7 +2042,7 @@ namespace ts { function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { // Lazily create this value to fix module loading errors. commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => - typeof o.type === "object" && !forEachValue(>o.type, v => typeof v !== "number")); + typeof o.type === "object" && !forEachProperty(o.type, v => typeof v !== "number")); options = clone(options); @@ -2058,7 +2058,7 @@ namespace ts { options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); } else { - if (!forEachValue(opt.type, v => v === value)) { + if (!forEachProperty(opt.type, v => v === value)) { // Supplied value isn't a valid enum value. diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); } @@ -2117,7 +2117,9 @@ namespace ts { sourceFile.moduleName = transpileOptions.moduleName; } - sourceFile.renamedDependencies = transpileOptions.renamedDependencies; + if (transpileOptions.renamedDependencies) { + sourceFile.renamedDependencies = createMap(transpileOptions.renamedDependencies); + } const newLine = getNewLineCharacter(options); @@ -2243,7 +2245,7 @@ namespace ts { export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have // for those settings. - const buckets: Map> = {}; + const buckets = createMap>(); const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { @@ -2251,7 +2253,7 @@ namespace ts { } function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap { - let bucket = lookUp(buckets, key); + let bucket = buckets[key]; if (!bucket && createIfMissing) { buckets[key] = bucket = createFileMap(); } @@ -2260,7 +2262,7 @@ namespace ts { function reportStats() { const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = lookUp(buckets, name); + const entries = buckets[name]; const sourceFiles: { name: string; refCount: number; references: string[]; }[] = []; entries.forEachValue((key, entry) => { sourceFiles.push({ @@ -3099,7 +3101,7 @@ namespace ts { oldSettings.allowJs !== newSettings.allowJs || oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit || oldSettings.baseUrl !== newSettings.baseUrl || - !mapIsEqualTo(oldSettings.paths, newSettings.paths)); + !equalOwnProperties(oldSettings.paths, newSettings.paths)); // Now create a new compiler const compilerHost: CompilerHost = { @@ -3114,7 +3116,6 @@ namespace ts { getCurrentDirectory: () => currentDirectory, fileExists: (fileName): boolean => { // stub missing host functionality - Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives); return hostCache.getOrCreateEntry(fileName) !== undefined; }, readFile: (fileName): string => { @@ -4102,7 +4103,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ImportOrExportSpecifier[]): Symbol[] { - const existingImportsOrExports: Map = {}; + const existingImportsOrExports = createMap(); for (const element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out @@ -4114,11 +4115,11 @@ namespace ts { existingImportsOrExports[name.text] = true; } - if (isEmpty(existingImportsOrExports)) { + if (!someProperties(existingImportsOrExports)) { return filter(exportsOfModule, e => e.name !== "default"); } - return filter(exportsOfModule, e => e.name !== "default" && !lookUp(existingImportsOrExports, e.name)); + return filter(exportsOfModule, e => e.name !== "default" && !existingImportsOrExports[e.name]); } /** @@ -4132,7 +4133,7 @@ namespace ts { return contextualMemberSymbols; } - const existingMemberNames: Map = {}; + const existingMemberNames = createMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyAssignment && @@ -4165,7 +4166,7 @@ namespace ts { existingMemberNames[existingName] = true; } - return filter(contextualMemberSymbols, m => !lookUp(existingMemberNames, m.name)); + return filter(contextualMemberSymbols, m => !existingMemberNames[m.name]); } /** @@ -4175,7 +4176,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames: Map = {}; + const seenNames = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (attr.getStart() <= position && position <= attr.getEnd()) { @@ -4187,7 +4188,7 @@ namespace ts { } } - return filter(symbols, a => !lookUp(seenNames, a.name)); + return filter(symbols, a => !seenNames[a.name]); } } @@ -4317,13 +4318,13 @@ namespace ts { function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map { const start = timestamp(); - const uniqueNames: Map = {}; + const uniqueNames = createMap(); if (symbols) { for (const symbol of symbols) { const entry = createCompletionEntry(symbol, location, performCharacterChecks); if (entry) { const id = escapeIdentifier(entry.name); - if (!lookUp(uniqueNames, id)) { + if (!uniqueNames[id]) { entries.push(entry); uniqueNames[id] = id; } @@ -5151,7 +5152,7 @@ namespace ts { // Type reference directives const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); if (typeReferenceDirective) { - const referenceFile = lookUp(program.getResolvedTypeReferenceDirectives(), typeReferenceDirective.fileName); + const referenceFile = program.getResolvedTypeReferenceDirectives()[typeReferenceDirective.fileName]; if (referenceFile && referenceFile.resolvedFileName) { return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; } @@ -5318,12 +5319,12 @@ namespace ts { return undefined; } - const fileNameToDocumentHighlights: Map = {}; + const fileNameToDocumentHighlights = createMap(); const result: DocumentHighlights[] = []; for (const referencedSymbol of referencedSymbols) { for (const referenceEntry of referencedSymbol.references) { const fileName = referenceEntry.fileName; - let documentHighlights = getProperty(fileNameToDocumentHighlights, fileName); + let documentHighlights = fileNameToDocumentHighlights[fileName]; if (!documentHighlights) { documentHighlights = { fileName, highlightSpans: [] }; @@ -6068,7 +6069,7 @@ namespace ts { const nameTable = getNameTable(sourceFile); - if (lookUp(nameTable, internedName) !== undefined) { + if (nameTable[internedName] !== undefined) { result = result || []; getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } @@ -6713,7 +6714,7 @@ namespace ts { // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); } }); @@ -6745,7 +6746,7 @@ namespace ts { // the function will add any found symbol of the property-name, then its sub-routine will call // getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already // visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol. - if (hasProperty(previousIterationSymbolsCache, symbol.name)) { + if (symbol.name in previousIterationSymbolsCache) { return; } @@ -6834,7 +6835,7 @@ namespace ts { // see if any is in the list if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { const result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); } @@ -8318,7 +8319,7 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable: Map = {}; + const nameTable = createMap(); walk(sourceFile); sourceFile.nameTable = nameTable; diff --git a/src/services/shims.ts b/src/services/shims.ts index 45c4b284ae7..ff57dd9cf7a 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -311,9 +311,9 @@ namespace ts { // 'in' does not have this effect. if ("getModuleResolutionsForFile" in this.shimHost) { this.resolveModuleNames = (moduleNames: string[], containingFile: string) => { - const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); + const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); return map(moduleNames, name => { - const result = lookUp(resolutionsInFile, name); + const result = getProperty(resolutionsInFile, name); return result ? { resolvedFileName: result } : undefined; }); }; @@ -323,8 +323,8 @@ namespace ts { } if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { this.resolveTypeReferenceDirectives = (typeDirectiveNames: string[], containingFile: string) => { - const typeDirectivesForFile = >JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile)); - return map(typeDirectiveNames, name => lookUp(typeDirectivesForFile, name)); + const typeDirectivesForFile = >JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile)); + return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)); }; } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 50378aa64b1..211e55b23ba 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -237,7 +237,7 @@ namespace ts.SignatureHelp { const typeChecker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { const nameToDeclarations = sourceFile.getNamedDeclarations(); - const declarations = getProperty(nameToDeclarations, name.text); + const declarations = nameToDeclarations[name.text]; if (declarations) { for (const declaration of declarations) { diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 7573f2ba99e..cb7fc3a4c5c 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -20,7 +20,7 @@ declare var path: any; import * as ts from "typescript"; function watch(rootFileNames: string[], options: ts.CompilerOptions) { - const files: ts.Map<{ version: number }> = {}; + const files: ts.MapLike<{ version: number }> = {}; // initialize the list of files rootFileNames.forEach(fileName => { diff --git a/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt b/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt deleted file mode 100644 index 875a656202d..00000000000 --- a/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt +++ /dev/null @@ -1,8 +0,0 @@ -tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts(1,16): error TS7005: Variable '"jquery"' implicitly has an 'any' type. - - -==== tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts (1 errors) ==== - declare module "jquery"; - ~~~~~~~~ -!!! error TS7005: Variable '"jquery"' implicitly has an 'any' type. - \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand_isImplicitAny.js b/tests/baselines/reference/ambientShorthand_isImplicitAny.js deleted file mode 100644 index ab05d2a9979..00000000000 --- a/tests/baselines/reference/ambientShorthand_isImplicitAny.js +++ /dev/null @@ -1,5 +0,0 @@ -//// [ambientShorthand_isImplicitAny.ts] -declare module "jquery"; - - -//// [ambientShorthand_isImplicitAny.js] diff --git a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt new file mode 100644 index 00000000000..6f4a0ef9d6d --- /dev/null +++ b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt @@ -0,0 +1,46 @@ +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: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): 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: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. + Type 'A[]' is not assignable to type 'B[]'. + Type 'A' is not assignable to type 'B'. + + +==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (2 errors) ==== + class A { a } + class B extends A { b } + class C extends Array { c } + declare var ara: A[]; + declare var arb: B[]; + declare var cra: C; + declare var crb: C; + declare var rra: ReadonlyArray; + declare var rrb: ReadonlyArray; + rra = ara; + rrb = arb; // OK, Array is assignable to ReadonlyArray + rra = arb; + rrb = ara; // error: 'A' is not assignable to 'B' + ~~~ +!!! error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray'. +!!! error TS2322: Types of property 'concat' are incompatible. +!!! error TS2322: Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): 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'. + + rra = cra; + rra = crb; // OK, C is assignable to ReadonlyArray + rrb = crb; + rrb = cra; // error: 'A' is not assignable to 'B' + ~~~ +!!! error TS2322: Type 'C' is not assignable to type 'ReadonlyArray'. +!!! error TS2322: Types of property 'concat' are incompatible. +!!! error TS2322: Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. +!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. +!!! error TS2322: Type 'A' is not assignable to type 'B'. + \ No newline at end of file diff --git a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js new file mode 100644 index 00000000000..255b52e16c9 --- /dev/null +++ b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js @@ -0,0 +1,54 @@ +//// [arrayOfSubtypeIsAssignableToReadonlyArray.ts] +class A { a } +class B extends A { b } +class C extends Array { c } +declare var ara: A[]; +declare var arb: B[]; +declare var cra: C; +declare var crb: C; +declare var rra: ReadonlyArray; +declare var rrb: ReadonlyArray; +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' + +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' + + +//// [arrayOfSubtypeIsAssignableToReadonlyArray.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var A = (function () { + function A() { + } + return A; +}()); +var B = (function (_super) { + __extends(B, _super); + function B() { + _super.apply(this, arguments); + } + return B; +}(A)); +var C = (function (_super) { + __extends(C, _super); + function C() { + _super.apply(this, arguments); + } + return C; +}(Array)); +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' diff --git a/tests/baselines/reference/classExpressionES63.js b/tests/baselines/reference/classExpressionES63.js index 6b3a06cf7c3..5a72e173475 100644 --- a/tests/baselines/reference/classExpressionES63.js +++ b/tests/baselines/reference/classExpressionES63.js @@ -13,14 +13,14 @@ let C = class extends class extends class { } } { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.b = 2; } } { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.c = 3; } } diff --git a/tests/baselines/reference/discriminantsAndPrimitives.js b/tests/baselines/reference/discriminantsAndPrimitives.js new file mode 100644 index 00000000000..1d11781a707 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.js @@ -0,0 +1,84 @@ +//// [discriminantsAndPrimitives.ts] + +// Repro from #10257 plus other tests + +interface Foo { + kind: "foo"; + name: string; +} + +interface Bar { + kind: "bar"; + length: string; +} + +function f1(x: Foo | Bar | string) { + if (typeof x !== 'string') { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f2(x: Foo | Bar | string | undefined) { + if (typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f3(x: Foo | Bar | string | null) { + if (x && typeof x !== "string") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f4(x: Foo | Bar | string | number | null) { + if (x && typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +//// [discriminantsAndPrimitives.js] +// Repro from #10257 plus other tests +function f1(x) { + if (typeof x !== 'string') { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f2(x) { + if (typeof x === "object") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f3(x) { + if (x && typeof x !== "string") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f4(x) { + if (x && typeof x === "object") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} diff --git a/tests/baselines/reference/discriminantsAndPrimitives.symbols b/tests/baselines/reference/discriminantsAndPrimitives.symbols new file mode 100644 index 00000000000..c84f32cd990 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.symbols @@ -0,0 +1,117 @@ +=== tests/cases/compiler/discriminantsAndPrimitives.ts === + +// Repro from #10257 plus other tests + +interface Foo { +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) + + kind: "foo"; +>kind : Symbol(Foo.kind, Decl(discriminantsAndPrimitives.ts, 3, 15)) + + name: string; +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +} + +interface Bar { +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + kind: "bar"; +>kind : Symbol(Bar.kind, Decl(discriminantsAndPrimitives.ts, 8, 15)) + + length: string; +>length : Symbol(Bar.length, Decl(discriminantsAndPrimitives.ts, 9, 16)) +} + +function f1(x: Foo | Bar | string) { +>f1 : Symbol(f1, Decl(discriminantsAndPrimitives.ts, 11, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (typeof x !== 'string') { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f2(x: Foo | Bar | string | undefined) { +>f2 : Symbol(f2, Decl(discriminantsAndPrimitives.ts, 20, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (typeof x === "object") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f3(x: Foo | Bar | string | null) { +>f3 : Symbol(f3, Decl(discriminantsAndPrimitives.ts, 29, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (x && typeof x !== "string") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f4(x: Foo | Bar | string | number | null) { +>f4 : Symbol(f4, Decl(discriminantsAndPrimitives.ts, 38, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (x && typeof x === "object") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} diff --git a/tests/baselines/reference/discriminantsAndPrimitives.types b/tests/baselines/reference/discriminantsAndPrimitives.types new file mode 100644 index 00000000000..d3e1b70e599 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.types @@ -0,0 +1,141 @@ +=== tests/cases/compiler/discriminantsAndPrimitives.ts === + +// Repro from #10257 plus other tests + +interface Foo { +>Foo : Foo + + kind: "foo"; +>kind : "foo" + + name: string; +>name : string +} + +interface Bar { +>Bar : Bar + + kind: "bar"; +>kind : "bar" + + length: string; +>length : string +} + +function f1(x: Foo | Bar | string) { +>f1 : (x: string | Foo | Bar) => void +>x : string | Foo | Bar +>Foo : Foo +>Bar : Bar + + if (typeof x !== 'string') { +>typeof x !== 'string' : boolean +>typeof x : string +>x : string | Foo | Bar +>'string' : "string" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f2(x: Foo | Bar | string | undefined) { +>f2 : (x: string | Foo | Bar | undefined) => void +>x : string | Foo | Bar | undefined +>Foo : Foo +>Bar : Bar + + if (typeof x === "object") { +>typeof x === "object" : boolean +>typeof x : string +>x : string | Foo | Bar | undefined +>"object" : "object" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f3(x: Foo | Bar | string | null) { +>f3 : (x: string | Foo | Bar | null) => void +>x : string | Foo | Bar | null +>Foo : Foo +>Bar : Bar +>null : null + + if (x && typeof x !== "string") { +>x && typeof x !== "string" : boolean | "" | null +>x : string | Foo | Bar | null +>typeof x !== "string" : boolean +>typeof x : string +>x : string | Foo | Bar +>"string" : "string" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f4(x: Foo | Bar | string | number | null) { +>f4 : (x: string | number | Foo | Bar | null) => void +>x : string | number | Foo | Bar | null +>Foo : Foo +>Bar : Bar +>null : null + + if (x && typeof x === "object") { +>x && typeof x === "object" : boolean | "" | 0 | null +>x : string | number | Foo | Bar | null +>typeof x === "object" : boolean +>typeof x : string +>x : string | number | Foo | Bar +>"object" : "object" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} diff --git a/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js b/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js index 03fb45b806e..91e6c506c2b 100644 --- a/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js +++ b/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js @@ -37,8 +37,8 @@ class D { } } class E extends D { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.z = true; } } diff --git a/tests/baselines/reference/implicitConstParameters.errors.txt b/tests/baselines/reference/implicitConstParameters.errors.txt new file mode 100644 index 00000000000..95ff60d71f8 --- /dev/null +++ b/tests/baselines/reference/implicitConstParameters.errors.txt @@ -0,0 +1,65 @@ +tests/cases/compiler/implicitConstParameters.ts(39,27): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/implicitConstParameters.ts(45,27): error TS2532: Object is possibly 'undefined'. + + +==== tests/cases/compiler/implicitConstParameters.ts (2 errors) ==== + + function doSomething(cb: () => void) { + cb(); + } + + function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } + } + + function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); + } + + function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } + } + + function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } + } + + function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + } + + function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + x = "abc"; // causes x to be considered non-const + } + + + function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } + } \ No newline at end of file diff --git a/tests/baselines/reference/implicitConstParameters.js b/tests/baselines/reference/implicitConstParameters.js new file mode 100644 index 00000000000..a5faf5b1253 --- /dev/null +++ b/tests/baselines/reference/implicitConstParameters.js @@ -0,0 +1,106 @@ +//// [implicitConstParameters.ts] + +function doSomething(cb: () => void) { + cb(); +} + +function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } +} + +function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); +} + +function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } +} + +function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } +} + +function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + } +} + +function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + } + x = "abc"; // causes x to be considered non-const +} + + +function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } +} + +//// [implicitConstParameters.js] +function doSomething(cb) { + cb(); +} +function fn(x) { + if (typeof x === 'number') { + doSomething(function () { return x.toFixed(); }); + } +} +function f1(x) { + if (!x) { + return; + } + doSomething(function () { return x.length; }); +} +function f2(x) { + if (x) { + doSomething(function () { + doSomething(function () { return x.length; }); + }); + } +} +function f3(x) { + inner(); + function inner() { + if (x) { + doSomething(function () { return x.length; }); + } + } +} +function f4(x) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(function () { return x.length; }); + } +} +function f5(x) { + if (x) { + doSomething(function () { return x.length; }); + } + x = "abc"; // causes x to be considered non-const +} +function f6(x) { + var y = x || ""; + if (x) { + doSomething(function () { return y.length; }); + } +} diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.js b/tests/baselines/reference/indexWithUndefinedAndNull.js new file mode 100644 index 00000000000..f885323efae --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.js @@ -0,0 +1,22 @@ +//// [indexWithUndefinedAndNull.ts] +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; + + +//// [indexWithUndefinedAndNull.js] +var n; +var s; +var str = n[undefined]; +str = n[null]; +var num = s[undefined]; +num = s[null]; diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.symbols b/tests/baselines/reference/indexWithUndefinedAndNull.symbols new file mode 100644 index 00000000000..15e8bbdbf41 --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.symbols @@ -0,0 +1,39 @@ +=== tests/cases/compiler/indexWithUndefinedAndNull.ts === +interface N { +>N : Symbol(N, Decl(indexWithUndefinedAndNull.ts, 0, 0)) + + [n: number]: string; +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 1, 5)) +} +interface S { +>S : Symbol(S, Decl(indexWithUndefinedAndNull.ts, 2, 1)) + + [s: string]: number; +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 4, 5)) +} +let n: N; +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) +>N : Symbol(N, Decl(indexWithUndefinedAndNull.ts, 0, 0)) + +let s: S; +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) +>S : Symbol(S, Decl(indexWithUndefinedAndNull.ts, 2, 1)) + +let str: string = n[undefined]; +>str : Symbol(str, Decl(indexWithUndefinedAndNull.ts, 8, 3)) +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) +>undefined : Symbol(undefined) + +str = n[null]; +>str : Symbol(str, Decl(indexWithUndefinedAndNull.ts, 8, 3)) +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) + +let num: number = s[undefined]; +>num : Symbol(num, Decl(indexWithUndefinedAndNull.ts, 10, 3)) +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) +>undefined : Symbol(undefined) + +num = s[null]; +>num : Symbol(num, Decl(indexWithUndefinedAndNull.ts, 10, 3)) +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) + diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.types b/tests/baselines/reference/indexWithUndefinedAndNull.types new file mode 100644 index 00000000000..07b6050503a --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.types @@ -0,0 +1,47 @@ +=== tests/cases/compiler/indexWithUndefinedAndNull.ts === +interface N { +>N : N + + [n: number]: string; +>n : number +} +interface S { +>S : S + + [s: string]: number; +>s : string +} +let n: N; +>n : N +>N : N + +let s: S; +>s : S +>S : S + +let str: string = n[undefined]; +>str : string +>n[undefined] : string +>n : N +>undefined : undefined + +str = n[null]; +>str = n[null] : string +>str : string +>n[null] : string +>n : N +>null : null + +let num: number = s[undefined]; +>num : number +>s[undefined] : number +>s : S +>undefined : undefined + +num = s[null]; +>num = s[null] : number +>num : number +>s[null] : number +>s : S +>null : null + diff --git a/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt new file mode 100644 index 00000000000..a1fce75c54e --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt @@ -0,0 +1,40 @@ +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(9,19): error TS2454: Variable 'n' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(9,19): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(10,7): error TS2454: Variable 'n' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(10,7): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(11,19): error TS2454: Variable 's' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(11,19): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(12,7): error TS2454: Variable 's' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(12,7): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + + +==== tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts (8 errors) ==== + interface N { + [n: number]: string; + } + interface S { + [s: string]: number; + } + let n: N; + let s: S; + let str: string = n[undefined]; + ~ +!!! error TS2454: Variable 'n' is used before being assigned. + ~~~~~~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + str = n[null]; + ~ +!!! error TS2454: Variable 'n' is used before being assigned. + ~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + let num: number = s[undefined]; + ~ +!!! error TS2454: Variable 's' is used before being assigned. + ~~~~~~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + num = s[null]; + ~ +!!! error TS2454: Variable 's' is used before being assigned. + ~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + \ No newline at end of file diff --git a/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js new file mode 100644 index 00000000000..9300b7c0549 --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js @@ -0,0 +1,22 @@ +//// [indexWithUndefinedAndNullStrictNullChecks.ts] +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; + + +//// [indexWithUndefinedAndNullStrictNullChecks.js] +var n; +var s; +var str = n[undefined]; +str = n[null]; +var num = s[undefined]; +num = s[null]; diff --git a/tests/baselines/reference/jsdocLiteral.js b/tests/baselines/reference/jsdocLiteral.js new file mode 100644 index 00000000000..1a017160302 --- /dev/null +++ b/tests/baselines/reference/jsdocLiteral.js @@ -0,0 +1,24 @@ +//// [in.js] +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; +} + + +//// [out.js] +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; +} diff --git a/tests/baselines/reference/jsdocLiteral.symbols b/tests/baselines/reference/jsdocLiteral.symbols new file mode 100644 index 00000000000..9f01fe782aa --- /dev/null +++ b/tests/baselines/reference/jsdocLiteral.symbols @@ -0,0 +1,24 @@ +=== tests/cases/conformance/jsdoc/in.js === +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { +>f : Symbol(f, Decl(in.js, 0, 0)) +>p1 : Symbol(p1, Decl(in.js, 7, 11)) +>p2 : Symbol(p2, Decl(in.js, 7, 14)) +>p3 : Symbol(p3, Decl(in.js, 7, 18)) +>p4 : Symbol(p4, Decl(in.js, 7, 22)) +>p5 : Symbol(p5, Decl(in.js, 7, 26)) + + return p1 + p2 + p3 + p4 + p5 + '.'; +>p1 : Symbol(p1, Decl(in.js, 7, 11)) +>p2 : Symbol(p2, Decl(in.js, 7, 14)) +>p3 : Symbol(p3, Decl(in.js, 7, 18)) +>p4 : Symbol(p4, Decl(in.js, 7, 22)) +>p5 : Symbol(p5, Decl(in.js, 7, 26)) +} + diff --git a/tests/baselines/reference/jsdocLiteral.types b/tests/baselines/reference/jsdocLiteral.types new file mode 100644 index 00000000000..b6c9e522ac3 --- /dev/null +++ b/tests/baselines/reference/jsdocLiteral.types @@ -0,0 +1,30 @@ +=== tests/cases/conformance/jsdoc/in.js === +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { +>f : (p1: "literal", p2: "literal", p3: "literal" | "other", p4: number | "literal", p5: true | 12 | "str") => string +>p1 : "literal" +>p2 : "literal" +>p3 : "literal" | "other" +>p4 : number | "literal" +>p5 : true | 12 | "str" + + return p1 + p2 + p3 + p4 + p5 + '.'; +>p1 + p2 + p3 + p4 + p5 + '.' : string +>p1 + p2 + p3 + p4 + p5 : string +>p1 + p2 + p3 + p4 : string +>p1 + p2 + p3 : string +>p1 + p2 : string +>p1 : "literal" +>p2 : "literal" +>p3 : "literal" | "other" +>p4 : number | "literal" +>p5 : true | 12 | "str" +>'.' : string +} + diff --git a/tests/baselines/reference/library-reference-5.errors.txt b/tests/baselines/reference/library-reference-5.errors.txt index a3729bc3a99..c23b49c61de 100644 --- a/tests/baselines/reference/library-reference-5.errors.txt +++ b/tests/baselines/reference/library-reference-5.errors.txt @@ -1,4 +1,4 @@ -/node_modules/bar/index.d.ts(1,1): message TS4090: Conflicting library definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Copy the correct file to the 'typings' folder to resolve this conflict. +/node_modules/bar/index.d.ts(1,1): message TS4090: Conflicting definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Consider installing a specific version of this library to resolve the conflict. ==== /src/root.ts (0 errors) ==== @@ -18,7 +18,7 @@ ==== /node_modules/bar/index.d.ts (1 errors) ==== /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! message TS4090: Conflicting library definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Copy the correct file to the 'typings' folder to resolve this conflict. +!!! message TS4090: Conflicting definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Consider installing a specific version of this library to resolve the conflict. declare var bar: any; ==== /node_modules/bar/node_modules/alpha/index.d.ts (0 errors) ==== diff --git a/tests/baselines/reference/multipleDeclarations.js b/tests/baselines/reference/multipleDeclarations.js index 5f5ac29e909..7fb7e0bca02 100644 --- a/tests/baselines/reference/multipleDeclarations.js +++ b/tests/baselines/reference/multipleDeclarations.js @@ -1,11 +1,38 @@ //// [input.js] - function C() { this.m = null; } C.prototype.m = function() { this.nothing(); -}; +} +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; +class Y { + mistake() { + } + m() { + } + constructor() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } +} +Y.prototype.mistake = true; +let y = new Y(); +y.m(); +y.mistake(); //// [output.js] @@ -15,3 +42,33 @@ function C() { C.prototype.m = function () { this.nothing(); }; +var X = (function () { + function X() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + X.prototype.m = function () { + }; + X.prototype.mistake = function () { + }; + return X; +}()); +var x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; +var Y = (function () { + function Y() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } + Y.prototype.mistake = function () { + }; + Y.prototype.m = function () { + }; + return Y; +}()); +Y.prototype.mistake = true; +var y = new Y(); +y.m(); +y.mistake(); diff --git a/tests/baselines/reference/multipleDeclarations.symbols b/tests/baselines/reference/multipleDeclarations.symbols index a256943dd50..9e49c2fc00c 100644 --- a/tests/baselines/reference/multipleDeclarations.symbols +++ b/tests/baselines/reference/multipleDeclarations.symbols @@ -1,19 +1,106 @@ === tests/cases/conformance/salsa/input.js === - function C() { >C : Symbol(C, Decl(input.js, 0, 0)) this.m = null; ->m : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>m : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) } C.prototype.m = function() { ->C.prototype : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>C.prototype : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) >C : Symbol(C, Decl(input.js, 0, 0)) >prototype : Symbol(Function.prototype, Decl(lib.d.ts, --, --)) ->m : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>m : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) this.nothing(); >this : Symbol(C, Decl(input.js, 0, 0)) +} +class X { +>X : Symbol(X, Decl(input.js, 5, 1)) -}; + constructor() { + this.m = this.m.bind(this); +>this.m : Symbol(X.m, Decl(input.js, 10, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>m : Symbol(X.m, Decl(input.js, 7, 19)) +>this.m.bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this.m : Symbol(X.m, Decl(input.js, 10, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>m : Symbol(X.m, Decl(input.js, 10, 5)) +>bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this : Symbol(X, Decl(input.js, 5, 1)) + + this.mistake = 'frankly, complete nonsense'; +>this.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>mistake : Symbol(X.mistake, Decl(input.js, 8, 35)) + } + m() { +>m : Symbol(X.m, Decl(input.js, 10, 5)) + } + mistake() { +>mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) + } +} +let x = new X(); +>x : Symbol(x, Decl(input.js, 16, 3)) +>X : Symbol(X, Decl(input.js, 5, 1)) + +X.prototype.mistake = false; +>X.prototype.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>X : Symbol(X, Decl(input.js, 5, 1)) +>prototype : Symbol(X.prototype) + +x.m(); +>x.m : Symbol(X.m, Decl(input.js, 10, 5)) +>x : Symbol(x, Decl(input.js, 16, 3)) +>m : Symbol(X.m, Decl(input.js, 10, 5)) + +x.mistake; +>x.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>x : Symbol(x, Decl(input.js, 16, 3)) +>mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) + +class Y { +>Y : Symbol(Y, Decl(input.js, 19, 10)) + + mistake() { +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) + } + m() { +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) + } + constructor() { + this.m = this.m.bind(this); +>this.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) + + this.mistake = 'even more nonsense'; +>this.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) + } +} +Y.prototype.mistake = true; +>Y.prototype.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>Y : Symbol(Y, Decl(input.js, 19, 10)) +>prototype : Symbol(Y.prototype) + +let y = new Y(); +>y : Symbol(y, Decl(input.js, 31, 3)) +>Y : Symbol(Y, Decl(input.js, 19, 10)) + +y.m(); +>y.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>y : Symbol(y, Decl(input.js, 31, 3)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) + +y.mistake(); +>y.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>y : Symbol(y, Decl(input.js, 31, 3)) +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) diff --git a/tests/baselines/reference/multipleDeclarations.types b/tests/baselines/reference/multipleDeclarations.types index 7c0a3de70c9..900d03195d4 100644 --- a/tests/baselines/reference/multipleDeclarations.types +++ b/tests/baselines/reference/multipleDeclarations.types @@ -1,5 +1,4 @@ === tests/cases/conformance/salsa/input.js === - function C() { >C : () => void @@ -24,6 +23,117 @@ C.prototype.m = function() { >this.nothing : any >this : { m: () => void; } >nothing : any +} +class X { +>X : X -}; + constructor() { + this.m = this.m.bind(this); +>this.m = this.m.bind(this) : any +>this.m : () => void +>this : this +>m : () => void +>this.m.bind(this) : any +>this.m.bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this.m : () => void +>this : this +>m : () => void +>bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this : this + + this.mistake = 'frankly, complete nonsense'; +>this.mistake = 'frankly, complete nonsense' : string +>this.mistake : () => void +>this : this +>mistake : () => void +>'frankly, complete nonsense' : string + } + m() { +>m : () => void + } + mistake() { +>mistake : () => void + } +} +let x = new X(); +>x : X +>new X() : X +>X : typeof X + +X.prototype.mistake = false; +>X.prototype.mistake = false : boolean +>X.prototype.mistake : () => void +>X.prototype : X +>X : typeof X +>prototype : X +>mistake : () => void +>false : boolean + +x.m(); +>x.m() : void +>x.m : () => void +>x : X +>m : () => void + +x.mistake; +>x.mistake : () => void +>x : X +>mistake : () => void + +class Y { +>Y : Y + + mistake() { +>mistake : any + } + m() { +>m : any + } + constructor() { + this.m = this.m.bind(this); +>this.m = this.m.bind(this) : any +>this.m : any +>this : this +>m : any +>this.m.bind(this) : any +>this.m.bind : any +>this.m : any +>this : this +>m : any +>bind : any +>this : this + + this.mistake = 'even more nonsense'; +>this.mistake = 'even more nonsense' : string +>this.mistake : any +>this : this +>mistake : any +>'even more nonsense' : string + } +} +Y.prototype.mistake = true; +>Y.prototype.mistake = true : boolean +>Y.prototype.mistake : any +>Y.prototype : Y +>Y : typeof Y +>prototype : Y +>mistake : any +>true : boolean + +let y = new Y(); +>y : Y +>new Y() : Y +>Y : typeof Y + +y.m(); +>y.m() : any +>y.m : any +>y : Y +>m : any + +y.mistake(); +>y.mistake() : any +>y.mistake : any +>y : Y +>mistake : any diff --git a/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt b/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt new file mode 100644 index 00000000000..e435b98050d --- /dev/null +++ b/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(11,17): error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'. +tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(16,17): error TS2339: Property 'massage' does not exist on type 'Error'. + + +==== tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts (2 errors) ==== + declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + + function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + ~~~~~~~ +!!! error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'. + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + ~~~~~~~ +!!! error TS2339: Property 'massage' does not exist on type 'Error'. + } + + else { + throw err; + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowExceptionVariableInCatchClause.js b/tests/baselines/reference/narrowExceptionVariableInCatchClause.js new file mode 100644 index 00000000000..5808ed76826 --- /dev/null +++ b/tests/baselines/reference/narrowExceptionVariableInCatchClause.js @@ -0,0 +1,44 @@ +//// [narrowExceptionVariableInCatchClause.ts] +declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + +function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + + else { + throw err; + } + } +} + + +//// [narrowExceptionVariableInCatchClause.js] +function tryCatch() { + try { + } + catch (err) { + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + else { + throw err; + } + } +} diff --git a/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt b/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt new file mode 100644 index 00000000000..3e152b0faf4 --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(17,7): error TS2339: Property 'mesage' does not exist on type 'Error'. +tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(22,7): error TS2339: Property 'getHuors' does not exist on type 'Date'. + + +==== tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts (2 errors) ==== + declare var x: any; + + if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; + } + + if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); + } + + if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; + ~~~~~~ +!!! error TS2339: Property 'mesage' does not exist on type 'Error'. + } + + if (x instanceof Date) { + x.getDate(); + x.getHuors(); + ~~~~~~~~ +!!! error TS2339: Property 'getHuors' does not exist on type 'Date'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowFromAnyWithInstanceof.js b/tests/baselines/reference/narrowFromAnyWithInstanceof.js new file mode 100644 index 00000000000..4cf1ca174aa --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithInstanceof.js @@ -0,0 +1,45 @@ +//// [narrowFromAnyWithInstanceof.ts] +declare var x: any; + +if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; +} + +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} + + +//// [narrowFromAnyWithInstanceof.js] +if (x instanceof Function) { + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} +if (x instanceof Object) { + x.method(); + x(); +} +if (x instanceof Error) { + x.message; + x.mesage; +} +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt b/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt new file mode 100644 index 00000000000..94f41becdad --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(22,7): error TS2339: Property 'method' does not exist on type '{}'. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(23,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(28,7): error TS2339: Property 'mesage' does not exist on type 'Error'. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(33,7): error TS2339: Property 'getHuors' does not exist on type 'Date'. + + +==== tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts (4 errors) ==== + declare var x: any; + declare function isFunction(x): x is Function; + declare function isObject(x): x is Object; + declare function isAnything(x): x is {}; + declare function isError(x): x is Error; + declare function isDate(x): x is Date; + + + if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; + } + + if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); + } + + if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + ~~~~~~ +!!! error TS2339: Property 'method' does not exist on type '{}'. + x(); + ~~~ +!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. + } + + if (isError(x)) { + x.message; + x.mesage; + ~~~~~~ +!!! error TS2339: Property 'mesage' does not exist on type 'Error'. + } + + if (isDate(x)) { + x.getDate(); + x.getHuors(); + ~~~~~~~~ +!!! error TS2339: Property 'getHuors' does not exist on type 'Date'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowFromAnyWithTypePredicate.js b/tests/baselines/reference/narrowFromAnyWithTypePredicate.js new file mode 100644 index 00000000000..958a3cfd70d --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithTypePredicate.js @@ -0,0 +1,60 @@ +//// [narrowFromAnyWithTypePredicate.ts] +declare var x: any; +declare function isFunction(x): x is Function; +declare function isObject(x): x is Object; +declare function isAnything(x): x is {}; +declare function isError(x): x is Error; +declare function isDate(x): x is Date; + + +if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + x(); +} + +if (isError(x)) { + x.message; + x.mesage; +} + +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} + + +//// [narrowFromAnyWithTypePredicate.js] +if (isFunction(x)) { + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} +if (isObject(x)) { + x.method(); + x(); +} +if (isAnything(x)) { + x.method(); + x(); +} +if (isError(x)) { + x.message; + x.mesage; +} +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/baselines/reference/tsxAttributeResolution14.errors.txt b/tests/baselines/reference/tsxAttributeResolution14.errors.txt new file mode 100644 index 00000000000..81d42e49059 --- /dev/null +++ b/tests/baselines/reference/tsxAttributeResolution14.errors.txt @@ -0,0 +1,38 @@ +tests/cases/conformance/jsx/file.tsx(14,28): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/conformance/jsx/file.tsx(16,28): error TS2322: Type 'boolean' is not assignable to type 'string | number'. + + +==== tests/cases/conformance/jsx/react.d.ts (0 errors) ==== + + declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } + } + +==== tests/cases/conformance/jsx/file.tsx (2 errors) ==== + + interface IProps { + primaryText: string, + [propName: string]: string | number + } + + function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+ } + + function VerticalNav() { + return ( +
+ // error + ~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + // ok + // error + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'string | number'. +
+ ) + } \ No newline at end of file diff --git a/tests/baselines/reference/tsxAttributeResolution14.js b/tests/baselines/reference/tsxAttributeResolution14.js new file mode 100644 index 00000000000..d920179458c --- /dev/null +++ b/tests/baselines/reference/tsxAttributeResolution14.js @@ -0,0 +1,47 @@ +//// [tests/cases/conformance/jsx/tsxAttributeResolution14.tsx] //// + +//// [react.d.ts] + +declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } +} + +//// [file.tsx] + +interface IProps { + primaryText: string, + [propName: string]: string | number +} + +function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+} + +function VerticalNav() { + return ( +
+ // error + // ok + // error +
+ ) +} + +//// [file.jsx] +function VerticalNavMenuItem(prop) { + return
props.primaryText
; +} +function VerticalNav() { + return (
+ // error + // error + // ok + // ok + // error + // error +
); +} diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt index c574118f91d..b67f7328856 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt @@ -1,16 +1,26 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(18,10): error TS2339: Property 'bar' does not exist on type 'A'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,5): error TS2322: Type 'string' is not assignable to type 'number'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type 'B'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(41,10): error TS2339: Property 'bar' does not exist on type 'B'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(72,10): error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(73,10): error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(85,10): error TS2339: Property 'bar' does not exist on type 'D'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(91,10): error TS2339: Property 'bar' does not exist on type 'D'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(118,11): error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(119,11): error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(134,11): error TS2339: Property 'foo' does not exist on type 'string | F'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(135,11): error TS2339: Property 'bar' does not exist on type 'string | F'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(160,11): error TS2339: Property 'foo2' does not exist on type 'G1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(166,11): error TS2339: Property 'foo2' does not exist on type 'G1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(182,11): error TS2339: Property 'bar' does not exist on type 'H'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(187,11): error TS2339: Property 'foo1' does not exist on type 'H'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(188,11): error TS2339: Property 'foo2' does not exist on type 'H'. -==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (10 errors) ==== +==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (20 errors) ==== interface AConstructor { new (): A; } @@ -28,9 +38,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj2: any; - if (obj2 instanceof A) { // can't narrow type from 'any' + if (obj2 instanceof A) { obj2.foo; obj2.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'A'. } // a construct signature with generics @@ -54,10 +66,12 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj4: any; - if (obj4 instanceof B) { // can't narrow type from 'any' + if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'B'. } // has multiple construct signature @@ -88,10 +102,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj6: any; - if (obj6 instanceof C) { // can't narrow type from 'any' + if (obj6 instanceof C) { obj6.foo; obj6.bar1; + ~~~~ +!!! error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. obj6.bar2; + ~~~~ +!!! error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. } // with object type literal @@ -109,9 +127,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj8: any; - if (obj8 instanceof D) { // can't narrow type from 'any' + if (obj8 instanceof D) { obj8.foo; obj8.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'D'. } // a construct signature that returns a union type @@ -138,10 +158,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj10: any; - if (obj10 instanceof E) { // can't narrow type from 'any' + if (obj10 instanceof E) { obj10.foo; obj10.bar1; + ~~~~ +!!! error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. obj10.bar2; + ~~~~ +!!! error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. } // a construct signature that returns any @@ -165,7 +189,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj12: any; - if (obj12 instanceof F) { // can't narrow type from 'any' + if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -192,9 +216,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj14: any; - if (obj14 instanceof G) { // can't narrow type from 'any' + if (obj14 instanceof G) { obj14.foo1; obj14.foo2; + ~~~~ +!!! error TS2339: Property 'foo2' does not exist on type 'G1'. } // a type with a prototype that has any type @@ -216,8 +242,24 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj16: any; - if (obj16 instanceof H) { // can't narrow type from 'any' + if (obj16 instanceof H) { obj16.foo1; + ~~~~ +!!! error TS2339: Property 'foo1' does not exist on type 'H'. obj16.foo2; + ~~~~ +!!! error TS2339: Property 'foo2' does not exist on type 'H'. + } + + var obj17: any; + if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; + } + + var obj18: any; + if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js index 7e6b3324470..40ef6587e75 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js @@ -14,7 +14,7 @@ if (obj1 instanceof A) { // narrowed to A. } var obj2: any; -if (obj2 instanceof A) { // can't narrow type from 'any' +if (obj2 instanceof A) { obj2.foo; obj2.bar; } @@ -36,7 +36,7 @@ if (obj3 instanceof B) { // narrowed to B. } var obj4: any; -if (obj4 instanceof B) { // can't narrow type from 'any' +if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; @@ -68,7 +68,7 @@ if (obj5 instanceof C) { // narrowed to C1|C2. } var obj6: any; -if (obj6 instanceof C) { // can't narrow type from 'any' +if (obj6 instanceof C) { obj6.foo; obj6.bar1; obj6.bar2; @@ -87,7 +87,7 @@ if (obj7 instanceof D) { // narrowed to D. } var obj8: any; -if (obj8 instanceof D) { // can't narrow type from 'any' +if (obj8 instanceof D) { obj8.foo; obj8.bar; } @@ -114,7 +114,7 @@ if (obj9 instanceof E) { // narrowed to E1 | E2 } var obj10: any; -if (obj10 instanceof E) { // can't narrow type from 'any' +if (obj10 instanceof E) { obj10.foo; obj10.bar1; obj10.bar2; @@ -137,7 +137,7 @@ if (obj11 instanceof F) { // can't type narrowing, construct signature returns a } var obj12: any; -if (obj12 instanceof F) { // can't narrow type from 'any' +if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -162,7 +162,7 @@ if (obj13 instanceof G) { // narrowed to G1. G1 is return type of prototype prop } var obj14: any; -if (obj14 instanceof G) { // can't narrow type from 'any' +if (obj14 instanceof G) { obj14.foo1; obj14.foo2; } @@ -184,10 +184,22 @@ if (obj15 instanceof H) { // narrowed to H. } var obj16: any; -if (obj16 instanceof H) { // can't narrow type from 'any' +if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } + +var obj17: any; +if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; +} + +var obj18: any; +if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; +} //// [typeGuardsWithInstanceOfByConstructorSignature.js] @@ -278,3 +290,13 @@ if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } +var obj17; +if (obj17 instanceof Object) { + obj17.foo1; + obj17.foo2; +} +var obj18; +if (obj18 instanceof Function) { + obj18.foo1; + obj18.foo2; +} diff --git a/tests/baselines/reference/typingsLookup2.js b/tests/baselines/reference/typingsLookup2.js new file mode 100644 index 00000000000..3e816526af2 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.js @@ -0,0 +1,9 @@ +//// [tests/cases/conformance/typings/typingsLookup2.ts] //// + +//// [package.json] +{ "typings": null } + +//// [a.ts] + + +//// [a.js] diff --git a/tests/baselines/reference/typingsLookup2.symbols b/tests/baselines/reference/typingsLookup2.symbols new file mode 100644 index 00000000000..7223c8589a6 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.symbols @@ -0,0 +1,3 @@ +=== /a.ts === + +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup2.trace.json b/tests/baselines/reference/typingsLookup2.trace.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.trace.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup2.types b/tests/baselines/reference/typingsLookup2.types new file mode 100644 index 00000000000..7223c8589a6 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.types @@ -0,0 +1,3 @@ +=== /a.ts === + +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup3.js b/tests/baselines/reference/typingsLookup3.js new file mode 100644 index 00000000000..b3be036b4ae --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.js @@ -0,0 +1,13 @@ +//// [tests/cases/conformance/typings/typingsLookup3.ts] //// + +//// [index.d.ts] +declare var $: { x: any }; + +//// [a.ts] +/// +$.x; + + +//// [a.js] +/// +$.x; diff --git a/tests/baselines/reference/typingsLookup3.symbols b/tests/baselines/reference/typingsLookup3.symbols new file mode 100644 index 00000000000..e641afb183b --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.symbols @@ -0,0 +1,12 @@ +=== /a.ts === +/// +$.x; +>$.x : Symbol(x, Decl(index.d.ts, 0, 16)) +>$ : Symbol($, Decl(index.d.ts, 0, 11)) +>x : Symbol(x, Decl(index.d.ts, 0, 16)) + +=== /node_modules/@types/jquery/index.d.ts === +declare var $: { x: any }; +>$ : Symbol($, Decl(index.d.ts, 0, 11)) +>x : Symbol(x, Decl(index.d.ts, 0, 16)) + diff --git a/tests/baselines/reference/typingsLookup3.trace.json b/tests/baselines/reference/typingsLookup3.trace.json new file mode 100644 index 00000000000..83b0e91d6c7 --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.trace.json @@ -0,0 +1,12 @@ +[ + "======== Resolving type reference directive 'jquery', containing file '/a.ts', root directory '/node_modules/@types'. ========", + "Resolving with primary search path '/node_modules/@types'", + "File '/node_modules/@types/jquery/package.json' does not exist.", + "File '/node_modules/@types/jquery/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'jquery' was successfully resolved to '/node_modules/@types/jquery/index.d.ts', primary: true. ========", + "======== Resolving type reference directive 'jquery', containing file '/__inferred type names__.ts', root directory '/node_modules/@types'. ========", + "Resolving with primary search path '/node_modules/@types'", + "File '/node_modules/@types/jquery/package.json' does not exist.", + "File '/node_modules/@types/jquery/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'jquery' was successfully resolved to '/node_modules/@types/jquery/index.d.ts', primary: true. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup3.types b/tests/baselines/reference/typingsLookup3.types new file mode 100644 index 00000000000..f57a974077e --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.types @@ -0,0 +1,12 @@ +=== /a.ts === +/// +$.x; +>$.x : any +>$ : { x: any; } +>x : any + +=== /node_modules/@types/jquery/index.d.ts === +declare var $: { x: any }; +>$ : { x: any; } +>x : any + diff --git a/tests/baselines/reference/unusedParameterProperty1.errors.txt b/tests/baselines/reference/unusedParameterProperty1.errors.txt new file mode 100644 index 00000000000..0581199313f --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty1.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/unusedParameterProperty1.ts(3,25): error TS6138: Property 'used' is declared but never used. + + +==== tests/cases/compiler/unusedParameterProperty1.ts (1 errors) ==== + + class A { + constructor(private used: string) { + ~~~~ +!!! error TS6138: Property 'used' is declared but never used. + let foge = used; + foge += ""; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/unusedParameterProperty1.js b/tests/baselines/reference/unusedParameterProperty1.js new file mode 100644 index 00000000000..d0b608a6d87 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty1.js @@ -0,0 +1,19 @@ +//// [unusedParameterProperty1.ts] + +class A { + constructor(private used: string) { + let foge = used; + foge += ""; + } +} + + +//// [unusedParameterProperty1.js] +var A = (function () { + function A(used) { + this.used = used; + var foge = used; + foge += ""; + } + return A; +}()); diff --git a/tests/baselines/reference/unusedParameterProperty2.errors.txt b/tests/baselines/reference/unusedParameterProperty2.errors.txt new file mode 100644 index 00000000000..cb3b3e95556 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty2.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/unusedParameterProperty2.ts(3,25): error TS6138: Property 'used' is declared but never used. + + +==== tests/cases/compiler/unusedParameterProperty2.ts (1 errors) ==== + + class A { + constructor(private used) { + ~~~~ +!!! error TS6138: Property 'used' is declared but never used. + let foge = used; + foge += ""; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/unusedParameterProperty2.js b/tests/baselines/reference/unusedParameterProperty2.js new file mode 100644 index 00000000000..2bb04fde088 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty2.js @@ -0,0 +1,19 @@ +//// [unusedParameterProperty2.ts] + +class A { + constructor(private used) { + let foge = used; + foge += ""; + } +} + + +//// [unusedParameterProperty2.js] +var A = (function () { + function A(used) { + this.used = used; + var foge = used; + foge += ""; + } + return A; +}()); diff --git a/tests/cases/compiler/APISample_watcher.ts b/tests/cases/compiler/APISample_watcher.ts index 34baa04c850..07922bd35c7 100644 --- a/tests/cases/compiler/APISample_watcher.ts +++ b/tests/cases/compiler/APISample_watcher.ts @@ -23,7 +23,7 @@ declare var path: any; import * as ts from "typescript"; function watch(rootFileNames: string[], options: ts.CompilerOptions) { - const files: ts.Map<{ version: number }> = {}; + const files: ts.MapLike<{ version: number }> = {}; // initialize the list of files rootFileNames.forEach(fileName => { diff --git a/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts b/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts new file mode 100644 index 00000000000..d621cf460bc --- /dev/null +++ b/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts @@ -0,0 +1,18 @@ +class A { a } +class B extends A { b } +class C extends Array { c } +declare var ara: A[]; +declare var arb: B[]; +declare var cra: C
; +declare var crb: C; +declare var rra: ReadonlyArray; +declare var rrb: ReadonlyArray; +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' + +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' diff --git a/tests/cases/compiler/discriminantsAndPrimitives.ts b/tests/cases/compiler/discriminantsAndPrimitives.ts new file mode 100644 index 00000000000..6352d741808 --- /dev/null +++ b/tests/cases/compiler/discriminantsAndPrimitives.ts @@ -0,0 +1,49 @@ +// @strictNullChecks: true + +// Repro from #10257 plus other tests + +interface Foo { + kind: "foo"; + name: string; +} + +interface Bar { + kind: "bar"; + length: string; +} + +function f1(x: Foo | Bar | string) { + if (typeof x !== 'string') { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f2(x: Foo | Bar | string | undefined) { + if (typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f3(x: Foo | Bar | string | null) { + if (x && typeof x !== "string") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f4(x: Foo | Bar | string | number | null) { + if (x && typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} \ No newline at end of file diff --git a/tests/cases/compiler/implicitConstParameters.ts b/tests/cases/compiler/implicitConstParameters.ts new file mode 100644 index 00000000000..97996789124 --- /dev/null +++ b/tests/cases/compiler/implicitConstParameters.ts @@ -0,0 +1,57 @@ +// @strictNullChecks: true + +function doSomething(cb: () => void) { + cb(); +} + +function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } +} + +function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); +} + +function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } +} + +function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } +} + +function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + } +} + +function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + } + x = "abc"; // causes x to be considered non-const +} + + +function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } +} \ No newline at end of file diff --git a/tests/cases/compiler/indexWithUndefinedAndNull.ts b/tests/cases/compiler/indexWithUndefinedAndNull.ts new file mode 100644 index 00000000000..2aeb2ee0b1d --- /dev/null +++ b/tests/cases/compiler/indexWithUndefinedAndNull.ts @@ -0,0 +1,13 @@ +// @strictNullChecks: false +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; diff --git a/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts b/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts new file mode 100644 index 00000000000..f8fe0a323c6 --- /dev/null +++ b/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts @@ -0,0 +1,13 @@ +// @strictNullChecks: true +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; diff --git a/tests/cases/compiler/unusedParameterProperty1.ts b/tests/cases/compiler/unusedParameterProperty1.ts new file mode 100644 index 00000000000..61c4374c60a --- /dev/null +++ b/tests/cases/compiler/unusedParameterProperty1.ts @@ -0,0 +1,9 @@ +//@noUnusedLocals:true +//@noUnusedParameters:true + +class A { + constructor(private used: string) { + let foge = used; + foge += ""; + } +} diff --git a/tests/cases/compiler/unusedParameterProperty2.ts b/tests/cases/compiler/unusedParameterProperty2.ts new file mode 100644 index 00000000000..b9e05fbc967 --- /dev/null +++ b/tests/cases/compiler/unusedParameterProperty2.ts @@ -0,0 +1,9 @@ +//@noUnusedLocals:true +//@noUnusedParameters:true + +class A { + constructor(private used) { + let foge = used; + foge += ""; + } +} diff --git a/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts b/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts deleted file mode 100644 index bf7de709ef2..00000000000 --- a/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @noImplicitAny: true -declare module "jquery"; diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts index b81dd26652b..0817954c35c 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts @@ -13,7 +13,7 @@ if (obj1 instanceof A) { // narrowed to A. } var obj2: any; -if (obj2 instanceof A) { // can't narrow type from 'any' +if (obj2 instanceof A) { obj2.foo; obj2.bar; } @@ -35,7 +35,7 @@ if (obj3 instanceof B) { // narrowed to B. } var obj4: any; -if (obj4 instanceof B) { // can't narrow type from 'any' +if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; @@ -67,7 +67,7 @@ if (obj5 instanceof C) { // narrowed to C1|C2. } var obj6: any; -if (obj6 instanceof C) { // can't narrow type from 'any' +if (obj6 instanceof C) { obj6.foo; obj6.bar1; obj6.bar2; @@ -86,7 +86,7 @@ if (obj7 instanceof D) { // narrowed to D. } var obj8: any; -if (obj8 instanceof D) { // can't narrow type from 'any' +if (obj8 instanceof D) { obj8.foo; obj8.bar; } @@ -113,7 +113,7 @@ if (obj9 instanceof E) { // narrowed to E1 | E2 } var obj10: any; -if (obj10 instanceof E) { // can't narrow type from 'any' +if (obj10 instanceof E) { obj10.foo; obj10.bar1; obj10.bar2; @@ -136,7 +136,7 @@ if (obj11 instanceof F) { // can't type narrowing, construct signature returns a } var obj12: any; -if (obj12 instanceof F) { // can't narrow type from 'any' +if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -161,7 +161,7 @@ if (obj13 instanceof G) { // narrowed to G1. G1 is return type of prototype prop } var obj14: any; -if (obj14 instanceof G) { // can't narrow type from 'any' +if (obj14 instanceof G) { obj14.foo1; obj14.foo2; } @@ -183,7 +183,19 @@ if (obj15 instanceof H) { // narrowed to H. } var obj16: any; -if (obj16 instanceof H) { // can't narrow type from 'any' +if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } + +var obj17: any; +if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; +} + +var obj18: any; +if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; +} diff --git a/tests/cases/conformance/jsdoc/jsdocLiteral.ts b/tests/cases/conformance/jsdoc/jsdocLiteral.ts new file mode 100644 index 00000000000..bd0d2d562f9 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocLiteral.ts @@ -0,0 +1,13 @@ +// @allowJs: true +// @filename: in.js +// @out: out.js +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; +} diff --git a/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx b/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx new file mode 100644 index 00000000000..1e4418e7fba --- /dev/null +++ b/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx @@ -0,0 +1,32 @@ +//@jsx: preserve +//@module: amd + +//@filename: react.d.ts +declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } +} + +//@filename: file.tsx + +interface IProps { + primaryText: string, + [propName: string]: string | number +} + +function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+} + +function VerticalNav() { + return ( +
+ // error + // ok + // error +
+ ) +} \ No newline at end of file diff --git a/tests/cases/conformance/salsa/multipleDeclarations.ts b/tests/cases/conformance/salsa/multipleDeclarations.ts index 6899be2ec89..ba2e14e11e9 100644 --- a/tests/cases/conformance/salsa/multipleDeclarations.ts +++ b/tests/cases/conformance/salsa/multipleDeclarations.ts @@ -1,10 +1,37 @@ // @filename: input.js // @out: output.js // @allowJs: true - function C() { this.m = null; } C.prototype.m = function() { this.nothing(); -}; +} +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; +class Y { + mistake() { + } + m() { + } + constructor() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } +} +Y.prototype.mistake = true; +let y = new Y(); +y.m(); +y.mistake(); diff --git a/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts b/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts new file mode 100644 index 00000000000..dfa60a415f9 --- /dev/null +++ b/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts @@ -0,0 +1,23 @@ +declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + +function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + + else { + throw err; + } + } +} diff --git a/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts b/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts new file mode 100644 index 00000000000..4fbfc46060a --- /dev/null +++ b/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts @@ -0,0 +1,23 @@ +declare var x: any; + +if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; +} + +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts b/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts new file mode 100644 index 00000000000..473bd349b5f --- /dev/null +++ b/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts @@ -0,0 +1,34 @@ +declare var x: any; +declare function isFunction(x): x is Function; +declare function isObject(x): x is Object; +declare function isAnything(x): x is {}; +declare function isError(x): x is Error; +declare function isDate(x): x is Date; + + +if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + x(); +} + +if (isError(x)) { + x.message; + x.mesage; +} + +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/cases/conformance/typings/typingsLookup2.ts b/tests/cases/conformance/typings/typingsLookup2.ts new file mode 100644 index 00000000000..90e1e463f0a --- /dev/null +++ b/tests/cases/conformance/typings/typingsLookup2.ts @@ -0,0 +1,13 @@ +// @traceResolution: true +// @noImplicitReferences: true +// @currentDirectory: / +// This tests that an @types package with `"typings": null` is not automatically included. +// (If it were, this test would break because there are no typings to be found.) + +// @filename: /tsconfig.json +{} + +// @filename: /node_modules/@types/angular2/package.json +{ "typings": null } + +// @filename: /a.ts diff --git a/tests/cases/conformance/typings/typingsLookup3.ts b/tests/cases/conformance/typings/typingsLookup3.ts new file mode 100644 index 00000000000..a0bb15476b9 --- /dev/null +++ b/tests/cases/conformance/typings/typingsLookup3.ts @@ -0,0 +1,14 @@ +// @traceResolution: true +// @noImplicitReferences: true +// @currentDirectory: / +// This tests that `types` references are automatically lowercased. + +// @filename: /tsconfig.json +{ "files": "a.ts" } + +// @filename: /node_modules/@types/jquery/index.d.ts +declare var $: { x: any }; + +// @filename: /a.ts +/// +$.x; diff --git a/tests/cases/fourslash/completionForStringLiteral4.ts b/tests/cases/fourslash/completionForStringLiteral4.ts new file mode 100644 index 00000000000..11ae699eab8 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral4.ts @@ -0,0 +1,23 @@ +/// +// @allowJs: true +// @Filename: in.js +/////** I am documentation +//// * @param {'literal'} p1 +//// * @param {"literal"} p2 +//// * @param {'other1' | 'other2'} p3 +//// * @param {'literal' | number} p4 +//// * @param {12 | true} p5 +//// */ +////function f(p1, p2, p3, p4, p5) { +//// return p1 + p2 + p3 + p4 + p5 + '.'; +////} +////f/*1*/('literal', 'literal', "o/*2*/ther1", 12); + +goTo.marker('1'); +verify.quickInfoExists(); +verify.quickInfoIs('function f(p1: "literal", p2: "literal", p3: "other1" | "other2", p4: number | "literal", p5: true | 12): string', 'I am documentation'); + +goTo.marker('2'); +verify.completionListContains("other1"); +verify.completionListContains("other2"); +verify.memberListCount(2); diff --git a/tests/cases/fourslash/javaScriptClass1.ts b/tests/cases/fourslash/javaScriptClass1.ts new file mode 100644 index 00000000000..19f7a39b615 --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass1.ts @@ -0,0 +1,31 @@ +/// + +// Classes have their shape inferred from assignments +// to properties of 'this' in the constructor + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.bar = 'world'; +//// this.thing = () => 0; +//// this.union = 'foo'; +//// this.union = 100; +//// } +//// } +//// var x = new Foo(); +//// x/**/ + + +goTo.marker(); +edit.insert('.'); +verify.completionListContains("bar", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("thing", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("union", /*displayText*/ undefined, /*documentation*/ undefined, "property"); + +edit.insert('bar.'); +verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); +edit.backspace('bar.'.length); + +edit.insert('union.'); +verify.completionListContains("toString", /*displayText*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/javaScriptClass2.ts b/tests/cases/fourslash/javaScriptClass2.ts new file mode 100644 index 00000000000..d6daa320c5a --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass2.ts @@ -0,0 +1,22 @@ +/// + +// In an inferred class, we can rename successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.[|union|] = 'foo'; +//// this./*1*/[|union|] = 100; +//// } +//// method() { return this./*2*/[|union|]; } +//// } +//// var x = new Foo(); +//// x./*3*/[|union|]; + +goTo.marker('1'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('2'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('3'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); diff --git a/tests/cases/fourslash/javaScriptClass3.ts b/tests/cases/fourslash/javaScriptClass3.ts new file mode 100644 index 00000000000..47004d53b04 --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass3.ts @@ -0,0 +1,24 @@ +/// + +// In an inferred class, we can to-to-def successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// /*dst1*/this.alpha = 10; +//// /*dst2*/this.beta = 'gamma'; +//// } +//// method() { return this.alpha; } +//// } +//// var x = new Foo(); +//// x.alpha/*src1*/; +//// x.beta/*src2*/; + +goTo.marker('src1'); +goTo.definition(); +verify.caretAtMarker('dst1'); + +goTo.marker('src2'); +goTo.definition(); +verify.caretAtMarker('dst2'); diff --git a/tests/cases/fourslash/javaScriptClass4.ts b/tests/cases/fourslash/javaScriptClass4.ts new file mode 100644 index 00000000000..1148d5f320e --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass4.ts @@ -0,0 +1,22 @@ +/// + +// Classes have their shape inferred from assignments +// to properties of 'this' in the constructor + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// /** +//// * @type {string} +//// */ +//// this.baz = null; +//// } +//// } +//// var x = new Foo(); +//// x/**/ + +goTo.marker(); +edit.insert('.baz.'); +verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); + diff --git a/tests/cases/fourslash/localGetReferences.ts b/tests/cases/fourslash/localGetReferences.ts index ada4947acde..b05346a366a 100644 --- a/tests/cases/fourslash/localGetReferences.ts +++ b/tests/cases/fourslash/localGetReferences.ts @@ -205,12 +205,13 @@ const rangesByText = test.rangesByText(); for (const text in rangesByText) { const ranges = rangesByText[text]; if (text === "globalVar") { - function isShadow(r) { - return r.marker && r.marker.data && r.marker.data.shadow; - } verify.rangesReferenceEachOther(ranges.filter(isShadow)); verify.rangesReferenceEachOther(ranges.filter(r => !isShadow(r))); } else { verify.rangesReferenceEachOther(ranges); } } + +function isShadow(r) { + return r.marker && r.marker.data && r.marker.data.shadow; +} diff --git a/tests/webTestServer.ts b/tests/webTestServer.ts index ac5f046c1dd..4d2cb7d4672 100644 --- a/tests/webTestServer.ts +++ b/tests/webTestServer.ts @@ -117,19 +117,21 @@ function handleResolutionRequest(filePath: string, res: http.ServerResponse) { let resolvedPath = path.resolve(filePath, ""); resolvedPath = resolvedPath.substring(resolvedPath.indexOf("tests")); resolvedPath = switchToForwardSlashes(resolvedPath); - send("success", res, resolvedPath); - return; + send(ResponseCode.Success, res, resolvedPath); } -function send(result: "fail", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: "success", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: "unknown", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: string, res: http.ServerResponse, contents: string, contentType?: string): void -function send(result: string, res: http.ServerResponse, contents: string, contentType = "binary"): void { - const responseCode = result === "success" ? 200 : result === "fail" ? 500 : result === "unknown" ? 404 : parseInt(result); +const enum ResponseCode { + Success = 200, + BadRequest = 400, + NotFound = 404, + MethodNotAllowed = 405, + PayloadTooLarge = 413, + Fail = 500 +} + +function send(responseCode: number, res: http.ServerResponse, contents: string, contentType = "binary"): void { res.writeHead(responseCode, { "Content-Type": contentType }); res.end(contents); - return; } // Reads the data from a post request and passes it to the given callback @@ -142,7 +144,7 @@ function processPost(req: http.ServerRequest, res: http.ServerResponse, callback queryData += data; if (queryData.length > 1e8) { queryData = ""; - send("413", res, undefined); + send(ResponseCode.PayloadTooLarge, res, undefined); console.log("ERROR: destroying connection"); req.connection.destroy(); } @@ -155,7 +157,7 @@ function processPost(req: http.ServerRequest, res: http.ServerResponse, callback } else { - send("405", res, undefined); + send(ResponseCode.MethodNotAllowed, res, undefined); } } @@ -201,16 +203,16 @@ function handleRequestOperation(req: http.ServerRequest, res: http.ServerRespons switch (operation) { case RequestType.GetDir: const filesInFolder = dir(reqPath, "", { recursive: true }); - send("success", res, filesInFolder.join(",")); + send(ResponseCode.Success, res, filesInFolder.join(",")); break; case RequestType.GetFile: fs.readFile(reqPath, (err, file) => { const contentType = contentTypeForExtension(path.extname(reqPath)); if (err) { - send("fail", res, err.message, contentType); + send(ResponseCode.NotFound, res, err.message, contentType); } else { - send("success", res, file, contentType); + send(ResponseCode.Success, res, file, contentType); } }); break; @@ -222,33 +224,33 @@ function handleRequestOperation(req: http.ServerRequest, res: http.ServerRespons processPost(req, res, (data) => { writeFile(reqPath, data, { recursive: true }); }); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.WriteDir: fs.mkdirSync(reqPath); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.DeleteFile: if (fs.existsSync(reqPath)) { fs.unlinkSync(reqPath); } - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.DeleteDir: if (fs.existsSync(reqPath)) { fs.rmdirSync(reqPath); } - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.AppendFile: processPost(req, res, (data) => { fs.appendFileSync(reqPath, data); }); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.Unknown: default: - send("unknown", res, undefined); + send(ResponseCode.BadRequest, res, undefined); break; } diff --git a/tslint.json b/tslint.json index a46e58f8c29..f058eab3dd6 100644 --- a/tslint.json +++ b/tslint.json @@ -32,7 +32,7 @@ "property-declaration": "nospace", "variable-declaration": "nospace" }], - "next-line": [true, + "next-line": [true, "check-catch", "check-else" ], @@ -44,7 +44,6 @@ "boolean-trivia": true, "type-operator-spacing": true, "prefer-const": true, - "no-in-operator": true, "no-increment-decrement": true, "object-literal-surrounding-space": true, "no-type-assertion-whitespace": true