From 2f624f5df369eb3bf245949953212cce7040887a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 14:36:15 -0800 Subject: [PATCH 01/14] Expose transformations as public API --- Gulpfile.ts | 9 +- Jakefile.js | 39 +++++-- src/compiler/comments.ts | 106 ++++++++++++++---- src/compiler/emitter.ts | 10 +- src/compiler/factory.ts | 32 ++++++ src/compiler/parser.ts | 6 +- src/compiler/program.ts | 10 +- src/compiler/scanner.ts | 16 +-- src/compiler/transformer.ts | 22 +++- src/compiler/transformers/generators.ts | 2 +- src/compiler/transformers/ts.ts | 11 +- src/compiler/types.ts | 40 +++++-- src/harness/tsconfig.json | 6 +- src/harness/unittests/customTransforms.ts | 86 ++++++++++++++ src/harness/unittests/transform.ts | 43 +++++++ src/services/outliningElementsCollector.ts | 4 +- src/services/services.ts | 3 +- src/services/transform.ts | 34 ++++++ src/services/transpile.ts | 3 +- src/services/tsconfig.json | 1 + src/services/types.ts | 5 + .../reference/customTransforms/after.js | 15 +++ .../reference/customTransforms/before.js | 15 +++ .../reference/customTransforms/both.js | 17 +++ .../transformsCorrectly.substitution.js | 1 + 25 files changed, 459 insertions(+), 77 deletions(-) create mode 100644 src/harness/unittests/customTransforms.ts create mode 100644 src/harness/unittests/transform.ts create mode 100644 src/services/transform.ts create mode 100644 tests/baselines/reference/customTransforms/after.js create mode 100644 tests/baselines/reference/customTransforms/before.js create mode 100644 tests/baselines/reference/customTransforms/both.js create mode 100644 tests/baselines/reference/transformApi/transformsCorrectly.substitution.js diff --git a/Gulpfile.ts b/Gulpfile.ts index 4f205bfb0ff01..314ca4a07c427 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -41,7 +41,7 @@ const {runTestsInParallel} = mochaParallel; Error.stackTraceLimit = 1000; const cmdLineOptions = minimist(process.argv.slice(2), { - boolean: ["debug", "light", "colors", "lint", "soft"], + boolean: ["debug", "inspect", "light", "colors", "lint", "soft"], string: ["browser", "tests", "host", "reporter", "stackTraceLimit"], alias: { d: "debug", @@ -57,6 +57,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), { soft: false, colors: process.env.colors || process.env.color || true, debug: process.env.debug || process.env.d, + inspect: process.env.inspect, host: process.env.TYPESCRIPT_HOST || process.env.host || "node", browser: process.env.browser || process.env.b || "IE", tests: process.env.test || process.env.tests || process.env.t, @@ -588,6 +589,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: cleanTestDirs((err) => { if (err) { console.error(err); failWithStatus(err, 1); } const debug = cmdLineOptions["debug"]; + const inspect = cmdLineOptions["inspect"]; const tests = cmdLineOptions["tests"]; const light = cmdLineOptions["light"]; const stackTraceLimit = cmdLineOptions["stackTraceLimit"]; @@ -624,7 +626,10 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer if (!runInParallel) { const args = []; - if (debug) { + if (inspect) { + args.push("--inspect"); + } + if (inspect || debug) { args.push("--debug-brk"); } args.push("-R", reporter); diff --git a/Jakefile.js b/Jakefile.js index 0b3a26dbb771e..8d58fd4d3f376 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -150,6 +150,7 @@ var servicesSources = [ "shims.ts", "signatureHelp.ts", "symbolDisplay.ts", + "transform.ts", "transpile.ts", // Formatting "formatting/formatting.ts", @@ -270,6 +271,8 @@ var harnessSources = harnessCoreSources.concat([ "matchFiles.ts", "initializeTSConfig.ts", "printer.ts", + "transform.ts", + "customTransforms.ts", ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ @@ -919,6 +922,7 @@ function runConsoleTests(defaultReporter, runInParallel) { } var debug = process.env.debug || process.env.d; + var inspect = process.env.inspect; tests = process.env.test || process.env.tests || process.env.t; var light = process.env.light || false; var stackTraceLimit = process.env.stackTraceLimit; @@ -948,18 +952,39 @@ function runConsoleTests(defaultReporter, runInParallel) { testTimeout = 800000; } - colors = process.env.colors || process.env.color; - colors = colors ? ' --no-colors ' : ' --colors '; - reporter = process.env.reporter || process.env.r || defaultReporter; - var bail = (process.env.bail || process.env.b) ? "--bail" : ""; + var colors = process.env.colors || process.env.color || true; + var reporter = process.env.reporter || process.env.r || defaultReporter; + var bail = process.env.bail || process.env.b; var lintFlag = process.env.lint !== 'false'; // timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer if (!runInParallel) { var startTime = mark(); - tests = tests ? ' -g "' + tests + '"' : ''; - var cmd = "mocha" + (debug ? " --debug-brk" : "") + " -R " + reporter + tests + colors + bail + ' -t ' + testTimeout + ' ' + run; + var args = []; + if (inspect) { + args.push("--inspect"); + } + if (inspect || debug) { + args.push("--debug-brk"); + } + args.push("-R", reporter); + if (tests) { + args.push("-g", `"${tests}"`); + } + if (colors) { + args.push("--colors"); + } + else { + args.push("--no-colors"); + } + if (bail) { + args.push("--bail"); + } + args.push("-t", testTimeout); + args.push(run); + + var cmd = "mocha " + args.join(" "); console.log(cmd); var savedNodeEnv = process.env.NODE_ENV; @@ -980,7 +1005,7 @@ function runConsoleTests(defaultReporter, runInParallel) { var savedNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = "development"; var startTime = mark(); - runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function (err) { + runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: !colors }, function (err) { process.env.NODE_ENV = savedNodeEnv; measure(startTime); // last worker clean everything and runs linter in case if there were no errors diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 200158c2f5ce7..49ed0986a559b 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -41,18 +41,14 @@ namespace ts { } if (node) { - const { pos, end } = getCommentRange(node); - const emitFlags = getEmitFlags(node); + hasWrittenComment = false; + + const emitNode = node.emitNode; + const emitFlags = emitNode && emitNode.flags; + const { pos, end } = emitNode && emitNode.commentRange || node; if ((pos < 0 && end < 0) || (pos === end)) { // Both pos and end are synthesized, so just emit the node without comments. - if (emitFlags & EmitFlags.NoNestedComments) { - disabled = true; - emitCallback(hint, node); - disabled = false; - } - else { - emitCallback(hint, node); - } + emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback); } else { if (extendedDiagnostics) { @@ -92,17 +88,10 @@ namespace ts { performance.measure("commentTime", "preEmitNodeWithComment"); } - if (emitFlags & EmitFlags.NoNestedComments) { - disabled = true; - emitCallback(hint, node); - disabled = false; - } - else { - emitCallback(hint, node); - } + emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback); if (extendedDiagnostics) { - performance.mark("beginEmitNodeWithComment"); + performance.mark("postEmitNodeWithComment"); } // Restore previous container state. @@ -117,12 +106,89 @@ namespace ts { } if (extendedDiagnostics) { - performance.measure("commentTime", "beginEmitNodeWithComment"); + performance.measure("commentTime", "postEmitNodeWithComment"); } } } } + function emitNodeWithSynthesizedComments(hint: EmitHint, node: Node, emitNode: EmitNode, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { + const leadingComments = emitNode && emitNode.leadingComments; + if (some(leadingComments)) { + if (extendedDiagnostics) { + performance.mark("preEmitNodeWithSynthesizedComments"); + } + + forEach(leadingComments, emitLeadingSynthesizedComment); + + if (extendedDiagnostics) { + performance.measure("commentTime", "preEmitNodeWithSynthesizedComments"); + } + } + + emitNodeWithNestedComments(hint, node, emitFlags, emitCallback); + + const trailingComments = emitNode && emitNode.trailingComments; + if (some(trailingComments)) { + if (extendedDiagnostics) { + performance.mark("postEmitNodeWithSynthesizedComments"); + } + + debugger; + forEach(trailingComments, emitTrailingSynthesizedComment); + + if (extendedDiagnostics) { + performance.measure("commentTime", "postEmitNodeWithSynthesizedComments"); + } + } + } + + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + else { + writer.write(" "); + } + } + + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.write(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + } + + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, lineMap, writer, 0, text.length, newLine); + } + + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } + + function emitNodeWithNestedComments(hint: EmitHint, node: Node, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { + if (emitFlags & EmitFlags.NoNestedComments) { + disabled = true; + emitCallback(hint, node); + disabled = false; + } + else { + emitCallback(hint, node); + } + } + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { if (extendedDiagnostics) { performance.mark("preEmitBodyWithDetachedComments"); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 700102a706962..96c87b2efd0c5 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -10,14 +10,13 @@ namespace ts { /*@internal*/ // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean): EmitResult { + export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: Transformer[]): EmitResult { const compilerOptions = host.getCompilerOptions(); const moduleKind = getEmitModuleKind(compilerOptions); const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; const emittedFilesList: string[] = compilerOptions.listEmittedFiles ? [] : undefined; const emitterDiagnostics = createDiagnosticCollection(); const newLine = host.getNewLine(); - const transformers = emitOnlyDtsFiles ? [] : getTransformers(compilerOptions); const writer = createTextWriter(newLine); const sourceMap = createSourceMapWriter(host, writer); @@ -56,9 +55,7 @@ namespace ts { performance.measure("printTime", "beforePrint"); // Clean up emit nodes on parse tree - for (const sourceFile of sourceFiles) { - disposeEmitNodes(sourceFile); - } + transform.dispose(); return { emitSkipped, @@ -756,9 +753,6 @@ namespace ts { // SyntaxKind.NumericLiteral function emitNumericLiteral(node: NumericLiteral) { emitLiteral(node); - if (node.trailingComment) { - write(` /*${node.trailingComment}*/`); - } } // SyntaxKind.StringLiteral diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d5d8482a0babd..95821ba10def3 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1906,6 +1906,34 @@ namespace ts { return node; } + export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.leadingComments; + } + + export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[]) { + getOrCreateEmitNode(node).leadingComments = comments; + return node; + } + + export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); + } + + export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.trailingComments; + } + + export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[]) { + getOrCreateEmitNode(node).trailingComments = comments; + return node; + } + + export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); + } + /** * Gets the constant value to emit for an expression. */ @@ -2018,6 +2046,8 @@ namespace ts { function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode) { const { flags, + leadingComments, + trailingComments, commentRange, sourceMapRange, tokenSourceMapRanges, @@ -2025,6 +2055,8 @@ namespace ts { helpers } = sourceEmitNode; if (!destEmitNode) destEmitNode = {}; + if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); + if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); if (flags) destEmitNode.flags = flags; if (commentRange) destEmitNode.commentRange = commentRange; if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c5439aa051b7b..51887474396cc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5816,7 +5816,11 @@ namespace ts { } } - const range = { pos: triviaScanner.getTokenPos(), end: triviaScanner.getTextPos(), kind: triviaScanner.getToken() }; + const range = { + kind: triviaScanner.getToken(), + pos: triviaScanner.getTokenPos(), + end: triviaScanner.getTextPos(), + }; const comment = sourceText.substring(range.pos, range.end); const referencePathMatchResult = getFileReferenceFromReferencePath(comment, range); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 62f3c06644276..eb9f0f0e0a1a2 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -754,15 +754,15 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult { - return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles)); + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers): EmitResult { + return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers)); } function isEmitBlocked(emitFileName: string): boolean { return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName)); } - function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult { + function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { let declarationDiagnostics: Diagnostic[] = []; if (options.noEmit) { @@ -804,11 +804,13 @@ namespace ts { performance.mark("beforeEmit"); + const transformers = emitOnlyDtsFiles ? [] : getTransformers(options, customTransformers); const emitResult = emitFiles( emitResolver, getEmitHost(writeFileCallback), sourceFile, - emitOnlyDtsFiles); + emitOnlyDtsFiles, + transformers); performance.mark("afterEmit"); performance.measure("Emit", "beforeEmit", "afterEmit"); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 52c75a370050f..fb91b0ff5b71e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -608,10 +608,10 @@ namespace ts { * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy * return value of the callback. */ - function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial?: U): U { + function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial?: U): U { let pendingPos: number; let pendingEnd: number; - let pendingKind: SyntaxKind; + let pendingKind: CommentKind; let pendingHasTrailingNewLine: boolean; let hasPendingCommentRange = false; let collecting = trailing || pos === 0; @@ -707,28 +707,28 @@ namespace ts { return accumulator; } - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) { + export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) { return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); } - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) { + export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) { return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); } - export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); } - export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); } - function appendCommentRange(pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { + function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { if (!comments) { comments = []; } - comments.push({ pos, end, hasTrailingNewLine, kind }); + comments.push({ kind, pos, end, hasTrailingNewLine }); return comments; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 62469df79c110..68f149c666fb9 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -29,12 +29,14 @@ namespace ts { EmitNotifications = 1 << 1, } - export function getTransformers(compilerOptions: CompilerOptions) { + export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers) { const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); const transformers: Transformer[] = []; + addRange(transformers, customTransformers && customTransformers.before); + transformers.push(transformTypeScript); if (jsx === JsxEmit.React) { @@ -66,6 +68,8 @@ namespace ts { transformers.push(transformES5); } + addRange(transformers, customTransformers && customTransformers.after); + return transformers; } @@ -79,16 +83,13 @@ namespace ts { */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult { const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); - let lexicalEnvironmentDisabled = false; - let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; - let emitHelpers: EmitHelper[]; // The transformation context is provided to each transformer as part of transformer @@ -113,6 +114,9 @@ namespace ts { isEmitNotificationEnabled }; + // Ensure the parse tree is clean before applying transformations + dispose(); + performance.mark("beforeTransform"); // Chain together and initialize each transformer. @@ -130,7 +134,8 @@ namespace ts { return { transformed, emitNodeWithSubstitution, - emitNodeWithNotification + emitNodeWithNotification, + dispose }; /** @@ -323,5 +328,12 @@ namespace ts { emitHelpers = undefined; return helpers; } + + function dispose() { + // Clean up emit nodes on parse tree + for (const sourceFile of sourceFiles) { + disposeEmitNodes(sourceFile); + } + } } } diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index cabd5d2a96d01..f40c2994998f2 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -2419,7 +2419,7 @@ namespace ts { */ function createInstruction(instruction: Instruction): NumericLiteral { const literal = createLiteral(instruction); - literal.trailingComment = getInstructionName(instruction); + addSyntheticTrailingComment(literal, SyntaxKind.MultiLineCommentTrivia, getInstructionName(instruction)); return literal; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 4ef3bd5c067d9..c28d74f85274e 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -3312,17 +3312,20 @@ namespace ts { function substituteConstantValue(node: PropertyAccessExpression | ElementAccessExpression): LeftHandSideExpression { const constantValue = tryGetConstEnumValue(node); if (constantValue !== undefined) { + // track the constant value on the node for the printer in needsDotDotForPropertyAccess + setConstantValue(node, constantValue); + const substitute = createLiteral(constantValue); - setSourceMapRange(substitute, node); - setCommentRange(substitute, node); if (!compilerOptions.removeComments) { const propertyName = isPropertyAccessExpression(node) ? declarationNameToString(node.name) : getTextOfNode(node.argumentExpression); - substitute.trailingComment = ` ${propertyName} `; + + addSyntheticTrailingComment(substitute, SyntaxKind.MultiLineCommentTrivia, ` ${propertyName} `); + // wrap the substituted node so that it emits its own comments. + return createPartiallyEmittedExpression(substitute); } - setConstantValue(node, constantValue); return substitute; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 49e744682bc1d..275760bfc6a44 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1313,7 +1313,6 @@ export interface NumericLiteral extends LiteralExpression { kind: SyntaxKind.NumericLiteral; - trailingComment?: string; } export interface TemplateHead extends LiteralLikeNode { @@ -1893,9 +1892,17 @@ fileName: string; } + export type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia; + export interface CommentRange extends TextRange { hasTrailingNewLine?: boolean; - kind: SyntaxKind; + kind: CommentKind; + } + + export interface SynthesizedComment extends CommentRange { + text: string; + pos: -1; + end: -1; } // represents a top level: { type } expression in a JSDoc comment. @@ -2265,7 +2272,7 @@ * used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter * will be invoked when writing the JavaScript and declaration files. */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; @@ -2299,6 +2306,13 @@ /* @internal */ structureIsReused?: boolean; } + export interface CustomTransformers { + /** Custom transformers to evaluate before built-in transformations. */ + before?: Transformer[]; + /** Custom transformers to evaluate after built-in transformations. */ + after?: Transformer[]; + } + export interface SourceMapSpan { /** Line number in the .js file. */ emittedLine: number; @@ -3724,9 +3738,11 @@ export interface EmitNode { annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup. flags?: EmitFlags; // Flags that customize emit + leadingComments?: SynthesizedComment[]; // Synthesized leading comments + trailingComments?: SynthesizedComment[]; // Synthesized trailing comments commentRange?: TextRange; // The text range to use when emitting leading or trailing comments sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings - tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens + tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens constantValue?: number; // The constant value of an expression externalHelpersModuleName?: Identifier; // The local name for an imported helpers module helpers?: EmitHelper[]; // Emit helpers for the node @@ -3762,7 +3778,7 @@ export interface EmitHelper { readonly name: string; // A unique name for this helper. - readonly scoped: boolean; // Indicates whether ther helper MUST be emitted in the current scope. + readonly scoped: boolean; // Indicates whether the helper MUST be emitted in the current scope. readonly text: string; // ES3-compatible raw script text. readonly priority?: number; // Helpers with a higher priority are emitted earlier than other helpers on the node. } @@ -3809,11 +3825,10 @@ writeFile: WriteFileCallback; } - /* @internal */ export interface TransformationContext { - getCompilerOptions(): CompilerOptions; - getEmitResolver(): EmitResolver; - getEmitHost(): EmitHost; + /*@internal*/ getCompilerOptions(): CompilerOptions; + /*@internal*/ getEmitResolver(): EmitResolver; + /*@internal*/ getEmitHost(): EmitHost; /** Starts a new lexical environment. */ startLexicalEnvironment(): void; @@ -3882,7 +3897,6 @@ onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; } - /* @internal */ export interface TransformationResult { /** * Gets the transformed source files. @@ -3906,9 +3920,13 @@ * @param emitCallback A callback used to emit the node. */ emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + + /** + * Clean up EmitNode entries on any parse-tree nodes. + */ + dispose(): void; } - /* @internal */ export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; export interface Printer { diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index bea688d358b17..32af0eb260196 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -77,8 +77,8 @@ "../services/codefixes/helpers.ts", "../services/codefixes/importFixes.ts", "../services/codefixes/unusedIdentifierFixes.ts", - "../services/harness.ts", + "harness.ts", "sourceMapRecorder.ts", "harnessLanguageService.ts", "fourslash.ts", @@ -119,6 +119,8 @@ "./unittests/compileOnSave.ts", "./unittests/typingsInstaller.ts", "./unittests/projectErrors.ts", - "./unittests/printer.ts" + "./unittests/printer.ts", + "./unittests/transform.ts", + "./unittests/customTransforms.ts" ] } diff --git a/src/harness/unittests/customTransforms.ts b/src/harness/unittests/customTransforms.ts new file mode 100644 index 0000000000000..af8db28b69b9d --- /dev/null +++ b/src/harness/unittests/customTransforms.ts @@ -0,0 +1,86 @@ +/// +/// + +namespace ts { + describe("customTransforms", () => { + function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers) { + it(name, () => { + const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); + const fileMap = arrayToMap(roots, file => file.fileName); + const outputs = createMap(); + const options: CompilerOptions = {}; + const host: CompilerHost = { + getSourceFile: (fileName) => fileMap.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + getCurrentDirectory: () => "", + getDirectories: () => [], + getCanonicalFileName: (fileName) => fileName, + useCaseSensitiveFileNames: () => true, + getNewLine: () => "\n", + fileExists: (fileName) => fileMap.has(fileName), + readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName).text : undefined, + writeFile: (fileName, text) => outputs.set(fileName, text), + }; + + const program = createProgram(arrayFrom(fileMap.keys()), options, host); + program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); + Harness.Baseline.runBaseline(`customTransforms/${name}.js`, () => { + let content = ""; + for (const [file, text] of arrayFrom(outputs.entries())) { + if (content) content += "\n\n"; + content += `// [${file}]\n`; + content += text; + } + return content; + }); + }); + } + + const sources = [{ + file: "source.ts", + text: ` + function f1() { } + class c() { } + enum e { } + // leading + function f2() { } // trailing + ` + }]; + + const before: Transformer = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunction(node); + default: + return visitEachChild(node, visit, context); + } + } + function visitFunction(node: FunctionDeclaration) { + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true); + return node; + } + }; + + const after: Transformer = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatement(node); + default: + return visitEachChild(node, visit, context); + } + } + function visitVariableStatement(node: VariableStatement) { + addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after"); + return node; + } + }; + + emitsCorrectly("before", sources, { before: [before] }); + emitsCorrectly("after", sources, { after: [after] }); + emitsCorrectly("both", sources, { before: [before], after: [after] }); + }); +} \ No newline at end of file diff --git a/src/harness/unittests/transform.ts b/src/harness/unittests/transform.ts new file mode 100644 index 0000000000000..1a05138b84ba3 --- /dev/null +++ b/src/harness/unittests/transform.ts @@ -0,0 +1,43 @@ +/// +/// + +namespace ts { + describe("TransformAPI", () => { + function transformsCorrectly(name: string, source: string, transformers: Transformer[]) { + it(name, () => { + Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${name}.js`, () => { + const transformed = transform(createSourceFile("source.ts", source, ScriptTarget.ES2015), transformers); + const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, { + onEmitNode: transformed.emitNodeWithNotification, + onSubstituteNode: transformed.emitNodeWithSubstitution + }); + const result = printer.printBundle(createBundle(transformed.transformed)); + transformed.dispose(); + return result; + }); + }); + } + + transformsCorrectly("substitution", ` + var a = undefined; + `, [ + context => { + const previousOnSubstituteNode = context.onSubstituteNode; + context.enableSubstitution(SyntaxKind.Identifier); + context.onSubstituteNode = (hint, node) => { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression && node.kind === SyntaxKind.Identifier && (node).text === "undefined") { + node = createPartiallyEmittedExpression( + addSyntheticTrailingComment( + setTextRange( + createVoidZero(), + node), + SyntaxKind.MultiLineCommentTrivia, "undefined")); + } + return node; + }; + return file => file; + } + ]); + }); +} diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 2ad20a7ed0cdc..43054a10f5d39 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -67,10 +67,10 @@ namespace ts.OutliningElementsCollector { // Only outline spans of two or more consecutive single line comments if (count > 1) { - const multipleSingleLineComments = { + const multipleSingleLineComments: CommentRange = { + kind: SyntaxKind.SingleLineCommentTrivia, pos: start, end: end, - kind: SyntaxKind.SingleLineCommentTrivia }; addOutliningSpanComments(multipleSingleLineComments, /*autoCollapse*/ false); diff --git a/src/services/services.ts b/src/services/services.ts index b3e3093cddb71..359699e47adaf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1447,7 +1447,8 @@ namespace ts { }); } - const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles); + const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); + const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); return { outputFiles, diff --git a/src/services/transform.ts b/src/services/transform.ts new file mode 100644 index 0000000000000..724075e4cf4dc --- /dev/null +++ b/src/services/transform.ts @@ -0,0 +1,34 @@ +/// +/// +namespace ts { + export interface TransformOptions { + newLine?: NewLineKind; + } + + /** + * Transform one or more source files using the supplied transformers. + * @param source A `SourceFile` or an array of `SourceFiles`. + * @param transformers An array of `Transformer` callbacks used to process the transformation. + * @param compilerOptions Optional compiler options. + */ + export function transform(source: SourceFile | SourceFile[], transformers: Transformer[], transformOptions?: TransformOptions) { + const compilerOptions = transformOptions || {}; + const newLine = getNewLineCharacter(compilerOptions); + const sourceFiles = isArray(source) ? source : [source]; + const fileMap = arrayToMap(sourceFiles, sourceFile => sourceFile.fileName); + const emitHost: EmitHost = { + getCompilerOptions: () => compilerOptions, + getCanonicalFileName: fileName => fileName, + getCommonSourceDirectory: () => "", + getCurrentDirectory: () => "", + getNewLine: () => newLine, + getSourceFile: fileName => fileMap.get(fileName), + getSourceFileByPath: fileName => fileMap.get(fileName), + getSourceFiles: () => sourceFiles, + isSourceFileFromExternalLibrary: () => false, + isEmitBlocked: () => false, + writeFile: () => Debug.fail("'writeFile()' is not supported during transformation.") + }; + return transformFiles(/*resolver*/ undefined, emitHost, sourceFiles, transformers); + } +} \ No newline at end of file diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 0b90e9d030b72..86c6a3e890412 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -123,7 +123,8 @@ let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; /** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */ - function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { + /*@internal*/ + export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { // Lazily create this value to fix module loading errors. commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index b4e8289f367bd..9bebdb6932ea6 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -57,6 +57,7 @@ "preProcess.ts", "rename.ts", "services.ts", + "transform.ts", "transpile.ts", "shims.ts", "signatureHelp.ts", diff --git a/src/services/types.ts b/src/services/types.ts index b4210538518e4..0b612d281a419 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -170,6 +170,11 @@ namespace ts { * completions will not be provided */ getDirectories?(directoryName: string): string[]; + + /** + * Gets a set of custom transformers to use during emit. + */ + getCustomTransformers?(): CustomTransformers | undefined; } // diff --git a/tests/baselines/reference/customTransforms/after.js b/tests/baselines/reference/customTransforms/after.js new file mode 100644 index 0000000000000..63c95725f437a --- /dev/null +++ b/tests/baselines/reference/customTransforms/after.js @@ -0,0 +1,15 @@ +// [source.js] +function f1() { } +//@after +var c = (function () { + function c() { + } + return c; +}()); +(function () { }); +//@after +var e; +(function (e) { +})(e || (e = {})); +// leading +function f2() { } // trailing diff --git a/tests/baselines/reference/customTransforms/before.js b/tests/baselines/reference/customTransforms/before.js new file mode 100644 index 0000000000000..4ee133afdbc78 --- /dev/null +++ b/tests/baselines/reference/customTransforms/before.js @@ -0,0 +1,15 @@ +// [source.js] +/*@before*/ +function f1() { } +var c = (function () { + function c() { + } + return c; +}()); +(function () { }); +var e; +(function (e) { +})(e || (e = {})); +// leading +/*@before*/ +function f2() { } // trailing diff --git a/tests/baselines/reference/customTransforms/both.js b/tests/baselines/reference/customTransforms/both.js new file mode 100644 index 0000000000000..3013e7f8780bd --- /dev/null +++ b/tests/baselines/reference/customTransforms/both.js @@ -0,0 +1,17 @@ +// [source.js] +/*@before*/ +function f1() { } +//@after +var c = (function () { + function c() { + } + return c; +}()); +(function () { }); +//@after +var e; +(function (e) { +})(e || (e = {})); +// leading +/*@before*/ +function f2() { } // trailing diff --git a/tests/baselines/reference/transformApi/transformsCorrectly.substitution.js b/tests/baselines/reference/transformApi/transformsCorrectly.substitution.js new file mode 100644 index 0000000000000..40686768759e1 --- /dev/null +++ b/tests/baselines/reference/transformApi/transformsCorrectly.substitution.js @@ -0,0 +1 @@ +var a = void 0 /*undefined*/; From 6c59ee4ce6fbee3fcb36f371aaa1034600000a74 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 15:10:32 -0800 Subject: [PATCH 02/14] Add assertions to verify user-exposed behavior. --- src/compiler/transformer.ts | 88 ++++++++++++++++++++++++++++--------- src/compiler/types.ts | 41 +++++++++-------- 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 68f149c666fb9..9c29c8c60a7f1 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -24,6 +24,13 @@ namespace ts { } } + const enum TransformationState { + Uninitialized, + Initialized, + Completed, + Disposed + } + const enum SyntaxKindFeatureFlags { Substitution = 1 << 0, EmitNotifications = 1 << 1, @@ -83,7 +90,6 @@ namespace ts { */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult { const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); - let lexicalEnvironmentDisabled = false; let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; @@ -91,6 +97,9 @@ namespace ts { let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[]; + let onSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node; + let onEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node); + let state = TransformationState.Uninitialized; // The transformation context is provided to each transformer as part of transformer // initialization. @@ -106,27 +115,40 @@ namespace ts { hoistFunctionDeclaration, requestEmitHelper, readEmitHelpers, - onSubstituteNode: (_, node) => node, enableSubstitution, - isSubstitutionEnabled, - onEmitNode: (hint, node, callback) => callback(hint, node), enableEmitNotification, - isEmitNotificationEnabled + isSubstitutionEnabled, + isEmitNotificationEnabled, + get onSubstituteNode() { return onSubstituteNode }, + set onSubstituteNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onSubstituteNode = value; + }, + get onEmitNode() { return onEmitNode }, + set onEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onEmitNode = value; + } }; // Ensure the parse tree is clean before applying transformations - dispose(); + forEach(sourceFiles, disposeEmitNodes); performance.mark("beforeTransform"); // Chain together and initialize each transformer. const transformation = chain(...transformers)(context); + // prevent modification of transformation hooks. + state = TransformationState.Initialized; + // Transform each source file. const transformed = map(sourceFiles, transformSourceFile); - // Disable modification of the lexical environment. - lexicalEnvironmentDisabled = true; + // prevent modification of the lexical environment. + state = TransformationState.Completed; performance.mark("afterTransform"); performance.measure("transformTime", "beforeTransform", "afterTransform"); @@ -155,6 +177,7 @@ namespace ts { * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ function enableSubstitution(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } @@ -174,9 +197,10 @@ namespace ts { * @param emitCallback The callback used to emit the node or its substitute. */ function emitNodeWithSubstitution(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); if (node) { if (isSubstitutionEnabled(node)) { - node = context.onSubstituteNode(hint, node) || node; + node = onSubstituteNode(hint, node) || node; } emitCallback(hint, node); } @@ -186,6 +210,7 @@ namespace ts { * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. */ function enableEmitNotification(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } @@ -206,9 +231,10 @@ namespace ts { * @param emitCallback The callback used to emit the node. */ function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); if (node) { if (isEmitNotificationEnabled(node)) { - context.onEmitNode(hint, node, emitCallback); + onEmitNode(hint, node, emitCallback); } else { emitCallback(hint, node); @@ -220,7 +246,8 @@ namespace ts { * 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."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); const decl = createVariableDeclaration(name); if (!lexicalEnvironmentVariableDeclarations) { lexicalEnvironmentVariableDeclarations = [decl]; @@ -234,7 +261,8 @@ namespace ts { * 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."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); if (!lexicalEnvironmentFunctionDeclarations) { lexicalEnvironmentFunctionDeclarations = [func]; } @@ -248,7 +276,8 @@ namespace ts { * 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."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); // Save the current lexical environment. Rather than resizing the array we adjust the @@ -264,14 +293,16 @@ namespace ts { /** Suspends the current lexical environment, usually after visiting a parameter list. */ function suspendLexicalEnvironment(): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot suspend a lexical environment during the print phase."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); lexicalEnvironmentSuspended = true; } /** Resumes a suspended lexical environment, usually before visiting a function body. */ function resumeLexicalEnvironment(): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot resume a lexical environment during the print phase."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); lexicalEnvironmentSuspended = false; } @@ -281,7 +312,8 @@ namespace ts { * any hoisted declarations added in this environment are returned. */ function endLexicalEnvironment(): Statement[] { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot end a lexical environment during the print phase."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); let statements: Statement[]; @@ -317,22 +349,36 @@ namespace ts { } function requestEmitHelper(helper: EmitHelper): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); emitHelpers = append(emitHelpers, helper); } function readEmitHelpers(): EmitHelper[] | undefined { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); const helpers = emitHelpers; emitHelpers = undefined; return helpers; } function dispose() { - // Clean up emit nodes on parse tree - for (const sourceFile of sourceFiles) { - disposeEmitNodes(sourceFile); + if (state < TransformationState.Disposed) { + // Clean up emit nodes on parse tree + forEach(sourceFiles, disposeEmitNodes); + + // Release references to external entries for GC purposes. + lexicalEnvironmentVariableDeclarations = undefined; + lexicalEnvironmentVariableDeclarationsStack = undefined; + lexicalEnvironmentFunctionDeclarations = undefined; + lexicalEnvironmentFunctionDeclarationsStack = undefined; + onSubstituteNode = undefined; + onEmitNode = undefined; + emitHelpers = undefined; + + // Prevent further use of the transformation result. + state = TransformationState.Disposed; } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 275760bfc6a44..c0da1329b0fe1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3842,39 +3842,30 @@ /** Ends a lexical environment, returning any declarations. */ endLexicalEnvironment(): Statement[]; - /** - * Hoists a function declaration to the containing scope. - */ + /** Hoists a function declaration to the containing scope. */ hoistFunctionDeclaration(node: FunctionDeclaration): void; - /** - * Hoists a variable declaration to the containing scope. - */ + /** Hoists a variable declaration to the containing scope. */ hoistVariableDeclaration(node: Identifier): void; - /** - * Records a request for a non-scoped emit helper in the current context. - */ + /** Records a request for a non-scoped emit helper in the current context. */ requestEmitHelper(helper: EmitHelper): void; - /** - * Gets and resets the requested non-scoped emit helpers. - */ + /** Gets and resets the requested non-scoped emit helpers. */ readEmitHelpers(): EmitHelper[] | undefined; - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ + /** Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ enableSubstitution(kind: SyntaxKind): void; - /** - * Determines whether expression substitutions are enabled for the provided node. - */ + /** Determines whether expression substitutions are enabled for the provided node. */ isSubstitutionEnabled(node: Node): boolean; /** * Hook used by transformers to substitute expressions just before they * are emitted by the pretty printer. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `FileTransformer` callback. */ onSubstituteNode?: (hint: EmitHint, node: Node) => Node; @@ -3893,6 +3884,9 @@ /** * Hook used to allow transformers to capture state before or after * the printer emits a node. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `FileTransformer` callback. */ onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; } @@ -3927,7 +3921,16 @@ dispose(): void; } - export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; + /** + * A function that is used to initialize and return a `FileTransformer` callback, which in turn + * will be used to transform one or more `SourceFile` objects. + */ + export type Transformer = (context: TransformationContext) => FileTransformer; + + /** + * A function that transforms a `SourceFile` object. + */ + export type FileTransformer = (node: SourceFile) => SourceFile; export interface Printer { /** From 1bbaaa3831008b9ee32f8eb9d592c7696eca3910 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 15:19:44 -0800 Subject: [PATCH 03/14] Make TransformationContext.getCompilerOptions public --- src/compiler/types.ts | 11 +++++++---- src/services/transform.ts | 13 ++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c0da1329b0fe1..3c859694b44b2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3826,10 +3826,12 @@ } export interface TransformationContext { - /*@internal*/ getCompilerOptions(): CompilerOptions; /*@internal*/ getEmitResolver(): EmitResolver; /*@internal*/ getEmitHost(): EmitHost; + /** Gets the compiler options supplied to the transformer. */ + getCompilerOptions(): CompilerOptions; + /** Starts a new lexical environment. */ startLexicalEnvironment(): void; @@ -3892,11 +3894,12 @@ } export interface TransformationResult { - /** - * Gets the transformed source files. - */ + /** Gets the transformed source files. */ transformed: SourceFile[]; + /** Gets diagnostics for the transformation. */ + diagnostics?: Diagnostic[]; + /** * Emits the substitute for a node, if one is available; otherwise, emits the node. * diff --git a/src/services/transform.ts b/src/services/transform.ts index 724075e4cf4dc..426e4280fd405 100644 --- a/src/services/transform.ts +++ b/src/services/transform.ts @@ -1,18 +1,15 @@ /// /// namespace ts { - export interface TransformOptions { - newLine?: NewLineKind; - } - /** * Transform one or more source files using the supplied transformers. * @param source A `SourceFile` or an array of `SourceFiles`. * @param transformers An array of `Transformer` callbacks used to process the transformation. * @param compilerOptions Optional compiler options. */ - export function transform(source: SourceFile | SourceFile[], transformers: Transformer[], transformOptions?: TransformOptions) { - const compilerOptions = transformOptions || {}; + export function transform(source: SourceFile | SourceFile[], transformers: Transformer[], compilerOptions?: CompilerOptions) { + const diagnostics: Diagnostic[] = []; + compilerOptions = fixupCompilerOptions(compilerOptions, diagnostics); const newLine = getNewLineCharacter(compilerOptions); const sourceFiles = isArray(source) ? source : [source]; const fileMap = arrayToMap(sourceFiles, sourceFile => sourceFile.fileName); @@ -29,6 +26,8 @@ namespace ts { isEmitBlocked: () => false, writeFile: () => Debug.fail("'writeFile()' is not supported during transformation.") }; - return transformFiles(/*resolver*/ undefined, emitHost, sourceFiles, transformers); + const result = transformFiles(/*resolver*/ undefined, emitHost, sourceFiles, transformers); + result.diagnostics = concatenate(result.diagnostics, diagnostics); + return result; } } \ No newline at end of file From 75fa22c682faffdd3b97a6e5e05ba89a482619e5 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 15:38:03 -0800 Subject: [PATCH 04/14] Remove debugger statement --- src/compiler/comments.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 49ed0986a559b..3668bd8eb556c 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -134,7 +134,6 @@ namespace ts { performance.mark("postEmitNodeWithSynthesizedComments"); } - debugger; forEach(trailingComments, emitTrailingSynthesizedComment); if (extendedDiagnostics) { From db23ca7c8bd8c3c7ee6bc4ec5adece781ce494fa Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 17:09:33 -0800 Subject: [PATCH 05/14] Guard public API surface of TypeChecker against synthesized nodes --- src/compiler/checker.ts | 157 ++++++++++++++++++++++++++++---------- src/compiler/utilities.ts | 14 +++- 2 files changed, 129 insertions(+), 42 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4423b574e31bc..7ec623e277aec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -64,6 +64,11 @@ namespace ts { undefinedSymbol.declarations = []; const argumentsSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "arguments"); + // for public members that accept a Node or one of its subtypes, we must guard against + // synthetic nodes created during transformations by calling `getParseTreeNode`. + // for most of these, we perform the guard only on `checker` to avoid any possible + // extra cost of calling `getParseTreeNode` when calling these functions from inside the + // checker. const checker: TypeChecker = { getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), @@ -74,8 +79,15 @@ namespace ts { isUnknownSymbol: symbol => symbol === unknownSymbol, getDiagnostics, getGlobalDiagnostics, - getTypeOfSymbolAtLocation, - getSymbolsOfParameterPropertyDeclaration, + getTypeOfSymbolAtLocation: (symbol, location) => { + location = getParseTreeNode(location); + return location ? getTypeOfSymbolAtLocation(symbol, location) : unknownType; + }, + getSymbolsOfParameterPropertyDeclaration: (parameter, parameterName) => { + parameter = getParseTreeNode(parameter, isParameter); + Debug.assert(parameter !== undefined, "Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); + return getSymbolsOfParameterPropertyDeclaration(parameter, parameterName); + }, getDeclaredTypeOfSymbol, getPropertiesOfType, getPropertyOfType, @@ -83,37 +95,88 @@ namespace ts { getSignaturesOfType, getIndexTypeOfType, getBaseTypes, - getTypeFromTypeNode, + getTypeFromTypeNode: node => { + node = getParseTreeNode(node, isTypeNode); + return node ? getTypeFromTypeNode(node) : unknownType; + }, getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, - getSymbolsInScope, - getSymbolAtLocation, - getShorthandAssignmentValueSymbol, - getExportSpecifierLocalTargetSymbol, - getTypeAtLocation: getTypeOfNode, - getPropertySymbolOfDestructuringAssignment, - signatureToString, - typeToString, + getSymbolsInScope: (location, meaning) => { + location = getParseTreeNode(location); + return location ? getSymbolsInScope(location, meaning) : []; + }, + getSymbolAtLocation: node => { + node = getParseTreeNode(node); + return node ? getSymbolAtLocation(node) : undefined; + }, + getShorthandAssignmentValueSymbol: node => { + node = getParseTreeNode(node); + return node ? getShorthandAssignmentValueSymbol(node) : undefined; + }, + getExportSpecifierLocalTargetSymbol: node => { + node = getParseTreeNode(node, isExportSpecifier); + return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; + }, + getTypeAtLocation: node => { + node = getParseTreeNode(node); + return node ? getTypeOfNode(node) : unknownType; + }, + getPropertySymbolOfDestructuringAssignment: location => { + location = getParseTreeNode(location, isIdentifier); + return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; + }, + signatureToString: (signature, enclosingDeclaration?, flags?, kind?) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); + }, + typeToString: (type, enclosingDeclaration?, flags?) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); + }, getSymbolDisplayBuilder, - symbolToString, + symbolToString: (symbol, enclosingDeclaration?, meaning?) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning); + }, getAugmentedPropertiesOfType, getRootSymbols, - getContextualType, + getContextualType: node => { + node = getParseTreeNode(node, isExpression) + return node ? getContextualType(node) : undefined; + }, getFullyQualifiedName, - getResolvedSignature, - getConstantValue, - isValidPropertyAccess, - getSignatureFromDeclaration, - isImplementationOfOverload, + getResolvedSignature: (node, candidatesOutArray?) => { + node = getParseTreeNode(node, isCallLikeExpression); + return node ? getResolvedSignature(node, candidatesOutArray) : undefined; + }, + getConstantValue: node => { + node = getParseTreeNode(node, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + isValidPropertyAccess: (node, propertyName) => { + node = getParseTreeNode(node, isPropertyAccessOrQualifiedName); + return node ? isValidPropertyAccess(node, propertyName) : false; + }, + getSignatureFromDeclaration: declaration => { + declaration = getParseTreeNode(declaration, isFunctionLike); + return declaration ? getSignatureFromDeclaration(declaration) : undefined; + }, + isImplementationOfOverload: node => { + node = getParseTreeNode(node, isFunctionLike); + return node ? isImplementationOfOverload(node) : undefined; + }, getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, getExportsAndPropertiesOfModule, getAmbientModules, - getJsxElementAttributesType, + getJsxElementAttributesType: node => { + node = getParseTreeNode(node, isJsxOpeningLikeElement); + return node ? getJsxElementAttributesType(node) : undefined; + }, getJsxIntrinsicTagNames, - isOptionalParameter, + isOptionalParameter: node => { + node = getParseTreeNode(node, isParameter); + return node ? isOptionalParameter(node) : false; + }, tryGetMemberInModuleExports, tryFindAmbientModuleWithoutAugmentations: moduleName => { // we deliberately exclude augmentations @@ -19819,14 +19882,14 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols = createMap(); - let memberFlags: ModifierFlags = ModifierFlags.None; - if (isInsideWithStatementBody(location)) { // We cannot answer semantic questions within a with block, do not proceed any further return []; } + const symbols = createMap(); + let memberFlags: ModifierFlags = ModifierFlags.None; + populateSymbols(); return symbolsToArray(symbols); @@ -20086,6 +20149,7 @@ namespace ts { if (node.kind === SyntaxKind.SourceFile) { return isExternalModule(node) ? getMergedSymbol(node.symbol) : undefined; } + if (isInsideWithStatementBody(node)) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; @@ -20536,12 +20600,6 @@ namespace ts { } function isValueAliasDeclaration(node: Node): boolean { - node = getParseTreeNode(node); - if (node === undefined) { - // A synthesized node comes from an emit transformation and is always a value. - return true; - } - switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ImportClause: @@ -20588,12 +20646,6 @@ namespace ts { } function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { - node = getParseTreeNode(node); - // Purely synthesized nodes are always emitted. - if (node === undefined) { - return true; - } - if (isAliasSymbolDeclaration(node)) { const symbol = getSymbolOfNode(node); if (symbol && getSymbolLinks(symbol).referenced) { @@ -20629,8 +20681,7 @@ namespace ts { } function getNodeCheckFlags(node: Node): NodeCheckFlags { - node = getParseTreeNode(node); - return node ? getNodeLinks(node).flags : undefined; + return getNodeLinks(node).flags; } function getEnumMemberValue(node: EnumMember): number { @@ -20638,6 +20689,16 @@ namespace ts { return getNodeLinks(node).enumMemberValue; } + function canHaveConstantValue(node: Node): node is EnumMember | PropertyAccessExpression | ElementAccessExpression { + switch (node.kind) { + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return true; + } + return false; + } + function getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number { if (node.kind === SyntaxKind.EnumMember) { return getEnumMemberValue(node); @@ -20814,10 +20875,21 @@ namespace ts { getReferencedImportDeclaration, getReferencedDeclarationWithCollidingName, isDeclarationWithCollidingName, - isValueAliasDeclaration, + isValueAliasDeclaration: node => { + node = getParseTreeNode(node); + // Synthesized nodes are always treated like values. + return node ? isValueAliasDeclaration(node) : true; + }, hasGlobalName, - isReferencedAliasDeclaration, - getNodeCheckFlags, + isReferencedAliasDeclaration: (node, checkChildren?) => { + node = getParseTreeNode(node); + // Synthesized nodes are always treated as referenced. + return node ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + getNodeCheckFlags: node => { + node = getParseTreeNode(node); + return node ? getNodeCheckFlags(node) : undefined; + }, isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, isImplementationOfOverload, @@ -20827,7 +20899,10 @@ namespace ts { writeBaseConstructorTypeOfClass, isSymbolAccessible, isEntityNameVisible, - getConstantValue, + getConstantValue: node => { + node = getParseTreeNode(node, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, collectLinkedAliases, getReferencedValueDeclaration, getTypeReferenceSerializationKind, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 91cd6a89b8b82..a38578a7a814f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3731,6 +3731,12 @@ namespace ts { return node.kind === SyntaxKind.PropertyAccessExpression; } + export function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAccessExpression | QualifiedName { + const kind = node.kind; + return kind === SyntaxKind.PropertyAccessExpression + || kind === SyntaxKind.QualifiedName; + } + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { return node.kind === SyntaxKind.ElementAccessExpression; } @@ -4081,6 +4087,12 @@ namespace ts { || kind === SyntaxKind.JsxExpression; } + export function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement { + const kind = node.kind; + return kind === SyntaxKind.JsxOpeningElement + || kind === SyntaxKind.JsxSelfClosingElement; + } + // Clauses export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { @@ -4531,7 +4543,7 @@ namespace ts { */ export function getParseTreeNode(node: Node, nodeTest?: (node: Node) => node is T): T; export function getParseTreeNode(node: Node, nodeTest?: (node: Node) => boolean): Node { - if (isParseTreeNode(node)) { + if (node == undefined || isParseTreeNode(node)) { return node; } From fc2cf2f78f71d4a38f39c721b6dc992f4636b163 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Feb 2017 17:21:29 -0800 Subject: [PATCH 06/14] Make visitor functions public --- src/compiler/types.ts | 7 + src/compiler/visitor.ts | 1606 +++++++++++++++++++-------------------- 2 files changed, 810 insertions(+), 803 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3c859694b44b2..c2bf7be4d15e4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3935,6 +3935,13 @@ */ export type FileTransformer = (node: SourceFile) => SourceFile; + export type VisitResult = T | T[]; + + /** + * A function that accepts and possible transforms a node. + */ + export type Visitor = (node: Node) => VisitResult; + export interface Printer { /** * Print a node and its subtree as-is, without any emit transformations. diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index fd2b0314dda74..bc6afacff66e9 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -2,1192 +2,1202 @@ /// /// -/* @internal */ namespace ts { - export type VisitResult = T | T[]; + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T, visitor: Visitor, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { + if (node === undefined || visitor === undefined) { + return node; + } - function reduceNode(node: Node, f: (memo: T, node: Node) => T, initial: T) { - return node ? f(initial, node) : initial; + aggregateTransformFlags(node); + const visited = visitor(node); + if (visited === node) { + return node; + } + + let visitedNode: Node; + if (visited === undefined) { + if (!optional) { + Debug.failNotOptional(); + } + + return undefined; + } + else if (isArray(visited)) { + visitedNode = (lift || extractSingleNode)(visited); + } + else { + visitedNode = visited; + } + + Debug.assertNode(visitedNode, test); + aggregateTransformFlags(visitedNode); + return visitedNode; } - function reduceNodeArray(nodes: Node[], f: (memo: T, nodes: Node[]) => T, initial: T) { - return nodes ? f(initial, nodes) : initial; + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray, visitor: Visitor, test: (node: Node) => boolean, start?: number, count?: number): NodeArray { + if (nodes === undefined || visitor === undefined) { + return nodes; + } + + let updated: NodeArray; + + // Ensure start and count have valid values + const length = nodes.length; + if (start === undefined || start < 0) { + start = 0; + } + + if (count === undefined || count > length - start) { + count = length - start; + } + + if (start > 0 || count < length) { + // If we are not visiting all of the original nodes, we must always create a new array. + // Since this is a fragment of a node array, we do not copy over the previous location + // and will only copy over `hasTrailingComma` if we are including the last element. + updated = createNodeArray([], /*hasTrailingComma*/ nodes.hasTrailingComma && start + count === length); + } + + // Visit each original node. + for (let i = 0; i < count; i++) { + const node = nodes[i + start]; + aggregateTransformFlags(node); + const visited = node !== undefined ? visitor(node) : undefined; + if (updated !== undefined || visited === undefined || visited !== node) { + if (updated === undefined) { + // Ensure we have a copy of `nodes`, up to the current index. + updated = createNodeArray(nodes.slice(0, i), nodes.hasTrailingComma); + setTextRange(updated, nodes); + } + if (visited) { + if (isArray(visited)) { + for (const visitedNode of visited) { + Debug.assertNode(visitedNode, test); + aggregateTransformFlags(visitedNode); + updated.push(visitedNode); + } + } + else { + Debug.assertNode(visited, test); + aggregateTransformFlags(visited); + updated.push(visited); + } + } + } + } + + return updated || nodes; } /** - * Similar to `reduceLeft`, performs a reduction against each child of a node. - * NOTE: Unlike `forEachChild`, this does *not* visit every node. + * Starts a new lexical environment and visits a statement list, ending the lexical environment + * and merging hoisted declarations upon completion. + */ + export function visitLexicalEnvironment(statements: NodeArray, visitor: Visitor, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { + context.startLexicalEnvironment(); + statements = visitNodes(statements, visitor, isStatement, start); + if (ensureUseStrict && !startsWithUseStrict(statements)) { + statements = setTextRange(createNodeArray([createStatement(createLiteral("use strict")), ...statements]), statements); + } + const declarations = context.endLexicalEnvironment(); + return setTextRange(createNodeArray(concatenate(statements, declarations)), statements); + } + + /** + * Starts a new lexical environment and visits a parameter list, suspending the lexical + * environment upon completion. + */ + export function visitParameterList(nodes: NodeArray, visitor: Visitor, context: TransformationContext) { + context.startLexicalEnvironment(); + const updated = visitNodes(nodes, visitor, isParameterDeclaration); + context.suspendLexicalEnvironment(); + return updated; + } + + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; + /** + * Resumes a suspended lexical environment and visits a concise body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody; + export function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody { + context.resumeLexicalEnvironment(); + const updated = visitNode(node, visitor, isConciseBody); + const declarations = context.endLexicalEnvironment(); + if (some(declarations)) { + const block = convertToFunctionBody(updated); + const statements = mergeLexicalEnvironment(block.statements, declarations); + return updateBlock(block, statements); + } + return updated; + } + + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. * - * @param node The node containing the children to reduce. - * @param initial The initial value to supply to the reduction. - * @param f The callback function + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. */ - export function reduceEachChild(node: Node, initial: T, cbNode: (memo: T, node: Node) => T, cbNodeArray?: (memo: T, nodes: Node[]) => T): T { + export function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; + export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext): Node { if (node === undefined) { - return initial; + return undefined; } - const reduceNodes: (nodes: Node[], f: (memo: T, node: Node | Node[]) => T, initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft; - const cbNodes = cbNodeArray || cbNode; const kind = node.kind; - // No need to visit nodes with no children. if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken)) { - return initial; + return node; } // We do not yet support types. if ((kind >= SyntaxKind.TypePredicate && kind <= SyntaxKind.LiteralType)) { - return initial; + return node; } - let result = initial; switch (node.kind) { - // Leaf nodes case SyntaxKind.SemicolonClassElement: case SyntaxKind.EmptyStatement: case SyntaxKind.OmittedExpression: case SyntaxKind.DebuggerStatement: - case SyntaxKind.NotEmittedStatement: // No need to visit nodes with no children. - break; + return node; // Names case SyntaxKind.QualifiedName: - result = reduceNode((node).left, cbNode, result); - result = reduceNode((node).right, cbNode, result); - break; + return updateQualifiedName(node, + visitNode((node).left, visitor, isEntityName), + visitNode((node).right, visitor, isIdentifier)); case SyntaxKind.ComputedPropertyName: - result = reduceNode((node).expression, cbNode, result); - break; + return updateComputedPropertyName(node, + visitNode((node).expression, visitor, isExpression)); // Signature elements case SyntaxKind.Parameter: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; + return updateParameter(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + (node).dotDotDotToken, + visitNode((node).name, visitor, isBindingName), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); case SyntaxKind.Decorator: - result = reduceNode((node).expression, cbNode, result); - break; + return updateDecorator(node, + visitNode((node).expression, visitor, isExpression)); // Type member case SyntaxKind.PropertyDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; + return updateProperty(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); case SyntaxKind.MethodDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateMethod(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.Constructor: - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateConstructor(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitParameterList((node).parameters, visitor, context), + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.GetAccessor: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateGetAccessor(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitParameterList((node).parameters, visitor, context), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.SetAccessor: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateSetAccessor(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitParameterList((node).parameters, visitor, context), + visitFunctionBody((node).body, visitor, context)); // Binding patterns case SyntaxKind.ObjectBindingPattern: + return updateObjectBindingPattern(node, + visitNodes((node).elements, visitor, isBindingElement)); + case SyntaxKind.ArrayBindingPattern: - result = reduceNodes((node).elements, cbNodes, result); - break; + return updateArrayBindingPattern(node, + visitNodes((node).elements, visitor, isArrayBindingElement)); case SyntaxKind.BindingElement: - result = reduceNode((node).propertyName, cbNode, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; + return updateBindingElement(node, + (node).dotDotDotToken, + visitNode((node).propertyName, visitor, isPropertyName, /*optional*/ true), + visitNode((node).name, visitor, isBindingName), + visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); // Expression case SyntaxKind.ArrayLiteralExpression: - result = reduceNodes((node).elements, cbNodes, result); - break; + return updateArrayLiteral(node, + visitNodes((node).elements, visitor, isExpression)); case SyntaxKind.ObjectLiteralExpression: - result = reduceNodes((node).properties, cbNodes, result); - break; + return updateObjectLiteral(node, + visitNodes((node).properties, visitor, isObjectLiteralElementLike)); case SyntaxKind.PropertyAccessExpression: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).name, cbNode, result); - break; + return updatePropertyAccess(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.ElementAccessExpression: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).argumentExpression, cbNode, result); - break; + return updateElementAccess(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).argumentExpression, visitor, isExpression)); case SyntaxKind.CallExpression: - result = reduceNode((node).expression, cbNode, result); - result = reduceNodes((node).typeArguments, cbNodes, result); - result = reduceNodes((node).arguments, cbNodes, result); - break; + return updateCall(node, + visitNode((node).expression, visitor, isExpression), + visitNodes((node).typeArguments, visitor, isTypeNode), + visitNodes((node).arguments, visitor, isExpression)); case SyntaxKind.NewExpression: - result = reduceNode((node).expression, cbNode, result); - result = reduceNodes((node).typeArguments, cbNodes, result); - result = reduceNodes((node).arguments, cbNodes, result); - break; + return updateNew(node, + visitNode((node).expression, visitor, isExpression), + visitNodes((node).typeArguments, visitor, isTypeNode), + visitNodes((node).arguments, visitor, isExpression)); case SyntaxKind.TaggedTemplateExpression: - result = reduceNode((node).tag, cbNode, result); - result = reduceNode((node).template, cbNode, result); - break; + return updateTaggedTemplate(node, + visitNode((node).tag, visitor, isExpression), + visitNode((node).template, visitor, isTemplateLiteral)); case SyntaxKind.TypeAssertionExpression: - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).expression, cbNode, result); - break; + return updateTypeAssertion(node, + visitNode((node).type, visitor, isTypeNode), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.ParenthesizedExpression: + return updateParen(node, + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.FunctionExpression: - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateFunctionExpression(node, + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ArrowFunction: - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateArrowFunction(node, + visitNodes((node).modifiers, visitor, isModifier), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitFunctionBody((node).body, visitor, context)); - case SyntaxKind.ParenthesizedExpression: case SyntaxKind.DeleteExpression: + return updateDelete(node, + visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.TypeOfExpression: + return updateTypeOf(node, + visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.VoidExpression: + return updateVoid(node, + visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.AwaitExpression: - case SyntaxKind.YieldExpression: - case SyntaxKind.SpreadElement: - case SyntaxKind.NonNullExpression: - result = reduceNode((node).expression, cbNode, result); - break; + return updateAwait(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.BinaryExpression: + return updateBinary(node, + visitNode((node).left, visitor, isExpression), + visitNode((node).right, visitor, isExpression)); case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - result = reduceNode((node).operand, cbNode, result); - break; + return updatePrefix(node, + visitNode((node).operand, visitor, isExpression)); - case SyntaxKind.BinaryExpression: - result = reduceNode((node).left, cbNode, result); - result = reduceNode((node).right, cbNode, result); - break; + case SyntaxKind.PostfixUnaryExpression: + return updatePostfix(node, + visitNode((node).operand, visitor, isExpression)); case SyntaxKind.ConditionalExpression: - result = reduceNode((node).condition, cbNode, result); - result = reduceNode((node).whenTrue, cbNode, result); - result = reduceNode((node).whenFalse, cbNode, result); - break; + return updateConditional(node, + visitNode((node).condition, visitor, isExpression), + visitNode((node).whenTrue, visitor, isExpression), + visitNode((node).whenFalse, visitor, isExpression)); case SyntaxKind.TemplateExpression: - result = reduceNode((node).head, cbNode, result); - result = reduceNodes((node).templateSpans, cbNodes, result); - break; + return updateTemplateExpression(node, + visitNode((node).head, visitor, isTemplateHead), + visitNodes((node).templateSpans, visitor, isTemplateSpan)); + + case SyntaxKind.YieldExpression: + return updateYield(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.SpreadElement: + return updateSpread(node, + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.ClassExpression: - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).heritageClauses, cbNodes, result); - result = reduceNodes((node).members, cbNodes, result); - break; + return updateClassExpression(node, + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitNodes((node).heritageClauses, visitor, isHeritageClause), + visitNodes((node).members, visitor, isClassElement)); case SyntaxKind.ExpressionWithTypeArguments: - result = reduceNode((node).expression, cbNode, result); - result = reduceNodes((node).typeArguments, cbNodes, result); - break; + return updateExpressionWithTypeArguments(node, + visitNodes((node).typeArguments, visitor, isTypeNode), + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.AsExpression: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).type, cbNode, result); - break; + return updateAsExpression(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.NonNullExpression: - result = reduceNode((node).expression, cbNode, result); - break; + return updateNonNullExpression(node, + visitNode((node).expression, visitor, isExpression)); // Misc case SyntaxKind.TemplateSpan: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).literal, cbNode, result); - break; + return updateTemplateSpan(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).literal, visitor, isTemplateMiddleOrTemplateTail)); // Element case SyntaxKind.Block: - result = reduceNodes((node).statements, cbNodes, result); - break; + return updateBlock(node, + visitNodes((node).statements, visitor, isStatement)); case SyntaxKind.VariableStatement: - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).declarationList, cbNode, result); - break; + return updateVariableStatement(node, + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).declarationList, visitor, isVariableDeclarationList)); case SyntaxKind.ExpressionStatement: - result = reduceNode((node).expression, cbNode, result); - break; + return updateStatement(node, + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.IfStatement: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).thenStatement, cbNode, result); - result = reduceNode((node).elseStatement, cbNode, result); - break; + return updateIf(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).thenStatement, visitor, isStatement, /*optional*/ false, liftToBlock), + visitNode((node).elseStatement, visitor, isStatement, /*optional*/ true, liftToBlock)); case SyntaxKind.DoStatement: - result = reduceNode((node).statement, cbNode, result); - result = reduceNode((node).expression, cbNode, result); - break; + return updateDo(node, + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock), + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).statement, cbNode, result); - break; + return updateWhile(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); case SyntaxKind.ForStatement: - result = reduceNode((node).initializer, cbNode, result); - result = reduceNode((node).condition, cbNode, result); - result = reduceNode((node).incrementor, cbNode, result); - result = reduceNode((node).statement, cbNode, result); - break; + return updateFor(node, + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).condition, visitor, isExpression), + visitNode((node).incrementor, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); case SyntaxKind.ForInStatement: + return updateForIn(node, + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + case SyntaxKind.ForOfStatement: - result = reduceNode((node).initializer, cbNode, result); - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).statement, cbNode, result); - break; + return updateForOf(node, + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + + case SyntaxKind.ContinueStatement: + return updateContinue(node, + visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); + + case SyntaxKind.BreakStatement: + return updateBreak(node, + visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - result = reduceNode((node).expression, cbNode, result); - break; + return updateReturn(node, + visitNode((node).expression, visitor, isExpression, /*optional*/ true)); + + case SyntaxKind.WithStatement: + return updateWith(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); case SyntaxKind.SwitchStatement: - result = reduceNode((node).expression, cbNode, result); - result = reduceNode((node).caseBlock, cbNode, result); - break; + return updateSwitch(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).caseBlock, visitor, isCaseBlock)); case SyntaxKind.LabeledStatement: - result = reduceNode((node).label, cbNode, result); - result = reduceNode((node).statement, cbNode, result); - break; + return updateLabel(node, + visitNode((node).label, visitor, isIdentifier), + visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + + case SyntaxKind.ThrowStatement: + return updateThrow(node, + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.TryStatement: - result = reduceNode((node).tryBlock, cbNode, result); - result = reduceNode((node).catchClause, cbNode, result); - result = reduceNode((node).finallyBlock, cbNode, result); - break; + return updateTry(node, + visitNode((node).tryBlock, visitor, isBlock), + visitNode((node).catchClause, visitor, isCatchClause, /*optional*/ true), + visitNode((node).finallyBlock, visitor, isBlock, /*optional*/ true)); case SyntaxKind.VariableDeclaration: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; - - case SyntaxKind.VariableDeclarationList: - result = reduceNodes((node).declarations, cbNodes, result); - break; + return updateVariableDeclaration(node, + visitNode((node).name, visitor, isBindingName), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); - case SyntaxKind.FunctionDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).parameters, cbNodes, result); - result = reduceNode((node).type, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + case SyntaxKind.VariableDeclarationList: + return updateVariableDeclarationList(node, + visitNodes((node).declarations, visitor, isVariableDeclaration)); + + case SyntaxKind.FunctionDeclaration: + return updateFunctionDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context), + visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ClassDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).typeParameters, cbNodes, result); - result = reduceNodes((node).heritageClauses, cbNodes, result); - result = reduceNodes((node).members, cbNodes, result); - break; + return updateClassDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNodes((node).typeParameters, visitor, isTypeParameter), + visitNodes((node).heritageClauses, visitor, isHeritageClause), + visitNodes((node).members, visitor, isClassElement)); case SyntaxKind.EnumDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNodes((node).members, cbNodes, result); - break; + return updateEnumDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + visitNodes((node).members, visitor, isEnumMember)); case SyntaxKind.ModuleDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).body, cbNode, result); - break; + return updateModuleDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + visitNode((node).body, visitor, isModuleBody)); case SyntaxKind.ModuleBlock: - result = reduceNodes((node).statements, cbNodes, result); - break; + return updateModuleBlock(node, + visitNodes((node).statements, visitor, isStatement)); case SyntaxKind.CaseBlock: - result = reduceNodes((node).clauses, cbNodes, result); - break; + return updateCaseBlock(node, + visitNodes((node).clauses, visitor, isCaseOrDefaultClause)); case SyntaxKind.ImportEqualsDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).moduleReference, cbNode, result); - break; + return updateImportEqualsDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + visitNode((node).moduleReference, visitor, isModuleReference)); case SyntaxKind.ImportDeclaration: - result = reduceNodes((node).decorators, cbNodes, result); - result = reduceNodes((node).modifiers, cbNodes, result); - result = reduceNode((node).importClause, cbNode, result); - result = reduceNode((node).moduleSpecifier, cbNode, result); - break; + return updateImportDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).importClause, visitor, isImportClause, /*optional*/ true), + visitNode((node).moduleSpecifier, visitor, isExpression)); case SyntaxKind.ImportClause: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).namedBindings, cbNode, result); - break; + return updateImportClause(node, + visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNode((node).namedBindings, visitor, isNamedImportBindings, /*optional*/ true)); case SyntaxKind.NamespaceImport: - result = reduceNode((node).name, cbNode, result); - break; + return updateNamespaceImport(node, + visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - result = reduceNodes((node).elements, cbNodes, result); - break; + return updateNamedImports(node, + visitNodes((node).elements, visitor, isImportSpecifier)); case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - result = reduceNode((node).propertyName, cbNode, result); - result = reduceNode((node).name, cbNode, result); - break; + return updateImportSpecifier(node, + visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.ExportAssignment: - result = reduceLeft((node).decorators, cbNode, result); - result = reduceLeft((node).modifiers, cbNode, result); - result = reduceNode((node).expression, cbNode, result); - break; + return updateExportAssignment(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.ExportDeclaration: - result = reduceLeft((node).decorators, cbNode, result); - result = reduceLeft((node).modifiers, cbNode, result); - result = reduceNode((node).exportClause, cbNode, result); - result = reduceNode((node).moduleSpecifier, cbNode, result); - break; + return updateExportDeclaration(node, + visitNodes((node).decorators, visitor, isDecorator), + visitNodes((node).modifiers, visitor, isModifier), + visitNode((node).exportClause, visitor, isNamedExports, /*optional*/ true), + visitNode((node).moduleSpecifier, visitor, isExpression, /*optional*/ true)); + + case SyntaxKind.NamedExports: + return updateNamedExports(node, + visitNodes((node).elements, visitor, isExportSpecifier)); + + case SyntaxKind.ExportSpecifier: + return updateExportSpecifier(node, + visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier)); // Module references case SyntaxKind.ExternalModuleReference: - result = reduceNode((node).expression, cbNode, result); - break; + return updateExternalModuleReference(node, + visitNode((node).expression, visitor, isExpression)); // JSX case SyntaxKind.JsxElement: - result = reduceNode((node).openingElement, cbNode, result); - result = reduceLeft((node).children, cbNode, result); - result = reduceNode((node).closingElement, cbNode, result); - break; + return updateJsxElement(node, + visitNode((node).openingElement, visitor, isJsxOpeningElement), + visitNodes((node).children, visitor, isJsxChild), + visitNode((node).closingElement, visitor, isJsxClosingElement)); case SyntaxKind.JsxSelfClosingElement: + return updateJsxSelfClosingElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression), + visitNodes((node).attributes, visitor, isJsxAttributeLike)); + case SyntaxKind.JsxOpeningElement: - result = reduceNode((node).tagName, cbNode, result); - result = reduceNodes((node).attributes, cbNodes, result); - break; + return updateJsxOpeningElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression), + visitNodes((node).attributes, visitor, isJsxAttributeLike)); case SyntaxKind.JsxClosingElement: - result = reduceNode((node).tagName, cbNode, result); - break; + return updateJsxClosingElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression)); case SyntaxKind.JsxAttribute: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; + return updateJsxAttribute(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).initializer, visitor, isStringLiteralOrJsxExpression)); case SyntaxKind.JsxSpreadAttribute: - result = reduceNode((node).expression, cbNode, result); - break; + return updateJsxSpreadAttribute(node, + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.JsxExpression: - result = reduceNode((node).expression, cbNode, result); - break; + return updateJsxExpression(node, + visitNode((node).expression, visitor, isExpression)); // Clauses case SyntaxKind.CaseClause: - result = reduceNode((node).expression, cbNode, result); - // fall-through + return updateCaseClause(node, + visitNode((node).expression, visitor, isExpression), + visitNodes((node).statements, visitor, isStatement)); case SyntaxKind.DefaultClause: - result = reduceNodes((node).statements, cbNodes, result); - break; + return updateDefaultClause(node, + visitNodes((node).statements, visitor, isStatement)); case SyntaxKind.HeritageClause: - result = reduceNodes((node).types, cbNodes, result); - break; + return updateHeritageClause(node, + visitNodes((node).types, visitor, isExpressionWithTypeArguments)); case SyntaxKind.CatchClause: - result = reduceNode((node).variableDeclaration, cbNode, result); - result = reduceNode((node).block, cbNode, result); - break; + return updateCatchClause(node, + visitNode((node).variableDeclaration, visitor, isVariableDeclaration), + visitNode((node).block, visitor, isBlock)); // Property assignments case SyntaxKind.PropertyAssignment: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - break; - - case SyntaxKind.ShorthandPropertyAssignment: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).objectAssignmentInitializer, cbNode, result); - break; - - case SyntaxKind.SpreadAssignment: - result = reduceNode((node).expression, cbNode, result); - break; - - // Enum - case SyntaxKind.EnumMember: - result = reduceNode((node).name, cbNode, result); - result = reduceNode((node).initializer, cbNode, result); - - // Top-level nodes - case SyntaxKind.SourceFile: - result = reduceNodes((node).statements, cbNodes, result); - break; - - // Transformation nodes - case SyntaxKind.PartiallyEmittedExpression: - result = reduceNode((node).expression, cbNode, result); - break; - - default: - break; - } - - return result; - } - - /** - * Visits a Node using the supplied visitor, possibly returning a new Node in its place. - * - * @param node The Node to visit. - * @param visitor The callback used to visit the Node. - * @param test A callback to execute to verify the Node is valid. - * @param optional An optional value indicating whether the Node is itself optional. - * @param lift An optional callback to execute to lift a NodeArray into a valid Node. - */ - export function visitNode(node: T, visitor: (node: Node) => VisitResult, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { - if (node === undefined || visitor === undefined) { - return node; - } - - aggregateTransformFlags(node); - const visited = visitor(node); - if (visited === node) { - return node; - } - - let visitedNode: Node; - if (visited === undefined) { - if (!optional) { - Debug.failNotOptional(); - } - - return undefined; - } - else if (isArray(visited)) { - visitedNode = (lift || extractSingleNode)(visited); - } - else { - visitedNode = visited; - } - - Debug.assertNode(visitedNode, test); - aggregateTransformFlags(visitedNode); - return visitedNode; - } + return updatePropertyAssignment(node, + visitNode((node).name, visitor, isPropertyName), + visitNode((node).initializer, visitor, isExpression)); - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray, visitor: (node: Node) => VisitResult, test: (node: Node) => boolean, start?: number, count?: number): NodeArray { - if (nodes === undefined) { - return undefined; - } + case SyntaxKind.ShorthandPropertyAssignment: + return updateShorthandPropertyAssignment(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).objectAssignmentInitializer, visitor, isExpression)); - let updated: NodeArray; + case SyntaxKind.SpreadAssignment: + return updateSpreadAssignment(node, + visitNode((node).expression, visitor, isExpression)); - // Ensure start and count have valid values - const length = nodes.length; - if (start === undefined || start < 0) { - start = 0; - } + // Enum + case SyntaxKind.EnumMember: + return updateEnumMember(node, + visitNode((node).name, visitor, isPropertyName), + visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); - if (count === undefined || count > length - start) { - count = length - start; - } + // Top-level nodes + case SyntaxKind.SourceFile: + return updateSourceFileNode(node, + visitLexicalEnvironment((node).statements, visitor, context)); - if (start > 0 || count < length) { - // If we are not visiting all of the original nodes, we must always create a new array. - // Since this is a fragment of a node array, we do not copy over the previous location - // and will only copy over `hasTrailingComma` if we are including the last element. - updated = createNodeArray([], /*hasTrailingComma*/ nodes.hasTrailingComma && start + count === length); - } + // Transformation nodes + case SyntaxKind.PartiallyEmittedExpression: + return updatePartiallyEmittedExpression(node, + visitNode((node).expression, visitor, isExpression)); - // Visit each original node. - for (let i = 0; i < count; i++) { - const node = nodes[i + start]; - aggregateTransformFlags(node); - const visited = node !== undefined ? visitor(node) : undefined; - if (updated !== undefined || visited === undefined || visited !== node) { - if (updated === undefined) { - // Ensure we have a copy of `nodes`, up to the current index. - updated = createNodeArray(nodes.slice(0, i), nodes.hasTrailingComma); - setTextRange(updated, nodes); - } - if (visited) { - if (isArray(visited)) { - for (const visitedNode of visited) { - Debug.assertNode(visitedNode, test); - aggregateTransformFlags(visitedNode); - updated.push(visitedNode); - } - } - else { - Debug.assertNode(visited, test); - aggregateTransformFlags(visited); - updated.push(visited); - } - } - } + default: + return node; } - - return updated || nodes; } /** - * Starts a new lexical environment and visits a statement list, ending the lexical environment - * and merging hoisted declarations upon completion. + * Extracts the single node from a NodeArray. + * + * @param nodes The NodeArray. */ - export function visitLexicalEnvironment(statements: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { - context.startLexicalEnvironment(); - statements = visitNodes(statements, visitor, isStatement, start); - if (ensureUseStrict && !startsWithUseStrict(statements)) { - statements = setTextRange(createNodeArray([createStatement(createLiteral("use strict")), ...statements]), statements); - } - const declarations = context.endLexicalEnvironment(); - return setTextRange(createNodeArray(concatenate(statements, declarations)), statements); + function extractSingleNode(nodes: Node[]): Node { + Debug.assert(nodes.length <= 1, "Too many nodes written to output."); + return singleOrUndefined(nodes); } +} - /** - * Starts a new lexical environment and visits a parameter list, suspending the lexical - * environment upon completion. - */ - export function visitParameterList(nodes: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext) { - context.startLexicalEnvironment(); - const updated = visitNodes(nodes, visitor, isParameterDeclaration); - context.suspendLexicalEnvironment(); - return updated; +/* @internal */ +namespace ts { + function reduceNode(node: Node, f: (memo: T, node: Node) => T, initial: T) { + return node ? f(initial, node) : initial; } - /** - * Resumes a suspended lexical environment and visits a function body, ending the lexical - * environment and merging hoisted declarations upon completion. - */ - export function visitFunctionBody(node: FunctionBody, visitor: (node: Node) => VisitResult, context: TransformationContext): FunctionBody; - /** - * Resumes a suspended lexical environment and visits a concise body, ending the lexical - * environment and merging hoisted declarations upon completion. - */ - export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext): ConciseBody; - export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext): ConciseBody { - context.resumeLexicalEnvironment(); - const updated = visitNode(node, visitor, isConciseBody); - const declarations = context.endLexicalEnvironment(); - if (some(declarations)) { - const block = convertToFunctionBody(updated); - const statements = mergeLexicalEnvironment(block.statements, declarations); - return updateBlock(block, statements); - } - return updated; + function reduceNodeArray(nodes: Node[], f: (memo: T, nodes: Node[]) => T, initial: T) { + return nodes ? f(initial, nodes) : initial; } /** - * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * Similar to `reduceLeft`, performs a reduction against each child of a node. + * NOTE: Unlike `forEachChild`, this does *not* visit every node. * - * @param node The Node whose children will be visited. - * @param visitor The callback used to visit each child. - * @param context A lexical environment context for the visitor. + * @param node The node containing the children to reduce. + * @param initial The initial value to supply to the reduction. + * @param f The callback function */ - export function visitEachChild(node: T, visitor: (node: Node) => VisitResult, context: TransformationContext): T; - export function visitEachChild(node: Node, visitor: (node: Node) => VisitResult, context: TransformationContext): Node { + export function reduceEachChild(node: Node, initial: T, cbNode: (memo: T, node: Node) => T, cbNodeArray?: (memo: T, nodes: Node[]) => T): T { if (node === undefined) { - return undefined; + return initial; } + const reduceNodes: (nodes: Node[], f: (memo: T, node: Node | Node[]) => T, initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft; + const cbNodes = cbNodeArray || cbNode; const kind = node.kind; + // No need to visit nodes with no children. if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken)) { - return node; + return initial; } // We do not yet support types. if ((kind >= SyntaxKind.TypePredicate && kind <= SyntaxKind.LiteralType)) { - return node; + return initial; } + let result = initial; switch (node.kind) { + // Leaf nodes case SyntaxKind.SemicolonClassElement: case SyntaxKind.EmptyStatement: case SyntaxKind.OmittedExpression: case SyntaxKind.DebuggerStatement: + case SyntaxKind.NotEmittedStatement: // No need to visit nodes with no children. - return node; + break; // Names case SyntaxKind.QualifiedName: - return updateQualifiedName(node, - visitNode((node).left, visitor, isEntityName), - visitNode((node).right, visitor, isIdentifier)); + result = reduceNode((node).left, cbNode, result); + result = reduceNode((node).right, cbNode, result); + break; case SyntaxKind.ComputedPropertyName: - return updateComputedPropertyName(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // Signature elements case SyntaxKind.Parameter: - return updateParameter(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - (node).dotDotDotToken, - visitNode((node).name, visitor, isBindingName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; case SyntaxKind.Decorator: - return updateDecorator(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // Type member case SyntaxKind.PropertyDeclaration: - return updateProperty(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; case SyntaxKind.MethodDeclaration: - return updateMethod(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.Constructor: - return updateConstructor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitParameterList((node).parameters, visitor, context), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.GetAccessor: - return updateGetAccessor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.SetAccessor: - return updateSetAccessor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).body, cbNode, result); + break; // Binding patterns case SyntaxKind.ObjectBindingPattern: - return updateObjectBindingPattern(node, - visitNodes((node).elements, visitor, isBindingElement)); - case SyntaxKind.ArrayBindingPattern: - return updateArrayBindingPattern(node, - visitNodes((node).elements, visitor, isArrayBindingElement)); + result = reduceNodes((node).elements, cbNodes, result); + break; case SyntaxKind.BindingElement: - return updateBindingElement(node, - (node).dotDotDotToken, - visitNode((node).propertyName, visitor, isPropertyName, /*optional*/ true), - visitNode((node).name, visitor, isBindingName), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + result = reduceNode((node).propertyName, cbNode, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; // Expression case SyntaxKind.ArrayLiteralExpression: - return updateArrayLiteral(node, - visitNodes((node).elements, visitor, isExpression)); + result = reduceNodes((node).elements, cbNodes, result); + break; case SyntaxKind.ObjectLiteralExpression: - return updateObjectLiteral(node, - visitNodes((node).properties, visitor, isObjectLiteralElementLike)); + result = reduceNodes((node).properties, cbNodes, result); + break; case SyntaxKind.PropertyAccessExpression: - return updatePropertyAccess(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).name, visitor, isIdentifier)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).name, cbNode, result); + break; case SyntaxKind.ElementAccessExpression: - return updateElementAccess(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).argumentExpression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).argumentExpression, cbNode, result); + break; case SyntaxKind.CallExpression: - return updateCall(node, - visitNode((node).expression, visitor, isExpression), - visitNodes((node).typeArguments, visitor, isTypeNode), - visitNodes((node).arguments, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNodes((node).typeArguments, cbNodes, result); + result = reduceNodes((node).arguments, cbNodes, result); + break; case SyntaxKind.NewExpression: - return updateNew(node, - visitNode((node).expression, visitor, isExpression), - visitNodes((node).typeArguments, visitor, isTypeNode), - visitNodes((node).arguments, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNodes((node).typeArguments, cbNodes, result); + result = reduceNodes((node).arguments, cbNodes, result); + break; case SyntaxKind.TaggedTemplateExpression: - return updateTaggedTemplate(node, - visitNode((node).tag, visitor, isExpression), - visitNode((node).template, visitor, isTemplateLiteral)); + result = reduceNode((node).tag, cbNode, result); + result = reduceNode((node).template, cbNode, result); + break; case SyntaxKind.TypeAssertionExpression: - return updateTypeAssertion(node, - visitNode((node).type, visitor, isTypeNode), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.ParenthesizedExpression: - return updateParen(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.FunctionExpression: - return updateFunctionExpression(node, - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.ArrowFunction: - return updateArrowFunction(node, - visitNodes((node).modifiers, visitor, isModifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; + case SyntaxKind.ParenthesizedExpression: case SyntaxKind.DeleteExpression: - return updateDelete(node, - visitNode((node).expression, visitor, isExpression)); - case SyntaxKind.TypeOfExpression: - return updateTypeOf(node, - visitNode((node).expression, visitor, isExpression)); - case SyntaxKind.VoidExpression: - return updateVoid(node, - visitNode((node).expression, visitor, isExpression)); - case SyntaxKind.AwaitExpression: - return updateAwait(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.BinaryExpression: - return updateBinary(node, - visitNode((node).left, visitor, isExpression), - visitNode((node).right, visitor, isExpression)); + case SyntaxKind.YieldExpression: + case SyntaxKind.SpreadElement: + case SyntaxKind.NonNullExpression: + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.PrefixUnaryExpression: - return updatePrefix(node, - visitNode((node).operand, visitor, isExpression)); - case SyntaxKind.PostfixUnaryExpression: - return updatePostfix(node, - visitNode((node).operand, visitor, isExpression)); + result = reduceNode((node).operand, cbNode, result); + break; + + case SyntaxKind.BinaryExpression: + result = reduceNode((node).left, cbNode, result); + result = reduceNode((node).right, cbNode, result); + break; case SyntaxKind.ConditionalExpression: - return updateConditional(node, - visitNode((node).condition, visitor, isExpression), - visitNode((node).whenTrue, visitor, isExpression), - visitNode((node).whenFalse, visitor, isExpression)); + result = reduceNode((node).condition, cbNode, result); + result = reduceNode((node).whenTrue, cbNode, result); + result = reduceNode((node).whenFalse, cbNode, result); + break; case SyntaxKind.TemplateExpression: - return updateTemplateExpression(node, - visitNode((node).head, visitor, isTemplateHead), - visitNodes((node).templateSpans, visitor, isTemplateSpan)); - - case SyntaxKind.YieldExpression: - return updateYield(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.SpreadElement: - return updateSpread(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).head, cbNode, result); + result = reduceNodes((node).templateSpans, cbNodes, result); + break; case SyntaxKind.ClassExpression: - return updateClassExpression(node, - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitNodes((node).heritageClauses, visitor, isHeritageClause), - visitNodes((node).members, visitor, isClassElement)); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).heritageClauses, cbNodes, result); + result = reduceNodes((node).members, cbNodes, result); + break; case SyntaxKind.ExpressionWithTypeArguments: - return updateExpressionWithTypeArguments(node, - visitNodes((node).typeArguments, visitor, isTypeNode), - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNodes((node).typeArguments, cbNodes, result); + break; case SyntaxKind.AsExpression: - return updateAsExpression(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).type, visitor, isTypeNode)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).type, cbNode, result); + break; case SyntaxKind.NonNullExpression: - return updateNonNullExpression(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // Misc case SyntaxKind.TemplateSpan: - return updateTemplateSpan(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).literal, visitor, isTemplateMiddleOrTemplateTail)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).literal, cbNode, result); + break; // Element case SyntaxKind.Block: - return updateBlock(node, - visitNodes((node).statements, visitor, isStatement)); + result = reduceNodes((node).statements, cbNodes, result); + break; case SyntaxKind.VariableStatement: - return updateVariableStatement(node, - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).declarationList, visitor, isVariableDeclarationList)); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).declarationList, cbNode, result); + break; case SyntaxKind.ExpressionStatement: - return updateStatement(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.IfStatement: - return updateIf(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).thenStatement, visitor, isStatement, /*optional*/ false, liftToBlock), - visitNode((node).elseStatement, visitor, isStatement, /*optional*/ true, liftToBlock)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).thenStatement, cbNode, result); + result = reduceNode((node).elseStatement, cbNode, result); + break; case SyntaxKind.DoStatement: - return updateDo(node, - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock), - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).statement, cbNode, result); + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.WhileStatement: - return updateWhile(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + case SyntaxKind.WithStatement: + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).statement, cbNode, result); + break; case SyntaxKind.ForStatement: - return updateFor(node, - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).condition, visitor, isExpression), - visitNode((node).incrementor, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + result = reduceNode((node).initializer, cbNode, result); + result = reduceNode((node).condition, cbNode, result); + result = reduceNode((node).incrementor, cbNode, result); + result = reduceNode((node).statement, cbNode, result); + break; case SyntaxKind.ForInStatement: - return updateForIn(node, - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); - case SyntaxKind.ForOfStatement: - return updateForOf(node, - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); - - case SyntaxKind.ContinueStatement: - return updateContinue(node, - visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); - - case SyntaxKind.BreakStatement: - return updateBreak(node, - visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); + result = reduceNode((node).initializer, cbNode, result); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).statement, cbNode, result); + break; case SyntaxKind.ReturnStatement: - return updateReturn(node, - visitNode((node).expression, visitor, isExpression, /*optional*/ true)); - - case SyntaxKind.WithStatement: - return updateWith(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + case SyntaxKind.ThrowStatement: + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.SwitchStatement: - return updateSwitch(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).caseBlock, visitor, isCaseBlock)); - - case SyntaxKind.LabeledStatement: - return updateLabel(node, - visitNode((node).label, visitor, isIdentifier), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).caseBlock, cbNode, result); + break; - case SyntaxKind.ThrowStatement: - return updateThrow(node, - visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.LabeledStatement: + result = reduceNode((node).label, cbNode, result); + result = reduceNode((node).statement, cbNode, result); + break; case SyntaxKind.TryStatement: - return updateTry(node, - visitNode((node).tryBlock, visitor, isBlock), - visitNode((node).catchClause, visitor, isCatchClause, /*optional*/ true), - visitNode((node).finallyBlock, visitor, isBlock, /*optional*/ true)); + result = reduceNode((node).tryBlock, cbNode, result); + result = reduceNode((node).catchClause, cbNode, result); + result = reduceNode((node).finallyBlock, cbNode, result); + break; case SyntaxKind.VariableDeclaration: - return updateVariableDeclaration(node, - visitNode((node).name, visitor, isBindingName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; case SyntaxKind.VariableDeclarationList: - return updateVariableDeclarationList(node, - visitNodes((node).declarations, visitor, isVariableDeclaration)); + result = reduceNodes((node).declarations, cbNodes, result); + break; case SyntaxKind.FunctionDeclaration: - return updateFunctionDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).parameters, cbNodes, result); + result = reduceNode((node).type, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.ClassDeclaration: - return updateClassDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitNodes((node).heritageClauses, visitor, isHeritageClause), - visitNodes((node).members, visitor, isClassElement)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).typeParameters, cbNodes, result); + result = reduceNodes((node).heritageClauses, cbNodes, result); + result = reduceNodes((node).members, cbNodes, result); + break; case SyntaxKind.EnumDeclaration: - return updateEnumDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - visitNodes((node).members, visitor, isEnumMember)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNodes((node).members, cbNodes, result); + break; case SyntaxKind.ModuleDeclaration: - return updateModuleDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - visitNode((node).body, visitor, isModuleBody)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).body, cbNode, result); + break; case SyntaxKind.ModuleBlock: - return updateModuleBlock(node, - visitNodes((node).statements, visitor, isStatement)); + result = reduceNodes((node).statements, cbNodes, result); + break; case SyntaxKind.CaseBlock: - return updateCaseBlock(node, - visitNodes((node).clauses, visitor, isCaseOrDefaultClause)); + result = reduceNodes((node).clauses, cbNodes, result); + break; case SyntaxKind.ImportEqualsDeclaration: - return updateImportEqualsDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - visitNode((node).moduleReference, visitor, isModuleReference)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).moduleReference, cbNode, result); + break; case SyntaxKind.ImportDeclaration: - return updateImportDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).importClause, visitor, isImportClause, /*optional*/ true), - visitNode((node).moduleSpecifier, visitor, isExpression)); + result = reduceNodes((node).decorators, cbNodes, result); + result = reduceNodes((node).modifiers, cbNodes, result); + result = reduceNode((node).importClause, cbNode, result); + result = reduceNode((node).moduleSpecifier, cbNode, result); + break; case SyntaxKind.ImportClause: - return updateImportClause(node, - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), - visitNode((node).namedBindings, visitor, isNamedImportBindings, /*optional*/ true)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).namedBindings, cbNode, result); + break; case SyntaxKind.NamespaceImport: - return updateNamespaceImport(node, - visitNode((node).name, visitor, isIdentifier)); + result = reduceNode((node).name, cbNode, result); + break; case SyntaxKind.NamedImports: - return updateNamedImports(node, - visitNodes((node).elements, visitor, isImportSpecifier)); + case SyntaxKind.NamedExports: + result = reduceNodes((node).elements, cbNodes, result); + break; case SyntaxKind.ImportSpecifier: - return updateImportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), - visitNode((node).name, visitor, isIdentifier)); + case SyntaxKind.ExportSpecifier: + result = reduceNode((node).propertyName, cbNode, result); + result = reduceNode((node).name, cbNode, result); + break; case SyntaxKind.ExportAssignment: - return updateExportAssignment(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).expression, visitor, isExpression)); + result = reduceLeft((node).decorators, cbNode, result); + result = reduceLeft((node).modifiers, cbNode, result); + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.ExportDeclaration: - return updateExportDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).exportClause, visitor, isNamedExports, /*optional*/ true), - visitNode((node).moduleSpecifier, visitor, isExpression, /*optional*/ true)); - - case SyntaxKind.NamedExports: - return updateNamedExports(node, - visitNodes((node).elements, visitor, isExportSpecifier)); - - case SyntaxKind.ExportSpecifier: - return updateExportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), - visitNode((node).name, visitor, isIdentifier)); + result = reduceLeft((node).decorators, cbNode, result); + result = reduceLeft((node).modifiers, cbNode, result); + result = reduceNode((node).exportClause, cbNode, result); + result = reduceNode((node).moduleSpecifier, cbNode, result); + break; // Module references case SyntaxKind.ExternalModuleReference: - return updateExternalModuleReference(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // JSX case SyntaxKind.JsxElement: - return updateJsxElement(node, - visitNode((node).openingElement, visitor, isJsxOpeningElement), - visitNodes((node).children, visitor, isJsxChild), - visitNode((node).closingElement, visitor, isJsxClosingElement)); + result = reduceNode((node).openingElement, cbNode, result); + result = reduceLeft((node).children, cbNode, result); + result = reduceNode((node).closingElement, cbNode, result); + break; case SyntaxKind.JsxSelfClosingElement: - return updateJsxSelfClosingElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression), - visitNodes((node).attributes, visitor, isJsxAttributeLike)); - case SyntaxKind.JsxOpeningElement: - return updateJsxOpeningElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression), - visitNodes((node).attributes, visitor, isJsxAttributeLike)); + result = reduceNode((node).tagName, cbNode, result); + result = reduceNodes((node).attributes, cbNodes, result); + break; case SyntaxKind.JsxClosingElement: - return updateJsxClosingElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression)); + result = reduceNode((node).tagName, cbNode, result); + break; case SyntaxKind.JsxAttribute: - return updateJsxAttribute(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).initializer, visitor, isStringLiteralOrJsxExpression)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; case SyntaxKind.JsxSpreadAttribute: - return updateJsxSpreadAttribute(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; case SyntaxKind.JsxExpression: - return updateJsxExpression(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // Clauses case SyntaxKind.CaseClause: - return updateCaseClause(node, - visitNode((node).expression, visitor, isExpression), - visitNodes((node).statements, visitor, isStatement)); + result = reduceNode((node).expression, cbNode, result); + // fall-through case SyntaxKind.DefaultClause: - return updateDefaultClause(node, - visitNodes((node).statements, visitor, isStatement)); + result = reduceNodes((node).statements, cbNodes, result); + break; case SyntaxKind.HeritageClause: - return updateHeritageClause(node, - visitNodes((node).types, visitor, isExpressionWithTypeArguments)); + result = reduceNodes((node).types, cbNodes, result); + break; case SyntaxKind.CatchClause: - return updateCatchClause(node, - visitNode((node).variableDeclaration, visitor, isVariableDeclaration), - visitNode((node).block, visitor, isBlock)); + result = reduceNode((node).variableDeclaration, cbNode, result); + result = reduceNode((node).block, cbNode, result); + break; // Property assignments case SyntaxKind.PropertyAssignment: - return updatePropertyAssignment(node, - visitNode((node).name, visitor, isPropertyName), - visitNode((node).initializer, visitor, isExpression)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); + break; case SyntaxKind.ShorthandPropertyAssignment: - return updateShorthandPropertyAssignment(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).objectAssignmentInitializer, visitor, isExpression)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).objectAssignmentInitializer, cbNode, result); + break; case SyntaxKind.SpreadAssignment: - return updateSpreadAssignment(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; // Enum case SyntaxKind.EnumMember: - return updateEnumMember(node, - visitNode((node).name, visitor, isPropertyName), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + result = reduceNode((node).name, cbNode, result); + result = reduceNode((node).initializer, cbNode, result); // Top-level nodes case SyntaxKind.SourceFile: - return updateSourceFileNode(node, - visitLexicalEnvironment((node).statements, visitor, context)); + result = reduceNodes((node).statements, cbNodes, result); + break; // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: - return updatePartiallyEmittedExpression(node, - visitNode((node).expression, visitor, isExpression)); + result = reduceNode((node).expression, cbNode, result); + break; default: - return node; + break; } + + return result; } /** @@ -1255,16 +1265,6 @@ namespace ts { return singleOrUndefined(nodes) || createBlock(>nodes); } - /** - * Extracts the single node from a NodeArray. - * - * @param nodes The NodeArray. - */ - function extractSingleNode(nodes: Node[]): Node { - Debug.assert(nodes.length <= 1, "Too many nodes written to output."); - return singleOrUndefined(nodes); - } - /** * Aggregates the TransformFlags for a Node and its subtree. */ From 3b20d82ba6e6325739303fdb6171a6fb19f03853 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Feb 2017 17:02:07 -0800 Subject: [PATCH 07/14] allow 'transform()' to transform arbitrary nodes. --- src/compiler/emitter.ts | 4 +-- src/compiler/transformer.ts | 41 +++++++++++------------ src/compiler/types.ts | 26 +++++++------- src/harness/unittests/customTransforms.ts | 4 +-- src/harness/unittests/transform.ts | 2 +- src/services/transform.ts | 27 ++++----------- 6 files changed, 43 insertions(+), 61 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 96c87b2efd0c5..4b3fb5dac9a7e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -10,7 +10,7 @@ namespace ts { /*@internal*/ // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: Transformer[]): EmitResult { + export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory[]): EmitResult { const compilerOptions = host.getCompilerOptions(); const moduleKind = getEmitModuleKind(compilerOptions); const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; @@ -28,7 +28,7 @@ namespace ts { const sourceFiles = getSourceFilesToEmit(host, targetSourceFile); // Transform the source files - const transform = transformFiles(resolver, host, sourceFiles, transformers); + const transform = transformNodes(resolver, host, compilerOptions, sourceFiles, transformers, /*allowDtsFiles*/ false); // Create a printer to print the nodes const printer = createPrinter(compilerOptions, { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 9c29c8c60a7f1..097a84b2cc9f3 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -13,7 +13,7 @@ /* @internal */ namespace ts { - function getModuleTransformer(moduleKind: ModuleKind): Transformer { + function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { switch (moduleKind) { case ModuleKind.ES2015: return transformES2015Module; @@ -40,7 +40,7 @@ namespace ts { const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); - const transformers: Transformer[] = []; + const transformers: TransformerFactory[] = []; addRange(transformers, customTransformers && customTransformers.before); @@ -84,11 +84,13 @@ namespace ts { * 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. + * @param host The emit host object used to interact with the file system. + * @param options Compiler options to surface in the `TransformationContext`. + * @param nodes An array of nodes to transform. + * @param transforms An array of `TransformerFactory` callbacks. + * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. */ - export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult { + export function transformNodes(resolver: EmitResolver, host: EmitHost, options: CompilerOptions, nodes: T[], transformers: TransformerFactory[], allowDtsFiles: boolean): TransformationResult { const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; @@ -104,7 +106,7 @@ namespace ts { // The transformation context is provided to each transformer as part of transformer // initialization. const context: TransformationContext = { - getCompilerOptions: () => host.getCompilerOptions(), + getCompilerOptions: () => options, getEmitResolver: () => resolver, getEmitHost: () => host, startLexicalEnvironment, @@ -134,7 +136,9 @@ namespace ts { }; // Ensure the parse tree is clean before applying transformations - forEach(sourceFiles, disposeEmitNodes); + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); + } performance.mark("beforeTransform"); @@ -144,8 +148,8 @@ namespace ts { // prevent modification of transformation hooks. state = TransformationState.Initialized; - // Transform each source file. - const transformed = map(sourceFiles, transformSourceFile); + // Transform each node. + const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot); // prevent modification of the lexical environment. state = TransformationState.Completed; @@ -160,17 +164,8 @@ namespace ts { dispose }; - /** - * Transforms a source file. - * - * @param sourceFile The source file to transform. - */ - function transformSourceFile(sourceFile: SourceFile) { - if (isDeclarationFile(sourceFile)) { - return sourceFile; - } - - return transformation(sourceFile); + function transformRoot(node: T) { + return node && (!isSourceFile(node) || !isDeclarationFile(node)) ? transformation(node) : node; } /** @@ -366,7 +361,9 @@ namespace ts { function dispose() { if (state < TransformationState.Disposed) { // Clean up emit nodes on parse tree - forEach(sourceFiles, disposeEmitNodes); + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); + } // Release references to external entries for GC purposes. lexicalEnvironmentVariableDeclarations = undefined; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c2bf7be4d15e4..7cdace0bd7f11 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2308,9 +2308,9 @@ export interface CustomTransformers { /** Custom transformers to evaluate before built-in transformations. */ - before?: Transformer[]; + before?: TransformerFactory[]; /** Custom transformers to evaluate after built-in transformations. */ - after?: Transformer[]; + after?: TransformerFactory[]; } export interface SourceMapSpan { @@ -3867,7 +3867,7 @@ * are emitted by the pretty printer. * * NOTE: Transformation hooks should only be modified during `Transformer` initialization, - * before returning the `FileTransformer` callback. + * before returning the `NodeTransformer` callback. */ onSubstituteNode?: (hint: EmitHint, node: Node) => Node; @@ -3888,14 +3888,14 @@ * the printer emits a node. * * NOTE: Transformation hooks should only be modified during `Transformer` initialization, - * before returning the `FileTransformer` callback. + * before returning the `NodeTransformer` callback. */ onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; } - export interface TransformationResult { + export interface TransformationResult { /** Gets the transformed source files. */ - transformed: SourceFile[]; + transformed: T[]; /** Gets diagnostics for the transformation. */ diagnostics?: Diagnostic[]; @@ -3925,23 +3925,23 @@ } /** - * A function that is used to initialize and return a `FileTransformer` callback, which in turn - * will be used to transform one or more `SourceFile` objects. + * A function that is used to initialize and return a `Transformer` callback, which in turn + * will be used to transform one or more nodes. */ - export type Transformer = (context: TransformationContext) => FileTransformer; + export type TransformerFactory = (context: TransformationContext) => Transformer; /** - * A function that transforms a `SourceFile` object. + * A function that transforms a node. */ - export type FileTransformer = (node: SourceFile) => SourceFile; - - export type VisitResult = T | T[]; + export type Transformer = (node: T) => T; /** * A function that accepts and possible transforms a node. */ export type Visitor = (node: Node) => VisitResult; + export type VisitResult = T | T[]; + export interface Printer { /** * Print a node and its subtree as-is, without any emit transformations. diff --git a/src/harness/unittests/customTransforms.ts b/src/harness/unittests/customTransforms.ts index af8db28b69b9d..4b05ebb9ea7bf 100644 --- a/src/harness/unittests/customTransforms.ts +++ b/src/harness/unittests/customTransforms.ts @@ -47,7 +47,7 @@ namespace ts { ` }]; - const before: Transformer = context => { + const before: TransformerFactory = context => { return file => visitEachChild(file, visit, context); function visit(node: Node): VisitResult { switch (node.kind) { @@ -63,7 +63,7 @@ namespace ts { } }; - const after: Transformer = context => { + const after: TransformerFactory = context => { return file => visitEachChild(file, visit, context); function visit(node: Node): VisitResult { switch (node.kind) { diff --git a/src/harness/unittests/transform.ts b/src/harness/unittests/transform.ts index 1a05138b84ba3..2f010028d17f9 100644 --- a/src/harness/unittests/transform.ts +++ b/src/harness/unittests/transform.ts @@ -3,7 +3,7 @@ namespace ts { describe("TransformAPI", () => { - function transformsCorrectly(name: string, source: string, transformers: Transformer[]) { + function transformsCorrectly(name: string, source: string, transformers: TransformerFactory[]) { it(name, () => { Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${name}.js`, () => { const transformed = transform(createSourceFile("source.ts", source, ScriptTarget.ES2015), transformers); diff --git a/src/services/transform.ts b/src/services/transform.ts index 426e4280fd405..07fc3c759e22e 100644 --- a/src/services/transform.ts +++ b/src/services/transform.ts @@ -2,31 +2,16 @@ /// namespace ts { /** - * Transform one or more source files using the supplied transformers. - * @param source A `SourceFile` or an array of `SourceFiles`. - * @param transformers An array of `Transformer` callbacks used to process the transformation. + * Transform one or more nodes using the supplied transformers. + * @param source A single `Node` or an array of `Node` objects. + * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. * @param compilerOptions Optional compiler options. */ - export function transform(source: SourceFile | SourceFile[], transformers: Transformer[], compilerOptions?: CompilerOptions) { + export function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions) { const diagnostics: Diagnostic[] = []; compilerOptions = fixupCompilerOptions(compilerOptions, diagnostics); - const newLine = getNewLineCharacter(compilerOptions); - const sourceFiles = isArray(source) ? source : [source]; - const fileMap = arrayToMap(sourceFiles, sourceFile => sourceFile.fileName); - const emitHost: EmitHost = { - getCompilerOptions: () => compilerOptions, - getCanonicalFileName: fileName => fileName, - getCommonSourceDirectory: () => "", - getCurrentDirectory: () => "", - getNewLine: () => newLine, - getSourceFile: fileName => fileMap.get(fileName), - getSourceFileByPath: fileName => fileMap.get(fileName), - getSourceFiles: () => sourceFiles, - isSourceFileFromExternalLibrary: () => false, - isEmitBlocked: () => false, - writeFile: () => Debug.fail("'writeFile()' is not supported during transformation.") - }; - const result = transformFiles(/*resolver*/ undefined, emitHost, sourceFiles, transformers); + const nodes = isArray(source) ? source : [source]; + const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true); result.diagnostics = concatenate(result.diagnostics, diagnostics); return result; } From a295aa8fd1934b43e8f26e41da2550656e615209 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 9 Feb 2017 13:10:17 -0800 Subject: [PATCH 08/14] Reduce stack depth due to substitution --- src/compiler/emitter.ts | 26 +++++++++++--------------- src/compiler/transformer.ts | 13 ++++--------- src/compiler/transformers/es2015.ts | 4 ++-- src/compiler/transformers/ts.ts | 2 -- src/compiler/types.ts | 16 ++++++---------- src/compiler/visitor.ts | 6 +++--- src/harness/unittests/transform.ts | 2 +- 7 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4b3fb5dac9a7e..46a34fd9a1844 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -37,7 +37,7 @@ namespace ts { // transform hooks onEmitNode: transform.emitNodeWithNotification, - onSubstituteNode: transform.emitNodeWithSubstitution, + substituteNode: transform.substituteNode, // sourcemap hooks onEmitSourceMapOfNode: sourceMap.emitNodeWithSourceMap, @@ -198,7 +198,7 @@ namespace ts { onEmitNode, onEmitHelpers, onSetSourceFile, - onSubstituteNode, + substituteNode, } = handlers; const newLine = getNewLineCharacter(printerOptions); @@ -327,8 +327,8 @@ namespace ts { setWriter(/*output*/ undefined); } - function emit(node: Node, hint = EmitHint.Unspecified) { - pipelineEmitWithNotification(hint, node); + function emit(node: Node) { + pipelineEmitWithNotification(EmitHint.Unspecified, node); } function emitIdentifierName(node: Identifier) { @@ -349,6 +349,7 @@ namespace ts { } function pipelineEmitWithComments(hint: EmitHint, node: Node) { + node = trySubstituteNode(hint, node); if (emitNodeWithComments && hint !== EmitHint.SourceFile) { emitNodeWithComments(hint, node, pipelineEmitWithSourceMap); } @@ -359,16 +360,7 @@ namespace ts { function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { if (onEmitSourceMapOfNode && hint !== EmitHint.SourceFile && hint !== EmitHint.IdentifierName) { - onEmitSourceMapOfNode(hint, node, pipelineEmitWithSubstitution); - } - else { - pipelineEmitWithSubstitution(hint, node); - } - } - - function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { - if (onSubstituteNode) { - onSubstituteNode(hint, node, pipelineEmitWithHint); + onEmitSourceMapOfNode(hint, node, pipelineEmitWithHint); } else { pipelineEmitWithHint(hint, node); @@ -634,7 +626,7 @@ namespace ts { // If the node is an expression, try to emit it as an expression with // substitution. if (isExpression(node)) { - return pipelineEmitWithSubstitution(EmitHint.Expression, node); + return pipelineEmitExpression(trySubstituteNode(EmitHint.Expression, node)); } } @@ -731,6 +723,10 @@ namespace ts { } } + function trySubstituteNode(hint: EmitHint, node: Node) { + return node && substituteNode && substituteNode(hint, node) || node; + } + function emitBodyIndirect(node: Node, elements: NodeArray, emitCallback: (node: Node) => void): void { if (emitBodyWithDetachedComments) { emitBodyWithDetachedComments(node, elements, emitCallback); diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 097a84b2cc9f3..b9a75015f4194 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -159,7 +159,7 @@ namespace ts { return { transformed, - emitNodeWithSubstitution, + substituteNode, emitNodeWithNotification, dispose }; @@ -191,14 +191,9 @@ namespace ts { * @param node The node to emit. * @param emitCallback The callback used to emit the node or its substitute. */ - function emitNodeWithSubstitution(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); - if (node) { - if (isSubstitutionEnabled(node)) { - node = onSubstituteNode(hint, node) || node; - } - emitCallback(hint, node); - } + function substituteNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); + return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; } /** diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index e0fd435a90dc6..25429ca783c0a 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3549,7 +3549,7 @@ namespace ts { if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { const original = getParseTreeNode(node, isIdentifier); if (original && isNameOfDeclarationWithCollidingName(original)) { - return getGeneratedNameForNode(original); + return setTextRange(getGeneratedNameForNode(original), node); } } @@ -3602,7 +3602,7 @@ namespace ts { if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { const declaration = resolver.getReferencedDeclarationWithCollidingName(node); if (declaration) { - return getGeneratedNameForNode(declaration.name); + return setTextRange(getGeneratedNameForNode(declaration.name), node); } } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index c28d74f85274e..5aebc645ab70e 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -3322,8 +3322,6 @@ namespace ts { : getTextOfNode(node.argumentExpression); addSyntheticTrailingComment(substitute, SyntaxKind.MultiLineCommentTrivia, ` ${propertyName} `); - // wrap the substituted node so that it emits its own comments. - return createPartiallyEmittedExpression(substitute); } return substitute; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7cdace0bd7f11..064e5330e9e63 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3901,13 +3901,12 @@ diagnostics?: Diagnostic[]; /** - * Emits the substitute for a node, if one is available; otherwise, emits the node. + * Gets a substitute for a node, if one is available; otherwise, returns the original node. * * @param hint A hint as to the intended usage of the node. * @param node The node to substitute. - * @param emitCallback A callback used to emit the node or its substitute. */ - emitNodeWithSubstitution(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + substituteNode(hint: EmitHint, node: Node): Node; /** * Emits a node with possible notification. @@ -3998,23 +3997,20 @@ /** * A hook used by the Printer to perform just-in-time substitution of a node. This is * primarily used by node transformations that need to substitute one node for another, - * such as replacing `myExportedVar` with `exports.myExportedVar`. A compatible - * implementation **must** invoke `emitCallback` eith the provided `hint` and either - * the provided `node`, or its substitute. + * such as replacing `myExportedVar` with `exports.myExportedVar`. * @param hint A hint indicating the intended purpose of the node. * @param node The node to emit. - * @param emitCallback A callback that, when invoked, will emit the node. * @example * ```ts * var printer = createPrinter(printerOptions, { - * onSubstituteNode(hint, node, emitCallback) { + * substituteNode(hint, node) { * // perform substitution if necessary... - * emitCallback(hint, node); + * return node; * } * }); * ``` */ - onSubstituteNode?(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + substituteNode?(hint: EmitHint, node: Node): Node; /*@internal*/ onEmitSourceMapOfNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; /*@internal*/ onEmitSourceMapOfToken?: (node: Node, token: SyntaxKind, pos: number, emitCallback: (token: SyntaxKind, pos: number) => number) => number; /*@internal*/ onEmitSourceMapOfPosition?: (pos: number) => void; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index bc6afacff66e9..9d3ab2ea20310 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -709,7 +709,7 @@ namespace ts { return node ? f(initial, node) : initial; } - function reduceNodeArray(nodes: Node[], f: (memo: T, nodes: Node[]) => T, initial: T) { + function reduceNodeArray(nodes: NodeArray, f: (memo: T, nodes: NodeArray) => T, initial: T) { return nodes ? f(initial, nodes) : initial; } @@ -721,12 +721,12 @@ namespace ts { * @param initial The initial value to supply to the reduction. * @param f The callback function */ - export function reduceEachChild(node: Node, initial: T, cbNode: (memo: T, node: Node) => T, cbNodeArray?: (memo: T, nodes: Node[]) => T): T { + export function reduceEachChild(node: Node, initial: T, cbNode: (memo: T, node: Node) => T, cbNodeArray?: (memo: T, nodes: NodeArray) => T): T { if (node === undefined) { return initial; } - const reduceNodes: (nodes: Node[], f: (memo: T, node: Node | Node[]) => T, initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft; + const reduceNodes: (nodes: NodeArray, f: (memo: T, node: Node | NodeArray) => T, initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft; const cbNodes = cbNodeArray || cbNode; const kind = node.kind; diff --git a/src/harness/unittests/transform.ts b/src/harness/unittests/transform.ts index 2f010028d17f9..ae2caf4f23ba5 100644 --- a/src/harness/unittests/transform.ts +++ b/src/harness/unittests/transform.ts @@ -9,7 +9,7 @@ namespace ts { const transformed = transform(createSourceFile("source.ts", source, ScriptTarget.ES2015), transformers); const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, { onEmitNode: transformed.emitNodeWithNotification, - onSubstituteNode: transformed.emitNodeWithSubstitution + substituteNode: transformed.substituteNode }); const result = printer.printBundle(createBundle(transformed.transformed)); transformed.dispose(); From 3a7874f1384efd14080627fc3e2a2febe5d6545f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 12:27:41 -0800 Subject: [PATCH 09/14] Update factories to work with strictNullChecks --- src/compiler/factory.ts | 160 +++++++++++---------- src/compiler/transformers/destructuring.ts | 2 +- src/compiler/visitor.ts | 1 + 3 files changed, 83 insertions(+), 80 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d946933c52ead..342b97e665517 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -215,7 +215,7 @@ namespace ts { // Signature elements - export function createParameter(decorators: Decorator[], modifiers: Modifier[], dotDotDotToken: DotDotDotToken, name: string | Identifier | BindingPattern, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { + export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -227,7 +227,7 @@ namespace ts { return node; } - export function updateParameter(node: ParameterDeclaration, decorators: Decorator[], modifiers: Modifier[], dotDotDotToken: DotDotDotToken, name: BindingName, type: TypeNode, initializer: Expression) { + export function updateParameter(node: ParameterDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.dotDotDotToken !== dotDotDotToken @@ -252,7 +252,7 @@ namespace ts { // Type members - export function createProperty(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, questionToken: QuestionToken, type: TypeNode, initializer: Expression) { + export function createProperty(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression) { const node = createSynthesizedNode(SyntaxKind.PropertyDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -263,7 +263,7 @@ namespace ts { return node; } - export function updateProperty(node: PropertyDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, type: TypeNode, initializer: Expression) { + export function updateProperty(node: PropertyDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: PropertyName, type: TypeNode | undefined, initializer: Expression) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -273,7 +273,7 @@ namespace ts { : node; } - export function createMethod(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | PropertyName, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function createMethod(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -286,7 +286,7 @@ namespace ts { return node; } - export function updateMethod(node: MethodDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function updateMethod(node: MethodDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -298,7 +298,7 @@ namespace ts { : node; } - export function createConstructor(decorators: Decorator[], modifiers: Modifier[], parameters: ParameterDeclaration[], body: Block) { + export function createConstructor(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, parameters: ParameterDeclaration[], body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.Constructor); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -309,7 +309,7 @@ namespace ts { return node; } - export function updateConstructor(node: ConstructorDeclaration, decorators: Decorator[], modifiers: Modifier[], parameters: ParameterDeclaration[], body: Block) { + export function updateConstructor(node: ConstructorDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, parameters: ParameterDeclaration[], body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.parameters !== parameters @@ -318,7 +318,7 @@ namespace ts { : node; } - export function createGetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function createGetAccessor(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | PropertyName, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.GetAccessor); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -330,7 +330,7 @@ namespace ts { return node; } - export function updateGetAccessor(node: GetAccessorDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function updateGetAccessor(node: GetAccessorDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: PropertyName, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -341,7 +341,7 @@ namespace ts { : node; } - export function createSetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block) { + export function createSetAccessor(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | PropertyName, parameters: ParameterDeclaration[], body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.SetAccessor); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -352,7 +352,7 @@ namespace ts { return node; } - export function updateSetAccessor(node: SetAccessorDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, parameters: ParameterDeclaration[], body: Block) { + export function updateSetAccessor(node: SetAccessorDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: PropertyName, parameters: ParameterDeclaration[], body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -388,21 +388,21 @@ namespace ts { : node; } - export function createBindingElement(propertyName: string | PropertyName, dotDotDotToken: DotDotDotToken, name: string | BindingName, initializer?: Expression) { + export function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.BindingElement); - node.propertyName = asName(propertyName); node.dotDotDotToken = dotDotDotToken; + node.propertyName = asName(propertyName); node.name = asName(name); node.initializer = initializer; return node; } - export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken, propertyName: PropertyName, name: BindingName, initializer: Expression) { + export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { return node.propertyName !== propertyName || node.dotDotDotToken !== dotDotDotToken || node.name !== name || node.initializer !== initializer - ? updateNode(createBindingElement(propertyName, dotDotDotToken, name, initializer), node) + ? updateNode(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) : node; } @@ -470,7 +470,7 @@ namespace ts { : node; } - export function createCall(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { + export function createCall(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[]) { const node = createSynthesizedNode(SyntaxKind.CallExpression); node.expression = parenthesizeForAccess(expression); node.typeArguments = asNodeArray(typeArguments); @@ -478,7 +478,7 @@ namespace ts { return node; } - export function updateCall(node: CallExpression, expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { + export function updateCall(node: CallExpression, expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[]) { return expression !== node.expression || typeArguments !== node.typeArguments || argumentsArray !== node.arguments @@ -486,7 +486,7 @@ namespace ts { : node; } - export function createNew(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { + export function createNew(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[] | undefined) { const node = createSynthesizedNode(SyntaxKind.NewExpression); node.expression = parenthesizeForNew(expression); node.typeArguments = asNodeArray(typeArguments); @@ -494,7 +494,7 @@ namespace ts { return node; } - export function updateNew(node: NewExpression, expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { + export function updateNew(node: NewExpression, expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[] | undefined) { return node.expression !== expression || node.typeArguments !== typeArguments || node.arguments !== argumentsArray @@ -542,7 +542,7 @@ namespace ts { : node; } - export function createFunctionExpression(modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function createFunctionExpression(modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block) { const node = createSynthesizedNode(SyntaxKind.FunctionExpression); node.modifiers = asNodeArray(modifiers); node.asteriskToken = asteriskToken; @@ -554,7 +554,7 @@ namespace ts { return node; } - export function updateFunctionExpression(node: FunctionExpression, modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function updateFunctionExpression(node: FunctionExpression, modifiers: Modifier[] | undefined, name: Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block) { return node.name !== name || node.modifiers !== modifiers || node.typeParameters !== typeParameters @@ -565,7 +565,7 @@ namespace ts { : node; } - export function createArrowFunction(modifiers: Modifier[], typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, equalsGreaterThanToken: EqualsGreaterThanToken, body: ConciseBody) { + export function createArrowFunction(modifiers: Modifier[] | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, equalsGreaterThanToken: EqualsGreaterThanToken | undefined, body: ConciseBody) { const node = createSynthesizedNode(SyntaxKind.ArrowFunction); node.modifiers = asNodeArray(modifiers); node.typeParameters = asNodeArray(typeParameters); @@ -576,7 +576,7 @@ namespace ts { return node; } - export function updateArrowFunction(node: ArrowFunction, modifiers: Modifier[], typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: ConciseBody) { + export function updateArrowFunction(node: ArrowFunction, modifiers: Modifier[] | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: ConciseBody) { return node.modifiers !== modifiers || node.typeParameters !== typeParameters || node.parameters !== parameters @@ -720,9 +720,10 @@ namespace ts { return node; } - export function updateYield(node: YieldExpression, expression: Expression) { + export function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { return node.expression !== expression - ? updateNode(createYield(node.asteriskToken, expression), node) + || node.asteriskToken !== asteriskToken + ? updateNode(createYield(asteriskToken, expression), node) : node; } @@ -738,7 +739,7 @@ namespace ts { : node; } - export function createClassExpression(modifiers: Modifier[], name: string | Identifier, typeParameters: TypeParameterDeclaration[], heritageClauses: HeritageClause[], members: ClassElement[]) { + export function createClassExpression(modifiers: Modifier[] | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, heritageClauses: HeritageClause[], members: ClassElement[]) { const node = createSynthesizedNode(SyntaxKind.ClassExpression); node.decorators = undefined; node.modifiers = asNodeArray(modifiers); @@ -749,7 +750,7 @@ namespace ts { return node; } - export function updateClassExpression(node: ClassExpression, modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], heritageClauses: HeritageClause[], members: ClassElement[]) { + export function updateClassExpression(node: ClassExpression, modifiers: Modifier[] | undefined, name: Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, heritageClauses: HeritageClause[], members: ClassElement[]) { return node.modifiers !== modifiers || node.name !== name || node.typeParameters !== typeParameters @@ -834,7 +835,7 @@ namespace ts { : node; } - export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList | VariableDeclaration[]): VariableStatement { + export function createVariableStatement(modifiers: Modifier[] | undefined, declarationList: VariableDeclarationList | VariableDeclaration[]) { const node = createSynthesizedNode(SyntaxKind.VariableStatement); node.decorators = undefined; node.modifiers = asNodeArray(modifiers); @@ -842,14 +843,14 @@ namespace ts { return node; } - export function updateVariableStatement(node: VariableStatement, modifiers: Modifier[], declarationList: VariableDeclarationList): VariableStatement { + export function updateVariableStatement(node: VariableStatement, modifiers: Modifier[] | undefined, declarationList: VariableDeclarationList) { return node.modifiers !== modifiers || node.declarationList !== declarationList ? updateNode(createVariableStatement(modifiers, declarationList), node) : node; } - export function createVariableDeclarationList(declarations: VariableDeclaration[], flags?: NodeFlags): VariableDeclarationList { + export function createVariableDeclarationList(declarations: VariableDeclaration[], flags?: NodeFlags) { const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); node.flags |= flags; node.declarations = createNodeArray(declarations); @@ -862,7 +863,7 @@ namespace ts { : node; } - export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression): VariableDeclaration { + export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); node.name = asName(name); node.type = type; @@ -870,7 +871,7 @@ namespace ts { return node; } - export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode, initializer: Expression) { + export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { return node.name !== name || node.type !== type || node.initializer !== initializer @@ -882,7 +883,7 @@ namespace ts { return createSynthesizedNode(SyntaxKind.EmptyStatement); } - export function createStatement(expression: Expression): ExpressionStatement { + export function createStatement(expression: Expression) { const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); node.expression = parenthesizeExpressionForExpressionStatement(expression); return node; @@ -902,7 +903,7 @@ namespace ts { return node; } - export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement) { + export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { return node.expression !== expression || node.thenStatement !== thenStatement || node.elseStatement !== elseStatement @@ -938,7 +939,7 @@ namespace ts { : node; } - export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement) { + export function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { const node = createSynthesizedNode(SyntaxKind.ForStatement); node.initializer = initializer; node.condition = condition; @@ -947,7 +948,7 @@ namespace ts { return node; } - export function updateFor(node: ForStatement, initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement) { + export function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { return node.initializer !== initializer || node.condition !== condition || node.incrementor !== incrementor @@ -994,7 +995,7 @@ namespace ts { return node; } - export function updateContinue(node: ContinueStatement, label: Identifier) { + export function updateContinue(node: ContinueStatement, label: Identifier | undefined) { return node.label !== label ? updateNode(createContinue(label), node) : node; @@ -1006,7 +1007,7 @@ namespace ts { return node; } - export function updateBreak(node: BreakStatement, label: Identifier) { + export function updateBreak(node: BreakStatement, label: Identifier | undefined) { return node.label !== label ? updateNode(createBreak(label), node) : node; @@ -1018,7 +1019,7 @@ namespace ts { return node; } - export function updateReturn(node: ReturnStatement, expression: Expression) { + export function updateReturn(node: ReturnStatement, expression: Expression | undefined) { return node.expression !== expression ? updateNode(createReturn(expression), node) : node; @@ -1078,7 +1079,7 @@ namespace ts { : node; } - export function createTry(tryBlock: Block, catchClause: CatchClause, finallyBlock: Block) { + export function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.TryStatement); node.tryBlock = tryBlock; node.catchClause = catchClause; @@ -1086,7 +1087,7 @@ namespace ts { return node; } - export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause, finallyBlock: Block) { + export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { return node.tryBlock !== tryBlock || node.catchClause !== catchClause || node.finallyBlock !== finallyBlock @@ -1094,7 +1095,7 @@ namespace ts { : node; } - export function createFunctionDeclaration(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function createFunctionDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1107,7 +1108,7 @@ namespace ts { return node; } - export function updateFunctionDeclaration(node: FunctionDeclaration, decorators: Decorator[], modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { + export function updateFunctionDeclaration(node: FunctionDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -1119,7 +1120,7 @@ namespace ts { : node; } - export function createClassDeclaration(decorators: Decorator[], modifiers: Modifier[], name: string | Identifier, typeParameters: TypeParameterDeclaration[], heritageClauses: HeritageClause[], members: ClassElement[]) { + export function createClassDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, heritageClauses: HeritageClause[], members: ClassElement[]) { const node = createSynthesizedNode(SyntaxKind.ClassDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1130,7 +1131,7 @@ namespace ts { return node; } - export function updateClassDeclaration(node: ClassDeclaration, decorators: Decorator[], modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], heritageClauses: HeritageClause[], members: ClassElement[]) { + export function updateClassDeclaration(node: ClassDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, heritageClauses: HeritageClause[], members: ClassElement[]) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -1141,7 +1142,7 @@ namespace ts { : node; } - export function createEnumDeclaration(decorators: Decorator[], modifiers: Modifier[], name: string | Identifier, members: EnumMember[]) { + export function createEnumDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | Identifier, members: EnumMember[]) { const node = createSynthesizedNode(SyntaxKind.EnumDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1150,7 +1151,7 @@ namespace ts { return node; } - export function updateEnumDeclaration(node: EnumDeclaration, decorators: Decorator[], modifiers: Modifier[], name: Identifier, members: EnumMember[]) { + export function updateEnumDeclaration(node: EnumDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: Identifier, members: EnumMember[]) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -1159,7 +1160,7 @@ namespace ts { : node; } - export function createModuleDeclaration(decorators: Decorator[], modifiers: Modifier[], name: ModuleName, body: ModuleBody, flags?: NodeFlags) { + export function createModuleDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags) { const node = createSynthesizedNode(SyntaxKind.ModuleDeclaration); node.flags |= flags; node.decorators = asNodeArray(decorators); @@ -1169,7 +1170,7 @@ namespace ts { return node; } - export function updateModuleDeclaration(node: ModuleDeclaration, decorators: Decorator[], modifiers: Modifier[], name: ModuleName, body: ModuleBody) { + export function updateModuleDeclaration(node: ModuleDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -1202,7 +1203,7 @@ namespace ts { : node; } - export function createImportEqualsDeclaration(decorators: Decorator[], modifiers: Modifier[], name: string | Identifier, moduleReference: ModuleReference) { + export function createImportEqualsDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference) { const node = createSynthesizedNode(SyntaxKind.ImportEqualsDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1211,7 +1212,7 @@ namespace ts { return node; } - export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: Decorator[], modifiers: Modifier[], name: Identifier, moduleReference: ModuleReference) { + export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference) { return node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name @@ -1220,7 +1221,7 @@ namespace ts { : node; } - export function createImportDeclaration(decorators: Decorator[], modifiers: Modifier[], importClause: ImportClause, moduleSpecifier?: Expression): ImportDeclaration { + export function createImportDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier?: Expression): ImportDeclaration { const node = createSynthesizedNode(SyntaxKind.ImportDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1229,7 +1230,7 @@ namespace ts { return node; } - export function updateImportDeclaration(node: ImportDeclaration, decorators: Decorator[], modifiers: Modifier[], importClause: ImportClause, moduleSpecifier: Expression) { + export function updateImportDeclaration(node: ImportDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.importClause !== importClause || node.moduleSpecifier !== moduleSpecifier @@ -1275,21 +1276,21 @@ namespace ts { : node; } - export function createImportSpecifier(propertyName: Identifier, name: Identifier) { + export function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { const node = createSynthesizedNode(SyntaxKind.ImportSpecifier); node.propertyName = propertyName; node.name = name; return node; } - export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier, name: Identifier) { + export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { return node.propertyName !== propertyName || node.name !== name ? updateNode(createImportSpecifier(propertyName, name), node) : node; } - export function createExportAssignment(decorators: Decorator[], modifiers: Modifier[], isExportEquals: boolean, expression: Expression) { + export function createExportAssignment(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, isExportEquals: boolean, expression: Expression) { const node = createSynthesizedNode(SyntaxKind.ExportAssignment); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1298,7 +1299,7 @@ namespace ts { return node; } - export function updateExportAssignment(node: ExportAssignment, decorators: Decorator[], modifiers: Modifier[], expression: Expression) { + export function updateExportAssignment(node: ExportAssignment, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, expression: Expression) { return node.decorators !== decorators || node.modifiers !== modifiers || node.expression !== expression @@ -1306,7 +1307,7 @@ namespace ts { : node; } - export function createExportDeclaration(decorators: Decorator[], modifiers: Modifier[], exportClause: NamedExports, moduleSpecifier?: Expression) { + export function createExportDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression) { const node = createSynthesizedNode(SyntaxKind.ExportDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -1315,7 +1316,7 @@ namespace ts { return node; } - export function updateExportDeclaration(node: ExportDeclaration, decorators: Decorator[], modifiers: Modifier[], exportClause: NamedExports, moduleSpecifier: Expression) { + export function updateExportDeclaration(node: ExportDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier: Expression | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.exportClause !== exportClause @@ -1336,16 +1337,17 @@ namespace ts { : node; } - export function createExportSpecifier(name: string | Identifier, propertyName?: string | Identifier) { + export function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { const node = createSynthesizedNode(SyntaxKind.ExportSpecifier); - node.name = asName(name); node.propertyName = asName(propertyName); + node.name = asName(name); return node; } - export function updateExportSpecifier(node: ExportSpecifier, name: Identifier, propertyName: Identifier) { - return node.name !== name || node.propertyName !== propertyName - ? updateNode(createExportSpecifier(name, propertyName), node) + export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { + return node.propertyName !== propertyName + || node.name !== name + ? updateNode(createExportSpecifier(propertyName, name), node) : node; } @@ -1547,26 +1549,26 @@ namespace ts { return node; } - export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer: Expression) { + export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.ShorthandPropertyAssignment); node.name = asName(name); node.objectAssignmentInitializer = objectAssignmentInitializer !== undefined ? parenthesizeExpressionForList(objectAssignmentInitializer) : undefined; return node; } - export function createSpreadAssignment(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.SpreadAssignment); - node.expression = expression !== undefined ? parenthesizeExpressionForList(expression) : undefined; - return node; - } - - export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression) { + export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { if (node.name !== name || node.objectAssignmentInitializer !== objectAssignmentInitializer) { return updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node); } return node; } + export function createSpreadAssignment(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.SpreadAssignment); + node.expression = expression !== undefined ? parenthesizeExpressionForList(expression) : undefined; + return node; + } + export function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { if (node.expression !== expression) { return updateNode(createSpreadAssignment(expression), node); @@ -1774,7 +1776,7 @@ namespace ts { } export function createExternalModuleExport(exportName: Identifier) { - return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(exportName)])); + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(/*propertyName*/ undefined, exportName)])); } // Utilities @@ -1855,7 +1857,7 @@ namespace ts { /** * Gets flags that control emit behavior of a node. */ - export function getEmitFlags(node: Node) { + export function getEmitFlags(node: Node): EmitFlags | undefined { const emitNode = node.emitNode; return emitNode && emitNode.flags; } @@ -1879,7 +1881,7 @@ namespace ts { /** * Sets a custom text range to use when emitting source maps. */ - export function setSourceMapRange(node: T, range: TextRange) { + export function setSourceMapRange(node: T, range: TextRange | undefined) { getOrCreateEmitNode(node).sourceMapRange = range; return node; } @@ -1887,7 +1889,7 @@ namespace ts { /** * Gets the TextRange to use for source maps for a token of a node. */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { + export function getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange | undefined { const emitNode = node.emitNode; const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; return tokenSourceMapRanges && tokenSourceMapRanges[token]; @@ -1896,7 +1898,7 @@ namespace ts { /** * Sets the TextRange to use for source maps for a token of a node. */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange) { + export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange | undefined) { const emitNode = getOrCreateEmitNode(node); const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); tokenSourceMapRanges[token] = range; @@ -2019,7 +2021,7 @@ namespace ts { return compareValues(x.priority, y.priority); } - export function setOriginalNode(node: T, original: Node): T { + export function setOriginalNode(node: T, original: Node | undefined): T { node.original = original; if (original) { const emitNode = original.emitNode; diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index b4473e6a261e9..63cee5380b42c 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -448,7 +448,7 @@ namespace ts { } function makeBindingElement(name: Identifier) { - return createBindingElement(/*propertyName*/ undefined, /*dotDotDotToken*/ undefined, name); + return createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); } function makeAssignmentElement(name: Identifier) { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 7ca71aec0d0cd..c1de4454ec023 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -875,6 +875,7 @@ namespace ts { case SyntaxKind.YieldExpression: return updateYield(node, + (node).asteriskToken, visitNode((node).expression, visitor, isExpression)); case SyntaxKind.SpreadElement: From fd320b0f83ec7d11b38407c0607559b5bcb94558 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 13:48:32 -0800 Subject: [PATCH 10/14] Support strictNullChecks mode in visitors --- src/compiler/visitor.ts | 102 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 49e3480100c5d..2009d5263bd18 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -12,7 +12,42 @@ namespace ts { * @param optional An optional value indicating whether the Node is itself optional. * @param lift An optional callback to execute to lift a NodeArray into a valid Node. */ - export function visitNode(node: T, visitor: Visitor, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { + export function visitNode(node: T, visitor: Visitor, test: (node: Node) => node is T, optional?: boolean, lift?: (node: NodeArray) => T): T; + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T; + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T | undefined, visitor: Visitor, test: (node: Node) => node is T, optional?: boolean, lift?: (node: NodeArray) => T): T | undefined; + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T | undefined; + + export function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { if (node === undefined || visitor === undefined) { return node; } @@ -52,7 +87,51 @@ namespace ts { * @param start An optional value indicating the starting offset at which to start visiting. * @param count An optional value indicating the maximum number of nodes to visit. */ - export function visitNodes(nodes: NodeArray, visitor: Visitor, test: (node: Node) => boolean, start?: number, count?: number): NodeArray { + export function visitNodes(nodes: NodeArray, visitor: Visitor, test: (node: Node) => node is T, start?: number, count?: number): NodeArray; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test: (node: Node) => node is T, start?: number, count?: number): NodeArray | undefined; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined { if (nodes === undefined || visitor === undefined) { return nodes; } @@ -137,6 +216,11 @@ namespace ts { * environment and merging hoisted declarations upon completion. */ export function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: FunctionBody | undefined, visitor: Visitor, context: TransformationContext): FunctionBody | undefined; /** * Resumes a suspended lexical environment and visits a concise body, ending the lexical * environment and merging hoisted declarations upon completion. @@ -162,6 +246,16 @@ namespace ts { * @param context A lexical environment context for the visitor. */ export function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; + + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext): T | undefined; + export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext): Node { if (node === undefined) { return undefined; @@ -317,7 +411,7 @@ namespace ts { case SyntaxKind.FunctionExpression: return updateFunctionExpression(node, visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), + visitNode((node).name, visitor, isIdentifier, /*optional*/ true), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), @@ -508,7 +602,7 @@ namespace ts { return updateFunctionDeclaration(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), + visitNode((node).name, visitor, isIdentifier, /*optional*/ true), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), From 0243510315228821da1f1dcd0975d671581a2443 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 13:56:32 -0800 Subject: [PATCH 11/14] clean up trailing whitespace --- src/compiler/factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 96f407eb39374..81346fe59ba93 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1346,7 +1346,7 @@ namespace ts { export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { return node.propertyName !== propertyName - || node.name !== name + || node.name !== name ? updateNode(createExportSpecifier(propertyName, name), node) : node; } From af18df3fe823422d011c1b8576fb1f6f5e1c620a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 14:11:53 -0800 Subject: [PATCH 12/14] Simplify visitNode --- src/compiler/transformers/es2015.ts | 4 +- src/compiler/transformers/generators.ts | 14 +-- src/compiler/transformers/module/module.ts | 4 +- src/compiler/transformers/module/system.ts | 18 +-- src/compiler/transformers/ts.ts | 6 +- src/compiler/visitor.ts | 139 +++++++-------------- 6 files changed, 65 insertions(+), 120 deletions(-) diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 25429ca783c0a..91315ffe790d4 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -2348,7 +2348,7 @@ namespace ts { addRange(statements, convertedLoopBodyStatements); } else { - const statement = visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock); + const statement = visitNode(node.statement, visitor, isStatement, liftToBlock); if (isBlock(statement)) { addRange(statements, statement.statements); bodyLocation = statement; @@ -2570,7 +2570,7 @@ namespace ts { } startLexicalEnvironment(); - let loopBody = visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock); + let loopBody = visitNode(node.statement, visitor, isStatement, liftToBlock); const lexicalEnvironment = endLexicalEnvironment(); const currentState = convertedLoopState; diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index f40c2994998f2..6085d66c57d2d 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -1227,7 +1227,7 @@ namespace ts { case SyntaxKind.TryStatement: return transformAndEmitTryStatement(node); default: - return emitStatement(visitNode(node, visitor, isStatement, /*optional*/ true)); + return emitStatement(visitNode(node, visitor, isStatement)); } } @@ -1485,9 +1485,9 @@ namespace ts { variables.length > 0 ? inlineExpressions(map(variables, transformInitializedVariable)) : undefined, - visitNode(node.condition, visitor, isExpression, /*optional*/ true), - visitNode(node.incrementor, visitor, isExpression, /*optional*/ true), - visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.condition, visitor, isExpression), + visitNode(node.incrementor, visitor, isExpression), + visitNode(node.statement, visitor, isStatement, liftToBlock) ); } else { @@ -1609,7 +1609,7 @@ namespace ts { node = updateForIn(node, initializer.declarations[0].name, visitNode(node.expression, visitor, isExpression), - visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, visitor, isStatement, liftToBlock) ); } else { @@ -1659,14 +1659,14 @@ namespace ts { function transformAndEmitReturnStatement(node: ReturnStatement): void { emitReturn( - visitNode(node.expression, visitor, isExpression, /*optional*/ true), + visitNode(node.expression, visitor, isExpression), /*location*/ node ); } function visitReturnStatement(node: ReturnStatement) { return createInlineReturn( - visitNode(node.expression, visitor, isExpression, /*optional*/ true), + visitNode(node.expression, visitor, isExpression), /*location*/ node ); } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index bf963bdc0e5c9..8b566d333bb19 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -88,7 +88,7 @@ namespace ts { append(statements, createUnderscoreUnderscoreESModule()); } - append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); + append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); addRange(statements, endLexicalEnvironment()); addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); @@ -380,7 +380,7 @@ namespace ts { } // Visit each statement of the module body. - append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); + append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); // End the lexical environment for the module body diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index b29992015041f..6d6107217c8c0 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -245,7 +245,7 @@ namespace ts { ); // Visit the synthetic external helpers import declaration if present - visitNode(moduleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true); + visitNode(moduleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement); // Visit the statements of the source file, emitting any transformations into // the `executeStatements` array. We do this *before* we fill the `setters` array @@ -1218,8 +1218,8 @@ namespace ts { node = updateFor( node, visitForInitializer(node.initializer), - visitNode(node.condition, destructuringVisitor, isExpression, /*optional*/ true), - visitNode(node.incrementor, destructuringVisitor, isExpression, /*optional*/ true), + visitNode(node.condition, destructuringVisitor, isExpression), + visitNode(node.incrementor, destructuringVisitor, isExpression), visitNode(node.statement, nestedElementVisitor, isStatement) ); @@ -1240,7 +1240,7 @@ namespace ts { node, visitForInitializer(node.initializer), visitNode(node.expression, destructuringVisitor, isExpression), - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock) ); enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; @@ -1260,7 +1260,7 @@ namespace ts { node, visitForInitializer(node.initializer), visitNode(node.expression, destructuringVisitor, isExpression), - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock) ); enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; @@ -1305,7 +1305,7 @@ namespace ts { function visitDoStatement(node: DoStatement): VisitResult { return updateDo( node, - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock), + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock), visitNode(node.expression, destructuringVisitor, isExpression) ); } @@ -1319,7 +1319,7 @@ namespace ts { return updateWhile( node, visitNode(node.expression, destructuringVisitor, isExpression), - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock) ); } @@ -1332,7 +1332,7 @@ namespace ts { return updateLabel( node, node.label, - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock) ); } @@ -1345,7 +1345,7 @@ namespace ts { return updateWith( node, visitNode(node.expression, destructuringVisitor, isExpression), - visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, liftToBlock) ); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5aebc645ab70e..8836ba314dc0c 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2833,7 +2833,7 @@ namespace ts { } // Elide the declaration if the import clause was elided. - const importClause = visitNode(node.importClause, visitImportClause, isImportClause, /*optional*/ true); + const importClause = visitNode(node.importClause, visitImportClause, isImportClause); return importClause ? updateImportDeclaration( node, @@ -2852,7 +2852,7 @@ namespace ts { function visitImportClause(node: ImportClause): VisitResult { // Elide the import clause if we elide both its name and its named bindings. const name = resolver.isReferencedAliasDeclaration(node) ? node.name : undefined; - const namedBindings = visitNode(node.namedBindings, visitNamedImportBindings, isNamedImportBindings, /*optional*/ true); + const namedBindings = visitNode(node.namedBindings, visitNamedImportBindings, isNamedImportBindings); return (name || namedBindings) ? updateImportClause(node, name, namedBindings) : undefined; } @@ -2914,7 +2914,7 @@ namespace ts { } // Elide the export declaration if all of its named exports are elided. - const exportClause = visitNode(node.exportClause, visitNamedExports, isNamedExports, /*optional*/ true); + const exportClause = visitNode(node.exportClause, visitNamedExports, isNamedExports); return exportClause ? updateExportDeclaration( node, diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 2009d5263bd18..048499fd3ef4a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -9,10 +9,9 @@ namespace ts { * @param node The Node to visit. * @param visitor The callback used to visit the Node. * @param test A callback to execute to verify the Node is valid. - * @param optional An optional value indicating whether the Node is itself optional. * @param lift An optional callback to execute to lift a NodeArray into a valid Node. */ - export function visitNode(node: T, visitor: Visitor, test: (node: Node) => node is T, optional?: boolean, lift?: (node: NodeArray) => T): T; + export function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T; /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. @@ -20,34 +19,11 @@ namespace ts { * @param node The Node to visit. * @param visitor The callback used to visit the Node. * @param test A callback to execute to verify the Node is valid. - * @param optional An optional value indicating whether the Node is itself optional. * @param lift An optional callback to execute to lift a NodeArray into a valid Node. */ - export function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T; + export function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined; - /** - * Visits a Node using the supplied visitor, possibly returning a new Node in its place. - * - * @param node The Node to visit. - * @param visitor The callback used to visit the Node. - * @param test A callback to execute to verify the Node is valid. - * @param optional An optional value indicating whether the Node is itself optional. - * @param lift An optional callback to execute to lift a NodeArray into a valid Node. - */ - export function visitNode(node: T | undefined, visitor: Visitor, test: (node: Node) => node is T, optional?: boolean, lift?: (node: NodeArray) => T): T | undefined; - - /** - * Visits a Node using the supplied visitor, possibly returning a new Node in its place. - * - * @param node The Node to visit. - * @param visitor The callback used to visit the Node. - * @param test A callback to execute to verify the Node is valid. - * @param optional An optional value indicating whether the Node is itself optional. - * @param lift An optional callback to execute to lift a NodeArray into a valid Node. - */ - export function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T | undefined; - - export function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { + export function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined { if (node === undefined || visitor === undefined) { return node; } @@ -60,10 +36,6 @@ namespace ts { let visitedNode: Node; if (visited === undefined) { - if (!optional) { - Debug.failNotOptional(); - } - return undefined; } else if (isArray(visited)) { @@ -78,17 +50,6 @@ namespace ts { return visitedNode; } - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray, visitor: Visitor, test: (node: Node) => node is T, start?: number, count?: number): NodeArray; - /** * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. * @@ -100,17 +61,6 @@ namespace ts { */ export function visitNodes(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test: (node: Node) => node is T, start?: number, count?: number): NodeArray | undefined; - /** * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. * @@ -297,8 +247,8 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), (node).dotDotDotToken, visitNode((node).name, visitor, isBindingName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.Decorator: return updateDecorator(node, @@ -310,8 +260,8 @@ namespace ts { visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.MethodDeclaration: return updateMethod(node, @@ -320,7 +270,7 @@ namespace ts { visitNode((node).name, visitor, isPropertyName), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.Constructor: @@ -336,7 +286,7 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.SetAccessor: @@ -359,9 +309,9 @@ namespace ts { case SyntaxKind.BindingElement: return updateBindingElement(node, (node).dotDotDotToken, - visitNode((node).propertyName, visitor, isPropertyName, /*optional*/ true), + visitNode((node).propertyName, visitor, isPropertyName), visitNode((node).name, visitor, isBindingName), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + visitNode((node).initializer, visitor, isExpression)); // Expression case SyntaxKind.ArrayLiteralExpression: @@ -411,10 +361,10 @@ namespace ts { case SyntaxKind.FunctionExpression: return updateFunctionExpression(node, visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ArrowFunction: @@ -422,7 +372,7 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.DeleteExpression: @@ -477,7 +427,7 @@ namespace ts { case SyntaxKind.ClassExpression: return updateClassExpression(node, visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier), visitNodes((node).typeParameters, visitor, isTypeParameter), visitNodes((node).heritageClauses, visitor, isHeritageClause), visitNodes((node).members, visitor, isClassElement)); @@ -519,54 +469,54 @@ namespace ts { case SyntaxKind.IfStatement: return updateIf(node, visitNode((node).expression, visitor, isExpression), - visitNode((node).thenStatement, visitor, isStatement, /*optional*/ false, liftToBlock), - visitNode((node).elseStatement, visitor, isStatement, /*optional*/ true, liftToBlock)); + visitNode((node).thenStatement, visitor, isStatement, liftToBlock), + visitNode((node).elseStatement, visitor, isStatement, liftToBlock)); case SyntaxKind.DoStatement: return updateDo(node, - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock), + visitNode((node).statement, visitor, isStatement, liftToBlock), visitNode((node).expression, visitor, isExpression)); case SyntaxKind.WhileStatement: return updateWhile(node, visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.ForStatement: return updateFor(node, visitNode((node).initializer, visitor, isForInitializer), visitNode((node).condition, visitor, isExpression), visitNode((node).incrementor, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.ForInStatement: return updateForIn(node, visitNode((node).initializer, visitor, isForInitializer), visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.ForOfStatement: return updateForOf(node, visitNode((node).initializer, visitor, isForInitializer), visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.ContinueStatement: return updateContinue(node, - visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); + visitNode((node).label, visitor, isIdentifier)); case SyntaxKind.BreakStatement: return updateBreak(node, - visitNode((node).label, visitor, isIdentifier, /*optional*/ true)); + visitNode((node).label, visitor, isIdentifier)); case SyntaxKind.ReturnStatement: return updateReturn(node, - visitNode((node).expression, visitor, isExpression, /*optional*/ true)); + visitNode((node).expression, visitor, isExpression)); case SyntaxKind.WithStatement: return updateWith(node, visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.SwitchStatement: return updateSwitch(node, @@ -576,7 +526,7 @@ namespace ts { case SyntaxKind.LabeledStatement: return updateLabel(node, visitNode((node).label, visitor, isIdentifier), - visitNode((node).statement, visitor, isStatement, /*optional*/ false, liftToBlock)); + visitNode((node).statement, visitor, isStatement, liftToBlock)); case SyntaxKind.ThrowStatement: return updateThrow(node, @@ -585,14 +535,14 @@ namespace ts { case SyntaxKind.TryStatement: return updateTry(node, visitNode((node).tryBlock, visitor, isBlock), - visitNode((node).catchClause, visitor, isCatchClause, /*optional*/ true), - visitNode((node).finallyBlock, visitor, isBlock, /*optional*/ true)); + visitNode((node).catchClause, visitor, isCatchClause), + visitNode((node).finallyBlock, visitor, isBlock)); case SyntaxKind.VariableDeclaration: return updateVariableDeclaration(node, visitNode((node).name, visitor, isBindingName), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.VariableDeclarationList: return updateVariableDeclarationList(node, @@ -602,17 +552,17 @@ namespace ts { return updateFunctionDeclaration(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier), visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), - visitNode((node).type, visitor, isTypeNode, /*optional*/ true), + visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ClassDeclaration: return updateClassDeclaration(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), + visitNode((node).name, visitor, isIdentifier), visitNodes((node).typeParameters, visitor, isTypeParameter), visitNodes((node).heritageClauses, visitor, isHeritageClause), visitNodes((node).members, visitor, isClassElement)); @@ -650,13 +600,13 @@ namespace ts { return updateImportDeclaration(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).importClause, visitor, isImportClause, /*optional*/ true), + visitNode((node).importClause, visitor, isImportClause), visitNode((node).moduleSpecifier, visitor, isExpression)); case SyntaxKind.ImportClause: return updateImportClause(node, - visitNode((node).name, visitor, isIdentifier, /*optional*/ true), - visitNode((node).namedBindings, visitor, isNamedImportBindings, /*optional*/ true)); + visitNode((node).name, visitor, isIdentifier), + visitNode((node).namedBindings, visitor, isNamedImportBindings)); case SyntaxKind.NamespaceImport: return updateNamespaceImport(node, @@ -668,7 +618,7 @@ namespace ts { case SyntaxKind.ImportSpecifier: return updateImportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), + visitNode((node).propertyName, visitor, isIdentifier), visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.ExportAssignment: @@ -681,8 +631,8 @@ namespace ts { return updateExportDeclaration(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - visitNode((node).exportClause, visitor, isNamedExports, /*optional*/ true), - visitNode((node).moduleSpecifier, visitor, isExpression, /*optional*/ true)); + visitNode((node).exportClause, visitor, isNamedExports), + visitNode((node).moduleSpecifier, visitor, isExpression)); case SyntaxKind.NamedExports: return updateNamedExports(node, @@ -690,7 +640,7 @@ namespace ts { case SyntaxKind.ExportSpecifier: return updateExportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier, /*optional*/ true), + visitNode((node).propertyName, visitor, isIdentifier), visitNode((node).name, visitor, isIdentifier)); // Module references @@ -774,7 +724,7 @@ namespace ts { case SyntaxKind.EnumMember: return updateEnumMember(node, visitNode((node).name, visitor, isPropertyName), - visitNode((node).initializer, visitor, isExpression, /*optional*/ true)); + visitNode((node).initializer, visitor, isExpression)); // Top-level nodes case SyntaxKind.SourceFile: @@ -1319,7 +1269,6 @@ namespace ts { : addRange(statements, declarations); } - /** * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. * @@ -1434,10 +1383,6 @@ namespace ts { } export namespace Debug { - export const failNotOptional = shouldAssert(AssertionLevel.Normal) - ? (message?: string) => assert(false, message || "Node not optional.") - : noop; - export const failBadSyntaxKind = shouldAssert(AssertionLevel.Normal) ? (node: Node, message?: string) => assert(false, message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} was unexpected.`) : noop; From 511cc41e56edfb8633ede7926ad6814cdbaabc6b Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 14:13:59 -0800 Subject: [PATCH 13/14] Better strictNullChecks support for TransformationContext --- src/compiler/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e02bdcab42e33..c87ed85504fa4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3912,7 +3912,7 @@ * NOTE: Transformation hooks should only be modified during `Transformer` initialization, * before returning the `NodeTransformer` callback. */ - onSubstituteNode?: (hint: EmitHint, node: Node) => Node; + onSubstituteNode: (hint: EmitHint, node: Node) => Node; /** * Enables before/after emit notifications in the pretty printer for the provided @@ -3933,7 +3933,7 @@ * NOTE: Transformation hooks should only be modified during `Transformer` initialization, * before returning the `NodeTransformer` callback. */ - onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; + onEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; } export interface TransformationResult { From 67a004195acbad92d32fd89898e061650cdf6c6e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 16 Feb 2017 17:42:58 -0800 Subject: [PATCH 14/14] Minor factory cleanup --- src/compiler/factory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 81346fe59ba93..25ba30c06a6e9 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1462,16 +1462,16 @@ namespace ts { : node; } - export function createJsxExpression(expression: Expression, dotDotDotToken: DotDotDotToken) { + export function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { const node = createSynthesizedNode(SyntaxKind.JsxExpression); node.dotDotDotToken = dotDotDotToken; node.expression = expression; return node; } - export function updateJsxExpression(node: JsxExpression, expression: Expression) { + export function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { return node.expression !== expression - ? updateNode(createJsxExpression(expression, node.dotDotDotToken), node) + ? updateNode(createJsxExpression(node.dotDotDotToken, expression), node) : node; }