From 74350a5cf1035f0d33640dce845a1f78aa33f009 Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Thu, 24 Jun 2021 10:28:42 +0800 Subject: [PATCH] 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, `
`, 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 --- .../typecheck/src/template_symbol_builder.ts | 25 ++++++++++++++++-- ...ecker__get_symbol_of_template_node_spec.ts | 26 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts index 696b425bbc..87520a37ba 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts @@ -6,7 +6,7 @@ * 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 {AbsoluteFsPath} from '../../file_system'; @@ -150,7 +150,24 @@ export class SymbolBuilder { private getDirectiveMeta( host: TmplAstTemplate|TmplAstElement, 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 `
`, + // 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) { return null; } @@ -577,3 +594,7 @@ export class SymbolBuilder { function anyNodeFilter(n: ts.Node): n is ts.Node { return true; } + +function sourceSpanEqual(a: ParseSourceSpan, b: ParseSourceSpan) { + return a.start.offset === b.start.offset && a.end.offset === b.end.offset; +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts index 8c133e94d1..d510ecf5b0 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts @@ -223,8 +223,9 @@ runInEachFileSystem(() => { beforeEach(() => { const fileName = absoluteFrom('/main.ts'); + const dirFile = absoluteFrom('/dir.ts'); const templateString = ` -
+
{{user.name}} {{user.streetNumber}}
`; @@ -239,9 +240,23 @@ runInEachFileSystem(() => { } export class Cmp { users: User[]; } `, - declarations: [ngForDeclaration()], + declarations: [ + ngForDeclaration(), + { + name: 'TestDir', + selector: '[dir]', + file: dirFile, + type: 'directive', + inputs: {name: 'name'} + }, + ], }, ngForTypeCheckTarget(), + { + fileName: dirFile, + source: `export class TestDir {name:string}`, + templates: {}, + }, ]); templateTypeChecker = testValues.templateTypeChecker; program = testValues.program; @@ -250,6 +265,13 @@ runInEachFileSystem(() => { 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', () => { const ngForOfBinding = templateNode.templateAttrs.find(a => a.name === 'ngForOf')! as TmplAstBoundAttribute;