refactor(compiler): add @nocollapse annotation using a synthetic comment (#35932)

In Ivy, Angular decorators are compiled into static fields that are
inserted into a class declaration in a TypeScript transform. When
targeting Closure compiler such fields need to be annotated with
`@nocollapse` to prevent them from being lifted from a static field into
a variable, as that would prevent the Ivy runtime from being able to
find the compiled definitions.

Previously, there was a bug in TypeScript where synthetic comments added
in a transform would not be emitted at all, so as a workaround a global
regex-replace was done in the emit's `writeFile` callback that would add
the `@nocollapse` annotation to all static Ivy definition fields. This
approach is no longer possible when ngtsc is running as TypeScript
plugin, as a plugin cannot control emit behavior.

The workaround is no longer necessary, as synthetic comments are now
properly emitted, likely as of
https://github.com/microsoft/TypeScript/pull/22141 which has been
released with TypeScript 2.8.

This change is required for running ngtsc as TypeScript plugin in
Bazel's `ts_library` rule, to move away from the custom `ngc_wrapped`
approach.

Resolves FW-1952

PR Close #35932
This commit is contained in:
JoostK
2020-03-07 17:14:25 +01:00
committed by Kara Erickson
parent a5eb0e56b6
commit 75afd80ae8
10 changed files with 65 additions and 136 deletions

View File

@ -1,26 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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 {nocollapseHack} from '../../src/transformers/nocollapse_hack';
describe('@nocollapse hack', () => {
it('should add @nocollapse to a basic class', () => {
const decl = `Foo.ɵinj = define(...);`;
expect(nocollapseHack(decl)).toEqual('/** @nocollapse */ ' + decl);
});
it('should add nocollapse to an if (false) declaration of the kind generated by tsickle', () => {
const decl = `
if (false) {
/** @type {?} */
Foo.ɵinj;
}
`;
expect(nocollapseHack(decl)).toContain('/** @nocollapse @type {?} */');
});
});

View File

@ -33,7 +33,7 @@ describe('TypeScriptNodeEmitter', () => {
beforeEach(() => {
context = new MockAotContext('/', FILES);
host = new MockCompilerHost(context);
emitter = new TypeScriptNodeEmitter();
emitter = new TypeScriptNodeEmitter(false);
someVar = o.variable('someVar', null, null);
});

View File

@ -40,14 +40,16 @@ describe('r3_transform_spec', () => {
});
it('should be able to modify multiple classes in the same module', () => {
const result = emit(getAngularClassTransformerFactory([{
fileName: someGenFileName,
statements: [
classMethod(new o.ReturnStatement(o.variable('v')), ['v'], 'someMethod', 'SomeClass'),
classMethod(
new o.ReturnStatement(o.variable('v')), ['v'], 'someOtherMethod', 'SomeOtherClass')
]
}]));
const result = emit(getAngularClassTransformerFactory(
[{
fileName: someGenFileName,
statements: [
classMethod(new o.ReturnStatement(o.variable('v')), ['v'], 'someMethod', 'SomeClass'),
classMethod(
new o.ReturnStatement(o.variable('v')), ['v'], 'someOtherMethod', 'SomeOtherClass')
]
}],
false));
expect(result).toContain('static someMethod(v) { return v; }');
expect(result).toContain('static someOtherMethod(v) { return v; }');
});
@ -94,7 +96,7 @@ describe('r3_transform_spec', () => {
fileName: someGenFileName,
statements: [classMethod(stmt, parameters, methodName, className)]
};
return emit(getAngularClassTransformerFactory([module]));
return emit(getAngularClassTransformerFactory([module], false));
}
function emitStaticField(
@ -104,7 +106,7 @@ describe('r3_transform_spec', () => {
fileName: someGenFileName,
statements: [classField(initializer, fieldName, className)]
};
return emit(getAngularClassTransformerFactory([module]));
return emit(getAngularClassTransformerFactory([module], false));
}
});