diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ff402a2fdc2..a6c576e3a40 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3807,7 +3807,7 @@ namespace ts { } // Type annotations are TypeScript syntax. - if (node.type) { + if (node.type || node.exclamationToken) { transformFlags |= TransformFlags.AssertTypeScript; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index cbff1fb2512..843b8036230 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2741,6 +2741,7 @@ namespace ts { function emitVariableDeclaration(node: VariableDeclaration) { emit(node.name); + emit(node.exclamationToken); emitTypeAnnotation(node.type); emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d54244c0832..12437d088ff 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1937,6 +1937,7 @@ namespace ts { } export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { + /* Internally, one should probably use createTypeScriptVariableDeclaration instead and handle definite assignment assertions */ const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); node.name = asName(name); node.type = type; @@ -1945,6 +1946,7 @@ namespace ts { } export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { + /* Internally, one should probably use updateTypeScriptVariableDeclaration instead and handle definite assignment assertions */ return node.name !== name || node.type !== type || node.initializer !== initializer @@ -1952,6 +1954,26 @@ namespace ts { : node; } + /* @internal */ + export function createTypeScriptVariableDeclaration(name: string | BindingName, exclaimationToken?: Token, type?: TypeNode, initializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); + node.name = asName(name); + node.type = type; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + node.exclamationToken = exclaimationToken; + return node; + } + + /* @internal */ + export function updateTypeScriptVariableDeclaration(node: VariableDeclaration, name: BindingName, exclaimationToken: Token | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.name !== name + || node.type !== type + || node.initializer !== initializer + || node.exclamationToken !== exclaimationToken + ? updateNode(createTypeScriptVariableDeclaration(name, exclaimationToken, type, initializer), node) + : node; + } + export function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); node.flags |= flags & NodeFlags.BlockScoped; diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 7746ad00b43..e6413af8e85 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -937,7 +937,7 @@ namespace ts { } shouldEnterSuppressNewDiagnosticsContextContext = true; suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types - return cleanup(updateVariableDeclaration(input, input.name, ensureType(input, input.type), ensureNoInitializer(input))); + return cleanup(updateTypeScriptVariableDeclaration(input, input.name, /*exclaimationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); } case SyntaxKind.TypeParameter: { if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index d413a835d0d..ccad86da3e7 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2188,9 +2188,10 @@ namespace ts { } function visitVariableDeclaration(node: VariableDeclaration) { - return updateVariableDeclaration( + return updateTypeScriptVariableDeclaration( node, visitNode(node.name, visitor, isBindingName), + /*exclaimationToken*/ undefined, /*type*/ undefined, visitNode(node.initializer, visitor, isExpression)); } diff --git a/src/testRunner/unittests/printer.ts b/src/testRunner/unittests/printer.ts index 1605e7edc60..39c5503933a 100644 --- a/src/testRunner/unittests/printer.ts +++ b/src/testRunner/unittests/printer.ts @@ -67,6 +67,17 @@ namespace ts { `class A extends B implements C implements D {}`, ScriptTarget.ES2017 ))); + + // github #35093 + printsCorrectly("definiteAssignmentAssertions", {}, printer => printer.printFile(createSourceFile( + "source.ts", + `class A { + prop!: string; + } + + let x!: string;`, + ScriptTarget.ES2017 + ))); }); describe("printBundle", () => { diff --git a/tests/baselines/reference/printerApi/printsFileCorrectly.definiteAssignmentAssertions.js b/tests/baselines/reference/printerApi/printsFileCorrectly.definiteAssignmentAssertions.js new file mode 100644 index 00000000000..46699aee0d1 --- /dev/null +++ b/tests/baselines/reference/printerApi/printsFileCorrectly.definiteAssignmentAssertions.js @@ -0,0 +1,4 @@ +class A { + prop!: string; +} +let x!: string;