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:

committed by
Andrew Kushnir

parent
b50283ed67
commit
3e5c1bcb9f
@ -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', () => {
|
||||
|
Reference in New Issue
Block a user