fix(ivy): track cyclic imports that are added (#29040)

ngtsc has cyclic import detection, to determine when adding an import to a
directive or pipe would create a cycle. However, this detection must also
account for already inserted imports, as it's possible for both directions
of a circular import to be inserted by Ivy (as opposed to at least one of
those edges existing in the user's program).

This commit fixes the circular import detection for components to take into
consideration already added edges. This is difficult for one critical
reason: only edges to files which will *actually* be imported should be
considered. However, that depends on which directives & pipes are used in
a given template, which is currently only known by running the
TemplateDefinitionBuilder during the 'compile' phase. This is too late; the
decision whether to use remote scoping (which consults the import graph) is
made during the 'resolve' phase, before any compilation has taken place.

Thus, the only way to correctly consider synthetic edges is for the compiler
to know exactly which directives & pipes are used in a template during
'resolve'. There are two ways to achieve this:

1) refactor `TemplateDefinitionBuilder` to do its work in two phases, with
directive matching occurring as a separate step which can be performed
earlier.

2) use the `R3TargetBinder` in the 'resolve' phase to independently bind the
template and get information about used directives.

Option 1 is ideal, but option 2 is currently used for practical reasons. The
cost of binding the template can be shared with template-typechecking.

PR Close #29040
This commit is contained in:
Alex Rickabaugh
2019-02-28 11:00:47 -08:00
committed by Andrew Kushnir
parent b50283ed67
commit 3e5c1bcb9f
5 changed files with 118 additions and 35 deletions

View File

@ -2045,6 +2045,41 @@ describe('ngtsc behavioral tests', () => {
/i\d\.ɵsetComponentScope\(NormalComponent,\s+\[NormalComponent,\s+CyclicComponent\],\s+\[\]\)/);
expect(jsContents).not.toContain('/*__PURE__*/ i0.ɵsetComponentScope');
});
it('should detect a cycle added entirely during compilation', () => {
env.tsconfig();
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {ACmp} from './a';
import {BCmp} from './b';
@NgModule({declarations: [ACmp, BCmp]})
export class Module {}
`);
env.write('a.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'a-cmp',
template: '<b-cmp></b-cmp>',
})
export class ACmp {}
`);
env.write('b.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'b-cmp',
template: '<a-cmp></a-cmp>',
})
export class BCmp {}
`);
env.driveMain();
const aJsContents = env.getContents('a.js');
const bJsContents = env.getContents('b.js');
expect(aJsContents).toMatch(/import \* as i\d? from ".\/b"/);
expect(bJsContents).not.toMatch(/import \* as i\d? from ".\/a"/);
});
});
describe('multiple local refs', () => {