fix(compiler-cli): return directives for an element on a microsyntax template (#42640)
When the template type checker try to get a symbol of a template node, it will not return the directives intended for an element on a microsyntax template, for example, `<div *ngFor="let user of users;" dir>`, the `dir` will be skipped, but it's needed in language service. Fixes https://github.com/angular/vscode-ng-language-service/issues/1420 PR Close #42640
This commit is contained in:
parent
bfa1b5d9eb
commit
74350a5cf1
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, ASTWithSource, BindingPipe, MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
import {AST, ASTWithSource, BindingPipe, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {AbsoluteFsPath} from '../../file_system';
|
import {AbsoluteFsPath} from '../../file_system';
|
||||||
|
@ -150,7 +150,24 @@ export class SymbolBuilder {
|
||||||
private getDirectiveMeta(
|
private getDirectiveMeta(
|
||||||
host: TmplAstTemplate|TmplAstElement,
|
host: TmplAstTemplate|TmplAstElement,
|
||||||
directiveDeclaration: ts.Declaration): TypeCheckableDirectiveMeta|null {
|
directiveDeclaration: ts.Declaration): TypeCheckableDirectiveMeta|null {
|
||||||
const directives = this.templateData.boundTarget.getDirectivesOfNode(host);
|
let directives = this.templateData.boundTarget.getDirectivesOfNode(host);
|
||||||
|
|
||||||
|
// `getDirectivesOfNode` will not return the directives intended for an element
|
||||||
|
// on a microsyntax template, for example `<div *ngFor="let user of users;" dir>`,
|
||||||
|
// the `dir` will be skipped, but it's needed in language service.
|
||||||
|
const firstChild = host.children[0];
|
||||||
|
if (firstChild instanceof TmplAstElement) {
|
||||||
|
const isMicrosyntaxTemplate = host instanceof TmplAstTemplate &&
|
||||||
|
sourceSpanEqual(firstChild.sourceSpan, host.sourceSpan);
|
||||||
|
if (isMicrosyntaxTemplate) {
|
||||||
|
const firstChildDirectives = this.templateData.boundTarget.getDirectivesOfNode(firstChild);
|
||||||
|
if (firstChildDirectives !== null && directives !== null) {
|
||||||
|
directives = directives.concat(firstChildDirectives);
|
||||||
|
} else {
|
||||||
|
directives = directives ?? firstChildDirectives;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (directives === null) {
|
if (directives === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -577,3 +594,7 @@ export class SymbolBuilder {
|
||||||
function anyNodeFilter(n: ts.Node): n is ts.Node {
|
function anyNodeFilter(n: ts.Node): n is ts.Node {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sourceSpanEqual(a: ParseSourceSpan, b: ParseSourceSpan) {
|
||||||
|
return a.start.offset === b.start.offset && a.end.offset === b.end.offset;
|
||||||
|
}
|
||||||
|
|
|
@ -223,8 +223,9 @@ runInEachFileSystem(() => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const fileName = absoluteFrom('/main.ts');
|
const fileName = absoluteFrom('/main.ts');
|
||||||
|
const dirFile = absoluteFrom('/dir.ts');
|
||||||
const templateString = `
|
const templateString = `
|
||||||
<div *ngFor="let user of users; let i = index;">
|
<div *ngFor="let user of users; let i = index;" dir>
|
||||||
{{user.name}} {{user.streetNumber}}
|
{{user.name}} {{user.streetNumber}}
|
||||||
<div [tabIndex]="i"></div>
|
<div [tabIndex]="i"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -239,9 +240,23 @@ runInEachFileSystem(() => {
|
||||||
}
|
}
|
||||||
export class Cmp { users: User[]; }
|
export class Cmp { users: User[]; }
|
||||||
`,
|
`,
|
||||||
declarations: [ngForDeclaration()],
|
declarations: [
|
||||||
|
ngForDeclaration(),
|
||||||
|
{
|
||||||
|
name: 'TestDir',
|
||||||
|
selector: '[dir]',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
inputs: {name: 'name'}
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
ngForTypeCheckTarget(),
|
ngForTypeCheckTarget(),
|
||||||
|
{
|
||||||
|
fileName: dirFile,
|
||||||
|
source: `export class TestDir {name:string}`,
|
||||||
|
templates: {},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
templateTypeChecker = testValues.templateTypeChecker;
|
templateTypeChecker = testValues.templateTypeChecker;
|
||||||
program = testValues.program;
|
program = testValues.program;
|
||||||
|
@ -250,6 +265,13 @@ runInEachFileSystem(() => {
|
||||||
templateNode = getAstTemplates(templateTypeChecker, cmp)[0];
|
templateNode = getAstTemplates(templateTypeChecker, cmp)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should retrieve a symbol for a directive on a microsyntax template', () => {
|
||||||
|
const symbol = templateTypeChecker.getSymbolOfNode(templateNode, cmp);
|
||||||
|
const testDir = symbol?.directives.find(dir => dir.selector === '[dir]');
|
||||||
|
expect(testDir).toBeDefined();
|
||||||
|
expect(program.getTypeChecker().symbolToString(testDir!.tsSymbol)).toEqual('TestDir');
|
||||||
|
});
|
||||||
|
|
||||||
it('should retrieve a symbol for an expression inside structural binding', () => {
|
it('should retrieve a symbol for an expression inside structural binding', () => {
|
||||||
const ngForOfBinding =
|
const ngForOfBinding =
|
||||||
templateNode.templateAttrs.find(a => a.name === 'ngForOf')! as TmplAstBoundAttribute;
|
templateNode.templateAttrs.find(a => a.name === 'ngForOf')! as TmplAstBoundAttribute;
|
||||||
|
|
Loading…
Reference in New Issue