fix(compiler): properly compile DI factories when coverage reporting is enabled (#44732)

When running tests with code coverage using Istanbul, the code is
instrumented with coverage reporting statements. These statements are
also inserted into synthesized constructors, preventing Angular from
properly recognizing them as synthesized constructor.

This commit changes the regex to detect synthesized constructors to allow
for statements within the constructor before the `super(...arguments);`
call. This is limited to code that does not contain a `}`, but this
is sufficient to support Istanbul's coverage instrumentation statements.

The tests have been extended with an input file that is being
instrumented using `babel-plugin-istanbul` for both ES2015 and ES5
targets, in order to verify that the approach works for real-world
usages.

Fixes #31337

PR Close #44732
This commit is contained in:
JoostK
2022-01-16 21:16:53 +01:00
committed by Andrew Kushnir
parent ef5486fab5
commit 71d28acf0c
2 changed files with 47 additions and 1 deletions
@@ -63,7 +63,7 @@ export const ES2015_INHERITED_CLASS_WITH_CTOR =
* and inherit a constructor.
*/
export const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR =
/^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{\s*super\(\.\.\.arguments\)/;
/^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/;
/**
* Determine whether a stringified type is a class which delegates its constructor
@@ -292,6 +292,51 @@ class TestObj {
expect(isDelegateCtor(ChildWithCtorComplexBase)).toBe(false);
});
it('should deal with coverage instrumentation statements', () => {
// The Istanbul coverage tool augments code with additional statements to capture coverage
// information, and this test verifies that those do not interfere with our ability to
// detect delegate constructors.
const Es2015ClassNoCtor = `class TestService extends Parent {
constructor() {
cov_8nt6qq5zt().f[2]++;
cov_8nt6qq5zt().s[6]++;
super(...arguments);
cov_8nt6qq5zt().s[7]++;
this.foo = 'bar';
}
}`;
const Es2015ClassWithCtor = `class TestService extends Parent {
constructor() {
cov_8nt6qq5zt().f[2]++;
cov_8nt6qq5zt().s[6]++;
super();
cov_8nt6qq5zt().s[7]++;
this.foo = 'bar';
}
}`;
const Es5ClassNoCtor = `function TestService() {
cov_8nt6qq5zt().f[4]++;
var _this = (cov_8nt6qq5zt().s[8]++, (cov_8nt6qq5zt().b[0][0]++, _super !== null) && (cov_8nt6qq5zt().b[0][1]++, _super.apply(this, arguments)) || (cov_8nt6qq5zt().b[0][2]++, this));
cov_8nt6qq5zt().s[9]++;
_this.foo = 'bar';
cov_8nt6qq5zt().s[10]++;
return _this;
}`;
const Es5ClassWithCtor = `function TestService() {
cov_8nt6qq5zt().f[4]++;
var _this = _super.call();
cov_8nt6qq5zt().s[9]++;
_this.foo = 'bar';
cov_8nt6qq5zt().s[10]++;
return _this;
}`;
expect(isDelegateCtor(Es2015ClassNoCtor)).toBe(true);
expect(isDelegateCtor(Es2015ClassWithCtor)).toBe(false);
expect(isDelegateCtor(Es5ClassNoCtor)).toBe(true);
expect(isDelegateCtor(Es5ClassWithCtor)).toBe(false);
});
it('should properly handle all class forms', () => {
const ctor = (str: string) => expect(isDelegateCtor(str)).toBe(false);
const noCtor = (str: string) => expect(isDelegateCtor(str)).toBe(true);
@@ -299,6 +344,7 @@ class TestObj {
ctor(`class Bar extends Foo {constructor(){}}`);
ctor(`class Bar extends Foo { constructor ( ) {} }`);
ctor(`class Bar extends Foo { other(){}; constructor(){} }`);
ctor(`class Bar extends Foo { constructor(){} a(){super(...arguments)} }`);
noCtor(`class extends Foo{}`);
noCtor(`class extends Foo {}`);