diff --git a/.gitignore b/.gitignore index e1492c2cd73e8..ab1893af903c4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,12 +7,14 @@ tests/cases/perf/* test-args.txt ~*.docx tests/baselines/local/* +tests/baselines/local.old/* tests/services/baselines/local/* tests/baselines/prototyping/local/* tests/baselines/rwc/* tests/baselines/test262/* tests/baselines/reference/projectOutput/* tests/baselines/local/projectOutput/* +tests/baselines/reference/testresults.tap tests/services/baselines/prototyping/local/* tests/services/browser/typescriptServices.js scripts/configureNightly.js diff --git a/Jakefile.js b/Jakefile.js index 1d017d5100fd2..bc6144d216661 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -5,6 +5,7 @@ var os = require("os"); var path = require("path"); var child_process = require("child_process"); var Linter = require("tslint"); +var readline = require("readline"); // Variables var compilerDirectory = "src/compiler/"; @@ -40,7 +41,20 @@ var compilerSources = [ "utilities.ts", "binder.ts", "checker.ts", + "factory.ts", + "visitor.ts", + "transformers/destructuring.ts", + "transformers/ts.ts", + "transformers/module/es6.ts", + "transformers/module/system.ts", + "transformers/module/module.ts", + "transformers/jsx.ts", + "transformers/es7.ts", + "transformers/es6.ts", + "transformer.ts", "sourcemap.ts", + "comments.ts", + "printer.ts", "declarationEmitter.ts", "emitter.ts", "program.ts", @@ -60,7 +74,20 @@ var servicesSources = [ "utilities.ts", "binder.ts", "checker.ts", + "factory.ts", + "visitor.ts", + "transformers/destructuring.ts", + "transformers/ts.ts", + "transformers/module/es6.ts", + "transformers/module/system.ts", + "transformers/module/module.ts", + "transformers/jsx.ts", + "transformers/es7.ts", + "transformers/es6.ts", + "transformer.ts", "sourcemap.ts", + "comments.ts", + "printer.ts", "declarationEmitter.ts", "emitter.ts", "program.ts", @@ -160,11 +187,22 @@ var harnessSources = harnessCoreSources.concat([ "protocol.d.ts", "session.ts", "client.ts", - "editorServices.ts", + "editorServices.ts" ].map(function (f) { return path.join(serverDirectory, f); })); +var librarySourceMap = [ + { target: "lib.core.d.ts", sources: ["header.d.ts", "core.d.ts"] }, + { target: "lib.dom.d.ts", sources: ["importcore.d.ts", "intl.d.ts", "dom.generated.d.ts"] }, + { target: "lib.webworker.d.ts", sources: ["importcore.d.ts", "intl.d.ts", "webworker.generated.d.ts"] }, + { target: "lib.scriptHost.d.ts", sources: ["importcore.d.ts", "scriptHost.d.ts"] }, + { target: "lib.d.ts", sources: ["header.d.ts", "core.d.ts", "intl.d.ts", "dom.generated.d.ts", "webworker.importscripts.d.ts", "scriptHost.d.ts"] }, + { target: "lib.core.es6.d.ts", sources: ["header.d.ts", "core.d.ts", "es6.d.ts"] }, + { target: "lib.es6.d.ts", sources: ["header.d.ts", "es6.d.ts", "core.d.ts", "intl.d.ts", "dom.generated.d.ts", "dom.es6.d.ts", "webworker.importscripts.d.ts", "scriptHost.d.ts"] }, + { target: "lib.core.es7.d.ts", sources: ["header.d.ts", "core.d.ts", "es6.d.ts", "es7.d.ts"] }, + { target: "lib.es7.d.ts", sources: ["header.d.ts", "es6.d.ts", "es7.d.ts", "core.d.ts", "intl.d.ts", "dom.generated.d.ts", "dom.es6.d.ts", "webworker.importscripts.d.ts", "scriptHost.d.ts"] } +]; var es2015LibrarySources = [ "es2015.core.d.ts", "es2015.collection.d.ts", @@ -293,7 +331,7 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts options += " --out " + outFile; } else { - options += " --module commonjs" + options += " --module commonjs"; } if(opts.noResolve) { @@ -308,7 +346,11 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts } if (opts.stripInternal) { - options += " --stripInternal" + options += " --stripInternal"; + } + + if (useBuiltCompiler && !environmentVariableIsDisabled("USE_TRANSFORMS")) { + console.warn("\u001b[93mwarning: 'USE_TRANSFORMS' environment variable is not set to 'false'. Experimental transforms will be enabled by default.\u001b[0m"); } var cmd = host + " " + compilerPath + " " + options + " "; @@ -446,9 +488,9 @@ file(scriptsTsdJson); task("tsd-scripts", [scriptsTsdJson], function () { var cmd = "tsd --config " + scriptsTsdJson + " install"; - console.log(cmd) + console.log(cmd); exec(cmd); -}, { async: true }) +}, { async: true }); var importDefinitelyTypedTestsDirectory = path.join(scriptsDirectory, "importDefinitelyTypedTests"); var importDefinitelyTypedTestsJs = path.join(importDefinitelyTypedTestsDirectory, "importDefinitelyTypedTests.js"); @@ -513,7 +555,7 @@ compileFile(servicesFileInBrowserTest, servicesSources,[builtLocalDirectory, cop var i = content.lastIndexOf("\n"); fs.writeFileSync(servicesFileInBrowserTest, content.substring(0, i) + "\r\n//# sourceURL=../built/local/typeScriptServices.js" + content.substring(i)); }); - + var serverFile = path.join(builtLocalDirectory, "tsserver.js"); compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true); @@ -615,7 +657,7 @@ directory(builtLocalDirectory); var run = path.join(builtLocalDirectory, "run.js"); compileFile(run, harnessSources, [builtLocalDirectory, tscFile].concat(libraryTargets).concat(harnessSources), [], /*useBuiltCompiler:*/ true); -var internalTests = "internal/" +var internalTests = "internal/"; var localBaseline = "tests/baselines/local/"; var refBaseline = "tests/baselines/reference/"; @@ -672,10 +714,25 @@ function cleanTestDirs() { } // used to pass data from jake command line directly to run.js -function writeTestConfigFile(tests, light, testConfigFile) { - console.log('Running test(s): ' + tests); - var testConfigContents = JSON.stringify({ test: [tests], light: light }); - fs.writeFileSync('test.config', testConfigContents); +function writeTestConfigFile(testConfigFile, tests, light, stackTraceLimit) { + var testConfig; + if (tests) { + console.log('Running test(s): ' + tests); + (testConfig || (testConfig = {})).tests = [tests]; + } + + if (light) { + (testConfig || (testConfig = {})).light = light; + } + + if (/^(\d+|full)$/.test(stackTraceLimit)) { + (testConfig || (testConfig = {})).stackTraceLimit = stackTraceLimit; + } + + if (testConfig) { + var testConfigContents = JSON.stringify(testConfig); + fs.writeFileSync(testConfigFile, testConfigContents); + } } function deleteTemporaryProjectOutput() { @@ -684,25 +741,154 @@ function deleteTemporaryProjectOutput() { } } -function runConsoleTests(defaultReporter, defaultSubsets) { +function runTestsAndWriteOutput(file) { cleanTestDirs(); + var tests = process.env.test || process.env.tests || process.env.t; + var light = process.env.light || false; + var testConfigFile = 'test.config'; + if (fs.existsSync(testConfigFile)) { + fs.unlinkSync(testConfigFile); + } + + writeTestConfigFile(testConfigFile, tests, light, 10); + + if (tests && tests.toLocaleLowerCase() === "rwc") { + testTimeout = 100000; + } + + var args = []; + args.push("-R", "tap"); + args.push("--no-colors"); + args.push("-t", testTimeout); + if (tests) { + args.push("-g", '"' + tests + '"'); + } + args.push(run); + + var cmd = "mocha " + args.join(" "); + console.log(cmd); + var p = child_process.spawn( + process.platform === "win32" ? "cmd" : "/bin/sh", + process.platform === "win32" ? ["/c", cmd] : ["-c", cmd], { + windowsVerbatimArguments: true + }); + + var out = fs.createWriteStream(file); + var tapRange = /^(\d+)\.\.(\d+)(?:$|\r\n?|\n)/; + var tapOk = /^ok\s/; + var tapNotOk = /^not\sok\s/; + var tapComment = /^#/; + var typeError = /^\s+TypeError:/; + var debugError = /^\s+Error:\sDebug\sFailure\./; + var progress = new ProgressBar("Running tests..."); + var expectedTestCount = 0; + var testCount = 0; + var failureCount = 0; + var successCount = 0; + var comments = []; + var typeErrorCount = 0; + var debugErrorCount = 0; + + var rl = readline.createInterface({ + input: p.stdout, + terminal: false + }); + + function updateProgress(percentComplete) { + progress.update(percentComplete, + /*foregroundColor*/ failureCount > 0 + ? "red" + : successCount === expectedTestCount + ? "green" + : "cyan", + /*backgroundColor*/ "gray" + ); + } + + rl.on("line", function (line) { + var m = tapRange.exec(line); + if (m) { + expectedTestCount = parseInt(m[2]); + return; + } + + if (tapOk.test(line)) { + out.write(line.replace(/^ok\s+\d+\s+/, "ok ") + os.EOL); + successCount++; + } + else if (tapNotOk.test(line)) { + out.write(line.replace(/^not\s+ok\s+\d+\s+/, "not ok ") + os.EOL); + failureCount++; + } + else { + out.write(line + os.EOL); + if (tapComment.test(line)) { + comments.push(line); + } + else if (typeError.test(line)) { + typeErrorCount++; + } + else if (debugError.test(line)) { + debugErrorCount++; + } + return; + } + + testCount++; + + var percentComplete = testCount * 100 / expectedTestCount; + updateProgress(percentComplete); + }); + + p.on("exit", function (status) { + if (progress.visible) { + updateProgress(100); + process.stdout.write("done." + os.EOL); + } + + console.log(comments.join(os.EOL)); + + if (typeErrorCount) { + console.log("# type errors: %s", typeErrorCount); + } + + if (debugErrorCount) { + console.log("# debug errors: %s", debugErrorCount); + } + + deleteTemporaryProjectOutput(); + if (status) { + fail("Process exited with code " + status); + } + else { + complete(); + } + }); +} + +function runConsoleTests(defaultReporter, defaultSubsets, dirty) { + if (!dirty) { + cleanTestDirs(); + } + var debug = process.env.debug || process.env.d; tests = process.env.test || process.env.tests || process.env.t; var light = process.env.light || false; + var stackTraceLimit = process.env.stackTraceLimit || 1; var testConfigFile = 'test.config'; - if(fs.existsSync(testConfigFile)) { + if (fs.existsSync(testConfigFile)) { fs.unlinkSync(testConfigFile); } if (tests || light) { - writeTestConfigFile(tests, light, testConfigFile); + writeTestConfigFile(testConfigFile, tests, light, stackTraceLimit); } if (tests && tests.toLocaleLowerCase() === "rwc") { testTimeout = 100000; } - colors = process.env.colors || process.env.color + colors = process.env.colors || process.env.color; colors = colors ? ' --no-colors ' : ' --colors '; reporter = process.env.reporter || process.env.r || defaultReporter; @@ -710,7 +896,7 @@ function runConsoleTests(defaultReporter, defaultSubsets) { // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer var subsetRegexes; if(defaultSubsets.length === 0) { - subsetRegexes = [tests] + subsetRegexes = [tests]; } else { var subsets = tests ? tests.split("|") : defaultSubsets; @@ -723,7 +909,7 @@ function runConsoleTests(defaultReporter, defaultSubsets) { console.log(cmd); exec(cmd, function () { deleteTemporaryProjectOutput(); - if (i === 0) { + if (i === 0 && !dirty) { var lint = jake.Task['lint']; lint.addListener('complete', function () { complete(); @@ -748,6 +934,13 @@ task("runtests", ["build-rules", "tests", builtLocalDirectory], function() { runConsoleTests('mocha-fivemat-progress-reporter', []); }, {async: true}); +task("runtests-file", ["build-rules", "tests", builtLocalDirectory], function () { + runTestsAndWriteOutput("tests/baselines/local/testresults.tap"); +}, { async: true }); +task("runtests-dirty", ["build-rules", "tests", builtLocalDirectory], function () { + runConsoleTests("mocha-fivemat-progress-reporter", [], /*dirty*/ true); +}, { async: true }); + desc("Generates code coverage data via instanbul"); task("generate-code-coverage", ["tests", builtLocalDirectory], function () { var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run; @@ -756,20 +949,20 @@ task("generate-code-coverage", ["tests", builtLocalDirectory], function () { }, { async: true }); // Browser tests -var nodeServerOutFile = 'tests/webTestServer.js' -var nodeServerInFile = 'tests/webTestServer.ts' +var nodeServerOutFile = 'tests/webTestServer.js'; +var nodeServerInFile = 'tests/webTestServer.ts'; compileFile(nodeServerOutFile, [nodeServerInFile], [builtLocalDirectory, tscFile], [], /*useBuiltCompiler:*/ true, { noOutFile: true }); desc("Runs browserify on run.js to produce a file suitable for running tests in the browser"); task("browserify", ["tests", builtLocalDirectory, nodeServerOutFile], function() { - var cmd = 'browserify built/local/run.js -o built/local/bundle.js'; + var cmd = 'browserify built/local/run.js -t ./scripts/browserify-optional -o built/local/bundle.js'; exec(cmd); }, {async: true}); desc("Runs the tests using the built run.js file like 'jake runtests'. Syntax is jake runtests-browser. Additional optional parameters tests=[regex], port=, browser=[chrome|IE]"); task("runtests-browser", ["tests", "browserify", builtLocalDirectory, servicesFileInBrowserTest], function() { cleanTestDirs(); - host = "node" + host = "node"; port = process.env.port || process.env.p || '8888'; browser = process.env.browser || process.env.b || "IE"; tests = process.env.test || process.env.tests || process.env.t; @@ -779,17 +972,17 @@ task("runtests-browser", ["tests", "browserify", builtLocalDirectory, servicesFi fs.unlinkSync(testConfigFile); } if(tests || light) { - writeTestConfigFile(tests, light, testConfigFile); + writeTestConfigFile(testConfigFile, tests, light); } tests = tests ? tests : ''; - var cmd = host + " tests/webTestServer.js " + port + " " + browser + " " + tests + var cmd = host + " tests/webTestServer.js " + port + " " + browser + " " + tests; console.log(cmd); exec(cmd); }, {async: true}); function getDiffTool() { - var program = process.env['DIFF'] + var program = process.env['DIFF']; if (!program) { fail("Add the 'DIFF' environment variable to the path of the program you want to use."); } @@ -818,11 +1011,11 @@ 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") { + if (!hardOrSoft || hardOrSoft === "hard") { jake.rmRf(refBaseline); fs.renameSync(localBaseline, refBaseline); } - else if (hardOrSoft == "soft") { + else if (hardOrSoft === "soft") { var files = jake.readdirR(localBaseline); for (var i in files) { jake.cpR(files[i], refBaseline); @@ -901,14 +1094,14 @@ task("update-sublime", ["local", serverFile], function() { }); var tslintRuleDir = "scripts/tslint"; -var tslintRules = ([ +var tslintRules = [ "nextLineRule", "preferConstRule", "booleanTriviaRule", "typeOperatorSpacingRule", "noInOperatorRule", "noIncrementDecrementRule" -]); +]; var tslintRulesFiles = tslintRules.map(function(p) { return path.join(tslintRuleDir, p + ".ts"); }); @@ -933,7 +1126,7 @@ function getLinterOptions() { function lintFileContents(options, path, contents) { var ll = new Linter(path, contents, options); - console.log("Linting '" + path + "'.") + console.log("Linting '" + path + "'."); return ll.lint(); } @@ -1029,3 +1222,93 @@ task("lint-server", ["build-rules"], function() { lintWatchFile(lintTargets[i]); } }); + +function ProgressBar(title) { + this.title = title; +} +ProgressBar.prototype = { + progressChars: ["\u0020", "\u2591", "\u2592", "\u2593", "\u2588"], + colors: { + foreground: { + black: "\u001b[90m", + red: "\u001b[91m", + green: "\u001b[92m", + yellow: "\u001b[93m", + blue: "\u001b[94m", + magenta: "\u001b[95m", + cyan: "\u001b[96m", + white: "\u001b[97m", + gray: "\u001b[37m" + }, + background: { + black: "\u001b[40m", + red: "\u001b[41m", + green: "\u001b[42m", + yellow: "\u001b[43m", + blue: "\u001b[44m", + magenta: "\u001b[45m", + cyan: "\u001b[46m", + white: "\u001b[47m", + gray: "\u001b[100m" + }, + reset: "\u001b[0m" + }, + update: function (percentComplete, foregroundColor, backgroundColor) { + var progress = ""; + for (var i = 0; i < 100; i += 4) { + progress += this.progressChars[Math.floor(Math.max(0, Math.min(4, percentComplete - i)))]; + } + + foregroundColor = foregroundColor && this.colors.foreground[foregroundColor]; + backgroundColor = backgroundColor && this.colors.background[backgroundColor]; + if (foregroundColor || backgroundColor) { + if (foregroundColor) { + progress = foregroundColor + progress; + } + if (backgroundColor) { + progress = backgroundColor + progress; + } + + progress += this.colors.reset; + } + + if (this._lastProgress !== progress || !this.visible) { + this._print(progress); + } + }, + hide: function () { + if (this.visible) { + this._savedProgress = this._lastProgress; + this.clear(); + } + }, + show: function () { + if (this._savedProgress && !this.visible) { + this._print(this._savedProgress); + this._savedProgress = undefined; + } + }, + clear: function () { + if (this._lastProgress) { + readline.moveCursor(process.stdout, -process.stdout.columns, 0); + readline.clearLine(process.stdout, 1); + this._lastProgress = undefined; + this.visible = false; + } + }, + _print: function (progress) { + readline.moveCursor(process.stdout, -process.stdout.columns, 0); + process.stdout.write(this.title ? progress + " " + this.title : progress); + readline.clearLine(process.stdout, 1); + this._lastProgress = progress; + this.visible = true; + } +}; + +function environmentVariableIsEnabled(name) { + return /^(y(es)?|t(rue)?|on|enabled?|1|\+)$/.test(process.env[name]); +} + +function environmentVariableIsDisabled(name) { + return /^(no?|f(alse)?|off|disabled?|0|-)$/.test(process.env[name]); +} diff --git a/scripts/browserify-optional.js b/scripts/browserify-optional.js new file mode 100644 index 0000000000000..43997c7803cbe --- /dev/null +++ b/scripts/browserify-optional.js @@ -0,0 +1,24 @@ +// simple script to optionally elide source-map-support (or other optional modules) when running browserify. + +var stream = require("stream"), + Transform = stream.Transform, + resolve = require("browser-resolve"); + +var requirePattern = /require\s*\(\s*['"](source-map-support)['"]\s*\)/; +module.exports = function (file) { + return new Transform({ + transform: function (data, encoding, cb) { + var text = encoding === "buffer" ? data.toString("utf8") : data; + this.push(new Buffer(text.replace(requirePattern, function (originalText, moduleName) { + try { + resolve.sync(moduleName, { filename: file }); + return originalText; + } + catch (e) { + return "(function () { throw new Error(\"module '" + moduleName + "' not found.\"); })()"; + } + }), "utf8")); + cb(); + } + }); +}; \ No newline at end of file diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9311aa7cc2698..21c5d14db1db5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -37,7 +37,7 @@ namespace ts { return ModuleInstanceState.ConstEnumOnly; } // 3. non-exported import declarations - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && !(node.flags & NodeFlags.Export)) { + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && !(hasModifier(node, ModifierFlags.Export))) { return ModuleInstanceState.NonInstantiated; } // 4. other uninstantiated module declarations. @@ -134,12 +134,17 @@ namespace ts { let Symbol: { new (flags: SymbolFlags, name: string): Symbol }; let classifiableNames: Map; + // state used to aggregate transform flags during bind. + let subtreeTransformFlags: TransformFlags = TransformFlags.None; + let skipTransformFlagAggregation: boolean; + function bindSourceFile(f: SourceFile, opts: CompilerOptions) { file = f; options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; classifiableNames = {}; + skipTransformFlagAggregation = isDeclarationFile(file); Symbol = objectAllocator.getSymbolConstructor(); @@ -165,6 +170,7 @@ namespace ts { hasAsyncFunctions = false; hasDecorators = false; hasParameterDecorators = false; + subtreeTransformFlags = TransformFlags.None; hasJsxSpreadAttribute = false; } @@ -255,7 +261,7 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: - return node.flags & NodeFlags.Default ? "default" : undefined; + return hasModifier(node, ModifierFlags.Default) ? "default" : undefined; case SyntaxKind.JSDocFunctionType: return isJSDocConstructSignature(node) ? "__new" : "__call"; case SyntaxKind.Parameter: @@ -283,7 +289,7 @@ namespace ts { function declareSymbol(symbolTable: SymbolTable, parent: Symbol, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags): Symbol { Debug.assert(!hasDynamicName(node)); - const isDefaultExport = node.flags & NodeFlags.Default; + const isDefaultExport = hasModifier(node, ModifierFlags.Default); // The exported symbol for an export default function/class node is always named "default" const name = isDefaultExport && parent ? "default" : getDeclarationName(node); @@ -329,7 +335,7 @@ namespace ts { : Diagnostics.Duplicate_identifier_0; forEach(symbol.declarations, declaration => { - if (declaration.flags & NodeFlags.Default) { + if (hasModifier(declaration, ModifierFlags.Default)) { message = Diagnostics.A_module_cannot_have_multiple_default_exports; } }); @@ -353,7 +359,7 @@ namespace ts { } function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { - const hasExportModifier = getCombinedNodeFlags(node) & NodeFlags.Export; + const hasExportModifier = getCombinedModifierFlags(node) & ModifierFlags.Export; if (symbolFlags & SymbolFlags.Alias) { if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); @@ -870,7 +876,7 @@ namespace ts { } function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return node.flags & NodeFlags.Static + return hasModifier(node, ModifierFlags.Static) ? declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes) : declareSymbol(container.symbol.members, container.symbol, node, symbolFlags, symbolExcludes); } @@ -907,7 +913,7 @@ namespace ts { function bindModuleDeclaration(node: ModuleDeclaration) { setExportContextFlag(node); if (isAmbientModule(node)) { - if (node.flags & NodeFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); } if (isExternalModuleAugmentation(node)) { @@ -1226,7 +1232,20 @@ namespace ts { // symbols we do specialized work when we recurse. For example, we'll keep track of // the current 'container' node when it changes. This helps us know which symbol table // a local should go into for example. - bindChildren(node); + if (skipTransformFlagAggregation) { + bindChildren(node); + } + else if (node.transformFlags & TransformFlags.HasComputedFlags) { + skipTransformFlagAggregation = true; + bindChildren(node); + skipTransformFlagAggregation = false; + } + else { + const savedSubtreeTransformFlags = subtreeTransformFlags; + subtreeTransformFlags = 0; + bindChildren(node); + subtreeTransformFlags = savedSubtreeTransformFlags | computeTransformFlagsForNode(node, subtreeTransformFlags); + } inStrictMode = savedInStrictMode; } @@ -1783,7 +1802,9 @@ namespace ts { case Reachability.Unreachable: const reportError = // report error on all statements except empty ones - (isStatement(node) && node.kind !== SyntaxKind.EmptyStatement) || + (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || + // report error on ExportAssignment + node.kind === SyntaxKind.ExportAssignment || // report error on class declarations node.kind === SyntaxKind.ClassDeclaration || // report error on instantiated modules or const-enums only modules if preserveConstEnums is set @@ -1838,4 +1859,612 @@ namespace ts { implicitLabels = []; } } + + /** + * Computes the transform flags for a node, given the transform flags of its subtree + * + * @param node The node to analyze + * @param subtreeFlags Transform flags computed for this node's subtree + */ + export function computeTransformFlagsForNode(node: Node, subtreeFlags: TransformFlags): TransformFlags { + // Mark transformations needed for each node + let transformFlags = TransformFlags.None; + let excludeFlags = TransformFlags.None; + switch (node.kind) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.AwaitExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.ReadonlyKeyword: + // These nodes are TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript; + break; + + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxText: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxExpression: + // These nodes are Jsx syntax. + transformFlags = TransformFlags.AssertJsx; + break; + + case SyntaxKind.ExportKeyword: + // This node is both ES6 and TypeScript syntax. + transformFlags = TransformFlags.AssertES6 | TransformFlags.TypeScript; + break; + + case SyntaxKind.DefaultKeyword: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + case SyntaxKind.TemplateExpression: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.ForOfStatement: + case SyntaxKind.YieldExpression: + // These nodes are ES6 syntax. + transformFlags = TransformFlags.AssertES6; + break; + + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.TypePredicate: + case SyntaxKind.TypeReference: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ThisType: + case SyntaxKind.StringLiteralType: + // Types and signatures are TypeScript syntax, and exclude all other facts. + subtreeFlags = TransformFlags.None; + excludeFlags = TransformFlags.TypeExcludes; + transformFlags = TransformFlags.AssertTypeScript; + break; + + case SyntaxKind.ComputedPropertyName: + // Even though computed property names are ES6, we don't treat them as such. + // This is so that they can flow through PropertyName transforms unaffected. + // Instead, we mark the container as ES6, so that it can properly handle the transform. + transformFlags = TransformFlags.ContainsComputedPropertyName; + if (subtreeFlags & TransformFlags.ContainsLexicalThis) { + // A computed method name like `[this.getName()](x: string) { ... }` needs to + // distinguish itself from the normal case of a method body containing `this`: + // `this` inside a method doesn't need to be rewritten (the method provides `this`), + // whereas `this` inside a computed name *might* need to be rewritten if the class/object + // is inside an arrow function: + // `_this = this; () => class K { [_this.getName()]() { ... } }` + // To make this distinction, use ContainsLexicalThisInComputedPropertyName + // instead of ContainsLexicalThis for computed property names + transformFlags |= TransformFlags.ContainsLexicalThisInComputedPropertyName; + } + break; + + case SyntaxKind.SpreadElementExpression: + // This node is ES6 syntax, but is handled by a containing node. + transformFlags = TransformFlags.ContainsSpreadElementExpression; + break; + + case SyntaxKind.SuperKeyword: + // This node is ES6 syntax. + transformFlags = TransformFlags.AssertES6; + break; + + case SyntaxKind.ThisKeyword: + // Mark this node and its ancestors as containing a lexical `this` keyword. + transformFlags = TransformFlags.ContainsLexicalThis; + break; + + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + // These nodes are ES6 syntax. + transformFlags = TransformFlags.AssertES6; + break; + + case SyntaxKind.Decorator: + // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript | TransformFlags.ContainsDecorators; + break; + + case SyntaxKind.ObjectLiteralExpression: + excludeFlags = TransformFlags.ObjectLiteralExcludes; + if (subtreeFlags & TransformFlags.ContainsComputedPropertyName) { + // If an ObjectLiteralExpression contains a ComputedPropertyName, then it + // is an ES6 node. + transformFlags = TransformFlags.AssertES6; + } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } + break; + + case SyntaxKind.CallExpression: + return computeCallExpression(node, subtreeFlags); + + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.NewExpression: + excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; + if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression) { + // If the this node contains a SpreadElementExpression, then it is an ES6 + // node. + transformFlags = TransformFlags.AssertES6; + } + + break; + + case SyntaxKind.ModuleDeclaration: + // An ambient declaration is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Ambient)) { + subtreeFlags = TransformFlags.None; + } + + // This node is TypeScript syntax, and excludes markers that should not escape the module scope. + excludeFlags = TransformFlags.ModuleExcludes; + transformFlags = TransformFlags.AssertTypeScript; + break; + + case SyntaxKind.ParenthesizedExpression: + return computeParenthesizedExpression(node, subtreeFlags); + + case SyntaxKind.BinaryExpression: + return computeBinaryExpression(node, subtreeFlags); + + case SyntaxKind.ExpressionStatement: + // If the expression of an expression statement is a destructuring assignment, + // then we treat the statement as ES6 so that we can indicate that we do not + // need to hold on to the right-hand side. + if ((node).expression.transformFlags & TransformFlags.DestructuringAssignment) { + transformFlags = TransformFlags.AssertES6; + } + + break; + + case SyntaxKind.Parameter: + return computeParameter(node, subtreeFlags); + + case SyntaxKind.ArrowFunction: + return computeArrowFunction(node, subtreeFlags); + + case SyntaxKind.FunctionExpression: + return computeFunctionExpression(node, subtreeFlags); + + case SyntaxKind.FunctionDeclaration: + return computeFunctionDeclaration(node, subtreeFlags); + + case SyntaxKind.VariableDeclaration: + return computeVariableDeclaration(node, subtreeFlags); + + case SyntaxKind.VariableDeclarationList: + // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. + if (node.flags & NodeFlags.BlockScoped) { + transformFlags = TransformFlags.AssertES6 | TransformFlags.ContainsBlockScopedBinding; + } + + break; + + case SyntaxKind.VariableStatement: + return computeVariableStatement(node, subtreeFlags); + + case SyntaxKind.LabeledStatement: + return computeLabeledStatement(node, subtreeFlags); + + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + // A loop containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding) { + transformFlags = TransformFlags.AssertES6; + } + + break; + + case SyntaxKind.ClassDeclaration: + return computeClassDeclaration(node, subtreeFlags); + + case SyntaxKind.ClassExpression: + return computeClassExpression(node, subtreeFlags); + + case SyntaxKind.HeritageClause: + if ((node).token === SyntaxKind.ExtendsKeyword) { + // An `extends` HeritageClause is ES6 syntax. + transformFlags = TransformFlags.AssertES6; + } + else { + // An `implements` HeritageClause is TypeScript syntax. + Debug.assert((node).token === SyntaxKind.ImplementsKeyword); + transformFlags = TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.ExpressionWithTypeArguments: + // An ExpressionWithTypeArguments is ES6 syntax, as it is used in the + // extends clause of a class. + transformFlags = TransformFlags.AssertES6; + + // If an ExpressionWithTypeArguments contains type arguments, then it + // is TypeScript syntax. + if ((node).typeArguments) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.Constructor: + // A Constructor is ES6 syntax. + excludeFlags = TransformFlags.ConstructorExcludes; + transformFlags = TransformFlags.AssertES6; + + // An overload constructor is TypeScript syntax. + if (!(node).body) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.PropertyDeclaration: + // A PropertyDeclaration is TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript; + + // If the PropertyDeclaration has an initializer, we need to inform its ancestor + // so that it handle the transformation. + if ((node).initializer) { + transformFlags |= TransformFlags.ContainsPropertyInitializer; + } + + break; + + case SyntaxKind.MethodDeclaration: + // A MethodDeclaration is ES6 syntax. + excludeFlags = TransformFlags.MethodOrAccessorExcludes; + transformFlags = TransformFlags.AssertES6; + + // A MethodDeclaration is TypeScript syntax if it is either async, abstract, overloaded, + // generic, or has both a computed property name and a decorator. + if ((node).body === undefined + || (node).typeParameters !== undefined + || hasModifier(node, ModifierFlags.Async | ModifierFlags.Abstract) + || (subtreeFlags & TransformFlags.ContainsDecorators + && subtreeFlags & TransformFlags.ContainsComputedPropertyName)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // A GetAccessor or SetAccessor is ES5 syntax. + excludeFlags = TransformFlags.MethodOrAccessorExcludes; + + // A GetAccessor or SetAccessor is TypeScript syntax if it is either abstract, + // or has both a computed property name and a decorator. + if ((node).body === undefined + || hasModifier(node, ModifierFlags.Abstract) + || (subtreeFlags & TransformFlags.ContainsDecorators + && subtreeFlags & TransformFlags.ContainsComputedPropertyName)) { + transformFlags = TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.ImportEqualsDeclaration: + // An ImportEqualsDeclaration with a namespace reference is TypeScript. + if (!isExternalModuleImportEqualsDeclaration(node)) { + transformFlags = TransformFlags.AssertTypeScript; + } + + break; + + case SyntaxKind.PropertyAccessExpression: + // If a PropertyAccessExpression starts with a super keyword, then it is + // ES6 syntax, and requires a lexical `this` binding. + if ((node).expression.kind === SyntaxKind.SuperKeyword) { + transformFlags = TransformFlags.ContainsLexicalThis; + } + + break; + + case SyntaxKind.SourceFile: + if (subtreeFlags & TransformFlags.ContainsCapturedLexicalThis) { + transformFlags = TransformFlags.AssertES6; + } + + break; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, excludeFlags); + } + + function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression + || isSuperCall(node) + || isSuperPropertyCall(node)) { + // If the this node contains a SpreadElementExpression, or is a super call, then it is an ES6 + // node. + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ArrayLiteralOrCallOrNewExcludes); + } + + function computeBinaryExpression(node: BinaryExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + if (isDestructuringAssignment(node)) { + // Destructuring assignments are ES6 syntax. + transformFlags = TransformFlags.AssertES6 | TransformFlags.DestructuringAssignment; + } + else if (isExponentiation(node.operatorToken)) { + // Exponentiation is ES7 syntax. + transformFlags = TransformFlags.AssertES7; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function isDestructuringAssignment(node: BinaryExpression) { + return node.operatorToken.kind === SyntaxKind.EqualsToken + && isObjectOrArrayLiteral(node.left); + } + + function isObjectOrArrayLiteral(node: Node) { + switch (node.kind) { + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + return true; + } + + return false; + } + + function isExponentiation(operatorToken: Node) { + switch (operatorToken.kind) { + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + return true; + } + + return false; + } + + function computeParameter(node: ParameterDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + // If the parameter has a question token, then it is TypeScript syntax. + if (isDefined(node.questionToken)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If the parameter's name is 'this', then it is TypeScript syntax. + if (node.name && (node.name as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If a parameter has an accessibility modifier, then it is TypeScript syntax. + if (hasModifier(node, ModifierFlags.AccessibilityModifier)) { + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments; + } + + // If a parameter has an initializer, a binding pattern or a dotDotDot token, then + // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. + if (isDefined(node.initializer) || isDefined(node.dotDotDotToken) || isBindingPattern(node.name)) { + transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsDefaultValueAssignments; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeParenthesizedExpression(node: ParenthesizedExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + // If the node is synthesized, it means the emitter put the parentheses there, + // not the user. If we didn't want them, the emitter would not have put them + // there. + if (node.expression.kind === SyntaxKind.AsExpression + || node.expression.kind === SyntaxKind.TypeAssertionExpression) { + transformFlags = TransformFlags.AssertTypeScript; + } + + // If the expression of a ParenthesizedExpression is a destructuring assignment, + // then the ParenthesizedExpression is a destructuring assignment. + if (node.expression.transformFlags & TransformFlags.DestructuringAssignment) { + transformFlags |= TransformFlags.DestructuringAssignment; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeClassDeclaration(node: ClassDeclaration, subtreeFlags: TransformFlags) { + // An ambient declaration is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Ambient)) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.TypeScript, TransformFlags.ClassExcludes); + } + + // A ClassDeclaration is ES6 syntax. + let transformFlags = TransformFlags.AssertES6; + + // A class with a parameter property assignment, property initializer, or decorator is + // TypeScript syntax. + // An exported declaration may be TypeScript syntax. + if (subtreeFlags + & (TransformFlags.ContainsParameterPropertyAssignments + | TransformFlags.ContainsPropertyInitializer + | TransformFlags.ContainsDecorators) + || hasModifier(node, ModifierFlags.Export)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); + } + + function computeClassExpression(node: ClassExpression, subtreeFlags: TransformFlags) { + // A ClassExpression is ES6 syntax. + let transformFlags = TransformFlags.AssertES6; + + // A class with a parameter property assignment, property initializer, or decorator is + // TypeScript syntax. + if (subtreeFlags + & (TransformFlags.ContainsParameterPropertyAssignments + | TransformFlags.ContainsPropertyInitializer + | TransformFlags.ContainsDecorators)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); + } + + function computeFunctionDeclaration(node: FunctionDeclaration, subtreeFlags: TransformFlags) { + const modifiers = getModifierFlags(node); + + // An ambient declaration is TypeScript syntax. + // A FunctionDeclaration without a body is an overload and is TypeScript syntax. + if (!node.body || modifiers & ModifierFlags.Ambient) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.AssertTypeScript, TransformFlags.FunctionExcludes); + } + + let transformFlags = TransformFlags.None; + + // If a FunctionDeclaration is exported, then it is either ES6 or TypeScript syntax. + if (modifiers & ModifierFlags.Export) { + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.AssertES6; + } + + // If a FunctionDeclaration is async, then it is TypeScript syntax. + if (modifiers & ModifierFlags.Async) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If a FunctionDeclaration has an asterisk token, is exported, or its + // subtree has marked the container as needing to capture the lexical `this`, + // then this node is ES6 syntax. + if (subtreeFlags & (TransformFlags.ContainsCapturedLexicalThis | TransformFlags.ContainsDefaultValueAssignments) + || node.asteriskToken) { + transformFlags |= TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.FunctionExcludes); + } + + function computeFunctionExpression(node: FunctionExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // An async function expression is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If a FunctionExpression contains an asterisk token, or its subtree has marked the container + // as needing to capture the lexical this, then this node is ES6 syntax. + if (subtreeFlags & (TransformFlags.ContainsCapturedLexicalThis | TransformFlags.ContainsDefaultValueAssignments) + || node.asteriskToken) { + transformFlags |= TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.FunctionExcludes); + } + + function computeArrowFunction(node: ArrowFunction, subtreeFlags: TransformFlags) { + // An ArrowFunction is ES6 syntax, and excludes markers that should not escape the scope of an ArrowFunction. + let transformFlags = TransformFlags.AssertES6; + + // An async arrow function is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If an ArrowFunction contains a lexical this, its container must capture the lexical this. + if (subtreeFlags & TransformFlags.ContainsLexicalThis) { + transformFlags |= TransformFlags.ContainsCapturedLexicalThis; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ArrowFunctionExcludes); + } + + function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // A VariableDeclaration with a binding pattern is ES6 syntax. + if (isBindingPattern((node).name)) { + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeVariableStatement(node: VariableStatement, subtreeFlags: TransformFlags) { + const modifiers = getModifierFlags(node); + // An ambient declaration is TypeScript syntax. + if (modifiers & ModifierFlags.Ambient) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.AssertTypeScript, TransformFlags.None); + } + + let transformFlags = TransformFlags.None; + + // If a VariableStatement is exported, then it is either ES6 or TypeScript syntax. + if (modifiers & ModifierFlags.Export) { + transformFlags = TransformFlags.AssertES6 | TransformFlags.AssertTypeScript; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeLabeledStatement(node: LabeledStatement, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding + && isIterationStatement(this, /*lookInLabeledStatements*/ true)) { + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function updateTransformFlags(node: Node, subtreeFlags: TransformFlags, transformFlags: TransformFlags, excludeFlags: TransformFlags) { + node.transformFlags = transformFlags | subtreeFlags | TransformFlags.HasComputedFlags; + node.excludeTransformFlags = excludeFlags | TransformFlags.NodeExcludes; + return node.transformFlags & ~node.excludeTransformFlags; + } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e001347e066f..e67c6f3146f0b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -542,7 +542,7 @@ namespace ts { const initializerOfNonStaticProperty = current.parent && current.parent.kind === SyntaxKind.PropertyDeclaration && - (current.parent.flags & NodeFlags.Static) === 0 && + (getModifierFlags(current.parent) & ModifierFlags.Static) === 0 && (current.parent).initializer === current; if (initializerOfNonStaticProperty) { @@ -660,7 +660,7 @@ namespace ts { // local variables of the constructor. This effectively means that entities from outer scopes // by the same name as a constructor parameter or local variable are inaccessible // in initializer expressions for instance member variables. - if (isClassLike(location.parent) && !(location.flags & NodeFlags.Static)) { + if (isClassLike(location.parent) && !(getModifierFlags(location) & ModifierFlags.Static)) { const ctor = findConstructorDeclaration(location.parent); if (ctor && ctor.locals) { if (getSymbol(ctor.locals, name, meaning & SymbolFlags.Value)) { @@ -674,7 +674,7 @@ namespace ts { case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: if (result = getSymbol(getSymbolOfNode(location).members, name, meaning & SymbolFlags.Type)) { - if (lastLocation && lastLocation.flags & NodeFlags.Static) { + if (lastLocation && getModifierFlags(lastLocation) & ModifierFlags.Static) { // TypeScript 1.0 spec (April 2014): 3.4.1 // The scope of a type parameter extends over the entire declaration with which the type // parameter list is associated, with the exception of static member declarations in classes. @@ -831,7 +831,7 @@ namespace ts { // No static member is present. // Check if we're in an instance method and look for a relevant instance member. - if (location === container && !(location.flags & NodeFlags.Static)) { + if (location === container && !(getModifierFlags(location) & ModifierFlags.Static)) { const instanceType = (getDeclaredTypeOfSymbol(classSymbol)).thisType; if (getPropertyOfType(instanceType, name)) { error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, typeof nameArg === "string" ? nameArg : declarationNameToString(nameArg)); @@ -1125,7 +1125,7 @@ namespace ts { } // Resolves a qualified name and any involved aliases - function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, ignoreErrors?: boolean): Symbol { + function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, ignoreErrors?: boolean, location?: Node): Symbol { if (nodeIsMissing(name)) { return undefined; } @@ -1134,7 +1134,7 @@ namespace ts { if (name.kind === SyntaxKind.Identifier) { const message = meaning === SymbolFlags.Namespace ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0; - symbol = resolveName(name, (name).text, meaning, ignoreErrors ? undefined : message, name); + symbol = resolveName(location || name, (name).text, meaning, ignoreErrors ? undefined : message, name); if (!symbol) { return undefined; } @@ -1143,7 +1143,7 @@ namespace ts { const left = name.kind === SyntaxKind.QualifiedName ? (name).left : (name).expression; const right = name.kind === SyntaxKind.QualifiedName ? (name).right : (name).name; - const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors); + const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, location); if (!namespace || namespace === unknownSymbol || nodeIsMissing(right)) { return undefined; } @@ -1655,7 +1655,7 @@ namespace ts { const anyImportSyntax = getAnyImportSyntax(declaration); if (anyImportSyntax && - !(anyImportSyntax.flags & NodeFlags.Export) && // import clause without export + !(getModifierFlags(anyImportSyntax) & ModifierFlags.Export) && // import clause without export isDeclarationVisible(anyImportSyntax.parent)) { getNodeLinks(declaration).isVisible = true; if (aliasesToMakeVisible) { @@ -1758,11 +1758,11 @@ namespace ts { return result; } - function visibilityToString(flags: NodeFlags) { - if (flags === NodeFlags.Private) { + function visibilityToString(flags: ModifierFlags) { + if (flags === ModifierFlags.Private) { return "private"; } - if (flags === NodeFlags.Protected) { + if (flags === ModifierFlags.Protected) { return "protected"; } return "public"; @@ -2065,7 +2065,7 @@ namespace ts { function shouldWriteTypeOfFunctionSymbol() { const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method - forEach(symbol.declarations, declaration => declaration.flags & NodeFlags.Static)); + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && (symbol.parent || // is exported function symbol forEach(symbol.declarations, declaration => @@ -2419,7 +2419,7 @@ namespace ts { } const parent = getDeclarationContainer(node); // If the node is not exported or it is not ambient module element (except import declaration) - if (!(getCombinedNodeFlags(node) & NodeFlags.Export) && + if (!(getCombinedModifierFlags(node) & ModifierFlags.Export) && !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && isInAmbientContext(parent))) { return isGlobalSourceFile(parent); } @@ -2432,7 +2432,7 @@ namespace ts { case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: - if (node.flags & (NodeFlags.Private | NodeFlags.Protected)) { + if (getModifierFlags(node) & (ModifierFlags.Private | ModifierFlags.Protected)) { // Private/protected properties/methods are not visible return false; } @@ -4015,7 +4015,7 @@ namespace ts { const type = getApparentType(current); if (type !== unknownType) { const prop = getPropertyOfType(type, name); - if (prop && !(getDeclarationFlagsFromSymbol(prop) & (NodeFlags.Private | NodeFlags.Protected))) { + if (prop && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))) { commonFlags &= prop.flags; if (!props) { props = [prop]; @@ -4482,7 +4482,7 @@ namespace ts { const declaration = getIndexDeclarationOfSymbol(symbol, kind); if (declaration) { return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, - (declaration.flags & NodeFlags.Readonly) !== 0, declaration); + (getModifierFlags(declaration) & ModifierFlags.Readonly) !== 0, declaration); } return undefined; } @@ -5019,8 +5019,8 @@ namespace ts { const container = getThisContainer(node, /*includeArrowFunctions*/ false); const parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!(container.flags & NodeFlags.Static) && - (container.kind !== SyntaxKind.Constructor || isNodeDescendentOf(node, (container).body))) { + if (!(getModifierFlags(container) & ModifierFlags.Static) && + (container.kind !== SyntaxKind.Constructor || isNodeDescendantOf(node, (container).body))) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)).thisType; } } @@ -5093,7 +5093,7 @@ namespace ts { case SyntaxKind.JSDocRecordType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); // This function assumes that an identifier or qualified name is a type expression - // Callers should first ensure this by calling isTypeNode + // Callers should first ensure this by calling isPartOfTypeNode case SyntaxKind.Identifier: case SyntaxKind.QualifiedName: const symbol = getSymbolAtLocation(node); @@ -6098,24 +6098,24 @@ namespace ts { } } else if (!(targetProp.flags & SymbolFlags.Prototype)) { - const sourcePropFlags = getDeclarationFlagsFromSymbol(sourceProp); - const targetPropFlags = getDeclarationFlagsFromSymbol(targetProp); - if (sourcePropFlags & NodeFlags.Private || targetPropFlags & NodeFlags.Private) { + const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); + const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); + if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { if (reportErrors) { - if (sourcePropFlags & NodeFlags.Private && targetPropFlags & NodeFlags.Private) { + if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); } else { reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), - typeToString(sourcePropFlags & NodeFlags.Private ? source : target), - typeToString(sourcePropFlags & NodeFlags.Private ? target : source)); + typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), + typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); } } return Ternary.False; } } - else if (targetPropFlags & NodeFlags.Protected) { + else if (targetPropFlags & ModifierFlags.Protected) { const sourceDeclaredInClass = sourceProp.parent && sourceProp.parent.flags & SymbolFlags.Class; const sourceClass = sourceDeclaredInClass ? getDeclaredTypeOfSymbol(getParentOfSymbol(sourceProp)) : undefined; const targetClass = getDeclaredTypeOfSymbol(getParentOfSymbol(targetProp)); @@ -6127,7 +6127,7 @@ namespace ts { return Ternary.False; } } - else if (sourcePropFlags & NodeFlags.Protected) { + else if (sourcePropFlags & ModifierFlags.Protected) { if (reportErrors) { reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); @@ -6360,21 +6360,21 @@ namespace ts { return true; } - const sourceAccessibility = sourceSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected); - const targetAccessibility = targetSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected); + const sourceAccessibility = getModifierFlags(sourceSignature.declaration) & ModifierFlags.NonPublicAccessibilityModifier; + const targetAccessibility = getModifierFlags(targetSignature.declaration) & ModifierFlags.NonPublicAccessibilityModifier; // A public, protected and private signature is assignable to a private signature. - if (targetAccessibility === NodeFlags.Private) { + if (targetAccessibility === ModifierFlags.Private) { return true; } // A public and protected signature is assignable to a protected signature. - if (targetAccessibility === NodeFlags.Protected && sourceAccessibility !== NodeFlags.Private) { + if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { return true; } // Only a public signature is assignable to public signature. - if (targetAccessibility !== NodeFlags.Protected && !sourceAccessibility) { + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { return true; } @@ -6392,7 +6392,7 @@ namespace ts { const symbol = type.symbol; if (symbol && symbol.flags & SymbolFlags.Class) { const declaration = getClassLikeDeclarationOfSymbol(symbol); - if (declaration && declaration.flags & NodeFlags.Abstract) { + if (declaration && getModifierFlags(declaration) & ModifierFlags.Abstract) { return true; } } @@ -6432,8 +6432,8 @@ namespace ts { if (sourceProp === targetProp) { return Ternary.True; } - const sourcePropAccessibility = getDeclarationFlagsFromSymbol(sourceProp) & (NodeFlags.Private | NodeFlags.Protected); - const targetPropAccessibility = getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected); + const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; + const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; if (sourcePropAccessibility !== targetPropAccessibility) { return Ternary.False; } @@ -7858,8 +7858,8 @@ namespace ts { let container = getContainingClass(node); while (container !== undefined) { if (container === localOrExportSymbol.valueDeclaration && container.name !== node) { - getNodeLinks(container).flags |= NodeCheckFlags.ClassWithBodyScopedClassBinding; - getNodeLinks(node).flags |= NodeCheckFlags.BodyScopedClassBinding; + getNodeLinks(container).flags |= NodeCheckFlags.DecoratedClassWithSelfReference; + getNodeLinks(node).flags |= NodeCheckFlags.SelfReferenceInDecoratedClass; break; } @@ -8077,7 +8077,7 @@ namespace ts { break; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: - if (container.flags & NodeFlags.Static) { + if (getModifierFlags(container) & ModifierFlags.Static) { error(node, Diagnostics.this_cannot_be_referenced_in_a_static_property_initializer); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks } @@ -8110,7 +8110,7 @@ namespace ts { } if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); - const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; + const type = getModifierFlags(container) & ModifierFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; return getNarrowedTypeOfReference(type, node); } @@ -8205,7 +8205,7 @@ namespace ts { return unknownType; } - if ((container.flags & NodeFlags.Static) || isCallExpression) { + if ((getModifierFlags(container) & ModifierFlags.Static) || isCallExpression) { nodeCheckFlag = NodeCheckFlags.SuperStatic; } else { @@ -8270,8 +8270,8 @@ namespace ts { // This helper creates an object with a "value" property that wraps the `super` property or indexed access for both get and set. // This is required for destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment // while a property access can. - if (container.kind === SyntaxKind.MethodDeclaration && container.flags & NodeFlags.Async) { - if (isSuperPropertyOrElementAccess(node.parent) && isAssignmentTarget(node.parent)) { + if (container.kind === SyntaxKind.MethodDeclaration && getModifierFlags(container) & ModifierFlags.Async) { + if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; } else { @@ -8336,7 +8336,7 @@ namespace ts { // topmost container must be something that is directly nested in the class declaration\object literal expression if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (container.flags & NodeFlags.Static) { + if (getModifierFlags(container) & ModifierFlags.Static) { return container.kind === SyntaxKind.MethodDeclaration || container.kind === SyntaxKind.MethodSignature || container.kind === SyntaxKind.GetAccessor || @@ -8399,9 +8399,9 @@ namespace ts { } // In a variable, parameter or property declaration with a type annotation, - // the contextual type of an initializer expression is the type of the variable, parameter or property. - // Otherwise, in a parameter declaration of a contextually typed function expression, - // the contextual type of an initializer expression is the contextual type of the parameter. + // the contextual type of an initializer expression is the type of the variable, parameter or property. + // Otherwise, in a parameter declaration of a contextually typed function expression, + // the contextual type of an initializer expression is the contextual type of the parameter. // Otherwise, in a variable or parameter declaration with a binding pattern name, // the contextual type of an initializer expression is the type implied by the binding pattern. // Otherwise, in a binding pattern inside a variable or parameter declaration, @@ -9566,8 +9566,12 @@ namespace ts { return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.PropertyDeclaration; } - function getDeclarationFlagsFromSymbol(s: Symbol): NodeFlags { - return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; + function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { + return s.valueDeclaration ? getCombinedModifierFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? ModifierFlags.Public | ModifierFlags.Static : 0; + } + + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; } /** @@ -9579,7 +9583,7 @@ namespace ts { * @param prop The symbol for the right hand side of the property access. */ function checkClassPropertyAccess(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration, left: Expression | QualifiedName, type: Type, prop: Symbol): boolean { - const flags = getDeclarationFlagsFromSymbol(prop); + const flags = getDeclarationModifierFlagsFromSymbol(prop); const declaringClass = getDeclaredTypeOfSymbol(getParentOfSymbol(prop)); const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration ? (node).name : @@ -9600,7 +9604,7 @@ namespace ts { return false; } - if (flags & NodeFlags.Abstract) { + if (flags & ModifierFlags.Abstract) { // A method cannot be accessed in a super property access if the method is abstract. // This error could mask a private property access error. But, a member // cannot simultaneously be private and abstract, so this will trigger an @@ -9612,14 +9616,14 @@ namespace ts { } // Public properties are otherwise accessible. - if (!(flags & (NodeFlags.Private | NodeFlags.Protected))) { + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { return true; } // Property is known to be private or protected at this point // Private property is accessible if the property is within the declaring class - if (flags & NodeFlags.Private) { + if (flags & ModifierFlags.Private) { const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)); if (!isNodeWithinClass(node, declaringClassDeclaration)) { error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(declaringClass)); @@ -9647,7 +9651,7 @@ namespace ts { return false; } // No further restrictions for static properties - if (flags & NodeFlags.Static) { + if (flags & ModifierFlags.Static) { return true; } // An instance property must be accessed through an instance of the enclosing class @@ -10913,7 +10917,7 @@ namespace ts { // In the case of a merged class-module or class-interface declaration, // only the class declaration node will have the Abstract flag set. const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); - if (valueDecl && valueDecl.flags & NodeFlags.Abstract) { + if (valueDecl && getModifierFlags(valueDecl) & ModifierFlags.Abstract) { error(node, Diagnostics.Cannot_create_an_instance_of_the_abstract_class_0, declarationNameToString(valueDecl.name)); return resolveErrorCall(node); } @@ -10966,22 +10970,22 @@ namespace ts { } const declaration = signature.declaration; - const flags = declaration.flags; + const modifiers = getModifierFlags(declaration); // Public constructor is accessible. - if (!(flags & (NodeFlags.Private | NodeFlags.Protected))) { + if (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier)) { return true; } const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol); const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol); - // A private or protected constructor can only be instantiated within it's own class + // A private or protected constructor can only be instantiated within it's own class if (!isNodeWithinClass(node, declaringClassDeclaration)) { - if (flags & NodeFlags.Private) { + if (modifiers & ModifierFlags.Private) { error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } - if (flags & NodeFlags.Protected) { + if (modifiers & ModifierFlags.Protected) { error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } return false; @@ -11586,8 +11590,8 @@ namespace ts { // Variables declared with 'const' // Get accessors without matching set accessors // Enum members - return symbol.flags & SymbolFlags.Property && (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Readonly) !== 0 || - symbol.flags & SymbolFlags.Variable && (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 || + return symbol.flags & SymbolFlags.Property && (getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly) !== 0 || + symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 || symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || (symbol.flags & SymbolFlags.EnumMember) !== 0; } @@ -12428,7 +12432,7 @@ namespace ts { checkVariableLikeDeclaration(node); let func = getContainingFunction(node); - if (node.flags & NodeFlags.AccessibilityModifier) { + if (getModifierFlags(node) & ModifierFlags.AccessibilityModifier) { func = getContainingFunction(node); if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); @@ -12686,7 +12690,7 @@ namespace ts { // Abstract methods cannot have an implementation. // Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node. - if (node.flags & NodeFlags.Abstract && node.body) { + if (getModifierFlags(node) & ModifierFlags.Abstract && node.body) { error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); } } @@ -12744,7 +12748,7 @@ namespace ts { function isInstancePropertyWithInitializer(n: Node): boolean { return n.kind === SyntaxKind.PropertyDeclaration && - !(n.flags & NodeFlags.Static) && + !(getModifierFlags(n) & ModifierFlags.Static) && !!(n).initializer; } @@ -12767,7 +12771,7 @@ namespace ts { // or the containing class declares instance member variables with initializers. const superCallShouldBeFirst = forEach((node.parent).members, isInstancePropertyWithInitializer) || - forEach(node.parameters, p => p.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected)); + forEach(node.parameters, p => getModifierFlags(p) & ModifierFlags.AccessibilityModifier); // Skip past any prologue directives to find the first statement // to ensure that it was a super call. @@ -12826,10 +12830,10 @@ namespace ts { const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; const otherAccessor = getDeclarationOfKind(node.symbol, otherKind); if (otherAccessor) { - if (((node.flags & NodeFlags.AccessibilityModifier) !== (otherAccessor.flags & NodeFlags.AccessibilityModifier))) { + if ((getModifierFlags(node) & ModifierFlags.AccessibilityModifier) !== (getModifierFlags(otherAccessor) & ModifierFlags.AccessibilityModifier)) { error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility); } - if (((node.flags & NodeFlags.Abstract) !== (otherAccessor.flags & NodeFlags.Abstract))) { + if (hasModifier(node, ModifierFlags.Abstract) !== hasModifier(otherAccessor, ModifierFlags.Abstract)) { error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); } @@ -12930,11 +12934,11 @@ namespace ts { } function isPrivateWithinAmbient(node: Node): boolean { - return (node.flags & NodeFlags.Private) && isInAmbientContext(node); + return (getModifierFlags(node) & ModifierFlags.Private) && isInAmbientContext(node); } - function getEffectiveDeclarationFlags(n: Node, flagsToCheck: NodeFlags): NodeFlags { - let flags = getCombinedNodeFlags(n); + function getEffectiveDeclarationFlags(n: Node, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlags(n); // children of classes (even ambient classes) should not be marked as ambient or export // because those flags have no useful semantics there. @@ -12942,11 +12946,11 @@ namespace ts { n.parent.kind !== SyntaxKind.ClassDeclaration && n.parent.kind !== SyntaxKind.ClassExpression && isInAmbientContext(n)) { - if (!(flags & NodeFlags.Ambient)) { + if (!(flags & ModifierFlags.Ambient)) { // It is nested in an ambient context, which means it is automatically exported - flags |= NodeFlags.Export; + flags |= ModifierFlags.Export; } - flags |= NodeFlags.Ambient; + flags |= ModifierFlags.Ambient; } return flags & flagsToCheck; @@ -12967,7 +12971,7 @@ namespace ts { return implementationSharesContainerWithFirstOverload ? implementation : overloads[0]; } - function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration, flagsToCheck: NodeFlags, someOverloadFlags: NodeFlags, allOverloadFlags: NodeFlags): void { + function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { // Error if some overloads have a flag that is not shared by all overloads. To find the // deviations, we XOR someOverloadFlags with allOverloadFlags const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; @@ -12976,16 +12980,16 @@ namespace ts { forEach(overloads, o => { const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; - if (deviation & NodeFlags.Export) { + if (deviation & ModifierFlags.Export) { error(o.name, Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); } - else if (deviation & NodeFlags.Ambient) { + else if (deviation & ModifierFlags.Ambient) { error(o.name, Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); } - else if (deviation & (NodeFlags.Private | NodeFlags.Protected)) { + else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { error(o.name || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); } - else if (deviation & NodeFlags.Abstract) { + else if (deviation & ModifierFlags.Abstract) { error(o.name, Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); } }); @@ -13004,8 +13008,8 @@ namespace ts { } } - const flagsToCheck: NodeFlags = NodeFlags.Export | NodeFlags.Ambient | NodeFlags.Private | NodeFlags.Protected | NodeFlags.Abstract; - let someNodeFlags: NodeFlags = 0; + const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; + let someNodeFlags: ModifierFlags = ModifierFlags.None; let allNodeFlags = flagsToCheck; let someHaveQuestionToken = false; let allHaveQuestionToken = true; @@ -13040,13 +13044,13 @@ namespace ts { if (node.name && (subsequentNode).name && (node.name).text === ((subsequentNode).name).text) { const reportError = (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && - (node.flags & NodeFlags.Static) !== (subsequentNode.flags & NodeFlags.Static); + (getModifierFlags(node) & ModifierFlags.Static) !== (getModifierFlags(subsequentNode) & ModifierFlags.Static); // we can get here in two cases // 1. mixed static and instance class members // 2. something with the same name was defined before the set of overloads that prevents them from merging // here we'll report error only for the first case since for second we should already report error in binder if (reportError) { - const diagnostic = node.flags & NodeFlags.Static ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; + const diagnostic = getModifierFlags(node) & ModifierFlags.Static ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; error(errorNode, diagnostic); } return; @@ -13064,7 +13068,7 @@ namespace ts { else { // Report different errors regarding non-consecutive blocks of declarations depending on whether // the node in question is abstract. - if (node.flags & NodeFlags.Abstract) { + if (getModifierFlags(node) & ModifierFlags.Abstract) { error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); } else { @@ -13143,7 +13147,7 @@ namespace ts { // Abstract methods can't have an implementation -- in particular, they don't need one. if (!isExportSymbolInsideModule && lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && - !(lastSeenNonAmbientDeclaration.flags & NodeFlags.Abstract)) { + !(getModifierFlags(lastSeenNonAmbientDeclaration) & ModifierFlags.Abstract)) { reportImplementationExpectedError(lastSeenNonAmbientDeclaration); } @@ -13193,10 +13197,10 @@ namespace ts { let defaultExportedDeclarationSpaces = SymbolFlags.None; for (const d of symbol.declarations) { const declarationSpaces = getDeclarationSpaces(d); - const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, NodeFlags.Export | NodeFlags.Default); + const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - if (effectiveDeclarationFlags & NodeFlags.Export) { - if (effectiveDeclarationFlags & NodeFlags.Default) { + if (effectiveDeclarationFlags & ModifierFlags.Export) { + if (effectiveDeclarationFlags & ModifierFlags.Default) { defaultExportedDeclarationSpaces |= declarationSpaces; } else { @@ -13767,7 +13771,7 @@ namespace ts { function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { // no rest parameters \ declaration context \ overload - no codegen impact - if (!hasRestParameter(node) || isInAmbientContext(node) || nodeIsMissing((node).body)) { + if (!hasDeclaredRestParameter(node) || isInAmbientContext(node) || nodeIsMissing((node).body)) { return; } @@ -13936,7 +13940,7 @@ namespace ts { if (localDeclarationSymbol && localDeclarationSymbol !== symbol && localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { - if (getDeclarationFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { + if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList); const container = varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent @@ -14726,7 +14730,7 @@ namespace ts { // Only process instance properties with computed names here. // Static properties cannot be in conflict with indexers, // and properties with literal names were already checked. - if (!(member.flags & NodeFlags.Static) && hasDynamicName(member)) { + if (!(getModifierFlags(member) & ModifierFlags.Static) && hasDynamicName(member)) { const propType = getTypeOfSymbol(member.symbol); checkIndexConstraintForProperty(member.symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); checkIndexConstraintForProperty(member.symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); @@ -14855,7 +14859,7 @@ namespace ts { } function checkClassDeclaration(node: ClassDeclaration) { - if (!node.name && !(node.flags & NodeFlags.Default)) { + if (!node.name && !(getModifierFlags(node) & ModifierFlags.Default)) { grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); } checkClassLikeDeclaration(node); @@ -14945,7 +14949,7 @@ namespace ts { const signatures = getSignaturesOfType(type, SignatureKind.Construct); if (signatures.length) { const declaration = signatures[0].declaration; - if (declaration && declaration.flags & NodeFlags.Private) { + if (declaration && getModifierFlags(declaration) & ModifierFlags.Private) { const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol); if (!isNodeWithinClass(node, typeClassDeclaration)) { error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, (node.expression).text); @@ -14990,7 +14994,7 @@ namespace ts { } const derived = getTargetSymbol(getPropertyOfObjectType(type, base.name)); - const baseDeclarationFlags = getDeclarationFlagsFromSymbol(base); + const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); @@ -15006,7 +15010,7 @@ namespace ts { // It is an error to inherit an abstract member without implementing it or being declared abstract. // If there is no declaration for the derived class (as in the case of class expressions), // then the class cannot be declared abstract. - if (baseDeclarationFlags & NodeFlags.Abstract && (!derivedClassDecl || !(derivedClassDecl.flags & NodeFlags.Abstract))) { + if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !(getModifierFlags(derivedClassDecl) & ModifierFlags.Abstract))) { if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, symbolToString(baseProperty), typeToString(baseType)); @@ -15019,13 +15023,13 @@ namespace ts { } else { // derived overrides base. - const derivedDeclarationFlags = getDeclarationFlagsFromSymbol(derived); - if ((baseDeclarationFlags & NodeFlags.Private) || (derivedDeclarationFlags & NodeFlags.Private)) { + const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); + if ((baseDeclarationFlags & ModifierFlags.Private) || (derivedDeclarationFlags & ModifierFlags.Private)) { // either base or derived property is private - not override, skip it continue; } - if ((baseDeclarationFlags & NodeFlags.Static) !== (derivedDeclarationFlags & NodeFlags.Static)) { + if ((baseDeclarationFlags & ModifierFlags.Static) !== (derivedDeclarationFlags & ModifierFlags.Static)) { // value of 'static' is not the same for properties - not override, skip it continue; } @@ -15668,12 +15672,12 @@ namespace ts { const symbol = getSymbolOfNode(node); const target = resolveAlias(symbol); if (target !== unknownSymbol) { - // For external modules symbol represent local symbol for an alias. + // For external modules symbol represent local symbol for an alias. // This local symbol will merge any other local declarations (excluding other aliases) // and symbol.flags will contains combined representation for all merged declaration. // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). const excludedMeanings = (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | @@ -15699,7 +15703,7 @@ namespace ts { // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. return; } - if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && (node.flags & NodeFlags.Modifier)) { + if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && getModifierFlags(node) !== 0) { grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); } if (checkExternalImportOrExportDeclaration(node)) { @@ -15729,7 +15733,7 @@ namespace ts { checkGrammarDecorators(node) || checkGrammarModifiers(node); if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { checkImportBinding(node); - if (node.flags & NodeFlags.Export) { + if (getModifierFlags(node) & ModifierFlags.Export) { markExportAsReferenced(node); } if (isInternalModuleImportEqualsDeclaration(node)) { @@ -15762,7 +15766,7 @@ namespace ts { return; } - if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && (node.flags & NodeFlags.Modifier)) { + if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && getModifierFlags(node) !== 0) { grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); } @@ -15821,7 +15825,7 @@ namespace ts { return; } // Grammar checking - if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && (node.flags & NodeFlags.Modifier)) { + if (!checkGrammarDecorators(node) && !checkGrammarModifiers(node) && getModifierFlags(node) !== 0) { grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); } if (node.expression.kind === SyntaxKind.Identifier) { @@ -15872,7 +15876,7 @@ namespace ts { continue; } const { declarations, flags } = exports[id]; - // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. + // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. // (TS Exceptions: namespaces, function overloads, enums, and interfaces) if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { continue; @@ -16157,7 +16161,7 @@ namespace ts { function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { const symbols: SymbolTable = {}; - let memberFlags: NodeFlags = 0; + let memberFlags: ModifierFlags = ModifierFlags.None; if (isInsideWithStatementBody(location)) { // We cannot answer semantic questions within a with block, do not proceed any further @@ -16198,7 +16202,7 @@ namespace ts { // add the type parameters into the symbol table // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. // Note: that the memberFlags come from previous iteration. - if (!(memberFlags & NodeFlags.Static)) { + if (!(memberFlags & ModifierFlags.Static)) { copySymbols(getSymbolOfNode(location).members, meaning & SymbolFlags.Type); } break; @@ -16214,7 +16218,7 @@ namespace ts { copySymbol(argumentsSymbol, meaning); } - memberFlags = location.flags; + memberFlags = getModifierFlags(location); location = location.parent; } @@ -16376,7 +16380,7 @@ namespace ts { meaning |= SymbolFlags.Alias; return resolveEntityName(entityName, meaning); } - else if (isExpression(entityName)) { + else if (isPartOfExpression(entityName)) { if (nodeIsMissing(entityName)) { // Missing entity name. return undefined; @@ -16463,7 +16467,7 @@ namespace ts { case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: - const type = isExpression(node) ? checkExpression(node) : getTypeFromTypeNode(node); + const type = isPartOfExpression(node) ? checkExpression(node) : getTypeFromTypeNode(node); return type.symbol; case SyntaxKind.ThisType: @@ -16524,11 +16528,11 @@ namespace ts { return unknownType; } - if (isTypeNode(node)) { + if (isPartOfTypeNode(node)) { return getTypeFromTypeNode(node); } - if (isExpression(node)) { + if (isPartOfExpression(node)) { return getTypeOfExpression(node); } @@ -16574,10 +16578,10 @@ namespace ts { } // Gets the type of object literal or array literal of destructuring assignment. - // { a } from + // { a } from // for ( { a } of elems) { // } - // [ a ] from + // [ a ] from // [a] = [ some array ...] function getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr: Expression): Type { Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); @@ -16610,10 +16614,10 @@ namespace ts { } // Gets the property symbol corresponding to the property in destructuring assignment - // 'property1' from + // 'property1' from // for ( { property1: a } of elems) { // } - // 'property1' at location 'a' from: + // 'property1' at location 'a' from: // [a] = [ property1, property2 ] function getPropertySymbolOfDestructuringAssignment(location: Identifier) { // Get the type of the object or array literal and then look for property of given name in the type @@ -16634,7 +16638,7 @@ namespace ts { */ function getParentTypeOfClassElement(node: ClassElement) { const classSymbol = getSymbolOfNode(node.parent); - return node.flags & NodeFlags.Static + return getModifierFlags(node) & ModifierFlags.Static ? getTypeOfSymbol(classSymbol) : getDeclaredTypeOfSymbol(classSymbol); } @@ -16678,7 +16682,14 @@ namespace ts { // Emitter support function isArgumentsLocalBinding(node: Identifier): boolean { - return getReferencedValueSymbol(node) === argumentsSymbol; + if (!isGeneratedIdentifier(node)) { + node = getSourceTreeNodeOfType(node, isIdentifier); + if (node) { + return getReferencedValueSymbol(node) === argumentsSymbol; + } + } + + return false; } function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { @@ -16710,40 +16721,61 @@ namespace ts { } } + function isNameOfModuleOrEnumDeclaration(node: Identifier) { + const parent = node.parent; + return isModuleOrEnumDeclaration(parent) && node === parent.name; + } + // When resolved as an expression identifier, if the given node references an exported entity, return the declaration // node of the exported entity's container. Otherwise, return undefined. - function getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration { - let symbol = getReferencedValueSymbol(node); - if (symbol) { - if (symbol.flags & SymbolFlags.ExportValue) { - // If we reference an exported entity within the same module declaration, then whether - // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the - // kinds that we do NOT prefix. - const exportSymbol = getMergedSymbol(symbol.exportSymbol); - if (exportSymbol.flags & SymbolFlags.ExportHasLocal) { - return undefined; - } - symbol = exportSymbol; - } - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol) { - if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) { - return parentSymbol.valueDeclaration; + function getReferencedExportContainer(node: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration { + node = getSourceTreeNodeOfType(node, isIdentifier); + if (node) { + // When resolving the export container for the name of a module or enum + // declaration, we need to start resolution at the declaration's container. + // Otherwise, we could incorrectly resolve the export container as the + // declaration if it contains an exported member with the same name. + let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); + if (symbol) { + if (symbol.flags & SymbolFlags.ExportValue) { + // If we reference an exported entity within the same module declaration, then whether + // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the + // kinds that we do NOT prefix. + const exportSymbol = getMergedSymbol(symbol.exportSymbol); + if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal) { + return undefined; + } + symbol = exportSymbol; } - for (let n = node.parent; n; n = n.parent) { - if ((n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(n) === parentSymbol) { - return n; + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol) { + if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) { + return parentSymbol.valueDeclaration; + } + for (let n = node.parent; n; n = n.parent) { + if (isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) { + return n; + } } } } } + + return undefined; } // When resolved as an expression identifier, if the given node references an import, return the declaration of // that import. Otherwise, return undefined. function getReferencedImportDeclaration(node: Identifier): Declaration { - const symbol = getReferencedValueSymbol(node); - return symbol && symbol.flags & SymbolFlags.Alias ? getDeclarationOfAliasSymbol(symbol) : undefined; + node = getSourceTreeNodeOfType(node, isIdentifier); + if (node) { + const symbol = getReferencedValueSymbol(node); + if (symbol && symbol.flags & SymbolFlags.Alias) { + return getDeclarationOfAliasSymbol(symbol); + } + } + + return undefined; } function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { @@ -16793,35 +16825,57 @@ namespace ts { // a name that either hides an existing name or might hide it when compiled downlevel, // return the declaration of that entity. Otherwise, return undefined. function getReferencedDeclarationWithCollidingName(node: Identifier): Declaration { - const symbol = getReferencedValueSymbol(node); - return symbol && isSymbolOfDeclarationWithCollidingName(symbol) ? symbol.valueDeclaration : undefined; + if (!isGeneratedIdentifier(node)) { + node = getSourceTreeNodeOfType(node, isIdentifier); + if (node) { + const symbol = getReferencedValueSymbol(node); + if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { + return symbol.valueDeclaration; + } + } + } + + return undefined; } // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an // existing name or might hide a name when compiled downlevel function isDeclarationWithCollidingName(node: Declaration): boolean { - return isSymbolOfDeclarationWithCollidingName(getSymbolOfNode(node)); + node = getSourceTreeNodeOfType(node, isDeclaration); + if (node) { + const symbol = getSymbolOfNode(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); + } + } + + return false; } function isValueAliasDeclaration(node: Node): boolean { + node = getSourceTreeNode(node); switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ImportClause: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: - return isAliasResolvedToValue(getSymbolOfNode(node)); + return isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol); case SyntaxKind.ExportDeclaration: const exportClause = (node).exportClause; return exportClause && forEach(exportClause.elements, isValueAliasDeclaration); case SyntaxKind.ExportAssignment: - return (node).expression && (node).expression.kind === SyntaxKind.Identifier ? isAliasResolvedToValue(getSymbolOfNode(node)) : true; + return (node).expression + && (node).expression.kind === SyntaxKind.Identifier + ? isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol) + : true; } return false; } function isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean { - if (node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { + node = getSourceTreeNodeOfType(node, isImportEqualsDeclaration); + if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { // parent is not source file or it is not reference to internal module return false; } @@ -16848,9 +16902,10 @@ namespace ts { } function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + node = getSourceTreeNode(node); if (isAliasSymbolDeclaration(node)) { const symbol = getSymbolOfNode(node); - if (getSymbolLinks(symbol).referenced) { + if (symbol && getSymbolLinks(symbol).referenced) { return true; } } @@ -16883,7 +16938,8 @@ namespace ts { } function getNodeCheckFlags(node: Node): NodeCheckFlags { - return getNodeLinks(node).flags; + node = getSourceTreeNode(node); + return node ? getNodeLinks(node).flags : undefined; } function getEnumMemberValue(node: EnumMember): number { @@ -16911,16 +16967,16 @@ namespace ts { return type.flags & TypeFlags.ObjectType && getSignaturesOfType(type, SignatureKind.Call).length > 0; } - function getTypeReferenceSerializationKind(typeName: EntityName): TypeReferenceSerializationKind { + function getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind { // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. - const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true); + const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, location); const constructorType = valueSymbol ? getTypeOfSymbol(valueSymbol) : undefined; if (constructorType && isConstructorType(constructorType)) { return TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; } // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. - const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true); + const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, location); // We might not be able to resolve type symbol so use unknown type in that case (eg error case) if (!typeSymbol) { return TypeReferenceSerializationKind.ObjectType; @@ -16992,16 +17048,37 @@ namespace ts { return hasProperty(globals, name); } - function getReferencedValueSymbol(reference: Identifier): Symbol { - return getNodeLinks(reference).resolvedSymbol || - resolveName(reference, reference.text, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, - /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; + } + + let location: Node = reference; + if (startInDeclarationContainer) { + // When resolving the name of a declaration as a value, we need to start resolution + // at a point outside of the declaration. + const parent = reference.parent; + if (isDeclaration(parent) && reference === parent.name) { + location = getDeclarationContainer(parent); + } + } + + return resolveName(location, reference.text, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); } function getReferencedValueDeclaration(reference: Identifier): Declaration { - Debug.assert(!nodeIsSynthesized(reference)); - const symbol = getReferencedValueSymbol(reference); - return symbol && getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; + if (!isGeneratedIdentifier(reference)) { + reference = getSourceTreeNodeOfType(reference, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; + } + } + } + + return undefined; } function createResolver(): EmitResolver { @@ -17311,7 +17388,7 @@ namespace ts { } let lastStatic: Node, lastPrivate: Node, lastProtected: Node, lastDeclare: Node, lastAsync: Node, lastReadonly: Node; - let flags = 0; + let flags = ModifierFlags.None; for (const modifier of node.modifiers) { if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { @@ -17339,22 +17416,22 @@ namespace ts { lastPrivate = modifier; } - if (flags & NodeFlags.AccessibilityModifier) { + if (flags & ModifierFlags.AccessibilityModifier) { return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); } - else if (flags & NodeFlags.Static) { + else if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); } - else if (flags & NodeFlags.Readonly) { + else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); } - else if (flags & NodeFlags.Async) { + else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); } else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); } - else if (flags & NodeFlags.Abstract) { + else if (flags & ModifierFlags.Abstract) { if (modifier.kind === SyntaxKind.PrivateKeyword) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); } @@ -17366,13 +17443,13 @@ namespace ts { break; case SyntaxKind.StaticKeyword: - if (flags & NodeFlags.Static) { + if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); } - else if (flags & NodeFlags.Readonly) { + else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); } - else if (flags & NodeFlags.Async) { + else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); } else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { @@ -17381,35 +17458,35 @@ namespace ts { else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); } - else if (flags & NodeFlags.Abstract) { + else if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } - flags |= NodeFlags.Static; + flags |= ModifierFlags.Static; lastStatic = modifier; break; case SyntaxKind.ReadonlyKeyword: - if (flags & NodeFlags.Readonly) { + if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); } else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature) { return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); } - flags |= NodeFlags.Readonly; + flags |= ModifierFlags.Readonly; lastReadonly = modifier; break; case SyntaxKind.ExportKeyword: - if (flags & NodeFlags.Export) { + if (flags & ModifierFlags.Export) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); } - else if (flags & NodeFlags.Ambient) { + else if (flags & ModifierFlags.Ambient) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); } - else if (flags & NodeFlags.Abstract) { + else if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); } - else if (flags & NodeFlags.Async) { + else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); } else if (node.parent.kind === SyntaxKind.ClassDeclaration) { @@ -17418,14 +17495,14 @@ namespace ts { else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); } - flags |= NodeFlags.Export; + flags |= ModifierFlags.Export; break; case SyntaxKind.DeclareKeyword: - if (flags & NodeFlags.Ambient) { + if (flags & ModifierFlags.Ambient) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); } - else if (flags & NodeFlags.Async) { + else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); } else if (node.parent.kind === SyntaxKind.ClassDeclaration) { @@ -17437,12 +17514,12 @@ namespace ts { else if (isInAmbientContext(node.parent) && node.parent.kind === SyntaxKind.ModuleBlock) { return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); } - flags |= NodeFlags.Ambient; + flags |= ModifierFlags.Ambient; lastDeclare = modifier; break; case SyntaxKind.AbstractKeyword: - if (flags & NodeFlags.Abstract) { + if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); } if (node.kind !== SyntaxKind.ClassDeclaration) { @@ -17452,58 +17529,58 @@ namespace ts { node.kind !== SyntaxKind.SetAccessor) { return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); } - if (!(node.parent.kind === SyntaxKind.ClassDeclaration && node.parent.flags & NodeFlags.Abstract)) { + if (!(node.parent.kind === SyntaxKind.ClassDeclaration && getModifierFlags(node.parent) & ModifierFlags.Abstract)) { return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); } - if (flags & NodeFlags.Static) { + if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } - if (flags & NodeFlags.Private) { + if (flags & ModifierFlags.Private) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); } } - flags |= NodeFlags.Abstract; + flags |= ModifierFlags.Abstract; break; case SyntaxKind.AsyncKeyword: - if (flags & NodeFlags.Async) { + if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); } - else if (flags & NodeFlags.Ambient || isInAmbientContext(node.parent)) { + else if (flags & ModifierFlags.Ambient || isInAmbientContext(node.parent)) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); } else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); } - flags |= NodeFlags.Async; + flags |= ModifierFlags.Async; lastAsync = modifier; break; } } if (node.kind === SyntaxKind.Constructor) { - if (flags & NodeFlags.Static) { + if (flags & ModifierFlags.Static) { return grammarErrorOnNode(lastStatic, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); } - if (flags & NodeFlags.Abstract) { + if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(lastStatic, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); } - else if (flags & NodeFlags.Async) { + else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } - else if (flags & NodeFlags.Readonly) { + else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(lastReadonly, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); } return; } - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) { + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { return grammarErrorOnNode(lastDeclare, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); } - else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.AccessibilityModifier) && isBindingPattern((node).name)) { + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.AccessibilityModifier) && isBindingPattern((node).name)) { return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_a_binding_pattern); } - if (flags & NodeFlags.Async) { + if (flags & ModifierFlags.Async) { return checkGrammarAsyncModifier(node, lastAsync); } } @@ -17620,7 +17697,7 @@ namespace ts { if (parameter.dotDotDotToken) { return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); } - if (parameter.flags & NodeFlags.Modifier) { + if (getModifierFlags(parameter) !== 0) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); } if (parameter.questionToken) { @@ -17807,11 +17884,13 @@ namespace ts { } // Modifiers are never allowed on properties except for 'async' on a method declaration - forEach(prop.modifiers, mod => { - if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + if (prop.modifiers) { + for (const mod of prop.modifiers) { + if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } } - }); + } // ECMA-262 11.1.5 Object Initializer // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true @@ -17944,7 +18023,7 @@ namespace ts { else if (isInAmbientContext(accessor)) { return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_be_declared_in_an_ambient_context); } - else if (accessor.body === undefined && !(accessor.flags & NodeFlags.Abstract)) { + else if (accessor.body === undefined && !(getModifierFlags(accessor) & ModifierFlags.Abstract)) { return grammarErrorAtPos(getSourceFileOfNode(accessor), accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); } else if (accessor.typeParameters) { @@ -17965,7 +18044,7 @@ namespace ts { if (parameter.dotDotDotToken) { return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); } - else if (parameter.flags & NodeFlags.Modifier) { + else if (getModifierFlags(parameter) !== 0) { return grammarErrorOnNode(accessor.name, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } else if (parameter.questionToken) { @@ -18271,8 +18350,7 @@ namespace ts { node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.ExportDeclaration || node.kind === SyntaxKind.ExportAssignment || - (node.flags & NodeFlags.Ambient) || - (node.flags & (NodeFlags.Export | NodeFlags.Default))) { + getModifierFlags(node) & (ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { return false; } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index bd9373e2d3f3f..fef057b910727 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -359,6 +359,12 @@ namespace ts { type: "boolean", description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output }, + { + name: "useLegacyEmitter", + type: "boolean", + experimental: true, + description: Diagnostics.Use_the_legacy_emitter_instead_of_the_transforming_emitter + }, { name: "listEmittedFiles", type: "boolean" diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts new file mode 100644 index 0000000000000..3e02558117bba --- /dev/null +++ b/src/compiler/comments.ts @@ -0,0 +1,288 @@ +/// + +/* @internal */ +namespace ts { + export interface CommentWriter { + reset(): void; + setSourceFile(sourceFile: SourceFile): void; + getLeadingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[]; + getLeadingComments(range: TextRange): CommentRange[]; + getLeadingCommentsOfPosition(pos: number): CommentRange[]; + getTrailingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[]; + getTrailingComments(range: TextRange): CommentRange[]; + getTrailingCommentsOfPosition(pos: number): CommentRange[]; + emitLeadingComments(range: TextRange, comments: CommentRange[]): void; + emitTrailingComments(range: TextRange, comments: CommentRange[]): void; + emitLeadingDetachedComments(range: TextRange): void; + emitLeadingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void; + emitTrailingDetachedComments(range: TextRange): void; + emitTrailingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void; + } + + export function createCommentWriter(host: EmitHost, writer: EmitTextWriter, sourceMap: SourceMapWriter): CommentWriter { + const compilerOptions = host.getCompilerOptions(); + const newLine = host.getNewLine(); + const { emitPos } = sourceMap; + + let currentSourceFile: SourceFile; + let currentText: string; + let currentLineMap: number[]; + let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; + + // This maps start->end for a comment range. See `hasConsumedCommentRange` and + // `consumeCommentRange` for usage. + let consumedCommentRanges: number[]; + let leadingCommentRangePositions: boolean[]; + let trailingCommentRangePositions: boolean[]; + + return compilerOptions.removeComments + ? createCommentRemovingWriter() + : createCommentPreservingWriter(); + + function createCommentRemovingWriter(): CommentWriter { + return { + reset, + setSourceFile, + getLeadingComments(range: TextRange, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[] { return undefined; }, + getLeadingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, + getTrailingComments(range: TextRange, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[] { return undefined; }, + getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, + emitLeadingComments(range: TextRange, comments: CommentRange[]): void { }, + emitTrailingComments(range: TextRange, comments: CommentRange[]): void { }, + emitLeadingDetachedComments, + emitTrailingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {} + }; + + function emitLeadingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + + emitDetachedCommentsAndUpdateCommentsInfo(node, /*removeComments*/ true); + } + } + + function createCommentPreservingWriter(): CommentWriter { + const noComments: CommentRange[] = []; + return { + reset, + setSourceFile, + getLeadingComments, + getLeadingCommentsOfPosition, + getTrailingComments, + getTrailingCommentsOfPosition, + emitLeadingComments, + emitTrailingComments, + emitLeadingDetachedComments, + emitTrailingDetachedComments + }; + + function getLeadingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[]; + function getLeadingComments(range: TextRange): CommentRange[]; + function getLeadingComments(range: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean) { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(range)) { + // If the node will not be emitted in JS, remove all the comments (normal, + // pinned and `///`) associated with the node, unless it is a triple slash + // comment at the top of the file. + // + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // + // The first `///` will NOT be removed while the second one will be removed + // even though both nodes will not be emitted. + if (range.pos === 0) { + return filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); + } + + return undefined; + } + + return getLeadingCommentsOfPosition(range.pos); + } + + /** + * Determine if the given comment is a triple-slash + **/ + function isTripleSlashComment(comment: CommentRange) { + // Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text + // so that we don't end up computing comment string and doing match for all // comments + if (currentText.charCodeAt(comment.pos + 1) === CharacterCodes.slash && + comment.pos + 2 < comment.end && + currentText.charCodeAt(comment.pos + 2) === CharacterCodes.slash) { + const textSubStr = currentText.substring(comment.pos, comment.end); + return fullTripleSlashReferencePathRegEx.test(textSubStr) + || fullTripleSlashAMDReferencePathRegEx.test(textSubStr); + } + return false; + } + + function getTrailingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[]; + function getTrailingComments(range: TextRange): CommentRange[]; + function getTrailingComments(range: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean) { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(range)) { + return undefined; + } + + return getTrailingCommentsOfPosition(range.end); + } + + function getLeadingCommentsOfPosition(pos: number) { + if (positionIsSynthesized(pos) || leadingCommentRangePositions[pos]) { + return undefined; + } + + leadingCommentRangePositions[pos] = true; + const comments = hasDetachedComments(pos) + ? getLeadingCommentsWithoutDetachedComments() + : getLeadingCommentRanges(currentText, pos); + return consumeCommentRanges(comments); + } + + function getTrailingCommentsOfPosition(pos: number) { + if (positionIsSynthesized(pos) || trailingCommentRangePositions[pos]) { + return undefined; + } + + trailingCommentRangePositions[pos] = true; + const comments = getTrailingCommentRanges(currentText, pos); + return consumeCommentRanges(comments); + } + + function emitLeadingComments(range: TextRange, comments: CommentRange[]) { + emitNewLineBeforeLeadingComments(currentLineMap, writer, range, comments); + + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); + } + + function emitTrailingComments(range: TextRange, comments: CommentRange[]) { + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ + emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); + } + + function emitLeadingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + + emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); + } + + function emitTrailingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + + range = collapseRangeToEnd(range); + emitLeadingComments(range, getLeadingComments(range)); + } + + function hasConsumedCommentRange(comment: CommentRange) { + return comment.end === consumedCommentRanges[comment.pos]; + } + + function consumeCommentRange(comment: CommentRange) { + if (!hasConsumedCommentRange(comment)) { + consumedCommentRanges[comment.pos] = comment.end; + return true; + } + + return false; + } + + function consumeCommentRanges(comments: CommentRange[]) { + let consumed: CommentRange[]; + if (comments) { + let commentsSkipped = 0; + let commentsConsumed = 0; + for (let i = 0; i < comments.length; i++) { + const comment = comments[i]; + if (consumeCommentRange(comment)) { + commentsConsumed++; + if (commentsSkipped !== 0) { + if (consumed === undefined) { + consumed = [comment]; + } + else { + consumed.push(comment); + } + } + } + else { + commentsSkipped++; + if (commentsConsumed !== 0 && consumed === undefined) { + consumed = comments.slice(0, i); + } + } + } + + if (commentsConsumed) { + return consumed || comments; + } + } + + return noComments; + } + } + + function reset() { + currentSourceFile = undefined; + currentText = undefined; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + consumedCommentRanges = undefined; + trailingCommentRangePositions = undefined; + leadingCommentRangePositions = undefined; + } + + function setSourceFile(sourceFile: SourceFile) { + currentSourceFile = sourceFile; + currentText = sourceFile.text; + currentLineMap = getLineStarts(sourceFile); + detachedCommentsInfo = undefined; + consumedCommentRanges = []; + leadingCommentRangePositions = []; + trailingCommentRangePositions = []; + } + + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos; + } + + function getLeadingCommentsWithoutDetachedComments() { + // get the leading comments from detachedPos + const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos; + const leadingComments = getLeadingCommentRanges(currentText, pos); + if (detachedCommentsInfo.length - 1) { + detachedCommentsInfo.pop(); + } + else { + detachedCommentsInfo = undefined; + } + + return leadingComments; + } + + function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange, removeComments: boolean) { + const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, node, newLine, removeComments); + + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); + } + else { + detachedCommentsInfo = [currentDetachedCommentInfo]; + } + } + } + + function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) { + emitPos(comment.pos); + writeCommentRange(text, lineMap, writer, comment, newLine); + emitPos(comment.end); + } + } +} \ No newline at end of file diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 76306e290ca22..bae17c642d29a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -91,6 +91,23 @@ namespace ts { return undefined; } + /** + * Iterates through `array` by index and performs the callback on each element of array until the callback + * returns a falsey value, then returns false. + * If no such value is found, the callback is applied to each element of array and `true` is returned. + */ + export function every(array: T[], callback: (element: T, index: number) => boolean): boolean { + if (array) { + for (let i = 0, len = array.length; i < len; i++) { + if (!callback(array[i], i)) { + return false; + } + } + } + + return true; + } + export function contains(array: T[], value: T): boolean { if (array) { for (const v of array) { @@ -113,11 +130,12 @@ namespace ts { return -1; } - export function countWhere(array: T[], predicate: (x: T) => boolean): number { + export function countWhere(array: T[], predicate: (x: T, i: number) => boolean): number { let count = 0; if (array) { - for (const v of array) { - if (predicate(v)) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (predicate(v, i)) { count++; } } @@ -125,35 +143,151 @@ namespace ts { return count; } - export function filter(array: T[], f: (x: T) => boolean): T[] { + export function filter(array: T[], f: (x: T, i: number) => x is U): U[]; + export function filter(array: T[], f: (x: T, i: number) => boolean): T[]; + export function filter(array: T[], f: (x: T, i: number) => boolean): T[] { let result: T[]; if (array) { result = []; - for (const item of array) { - if (f(item)) { - result.push(item); + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (f(v, i)) { + result.push(v); } } } return result; } - export function map(array: T[], f: (x: T) => U): U[] { + export function map(array: T[], f: (x: T, i: number) => U): U[] { let result: U[]; + if (array) { + result = []; + for (let i = 0; i < array.length; i++) { + const v = array[i]; + result.push(f(v, i)); + } + } + return result; + } + + /** + * Flattens an array containing a mix of array or non-array elements. + * + * @param array The array to flatten. + */ + export function flatten(array: (T | T[])[]): T[] { + let result: T[]; if (array) { result = []; for (const v of array) { - result.push(f(v)); + if (v) { + if (isArray(v)) { + addRange(result, v); + } + else { + result.push(v); + } + } + } + } + + return result; + } + + /** + * Maps an array. If the mapped value is an array, it is spread into the result. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ + export function flatMap(array: T[], mapfn: (x: T, i: number) => U | U[]): U[] { + let result: U[]; + if (array) { + result = []; + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); + if (v) { + if (isArray(v)) { + addRange(result, v); + } + else { + result.push(v); + } + } + } + } + return result; + } + + /** + * Computes the first matching span of elements and returns a tuple of the first span + * and the remaining elements. + */ + export function span(array: T[], f: (x: T, i: number) => boolean): [T[], T[]] { + if (array) { + for (let i = 0; i < array.length; i++) { + if (!f(array[i], i)) { + return [array.slice(0, i), array.slice(i)]; + } } + return [array.slice(0), []]; } + + return undefined; + } + + /** + * Maps contiguous spans of values with the same key. + * + * @param array The array to map. + * @param keyfn A callback used to select the key for an element. + * @param mapfn A callback used to map a contiguous chunk of values to a single value. + */ + export function spanMap(array: T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] { + let result: U[]; + if (array) { + result = []; + const len = array.length; + let previousKey: K; + let key: K; + let start = 0; + let pos = 0; + while (start < len) { + while (pos < len) { + const value = array[pos]; + key = keyfn(value, pos); + if (pos === 0) { + previousKey = key; + } + else if (key !== previousKey) { + break; + } + + pos++; + } + + if (start < pos) { + const v = mapfn(array.slice(start, pos), previousKey, start, pos); + if (v) { + result.push(v); + } + + start = pos; + } + + previousKey = key; + pos++; + } + } + return result; } export function concatenate(array1: T[], array2: T[]): T[] { if (!array2 || !array2.length) return array1; if (!array1 || !array1.length) return array2; - - return array1.concat(array2); + return [...array1, ...array2]; } export function deduplicate(array: T[]): T[] { @@ -169,6 +303,27 @@ namespace ts { return result; } + /** + * Compacts an array, removing any falsey elements. + */ + export function compact(array: T[]): T[] { + let result: T[]; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (result || !v) { + if (!result) { + result = array.slice(0, i); + } + if (v) { + result.push(v); + } + } + } + } + return result || array; + } + export function sum(array: any[], prop: string): number { let result = 0; for (const v of array) { @@ -195,15 +350,31 @@ namespace ts { return true; } + export function firstOrUndefined(array: T[]): T { + return array && array.length > 0 + ? array[0] + : undefined; + } + + export function singleOrUndefined(array: T[]): T { + return array && array.length === 1 + ? array[0] + : undefined; + } + + export function singleOrMany(array: T[]): T | T[] { + return array && array.length === 1 + ? array[0] + : array; + } + /** * Returns the last element of an array if non-empty, undefined otherwise. */ export function lastOrUndefined(array: T[]): T { - if (array.length === 0) { - return undefined; - } - - return array[array.length - 1]; + return array && array.length > 0 + ? array[array.length - 1] + : undefined; } /** @@ -235,14 +406,14 @@ namespace ts { return ~low; } - export function reduceLeft(array: T[], f: (a: T, x: T) => T): T; - export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial: U): U; - export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial?: U): U { + export function reduceLeft(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { if (array) { const count = array.length; if (count > 0) { let pos = 0; - let result: T | U; + let result: T; if (arguments.length <= 2) { result = array[pos]; pos++; @@ -251,22 +422,22 @@ namespace ts { result = initial; } while (pos < count) { - result = f(result, array[pos]); + result = f(result, array[pos], pos); pos++; } - return result; + return result; } } return initial; } - export function reduceRight(array: T[], f: (a: T, x: T) => T): T; - export function reduceRight(array: T[], f: (a: U, x: T) => U, initial: U): U; - export function reduceRight(array: T[], f: (a: U, x: T) => U, initial?: U): U { + export function reduceRight(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { if (array) { let pos = array.length - 1; if (pos >= 0) { - let result: T | U; + let result: T; if (arguments.length <= 2) { result = array[pos]; pos--; @@ -275,10 +446,10 @@ namespace ts { result = initial; } while (pos >= 0) { - result = f(result, array[pos]); + result = f(result, array[pos], pos); pos--; } - return result; + return result; } } return initial; @@ -882,7 +1053,11 @@ namespace ts { this.pos = pos; this.end = end; this.flags = NodeFlags.None; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.excludeTransformFlags = TransformFlags.None; this.parent = undefined; + this.original = undefined; } export let objectAllocator: ObjectAllocator = { @@ -901,10 +1076,13 @@ namespace ts { } export namespace Debug { - const currentAssertionLevel = AssertionLevel.None; + declare var process: any; + declare var require: any; + + let currentAssertionLevel: AssertionLevel; export function shouldAssert(level: AssertionLevel): boolean { - return currentAssertionLevel >= level; + return getCurrentAssertionLevel() >= level; } export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string): void { @@ -921,6 +1099,23 @@ namespace ts { export function fail(message?: string): void { Debug.assert(/*expression*/ false, message); } + + function getCurrentAssertionLevel() { + if (currentAssertionLevel !== undefined) { + return currentAssertionLevel; + } + + if (sys === undefined) { + return AssertionLevel.None; + } + + const developmentMode = /^development$/i.test(getEnvironmentVariable("NODE_ENV")); + currentAssertionLevel = developmentMode + ? AssertionLevel.Normal + : AssertionLevel.None; + + return currentAssertionLevel; + } } export function copyListRemovingItem(item: T, list: T[]) { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 38df2e45d4854..6be1a0d368300 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -95,7 +95,7 @@ namespace ts { // Emit reference in dts, if the file reference was not already emitted if (referencedFile && !contains(emittedReferencedFiles, referencedFile)) { // Add a reference to generated dts file, - // global file reference is added only + // global file reference is added only // - if it is not bundled emit (because otherwise it would be self reference) // - and it is not already added if (writeReferencePath(referencedFile, !isBundledEmit && !addedGlobalFileReference)) { @@ -148,7 +148,7 @@ namespace ts { if (!isBundledEmit && isExternalModule(sourceFile) && sourceFile.moduleAugmentations.length && !resultHasExternalModuleIndicator) { // if file was external module with augmentations - this fact should be preserved in .d.ts as well. - // in case if we didn't write any external module specifiers in .d.ts we need to emit something + // in case if we didn't write any external module specifiers in .d.ts we need to emit something // that will force compiler to think that this file is an external module - 'export {}' is a reasonable choice here. write("export {};"); writeLine(); @@ -376,7 +376,7 @@ namespace ts { const jsDocComments = getJsDocCommentsFromText(declaration, currentText); emitNewLineBeforeLeadingComments(currentLineMap, writer, declaration, jsDocComments); // jsDoc comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, jsDocComments, /*trailingSeparator*/ true, newLine, writeCommentRange); + emitComments(currentText, currentLineMap, writer, jsDocComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeCommentRange); } } @@ -656,12 +656,13 @@ namespace ts { function emitModuleElementDeclarationFlags(node: Node) { // If the node is parented in the current source file we need to emit export declare or just export if (node.parent.kind === SyntaxKind.SourceFile) { + const modifiers = getModifierFlags(node); // If the node is exported - if (node.flags & NodeFlags.Export) { + if (modifiers & ModifierFlags.Export) { write("export "); } - if (node.flags & NodeFlags.Default) { + if (modifiers & ModifierFlags.Default) { write("default "); } else if (node.kind !== SyntaxKind.InterfaceDeclaration && !noDeclare) { @@ -670,21 +671,21 @@ namespace ts { } } - function emitClassMemberDeclarationFlags(flags: NodeFlags) { - if (flags & NodeFlags.Private) { + function emitClassMemberDeclarationFlags(flags: ModifierFlags) { + if (flags & ModifierFlags.Private) { write("private "); } - else if (flags & NodeFlags.Protected) { + else if (flags & ModifierFlags.Protected) { write("protected "); } - if (flags & NodeFlags.Static) { + if (flags & ModifierFlags.Static) { write("static "); } - if (flags & NodeFlags.Readonly) { + if (flags & ModifierFlags.Readonly) { write("readonly "); } - if (flags & NodeFlags.Abstract) { + if (flags & ModifierFlags.Abstract) { write("abstract "); } } @@ -693,7 +694,7 @@ namespace ts { // note usage of writer. methods instead of aliases created, just to make sure we are using // correct writer especially to handle asynchronous alias writing emitJsDocComments(node); - if (node.flags & NodeFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { write("export "); } write("import "); @@ -732,7 +733,7 @@ namespace ts { function writeImportDeclaration(node: ImportDeclaration) { emitJsDocComments(node); - if (node.flags & NodeFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { write("export "); } write("import "); @@ -765,7 +766,7 @@ namespace ts { function emitExternalModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration) { // emitExternalModuleSpecifier is usually called when we emit something in the.d.ts file that will make it an external module (i.e. import/export declarations). - // the only case when it is not true is when we call it to emit correct name for module augmentation - d.ts files with just module augmentations are not considered + // the only case when it is not true is when we call it to emit correct name for module augmentation - d.ts files with just module augmentations are not considered // external modules since they are indistinguishable from script files with ambient modules. To fix this in such d.ts files we'll emit top level 'export {}' // so compiler will treat them as external modules. resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || parent.kind !== SyntaxKind.ModuleDeclaration; @@ -922,7 +923,7 @@ namespace ts { } function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { - return node.parent.kind === SyntaxKind.MethodDeclaration && (node.parent.flags & NodeFlags.Private); + return node.parent.kind === SyntaxKind.MethodDeclaration && hasModifier(node.parent, ModifierFlags.Private); } function emitTypeParameters(typeParameters: TypeParameterDeclaration[]) { @@ -972,7 +973,7 @@ namespace ts { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: - if (node.parent.flags & NodeFlags.Static) { + if (hasModifier(node.parent, ModifierFlags.Static)) { diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; } else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { @@ -1051,7 +1052,7 @@ namespace ts { function emitParameterProperties(constructorDeclaration: ConstructorDeclaration) { if (constructorDeclaration) { forEach(constructorDeclaration.parameters, param => { - if (param.flags & NodeFlags.AccessibilityModifier) { + if (hasModifier(param, ModifierFlags.AccessibilityModifier)) { emitPropertyDeclaration(param); } }); @@ -1060,7 +1061,7 @@ namespace ts { emitJsDocComments(node); emitModuleElementDeclarationFlags(node); - if (node.flags & NodeFlags.Abstract) { + if (hasModifier(node, ModifierFlags.Abstract)) { write("abstract "); } @@ -1110,7 +1111,7 @@ namespace ts { } emitJsDocComments(node); - emitClassMemberDeclarationFlags(node.flags); + emitClassMemberDeclarationFlags(getModifierFlags(node)); emitVariableDeclaration(node); write(";"); writeLine(); @@ -1135,7 +1136,7 @@ namespace ts { if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) && node.parent.kind === SyntaxKind.TypeLiteral) { emitTypeOfVariableDeclarationFromTypeLiteral(node); } - else if (!(node.flags & NodeFlags.Private)) { + else if (!hasModifier(node, ModifierFlags.Private)) { writeTypeOfDeclaration(node, node.type, getVariableDeclarationTypeVisibilityError); } } @@ -1152,7 +1153,7 @@ namespace ts { // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) { // TODO(jfreeman): Deal with computed properties in error reporting. - if (node.flags & NodeFlags.Static) { + if (hasModifier(node, ModifierFlags.Static)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : @@ -1263,9 +1264,9 @@ namespace ts { if (node === accessors.firstAccessor) { emitJsDocComments(accessors.getAccessor); emitJsDocComments(accessors.setAccessor); - emitClassMemberDeclarationFlags(node.flags | (accessors.setAccessor ? 0 : NodeFlags.Readonly)); + emitClassMemberDeclarationFlags(getModifierFlags(node) | (accessors.setAccessor ? 0 : ModifierFlags.Readonly)); writeTextOfNode(currentText, node.name); - if (!(node.flags & NodeFlags.Private)) { + if (!hasModifier(node, ModifierFlags.Private)) { accessorWithTypeAnnotation = node; let type = getTypeAnnotationFromAccessor(node); if (!type) { @@ -1296,7 +1297,7 @@ namespace ts { let diagnosticMessage: DiagnosticMessage; if (accessorWithTypeAnnotation.kind === SyntaxKind.SetAccessor) { // Setters have to have type named and cannot infer it so, the type should always be named - if (accessorWithTypeAnnotation.parent.flags & NodeFlags.Static) { + if (hasModifier(accessorWithTypeAnnotation.parent, ModifierFlags.Static)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? Diagnostics.Parameter_0_of_public_static_property_setter_from_exported_class_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Parameter_0_of_public_static_property_setter_from_exported_class_has_or_is_using_private_name_1; @@ -1314,7 +1315,7 @@ namespace ts { }; } else { - if (accessorWithTypeAnnotation.flags & NodeFlags.Static) { + if (hasModifier(accessorWithTypeAnnotation, ModifierFlags.Static)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Return_type_of_public_static_property_getter_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : @@ -1350,7 +1351,7 @@ namespace ts { emitModuleElementDeclarationFlags(node); } else if (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.Constructor) { - emitClassMemberDeclarationFlags(node.flags); + emitClassMemberDeclarationFlags(getModifierFlags(node)); } if (node.kind === SyntaxKind.FunctionDeclaration) { write("function "); @@ -1380,7 +1381,7 @@ namespace ts { if (node.kind === SyntaxKind.IndexSignature) { // Index signature can have readonly modifier - emitClassMemberDeclarationFlags(node.flags); + emitClassMemberDeclarationFlags(getModifierFlags(node)); write("["); } else { @@ -1411,7 +1412,7 @@ namespace ts { emitType(node.type); } } - else if (node.kind !== SyntaxKind.Constructor && !(node.flags & NodeFlags.Private)) { + else if (node.kind !== SyntaxKind.Constructor && !hasModifier(node, ModifierFlags.Private)) { writeReturnTypeAtSignature(node, getReturnTypeVisibilityError); } @@ -1448,7 +1449,7 @@ namespace ts { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: - if (node.flags & NodeFlags.Static) { + if (hasModifier(node, ModifierFlags.Static)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : @@ -1514,7 +1515,7 @@ namespace ts { node.parent.parent.kind === SyntaxKind.TypeLiteral) { emitTypeOfVariableDeclarationFromTypeLiteral(node); } - else if (!(node.parent.flags & NodeFlags.Private)) { + else if (!hasModifier(node.parent, ModifierFlags.Private)) { writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError); } @@ -1550,7 +1551,7 @@ namespace ts { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: - if (node.parent.flags & NodeFlags.Static) { + if (hasModifier(node.parent, ModifierFlags.Static)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 596e5335072c4..9fd19cb20030d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2740,6 +2740,10 @@ "category": "Message", "code": 6128 }, + "Use the legacy emitter instead of the transforming emitter.": { + "category": "Message", + "code": 6129 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 254b8d7e61b17..8bcdd4c820120 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -406,7 +406,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) { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer) { if (node.locals && hasProperty(node.locals, name)) { // 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)) { @@ -962,7 +962,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function emitLiteral(node: LiteralExpression | TemplateLiteralFragment) { - const text = getLiteralText(node); + const text = getLiteralText(node, currentSourceFile, languageVersion); if ((compilerOptions.sourceMap || compilerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { writer.writeLiteral(text); @@ -976,43 +976,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } } - function getLiteralText(node: LiteralExpression | TemplateLiteralFragment) { - // Any template literal or string literal with an extended escape - // (e.g. "\u{0067}") will need to be downleveled as a escaped string literal. - if (languageVersion < ScriptTarget.ES6 && (isTemplateLiteralKind(node.kind) || node.hasExtendedUnicodeEscape)) { - return getQuotedEscapedLiteralText('"', node.text, '"'); - } - - // If we don't need to downlevel and we can reach the original source text using - // the node's parent reference, then simply get the text as it was originally written. - if (node.parent) { - return getTextOfNodeFromSourceText(currentText, node); - } - - // If we can't reach the original source text, use the canonical form if it's a number, - // or an escaped quoted form of the original text if it's string-like. - switch (node.kind) { - case SyntaxKind.StringLiteral: - return getQuotedEscapedLiteralText('"', node.text, '"'); - case SyntaxKind.NoSubstitutionTemplateLiteral: - return getQuotedEscapedLiteralText("`", node.text, "`"); - case SyntaxKind.TemplateHead: - return getQuotedEscapedLiteralText("`", node.text, "${"); - case SyntaxKind.TemplateMiddle: - return getQuotedEscapedLiteralText("}", node.text, "${"); - case SyntaxKind.TemplateTail: - return getQuotedEscapedLiteralText("}", node.text, "`"); - case SyntaxKind.NumericLiteral: - return node.text; - } - - Debug.fail(`Literal kind '${node.kind}' not accounted for.`); - } - - function getQuotedEscapedLiteralText(leftQuote: string, text: string, rightQuote: string) { - return leftQuote + escapeNonAsciiCharacters(escapeString(text)) + rightQuote; - } - function emitDownlevelRawTemplateLiteral(node: LiteralExpression) { // Find original source text, since we need to emit the raw strings of the tagged template. // The raw strings contain the (escaped) strings of what the user wrote. @@ -1087,7 +1050,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return; } - const emitOuterParens = isExpression(node.parent) + const emitOuterParens = node.parent.kind !== SyntaxKind.ArrowFunction + && isPartOfExpression(node.parent) && templateNeedsParens(node, node.parent); if (emitOuterParens) { @@ -1633,7 +1597,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return; } } - else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { + else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.SelfReferenceInDecoratedClass) { // Due to the emit for class decorators, any reference to the class from inside of the class body // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind // behavior of class names in ES6. @@ -2019,7 +1983,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (multiLine) { decreaseIndent(); - writeLine(); } write(")"); @@ -2293,41 +2256,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge emit(node.right); } - function emitQualifiedNameAsExpression(node: QualifiedName, useFallback: boolean) { + function emitQualifiedNameAsExpression(node: QualifiedName, useFallback: boolean, location?: Node) { if (node.left.kind === SyntaxKind.Identifier) { - emitEntityNameAsExpression(node.left, useFallback); + emitEntityNameAsExpression(node.left, useFallback, location); } else if (useFallback) { const temp = createAndRecordTempVariable(TempFlags.Auto); write("("); emitNodeWithoutSourceMap(temp); write(" = "); - emitEntityNameAsExpression(node.left, /*useFallback*/ true); + emitEntityNameAsExpression(node.left, /*useFallback*/ true, location); write(") && "); emitNodeWithoutSourceMap(temp); } else { - emitEntityNameAsExpression(node.left, /*useFallback*/ false); + emitEntityNameAsExpression(node.left, /*useFallback*/ false, location); } write("."); emit(node.right); } - function emitEntityNameAsExpression(node: EntityName | Expression, useFallback: boolean) { + function emitEntityNameAsExpression(node: EntityName | Expression, useFallback: boolean, location?: Node) { switch (node.kind) { case SyntaxKind.Identifier: + let name = node; + if (location) { + // to resolve the expression to the correct container, create a shallow + // clone of `node` with a new parent. + name = clone(name); + name.parent = location; + } + if (useFallback) { write("typeof "); - emitExpressionIdentifier(node); + emitExpressionIdentifier(name); write(" !== 'undefined' && "); } - emitExpressionIdentifier(node); + emitExpressionIdentifier(name); break; case SyntaxKind.QualifiedName: - emitQualifiedNameAsExpression(node, useFallback); + emitQualifiedNameAsExpression(node, useFallback, location); break; default: @@ -2358,7 +2329,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return forEach(elements, e => e.kind === SyntaxKind.SpreadElementExpression); } - function skipParentheses(node: Expression): Expression { + function skipParenthesesAndAssertions(node: Expression): Expression { while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression || @@ -2368,6 +2339,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return node; } + function skipAssertions(node: Expression): Expression { + while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; + } + return node; + } + function emitCallTarget(node: Expression): Expression { if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { emit(node); @@ -2385,7 +2363,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function emitCallWithSpread(node: CallExpression) { let target: Expression; - const expr = skipParentheses(node.expression); + const expr = skipParenthesesAndAssertions(node.expression); if (expr.kind === SyntaxKind.PropertyAccessExpression) { // Target will be emitted as "this" argument target = emitCallTarget((expr).expression); @@ -2459,7 +2437,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge superCall = true; } else { - superCall = isSuperPropertyOrElementAccess(expression); + superCall = isSuperProperty(expression); isAsyncMethodWithSuper = superCall && isInAsyncMethodWithSuperInES6(node); emit(expression); } @@ -2703,7 +2681,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge let current = getRootDeclaration(node).parent; while (current) { if (current.kind === SyntaxKind.SourceFile) { - return !isExported || ((getCombinedNodeFlags(node) & NodeFlags.Export) !== 0); + return !isExported || ((getCombinedModifierFlags(node) & ModifierFlags.Export) !== 0); } else if (isDeclaration(current)) { return false; @@ -2821,7 +2799,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function synthesizedNodeStartsOnNewLine(node: Node) { - return nodeIsSynthesized(node) && (node).startsOnNewLine; + return nodeIsSynthesized(node) && node.startsOnNewLine; } function emitConditionalExpression(node: ConditionalExpression) { @@ -3484,8 +3462,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // we can't reuse 'arr' because it might be modified within the body of the loop. const counter = createTempVariable(TempFlags._i); const rhsReference = createSynthesizedNode(SyntaxKind.Identifier) as Identifier; - rhsReference.text = node.expression.kind === SyntaxKind.Identifier ? - makeUniqueName((node.expression).text) : + const expressionWithoutAssertions = skipAssertions(node.expression); + rhsReference.text = expressionWithoutAssertions.kind === SyntaxKind.Identifier ? + makeUniqueName((expressionWithoutAssertions).text) : makeTempVariableName(TempFlags.Auto); // This is the let keyword for the counter and rhsReference. The let keyword for @@ -3800,7 +3779,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function emitModuleMemberName(node: Declaration) { emitStart(node.name); - if (getCombinedNodeFlags(node) & NodeFlags.Export) { + if (getCombinedModifierFlags(node) & ModifierFlags.Export) { const container = getContainingModule(node); if (container) { write(getGeneratedNameForNode(container)); @@ -3824,7 +3803,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function emitEs6ExportDefaultCompat(node: Node) { if (node.parent.kind === SyntaxKind.SourceFile) { - Debug.assert(!!(node.flags & NodeFlags.Default) || node.kind === SyntaxKind.ExportAssignment); + Debug.assert(hasModifier(node, ModifierFlags.Default) || node.kind === SyntaxKind.ExportAssignment); // only allow export default at a source file level if (modulekind === ModuleKind.CommonJS || modulekind === ModuleKind.AMD || modulekind === ModuleKind.UMD) { if (!isEs6Module) { @@ -3843,7 +3822,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function emitExportMemberAssignment(node: FunctionLikeDeclaration | ClassDeclaration) { - if (node.flags & NodeFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { writeLine(); emitStart(node); @@ -3852,7 +3831,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // emit export default as // export("default", ) write(`${exportFunctionForFile}("`); - if (node.flags & NodeFlags.Default) { + if (hasModifier(node, ModifierFlags.Default)) { write("default"); } else { @@ -3863,7 +3842,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge write(")"); } else { - if (node.flags & NodeFlags.Default) { + if (hasModifier(node, ModifierFlags.Default)) { emitEs6ExportDefaultCompat(node); if (languageVersion === ScriptTarget.ES3) { write('exports["default"]'); @@ -3957,7 +3936,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge write(" = "); emit(value); }); - emitEnd(nodeForSourceMap, /*stopOverridingSpan*/true); + emitEnd(nodeForSourceMap); + sourceMap.stopOverridingSpan(); if (exportChanged) { write(")"); @@ -3994,7 +3974,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // because actual variable declarations are hoisted let canDefineTempVariablesInPlace = false; if (root.kind === SyntaxKind.VariableDeclaration) { - const isExported = getCombinedNodeFlags(root) & NodeFlags.Export; + const isExported = getCombinedModifierFlags(root) & ModifierFlags.Export; const isSourceLevelForSystemModuleKind = shouldHoistDeclarationInSystemJsModule(root); canDefineTempVariablesInPlace = !isExported && !isSourceLevelForSystemModuleKind; } @@ -4241,7 +4221,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function emitVariableDeclaration(node: VariableDeclaration) { if (isBindingPattern(node.name)) { - const isExported = getCombinedNodeFlags(node) & NodeFlags.Export; + const isExported = getCombinedModifierFlags(node) & ModifierFlags.Export; if (languageVersion >= ScriptTarget.ES6 && (!isExported || modulekind === ModuleKind.ES6)) { // emit ES6 destructuring only if target module is ES6 or variable is not exported // exported variables in CJS/AMD are prefixed with 'exports.' so result javascript { exports.toString } = 1; is illegal @@ -4355,7 +4335,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function isES6ExportedDeclaration(node: Node) { - return !!(node.flags & NodeFlags.Export) && + return hasModifier(node, ModifierFlags.Export) && modulekind === ModuleKind.ES6 && node.parent.kind === SyntaxKind.SourceFile; } @@ -4363,7 +4343,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function emitVariableStatement(node: VariableStatement) { let startIsEmitted = false; - if (node.flags & NodeFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { if (isES6ExportedDeclaration(node)) { // Exported ES6 module member write("export "); @@ -4392,7 +4372,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function shouldEmitLeadingAndTrailingCommentsForVariableStatement(node: VariableStatement) { // If we're not exporting the variables, there's nothing special here. // Always emit comments for these nodes. - if (!(node.flags & NodeFlags.Export)) { + if (!hasModifier(node, ModifierFlags.Export)) { return true; } @@ -4487,7 +4467,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function emitRestParameter(node: FunctionLikeDeclaration) { - if (languageVersion < ScriptTarget.ES6 && hasRestParameter(node)) { + if (languageVersion < ScriptTarget.ES6 && hasDeclaredRestParameter(node)) { const restIndex = node.parameters.length - 1; const restParam = node.parameters[restIndex]; @@ -4523,7 +4503,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge writeLine(); emitStart(restParam); emitNodeWithCommentsAndWithoutSourcemap(restParam.name); - write("[" + tempName + " - " + restIndex + "] = arguments[" + tempName + "];"); + write(`[${tempName} - ${restIndex}] = arguments[${tempName}];`); emitEnd(restParam); decreaseIndent(); writeLine(); @@ -4600,7 +4580,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (!shouldEmitAsArrowFunction(node)) { if (isES6ExportedDeclaration(node)) { write("export "); - if (node.flags & NodeFlags.Default) { + if (hasModifier(node, ModifierFlags.Default)) { write("default "); } } @@ -4644,7 +4624,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (node) { const parameters = node.parameters; const skipCount = node.parameters.length && (node.parameters[0].name).text === "this" ? 1 : 0; - const omitCount = languageVersion < ScriptTarget.ES6 && hasRestParameter(node) ? 1 : 0; + const omitCount = languageVersion < ScriptTarget.ES6 && hasDeclaredRestParameter(node) ? 1 : 0; emitList(parameters, skipCount, parameters.length - omitCount - skipCount, /*multiLine*/ false, /*trailingComma*/ false); } write(")"); @@ -4856,7 +4836,7 @@ const _super = (function (geti, seti) { } function emitExpressionFunctionBody(node: FunctionLikeDeclaration, body: Expression) { - if (languageVersion < ScriptTarget.ES6 || node.flags & NodeFlags.Async) { + if (languageVersion < ScriptTarget.ES6 || hasModifier(node, ModifierFlags.Async)) { emitDownLevelExpressionFunctionBody(node, body); return; } @@ -4978,7 +4958,7 @@ const _super = (function (geti, seti) { function emitParameterPropertyAssignments(node: ConstructorDeclaration) { forEach(node.parameters, param => { - if (param.flags & NodeFlags.AccessibilityModifier) { + if (hasModifier(param, ModifierFlags.AccessibilityModifier)) { writeLine(); emitStart(param); emitStart(param.name); @@ -5014,7 +4994,7 @@ const _super = (function (geti, seti) { function getInitializedProperties(node: ClassLikeDeclaration, isStatic: boolean) { const properties: PropertyDeclaration[] = []; for (const member of node.members) { - if (member.kind === SyntaxKind.PropertyDeclaration && isStatic === ((member.flags & NodeFlags.Static) !== 0) && (member).initializer) { + if (member.kind === SyntaxKind.PropertyDeclaration && isStatic === hasModifier(member, ModifierFlags.Static) && (member).initializer) { properties.push(member); } } @@ -5037,7 +5017,7 @@ const _super = (function (geti, seti) { emit(receiver); } else { - if (property.flags & NodeFlags.Static) { + if (hasModifier(property, ModifierFlags.Static)) { emitDeclarationName(node); } else { @@ -5139,7 +5119,7 @@ const _super = (function (geti, seti) { writeLine(); emitLeadingComments(member); emitStart(member); - if (member.flags & NodeFlags.Static) { + if (hasModifier(member, ModifierFlags.Static)) { write("static "); } @@ -5197,7 +5177,7 @@ const _super = (function (geti, seti) { emitCommentsOnNotEmittedNode(member); } // Check if there is any non-static property assignment - if (member.kind === SyntaxKind.PropertyDeclaration && (member).initializer && (member.flags & NodeFlags.Static) === 0) { + if (member.kind === SyntaxKind.PropertyDeclaration && (member).initializer && !hasModifier(member, ModifierFlags.Static)) { hasInstancePropertyWithInitializer = true; } }); @@ -5409,16 +5389,16 @@ const _super = (function (geti, seti) { // // NOTE: we reuse the same rewriting logic for cases when targeting ES6 and module kind is System. - // Because of hoisting top level class declaration need to be emitted as class expressions. + // Because of hoisting top level class declaration need to be emitted as class expressions. // Double bind case is only required if node is decorated. - if (isDecorated && resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithBodyScopedClassBinding) { + if (isDecorated && resolver.getNodeCheckFlags(node) & NodeCheckFlags.DecoratedClassWithSelfReference) { decoratedClassAlias = unescapeIdentifier(makeUniqueName(node.name ? node.name.text : "default")); decoratedClassAliases[getNodeId(node)] = decoratedClassAlias; write(`let ${decoratedClassAlias};`); writeLine(); } - if (isES6ExportedDeclaration(node) && !(node.flags & NodeFlags.Default)) { + if (isES6ExportedDeclaration(node) && !hasModifier(node, ModifierFlags.Default)) { write("export "); } @@ -5434,7 +5414,7 @@ const _super = (function (geti, seti) { } else if (isES6ExportedDeclaration(node)) { write("export "); - if (node.flags & NodeFlags.Default) { + if (hasModifier(node, ModifierFlags.Default)) { write("default "); } } @@ -5468,7 +5448,7 @@ const _super = (function (geti, seti) { // emit name if // - node has a name // - this is default export with static initializers - if (node.name || (node.flags & NodeFlags.Default && (staticProperties.length > 0 || modulekind !== ModuleKind.ES6) && !rewriteAsClassExpression)) { + if (node.name || (hasModifier(node, ModifierFlags.Default) && (staticProperties.length > 0 || modulekind !== ModuleKind.ES6) && !rewriteAsClassExpression)) { write(" "); emitDeclarationName(node); } @@ -5517,7 +5497,7 @@ const _super = (function (geti, seti) { emitDecoratorsOfClass(node, decoratedClassAlias); } - if (!(node.flags & NodeFlags.Export)) { + if (!hasModifier(node, ModifierFlags.Export)) { return; } if (modulekind !== ModuleKind.ES6) { @@ -5526,7 +5506,7 @@ const _super = (function (geti, seti) { else { // If this is an exported class, but not on the top level (i.e. on an internal // module), export it - if (node.flags & NodeFlags.Default) { + if (hasModifier(node, ModifierFlags.Default)) { // if this is a top level default export of decorated class, write the export after the declaration. if (isDecorated) { writeLine(); @@ -5557,6 +5537,18 @@ const _super = (function (geti, seti) { write(" = "); } + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const isClassExpressionWithStaticProperties = staticProperties.length > 0 && node.kind === SyntaxKind.ClassExpression; + let tempVariable: Identifier; + + if (isClassExpressionWithStaticProperties) { + tempVariable = createAndRecordTempVariable(TempFlags.Auto); + write("("); + increaseIndent(); + emit(tempVariable); + write(" = "); + } + write("(function ("); const baseTypeNode = getClassExtendsHeritageClauseElement(node); if (baseTypeNode) { @@ -5586,9 +5578,6 @@ const _super = (function (geti, seti) { writeLine(); emitConstructor(node, baseTypeNode); emitMemberFunctionsForES5AndLower(node); - emitPropertyDeclarations(node, getInitializedProperties(node, /*isStatic*/ true)); - writeLine(); - emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); writeLine(); emitToken(SyntaxKind.CloseBraceToken, node.members.end, () => { write("return "); @@ -5615,7 +5604,22 @@ const _super = (function (geti, seti) { write("))"); if (node.kind === SyntaxKind.ClassDeclaration) { write(";"); + emitPropertyDeclarations(node, staticProperties); + emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); } + else if (isClassExpressionWithStaticProperties) { + for (const property of staticProperties) { + write(","); + writeLine(); + emitPropertyDeclaration(node, property, /*receiver*/ tempVariable, /*isExpression*/ true); + } + write(","); + writeLine(); + emit(tempVariable); + decreaseIndent(); + write(")"); + } + emitEnd(node); if (node.kind === SyntaxKind.ClassDeclaration) { @@ -5625,14 +5629,14 @@ const _super = (function (geti, seti) { function emitClassMemberPrefix(node: ClassLikeDeclaration, member: Node) { emitDeclarationName(node); - if (!(member.flags & NodeFlags.Static)) { + if (!hasModifier(member, ModifierFlags.Static)) { write(".prototype"); } } function emitDecoratorsOfClass(node: ClassLikeDeclaration, decoratedClassAlias: string) { emitDecoratorsOfMembers(node, /*staticFlag*/ 0); - emitDecoratorsOfMembers(node, NodeFlags.Static); + emitDecoratorsOfMembers(node, ModifierFlags.Static); emitDecoratorsOfConstructor(node, decoratedClassAlias); } @@ -5686,10 +5690,10 @@ const _super = (function (geti, seti) { writeLine(); } - function emitDecoratorsOfMembers(node: ClassLikeDeclaration, staticFlag: NodeFlags) { + function emitDecoratorsOfMembers(node: ClassLikeDeclaration, staticFlag: ModifierFlags) { for (const member of node.members) { // only emit members in the correct group - if ((member.flags & NodeFlags.Static) !== staticFlag) { + if ((getModifierFlags(member) & ModifierFlags.Static) !== staticFlag) { continue; } @@ -5968,27 +5972,26 @@ const _super = (function (geti, seti) { /** Serializes a TypeReferenceNode to an appropriate JS constructor value. Used by the __metadata decorator. */ function emitSerializedTypeReferenceNode(node: TypeReferenceNode) { let location: Node = node.parent; - while (isDeclaration(location) || isTypeNode(location)) { + while (isDeclaration(location) || isPartOfTypeNode(location)) { location = location.parent; } // Clone the type name and parent it to a location outside of the current declaration. - const typeName = cloneEntityName(node.typeName, location); - const result = resolver.getTypeReferenceSerializationKind(typeName); + const result = resolver.getTypeReferenceSerializationKind(node.typeName, location); switch (result) { case TypeReferenceSerializationKind.Unknown: let temp = createAndRecordTempVariable(TempFlags.Auto); write("(typeof ("); emitNodeWithoutSourceMap(temp); write(" = "); - emitEntityNameAsExpression(typeName, /*useFallback*/ true); + emitEntityNameAsExpression(node.typeName, /*useFallback*/ true, location); write(") === 'function' && "); emitNodeWithoutSourceMap(temp); write(") || Object"); break; case TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue: - emitEntityNameAsExpression(typeName, /*useFallback*/ false); + emitEntityNameAsExpression(node.typeName, /*useFallback*/ false, location); break; case TypeReferenceSerializationKind.VoidType: @@ -6150,7 +6153,7 @@ const _super = (function (geti, seti) { // do not emit var if variable was already hoisted const isES6ExportedEnum = isES6ExportedDeclaration(node); - if (!(node.flags & NodeFlags.Export) || (isES6ExportedEnum && isFirstDeclarationOfKind(node, node.symbol && node.symbol.declarations, SyntaxKind.EnumDeclaration))) { + if (!hasModifier(node, ModifierFlags.Export) || (isES6ExportedEnum && isFirstDeclarationOfKind(node, node.symbol && node.symbol.declarations, SyntaxKind.EnumDeclaration))) { emitStart(node); if (isES6ExportedEnum) { write("export "); @@ -6179,7 +6182,7 @@ const _super = (function (geti, seti) { emitModuleMemberName(node); write(" = {}));"); emitEnd(node); - if (!isES6ExportedDeclaration(node) && node.flags & NodeFlags.Export && !shouldHoistDeclarationInSystemJsModule(node)) { + if (!isES6ExportedDeclaration(node) && hasModifier(node, ModifierFlags.Export) && !shouldHoistDeclarationInSystemJsModule(node)) { // do not emit var if variable was already hoisted writeLine(); emitStart(node); @@ -6191,7 +6194,7 @@ const _super = (function (geti, seti) { write(";"); } if (modulekind !== ModuleKind.ES6 && node.parent === currentSourceFile) { - if (modulekind === ModuleKind.System && (node.flags & NodeFlags.Export)) { + if (modulekind === ModuleKind.System && hasModifier(node, ModifierFlags.Export)) { // write the call to exporter for enum writeLine(); write(`${exportFunctionForFile}("`); @@ -6313,7 +6316,7 @@ const _super = (function (geti, seti) { } write(")("); // write moduleDecl = containingModule.m only if it is not exported es6 module member - if ((node.flags & NodeFlags.Export) && !isES6ExportedDeclaration(node)) { + if (hasModifier(node, ModifierFlags.Export) && !isES6ExportedDeclaration(node)) { emit(node.name); write(" = "); } @@ -6323,7 +6326,7 @@ const _super = (function (geti, seti) { write(" = {}));"); emitEnd(node); if (!isES6ExportedDeclaration(node) && node.name.kind === SyntaxKind.Identifier && node.parent === currentSourceFile) { - if (modulekind === ModuleKind.System && (node.flags & NodeFlags.Export)) { + if (modulekind === ModuleKind.System && hasModifier(node, ModifierFlags.Export)) { writeLine(); write(`${exportFunctionForFile}("`); emitDeclarationName(node); @@ -6435,7 +6438,7 @@ const _super = (function (geti, seti) { function emitExternalImportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration) { if (contains(externalImports, node)) { - const isExportedImport = node.kind === SyntaxKind.ImportEqualsDeclaration && (node.flags & NodeFlags.Export) !== 0; + const isExportedImport = node.kind === SyntaxKind.ImportEqualsDeclaration && hasModifier(node, ModifierFlags.Export); const namespaceDeclaration = getNamespaceDeclarationNode(node); const varOrConst = (languageVersion <= ScriptTarget.ES5) ? "var " : "const "; @@ -6525,7 +6528,7 @@ const _super = (function (geti, seti) { write("export "); write("var "); } - else if (!(node.flags & NodeFlags.Export)) { + else if (!hasModifier(node, ModifierFlags.Export)) { write("var "); } } @@ -6770,7 +6773,8 @@ const _super = (function (geti, seti) { } const moduleName = getExternalModuleName(importNode); if (moduleName.kind === SyntaxKind.StringLiteral) { - return tryRenameExternalModule(moduleName) || getLiteralText(moduleName); + return tryRenameExternalModule(moduleName) + || getLiteralText(moduleName, currentSourceFile, languageVersion); } return undefined; @@ -6917,7 +6921,7 @@ const _super = (function (geti, seti) { function writeExportedName(node: Identifier | Declaration): void { // do not record default exports // they are local to module and never overwritten (explicitly skipped) by star export - if (node.kind !== SyntaxKind.Identifier && node.flags & NodeFlags.Default) { + if (node.kind !== SyntaxKind.Identifier && hasModifier(node, ModifierFlags.Default)) { return; } @@ -6989,8 +6993,8 @@ const _super = (function (geti, seti) { emit(local); } - const flags = getCombinedNodeFlags(local.kind === SyntaxKind.Identifier ? local.parent : local); - if (flags & NodeFlags.Export) { + const flags = getCombinedModifierFlags(local.kind === SyntaxKind.Identifier ? local.parent : local); + if (flags & ModifierFlags.Export) { if (!exportedDeclarations) { exportedDeclarations = []; } @@ -7005,7 +7009,7 @@ const _super = (function (geti, seti) { writeLine(); emit(f); - if (f.flags & NodeFlags.Export) { + if (hasModifier(f, ModifierFlags.Export)) { if (!exportedDeclarations) { exportedDeclarations = []; } @@ -7017,7 +7021,7 @@ const _super = (function (geti, seti) { return exportedDeclarations; function visit(node: Node): void { - if (node.flags & NodeFlags.Ambient) { + if (hasModifier(node, ModifierFlags.Ambient)) { return; } @@ -7786,7 +7790,7 @@ const _super = (function (geti, seti) { function emitNodeConsideringCommentsOption(node: Node, emitNodeConsideringSourcemap: (node: Node) => void): void { if (node) { - if (node.flags & NodeFlags.Ambient) { + if (hasModifier(node, ModifierFlags.Ambient)) { return emitCommentsOnNotEmittedNode(node); } @@ -8158,7 +8162,7 @@ const _super = (function (geti, seti) { emitNewLineBeforeLeadingComments(currentLineMap, writer, node, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitTrailingComments(node: Node) { @@ -8170,7 +8174,7 @@ const _super = (function (geti, seti) { const trailingComments = getTrailingCommentsToEmit(node); // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ false, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); } /** @@ -8185,8 +8189,8 @@ const _super = (function (geti, seti) { const trailingComments = getTrailingCommentRanges(currentText, pos); - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment); + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitLeadingCommentsOfPositionWorker(pos: number) { @@ -8207,7 +8211,7 @@ const _super = (function (geti, seti) { emitNewLineBeforeLeadingComments(currentLineMap, writer, { pos: pos, end: pos }, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts new file mode 100644 index 0000000000000..067fc16cc53f1 --- /dev/null +++ b/src/compiler/factory.ts @@ -0,0 +1,1823 @@ +/// +/// + +/* @internal */ +namespace ts { + const synthesizedLocation: TextRange = { pos: -1, end: -1 }; + + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { + const ConstructorForKind = kind === SyntaxKind.SourceFile + ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) + : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); + + const node = location + ? new ConstructorForKind(kind, location.pos, location.end) + : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); + + if (flags) { + node.flags = flags; + } + + return node; + } + + export function createNodeArray(elements?: T[], location?: TextRange, hasTrailingComma?: boolean): NodeArray { + if (elements) { + if (isNodeArray(elements)) { + return elements; + } + } + else { + elements = []; + } + + const array = >elements; + if (location) { + array.pos = location.pos; + array.end = location.end; + } + else { + array.pos = -1; + array.end = -1; + } + + if (hasTrailingComma) { + array.hasTrailingComma = true; + } + + return array; + } + + export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { + const node = createNode(kind, /*location*/ undefined); + node.startsOnNewLine = startsOnNewLine; + return node; + } + + export function createSynthesizedNodeArray(elements?: T[]): NodeArray { + return createNodeArray(elements, /*location*/ undefined); + } + + /** + * Creates a shallow, memberwise clone of a node with no source map location. + */ + export function getSynthesizedClone(node: T): T { + // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of + // the original node. We also need to exclude specific properties and only include own- + // properties (to skip members already defined on the shared prototype). + const clone = createNode(node.kind, /*location*/ undefined); + clone.flags = node.flags; + clone.original = node; + + for (const key in node) { + if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { + continue; + } + + (clone)[key] = (node)[key]; + } + + return clone; + } + + /** + * Creates a shallow, memberwise clone of a node for mutation. + */ + export function getMutableClone(node: T): T { + const clone = getSynthesizedClone(node); + clone.pos = node.pos; + clone.end = node.end; + clone.parent = node.parent; + return clone; + } + + /** + * Creates a shallow, memberwise clone of a node at the specified source map location. + */ + export function getRelocatedClone(node: T, location: TextRange): T { + const clone = getSynthesizedClone(node); + clone.pos = location.pos; + clone.end = location.end; + return clone; + } + + /** + * Gets a clone of a node with a unique node ID. + */ + export function getUniqueClone(node: T): T { + const clone = getMutableClone(node); + clone.id = undefined; + getNodeId(clone); + return clone; + } + + // Literals + + export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange): StringLiteral; + export function createLiteral(value: string, location?: TextRange): StringLiteral; + export function createLiteral(value: number, location?: TextRange): LiteralExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; + export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange): PrimaryExpression { + if (typeof value === "number") { + const node = createNode(SyntaxKind.NumericLiteral, location); + node.text = value.toString(); + return node; + } + else if (typeof value === "boolean") { + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location); + } + else if (typeof value === "string") { + const node = createNode(SyntaxKind.StringLiteral, location); + node.text = value; + return node; + } + else { + const node = createNode(SyntaxKind.StringLiteral, location); + node.textSourceNode = value; + node.text = value.text; + return node; + } + } + + // Identifiers + + export function createIdentifier(text: string, location?: TextRange): Identifier { + const node = createNode(SyntaxKind.Identifier, location); + node.text = escapeIdentifier(text); + node.originalKeywordKind = stringToToken(text); + return node; + } + + export function createTempVariable(recordTempVariable: (node: Identifier) => void, location?: TextRange): Identifier { + const name = createNode(SyntaxKind.Identifier, location); + name.autoGenerateKind = GeneratedIdentifierKind.Auto; + getNodeId(name); + if (recordTempVariable) { + recordTempVariable(name); + } + return name; + } + + export function createLoopVariable(location?: TextRange): Identifier { + const name = createNode(SyntaxKind.Identifier, location); + name.autoGenerateKind = GeneratedIdentifierKind.Loop; + getNodeId(name); + return name; + } + + export function createUniqueName(text: string, location?: TextRange): Identifier { + const name = createNode(SyntaxKind.Identifier, location); + name.text = text; + name.autoGenerateKind = GeneratedIdentifierKind.Unique; + getNodeId(name); + return name; + } + + export function getGeneratedNameForNode(node: Node, location?: TextRange): Identifier { + const name = createNode(SyntaxKind.Identifier, location); + name.autoGenerateKind = GeneratedIdentifierKind.Node; + name.original = node; + getNodeId(name); + return name; + } + + // Reserved words + + export function createSuper() { + const node = createNode(SyntaxKind.SuperKeyword); + return node; + } + + export function createThis(location?: TextRange) { + const node = createNode(SyntaxKind.ThisKeyword, location); + return node; + } + + export function createNull() { + const node = createNode(SyntaxKind.NullKeyword); + return node; + } + + // Names + + export function createComputedPropertyName(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ComputedPropertyName, location); + node.expression = expression; + return node; + } + + // Type members + + export function createMethod(modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.MethodDeclaration, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.body = body; + return node; + } + + export function createConstructor(parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.Constructor, location); + node.decorators = undefined; + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createGetAccessor(modifiers: Modifier[], name: string | PropertyName, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.GetAccessor, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(); + node.body = body; + return node; + } + + export function createSetAccessor(modifiers: Modifier[], name: string | PropertyName, parameter: ParameterDeclaration, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.SetAccessor, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray([parameter]); + node.body = body; + return node; + } + + export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.Parameter, location); + node.decorators = undefined; + node.modifiers = undefined; + node.dotDotDotToken = undefined; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.questionToken = undefined; + node.type = undefined; + node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; + return node; + } + + // Expression + + export function createArrayLiteral(elements?: Expression[], location?: TextRange, multiLine?: boolean) { + const node = createNode(SyntaxKind.ArrayLiteralExpression, location); + node.elements = parenthesizeListElements(createNodeArray(elements)); + if (multiLine) { + node.multiLine = multiLine; + } + return node; + } + + export function createObjectLiteral(properties?: ObjectLiteralElement[], location?: TextRange, multiLine?: boolean) { + const node = createNode(SyntaxKind.ObjectLiteralExpression, location); + node.properties = createNodeArray(properties); + if (multiLine) { + node.multiLine = multiLine; + } + return node; + } + + export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAccessExpression, location); + node.expression = parenthesizeForAccess(expression); + node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); + node.name = typeof name === "string" ? createIdentifier(name) : name; + return node; + } + + export function createElementAccess(expression: Expression, index: number | Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ElementAccessExpression, location); + node.expression = parenthesizeForAccess(expression); + node.argumentExpression = typeof index === "number" ? createLiteral(index) : index; + return node; + } + + export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.CallExpression, location); + node.expression = parenthesizeForAccess(expression); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; + } + + export function createNew(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.NewExpression, location); + node.expression = parenthesizeForNew(expression); + node.arguments = argumentsArray + ? parenthesizeListElements(createNodeArray(argumentsArray)) + : undefined; + return node; + } + + export function createParen(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ParenthesizedExpression, location); + node.expression = expression; + return node; + } + + export function createFunctionExpression(asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange, original?: Node) { + const node = createNode(SyntaxKind.FunctionExpression, location); + node.modifiers = undefined; + node.asteriskToken = asteriskToken; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + if (original) { + node.original = original; + } + + return node; + } + + export function createArrowFunction(parameters: ParameterDeclaration[], body: Expression | Block, location?: TextRange) { + const node = createNode(SyntaxKind.ArrowFunction, location); + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.equalsGreaterThanToken = createNode(SyntaxKind.EqualsGreaterThanToken); + node.body = parenthesizeConciseBody(body); + return node; + } + + export function createTypeOf(expression: Expression) { + const node = createNode(SyntaxKind.TypeOfExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function createVoid(expression: Expression) { + const node = createNode(SyntaxKind.VoidExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function createPrefix(operator: SyntaxKind, operand: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.PrefixUnaryExpression, location); + node.operator = operator; + node.operand = parenthesizePrefixOperand(operand); + return node; + } + + export function createPostfix(operand: Expression, operator: SyntaxKind, location?: TextRange) { + const node = createNode(SyntaxKind.PostfixUnaryExpression, location); + node.operand = parenthesizePostfixOperand(operand); + node.operator = operator; + return node; + } + + export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.BinaryExpression, location); + node.left = parenthesizeBinaryOperand(operator, left, /*isLeftSideOfBinary*/ true, /*leftOperand*/ undefined); + node.operatorToken = createSynthesizedNode(operator); + node.right = parenthesizeBinaryOperand(operator, right, /*isLeftSideOfBinary*/ false, node.left); + return node; + } + + export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression) { + const node = createNode(SyntaxKind.ConditionalExpression); + node.condition = condition; + node.questionToken = createSynthesizedNode(SyntaxKind.QualifiedName); + node.whenTrue = whenTrue; + node.colonToken = createSynthesizedNode(SyntaxKind.ColonToken); + node.whenFalse = whenFalse; + return node; + } + + export function createYield(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.YieldExpression, location); + node.expression = expression; + return node; + } + + export function createSpread(expression: Expression) { + const node = createNode(SyntaxKind.SpreadElementExpression); + node.expression = parenthesizeExpressionForList(expression); + return node; + } + + export function createClassExpression(name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassExpression, location); + node.decorators = undefined; + node.modifiers = undefined; + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; + } + + export function createOmittedExpression(location?: TextRange) { + const node = createNode(SyntaxKind.OmittedExpression, location); + return node; + } + + export function createExpressionWithTypeArguments(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ExpressionWithTypeArguments, location); + node.typeArguments = undefined; + node.expression = parenthesizeForAccess(expression); + return node; + } + + // Element + + export function createBlock(statements: Statement[], location?: TextRange, multiLine?: boolean): Block { + const block = createNode(SyntaxKind.Block, location); + block.statements = createNodeArray(statements); + if (multiLine) { + block.multiLine = true; + } + return block; + } + + export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList | VariableDeclaration[], location?: TextRange): VariableStatement { + const node = createNode(SyntaxKind.VariableStatement, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; + return node; + } + + export function createVariableDeclarationList(declarations: VariableDeclaration[], location?: TextRange, flags?: NodeFlags): VariableDeclarationList { + const node = createNode(SyntaxKind.VariableDeclarationList, location, flags); + node.declarations = createNodeArray(declarations); + return node; + } + + export function createLetDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Let); + } + + export function createConstDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Const); + } + + export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { + const node = createNode(SyntaxKind.VariableDeclaration, location); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + return node; + } + + export function createEmptyStatement(location: TextRange) { + return createNode(SyntaxKind.EmptyStatement, location); + } + + export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { + const node = createNode(SyntaxKind.ExpressionStatement, location); + node.expression = parenthesizeExpressionForExpressionStatement(expression); + return node; + } + + export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement, location?: TextRange, options?: { startOnNewLine?: boolean; }) { + const node = createNode(SyntaxKind.IfStatement, location); + node.expression = expression; + node.thenStatement = thenStatement; + node.elseStatement = elseStatement; + if (options && options.startOnNewLine) { + node.startsOnNewLine = true; + } + + return node; + } + + export function createSwitch(expression: Expression, caseBlock: CaseBlock, location?: TextRange): SwitchStatement { + const node = createNode(SyntaxKind.SwitchStatement, location); + node.expression = parenthesizeExpressionForList(expression); + node.caseBlock = caseBlock; + return node; + } + + export function createCaseBlock(clauses: CaseClause[], location?: TextRange): CaseBlock { + const node = createNode(SyntaxKind.CaseBlock, location); + node.clauses = createNodeArray(clauses); + return node; + } + + export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForStatement, location); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = statement; + return node; + } + + export function createLabel(label: string | Identifier, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.LabeledStatement, location); + node.label = typeof label === "string" ? createIdentifier(label) : label; + node.statement = statement; + return node; + } + + export function createDo(expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.DoStatement, location); + node.expression = expression; + node.statement = statement; + return node; + } + + export function createWhile(statement: Statement, expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.WhileStatement, location); + node.statement = statement; + node.expression = expression; + return node; + } + + export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForInStatement, location); + node.initializer = initializer; + node.expression = expression; + node.statement = statement; + return node; + } + + export function createForOf(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForOfStatement, location); + node.initializer = initializer; + node.expression = expression; + node.statement = statement; + return node; + } + + export function createReturn(expression?: Expression, location?: TextRange): ReturnStatement { + const node = createNode(SyntaxKind.ReturnStatement, location); + node.expression = expression; + return node; + } + + export function createFunctionDeclaration(modifiers: Modifier[], asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange, original?: Node) { + const node = createNode(SyntaxKind.FunctionDeclaration, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.asteriskToken = asteriskToken; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + if (original) { + node.original = original; + } + return node; + } + + export function createClassDeclaration(modifiers: Modifier[], name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassDeclaration, location); + node.decorators = undefined; + node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; + } + + export function createExportDefault(expression: Expression) { + const node = createNode(SyntaxKind.ExportAssignment); + node.isExportEquals = false; + node.expression = expression; + return node; + } + + export function createExportDeclaration(exportClause: NamedExports, moduleSpecifier?: Expression) { + const node = createNode(SyntaxKind.ExportDeclaration); + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function createNamedExports(elements: ExportSpecifier[]) { + const node = createNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + return node; + } + + export function createExportSpecifier(name: string | Identifier, propertyName?: string | Identifier) { + const node = createNode(SyntaxKind.ExportSpecifier); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.propertyName = typeof propertyName === "string" ? createIdentifier(propertyName) : propertyName; + return node; + } + + // Clauses + + export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { + const node = createNode(SyntaxKind.HeritageClause, location); + node.token = token; + node.types = createNodeArray(types); + return node; + } + + export function createCaseClause(expression: Expression, statements: Statement[], location?: TextRange) { + const node = createNode(SyntaxKind.CaseClause, location); + node.expression = parenthesizeExpressionForList(expression); + node.statements = createNodeArray(statements); + return node; + } + + // Property assignments + + export function createPropertyAssignment(name: string | PropertyName, initializer: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAssignment, location); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.questionToken = undefined; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + return node; + } + + // Transformation nodes + + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + export function createNotEmittedStatement(original: Node) { + const node = createNode(SyntaxKind.NotEmittedStatement, /*location*/ original); + node.original = original; + return node; + } + + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. + */ + export function createPartiallyEmittedExpression(expression: Expression, original?: Node, location?: TextRange) { + const node = createNode(SyntaxKind.PartiallyEmittedExpression, /*location*/ location || original); + node.expression = expression; + node.original = original; + return node; + } + + // Compound nodes + + export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); + } + + export function createLessThan(left: Expression, right: Expression, location?: TextRange) { + return createBinary(left, SyntaxKind.LessThanToken, right, location); + } + + export function createAssignment(left: Expression, right: Expression, location?: TextRange) { + return createBinary(left, SyntaxKind.EqualsToken, right, location); + } + + export function createStrictEquality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); + } + + export function createStrictInequality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); + } + + export function createAdd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.PlusToken, right); + } + + export function createSubtract(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.MinusToken, right); + } + + export function createPostfixIncrement(operand: Expression, location?: TextRange) { + return createPostfix(operand, SyntaxKind.PlusPlusToken, location); + } + + export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); + } + + export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); + } + + export function createLogicalNot(operand: Expression) { + return createPrefix(SyntaxKind.ExclamationToken, operand); + } + + export function createVoidZero() { + return createVoid(createLiteral(0)); + } + + export function createImportDeclaration(importClause: ImportClause, moduleSpecifier?: Expression, location?: TextRange): ImportDeclaration { + const node = createNode(SyntaxKind.ImportDeclaration, location); + node.importClause = importClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function createImportClause(name: Identifier, namedBindings: NamedImportBindings, location?: TextRange): ImportClause { + const node = createNode(SyntaxKind.ImportClause, location); + node.name = name; + node.namedBindings = namedBindings; + return node; + } + + export function createNamedImports(elements: NodeArray, location?: TextRange): NamedImports { + const node = createNode(SyntaxKind.NamedImports, location); + node.elements = elements; + return node; + } + + export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + if (isIdentifier(memberName)) { + return createPropertyAccess(target, getSynthesizedClone(memberName), location); + } + else if (isComputedPropertyName(memberName)) { + return createElementAccess(target, memberName.expression, location); + } + else { + return createElementAccess(target, memberName, location); + } + } + + export function createRestParameter(name: string | Identifier) { + const node = createParameter(name, /*initializer*/ undefined); + node.dotDotDotToken = createSynthesizedNode(SyntaxKind.DotDotDotToken); + return node; + } + + export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: Expression[], location?: TextRange) { + return createCall( + createPropertyAccess(func, "call"), + [ + thisArg, + ...argumentsList + ], + location + ); + } + + export function createBreak(label?: Identifier, location?: TextRange): BreakStatement { + const node = createNode(SyntaxKind.BreakStatement, location); + if (label) { + node.label = label; + } + return node; + } + + export function createContinue(label?: Identifier, location?: TextRange): BreakStatement { + const node = createNode(SyntaxKind.ContinueStatement, location); + if (label) { + node.label = label; + } + return node; + } + + export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { + return createCall( + createPropertyAccess(func, "apply"), + [ + thisArg, + argumentsExpression + ], + location + ); + } + + export function createArraySlice(array: Expression, start?: number | Expression) { + const argumentsList: Expression[] = []; + if (start !== undefined) { + argumentsList.push(typeof start === "number" ? createLiteral(start) : start); + } + + return createCall(createPropertyAccess(array, "slice"), argumentsList); + } + + export function createArrayConcat(array: Expression, values: Expression[]) { + return createCall( + createPropertyAccess(array, "concat"), + values + ); + } + + export function createMathPow(left: Expression, right: Expression, location?: TextRange) { + return createCall( + createPropertyAccess(createIdentifier("Math"), "pow"), + [left, right], + location + ); + } + + function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { + // Create an identifier and give it a parent. This allows us to resolve the react + // namespace during emit. + const react = createIdentifier(reactNamespace || "React"); + react.parent = parent; + return react; + } + + export function createReactCreateElement(reactNamespace: string, tagName: Expression, props: Expression, children: Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression { + const argumentsList = [tagName]; + if (props) { + argumentsList.push(props); + } + + if (children && children.length > 0) { + if (!props) { + argumentsList.push(createNull()); + } + + addNodes(argumentsList, children, /*startOnNewLine*/ children.length > 1); + } + + return createCall( + createPropertyAccess( + createReactNamespace(reactNamespace, parentElement), + "createElement" + ), + argumentsList, + location + ); + } + + // Helpers + + export function createExtendsHelper(name: Identifier) { + return createCall( + createIdentifier("__extends"), + [ + name, + createIdentifier("_super") + ] + ); + } + + export function createAssignHelper(attributesSegments: Expression[]) { + return createCall( + createIdentifier("__assign"), + attributesSegments + ); + } + + export function createParamHelper(expression: Expression, parameterOffset: number) { + return createCall( + createIdentifier("__param"), + [ + createLiteral(parameterOffset), + expression + ] + ); + } + + export function createMetadataHelper(metadataKey: string, metadataValue: Expression) { + return createCall( + createIdentifier("__metadata"), + [ + createLiteral(metadataKey), + metadataValue + ] + ); + } + + export function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { + const argumentsArray: Expression[] = []; + argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); + } + } + + return createCall(createIdentifier("__decorate"), argumentsArray); + } + + export function createAwaiterHelper(hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + return createCall( + createIdentifier("__awaiter"), + [ + createThis(), + hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), + createFunctionExpression( + createNode(SyntaxKind.AsteriskToken), + /*name*/ undefined, + [], + body + ) + ] + ); + } + + export function createHasOwnProperty(target: LeftHandSideExpression, propertyName: Expression) { + return createCall( + createPropertyAccess(target, "hasOwnProperty"), + [propertyName] + ); + } + + export interface PropertyDescriptorOptions { + [key: string]: boolean | Expression; + get?: Expression; + set?: Expression; + value?: Expression; + enumerable?: boolean | Expression; + configurable?: boolean | Expression; + writable?: boolean | Expression; + } + + export interface PropertyDescriptorExtendedOptions { + [key: string]: PropertyDescriptorExtendedOption; + get?: PropertyDescriptorExtendedOption; + set?: PropertyDescriptorExtendedOption; + value?: PropertyDescriptorExtendedOption; + enumerable?: PropertyDescriptorExtendedOption; + configurable?: PropertyDescriptorExtendedOption; + writable?: PropertyDescriptorExtendedOption; + } + + export interface PropertyDescriptorExtendedOption { + location?: TextRange; + emitFlags?: NodeEmitFlags; + newLine?: boolean; + } + + export function createObjectDefineProperty(target: Expression, memberName: Expression, descriptor: PropertyDescriptorOptions, preferNewLine?: boolean, location?: TextRange, descriptorOptions?: PropertyDescriptorExtendedOptions, context?: TransformationContext) { + return createCall( + createPropertyAccess( + createIdentifier("Object"), + "defineProperty" + ), + [ + target, + memberName, + createObjectLiteral( + createPropertyDescriptorProperties(descriptor, descriptorOptions, preferNewLine, context), + /*location*/ undefined, + /*multiLine*/ preferNewLine + ) + ], + location + ); + } + + function createPropertyDescriptorProperties(descriptor: PropertyDescriptorOptions, descriptorExtendedOptions: PropertyDescriptorExtendedOptions, preferNewLine: boolean, context: TransformationContext) { + const properties: ObjectLiteralElement[] = []; + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "get", descriptor, descriptorExtendedOptions, preferNewLine, context); + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "set", descriptor, descriptorExtendedOptions, preferNewLine, context); + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "value", descriptor, descriptorExtendedOptions, preferNewLine, context); + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "enumerable", descriptor, descriptorExtendedOptions, preferNewLine, context); + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "configurable", descriptor, descriptorExtendedOptions, preferNewLine, context); + addPropertyDescriptorPropertyAssignmentIfNeeded(properties, "writable", descriptor, descriptorExtendedOptions, preferNewLine, context); + return properties; + } + + function addPropertyDescriptorPropertyAssignmentIfNeeded(properties: ObjectLiteralElement[], name: string, descriptor: PropertyDescriptorOptions, descriptorExtendedOptions: PropertyDescriptorExtendedOptions, preferNewLine: boolean, context: TransformationContext) { + const value = getProperty(descriptor, name); + if (value !== undefined) { + let options: PropertyDescriptorExtendedOption; + let location: TextRange; + let emitFlags: NodeEmitFlags; + if (descriptorExtendedOptions !== undefined) { + options = getProperty(descriptorExtendedOptions, name); + if (options !== undefined) { + location = options.location; + emitFlags = options.emitFlags; + if (options.newLine !== undefined) { + preferNewLine = options.newLine; + } + } + } + + const property = createPropertyAssignment( + name, + typeof value === "boolean" ? createLiteral(value) : value, + location + ); + + if (emitFlags !== undefined) { + Debug.assert(context !== undefined, "TransformationContext must be supplied when emitFlags are provided."); + context.setNodeEmitFlags(property, emitFlags); + } + + if (preferNewLine) { + startOnNewLine(property); + } + + properties.push(property); + } + } + + function createObjectCreate(prototype: Expression) { + return createCall( + createPropertyAccess(createIdentifier("Object"), "create"), + [prototype] + ); + } + + function createGeti(target: LeftHandSideExpression) { + // name => super[name] + return createArrowFunction( + [createParameter("name")], + createElementAccess( + target, + createIdentifier("name") + ) + ); + } + + function createSeti(target: LeftHandSideExpression) { + // (name, value) => super[name] = value + return createArrowFunction( + [ + createParameter("name"), + createParameter("value") + ], + createAssignment( + createElementAccess( + target, + createIdentifier("name") + ), + createIdentifier("value") + ) + ); + } + + export function createAdvancedAsyncSuperHelper() { + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + + // const cache = Object.create(null); + const createCache = createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "cache", + createObjectCreate(createNull()) + ) + ]) + ); + + // get value() { return geti(name); } + const getter = createGetAccessor( + /*modifiers*/ undefined, + "value", + createBlock([ + createReturn( + createCall( + createIdentifier("geti"), + [createIdentifier("name")] + ) + ) + ]) + ); + + // set value(v) { seti(name, v); } + const setter = createSetAccessor( + /*modifiers*/ undefined, + "value", + createParameter("v"), + createBlock([ + createStatement( + createCall( + createIdentifier("seti"), + [ + createIdentifier("name"), + createIdentifier("v") + ] + ) + ) + ]) + ); + + // return name => cache[name] || ... + const getOrCreateAccessorsForName = createReturn( + createArrowFunction( + [createParameter("name")], + createLogicalOr( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createParen( + createAssignment( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createObjectLiteral([ + getter, + setter + ]) + ) + ) + ) + ) + ); + + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createCall( + createParen( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [ + createParameter("geti"), + createParameter("seti") + ], + createBlock([ + createCache, + getOrCreateAccessorsForName + ]) + ) + ), + [ + createGeti(createSuper()), + createSeti(createSuper()) + ] + ) + ) + ]) + ); + } + + export function createSimpleAsyncSuperHelper() { + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createGeti(createSuper()) + ) + ]) + ); + } + + export interface CallBinding { + target: LeftHandSideExpression; + thisArg: Expression; + } + + function shouldBeCapturedInTempVariable(node: Expression): boolean { + switch (skipParentheses(node).kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return false; + default: + return true; + } + } + + export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget): CallBinding { + const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); + let thisArg: Expression; + let target: LeftHandSideExpression; + if (isSuperProperty(callee)) { + thisArg = createThis(); + target = callee; + } + else if (callee.kind === SyntaxKind.SuperKeyword) { + thisArg = createThis(); + target = languageVersion < ScriptTarget.ES6 ? createIdentifier("_super", /*location*/ callee) : callee; + } + else { + switch (callee.kind) { + case SyntaxKind.PropertyAccessExpression: { + if (shouldBeCapturedInTempVariable((callee).expression)) { + // for `a.b()` target is `(_a = a).b` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createPropertyAccess( + createAssignment( + thisArg, + (callee).expression, + /*location*/ (callee).expression + ), + (callee).name, + /*location*/ callee + ); + } + else { + thisArg = (callee).expression; + target = callee; + } + break; + } + + case SyntaxKind.ElementAccessExpression: { + if (shouldBeCapturedInTempVariable((callee).expression)) { + // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createElementAccess( + createAssignment( + thisArg, + (callee).expression, + /*location*/ (callee).expression + ), + (callee).argumentExpression, + /*location*/ callee + ); + } + else { + thisArg = (callee).expression; + target = callee; + } + + break; + } + + default: { + // for `a()` target is `a` and thisArg is `void 0` + thisArg = createVoidZero(); + target = parenthesizeForAccess(expression); + break; + } + } + } + + return { target, thisArg }; + } + + export function inlineExpressions(expressions: Expression[]) { + return reduceLeft(expressions, createComma); + } + + export function createExpressionFromEntityName(node: EntityName | Expression): Expression { + return isQualifiedName(node) + ? createPropertyAccess( + createExpressionFromEntityName(node.left), + getSynthesizedClone(node.right) + ) + : getSynthesizedClone(node); + } + + export function createExpressionForPropertyName(memberName: PropertyName, location?: TextRange): Expression { + return isIdentifier(memberName) ? createLiteral(memberName.text, location) + : isComputedPropertyName(memberName) ? getRelocatedClone(memberName.expression, location || synthesizedLocation) + : getRelocatedClone(memberName, location || synthesizedLocation); + } + + // Utilities + + function isUseStrictPrologue(node: ExpressionStatement): boolean { + return (node.expression as StringLiteral).text === "use strict"; + } + + /** + * Add any necessary prologue-directives into target statement-array. + * The function needs to be called during each transformation step. + * This function needs to be called whenever we transform the statement + * list of a source file, namespace, or function-like body. + * + * @param target: result statements array + * @param source: origin statements array + * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives + */ + export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean): number { + Debug.assert(target.length === 0, "PrologueDirectives should be at the first statement in the target statements array"); + let foundUseStrict = false; + for (let i = 0; i < source.length; i++) { + if (isPrologueDirective(source[i])) { + if (isUseStrictPrologue(source[i] as ExpressionStatement)) { + foundUseStrict = true; + } + + target.push(source[i]); + } + else { + if (ensureUseStrict && !foundUseStrict) { + target.push(startOnNewLine(createStatement(createLiteral("use strict")))); + } + + return i; + } + } + + return source.length; + } + + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { + const skipped = skipPartiallyEmittedExpressions(operand); + + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (skipped.kind === SyntaxKind.ParenthesizedExpression) { + return operand; + } + + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) + ? createParen(operand) + : operand; + } + + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + const emittedOperand = skipPartiallyEmittedExpressions(operand); + const operandPrecedence = getExpressionPrecedence(emittedOperand); + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { + case Comparison.LessThan: + // If the operand is the right side of a right-associative binary operation + // and is a yield expression, then we do not need parentheses. + if (!isLeftSideOfBinary + && binaryOperatorAssociativity === Associativity.Right + && operand.kind === SyntaxKind.YieldExpression) { + return false; + } + + return true; + + case Comparison.GreaterThan: + return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + // + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + return binaryOperatorAssociativity === Associativity.Right; + } + else { + if (isBinaryExpression(emittedOperand) + && emittedOperand.operatorToken.kind === binaryOperator) { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (operatorHasAssociativeProperty(binaryOperator)) { + return false; + } + + // No need to parenthesize the right operand when the binary operator + // is plus (+) if both the left and right operands consist solely of either + // literals of the same kind or binary plus (+) expressions for literals of + // the same kind (recursively). + // "a"+(1+2) => "a"+(1+2) + // "a"+("b"+"c") => "a"+"b"+"c" + if (binaryOperator === SyntaxKind.PlusToken) { + const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; + if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { + return false; + } + } + } + + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + // + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(emittedOperand); + return operandAssociativity === Associativity.Left; + } + } + } + + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; + } + + interface BinaryPlusExpression extends BinaryExpression { + cachedLiteralKind: SyntaxKind; + } + + /** + * This function determines whether an expression consists of a homogeneous set of + * literal expressions or binary plus expressions that all share the same literal kind. + * It is used to determine whether the right-hand operand of a binary plus expression can be + * emitted without parentheses. + */ + function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { + node = skipPartiallyEmittedExpressions(node); + + if (isLiteralKind(node.kind)) { + return node.kind; + } + + if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.PlusToken) { + if ((node).cachedLiteralKind !== undefined) { + return (node).cachedLiteralKind; + } + + const leftKind = getLiteralKindOfBinaryPlusOperand((node).left); + const literalKind = isLiteralKind(leftKind) + && leftKind === getLiteralKindOfBinaryPlusOperand((node).right) + ? leftKind + : SyntaxKind.Unknown; + + (node).cachedLiteralKind = literalKind; + return literalKind; + } + + return SyntaxKind.Unknown; + } + + /** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a NewExpression node. + * + * @param expression The Expression node. + */ + export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + switch (emittedExpression.kind) { + case SyntaxKind.CallExpression: + return createParen(expression); + + case SyntaxKind.NewExpression: + return (emittedExpression).arguments + ? expression + : createParen(expression); + } + + return parenthesizeForAccess(expression); + } + + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + * + * @param expr The expression node. + */ + export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exceptions are: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // NumericLiteral + // 1.x -> not the same as (1).x + // + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isLeftHandSideExpression(emittedExpression) + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression).arguments) + && emittedExpression.kind !== SyntaxKind.NumericLiteral) { + return expression; + } + + return createParen(expression, /*location*/ expression); + } + + export function parenthesizePostfixOperand(operand: Expression) { + return isLeftHandSideExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizePrefixOperand(operand: Expression) { + return isUnaryExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + function parenthesizeListElements(elements: NodeArray) { + let result: Expression[]; + for (let i = 0; i < elements.length; i++) { + const element = parenthesizeExpressionForList(elements[i]); + if (result !== undefined || element !== elements[i]) { + if (result === undefined) { + result = elements.slice(0, i); + } + + result.push(element); + } + } + + if (result !== undefined) { + return createNodeArray(result, elements, elements.hasTrailingComma); + } + + return elements; + } + + export function parenthesizeExpressionForList(expression: Expression) { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + const expressionPrecedence = getExpressionPrecedence(emittedExpression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + return expressionPrecedence > commaPrecedence + ? expression + : createParen(expression, /*location*/ expression); + } + + export function parenthesizeExpressionForExpressionStatement(expression: Expression) { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isCallExpression(emittedExpression)) { + const callee = emittedExpression.expression; + const kind = skipPartiallyEmittedExpressions(callee).kind; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { + const mutableCall = getMutableClone(emittedExpression); + mutableCall.expression = createParen(callee, /*location*/ callee); + return recreatePartiallyEmittedExpressions(expression, mutableCall); + } + } + else { + const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + return createParen(expression, /*location*/ expression); + } + } + + return expression; + } + + /** + * Clones a series of not-emitted expressions with a new inner expression. + * + * @param originalOuterExpression The original outer expression. + * @param newInnerExpression The new inner expression. + */ + function recreatePartiallyEmittedExpressions(originalOuterExpression: Expression, newInnerExpression: Expression) { + if (isPartiallyEmittedExpression(originalOuterExpression)) { + const clone = getMutableClone(originalOuterExpression); + clone.expression = recreatePartiallyEmittedExpressions(clone.expression, newInnerExpression); + return clone; + } + + return newInnerExpression; + } + + function getLeftmostExpression(node: Expression): Expression { + while (true) { + switch (node.kind) { + case SyntaxKind.PostfixUnaryExpression: + node = (node).operand; + continue; + + case SyntaxKind.BinaryExpression: + node = (node).left; + continue; + + case SyntaxKind.ConditionalExpression: + node = (node).condition; + continue; + + case SyntaxKind.CallExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + node = (node).expression; + continue; + + case SyntaxKind.PartiallyEmittedExpression: + node = (node).expression; + continue; + } + + return node; + } + } + + export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { + const emittedBody = skipPartiallyEmittedExpressions(body); + if (emittedBody.kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(body, /*location*/ body); + } + + return body; + } + + export const enum OuterExpressionKinds { + Parentheses = 1 << 0, + Assertions = 1 << 1, + PartiallyEmittedExpressions = 1 << 2, + + All = Parentheses | Assertions | PartiallyEmittedExpressions + } + + export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; + export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; + export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { + let previousNode: Node; + do { + previousNode = node; + if (kinds & OuterExpressionKinds.Parentheses) { + node = skipParentheses(node); + } + + if (kinds & OuterExpressionKinds.Assertions) { + node = skipAssertions(node); + } + + if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { + node = skipPartiallyEmittedExpressions(node); + } + } + while (previousNode !== node); + + return node; + } + + export function skipParentheses(node: Expression): Expression; + export function skipParentheses(node: Node): Node; + export function skipParentheses(node: Node): Node { + while (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node).expression; + } + + return node; + } + + export function skipAssertions(node: Expression): Expression; + export function skipAssertions(node: Node): Node; + export function skipAssertions(node: Node): Node { + while (isAssertionExpression(node)) { + node = (node).expression; + } + + return node; + } + + export function skipPartiallyEmittedExpressions(node: Expression): Expression; + export function skipPartiallyEmittedExpressions(node: Node): Node; + export function skipPartiallyEmittedExpressions(node: Node) { + while (node.kind === SyntaxKind.PartiallyEmittedExpression) { + node = (node).expression; + } + + return node; + } + + export function startOnNewLine(node: T): T { + node.startsOnNewLine = true; + return node; + } + + export function setOriginalNode(node: T, original: Node): T { + node.original = original; + return node; + } + + export function setTextRange(node: T, location: TextRange): T { + if (location) { + node.pos = location.pos; + node.end = location.end; + } + return node; + } + + export function setNodeFlags(node: T, flags: NodeFlags): T { + node.flags = flags; + return node; + } + + export function setMultiLine(node: T, multiLine: boolean): T { + node.multiLine = multiLine; + return node; + } + + export function setHasTrailingComma(nodes: NodeArray, hasTrailingComma: boolean): NodeArray { + nodes.hasTrailingComma = hasTrailingComma; + return nodes; + } + + /** + * Get the name of that target module from an import or export declaration + */ + export function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier { + const namespaceDeclaration = getNamespaceDeclarationNode(node); + if (namespaceDeclaration && !isDefaultImport(node)) { + return createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, namespaceDeclaration.name)); + } + if (node.kind === SyntaxKind.ImportDeclaration && (node).importClause) { + return getGeneratedNameForNode(node); + } + if (node.kind === SyntaxKind.ExportDeclaration && (node).moduleSpecifier) { + return getGeneratedNameForNode(node); + } + return undefined; + } + + /** + * Get the name of a target module from an import/export declaration as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ + export function getExternalModuleNameLiteral(importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + const moduleName = getExternalModuleName(importNode); + if (moduleName.kind === SyntaxKind.StringLiteral) { + return tryGetModuleNameFromDeclaration(importNode, host, resolver, compilerOptions) + || tryRenameExternalModule(moduleName, sourceFile) + || getSynthesizedClone(moduleName); + } + + return undefined; + } + + /** + * Some bundlers (SystemJS builder) sometimes want to rename dependencies. + * Here we check if alternative name was provided for a given moduleName and return it if possible. + */ + function tryRenameExternalModule(moduleName: LiteralExpression, sourceFile: SourceFile) { + if (sourceFile.renamedDependencies && hasProperty(sourceFile.renamedDependencies, moduleName.text)) { + return createLiteral(sourceFile.renamedDependencies[moduleName.text]); + } + return undefined; + } + + /** + * Get the name of a module as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ + export function tryGetModuleNameFromFile(file: SourceFile, host: EmitHost, options: CompilerOptions): StringLiteral { + if (!file) { + return undefined; + } + if (file.moduleName) { + return createLiteral(file.moduleName); + } + if (!isDeclarationFile(file) && (options.out || options.outFile)) { + return createLiteral(getExternalModuleNameFromPath(host, file.fileName)); + } + return undefined; + } + + function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); + } +} \ No newline at end of file diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 38a341eaa5af7..7689055c746b5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,5 +1,6 @@ /// /// +/// namespace ts { /* @internal */ export let parseTime = 0; @@ -401,6 +402,8 @@ namespace ts { return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNodes, (node).typeParameters); + case SyntaxKind.PartiallyEmittedExpression: + return visitNode(cbNode, (node).expression); } } @@ -886,7 +889,7 @@ namespace ts { /** Invokes the provided callback then unconditionally restores the parser to the state it * was in immediately prior to invoking the callback. The result of invoking the callback - * is returned from this function. + * is returned from this function. */ function lookAhead(callback: () => T): T { return speculationHelper(callback, /*isLookAhead*/ true); @@ -1000,6 +1003,16 @@ namespace ts { return new NodeConstructor(kind, pos, pos); } + function createNodeArray(elements?: T[], pos?: number): NodeArray { + const array = >(elements || []); + if (!(pos >= 0)) { + pos = getNodePos(); + } + array.pos = pos; + array.end = pos; + return array; + } + function finishNode(node: T, end?: number): T { node.end = end === undefined ? scanner.getStartPos() : end; @@ -1385,8 +1398,7 @@ namespace ts { function parseList(kind: ParsingContext, parseElement: () => T): NodeArray { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; - const result = >[]; - result.pos = getNodePos(); + const result = createNodeArray(); while (!isListTerminator(kind)) { if (isListElement(kind, /*inErrorRecovery*/ false)) { @@ -1733,8 +1745,7 @@ namespace ts { function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; - const result = >[]; - result.pos = getNodePos(); + const result = createNodeArray(); let commaStart = -1; // Meaning the previous token was not a comma while (true) { @@ -1789,12 +1800,8 @@ namespace ts { return result; } - function createMissingList(): NodeArray { - const pos = getNodePos(); - const result = >[]; - result.pos = pos; - result.end = pos; - return result; + function createMissingList(): NodeArray { + return createNodeArray(); } function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { @@ -1859,8 +1866,7 @@ namespace ts { template.head = parseTemplateLiteralFragment(); Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - const templateSpans = >[]; - templateSpans.pos = getNodePos(); + const templateSpans = createNodeArray(); do { templateSpans.push(parseTemplateSpan()); @@ -2013,13 +2019,6 @@ namespace ts { return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifierKind(token) || token === SyntaxKind.AtToken || token === SyntaxKind.ThisKeyword; } - function setModifiers(node: Node, modifiers: ModifiersArray) { - if (modifiers) { - node.flags |= modifiers.flags; - node.modifiers = modifiers; - } - } - function parseParameter(): ParameterDeclaration { const node = createNode(SyntaxKind.Parameter); if (token === SyntaxKind.ThisKeyword) { @@ -2029,7 +2028,7 @@ namespace ts { } node.decorators = parseDecorators(); - setModifiers(node, parseModifiers()); + node.modifiers = parseModifiers(); node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); // FormalParameter [Yield,Await]: @@ -2216,23 +2215,23 @@ namespace ts { return token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBracketToken; } - function parseIndexSignatureDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): IndexSignatureDeclaration { + function parseIndexSignatureDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): IndexSignatureDeclaration { const node = createNode(SyntaxKind.IndexSignature, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); node.type = parseTypeAnnotation(); parseTypeMemberSemicolon(); return finishNode(node); } - function parsePropertyOrMethodSignature(fullStart: number, modifiers: ModifiersArray): PropertySignature | MethodSignature { + function parsePropertyOrMethodSignature(fullStart: number, modifiers: NodeArray): PropertySignature | MethodSignature { const name = parsePropertyName(); const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { const method = createNode(SyntaxKind.MethodSignature, fullStart); - setModifiers(method, modifiers); + method.modifiers = modifiers; method.name = name; method.questionToken = questionToken; @@ -2244,7 +2243,7 @@ namespace ts { } else { const property = createNode(SyntaxKind.PropertySignature, fullStart); - setModifiers(property, modifiers); + property.modifiers = modifiers; property.name = name; property.questionToken = questionToken; property.type = parseTypeAnnotation(); @@ -2444,8 +2443,7 @@ namespace ts { function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode { let type = parseConstituentType(); if (token === operator) { - const types = >[type]; - types.pos = type.pos; + const types = createNodeArray([type], type.pos); while (parseOptional(operator)) { types.push(parseConstituentType()); } @@ -2800,8 +2798,7 @@ namespace ts { parameter.name = identifier; finishNode(parameter); - node.parameters = >[parameter]; - node.parameters.pos = parameter.pos; + node.parameters = createNodeArray([parameter], parameter.pos); node.parameters.end = parameter.end; node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "=>"); @@ -2830,7 +2827,7 @@ namespace ts { return undefined; } - const isAsync = !!(arrowFunction.flags & NodeFlags.Async); + const isAsync = !!(getModifierFlags(arrowFunction) & ModifierFlags.Async); // If we have an arrow, then try to parse the body. Even if not, try to parse if we // have an opening brace, just in case we're in an error state. @@ -2975,8 +2972,8 @@ namespace ts { function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction { const node = createNode(SyntaxKind.ArrowFunction); - setModifiers(node, parseModifiersForArrowFunction()); - const isAsync = !!(node.flags & NodeFlags.Async); + node.modifiers = parseModifiersForArrowFunction(); + const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async); // Arrow functions are never generators. // @@ -3576,8 +3573,7 @@ namespace ts { } function parseJsxChildren(openingTagName: EntityName): NodeArray { - const result = >[]; - result.pos = scanner.getStartPos(); + const result = createNodeArray(); const saveParsingContext = parsingContext; parsingContext |= 1 << ParsingContext.JsxChildren; @@ -3955,7 +3951,7 @@ namespace ts { return finishNode(node); } - function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): AccessorDeclaration { + function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): AccessorDeclaration { if (parseContextualModifier(SyntaxKind.GetKeyword)) { return addJSDocComment(parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers)); } @@ -4040,12 +4036,12 @@ namespace ts { } const node = createNode(SyntaxKind.FunctionExpression); - setModifiers(node, parseModifiers()); + node.modifiers = parseModifiers(); parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); const isGenerator = !!node.asteriskToken; - const isAsync = !!(node.flags & NodeFlags.Async); + const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async); node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : isGenerator ? doInYieldContext(parseOptionalIdentifier) : @@ -4082,6 +4078,10 @@ namespace ts { function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { const node = createNode(SyntaxKind.Block); if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } @@ -4632,7 +4632,7 @@ namespace ts { const node = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); node.pos = fullStart; node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; return finishNode(node); } } @@ -4767,57 +4767,57 @@ namespace ts { return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; } - function parseVariableStatement(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): VariableStatement { + function parseVariableStatement(fullStart: number, decorators: NodeArray, modifiers: NodeArray): VariableStatement { const node = createNode(SyntaxKind.VariableStatement, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); parseSemicolon(); return addJSDocComment(finishNode(node)); } - function parseFunctionDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): FunctionDeclaration { + function parseFunctionDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): FunctionDeclaration { const node = createNode(SyntaxKind.FunctionDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - node.name = node.flags & NodeFlags.Default ? parseOptionalIdentifier() : parseIdentifier(); + node.name = hasModifier(node, ModifierFlags.Default) ? parseOptionalIdentifier() : parseIdentifier(); const isGenerator = !!node.asteriskToken; - const isAsync = !!(node.flags & NodeFlags.Async); + const isAsync = hasModifier(node, ModifierFlags.Async); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, Diagnostics.or_expected); return addJSDocComment(finishNode(node)); } - function parseConstructorDeclaration(pos: number, decorators: NodeArray, modifiers: ModifiersArray): ConstructorDeclaration { + function parseConstructorDeclaration(pos: number, decorators: NodeArray, modifiers: NodeArray): ConstructorDeclaration { const node = createNode(SyntaxKind.Constructor, pos); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.ConstructorKeyword); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false, Diagnostics.or_expected); return addJSDocComment(finishNode(node)); } - function parseMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, asteriskToken: Node, name: PropertyName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { + function parseMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray, asteriskToken: Node, name: PropertyName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { const method = createNode(SyntaxKind.MethodDeclaration, fullStart); method.decorators = decorators; - setModifiers(method, modifiers); + method.modifiers = modifiers; method.asteriskToken = asteriskToken; method.name = name; method.questionToken = questionToken; const isGenerator = !!asteriskToken; - const isAsync = !!(method.flags & NodeFlags.Async); + const isAsync = hasModifier(method, ModifierFlags.Async); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, method); method.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, diagnosticMessage); return addJSDocComment(finishNode(method)); } - function parsePropertyDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, name: PropertyName, questionToken: Node): ClassElement { + function parsePropertyDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray, name: PropertyName, questionToken: Node): ClassElement { const property = createNode(SyntaxKind.PropertyDeclaration, fullStart); property.decorators = decorators; - setModifiers(property, modifiers); + property.modifiers = modifiers; property.name = name; property.questionToken = questionToken; property.type = parseTypeAnnotation(); @@ -4831,7 +4831,7 @@ namespace ts { // AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initializer_opt[In, ?Yield]; // // The checker may still error in the static case to explicitly disallow the yield expression. - property.initializer = modifiers && modifiers.flags & NodeFlags.Static + property.initializer = hasModifier(property, ModifierFlags.Static) ? allowInAnd(parseNonParameterInitializer) : doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.DisallowInContext, parseNonParameterInitializer); @@ -4839,7 +4839,7 @@ namespace ts { return finishNode(property); } - function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ClassElement { + function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ClassElement { const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); const name = parsePropertyName(); @@ -4858,10 +4858,10 @@ namespace ts { return parseInitializer(/*inParameter*/ false); } - function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): AccessorDeclaration { + function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, decorators: NodeArray, modifiers: NodeArray): AccessorDeclaration { const node = createNode(kind, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; node.name = parsePropertyName(); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false); @@ -4957,14 +4957,15 @@ namespace ts { break; } - if (!decorators) { - decorators = >[]; - decorators.pos = decoratorStart; - } - const decorator = createNode(SyntaxKind.Decorator, decoratorStart); decorator.expression = doInDecoratorContext(parseLeftHandSideExpressionOrHigher); - decorators.push(finishNode(decorator)); + finishNode(decorator); + if (!decorators) { + decorators = createNodeArray([decorator], decoratorStart); + } + else { + decorators.push(decorator); + } } if (decorators) { decorators.end = getNodeEnd(); @@ -4979,16 +4980,15 @@ namespace ts { * * In such situations, 'permitInvalidConstAsModifier' should be set to true. */ - function parseModifiers(permitInvalidConstAsModifier?: boolean): ModifiersArray { - let flags = 0; - let modifiers: ModifiersArray; + function parseModifiers(permitInvalidConstAsModifier?: boolean): NodeArray { + let modifiers: NodeArray; while (true) { const modifierStart = scanner.getStartPos(); const modifierKind = token; if (token === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { // We need to ensure that any subsequent modifiers appear on the same line - // so that when 'const' is a standalone declaration, we don't issue an error. + // so that when 'const' is a standalone declaration, we don't issue an error. if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { break; } @@ -4999,33 +4999,28 @@ namespace ts { } } + const modifier = finishNode(createNode(modifierKind, modifierStart)); if (!modifiers) { - modifiers = []; - modifiers.pos = modifierStart; + modifiers = createNodeArray([modifier], modifierStart); + } + else { + modifiers.push(modifier); } - - flags |= modifierToFlag(modifierKind); - modifiers.push(finishNode(createNode(modifierKind, modifierStart))); } if (modifiers) { - modifiers.flags = flags; modifiers.end = scanner.getStartPos(); } return modifiers; } - function parseModifiersForArrowFunction(): ModifiersArray { - let flags = 0; - let modifiers: ModifiersArray; + function parseModifiersForArrowFunction(): NodeArray { + let modifiers: NodeArray; if (token === SyntaxKind.AsyncKeyword) { const modifierStart = scanner.getStartPos(); const modifierKind = token; nextToken(); - modifiers = []; - modifiers.pos = modifierStart; - flags |= modifierToFlag(modifierKind); - modifiers.push(finishNode(createNode(modifierKind, modifierStart))); - modifiers.flags = flags; + const modifier = finishNode(createNode(modifierKind, modifierStart)); + modifiers = createNodeArray([modifier], modifierStart); modifiers.end = scanner.getStartPos(); } @@ -5085,14 +5080,14 @@ namespace ts { SyntaxKind.ClassExpression); } - function parseClassDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ClassDeclaration { + function parseClassDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ClassDeclaration { return parseClassDeclarationOrExpression(fullStart, decorators, modifiers, SyntaxKind.ClassDeclaration); } - function parseClassDeclarationOrExpression(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, kind: SyntaxKind): ClassLikeDeclaration { + function parseClassDeclarationOrExpression(fullStart: number, decorators: NodeArray, modifiers: NodeArray, kind: SyntaxKind): ClassLikeDeclaration { const node = createNode(kind, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.ClassKeyword); node.name = parseNameOfClassDeclarationOrExpression(); node.typeParameters = parseTypeParameters(); @@ -5167,10 +5162,10 @@ namespace ts { return parseList(ParsingContext.ClassMembers, parseClassElement); } - function parseInterfaceDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): InterfaceDeclaration { + function parseInterfaceDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): InterfaceDeclaration { const node = createNode(SyntaxKind.InterfaceDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.InterfaceKeyword); node.name = parseIdentifier(); node.typeParameters = parseTypeParameters(); @@ -5179,10 +5174,10 @@ namespace ts { return finishNode(node); } - function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): TypeAliasDeclaration { + function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): TypeAliasDeclaration { const node = createNode(SyntaxKind.TypeAliasDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.TypeKeyword); node.name = parseIdentifier(); node.typeParameters = parseTypeParameters(); @@ -5203,10 +5198,10 @@ namespace ts { return finishNode(node); } - function parseEnumDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): EnumDeclaration { + function parseEnumDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): EnumDeclaration { const node = createNode(SyntaxKind.EnumDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; parseExpected(SyntaxKind.EnumKeyword); node.name = parseIdentifier(); if (parseExpected(SyntaxKind.OpenBraceToken)) { @@ -5231,27 +5226,27 @@ namespace ts { return finishNode(node); } - function parseModuleOrNamespaceDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, flags: NodeFlags): ModuleDeclaration { + function parseModuleOrNamespaceDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray, flags: NodeFlags): ModuleDeclaration { const node = createNode(SyntaxKind.ModuleDeclaration, fullStart); // If we are parsing a dotted namespace name, we want to // propagate the 'Namespace' flag across the names if set. const namespaceFlag = flags & NodeFlags.Namespace; node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; node.flags |= flags; node.name = parseIdentifier(); node.body = parseOptional(SyntaxKind.DotToken) - ? parseModuleOrNamespaceDeclaration(getNodePos(), /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.Export | namespaceFlag) + ? parseModuleOrNamespaceDeclaration(getNodePos(), /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) : parseModuleBlock(); return finishNode(node); } - function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ModuleDeclaration { + function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ModuleDeclaration { const node = createNode(SyntaxKind.ModuleDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; if (token === SyntaxKind.GlobalKeyword) { - // parse 'global' as name of global scope augmentation + // parse 'global' as name of global scope augmentation node.name = parseIdentifier(); node.flags |= NodeFlags.GlobalAugmentation; } @@ -5262,8 +5257,8 @@ namespace ts { return finishNode(node); } - function parseModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ModuleDeclaration { - let flags = modifiers ? modifiers.flags : 0; + function parseModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ModuleDeclaration { + let flags: NodeFlags = 0; if (token === SyntaxKind.GlobalKeyword) { // global augmentation return parseAmbientExternalModuleDeclaration(fullStart, decorators, modifiers); @@ -5293,7 +5288,7 @@ namespace ts { return nextToken() === SyntaxKind.SlashToken; } - function parseGlobalModuleExportDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): GlobalModuleExportDeclaration { + function parseGlobalModuleExportDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): GlobalModuleExportDeclaration { const exportDeclaration = createNode(SyntaxKind.GlobalModuleExportDeclaration, fullStart); exportDeclaration.decorators = decorators; exportDeclaration.modifiers = modifiers; @@ -5307,7 +5302,7 @@ namespace ts { return finishNode(exportDeclaration); } - function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration { + function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ImportEqualsDeclaration | ImportDeclaration { parseExpected(SyntaxKind.ImportKeyword); const afterImportPos = scanner.getStartPos(); @@ -5320,7 +5315,7 @@ namespace ts { // import x = M.x; const importEqualsDeclaration = createNode(SyntaxKind.ImportEqualsDeclaration, fullStart); importEqualsDeclaration.decorators = decorators; - setModifiers(importEqualsDeclaration, modifiers); + importEqualsDeclaration.modifiers = modifiers; importEqualsDeclaration.name = identifier; parseExpected(SyntaxKind.EqualsToken); importEqualsDeclaration.moduleReference = parseModuleReference(); @@ -5332,7 +5327,7 @@ namespace ts { // Import statement const importDeclaration = createNode(SyntaxKind.ImportDeclaration, fullStart); importDeclaration.decorators = decorators; - setModifiers(importDeclaration, modifiers); + importDeclaration.modifiers = modifiers; // ImportDeclaration: // import ImportClause from ModuleSpecifier ; @@ -5468,10 +5463,10 @@ namespace ts { return finishNode(node); } - function parseExportDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ExportDeclaration { + function parseExportDeclaration(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ExportDeclaration { const node = createNode(SyntaxKind.ExportDeclaration, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; if (parseOptional(SyntaxKind.AsteriskToken)) { parseExpected(SyntaxKind.FromKeyword); node.moduleSpecifier = parseModuleSpecifier(); @@ -5491,10 +5486,10 @@ namespace ts { return finishNode(node); } - function parseExportAssignment(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ExportAssignment { + function parseExportAssignment(fullStart: number, decorators: NodeArray, modifiers: NodeArray): ExportAssignment { const node = createNode(SyntaxKind.ExportAssignment, fullStart); node.decorators = decorators; - setModifiers(node, modifiers); + node.modifiers = modifiers; if (parseOptional(SyntaxKind.EqualsToken)) { node.isExportEquals = true; } @@ -5580,7 +5575,7 @@ namespace ts { function setExternalModuleIndicator(sourceFile: SourceFile) { sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node => - node.flags & NodeFlags.Export + hasModifier(node, ModifierFlags.Export) || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference || node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ExportAssignment @@ -5913,10 +5908,8 @@ namespace ts { function parseJSDocTypeList(firstType: JSDocType) { Debug.assert(!!firstType); - const types = >[]; - types.pos = firstType.pos; + const types = createNodeArray([firstType], firstType.pos); - types.push(firstType); while (parseOptional(SyntaxKind.BarToken)) { types.push(parseJSDocType()); } @@ -6124,11 +6117,11 @@ namespace ts { function addTag(tag: JSDocTag): void { if (tag) { if (!tags) { - tags = >[]; - tags.pos = tag.pos; + tags = createNodeArray([tag], tag.pos); + } + else { + tags.push(tag); } - - tags.push(tag); tags.end = tag.end; } } @@ -6221,8 +6214,7 @@ namespace ts { } // Type parameter list looks like '@template T,U,V' - const typeParameters = >[]; - typeParameters.pos = scanner.getStartPos(); + const typeParameters = createNodeArray(); while (true) { const name = parseJSDocIdentifier(); diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts new file mode 100644 index 0000000000000..b3fa2873225ab --- /dev/null +++ b/src/compiler/printer.ts @@ -0,0 +1,2846 @@ +/// +/// +/// +/// +/// + +/* @internal */ +namespace ts { + // Flags enum to track count of temp variables and a few dedicated names + const enum TempFlags { + Auto = 0x00000000, // No preferred name + CountMask = 0x0FFFFFFF, // Temp variable counter + _i = 0x10000000, // Use/preference flag for '_i' + } + + // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature + export function printFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { + const delimiters = createDelimiterMap(); + const brackets = createBracketsMap(); + + // emit output for the __extends helper function + const extendsHelper = ` +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 __()); +};`; + + // Emit output for the __assign helper function. + // This is typically used for JSX spread attributes, + // and can be used for object literal spread properties. + const assignHelper = ` +var __assign = (this && this.__assign) || Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; +};`; + + // emit output for the __decorate helper function + const decorateHelper = ` +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +};`; + + // emit output for the __metadata helper function + const metadataHelper = ` +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +};`; + + // emit output for the __param helper function + const paramHelper = ` +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +};`; + + // emit output for the __awaiter helper function + const awaiterHelper = ` +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments)).next()); + }); +};`; + + // emit output for the __export helper function + const exportStarHelper = ` +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +}`; + + // emit output for the UMD helper function. + const umdHelper = ` +(function (dependencies, factory) { + if (typeof module === 'object' && typeof module.exports === 'object') { + var v = factory(require, exports); if (v !== undefined) module.exports = v; + } + else if (typeof define === 'function' && define.amd) { + define(dependencies, factory); + } +})`; + + const superHelper = ` +const _super = name => super[name];`; + + const advancedSuperHelper = ` +const _super = (function (geti, seti) { + const cache = Object.create(null); + return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); +})(name => super[name], (name, value) => super[name] = value);`; + + const compilerOptions = host.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; + const emittedFilesList: string[] = compilerOptions.listEmittedFiles ? [] : undefined; + const emitterDiagnostics = createDiagnosticCollection(); + + let emitSkipped = false; + const newLine = host.getNewLine(); + const printFile = createFilePrinter(); + forEachExpectedEmitFile(host, emitFile, targetSourceFile); + + return { + emitSkipped, + diagnostics: emitterDiagnostics.getDiagnostics(), + emittedFiles: emittedFilesList, + sourceMaps: sourceMapDataList + }; + + function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath}: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean) { + // Make sure not to write js file and source map file if any of them cannot be written + if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) { + printFile(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); + } + else { + emitSkipped = true; + } + + if (declarationFilePath) { + emitSkipped = writeDeclarationFile(declarationFilePath, sourceFiles, isBundledEmit, host, resolver, emitterDiagnostics) || emitSkipped; + } + + if (!emitSkipped && emittedFilesList) { + emittedFilesList.push(jsFilePath); + if (sourceMapFilePath) { + emittedFilesList.push(sourceMapFilePath); + } + if (declarationFilePath) { + emittedFilesList.push(declarationFilePath); + } + } + } + + function createFilePrinter() { + const transformers = getTransformers(compilerOptions).concat(initializePrinter); + + const writer = createTextWriter(newLine); + const { + write, + writeLine, + increaseIndent, + decreaseIndent + } = writer; + + const sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter(); + const { + emitStart, + emitEnd, + emitPos + } = sourceMap; + + const comments = createCommentWriter(host, writer, sourceMap); + const { + getLeadingComments, + getTrailingComments, + getTrailingCommentsOfPosition, + emitLeadingComments, + emitTrailingComments, + emitLeadingDetachedComments, + emitTrailingDetachedComments + } = comments; + + let context: TransformationContext; + let getNodeEmitFlags: (node: Node) => NodeEmitFlags; + let setNodeEmitFlags: (node: Node, flags: NodeEmitFlags) => void; + let isSubstitutionEnabled: (node: Node) => boolean; + let isEmitNotificationEnabled: (node: Node) => boolean; + let onSubstituteNode: (node: Node, isExpression: boolean) => Node; + let onEmitNode: (node: Node, emit: (node: Node) => void) => void; + let nodeToGeneratedName: string[]; + let generatedNameSet: Map; + let tempFlags: TempFlags; + let currentSourceFile: SourceFile; + let currentText: string; + let currentFileIdentifiers: Map; + let extendsEmitted: boolean; + let assignEmitted: boolean; + let decorateEmitted: boolean; + let paramEmitted: boolean; + let awaiterEmitted: boolean; + let isOwnFileEmit: boolean; + + return doPrint; + + function doPrint(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { + sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); + nodeToGeneratedName = []; + generatedNameSet = {}; + isOwnFileEmit = !isBundledEmit; + + // Emit helpers from all the files + if (isBundledEmit && moduleKind) { + forEach(sourceFiles, emitEmitHelpers); + } + + // Transform and print the source files + transformFiles(resolver, host, sourceFiles, transformers); + + writeLine(); + + const sourceMappingURL = sourceMap.getSourceMappingURL(); + if (sourceMappingURL) { + write(`//# sourceMappingURL=${sourceMappingURL}`); + } + + // Write the source map + if (compilerOptions.sourceMap && !compilerOptions.inlineSourceMap) { + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), compilerOptions.emitBOM); + } + + // Record source map data for the test harness. + if (sourceMapDataList) { + sourceMapDataList.push(sourceMap.getSourceMapData()); + } + + // Write the output file + writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), compilerOptions.emitBOM); + + // Reset state + sourceMap.reset(); + comments.reset(); + writer.reset(); + + getNodeEmitFlags = undefined; + setNodeEmitFlags = undefined; + isSubstitutionEnabled = undefined; + isEmitNotificationEnabled = undefined; + onSubstituteNode = undefined; + onEmitNode = undefined; + tempFlags = TempFlags.Auto; + currentSourceFile = undefined; + currentText = undefined; + extendsEmitted = false; + assignEmitted = false; + decorateEmitted = false; + paramEmitted = false; + awaiterEmitted = false; + isOwnFileEmit = false; + } + + function initializePrinter(_context: TransformationContext) { + context = _context; + getNodeEmitFlags = context.getNodeEmitFlags; + setNodeEmitFlags = context.setNodeEmitFlags; + isSubstitutionEnabled = context.isSubstitutionEnabled; + isEmitNotificationEnabled = context.isEmitNotificationEnabled; + onSubstituteNode = context.onSubstituteNode; + onEmitNode = context.onEmitNode; + return printSourceFile; + } + + function printSourceFile(node: SourceFile) { + currentSourceFile = node; + currentText = node.text; + currentFileIdentifiers = node.identifiers; + sourceMap.setSourceFile(node); + comments.setSourceFile(node); + emitNodeWithNotificationOption(node, emitWorker); + return node; + } + + /** + * Emits a node. + */ + function emit(node: Node) { + emitNodeWithNotificationOption(node, emitWithoutNotificationOption); + } + + /** + * Emits a node with specialized emit flags. + */ + // TODO(rbuckton): This should be removed once source maps are aligned with the old + // emitter and new baselines are taken. This exists solely to + // align with the old emitter. + function emitSpecialized(node: Node, flags: NodeEmitFlags) { + if (node) { + const flagsToAdd = flags & ~getNodeEmitFlags(node); + if (flagsToAdd) { + setNodeEmitFlags(node, flagsToAdd); + emit(node); + setNodeEmitFlags(node, getNodeEmitFlags(node) & ~flagsToAdd); + return; + } + + emit(node); + } + } + + /** + * Emits a node without calling onEmitNode. + * NOTE: Do not call this method directly. + */ + function emitWithoutNotificationOption(node: Node) { + emitNodeWithWorker(node, emitWorker); + } + + /** + * Emits an expression node. + */ + function emitExpression(node: Expression) { + emitNodeWithNotificationOption(node, emitExpressionWithoutNotificationOption); + } + + /** + * Emits an expression without calling onEmitNode. + * NOTE: Do not call this method directly. + */ + function emitExpressionWithoutNotificationOption(node: Expression) { + emitNodeWithWorker(node, emitExpressionWorker); + } + + /** + * Emits a node with emit notification if available. + */ + function emitNodeWithNotificationOption(node: Node, emit: (node: Node) => void) { + if (node) { + if (isEmitNotificationEnabled(node)) { + onEmitNode(node, emit); + } + else { + emit(node); + } + } + } + + function emitNodeWithWorker(node: Node, emitWorker: (node: Node) => void) { + if (node) { + const leadingComments = getLeadingComments(node, shouldSkipLeadingCommentsForNode); + const trailingComments = getTrailingComments(node, shouldSkipTrailingCommentsForNode); + emitLeadingComments(node, leadingComments); + emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren); + emitWorker(node); + emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren); + emitTrailingComments(node, trailingComments); + } + } + + /** + * Determines whether to skip leading comment emit for a node. + * + * We do not emit comments for NotEmittedStatement nodes or any node that has + * NodeEmitFlags.NoLeadingComments. + * + * @param node A Node. + */ + function shouldSkipLeadingCommentsForNode(node: Node) { + return isNotEmittedStatement(node) + || (getNodeEmitFlags(node) & NodeEmitFlags.NoLeadingComments) !== 0; + } + + /** + * Determines whether to skip trailing comment emit for a node. + * + * We do not emit comments for NotEmittedStatement nodes or any node that has + * NodeEmitFlags.NoTrailingComments. + * + * @param node A Node. + */ + function shouldSkipTrailingCommentsForNode(node: Node) { + return isNotEmittedStatement(node) + || (getNodeEmitFlags(node) & NodeEmitFlags.NoTrailingComments) !== 0; + } + + /** + * Determines whether to skip source map emit for the start position of a node. + * + * We do not emit source maps for NotEmittedStatement nodes or any node that + * has NodeEmitFlags.NoLeadingSourceMap. + * + * @param node A Node. + */ + function shouldSkipLeadingSourceMapForNode(node: Node) { + return isNotEmittedStatement(node) + || (getNodeEmitFlags(node) & NodeEmitFlags.NoLeadingSourceMap) !== 0; + } + + + /** + * Determines whether to skip source map emit for the end position of a node. + * + * We do not emit source maps for NotEmittedStatement nodes or any node that + * has NodeEmitFlags.NoTrailingSourceMap. + * + * @param node A Node. + */ + function shouldSkipTrailingSourceMapForNode(node: Node) { + return isNotEmittedStatement(node) + || (getNodeEmitFlags(node) & NodeEmitFlags.NoTrailingSourceMap) !== 0; + } + + /** + * Determines whether to skip source map emit for a node and its children. + * + * We do not emit source maps for a node that has NodeEmitFlags.NoNestedSourceMaps. + */ + function shouldSkipSourceMapForChildren(node: Node) { + return (getNodeEmitFlags(node) & NodeEmitFlags.NoNestedSourceMaps) !== 0; + } + + function emitWorker(node: Node): void { + if (tryEmitSubstitute(node, emitWorker, /*isExpression*/ false)) { + return; + } + + const kind = node.kind; + switch (kind) { + // Pseudo-literals + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return emitLiteral(node); + + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node); + + // Reserved words + case SyntaxKind.ConstKeyword: + case SyntaxKind.DefaultKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.VoidKeyword: + + // Strict mode reserved words + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.StaticKeyword: + + // Contextual keywords + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.GlobalKeyword: + return writeTokenNode(node); + + // Parse tree nodes + + // Names + case SyntaxKind.QualifiedName: + return emitQualifiedName(node); + case SyntaxKind.ComputedPropertyName: + return emitComputedPropertyName(node); + + // Signature elements + case SyntaxKind.TypeParameter: + return emitTypeParameter(node); + case SyntaxKind.Parameter: + return emitParameter(node); + case SyntaxKind.Decorator: + return emitDecorator(node); + + // Type members + case SyntaxKind.PropertySignature: + return emitPropertySignature(node); + case SyntaxKind.PropertyDeclaration: + return emitPropertyDeclaration(node); + case SyntaxKind.MethodSignature: + return emitMethodSignature(node); + case SyntaxKind.MethodDeclaration: + return emitMethodDeclaration(node); + case SyntaxKind.Constructor: + return emitConstructor(node); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return emitAccessorDeclaration(node); + case SyntaxKind.CallSignature: + return emitCallSignature(node); + case SyntaxKind.ConstructSignature: + return emitConstructSignature(node); + case SyntaxKind.IndexSignature: + return emitIndexSignature(node); + + // Types + case SyntaxKind.TypePredicate: + return emitTypePredicate(node); + case SyntaxKind.TypeReference: + return emitTypeReference(node); + case SyntaxKind.FunctionType: + return emitFunctionType(node); + case SyntaxKind.ConstructorType: + return emitConstructorType(node); + case SyntaxKind.TypeQuery: + return emitTypeQuery(node); + case SyntaxKind.TypeLiteral: + return emitTypeLiteral(node); + case SyntaxKind.ArrayType: + return emitArrayType(node); + case SyntaxKind.TupleType: + return emitTupleType(node); + case SyntaxKind.UnionType: + return emitUnionType(node); + case SyntaxKind.IntersectionType: + return emitIntersectionType(node); + case SyntaxKind.ParenthesizedType: + return emitParenthesizedType(node); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node); + case SyntaxKind.ThisType: + return emitThisType(node); + case SyntaxKind.StringLiteralType: + return emitLiteral(node); + + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return emitObjectBindingPattern(node); + case SyntaxKind.ArrayBindingPattern: + return emitArrayBindingPattern(node); + case SyntaxKind.BindingElement: + return emitBindingElement(node); + + // Misc + case SyntaxKind.TemplateSpan: + return emitTemplateSpan(node); + case SyntaxKind.SemicolonClassElement: + return emitSemicolonClassElement(node); + + // Statements + case SyntaxKind.Block: + return emitBlock(node); + case SyntaxKind.VariableStatement: + return emitVariableStatement(node); + case SyntaxKind.EmptyStatement: + return emitEmptyStatement(node); + case SyntaxKind.ExpressionStatement: + return emitExpressionStatement(node); + case SyntaxKind.IfStatement: + return emitIfStatement(node); + case SyntaxKind.DoStatement: + return emitDoStatement(node); + case SyntaxKind.WhileStatement: + return emitWhileStatement(node); + case SyntaxKind.ForStatement: + return emitForStatement(node); + case SyntaxKind.ForInStatement: + return emitForInStatement(node); + case SyntaxKind.ForOfStatement: + return emitForOfStatement(node); + case SyntaxKind.ContinueStatement: + return emitContinueStatement(node); + case SyntaxKind.BreakStatement: + return emitBreakStatement(node); + case SyntaxKind.ReturnStatement: + return emitReturnStatement(node); + case SyntaxKind.WithStatement: + return emitWithStatement(node); + case SyntaxKind.SwitchStatement: + return emitSwitchStatement(node); + case SyntaxKind.LabeledStatement: + return emitLabeledStatement(node); + case SyntaxKind.ThrowStatement: + return emitThrowStatement(node); + case SyntaxKind.TryStatement: + return emitTryStatement(node); + case SyntaxKind.DebuggerStatement: + return emitDebuggerStatement(node); + + // Declarations + case SyntaxKind.VariableDeclaration: + return emitVariableDeclaration(node); + case SyntaxKind.VariableDeclarationList: + return emitVariableDeclarationList(node); + case SyntaxKind.FunctionDeclaration: + return emitFunctionDeclaration(node); + case SyntaxKind.ClassDeclaration: + return emitClassDeclaration(node); + case SyntaxKind.InterfaceDeclaration: + return emitInterfaceDeclaration(node); + case SyntaxKind.TypeAliasDeclaration: + return emitTypeAliasDeclaration(node); + case SyntaxKind.EnumDeclaration: + return emitEnumDeclaration(node); + case SyntaxKind.ModuleDeclaration: + return emitModuleDeclaration(node); + case SyntaxKind.ModuleBlock: + return emitModuleBlock(node); + case SyntaxKind.CaseBlock: + return emitCaseBlock(node); + case SyntaxKind.ImportEqualsDeclaration: + return emitImportEqualsDeclaration(node); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration(node); + case SyntaxKind.ImportClause: + return emitImportClause(node); + case SyntaxKind.NamespaceImport: + return emitNamespaceImport(node); + case SyntaxKind.NamedImports: + return emitNamedImports(node); + case SyntaxKind.ImportSpecifier: + return emitImportSpecifier(node); + case SyntaxKind.ExportAssignment: + return emitExportAssignment(node); + case SyntaxKind.ExportDeclaration: + return emitExportDeclaration(node); + case SyntaxKind.NamedExports: + return emitNamedExports(node); + case SyntaxKind.ExportSpecifier: + return emitExportSpecifier(node); + case SyntaxKind.MissingDeclaration: + return; + + // Module references + case SyntaxKind.ExternalModuleReference: + return emitExternalModuleReference(node); + + // JSX (non-expression) + case SyntaxKind.JsxText: + return emitJsxText(node); + case SyntaxKind.JsxOpeningElement: + return emitJsxOpeningElement(node); + case SyntaxKind.JsxClosingElement: + return emitJsxClosingElement(node); + case SyntaxKind.JsxAttribute: + return emitJsxAttribute(node); + case SyntaxKind.JsxSpreadAttribute: + return emitJsxSpreadAttribute(node); + case SyntaxKind.JsxExpression: + return emitJsxExpression(node); + + // Clauses + case SyntaxKind.CaseClause: + return emitCaseClause(node); + case SyntaxKind.DefaultClause: + return emitDefaultClause(node); + case SyntaxKind.HeritageClause: + return emitHeritageClause(node); + case SyntaxKind.CatchClause: + return emitCatchClause(node); + + // Property assignments + case SyntaxKind.PropertyAssignment: + return emitPropertyAssignment(node); + case SyntaxKind.ShorthandPropertyAssignment: + return emitShorthandPropertyAssignment(node); + + // Enum + case SyntaxKind.EnumMember: + return emitEnumMember(node); + + // Top-level nodes + case SyntaxKind.SourceFile: + return emitSourceFile(node); + + // JSDoc nodes (ignored) + + // Transformation nodes (ignored) + } + + if (isExpression(node)) { + return emitExpressionWorker(node); + } + } + + function emitExpressionWorker(node: Node) { + if (tryEmitSubstitute(node, emitExpressionWorker, /*isExpression*/ true)) { + return; + } + + const kind = node.kind; + switch (kind) { + // Literals + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return emitLiteral(node); + + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node); + + // Reserved words + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.ThisKeyword: + return writeTokenNode(node); + + // Expressions + case SyntaxKind.ArrayLiteralExpression: + return emitArrayLiteralExpression(node); + case SyntaxKind.ObjectLiteralExpression: + return emitObjectLiteralExpression(node); + case SyntaxKind.PropertyAccessExpression: + return emitPropertyAccessExpression(node); + case SyntaxKind.ElementAccessExpression: + return emitElementAccessExpression(node); + case SyntaxKind.CallExpression: + return emitCallExpression(node); + case SyntaxKind.NewExpression: + return emitNewExpression(node); + case SyntaxKind.TaggedTemplateExpression: + return emitTaggedTemplateExpression(node); + case SyntaxKind.TypeAssertionExpression: + return emitTypeAssertionExpression(node); + case SyntaxKind.ParenthesizedExpression: + return emitParenthesizedExpression(node); + case SyntaxKind.FunctionExpression: + return emitFunctionExpression(node); + case SyntaxKind.ArrowFunction: + return emitArrowFunction(node); + case SyntaxKind.DeleteExpression: + return emitDeleteExpression(node); + case SyntaxKind.TypeOfExpression: + return emitTypeOfExpression(node); + case SyntaxKind.VoidExpression: + return emitVoidExpression(node); + case SyntaxKind.AwaitExpression: + return emitAwaitExpression(node); + case SyntaxKind.PrefixUnaryExpression: + return emitPrefixUnaryExpression(node); + case SyntaxKind.PostfixUnaryExpression: + return emitPostfixUnaryExpression(node); + case SyntaxKind.BinaryExpression: + return emitBinaryExpression(node); + case SyntaxKind.ConditionalExpression: + return emitConditionalExpression(node); + case SyntaxKind.TemplateExpression: + return emitTemplateExpression(node); + case SyntaxKind.YieldExpression: + return emitYieldExpression(node); + case SyntaxKind.SpreadElementExpression: + return emitSpreadElementExpression(node); + case SyntaxKind.ClassExpression: + return emitClassExpression(node); + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.AsExpression: + return emitAsExpression(node); + case SyntaxKind.NonNullExpression: + return emitNonNullExpression(node); + + // JSX + case SyntaxKind.JsxElement: + return emitJsxElement(node); + case SyntaxKind.JsxSelfClosingElement: + return emitJsxSelfClosingElement(node); + + // Transformation nodes + case SyntaxKind.PartiallyEmittedExpression: + return emitPartiallyEmittedExpression(node); + } + } + + // + // Literals/Pseudo-literals + // + + // SyntaxKind.NumericLiteral + // SyntaxKind.StringLiteral + // SyntaxKind.RegularExpressionLiteral + // SyntaxKind.NoSubstitutionTemplateLiteral + // SyntaxKind.TemplateHead + // SyntaxKind.TemplateMiddle + // SyntaxKind.TemplateTail + function emitLiteral(node: LiteralLikeNode) { + const text = getLiteralTextOfNode(node); + if ((compilerOptions.sourceMap || compilerOptions.inlineSourceMap) + && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { + writer.writeLiteral(text); + } + else { + write(text); + } + } + + // + // Identifiers + // + + function emitIdentifier(node: Identifier) { + if (getNodeEmitFlags(node) & NodeEmitFlags.UMDDefine) { + writeLines(umdHelper); + } + else { + write(getTextOfNode(node, /*includeTrivia*/ false)); + } + } + + // + // Names + // + + function emitQualifiedName(node: QualifiedName) { + emitEntityName(node.left); + write("."); + emit(node.right); + } + + function emitEntityName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); + } + } + + function emitComputedPropertyName(node: ComputedPropertyName) { + write("["); + emitExpression(node.expression); + write("]"); + } + + // + // Signature elements + // + + function emitTypeParameter(node: TypeParameterDeclaration) { + emit(node.name); + emitWithPrefix(" extends ", node.constraint); + } + + function emitParameter(node: ParameterDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeIfPresent(node.dotDotDotToken, "..."); + emit(node.name); + writeIfPresent(node.questionToken, "?"); + emitExpressionWithPrefix(" = ", node.initializer); + emitWithPrefix(": ", node.type); + } + + function emitDecorator(decorator: Decorator) { + write("@"); + emitExpression(decorator.expression); + } + + // + // Type members + // + + function emitPropertySignature(node: PropertySignature) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + writeIfPresent(node.questionToken, "?"); + emitWithPrefix(": ", node.type); + write(";"); + } + + function emitPropertyDeclaration(node: PropertyDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emitWithPrefix(": ", node.type); + emitExpressionWithPrefix(" = ", node.initializer); + write(";"); + } + + function emitMethodSignature(node: MethodSignature) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + writeIfPresent(node.questionToken, "?"); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitWithPrefix(": ", node.type); + write(";"); + } + + function emitMethodDeclaration(node: MethodDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeIfPresent(node.asteriskToken, "*"); + emit(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitConstructor(node: ConstructorDeclaration) { + emitModifiers(node, node.modifiers); + write("constructor"); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitAccessorDeclaration(node: AccessorDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write(node.kind === SyntaxKind.GetAccessor ? "get " : "set "); + emit(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitCallSignature(node: CallSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitWithPrefix(": ", node.type); + write(";"); + } + + function emitConstructSignature(node: ConstructSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write("new "); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitWithPrefix(": ", node.type); + write(";"); + } + + function emitIndexSignature(node: IndexSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitParametersForIndexSignature(node, node.parameters); + emitWithPrefix(": ", node.type); + write(";"); + } + + function emitSemicolonClassElement(node: SemicolonClassElement) { + write(";"); + } + + // + // Types + // + + function emitTypePredicate(node: TypePredicateNode) { + emit(node.parameterName); + write(" is "); + emit(node.type); + } + + function emitTypeReference(node: TypeReferenceNode) { + emit(node.typeName); + emitTypeArguments(node, node.typeArguments); + } + + function emitFunctionType(node: FunctionTypeNode) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + write(" => "); + emit(node.type); + } + + function emitConstructorType(node: ConstructorTypeNode) { + write("new "); + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + write(" => "); + emit(node.type); + } + + function emitTypeQuery(node: TypeQueryNode) { + write("typeof "); + emit(node.exprName); + } + + function emitTypeLiteral(node: TypeLiteralNode) { + write("{"); + emitList(node, node.members, ListFormat.TypeLiteralMembers); + write("}"); + } + + function emitArrayType(node: ArrayTypeNode) { + emit(node.elementType); + write("[]"); + } + + function emitTupleType(node: TupleTypeNode) { + write("["); + emitList(node, node.elementTypes, ListFormat.TupleTypeElements); + write("]"); + } + + function emitUnionType(node: UnionTypeNode) { + emitList(node, node.types, ListFormat.UnionTypeConstituents); + } + + function emitIntersectionType(node: IntersectionTypeNode) { + emitList(node, node.types, ListFormat.IntersectionTypeConstituents); + } + + function emitParenthesizedType(node: ParenthesizedTypeNode) { + write("("); + emit(node.type); + write(")"); + } + + function emitThisType(node: ThisTypeNode) { + write("this"); + } + + // + // Binding patterns + // + + function emitObjectBindingPattern(node: ObjectBindingPattern) { + const elements = node.elements; + if (elements.length === 0) { + write("{}"); + } + else { + write("{"); + emitList(node, elements, ListFormat.ObjectBindingPatternElements); + write("}"); + } + } + + function emitArrayBindingPattern(node: ArrayBindingPattern) { + const elements = node.elements; + if (elements.length === 0) { + write("[]"); + } + else { + write("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + write("]"); + } + } + + function emitBindingElement(node: BindingElement) { + emitWithSuffix(node.propertyName, ": "); + writeIfPresent(node.dotDotDotToken, "..."); + emit(node.name); + emitExpressionWithPrefix(" = ", node.initializer); + } + + // + // Expressions + // + + function emitArrayLiteralExpression(node: ArrayLiteralExpression) { + const elements = node.elements; + if (elements.length === 0) { + write("[]"); + } + else { + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine); + } + } + + function emitObjectLiteralExpression(node: ObjectLiteralExpression) { + const properties = node.properties; + if (properties.length === 0) { + write("{}"); + } + else { + const indentedFlag = getNodeEmitFlags(node) & NodeEmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } + + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + const allowTrailingComma = languageVersion >= ScriptTarget.ES5 ? ListFormat.AllowTrailingComma : ListFormat.None; + emitList(node, properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); + + if (indentedFlag) { + decreaseIndent(); + } + } + } + + function emitPropertyAccessExpression(node: PropertyAccessExpression) { + if (tryEmitConstantValue(node)) { + return; + } + + const indentBeforeDot = needsIndentation(node, node.expression, node.dotToken); + const indentAfterDot = needsIndentation(node, node.dotToken, node.name); + const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); + + emitExpression(node.expression); + increaseIndentIf(indentBeforeDot); + write(shouldEmitDotDot ? ".." : "."); + increaseIndentIf(indentAfterDot); + emit(node.name); + decreaseIndentIf(indentBeforeDot, indentAfterDot); + } + + // 1..toString is a valid property access, emit a dot after the literal + // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal + function needsDotDotForPropertyAccess(expression: Expression) { + if (expression.kind === SyntaxKind.NumericLiteral) { + // check if numeric literal was originally written with a dot + const text = getLiteralTextOfNode(expression); + return text.indexOf(tokenToString(SyntaxKind.DotToken)) < 0; + } + else { + // check if constant enum value is integer + const constantValue = tryGetConstEnumValue(expression); + // isFinite handles cases when constantValue is undefined + return isFinite(constantValue) && Math.floor(constantValue) === constantValue; + } + } + + function emitElementAccessExpression(node: ElementAccessExpression) { + if (tryEmitConstantValue(node)) { + return; + } + + emitExpression(node.expression); + write("["); + emitExpression(node.argumentExpression); + write("]"); + } + + function emitCallExpression(node: CallExpression) { + emitExpression(node.expression); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); + } + + function emitNewExpression(node: NewExpression) { + write("new "); + emitExpression(node.expression); + emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments); + } + + function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { + emitExpression(node.tag); + write(" "); + emitExpression(node.template); + } + + function emitTypeAssertionExpression(node: TypeAssertion) { + if (node.type) { + write("<"); + emit(node.type); + write(">"); + } + + emitExpression(node.expression); + } + + function emitParenthesizedExpression(node: ParenthesizedExpression) { + write("("); + emitExpression(node.expression); + write(")"); + } + + function emitFunctionExpression(node: FunctionExpression) { + emitFunctionDeclarationOrExpression(node); + } + + function emitArrowFunction(node: ArrowFunction) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitSignatureAndBody(node, emitArrowFunctionHead); + } + + function emitArrowFunctionHead(node: ArrowFunction) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + emitWithPrefix(": ", node.type); + write(" =>"); + } + + function emitDeleteExpression(node: DeleteExpression) { + write("delete "); + emitExpression(node.expression); + } + + function emitTypeOfExpression(node: TypeOfExpression) { + write("typeof "); + emitExpression(node.expression); + } + + function emitVoidExpression(node: VoidExpression) { + write("void "); + emitExpression(node.expression); + } + + function emitAwaitExpression(node: AwaitExpression) { + write("await "); + emitExpression(node.expression); + } + + function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { + writeTokenText(node.operator); + if (shouldEmitWhitespaceBeforeOperand(node)) { + write(" "); + } + emitExpression(node.operand); + } + + function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { + // In some cases, we need to emit a space between the operator and the operand. One obvious case + // is when the operator is an identifier, like delete or typeof. We also need to do this for plus + // and minus expressions in certain cases. Specifically, consider the following two cases (parens + // are just for clarity of exposition, and not part of the source code): + // + // (+(+1)) + // (+(++1)) + // + // We need to emit a space in both cases. In the first case, the absence of a space will make + // the resulting expression a prefix increment operation. And in the second, it will make the resulting + // expression a prefix increment whose operand is a plus expression - (++(+x)) + // The same is true of minus of course. + const operand = node.operand; + return operand.kind === SyntaxKind.PrefixUnaryExpression + && ((node.operator === SyntaxKind.PlusToken && ((operand).operator === SyntaxKind.PlusToken || (operand).operator === SyntaxKind.PlusPlusToken)) + || (node.operator === SyntaxKind.MinusToken && ((operand).operator === SyntaxKind.MinusToken || (operand).operator === SyntaxKind.MinusMinusToken))); + } + + function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { + emitExpression(node.operand); + writeTokenText(node.operator); + } + + function emitBinaryExpression(node: BinaryExpression) { + const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken; + const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken); + const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right); + + emitExpression(node.left); + increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined); + writeTokenText(node.operatorToken.kind); + increaseIndentIf(indentAfterOperator, " "); + emitExpression(node.right); + decreaseIndentIf(indentBeforeOperator, indentAfterOperator); + } + + function emitConditionalExpression(node: ConditionalExpression) { + const indentBeforeQuestion = needsIndentation(node, node.condition, node.questionToken); + const indentAfterQuestion = needsIndentation(node, node.questionToken, node.whenTrue); + const indentBeforeColon = needsIndentation(node, node.whenTrue, node.colonToken); + const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse); + + emitExpression(node.condition); + increaseIndentIf(indentBeforeQuestion, " "); + write("?"); + increaseIndentIf(indentAfterQuestion, " "); + emitExpression(node.whenTrue); + decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion); + + increaseIndentIf(indentBeforeColon, " "); + write(":"); + increaseIndentIf(indentAfterColon, " "); + emitExpression(node.whenFalse); + decreaseIndentIf(indentBeforeColon, indentAfterColon); + } + + function emitTemplateExpression(node: TemplateExpression) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } + + function emitYieldExpression(node: YieldExpression) { + write(node.asteriskToken ? "yield*" : "yield"); + emitExpressionWithPrefix(" ", node.expression); + } + + function emitSpreadElementExpression(node: SpreadElementExpression) { + write("..."); + emitExpression(node.expression); + } + + function emitClassExpression(node: ClassExpression) { + emitClassDeclarationOrExpression(node); + } + + function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + emitExpression(node.expression); + emitTypeArguments(node, node.typeArguments); + } + + function emitAsExpression(node: AsExpression) { + emitExpression(node.expression); + if (node.type) { + write(" as "); + emit(node.type); + } + } + + function emitNonNullExpression(node: NonNullExpression) { + emitExpression(node.expression); + write("!"); + } + + // + // Misc + // + + function emitTemplateSpan(node: TemplateSpan) { + emitExpression(node.expression); + emit(node.literal); + } + + // + // Statements + // + + function emitBlock(node: Block, format?: ListFormat) { + if (isSingleLineEmptyBlock(node)) { + writeToken(SyntaxKind.OpenBraceToken, node.pos); + write(" "); + writeToken(SyntaxKind.CloseBraceToken, node.statements.end); + } + else { + writeToken(SyntaxKind.OpenBraceToken, node.pos); + emitBlockStatements(node); + writeToken(SyntaxKind.CloseBraceToken, node.statements.end); + } + } + + function emitBlockStatements(node: Block) { + if (getNodeEmitFlags(node) & NodeEmitFlags.SingleLine) { + emitList(node, node.statements, ListFormat.SingleLineBlockStatements); + } + else { + emitList(node, node.statements, ListFormat.MultiLineBlockStatements); + } + } + + function emitVariableStatement(node: VariableStatement) { + emitModifiers(node, node.modifiers); + emit(node.declarationList); + write(";"); + } + + function emitEmptyStatement(node: EmptyStatement) { + write(";"); + } + + function emitExpressionStatement(node: ExpressionStatement) { + emitExpression(node.expression); + write(";"); + } + + function emitIfStatement(node: IfStatement) { + const openParenPos = writeToken(SyntaxKind.IfKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emitExpression(node.expression); + writeToken(SyntaxKind.CloseParenToken, node.expression.end); + emitEmbeddedStatement(node.thenStatement); + if (node.elseStatement) { + writeLine(); + writeToken(SyntaxKind.ElseKeyword, node.thenStatement.end); + if (node.elseStatement.kind === SyntaxKind.IfStatement) { + write(" "); + emit(node.elseStatement); + } + else { + emitEmbeddedStatement(node.elseStatement); + } + } + } + + function emitDoStatement(node: DoStatement) { + write("do"); + emitEmbeddedStatement(node.statement); + if (isBlock(node.statement)) { + write(" "); + } + else { + writeLine(); + } + + write("while ("); + emitExpression(node.expression); + write(");"); + } + + function emitWhileStatement(node: WhileStatement) { + write("while ("); + emitExpression(node.expression); + write(")"); + emitEmbeddedStatement(node.statement); + } + + function emitForStatement(node: ForStatement) { + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emitForBinding(node.initializer); + write(";"); + emitExpressionWithPrefix(" ", node.condition); + write(";"); + emitExpressionWithPrefix(" ", node.incrementor); + write(")"); + emitEmbeddedStatement(node.statement); + } + + function emitForInStatement(node: ForInStatement) { + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emitForBinding(node.initializer); + write(" in "); + emitExpression(node.expression); + writeToken(SyntaxKind.CloseParenToken, node.expression.end); + emitEmbeddedStatement(node.statement); + } + + function emitForOfStatement(node: ForOfStatement) { + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emitForBinding(node.initializer); + write(" of "); + emitExpression(node.expression); + writeToken(SyntaxKind.CloseParenToken, node.expression.end); + emitEmbeddedStatement(node.statement); + } + + function emitForBinding(node: VariableDeclarationList | Expression) { + if (node !== undefined) { + if (node.kind === SyntaxKind.VariableDeclarationList) { + emit(node); + } + else { + emitExpression(node); + } + } + } + + function emitContinueStatement(node: ContinueStatement) { + writeToken(SyntaxKind.ContinueKeyword, node.pos); + emitWithPrefix(" ", node.label); + write(";"); + } + + function emitBreakStatement(node: BreakStatement) { + writeToken(SyntaxKind.BreakKeyword, node.pos); + emitWithPrefix(" ", node.label); + write(";"); + } + + function emitReturnStatement(node: ReturnStatement) { + writeToken(SyntaxKind.ReturnKeyword, node.pos, node, shouldSkipSourceMapForToken); + emitExpressionWithPrefix(" ", node.expression); + write(";"); + } + + function emitWithStatement(node: WithStatement) { + write("with ("); + emitExpression(node.expression); + write(")"); + emitEmbeddedStatement(node.statement); + } + + function emitSwitchStatement(node: SwitchStatement) { + const openParenPos = writeToken(SyntaxKind.SwitchKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emitExpression(node.expression); + writeToken(SyntaxKind.CloseParenToken, node.expression.end); + write(" "); + emit(node.caseBlock); + } + + function emitLabeledStatement(node: LabeledStatement) { + emit(node.label); + write(": "); + emit(node.statement); + } + + function emitThrowStatement(node: ThrowStatement) { + write("throw"); + emitExpressionWithPrefix(" ", node.expression); + write(";"); + } + + function emitTryStatement(node: TryStatement) { + write("try "); + emit(node.tryBlock); + emit(node.catchClause); + if (node.finallyBlock) { + writeLine(); + write("finally "); + emit(node.finallyBlock); + } + } + + function emitDebuggerStatement(node: DebuggerStatement) { + writeToken(SyntaxKind.DebuggerKeyword, node.pos); + write(";"); + } + + // + // Declarations + // + + function emitVariableDeclaration(node: VariableDeclaration) { + emit(node.name); + emitExpressionWithPrefix(" = ", node.initializer); + } + + function emitVariableDeclarationList(node: VariableDeclarationList) { + write(isLet(node) ? "let " : isConst(node) ? "const " : "var "); + emitList(node, node.declarations, ListFormat.VariableDeclarationList); + } + + function emitFunctionDeclaration(node: FunctionDeclaration) { + emitFunctionDeclarationOrExpression(node); + } + + function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write(node.asteriskToken ? "function* " : "function "); + emitSpecialized(node.name, NodeEmitFlags.NoSourceMap); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { + const body = node.body; + if (body) { + if (isBlock(body)) { + const indentedFlag = getNodeEmitFlags(node) & NodeEmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } + + const savedTempFlags = tempFlags; + tempFlags = 0; + emitSignatureHead(node); + emitBlockFunctionBodyAndEndLexicalEnvironment(node, body); + if (indentedFlag) { + decreaseIndent(); + } + + tempFlags = savedTempFlags; + } + else { + emitSignatureHead(node); + write(" "); + emitExpression(body); + } + } + else { + emitSignatureHead(node); + write(";"); + } + + } + + function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitWithPrefix(": ", node.type); + } + + function shouldEmitBlockFunctionBodyOnSingleLine(parentNode: Node, body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. + + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. + + if (getNodeEmitFlags(body) & NodeEmitFlags.SingleLine) { + return true; + } + + if (body.multiLine) { + return false; + } + + if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile)) { + return false; + } + + if (shouldWriteLeadingLineTerminator(body, body.statements, ListFormat.PreserveLines) + || shouldWriteClosingLineTerminator(body, body.statements, ListFormat.PreserveLines)) { + return false; + } + + let previousStatement: Statement; + for (const statement of body.statements) { + if (shouldWriteSeparatingLineTerminator(previousStatement, statement, ListFormat.PreserveLines)) { + return false; + } + + previousStatement = statement; + } + + return true; + } + + function emitBlockFunctionBodyAndEndLexicalEnvironment(parentNode: Node, body: Block) { + // TODO(rbuckton): This should be removed once source maps are aligned with the old + // emitter and new baselines are taken. This exists solely to + // align with the old emitter. + if (getNodeEmitFlags(body) & NodeEmitFlags.SourceMapEmitOpenBraceAsToken) { + write(" "); + writeToken(SyntaxKind.OpenBraceToken, body.pos); + } + else { + write(" {"); + } + + increaseIndent(); + emitLeadingDetachedComments(body.statements, body, shouldSkipLeadingCommentsForNode); + + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); + const helpersEmitted = emitHelpers(body); + + if (statementOffset === 0 && !helpersEmitted && shouldEmitBlockFunctionBodyOnSingleLine(parentNode, body)) { + decreaseIndent(); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); + } + else { + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); + } + + emitTrailingDetachedComments(body.statements, body, shouldSkipTrailingCommentsForNode); + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end); + } + + function emitClassDeclaration(node: ClassDeclaration) { + emitClassDeclarationOrExpression(node); + } + + function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write("class"); + emitSpecializedWithPrefix(" ", node.name, NodeEmitFlags.NoSourceMap); + + const indentedFlag = getNodeEmitFlags(node) & NodeEmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } + + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + + const savedTempFlags = tempFlags; + tempFlags = 0; + + write(" {"); + emitList(node, node.members, ListFormat.ClassMembers); + write("}"); + + if (indentedFlag) { + decreaseIndent(); + } + + tempFlags = savedTempFlags; + } + + function emitInterfaceDeclaration(node: InterfaceDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write("interface "); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + write(" {"); + emitList(node, node.members, ListFormat.InterfaceMembers); + write("}"); + } + + function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + write("type "); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + write(" = "); + emit(node.type); + write(";"); + } + + function emitEnumDeclaration(node: EnumDeclaration) { + emitModifiers(node, node.modifiers); + write("enum "); + emit(node.name); + + const savedTempFlags = tempFlags; + tempFlags = 0; + + write(" {"); + emitList(node, node.members, ListFormat.EnumMembers); + write("}"); + tempFlags = savedTempFlags; + } + + function emitModuleDeclaration(node: ModuleDeclaration) { + emitModifiers(node, node.modifiers); + write(node.flags & NodeFlags.Namespace ? "namespace " : "module "); + emit(node.name); + + let body = node.body; + while (body.kind === SyntaxKind.ModuleDeclaration) { + write("."); + emit((body).name); + body = (body).body; + } + + write(" "); + emit(body); + } + + function emitModuleBlock(node: ModuleBlock) { + if (isSingleLineEmptyBlock(node)) { + write("{ }"); + } + else { + const savedTempFlags = tempFlags; + tempFlags = 0; + write("{"); + increaseIndent(); + emitBlockStatements(node); + write("}"); + tempFlags = savedTempFlags; + } + } + + function emitCaseBlock(node: CaseBlock) { + writeToken(SyntaxKind.OpenBraceToken, node.pos); + emitList(node, node.clauses, ListFormat.CaseBlockClauses); + writeToken(SyntaxKind.CloseBraceToken, node.clauses.end); + } + + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + emitModifiers(node, node.modifiers); + write("import "); + emit(node.name); + write(" = "); + emitModuleReference(node.moduleReference); + write(";"); + } + + function emitModuleReference(node: ModuleReference) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); + } + } + + function emitImportDeclaration(node: ImportDeclaration) { + emitModifiers(node, node.modifiers); + write("import "); + if (node.importClause) { + emit(node.importClause); + write(" from "); + } + emitExpression(node.moduleSpecifier); + write(";"); + } + + function emitImportClause(node: ImportClause) { + emit(node.name); + if (node.name && node.namedBindings) { + write(", "); + } + emit(node.namedBindings); + } + + function emitNamespaceImport(node: NamespaceImport) { + write("* as "); + emit(node.name); + } + + function emitNamedImports(node: NamedImports) { + emitNamedImportsOrExports(node); + } + + function emitImportSpecifier(node: ImportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitExportAssignment(node: ExportAssignment) { + write(node.isExportEquals ? "export = " : "export default "); + emitExpression(node.expression); + write(";"); + } + + function emitExportDeclaration(node: ExportDeclaration) { + write("export "); + if (node.exportClause) { + emit(node.exportClause); + } + else { + write("*"); + } + if (node.moduleSpecifier) { + write(" from "); + emitExpression(node.moduleSpecifier); + } + write(";"); + } + + function emitNamedExports(node: NamedExports) { + emitNamedImportsOrExports(node); + } + + function emitExportSpecifier(node: ExportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitNamedImportsOrExports(node: NamedImportsOrExports) { + write("{"); + emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + write("}"); + } + + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.propertyName) { + emit(node.propertyName); + write(" as "); + } + + emit(node.name); + } + + // + // Module references + // + + function emitExternalModuleReference(node: ExternalModuleReference) { + write("require("); + emitExpression(node.expression); + write(")"); + } + + // + // JSX + // + + function emitJsxElement(node: JsxElement) { + emit(node.openingElement); + emitList(node, node.children, ListFormat.JsxElementChildren); + emit(node.closingElement); + } + + function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { + write("<"); + emitJsxTagName(node.tagName); + write(" "); + emitList(node, node.attributes, ListFormat.JsxElementAttributes); + write("/>"); + } + + function emitJsxOpeningElement(node: JsxOpeningElement) { + write("<"); + emitJsxTagName(node.tagName); + writeIfAny(node.attributes, " "); + emitList(node, node.attributes, ListFormat.JsxElementAttributes); + write(">"); + } + + function emitJsxText(node: JsxText) { + writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true)); + } + + function emitJsxClosingElement(node: JsxClosingElement) { + write(""); + } + + function emitJsxAttribute(node: JsxAttribute) { + emit(node.name); + emitWithPrefix("=", node.initializer); + } + + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + write("{..."); + emitExpression(node.expression); + write("}"); + } + + function emitJsxExpression(node: JsxExpression) { + if (node.expression) { + write("{"); + emitExpression(node.expression); + write("}"); + } + } + + function emitJsxTagName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); + } + } + + // + // Clauses + // + + function emitCaseClause(node: CaseClause) { + write("case "); + emitExpression(node.expression); + write(":"); + + emitCaseOrDefaultClauseStatements(node, node.statements); + } + + function emitDefaultClause(node: DefaultClause) { + write("default:"); + emitCaseOrDefaultClauseStatements(node, node.statements); + } + + function emitCaseOrDefaultClauseStatements(parentNode: Node, statements: NodeArray) { + const emitAsSingleStatement = + statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile) + ); + + if (emitAsSingleStatement) { + write(" "); + emit(statements[0]); + } + else { + emitList(parentNode, statements, ListFormat.CaseOrDefaultClauseStatements); + } + } + + function emitHeritageClause(node: HeritageClause) { + write(" "); + writeTokenText(node.token); + write(" "); + emitList(node, node.types, ListFormat.HeritageClauseTypes); + } + + function emitCatchClause(node: CatchClause) { + writeLine(); + const openParenPos = writeToken(SyntaxKind.CatchKeyword, node.pos); + write(" "); + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emit(node.variableDeclaration); + writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration ? node.variableDeclaration.end : openParenPos); + write(" "); + emit(node.block); + } + + // + // Property assignments + // + + function emitPropertyAssignment(node: PropertyAssignment) { + emit(node.name); + write(": "); + // This is to ensure that we emit comment in the following case: + // For example: + // obj = { + // id: /*comment1*/ ()=>void + // } + // "comment1" is not considered to be leading comment for node.initializer + // but rather a trailing comment on the previous node. + if (!shouldSkipLeadingCommentsForNode(node.initializer)) { + emitLeadingComments(node.initializer, getTrailingComments(collapseRangeToStart(node.initializer))); + } + emitExpression(node.initializer); + } + + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + emit(node.name); + if (node.objectAssignmentInitializer) { + write(" = "); + emitExpression(node.objectAssignmentInitializer); + } + } + + // + // Enum + // + + function emitEnumMember(node: EnumMember) { + emit(node.name); + emitExpressionWithPrefix(" = ", node.initializer); + } + + // + // Top-level nodes + // + + function emitSourceFile(node: SourceFile) { + writeLine(); + emitShebang(); + emitLeadingDetachedComments(node); + + const statements = node.statements; + const statementOffset = emitPrologueDirectives(statements); + if (getNodeEmitFlags(node) & NodeEmitFlags.NoLexicalEnvironment) { + emitHelpers(node); + emitList(node, statements, ListFormat.MultiLine, statementOffset); + } + else { + const savedTempFlags = tempFlags; + tempFlags = 0; + emitHelpers(node); + emitList(node, statements, ListFormat.MultiLine, statementOffset); + tempFlags = savedTempFlags; + } + + emitTrailingDetachedComments(node.statements); + } + + // Transformation nodes + + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + emitExpression(node.expression); + } + + /** + * Emits any prologue directives at the start of a Statement list, returning the + * number of prologue directives written to the output. + */ + function emitPrologueDirectives(statements: Node[], startWithNewLine?: boolean): number { + for (let i = 0; i < statements.length; i++) { + if (isPrologueDirective(statements[i])) { + if (startWithNewLine || i > 0) { + writeLine(); + } + emit(statements[i]); + } + else { + // return index of the first non prologue directive + return i; + } + } + + return statements.length; + } + + function emitHelpers(node: Node) { + const emitFlags = getNodeEmitFlags(node); + let helpersEmitted = false; + if (emitFlags & NodeEmitFlags.EmitEmitHelpers) { + helpersEmitted = emitEmitHelpers(currentSourceFile); + } + + if (emitFlags & NodeEmitFlags.EmitExportStar) { + writeLines(exportStarHelper); + helpersEmitted = true; + } + + if (emitFlags & NodeEmitFlags.EmitSuperHelper) { + writeLines(superHelper); + helpersEmitted = true; + } + + if (emitFlags & NodeEmitFlags.EmitAdvancedSuperHelper) { + writeLines(advancedSuperHelper); + helpersEmitted = true; + } + + return helpersEmitted; + } + + function emitEmitHelpers(node: SourceFile) { + let helpersEmitted = false; + + // Only emit helpers if the user did not say otherwise. + if (!compilerOptions.noEmitHelpers) { + // Only Emit __extends function when target ES5. + // For target ES6 and above, we can emit classDeclaration as is. + if ((languageVersion < ScriptTarget.ES6) && (!extendsEmitted && node.flags & NodeFlags.HasClassExtends)) { + writeLines(extendsHelper); + extendsEmitted = true; + helpersEmitted = true; + } + + if (compilerOptions.jsx !== JsxEmit.Preserve && !assignEmitted && (node.flags & NodeFlags.HasJsxSpreadAttribute)) { + writeLines(assignHelper); + assignEmitted = true; + } + + if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) { + writeLines(decorateHelper); + if (compilerOptions.emitDecoratorMetadata) { + writeLines(metadataHelper); + } + + decorateEmitted = true; + helpersEmitted = true; + } + + if (!paramEmitted && node.flags & NodeFlags.HasParamDecorators) { + writeLines(paramHelper); + paramEmitted = true; + helpersEmitted = true; + } + + if (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions) { + writeLines(awaiterHelper); + awaiterEmitted = true; + helpersEmitted = true; + } + + if (helpersEmitted) { + writeLine(); + } + } + + return helpersEmitted; + } + + function writeLines(text: string): void { + const lines = text.split(/\r\n|\r|\n/g); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.length) { + if (i > 0) { + writeLine(); + } + write(line); + } + } + } + + // + // Helpers + // + + function emitShebang() { + const shebang = getShebang(currentText); + if (shebang) { + write(shebang); + writeLine(); + } + } + + function emitModifiers(node: Node, modifiers: NodeArray) { + if (modifiers && modifiers.length) { + emitList(node, modifiers, ListFormat.Modifiers); + write(" "); + } + } + + function emitWithPrefix(prefix: string, node: Node) { + emitNodeWithPrefix(prefix, node, emit); + } + + // TODO(rbuckton): This should be removed once source maps are aligned with the old + // emitter and new baselines are taken. This exists solely to + // align with the old emitter. + function emitSpecializedWithPrefix(prefix: string, node: Node, flags: NodeEmitFlags) { + if (node) { + write(prefix); + emitSpecialized(node, flags); + } + } + + function emitExpressionWithPrefix(prefix: string, node: Node) { + emitNodeWithPrefix(prefix, node, emitExpression); + } + + function emitNodeWithPrefix(prefix: string, node: Node, emit: (node: Node) => void) { + if (node) { + write(prefix); + emit(node); + } + } + + function emitWithSuffix(node: Node, suffix: string) { + if (node) { + emit(node); + write(suffix); + } + } + + function tryEmitSubstitute(node: Node, emitNode: (node: Node) => void, isExpression: boolean) { + if (isSubstitutionEnabled(node) && (getNodeEmitFlags(node) & NodeEmitFlags.NoSubstitution) === 0) { + const substitute = onSubstituteNode(node, isExpression); + if (substitute !== node) { + setNodeEmitFlags(substitute, NodeEmitFlags.NoSubstitution | getNodeEmitFlags(substitute)); + emitNode(substitute); + return true; + } + } + + return false; + } + + function tryEmitConstantValue(node: PropertyAccessExpression | ElementAccessExpression): boolean { + const constantValue = tryGetConstEnumValue(node); + if (constantValue !== undefined) { + write(String(constantValue)); + if (!compilerOptions.removeComments) { + const propertyName = isPropertyAccessExpression(node) + ? declarationNameToString(node.name) + : getTextOfNode(node.argumentExpression); + write(` /* ${propertyName} */`); + } + + return true; + } + + return false; + } + + function emitEmbeddedStatement(node: Statement) { + if (isBlock(node)) { + write(" "); + emit(node); + } + else { + writeLine(); + increaseIndent(); + emit(node); + decreaseIndent(); + } + } + + function emitDecorators(parentNode: Node, decorators: NodeArray) { + emitList(parentNode, decorators, ListFormat.Decorators); + } + + function emitTypeArguments(parentNode: Node, typeArguments: NodeArray) { + emitList(parentNode, typeArguments, ListFormat.TypeArguments); + } + + function emitTypeParameters(parentNode: Node, typeParameters: NodeArray) { + emitList(parentNode, typeParameters, ListFormat.TypeParameters); + } + + function emitParameters(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.Parameters); + } + + function emitParametersForArrow(parentNode: Node, parameters: NodeArray) { + if (parameters && + parameters.length === 1 && + parameters[0].type === undefined && + parameters[0].pos === parentNode.pos) { + emit(parameters[0]); + } + else { + emitParameters(parentNode, parameters); + } + } + + function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + } + + function emitList(parentNode: Node, children: NodeArray, format: ListFormat, start?: number, count?: number) { + emitNodeList(emit, parentNode, children, format, start, count); + } + + function emitExpressionList(parentNode: Node, children: NodeArray, format: ListFormat, start?: number, count?: number) { + emitNodeList(emitExpression, parentNode, children, format, start, count); + } + + function emitNodeList(emit: (node: Node) => void, parentNode: Node, children: NodeArray, format: ListFormat, start = 0, count = children ? children.length - start : 0) { + const isUndefined = children === undefined; + if (isUndefined && format & ListFormat.OptionalIfUndefined) { + return; + } + + const isEmpty = isUndefined || children.length === 0 || start >= children.length || count === 0; + if (isEmpty && format & ListFormat.OptionalIfEmpty) { + return; + } + + if (format & ListFormat.BracketsMask) { + write(getOpeningBracket(format)); + } + + if (isEmpty) { + // Write a line terminator if the parent node was multi-line + if (format & ListFormat.MultiLine) { + writeLine(); + } + else if (format & ListFormat.SpaceBetweenBraces) { + write(" "); + } + } + else { + // Write the opening line terminator or leading whitespace. + let shouldEmitInterveningComments = true; + if (shouldWriteLeadingLineTerminator(parentNode, children, format)) { + writeLine(); + shouldEmitInterveningComments = false; + } + else if (format & ListFormat.SpaceBetweenBraces) { + write(" "); + } + + // Increase the indent, if requested. + if (format & ListFormat.Indented) { + increaseIndent(); + } + + // Emit each child. + let previousSibling: Node; + let shouldDecreaseIndentAfterEmit: boolean; + const delimiter = getDelimiter(format); + for (let i = 0; i < count; i++) { + const child = children[start + i]; + + // Write the delimiter if this is not the first node. + if (previousSibling) { + write(delimiter); + + // Write either a line terminator or whitespace to separate the elements. + if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) { + // If a synthesized node in a single-line list starts on a new + // line, we should increase the indent. + if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { + increaseIndent(); + shouldDecreaseIndentAfterEmit = true; + } + + writeLine(); + shouldEmitInterveningComments = false; + } + else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { + write(" "); + } + } + + if (shouldEmitInterveningComments) { + emitLeadingComments(child, getTrailingCommentsOfPosition(child.pos)); + } + else { + shouldEmitInterveningComments = true; + } + + // Emit this child. + emit(child); + + if (shouldDecreaseIndentAfterEmit) { + decreaseIndent(); + shouldDecreaseIndentAfterEmit = false; + } + + previousSibling = child; + } + + // Write a trailing comma, if requested. + const hasTrailingComma = (format & ListFormat.AllowTrailingComma) && children.hasTrailingComma; + if (format & ListFormat.CommaDelimited && hasTrailingComma) { + write(","); + } + + // Decrease the indent, if requested. + if (format & ListFormat.Indented) { + decreaseIndent(); + } + + // Write the closing line terminator or closing whitespace. + if (shouldWriteClosingLineTerminator(parentNode, children, format)) { + writeLine(); + } + else if (format & ListFormat.SpaceBetweenBraces) { + write(" "); + } + } + + if (format & ListFormat.BracketsMask) { + write(getClosingBracket(format)); + } + } + + function writeIfAny(nodes: NodeArray, text: string) { + if (nodes && nodes.length > 0) { + write(text); + } + } + + function writeIfPresent(node: Node, text: string) { + if (node !== undefined) { + write(text); + } + } + + function writeToken(token: SyntaxKind, tokenStartPos: number): number; + function writeToken(token: SyntaxKind, tokenStartPos: number, contextNode: Node, shouldIgnoreSourceMapForTokenCallback: (contextNode: Node) => boolean): number; + function writeToken(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, shouldIgnoreSourceMapForTokenCallback?: (contextNode: Node) => boolean) { + tokenStartPos = skipTrivia(currentText, tokenStartPos); + emitPos(tokenStartPos, contextNode, shouldIgnoreSourceMapForTokenCallback); + const tokenEndPos = writeTokenText(token, tokenStartPos); + emitPos(tokenEndPos, contextNode, shouldIgnoreSourceMapForTokenCallback); + return tokenEndPos; + } + + function shouldSkipSourceMapForToken(contextNode: Node) { + return (getNodeEmitFlags(contextNode) & NodeEmitFlags.NoTokenSourceMaps) !== 0; + } + + function writeTokenText(token: SyntaxKind, pos?: number) { + const tokenString = tokenToString(token); + write(tokenString); + return positionIsSynthesized(pos) ? -1 : pos + tokenString.length; + } + + function writeTokenNode(node: Node) { + if (node) { + emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren); + writeTokenText(node.kind); + emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren); + } + } + + function increaseIndentIf(value: boolean, valueToWriteWhenNotIndenting?: string) { + if (value) { + increaseIndent(); + writeLine(); + } + else if (valueToWriteWhenNotIndenting) { + write(valueToWriteWhenNotIndenting); + } + } + + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean, value2?: boolean) { + if (value1) { + decreaseIndent(); + } + if (value2) { + decreaseIndent(); + } + } + + function shouldWriteLeadingLineTerminator(parentNode: Node, children: NodeArray, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return true; + } + + if (format & ListFormat.PreserveLines) { + if (format & ListFormat.PreferNewLine) { + return true; + } + + const firstChild = children[0]; + if (firstChild === undefined) { + return !rangeIsOnSingleLine(parentNode, currentSourceFile); + } + else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(firstChild)) { + return synthesizedNodeStartsOnNewLine(firstChild, format); + } + else { + return !rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile); + } + } + else { + return false; + } + } + + function shouldWriteSeparatingLineTerminator(previousNode: Node, nextNode: Node, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return true; + } + else if (format & ListFormat.PreserveLines) { + if (previousNode === undefined || nextNode === undefined) { + return false; + } + else if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) { + return synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format); + } + else { + return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile); + } + } + else { + return nextNode.startsOnNewLine; + } + } + + function shouldWriteClosingLineTerminator(parentNode: Node, children: NodeArray, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return (format & ListFormat.NoTrailingNewLine) === 0; + } + else if (format & ListFormat.PreserveLines) { + if (format & ListFormat.PreferNewLine) { + return true; + } + + const lastChild = lastOrUndefined(children); + if (lastChild === undefined) { + return !rangeIsOnSingleLine(parentNode, currentSourceFile); + } + else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(lastChild)) { + return synthesizedNodeStartsOnNewLine(lastChild, format); + } + else { + return !rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile); + } + } + else { + return false; + } + } + + function synthesizedNodeStartsOnNewLine(node: Node, format?: ListFormat) { + if (nodeIsSynthesized(node)) { + const startsOnNewLine = node.startsOnNewLine; + if (startsOnNewLine === undefined) { + return (format & ListFormat.PreferNewLine) !== 0; + } + + return startsOnNewLine; + } + + return (format & ListFormat.PreferNewLine) !== 0; + } + + function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); + + // Always use a newline for synthesized code if the synthesizer desires it. + if (node2.startsOnNewLine) { + return true; + } + + return !nodeIsSynthesized(parent) + && !nodeIsSynthesized(node1) + && !nodeIsSynthesized(node2) + && !rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile); + } + + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node).expression; + } + + return node; + } + + function getTextOfNode(node: Node, includeTrivia?: boolean): string { + if (isGeneratedIdentifier(node)) { + return getGeneratedIdentifier(node); + } + else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent)) { + return unescapeIdentifier(node.text); + } + else if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + return getTextOfNode((node).textSourceNode, includeTrivia); + } + else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { + return node.text; + } + + return getSourceTextOfNodeFromSourceFile(currentSourceFile, node, includeTrivia); + } + + function getLiteralTextOfNode(node: LiteralLikeNode): string { + if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + const textSourceNode = (node).textSourceNode; + if (isIdentifier(textSourceNode)) { + return "\"" + escapeNonAsciiCharacters(escapeString(getTextOfNode(textSourceNode))) + "\""; + } + else { + return getLiteralTextOfNode(textSourceNode); + } + } + + return getLiteralText(node, currentSourceFile, languageVersion); + } + + function tryGetConstEnumValue(node: Node): number { + if (compilerOptions.isolatedModules) { + return undefined; + } + + return isPropertyAccessExpression(node) || isElementAccessExpression(node) + ? resolver.getConstantValue(node) + : undefined; + } + + function isSingleLineEmptyBlock(block: Block) { + return !block.multiLine + && block.statements.length === 0 + && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile); + } + + function isUniqueName(name: string): boolean { + return !resolver.hasGlobalName(name) && + !hasProperty(currentFileIdentifiers, name) && + !hasProperty(generatedNameSet, name); + } + + function isUniqueLocalName(name: string, container: Node): boolean { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer) { + if (node.locals && hasProperty(node.locals, name)) { + // 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; + } + } + } + return true; + } + + /** + * Return the next available name in the pattern _a ... _z, _0, _1, ... + * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + */ + function makeTempVariableName(flags: TempFlags): string { + if (flags && !(tempFlags & flags)) { + const name = flags === TempFlags._i ? "_i" : "_n"; + if (isUniqueName(name)) { + tempFlags |= flags; + return name; + } + } + while (true) { + const count = tempFlags & TempFlags.CountMask; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + const name = count < 26 + ? "_" + String.fromCharCode(CharacterCodes.a + count) + : "_" + (count - 26); + if (isUniqueName(name)) { + return name; + } + } + } + } + + // Generate a name that is unique within the current file and doesn't conflict with any names + // in global scope. The name is formed by adding an '_n' suffix to the specified base name, + // where n is a positive integer. Note that names generated by makeTempVariableName and + // makeUniqueName are guaranteed to never conflict. + function makeUniqueName(baseName: string): string { + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + let i = 1; + while (true) { + const generatedName = baseName + i; + if (isUniqueName(generatedName)) { + return generatedNameSet[generatedName] = generatedName; + } + i++; + } + } + + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = getTextOfNode(node.name); + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name, node) ? name : makeUniqueName(name); + } + + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = getExternalModuleName(node); + const baseName = expr.kind === SyntaxKind.StringLiteral ? + escapeIdentifier(makeIdentifierFromModuleName((expr).text)) : "module"; + return makeUniqueName(baseName); + } + + function generateNameForExportDefault() { + return makeUniqueName("default"); + } + + function generateNameForClassExpression() { + return makeUniqueName("class"); + } + + function generateNameForNode(node: Node): string { + switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName(getTextOfNode(node)); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + return generateNameForModuleOrEnum(node); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return generateNameForImportOrExportDeclaration(node); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + return generateNameForClassExpression(); + default: + return makeTempVariableName(TempFlags.Auto); + } + } + + function generateIdentifier(node: Identifier) { + switch (node.autoGenerateKind) { + case GeneratedIdentifierKind.Auto: + return makeTempVariableName(TempFlags.Auto); + case GeneratedIdentifierKind.Loop: + return makeTempVariableName(TempFlags._i); + case GeneratedIdentifierKind.Unique: + return makeUniqueName(node.text); + case GeneratedIdentifierKind.Node: + return generateNameForNode(getSourceNodeForGeneratedName(node)); + } + } + + function getGeneratedIdentifier(node: Identifier) { + const id = getNodeIdForGeneratedIdentifier(node); + return nodeToGeneratedName[id] || (nodeToGeneratedName[id] = unescapeIdentifier(generateIdentifier(node))); + } + + function getSourceNodeForGeneratedName(name: Identifier) { + let node: Node = name; + while (node.original !== undefined) { + const nodeId = node.id; + node = node.original; + // If "node" is not the exact clone of "original" identifier, use "original" identifier to generate the name + if (isIdentifier(node) && node.autoGenerateKind === GeneratedIdentifierKind.Node && node.id !== nodeId) { + break; + } + } + + return node; + } + + function getNodeIdForGeneratedIdentifier(node: Identifier) { + switch (node.autoGenerateKind) { + case GeneratedIdentifierKind.Auto: + case GeneratedIdentifierKind.Loop: + case GeneratedIdentifierKind.Unique: + return getNodeId(node); + case GeneratedIdentifierKind.Node: + return getNodeId(getSourceNodeForGeneratedName(node)); + } + } + } + + function createDelimiterMap() { + const delimiters: string[] = []; + delimiters[ListFormat.None] = ""; + delimiters[ListFormat.CommaDelimited] = ","; + delimiters[ListFormat.BarDelimited] = " |"; + delimiters[ListFormat.AmpersandDelimited] = " &"; + return delimiters; + } + + function getDelimiter(format: ListFormat) { + return delimiters[format & ListFormat.DelimitersMask]; + } + + function createBracketsMap() { + const brackets: string[][] = []; + brackets[ListFormat.Braces] = ["{", "}"]; + brackets[ListFormat.Parenthesis] = ["(", ")"]; + brackets[ListFormat.AngleBrackets] = ["<", ">"]; + brackets[ListFormat.SquareBrackets] = ["[", "]"]; + return brackets; + } + + function getOpeningBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][0]; + } + + function getClosingBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][1]; + } + } + + const enum ListFormat { + None = 0, + + // Line separators + SingleLine = 0, // Prints the list on a single line (default). + MultiLine = 1 << 0, // Prints the list on multiple lines. + PreserveLines = 1 << 1, // Prints the list using line preservation if possible. + LinesMask = SingleLine | MultiLine | PreserveLines, + + // Delimiters + NotDelimited = 0, // There is no delimiter between list items (default). + BarDelimited = 1 << 2, // Each list item is space-and-bar (" |") delimited. + AmpersandDelimited = 1 << 3, // Each list item is space-and-ampersand (" &") delimited. + CommaDelimited = 1 << 4, // Each list item is comma (",") delimited. + DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited, + + AllowTrailingComma = 1 << 5, // Write a trailing comma (",") if present. + + // Whitespace + Indented = 1 << 6, // The list should be indented. + SpaceBetweenBraces = 1 << 7, // Inserts a space after the opening brace and before the closing brace. + SpaceBetweenSiblings = 1 << 8, // Inserts a space between each sibling node. + + // Brackets/Braces + Braces = 1 << 9, // The list is surrounded by "{" and "}". + Parenthesis = 1 << 10, // The list is surrounded by "(" and ")". + AngleBrackets = 1 << 11, // The list is surrounded by "<" and ">". + SquareBrackets = 1 << 12, // The list is surrounded by "[" and "]". + BracketsMask = Braces | Parenthesis | AngleBrackets | SquareBrackets, + + OptionalIfUndefined = 1 << 13, // Do not emit brackets if the list is undefined. + OptionalIfEmpty = 1 << 14, // Do not emit brackets if the list is empty. + Optional = OptionalIfUndefined | OptionalIfEmpty, + + // Other + PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes. + NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list. + + // Precomputed Formats + Modifiers = SingleLine | SpaceBetweenSiblings, + HeritageClauses = SingleLine | SpaceBetweenSiblings, + TypeLiteralMembers = MultiLine | Indented, + TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented, + UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine, + IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine, + ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings, + ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings, + ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces, + ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, + CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, + NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, + TemplateExpressionSpans = SingleLine, + SingleLineBlockStatements = SpaceBetweenBraces | SpaceBetweenSiblings | SingleLine, + MultiLineBlockStatements = Indented | MultiLine, + VariableDeclarationList = CommaDelimited | SpaceBetweenSiblings | SingleLine, + SingleLineFunctionBodyStatements = SingleLine | SpaceBetweenSiblings | SpaceBetweenBraces, + MultiLineFunctionBodyStatements = MultiLine, + ClassHeritageClauses = SingleLine | SpaceBetweenSiblings, + ClassMembers = Indented | MultiLine, + InterfaceMembers = Indented | MultiLine, + EnumMembers = CommaDelimited | Indented | MultiLine, + CaseBlockClauses = Indented | MultiLine, + NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces, + JsxElementChildren = SingleLine, + JsxElementAttributes = SingleLine | SpaceBetweenSiblings, + CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty, + HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine, + SourceFileStatements = MultiLine | NoTrailingNewLine, + Decorators = MultiLine | Optional, + TypeArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | AngleBrackets | Optional, + TypeParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | AngleBrackets | Optional, + Parameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | Parenthesis, + IndexSignatureParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | SquareBrackets, + } +} diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 658d6946a3956..bf5b7cb57278e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,6 +1,7 @@ /// /// /// +/// namespace ts { /* @internal */ export let programTime = 0; @@ -886,7 +887,8 @@ namespace ts { fileExists: fileName => sys.fileExists(fileName), readFile: fileName => sys.readFile(fileName), trace: (s: string) => sys.write(s + newLine), - directoryExists: directoryName => sys.directoryExists(directoryName) + directoryExists: directoryName => sys.directoryExists(directoryName), + getEnvironmentVariable: name => getEnvironmentVariable(name, /*host*/ undefined) }; } @@ -1288,7 +1290,14 @@ namespace ts { const start = new Date().getTime(); - const emitResult = emitFiles( + // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. + let useLegacyEmitter = options.useLegacyEmitter; + if (/^(no?|f(alse)?|0|-)$/i.test(getEnvironmentVariable("USE_TRANSFORMS", host))) { + useLegacyEmitter = true; + } + + const fileEmitter = useLegacyEmitter ? emitFiles : printFiles; + const emitResult = fileEmitter( emitResolver, getEmitHost(writeFileCallback), sourceFile); @@ -1523,7 +1532,7 @@ namespace ts { return false; } - function checkModifiers(modifiers: ModifiersArray): boolean { + function checkModifiers(modifiers: NodeArray): boolean { if (modifiers) { for (const modifier of modifiers) { switch (modifier.kind) { @@ -1640,7 +1649,7 @@ namespace ts { } break; case SyntaxKind.ModuleDeclaration: - if (isAmbientModule(node) && (inAmbientModule || node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { + if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || isDeclarationFile(file))) { const moduleName = (node).name; // Ambient module declarations can be interpreted as augmentations for some existing external modules. // This will happen in two cases: @@ -2067,7 +2076,10 @@ namespace ts { } // Cannot specify module gen that isn't amd or system with --out - if (outFile && options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { + // Report this error if user specified --module moduleKind + // or if there is external module in compilation which defaults to commonjs + const emitModuleKind = getEmitModuleKind(options); + if (outFile && (options.module || firstExternalModuleSourceFile) && !(emitModuleKind === ModuleKind.AMD || emitModuleKind === ModuleKind.System)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile")); } @@ -2095,7 +2107,7 @@ namespace ts { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators")); } - if (options.reactNamespace && !isIdentifier(options.reactNamespace, languageVersion)) { + if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace)); } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 8979814a7a2a6..1e280a06cf610 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -434,9 +434,7 @@ namespace ts { /* @internal */ export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean): number { - // Using ! with a greater than test is a fast way of testing the following conditions: - // pos === undefined || pos === null || isNaN(pos) || pos < 0; - if (!(pos >= 0)) { + if (positionIsSynthesized(pos)) { return pos; } @@ -643,6 +641,7 @@ namespace ts { pos++; } } + if (collecting) { if (!result) { result = []; @@ -697,7 +696,7 @@ namespace ts { } /* @internal */ - export function isIdentifier(name: string, languageVersion: ScriptTarget): boolean { + export function isIdentifierText(name: string, languageVersion: ScriptTarget): boolean { if (!isIdentifierStart(name.charCodeAt(0), languageVersion)) { return false; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 86bad38cb6e1f..832a89a4c56e5 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -5,14 +5,20 @@ namespace ts { export interface SourceMapWriter { getSourceMapData(): SourceMapData; setSourceFile(sourceFile: SourceFile): void; + emitPos(pos: number, contextNode: Node, shouldIgnorePosCallback: (node: Node) => boolean): void; emitPos(pos: number): void; + emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; emitStart(range: TextRange): void; - emitEnd(range: TextRange, stopOverridingSpan?: boolean): void; - changeEmitSourcePos(): void; + emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; + emitEnd(range: TextRange): void; + /*@deprecated*/ changeEmitSourcePos(): void; + /*@deprecated*/ stopOverridingSpan(): void; getText(): string; getSourceMappingURL(): string; initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void; reset(): void; + enable(): void; + disable(): void; } let nullSourceMapWriter: SourceMapWriter; @@ -30,14 +36,17 @@ namespace ts { nullSourceMapWriter = { getSourceMapData(): SourceMapData { return undefined; }, setSourceFile(sourceFile: SourceFile): void { }, - emitStart(range: TextRange): void { }, - emitEnd(range: TextRange, stopOverridingSpan?: boolean): void { }, + emitStart(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void { }, + emitEnd(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void { }, emitPos(pos: number): void { }, changeEmitSourcePos(): void { }, + stopOverridingSpan(): void { }, getText(): string { return undefined; }, getSourceMappingURL(): string { return undefined; }, initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void { }, reset(): void { }, + enable(): void { }, + disable(): void { } }; } @@ -62,6 +71,13 @@ namespace ts { // Source map data let sourceMapData: SourceMapData; + // This keeps track of the number of times `disable` has been called without a + // corresponding call to `enable`. As long as this value is non-zero, mappings will not + // be recorded. + // This is primarily used to provide a better experience when debugging binding + // patterns and destructuring assignments for simple expressions. + let disableDepth: number; + return { getSourceMapData: () => sourceMapData, setSourceFile, @@ -69,10 +85,13 @@ namespace ts { emitStart, emitEnd, changeEmitSourcePos, + stopOverridingSpan: () => stopOverridingSpan = true, getText, getSourceMappingURL, initialize, reset, + enable, + disable, }; function initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { @@ -81,6 +100,7 @@ namespace ts { } currentSourceFile = undefined; + disableDepth = 0; // Current source map file and its index in the sources list sourceMapSourceIndex = -1; @@ -147,6 +167,23 @@ namespace ts { lastEncodedSourceMapSpan = undefined; lastEncodedNameIndex = undefined; sourceMapData = undefined; + disableDepth = 0; + } + + /** + * Re-enables the recording of mappings. + */ + function enable() { + if (disableDepth > 0) { + disableDepth--; + } + } + + /** + * Disables the recording of mappings. + */ + function disable() { + disableDepth++; } function updateLastEncodedAndRecordedSpans() { @@ -168,7 +205,7 @@ namespace ts { sourceMapData.sourceMapDecodedMappings[sourceMapData.sourceMapDecodedMappings.length - 1] : defaultLastEncodedSourceMapSpan; - // TODO: Update lastEncodedNameIndex + // TODO: Update lastEncodedNameIndex // Since we dont support this any more, lets not worry about it right now. // When we start supporting nameIndex, we will get back to this @@ -235,8 +272,12 @@ namespace ts { sourceMapData.sourceMapDecodedMappings.push(lastEncodedSourceMapSpan); } - function emitPos(pos: number) { - if (pos === -1) { + function emitPos(pos: number, contextNode?: Node, shouldIgnorePosCallback?: (node: Node) => boolean) { + if (positionIsSynthesized(pos) || disableDepth > 0) { + return; + } + + if (shouldIgnorePosCallback && shouldIgnorePosCallback(contextNode)) { return; } @@ -286,13 +327,30 @@ namespace ts { return range.pos !== -1 ? skipTrivia(currentSourceFile.text, rangeHasDecorators ? (range as Node).decorators.end : range.pos) : -1; } - function emitStart(range: TextRange) { - emitPos(getStartPos(range)); + function emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; + function emitStart(range: TextRange): void; + function emitStart(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean) { + if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(range))) { + emitPos(getStartPos(range)); + } + + if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(range)) { + disable(); + } } - function emitEnd(range: TextRange, stopOverridingEnd?: boolean) { - emitPos(range.end); - stopOverridingSpan = stopOverridingEnd; + function emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; + function emitEnd(range: TextRange): void; + function emitEnd(range: TextRange, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean) { + if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(range)) { + enable(); + } + + if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(range))) { + emitPos(range.end); + } + + stopOverridingSpan = false; } function changeEmitSourcePos() { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c391560befd90..f302940d1ac4c 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -24,6 +24,8 @@ namespace ts { createHash?(data: string): string; getMemoryUsage?(): number; exit(exitCode?: number): void; + /*@internal*/ getEnvironmentVariable(name: string): string; + /*@internal*/ tryEnableSourceMapsForHost?(): void; } interface WatchedFile { @@ -74,6 +76,7 @@ namespace ts { readDirectory(path: string, extension?: string, exclude?: string[]): string[]; watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + getEnvironmentVariable?(name: string): string; }; export var sys: System = (function () { @@ -213,6 +216,9 @@ namespace ts { getCurrentDirectory() { return new ActiveXObject("WScript.Shell").CurrentDirectory; }, + getEnvironmentVariable(name: string) { + return new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(`%${name}%`); + }, readDirectory, exit(exitCode?: number): void { try { @@ -586,6 +592,9 @@ namespace ts { getCurrentDirectory() { return process.cwd(); }, + getEnvironmentVariable(name: string) { + return process.env[name] || ""; + }, readDirectory, getModifiedTime(path) { try { @@ -608,6 +617,14 @@ namespace ts { }, exit(exitCode?: number): void { process.exit(exitCode); + }, + tryEnableSourceMapsForHost() { + try { + require("source-map-support").install(); + } + catch (e) { + // Could not enable source maps. + } } }; } @@ -637,6 +654,7 @@ namespace ts { createDirectory: ChakraHost.createDirectory, getExecutingFilePath: () => ChakraHost.executingFile, getCurrentDirectory: () => ChakraHost.currentDirectory, + getEnvironmentVariable: ChakraHost.getEnvironmentVariable || ((name: string) => ""), readDirectory: ChakraHost.readDirectory, exit: ChakraHost.quit, }; diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts new file mode 100644 index 0000000000000..ab28e493c9d01 --- /dev/null +++ b/src/compiler/transformer.ts @@ -0,0 +1,328 @@ +/// +/// +/// +/// +/// +/// +/// +/// + +/* @internal */ +namespace ts { + const moduleTransformerMap: Map = { + [ModuleKind.ES6]: transformES6Module, + [ModuleKind.System]: transformSystemModule, + [ModuleKind.AMD]: transformModule, + [ModuleKind.CommonJS]: transformModule, + [ModuleKind.UMD]: transformModule, + [ModuleKind.None]: transformModule, + }; + + const enum SyntaxKindFeatureFlags { + Substitution = 1 << 0, + EmitNotifications = 1 << 1, + } + + export function getTransformers(compilerOptions: CompilerOptions) { + const jsx = compilerOptions.jsx; + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const transformers: Transformer[] = []; + + transformers.push(transformTypeScript); + transformers.push(moduleTransformerMap[moduleKind]); + + if (jsx === JsxEmit.React) { + transformers.push(transformJsx); + } + + transformers.push(transformES7); + + if (languageVersion < ScriptTarget.ES6) { + transformers.push(transformES6); + } + + return transformers; + } + + /** + * Transforms an array of SourceFiles by passing them through each transformer. + * + * @param resolver The emit resolver provided by the checker. + * @param host The emit host. + * @param sourceFiles An array of source files + * @param transforms An array of Transformers. + */ + export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { + const nodeEmitFlags: NodeEmitFlags[] = []; + const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + let lexicalEnvironmentStackOffset = 0; + let hoistedVariableDeclarations: VariableDeclaration[]; + let hoistedFunctionDeclarations: FunctionDeclaration[]; + let currentSourceFile: SourceFile; + let lexicalEnvironmentDisabled: boolean; + + // The transformation context is provided to each transformer as part of transformer + // initialization. + const context: TransformationContext = { + getCompilerOptions: () => host.getCompilerOptions(), + getEmitResolver: () => resolver, + getEmitHost: () => host, + getNodeEmitFlags, + setNodeEmitFlags, + hoistVariableDeclaration, + hoistFunctionDeclaration, + startLexicalEnvironment, + endLexicalEnvironment, + onSubstituteNode, + enableSubstitution, + isSubstitutionEnabled, + onEmitNode, + enableEmitNotification, + isEmitNotificationEnabled + }; + + // Chain together and initialize each transformer. + const transformation = chain(...transformers)(context); + + // Transform each source file. + return map(sourceFiles, transformSourceFile); + + /** + * Transforms a source file. + * + * @param sourceFile The source file to transform. + */ + function transformSourceFile(sourceFile: SourceFile) { + if (isDeclarationFile(sourceFile)) { + return sourceFile; + } + + currentSourceFile = sourceFile; + return transformation(sourceFile); + } + + function enableSubstitution(kind: SyntaxKind) { + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; + } + + function isSubstitutionEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0; + } + + /** + * Default hook for node substitutions. + * + * @param node The node to substitute. + * @param isExpression A value indicating whether the node is to be used in an expression + * position. + */ + function onSubstituteNode(node: Node, isExpression: boolean) { + return node; + } + + function enableEmitNotification(kind: SyntaxKind) { + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; + } + + function isEmitNotificationEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 + || (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0; + } + + /** + * Default hook for node emit. + * + * @param node The node to emit. + * @param emit A callback used to emit the node in the printer. + */ + function onEmitNode(node: Node, emit: (node: Node) => void) { + // Ensure that lexical environment modifications are disabled during the print phase. + if (!lexicalEnvironmentDisabled) { + const savedLexicalEnvironmentDisabled = lexicalEnvironmentDisabled; + lexicalEnvironmentDisabled = true; + emit(node); + lexicalEnvironmentDisabled = savedLexicalEnvironmentDisabled; + return; + } + + emit(node); + } + + /** + * Gets flags that control emit behavior of a node. + */ + function getNodeEmitFlags(node: Node) { + while (node) { + const nodeId = getNodeId(node); + if (nodeEmitFlags[nodeId] !== undefined) { + return nodeEmitFlags[nodeId]; + } + + node = node.original; + } + + return undefined; + } + + /** + * Sets flags that control emit behavior of a node. + */ + function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { + nodeEmitFlags[getNodeId(node)] = flags; + return node; + } + + /** + * Records a hoisted variable declaration for the provided name within a lexical environment. + */ + function hoistVariableDeclaration(name: Identifier): void { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + const decl = createVariableDeclaration(name); + if (!hoistedVariableDeclarations) { + hoistedVariableDeclarations = [decl]; + } + else { + hoistedVariableDeclarations.push(decl); + } + } + + /** + * Records a hoisted function declaration within a lexical environment. + */ + function hoistFunctionDeclaration(func: FunctionDeclaration): void { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + if (!hoistedFunctionDeclarations) { + hoistedFunctionDeclarations = [func]; + } + else { + hoistedFunctionDeclarations.push(func); + } + } + + /** + * Starts a new lexical environment. Any existing hoisted variable or function declarations + * are pushed onto a stack, and the related storage variables are reset. + */ + function startLexicalEnvironment(): void { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot start a lexical environment during the print phase."); + + // Save the current lexical environment. Rather than resizing the array we adjust the + // stack size variable. This allows us to reuse existing array slots we've + // already allocated between transformations to avoid allocation and GC overhead during + // transformation. + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedFunctionDeclarations; + lexicalEnvironmentStackOffset++; + hoistedVariableDeclarations = undefined; + hoistedFunctionDeclarations = undefined; + } + + /** + * Ends a lexical environment. The previous set of hoisted declarations are restored and + * any hoisted declarations added in this environment are returned. + */ + function endLexicalEnvironment(): Statement[] { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot end a lexical environment during the print phase."); + + let statements: Statement[]; + if (hoistedVariableDeclarations || hoistedFunctionDeclarations) { + if (hoistedFunctionDeclarations) { + statements = [...hoistedFunctionDeclarations]; + } + + if (hoistedVariableDeclarations) { + const statement = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList(hoistedVariableDeclarations) + ); + + if (!statements) { + statements = [statement]; + } + else { + statements.push(statement); + } + } + } + + // Restore the previous lexical environment. + lexicalEnvironmentStackOffset--; + hoistedVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + hoistedFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + return statements; + } + } + + /** + * High-order function, creates a function that executes a function composition. + * For example, `chain(a, b)` is the equivalent of `x => ((a', b') => y => b'(a'(y)))(a(x), b(x))` + * + * @param args The functions to chain. + */ + function chain(...args: ((t: T) => (u: U) => U)[]): (t: T) => (u: U) => U; + function chain(a: (t: T) => (u: U) => U, b: (t: T) => (u: U) => U, c: (t: T) => (u: U) => U, d: (t: T) => (u: U) => U, e: (t: T) => (u: U) => U): (t: T) => (u: U) => U { + if (e) { + const args = arrayOf<(t: T) => (u: U) => U>(arguments); + return t => compose(...map(args, f => f(t))); + } + else if (d) { + return t => compose(a(t), b(t), c(t), d(t)); + } + else if (c) { + return t => compose(a(t), b(t), c(t)); + } + else if (b) { + return t => compose(a(t), b(t)); + } + else if (a) { + return t => compose(a(t)); + } + else { + return t => u => u; + } + } + + /** + * High-order function, composes functions. Note that functions are composed inside-out; + * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. + * + * @param args The functions to compose. + */ + function compose(...args: ((t: T) => T)[]): (t: T) => T; + function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { + if (e) { + const args = arrayOf(arguments); + return t => reduceLeft<(t: T) => T, T>(args, (u, f) => f(u), t); + } + else if (d) { + return t => d(c(b(a(t)))); + } + else if (c) { + return t => c(b(a(t))); + } + else if (b) { + return t => b(a(t)); + } + else if (a) { + return t => a(t); + } + else { + return t => t; + } + } + + /** + * Makes an array from an ArrayLike. + */ + function arrayOf(arrayLike: ArrayLike) { + const length = arrayLike.length; + const array: T[] = new Array(length); + for (let i = 0; i < length; i++) { + array[i] = arrayLike[i]; + } + return array; + } +} \ No newline at end of file diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts new file mode 100644 index 0000000000000..c8ee58d8afccb --- /dev/null +++ b/src/compiler/transformers/destructuring.ts @@ -0,0 +1,420 @@ +/// +/// + +/*@internal*/ +namespace ts { + /** + * Flattens a destructuring assignment expression. + * + * @param root The destructuring assignment expression. + * @param needsValue Indicates whether the value from the right-hand-side of the + * destructuring assignment is needed as part of a larger expression. + * @param recordTempVariable A callback used to record new temporary variables. + * @param visitor An optional visitor to use to visit expressions. + */ + export function flattenDestructuringAssignment( + context: TransformationContext, + node: BinaryExpression, + needsValue: boolean, + recordTempVariable: (node: Identifier) => void, + visitor?: (node: Node) => VisitResult): Expression { + + if (isEmptyObjectLiteralOrArrayLiteral(node.left)) { + const right = node.right; + if (isDestructuringAssignment(right)) { + return flattenDestructuringAssignment(context, right, needsValue, recordTempVariable, visitor); + } + else { + return node.right; + } + } + + let location: TextRange = node; + let value = node.right; + const expressions: Expression[] = []; + if (needsValue) { + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignmen) is part of a larger expression), + // then we need to cache the right-hand value. + // + // The source map location for the assignment should point to the entire binary + // expression. + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment, visitor); + } + else if (nodeIsSynthesized(node)) { + // Generally, the source map location for a destructuring assignment is the root + // expression. + // + // However, if the root expression is synthesized (as in the case + // of the initializer when transforming a ForOfStatement), then the source map + // location should point to the right-hand value of the expression. + location = value; + } + + flattenDestructuring(context, node, value, location, emitAssignment, emitTempVariableAssignment, visitor); + + if (needsValue) { + expressions.push(value); + } + + const expression = inlineExpressions(expressions); + aggregateTransformFlags(expression); + return expression; + + function emitAssignment(name: Identifier, value: Expression, location: TextRange) { + const expression = createAssignment(name, value, location); + if (isSimpleExpression(value)) { + context.setNodeEmitFlags(expression, NodeEmitFlags.NoNestedSourceMaps); + } + + aggregateTransformFlags(expression); + expressions.push(expression); + } + + function emitTempVariableAssignment(value: Expression, location: TextRange) { + const name = createTempVariable(recordTempVariable); + emitAssignment(name, value, location); + return name; + } + } + + /** + * Flattens binding patterns in a parameter declaration. + * + * @param node The ParameterDeclaration to flatten. + * @param value The rhs value for the binding pattern. + * @param visitor An optional visitor to use to visit expressions. + */ + export function flattenParameterDestructuring( + context: TransformationContext, + node: ParameterDeclaration, + value: Expression, + visitor?: (node: Node) => VisitResult) { + const declarations: VariableDeclaration[] = []; + + flattenDestructuring(context, node, value, node, emitAssignment, emitTempVariableAssignment, visitor); + + return declarations; + + function emitAssignment(name: Identifier, value: Expression, location: TextRange) { + const declaration = createVariableDeclaration(name, value, location); + if (isSimpleExpression(value)) { + context.setNodeEmitFlags(declaration, NodeEmitFlags.NoNestedSourceMaps); + } + + aggregateTransformFlags(declaration); + declarations.push(declaration); + } + + function emitTempVariableAssignment(value: Expression, location: TextRange) { + const name = createTempVariable(/*recordTempVariable*/ undefined); + emitAssignment(name, value, location); + return name; + } + } + + /** + * Flattens binding patterns in a variable declaration. + * + * @param node The VariableDeclaration to flatten. + * @param value An optional rhs value for the binding pattern. + * @param visitor An optional visitor to use to visit expressions. + */ + export function flattenVariableDestructuring( + context: TransformationContext, + node: VariableDeclaration, + value?: Expression, + visitor?: (node: Node) => VisitResult) { + const declarations: VariableDeclaration[] = []; + + flattenDestructuring(context, node, value, node, emitAssignment, emitTempVariableAssignment, visitor); + + return declarations; + + function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { + const declaration = createVariableDeclaration(name, value, location); + if (declarations.length === 0) { + declaration.pos = -1; + } + + if (isSimpleExpression(value)) { + context.setNodeEmitFlags(declaration, NodeEmitFlags.NoNestedSourceMaps); + } + + declaration.original = original; + declarations.push(declaration); + aggregateTransformFlags(declaration); + } + + function emitTempVariableAssignment(value: Expression, location: TextRange) { + const name = createTempVariable(/*recordTempVariable*/ undefined); + emitAssignment(name, value, location, /*original*/ undefined); + return name; + } + } + + /** + * Flattens binding patterns in a variable declaration and transforms them into an expression. + * + * @param node The VariableDeclaration to flatten. + * @param recordTempVariable A callback used to record new temporary variables. + * @param nameSubstitution An optional callback used to substitute binding names. + * @param visitor An optional visitor to use to visit expressions. + */ + export function flattenVariableDestructuringToExpression( + context: TransformationContext, + node: VariableDeclaration, + recordTempVariable: (name: Identifier) => void, + nameSubstitution?: (name: Identifier) => Expression, + visitor?: (node: Node) => VisitResult) { + + const pendingAssignments: Expression[] = []; + + flattenDestructuring(context, node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, visitor); + + const expression = inlineExpressions(pendingAssignments); + aggregateTransformFlags(expression); + return expression; + + function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { + const left = nameSubstitution && nameSubstitution(name) || name; + emitPendingAssignment(left, value, location, original); + } + + function emitTempVariableAssignment(value: Expression, location: TextRange) { + const name = createTempVariable(recordTempVariable); + emitPendingAssignment(name, value, location, /*original*/ undefined); + return name; + } + + function emitPendingAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { + const expression = createAssignment(name, value, location); + if (isSimpleExpression(value)) { + context.setNodeEmitFlags(expression, NodeEmitFlags.NoNestedSourceMaps); + } + + expression.original = original; + pendingAssignments.push(expression); + return expression; + } + } + + function flattenDestructuring( + context: TransformationContext, + root: BindingElement | BinaryExpression, + value: Expression, + location: TextRange, + emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void, + emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, + visitor?: (node: Node) => VisitResult) { + if (value && visitor) { + value = visitNode(value, visitor, isExpression); + } + + if (isBinaryExpression(root)) { + emitDestructuringAssignment(root.left, value, location); + } + else { + emitBindingElement(root, value); + } + + function emitDestructuringAssignment(bindingTarget: Expression | ShorthandPropertyAssignment, value: Expression, location: TextRange) { + // When emitting target = value use source map node to highlight, including any temporary assignments needed for this + let target: Expression; + if (isShorthandPropertyAssignment(bindingTarget)) { + const initializer = visitor + ? visitNode(bindingTarget.objectAssignmentInitializer, visitor, isExpression) + : bindingTarget.objectAssignmentInitializer; + + if (initializer) { + value = createDefaultValueCheck(value, initializer, location); + } + + target = bindingTarget.name; + } + else if (isBinaryExpression(bindingTarget) && bindingTarget.operatorToken.kind === SyntaxKind.EqualsToken) { + const initializer = visitor + ? visitNode(bindingTarget.right, visitor, isExpression) + : bindingTarget.right; + + value = createDefaultValueCheck(value, initializer, location); + target = bindingTarget.left; + } + else { + target = bindingTarget; + } + + if (target.kind === SyntaxKind.ObjectLiteralExpression) { + emitObjectLiteralAssignment(target, value, location); + } + else if (target.kind === SyntaxKind.ArrayLiteralExpression) { + emitArrayLiteralAssignment(target, value, location); + } + else { + const name = getRelocatedClone(target, /*location*/ target); + emitAssignment(name, value, location, /*original*/ undefined); + } + } + + function emitObjectLiteralAssignment(target: ObjectLiteralExpression, value: Expression, location: TextRange) { + const properties = target.properties; + if (properties.length !== 1) { + // For anything but a single element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. + // When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); + } + + for (const p of properties) { + if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) { + const propName = (p).name; + const target = p.kind === SyntaxKind.ShorthandPropertyAssignment ? p : (p).initializer || propName; + // Assignment for target = value.propName should highligh whole property, hence use p as source map node + emitDestructuringAssignment(target, createDestructuringPropertyAccess(value, propName), p); + } + } + } + + function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) { + const elements = target.elements; + const numElements = elements.length; + if (numElements !== 1) { + // For anything but a single element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. + // When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); + } + + for (let i = 0; i < numElements; i++) { + const e = elements[i]; + if (e.kind !== SyntaxKind.OmittedExpression) { + // Assignment for target = value.propName should highligh whole property, hence use e as source map node + if (e.kind !== SyntaxKind.SpreadElementExpression) { + emitDestructuringAssignment(e, createElementAccess(value, createLiteral(i)), e); + } + else if (i === numElements - 1) { + emitDestructuringAssignment((e).expression, createArraySlice(value, i), e); + } + } + } + } + + function emitBindingElement(target: BindingElement, value: Expression) { + // Any temporary assignments needed to emit target = value should point to target + const initializer = visitor ? visitNode(target.initializer, visitor, isExpression) : target.initializer; + if (initializer) { + // Combine value and initializer + value = value ? createDefaultValueCheck(value, initializer, target) : initializer; + } + else if (!value) { + // Use 'void 0' in absence of value and initializer + value = createVoidZero(); + } + + const name = target.name; + if (isBindingPattern(name)) { + const elements = name.elements; + const numElements = elements.length; + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target, emitTempVariableAssignment); + } + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (name.kind === SyntaxKind.ObjectBindingPattern) { + // Rewrite element to a declaration with an initializer that fetches property + const propName = element.propertyName || element.name; + emitBindingElement(element, createDestructuringPropertyAccess(value, propName)); + } + else if (element.kind !== SyntaxKind.OmittedExpression) { + if (!element.dotDotDotToken) { + // Rewrite element to a declaration that accesses array element at index i + emitBindingElement(element, createElementAccess(value, i)); + } + else if (i === numElements - 1) { + emitBindingElement(element, createArraySlice(value, i)); + } + } + } + } + else { + const clonedName = getSynthesizedClone(name); + emitAssignment(clonedName, value, target, target); + } + } + + function createDefaultValueCheck(value: Expression, defaultValue: Expression, location: TextRange): Expression { + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); + return createConditional( + createStrictEquality(value, createVoidZero()), + defaultValue, + value + ); + } + + /** + * Creates either a PropertyAccessExpression or an ElementAccessExpression for the + * right-hand side of a transformed destructuring assignment. + * + * @param expression The right-hand expression that is the source of the property. + * @param propertyName The destructuring property name. + */ + function createDestructuringPropertyAccess(expression: Expression, propertyName: PropertyName): LeftHandSideExpression { + if (isComputedPropertyName(propertyName)) { + return createElementAccess( + expression, + ensureIdentifier(propertyName.expression, /*reuseIdentifierExpressions*/ false, /*location*/ propertyName, emitTempVariableAssignment) + ); + } + else if (isLiteralExpression(propertyName)) { + const clone = getSynthesizedClone(propertyName); + clone.text = unescapeIdentifier(clone.text); + return createElementAccess(expression, clone); + } + else { + if (isGeneratedIdentifier(propertyName)) { + const clone = getSynthesizedClone(propertyName); + clone.text = unescapeIdentifier(clone.text); + return createPropertyAccess(expression, clone); + } + else { + return createPropertyAccess(expression, createIdentifier(unescapeIdentifier(propertyName.text))); + } + } + } + } + + /** + * Ensures that there exists a declared identifier whose value holds the given expression. + * This function is useful to ensure that the expression's value can be read from in subsequent expressions. + * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. + * + * @param value the expression whose value needs to be bound. + * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; + * false if it is necessary to always emit an identifier. + * @param location The location to use for source maps and comments. + * @param emitTempVariableAssignment A callback used to emit a temporary variable. + * @param visitor An optional callback used to visit the value. + */ + function ensureIdentifier( + value: Expression, + reuseIdentifierExpressions: boolean, + location: TextRange, + emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, + visitor?: (node: Node) => VisitResult) { + + if (isIdentifier(value) && reuseIdentifierExpressions) { + return value; + } + else { + if (visitor) { + value = visitNode(value, visitor, isExpression); + } + + return emitTempVariableAssignment(value, location); + } + } +} diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts new file mode 100644 index 0000000000000..689229ddb37ea --- /dev/null +++ b/src/compiler/transformers/es6.ts @@ -0,0 +1,2899 @@ +/// +/// + +/*@internal*/ +namespace ts { + + const enum ES6SubstitutionFlags { + /** Enables substitutions for captured `this` */ + CapturedThis = 1 << 0, + /** Enables substitutions for block-scoped bindings. */ + BlockScopedBindings = 1 << 1, + } + /** + * If loop contains block scoped binding captured in some function then loop body is converted to a function. + * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, + * however if this binding is modified inside the body - this new value should be propagated back to the original binding. + * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. + * On every iteration this variable is initialized with value of corresponding binding. + * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) + * we copy the value inside the loop to the out parameter holder. + * + * for (let x;;) { + * let a = 1; + * let b = () => a; + * x++ + * if (...) break; + * ... + * } + * + * will be converted to + * + * var out_x; + * var loop = function(x) { + * var a = 1; + * var b = function() { return a; } + * x++; + * if (...) return out_x = x, "break"; + * ... + * out_x = x; + * } + * for (var x;;) { + * out_x = x; + * var state = loop(x); + * x = out_x; + * if (state === "break") break; + * } + * + * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function + * so nobody can observe this new value. + */ + interface LoopOutParameter { + originalName: Identifier; + outParamName: Identifier; + } + + const enum CopyDirection { + ToOriginal, + ToOutParameter + } + + const enum Jump { + Break = 1 << 1, + Continue = 1 << 2, + Return = 1 << 3 + } + + interface ConvertedLoopState { + /* + * set of labels that occurred inside the converted loop + * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code + */ + labels?: Map; + /* + * collection of labeled jumps that transfer control outside the converted loop. + * maps store association 'label -> labelMarker' where + * - label - value of label as it appear in code + * - label marker - return value that should be interpreted by calling code as 'jump to