fix(ngcc): libraries using spread in object literals cannot be processed (#34661)

Consider a library that uses a shared constant for host bindings. e.g.

```ts
export const BASE_BINDINGS= {
  '[class.mat-themed]': '_isThemed',
}

----

@Directive({
  host: {...BASE_BINDINGS, '(click)': '...'}
})
export class Dir1 {}

@Directive({
  host: {...BASE_BINDINGS, '(click)': '...'}
})
export class Dir2 {}
```

Previously when these components were shipped as part of the
library to NPM, consumers were able to consume `Dir1` and `Dir2`.
No errors showed up.

Now with Ivy, when ngcc tries to process the library, an error
will be thrown. The error is stating that the host bindings should
be an object (which they obviously are). This happens because
TypeScript transforms the object spread to individual
`Object.assign` calls (for compatibility).

The partial evaluator used by the `@Directive` annotation handler
is unable to process this expression because there is no
integrated support for `Object.assign`. In View Engine, this was
not a problem because the `metadata.json` files from the library
were used to compute the host bindings.

Fixes #34659

PR Close #34661
This commit is contained in:
Paul Gschwendtner
2020-01-09 20:37:02 +01:00
committed by Andrew Kushnir
parent a10d2a8dc4
commit 4eeb6cf24d
18 changed files with 306 additions and 27 deletions

View File

@ -1814,6 +1814,86 @@ runInEachFileSystem(() => {
expect(definition.helper).toBe(TsHelperFn.SpreadArrays);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function declaration', () => {
const file: TestFile = {
name: _('/declaration.d.ts'),
contents: `export declare function __assign(...args: object[]): object;`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign', isNamedFunctionDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function implementation', () => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function implementation when suffixed',
() => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __assign$2 = (this && this.__assign$2) || function () {
__assign$2 = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign$2.apply(this, arguments);
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign$2', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
});
describe('getImportOfIdentifier()', () => {