From 973f797ad528eb2ede7231bc0a04710726a3dda8 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 9 Dec 2020 10:09:55 -0800 Subject: [PATCH] feat(language-service): enable get references for directive and component from template (#40054) This commit adds the ability to find references for a directive or component from within a component template. That is, you can find component references from the element tag `` (where `|` is the cursor position) as well as find references for directives that match a given attribute `
`. PR Close #40054 --- packages/language-service/.build.sh.swp | Bin 0 -> 12288 bytes packages/language-service/ivy/references.ts | 51 ++++++++-- .../ivy/test/references_spec.ts | 94 ++++++++++++++++++ 3 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 packages/language-service/.build.sh.swp diff --git a/packages/language-service/.build.sh.swp b/packages/language-service/.build.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..934b693ece2dc13bc14a7d1a384916da64882974 GIT binary patch literal 12288 zcmeI2&2H2%6oqfuu_z!4EYWS+1uT+u=mr&4DmyAsi3Md-RfCgw5<|weY-i}SD)1Wg zW#C2n1h8a{xXGkHs%X_Ma4j9#6WhnWUuH9!ldZhj+`^~H0I1yo`0(J}@a@syrhPaE zBKX{d(9T@h`gQSGY zKCRm)G>W}+6P3#}YgAn%zPj>wo@4?{;93OcVfD#irCqudE#ku6?Q8AgMNEJRFaajO z1egF5U;<2l3H%!bf}Mj;RLYIMoQ1xhyU_O^xnlxMfC(@GCcp%k025#WOn?b60Vco% z{z3wx09cp@I3TtE|DXK+|9KnW2kATMh_pqzPr64sz6J1&^p*65v`urU2Ec!}xdXRVz(3t*%GQSPaLgLaTt2BTLq4+?}&R3XudP+>3_Dv1+{mKQSCWqh$- zY_AxB8sW|k#`}M`euTTb&srA^;G`&wuBUYL`4ZQoMgenatz5M31&$rcyfP^6z8Z(K+(>G&TU^eXve?AMn zpWPQn#$j!8QDY$|vNm=sbx6qUb)`J|+$l?j9ve?C?c(lLDP4Acmws;ucNeRldDl|_ TF9XJM50(~Fsx!?51?ztU): + ts.ReferenceEntry[]|undefined { + const allDirectiveRefs: ts.ReferenceEntry[] = []; + for (const dir of directives.values()) { + const dirClass = dir.tsSymbol.valueDeclaration; + if (dirClass === undefined || !ts.isClassDeclaration(dirClass) || + dirClass.name === undefined) { + continue; + } + + const dirFile = dirClass.getSourceFile().fileName; + const dirPosition = dirClass.name.getStart(); + const directiveRefs = this.getReferencesAtTypescriptPosition(dirFile, dirPosition); + if (directiveRefs !== undefined) { + allDirectiveRefs.push(...directiveRefs); + } + } + + return allDirectiveRefs.length > 0 ? allDirectiveRefs : undefined; + } + private getReferencesAtTypescriptPosition(fileName: string, position: number): ts.ReferenceEntry[]|undefined { const refs = this.tsLS.getReferencesAtPosition(fileName, position); diff --git a/packages/language-service/ivy/test/references_spec.ts b/packages/language-service/ivy/test/references_spec.ts index b56e3ca8ff..ecc56b1e4c 100644 --- a/packages/language-service/ivy/test/references_spec.ts +++ b/packages/language-service/ivy/test/references_spec.ts @@ -679,6 +679,100 @@ describe('find references', () => { assertTextSpans(refs, ['
', 'Dir']); assertFileNames(refs, ['app.ts', 'dir.ts']); }); + + it('gets references to all matching directives when cursor is on an attribute', () => { + const dirFile = ` + import {Directive} from '@angular/core'; + + @Directive({selector: '[dir]'}) + export class Dir {}`; + const dirFile2 = ` + import {Directive} from '@angular/core'; + + @Directive({selector: '[dir]'}) + export class Dir2 {}`; + const {text, cursor} = extractCursorInfo(` + import {Component, NgModule} from '@angular/core'; + import {Dir} from './dir'; + import {Dir2} from './dir2'; + + @Component({template: '
'}) + export class AppCmp { + } + + @NgModule({declarations: [AppCmp, Dir, Dir2]}) + export class AppModule {} + `); + env = LanguageServiceTestEnvironment.setup([ + {name: _('/app.ts'), contents: text, isRoot: true}, + {name: _('/dir.ts'), contents: dirFile}, + {name: _('/dir2.ts'), contents: dirFile2}, + ]); + const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + expect(refs.length).toBe(8); + assertTextSpans(refs, ['
', 'Dir', 'Dir2']); + assertFileNames(refs, ['app.ts', 'dir.ts', 'dir2.ts']); + }); + }); + + describe('components', () => { + it('works for component classes', () => { + const {text, cursor} = extractCursorInfo(` + import {Component} from '@angular/core'; + + @Component({selector: 'my-comp', template: ''}) + export class MyCo¦mp {}`); + const appFile = ` + import {Component, NgModule} from '@angular/core'; + import {MyComp} from './comp'; + + @Component({template: ''}) + export class AppCmp { + } + + @NgModule({declarations: [AppCmp, MyComp]}) + export class AppModule {} + `; + env = LanguageServiceTestEnvironment.setup([ + {name: _('/app.ts'), contents: appFile, isRoot: true}, + {name: _('/comp.ts'), contents: text}, + ]); + const refs = getReferencesAtPosition(_('/comp.ts'), cursor)!; + // 4 references are: class declaration, template usage, app import and use in declarations + // list. + expect(refs.length).toBe(4); + assertTextSpans(refs, ['', 'MyComp']); + assertFileNames(refs, ['app.ts', 'comp.ts']); + }); + + it('gets works when cursor is on element tag', () => { + const compFile = ` + import {Component} from '@angular/core'; + + @Component({selector: 'my-comp', template: ''}) + export class MyComp {}`; + const {text, cursor} = extractCursorInfo(` + import {Component, NgModule} from '@angular/core'; + import {MyComp} from './comp'; + + @Component({template: ''}) + export class AppCmp { + } + + @NgModule({declarations: [AppCmp, MyComp]}) + export class AppModule {} + `); + env = LanguageServiceTestEnvironment.setup([ + {name: _('/app.ts'), contents: text, isRoot: true}, + {name: _('/comp.ts'), contents: compFile}, + ]); + const refs = getReferencesAtPosition(_('/app.ts'), cursor)!; + // 4 references are: class declaration, template usage, app import and use in declarations + // list. + expect(refs.length).toBe(4); + assertTextSpans(refs, ['', 'MyComp']); + assertFileNames(refs, ['app.ts', 'comp.ts']); + }); }); function getReferencesAtPosition(fileName: string, position: number) {