From 033aba9351cd5bb37712693de3cd2511903a4b76 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 4 Nov 2019 19:29:01 +0200 Subject: [PATCH] fix(ngcc): do not emit ES2015 code in ES5 files (#33514) Previously, ngcc's `Renderer` would add some constants in the processed files which were emitted as ES2015 code (e.g. `const` declarations). This would result in invalid ES5 generated code that would break when run on browsers that do not support the emitted format. This commit fixes it by adding a `printStatement()` method to `RenderingFormatter`, which can convert statements to JavaScript code in a suitable format for the corresponding `RenderingFormatter`. Additionally, the `translateExpression()` and `translateStatement()` ngtsc helper methods are augmented to accept an extra hint to know whether the code needs to be translated to ES5 format or not. Fixes #32665 PR Close #33514 --- integration/ngcc/test.sh | 2 +- .../src/rendering/esm5_rendering_formatter.ts | 21 ++++++++++ .../src/rendering/esm_rendering_formatter.ts | 25 ++++++++++- .../ngcc/src/rendering/renderer.ts | 35 ++++++---------- .../ngcc/src/rendering/rendering_formatter.ts | 2 + packages/compiler-cli/ngcc/test/BUILD.bazel | 1 + .../ngcc/test/integration/ngcc_spec.ts | 32 +++++++++++++++ .../commonjs_rendering_formatter_spec.ts | 15 +++++++ .../ngcc/test/rendering/dts_renderer_spec.ts | 2 + .../esm5_rendering_formatter_spec.ts | 15 +++++++ .../rendering/esm_rendering_formatter_spec.ts | 15 +++++++ .../ngcc/test/rendering/renderer_spec.ts | 41 ++++++++++++------- .../rendering/umd_rendering_formatter_spec.ts | 15 +++++++ .../ngtsc/annotations/test/metadata_spec.ts | 3 +- .../src/ngtsc/transform/src/transform.ts | 12 ++++-- .../src/ngtsc/translator/src/translator.ts | 38 ++++++++++++----- .../src/ngtsc/typecheck/src/environment.ts | 3 +- 17 files changed, 222 insertions(+), 55 deletions(-) diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index 25c28f9f19..65e3f0791b 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -95,7 +95,7 @@ assertSucceeded "Expected 'ngcc' to log 'Compiling'." grep "const ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable);" node_modules/@angular/material/esm2015/table.js assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm2015)." - grep "const ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable);" node_modules/@angular/material/esm5/table.es5.js + grep "var ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable);" node_modules/@angular/material/esm5/table.es5.js assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm5)." diff --git a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts index 138398ec00..0feb106de0 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts @@ -5,8 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {Statement} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; +import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports'; +import {ImportManager, translateStatement} from '../../../src/ngtsc/translator'; import {CompiledClass} from '../analysis/types'; import {getIifeBody} from '../host/esm5_host'; import {EsmRenderingFormatter} from './esm_rendering_formatter'; @@ -35,4 +38,22 @@ export class Esm5RenderingFormatter extends EsmRenderingFormatter { const insertionPoint = returnStatement.getFullStart(); output.appendLeft(insertionPoint, '\n' + definitions); } + + /** + * Convert a `Statement` to JavaScript code in a format suitable for rendering by this formatter. + * + * @param stmt The `Statement` to print. + * @param sourceFile A `ts.SourceFile` that provides context for the statement. See + * `ts.Printer#printNode()` for more info. + * @param importManager The `ImportManager` to use for managing imports. + * + * @return The JavaScript code corresponding to `stmt` (in the appropriate format). + */ + printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string { + const node = + translateStatement(stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES5); + const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); + + return code; + } } diff --git a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts index e6c1da5e2f..ef8e79c2ac 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts @@ -5,10 +5,12 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {Statement} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {relative, dirname, AbsoluteFsPath, absoluteFromSourceFile} from '../../../src/ngtsc/file_system'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; +import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports'; +import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator'; import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; import {CompiledClass} from '../analysis/types'; import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; @@ -16,13 +18,14 @@ import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyze import {ExportInfo} from '../analysis/private_declarations_analyzer'; import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter'; import {stripExtension} from './utils'; -import {Reexport} from '../../../src/ngtsc/imports'; import {isAssignment} from '../host/esm2015_host'; /** * A RenderingFormatter that works with ECMAScript Module import and export statements. */ export class EsmRenderingFormatter implements RenderingFormatter { + protected printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); + constructor(protected host: NgccReflectionHost, protected isCore: boolean) {} /** @@ -225,6 +228,24 @@ export class EsmRenderingFormatter implements RenderingFormatter { }); } + /** + * Convert a `Statement` to JavaScript code in a format suitable for rendering by this formatter. + * + * @param stmt The `Statement` to print. + * @param sourceFile A `ts.SourceFile` that provides context for the statement. See + * `ts.Printer#printNode()` for more info. + * @param importManager The `ImportManager` to use for managing imports. + * + * @return The JavaScript code corresponding to `stmt` (in the appropriate format). + */ + printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string { + const node = translateStatement( + stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015); + const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); + + return code; + } + protected findEndOfImports(sf: ts.SourceFile): number { for (const stmt of sf.statements) { if (!ts.isImportDeclaration(stmt) && !ts.isImportEqualsDeclaration(stmt) && diff --git a/packages/compiler-cli/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/ngcc/src/rendering/renderer.ts index 24fc2f93d1..0f30f7bc3a 100644 --- a/packages/compiler-cli/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/renderer.ts @@ -8,19 +8,18 @@ import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; -import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports'; -import {translateStatement, ImportManager} from '../../../src/ngtsc/translator'; +import {ImportManager} from '../../../src/ngtsc/translator'; import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/types'; import {PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer'; import {IMPORT_PREFIX} from '../constants'; import {FileSystem} from '../../../src/ngtsc/file_system'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; +import {NgccReflectionHost} from '../host/ngcc_host'; import {Logger} from '../logging/logger'; -import {FileToWrite, getImportRewriter, stripExtension} from './utils'; +import {EntryPointBundle} from '../packages/entry_point_bundle'; import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter'; import {extractSourceMap, renderSourceAndMap} from './source_maps'; -import {NgccReflectionHost} from '../host/ngcc_host'; +import {FileToWrite, getImportRewriter, stripExtension} from './utils'; /** * A base-class for rendering an `AnalyzedFile`. @@ -98,7 +97,8 @@ export class Renderer { this.srcFormatter.addConstants( outputText, - renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager), + renderConstantPool( + this.srcFormatter, compiledFile.sourceFile, compiledFile.constantPool, importManager), compiledFile.sourceFile); } @@ -185,12 +185,9 @@ export class Renderer { private renderStatements( sourceFile: ts.SourceFile, statements: Statement[], imports: ImportManager): string { - const printer = createPrinter(); - const translate = (stmt: Statement) => - translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER); - const print = (stmt: Statement) => - printer.printNode(ts.EmitHint.Unspecified, translate(stmt), sourceFile); - return statements.map(print).join('\n'); + const printStatement = (stmt: Statement) => + this.srcFormatter.printStatement(stmt, sourceFile, imports); + return statements.map(printStatement).join('\n'); } } @@ -198,12 +195,10 @@ export class Renderer { * Render the constant pool as source code for the given class. */ export function renderConstantPool( - sourceFile: ts.SourceFile, constantPool: ConstantPool, imports: ImportManager): string { - const printer = createPrinter(); - return constantPool.statements - .map(stmt => translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER)) - .map(stmt => printer.printNode(ts.EmitHint.Unspecified, stmt, sourceFile)) - .join('\n'); + formatter: RenderingFormatter, sourceFile: ts.SourceFile, constantPool: ConstantPool, + imports: ImportManager): string { + const printStatement = (stmt: Statement) => formatter.printStatement(stmt, sourceFile, imports); + return constantPool.statements.map(printStatement).join('\n'); } /** @@ -216,7 +211,3 @@ function createAssignmentStatement( const receiver = new WrappedNodeExpr(receiverName); return new WritePropExpr(receiver, propName, initializer).toStmt(); } - -function createPrinter(): ts.Printer { - return ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); -} diff --git a/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts index db3bd2993d..e97b106568 100644 --- a/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {Statement} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {Reexport} from '../../../src/ngtsc/imports'; @@ -45,4 +46,5 @@ export interface RenderingFormatter { addModuleWithProvidersParams( outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], importManager: ImportManager): void; + printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string; } diff --git a/packages/compiler-cli/ngcc/test/BUILD.bazel b/packages/compiler-cli/ngcc/test/BUILD.bazel index d0644397c2..8166e70b85 100644 --- a/packages/compiler-cli/ngcc/test/BUILD.bazel +++ b/packages/compiler-cli/ngcc/test/BUILD.bazel @@ -10,6 +10,7 @@ ts_library( exclude = ["integration/**/*.ts"], ), deps = [ + "//packages/compiler", "//packages/compiler-cli/ngcc", "//packages/compiler-cli/ngcc/test/helpers", "//packages/compiler-cli/src/ngtsc/diagnostics", diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index c5676b6def..26bf7f6df0 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -147,6 +147,38 @@ runInEachFileSystem(() => { '{ bar: [{ type: Input }] });'); }); + it('should not add `const` in ES5 generated code', () => { + genNodeModules({ + 'test-package': { + '/index.ts': ` + import {Directive, Input, NgModule} from '@angular/core'; + + @Directive({ + selector: '[foo]', + host: {bar: ''}, + }) + export class FooDirective { + } + + @NgModule({ + declarations: [FooDirective], + }) + export class FooModule {} + `, + }, + }); + + mainNgcc({ + basePath: '/node_modules', + targetEntryPointPath: 'test-package', + propertiesToConsider: ['main'], + }); + + const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); + expect(jsContents).not.toMatch(/\bconst \w+\s*=/); + expect(jsContents).toMatch(/\bvar _c0 =/); + }); + describe('in async mode', () => { it('should run ngcc without errors for fesm2015', async() => { const promise = mainNgcc({ diff --git a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts index 66135482b7..85674a9a2b 100644 --- a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {NoopImportRewriter} from '../../../src/ngtsc/imports'; @@ -518,5 +519,19 @@ SOME DEFINITION TEXT expect(output.toString()).toContain(`function C() {\n }\n return C;`); }); }); + + describe('printStatement', () => { + it('should transpile code to ES5', () => { + const {renderer, sourceFile, importManager} = setup(PROGRAM); + + const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); + + expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); + }); + }); }); }); diff --git a/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts index e86db2cec7..626e8f9264 100644 --- a/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts @@ -53,6 +53,7 @@ class TestRenderingFormatter implements RenderingFormatter { importManager: ImportManager): void { output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n'); } + printStatement(): string { return 'IGNORED'; } } function createTestRenderer( @@ -87,6 +88,7 @@ function createTestRenderer( spyOn(testFormatter, 'removeDecorators').and.callThrough(); spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough(); spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough(); + spyOn(testFormatter, 'printStatement').and.callThrough(); const renderer = new DtsRenderer(testFormatter, fs, logger, host, bundle); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts index 1f46ae6e90..53141ba3e5 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {NoopImportRewriter} from '../../../src/ngtsc/imports'; @@ -545,5 +546,19 @@ SOME DEFINITION TEXT }); }); }); + + describe('printStatement', () => { + it('should transpile code to ES5', () => { + const {renderer, sourceFile, importManager} = setup(PROGRAM); + + const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); + + expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); + }); + }); }); }); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts index 49e9d6f099..e1e200e8e1 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {NoopImportRewriter} from '../../../src/ngtsc/imports'; @@ -623,5 +624,19 @@ export { D }; static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`); }); }); + + describe('printStatement', () => { + it('should transpile code to ES2015', () => { + const {renderer, sourceFile, importManager} = setup([PROGRAM]); + + const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Final]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); + + expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('const foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); + }); + }); }); }); diff --git a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts index 49771e9863..e7e0b958bb 100644 --- a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts @@ -5,14 +5,15 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {Statement} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {fromObject, generateMapFileComment, SourceMapConverter} from 'convert-source-map'; import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {Reexport} from '../../../src/ngtsc/imports'; +import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports'; import {loadTestFiles} from '../../../test/helpers'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; +import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {CompiledClass} from '../../src/analysis/types'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; @@ -27,6 +28,8 @@ import {RenderingFormatter, RedundantDecoratorMap} from '../../src/rendering/ren import {makeTestEntryPointBundle, getRootFiles} from '../helpers/utils'; class TestRenderingFormatter implements RenderingFormatter { + private printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); + addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) { output.prepend('\n// ADD IMPORTS\n'); } @@ -56,6 +59,13 @@ class TestRenderingFormatter implements RenderingFormatter { importManager: ImportManager): void { output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n'); } + printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string { + const node = translateStatement( + stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015); + const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); + + return `// TRANSPILED\n${code}`; + } } function createTestRenderer( @@ -92,6 +102,7 @@ function createTestRenderer( spyOn(testFormatter, 'removeDecorators').and.callThrough(); spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough(); spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough(); + spyOn(testFormatter, 'printStatement').and.callThrough(); const renderer = new Renderer(host, testFormatter, fs, logger, bundle); @@ -200,8 +211,9 @@ runInEachFileSystem(() => { renderer.renderProgram( decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[2]) - .toEqual(`A.ɵfac = function A_Factory(t) { return new (t || A)(); }; + expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED +A.ɵfac = function A_Factory(t) { return new (t || A)(); }; +// TRANSPILED A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) { ɵngcc0.ɵɵtext(0); } if (rf & 2) { @@ -209,8 +221,8 @@ A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, v } }, encapsulation: 2 });`); const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - expect(addAdjacentStatementsSpy.calls.first().args[2]) - .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ + expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED +/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }], null, null);`); @@ -243,8 +255,9 @@ A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, v name: 'A', decorators: [jasmine.objectContaining({name: 'Directive'})] })); - expect(addDefinitionsSpy.calls.first().args[2]) - .toEqual(`A.ɵfac = function A_Factory(t) { return new (t || A)(); }; + expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED +A.ɵfac = function A_Factory(t) { return new (t || A)(); }; +// TRANSPILED A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`); }); @@ -260,8 +273,8 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });` expect(addAdjacentStatementsSpy.calls.first().args[1]) .toEqual(jasmine.objectContaining( {name: 'A', decorators: [jasmine.objectContaining({name: 'Directive'})]})); - expect(addAdjacentStatementsSpy.calls.first().args[2]) - .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ + expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED +/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ type: Directive, args: [{ selector: '[a]' }] }], null, null);`); @@ -271,7 +284,7 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });` () => { const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses, testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]); - const result = renderer.renderProgram( + renderer.renderProgram( decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); const removeDecoratorsSpy = testFormatter.removeDecorators as jasmine.Spy; expect(removeDecoratorsSpy.calls.first().args[0].toString()) @@ -467,9 +480,9 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });` decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[2]) - .toEqual( - `UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); }; + expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED +UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); }; +// TRANSPILED UndecoratedBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) { ɵngcc0.ɵɵstaticViewQuery(_c0, true); } if (rf & 2) { diff --git a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts index 0ea177b569..783e6c90e5 100644 --- a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {NoopImportRewriter} from '../../../src/ngtsc/imports'; @@ -592,5 +593,19 @@ SOME DEFINITION TEXT expect(output.toString()).toContain(`function C() {\n }\n return C;`); }); }); + + describe('printStatement', () => { + it('should transpile code to ES5', () => { + const {renderer, sourceFile, importManager} = setup(PROGRAM); + + const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); + + expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); + }); + }); }); }); diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts index 6697ec5776..8eff50e326 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/metadata_spec.ts @@ -119,7 +119,8 @@ runInEachFileSystem(() => { } const sf = getSourceFileOrError(program, _('/index.ts')); const im = new ImportManager(new NoopImportRewriter(), 'i'); - const tsStatement = translateStatement(call, im, NOOP_DEFAULT_IMPORT_RECORDER); + const tsStatement = + translateStatement(call, im, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015); const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf); return res.replace(/\s+/g, ' '); } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 563e3f02cf..06233f111f 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -64,8 +64,9 @@ class IvyVisitor extends Visitor { res.forEach(field => { // Translate the initializer for the field into TS nodes. - const exprNode = - translateExpression(field.initializer, this.importManager, this.defaultImportRecorder); + const exprNode = translateExpression( + field.initializer, this.importManager, this.defaultImportRecorder, + ts.ScriptTarget.ES2015); // Create a static property declaration for the new field. const property = ts.createProperty( @@ -73,7 +74,9 @@ class IvyVisitor extends Visitor { undefined, exprNode); field.statements - .map(stmt => translateStatement(stmt, this.importManager, this.defaultImportRecorder)) + .map( + stmt => translateStatement( + stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015)) .forEach(stmt => statements.push(stmt)); members.push(property); @@ -218,7 +221,8 @@ function transformIvySourceFile( // Generate the constant statements first, as they may involve adding additional imports // to the ImportManager. const constants = constantPool.statements.map( - stmt => translateStatement(stmt, importManager, defaultImportRecorder)); + stmt => + translateStatement(stmt, importManager, defaultImportRecorder, ts.ScriptTarget.ES2015)); // Preserve @fileoverview comments required by Closure, since the location might change as a // result of adding extra imports and constant pool statements. diff --git a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts index 6b9b39314a..c4462fdbbc 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts @@ -100,17 +100,19 @@ export class ImportManager { } export function translateExpression( - expression: Expression, imports: ImportManager, - defaultImportRecorder: DefaultImportRecorder): ts.Expression { + expression: Expression, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder, + scriptTarget: Exclude): ts.Expression { return expression.visitExpression( - new ExpressionTranslatorVisitor(imports, defaultImportRecorder), new Context(false)); + new ExpressionTranslatorVisitor(imports, defaultImportRecorder, scriptTarget), + new Context(false)); } export function translateStatement( - statement: Statement, imports: ImportManager, - defaultImportRecorder: DefaultImportRecorder): ts.Statement { + statement: Statement, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder, + scriptTarget: Exclude): ts.Statement { return statement.visitStatement( - new ExpressionTranslatorVisitor(imports, defaultImportRecorder), new Context(true)); + new ExpressionTranslatorVisitor(imports, defaultImportRecorder, scriptTarget), + new Context(true)); } export function translateType(type: Type, imports: ImportManager): ts.TypeNode { @@ -120,10 +122,14 @@ export function translateType(type: Type, imports: ImportManager): ts.TypeNode { class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor { private externalSourceFiles = new Map(); constructor( - private imports: ImportManager, private defaultImportRecorder: DefaultImportRecorder) {} + private imports: ImportManager, private defaultImportRecorder: DefaultImportRecorder, + private scriptTarget: Exclude) {} visitDeclareVarStmt(stmt: DeclareVarStmt, context: Context): ts.VariableStatement { - const nodeFlags = stmt.hasModifier(StmtModifier.Final) ? ts.NodeFlags.Const : ts.NodeFlags.None; + const nodeFlags = + ((this.scriptTarget >= ts.ScriptTarget.ES2015) && stmt.hasModifier(StmtModifier.Final)) ? + ts.NodeFlags.Const : + ts.NodeFlags.None; return ts.createVariableStatement( undefined, ts.createVariableDeclarationList( [ts.createVariableDeclaration( @@ -149,6 +155,11 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor } visitDeclareClassStmt(stmt: ClassStmt, context: Context) { + if (this.scriptTarget < ts.ScriptTarget.ES2015) { + throw new Error( + `Unsupported mode: Visiting a "declare class" statement (class ${stmt.name}) while ` + + `targeting ${ts.ScriptTarget[this.scriptTarget]}.`); + } throw new Error('Method not implemented.'); } @@ -200,7 +211,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor const exprContext = context.withExpressionMode; const lhs = ts.createElementAccess( expr.receiver.visitExpression(this, exprContext), - expr.index.visitExpression(this, exprContext), ); + expr.index.visitExpression(this, exprContext)); const rhs = expr.value.visitExpression(this, exprContext); const result: ts.Expression = ts.createBinary(lhs, ts.SyntaxKind.EqualsToken, rhs); return context.isStatement ? result : ts.createParen(result); @@ -252,6 +263,12 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor } visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression { + if (this.scriptTarget < ts.ScriptTarget.ES2015) { + // This should never happen. + throw new Error( + 'Unsupported mode: Visiting a localized string (which produces a tagged template ' + + `literal) ' while targeting ${ts.ScriptTarget[this.scriptTarget]}.`); + } return visitLocalizedString(ast, context, this); } @@ -518,7 +535,8 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode { - let expr = translateExpression(ast.expr, this.imports, NOOP_DEFAULT_IMPORT_RECORDER); + let expr = translateExpression( + ast.expr, this.imports, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015); return ts.createTypeQueryNode(expr as ts.Identifier); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts index 895a2258b5..c96cfb8005 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts @@ -204,7 +204,8 @@ export class Environment { const ngExpr = this.refEmitter.emit(ref, this.contextFile); // Use `translateExpression` to convert the `Expression` into a `ts.Expression`. - return translateExpression(ngExpr, this.importManager, NOOP_DEFAULT_IMPORT_RECORDER); + return translateExpression( + ngExpr, this.importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015); } /**