Skip to content

Commit

Permalink
feat(language-service): TS references from template items
Browse files Browse the repository at this point in the history
Keen and I were talking about what it would take to support getting
references at a position in the current language service, since it's
unclear when more investment in the Ivy LS will be available. Getting TS
references from a template is trivial -- we simply need to get the
definition of a symbol, which is already handled by the language
service, and ask the TS language service to give us the references for
that definition.

This doesn't handle references in templates, but that could be done in a
subsequent pass.

Part of angular/vscode-ng-language-service#29
  • Loading branch information
ayazhafiz committed Jun 7, 2020
1 parent aaa2009 commit a272254
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 1 deletion.
13 changes: 13 additions & 0 deletions packages/language-service/src/language_service.ts
Expand Up @@ -98,4 +98,17 @@ class LanguageServiceImpl implements ng.LanguageService {
const declarations = this.host.getDeclarations(fileName);
return getTsHover(position, declarations, analyzedModules);
}

getReferencesAtPosition(fileName: string, position: number): tss.ReferenceEntry[]|undefined {
const defAndSpan = this.getDefinitionAndBoundSpan(fileName, position);
if (!defAndSpan || !defAndSpan.definitions) {
return;
}
const {definitions} = defAndSpan;
const tsDef = definitions.find(def => def.fileName.endsWith('.ts'));
if (!tsDef) {
return;
}
return this.host.getReferencesAtPosition(tsDef.fileName, tsDef.textSpan.start);
}
}
2 changes: 1 addition & 1 deletion packages/language-service/src/types.ts
Expand Up @@ -254,7 +254,7 @@ export interface Diagnostic {
export type LanguageService = Pick<
ts.LanguageService,
'getCompletionsAtPosition'|'getDefinitionAndBoundSpan'|'getQuickInfoAtPosition'|
'getSemanticDiagnostics'>;
'getSemanticDiagnostics'|'getReferencesAtPosition'>;

/** Information about an Angular template AST. */
export interface AstResult {
Expand Down
4 changes: 4 additions & 0 deletions packages/language-service/src/typescript_host.ts
Expand Up @@ -359,6 +359,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
return program;
}

getReferencesAtPosition(fileName: string, position: number): tss.ReferenceEntry[]|undefined {
return this.tsLS.getReferencesAtPosition(fileName, position);
}

/**
* Return the TemplateSource if `node` is a template node.
*
Expand Down
1 change: 1 addition & 0 deletions packages/language-service/test/BUILD.bazel
Expand Up @@ -29,6 +29,7 @@ ts_library(
"definitions_spec.ts",
"diagnostics_spec.ts",
"hover_spec.ts",
"references_spec.ts",
],
data = [":project"],
deps = [
Expand Down
57 changes: 57 additions & 0 deletions packages/language-service/test/references_spec.ts
@@ -0,0 +1,57 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import * as ts from 'typescript';

import {createLanguageService} from '../src/language_service';
import {TypeScriptServiceHost} from '../src/typescript_host';

import {MockTypescriptHost} from './test_utils';

describe('references', () => {
const mockHost = new MockTypescriptHost(['/app/main.ts']);
const tsLS = ts.createLanguageService(mockHost);
const ngHost = new TypeScriptServiceHost(mockHost, tsLS);
const ngLS = createLanguageService(ngHost);

beforeEach(() => {
mockHost.reset();
});

describe('component members', () => {
it('should get TS references for a member in template', () => {
const fileName = mockHost.addCode(`
@Component({
template: '{{«title»}}'
})
export class MyComponent {
/*1*/title: string;
setTitle(newTitle: string) {
this./*2*/title = newTitle;
}
}`);
const content = mockHost.readFile(fileName)!;

const varName = 'title';
const marker = mockHost.getReferenceMarkerFor(fileName, varName);

const references = ngLS.getReferencesAtPosition(fileName, marker.start)!;
expect(references).toBeDefined();
expect(references.length).toBe(2);

for (let i = 0; i < references.length; ++i) {
const comment = `/*${i + 1}*/`;
const start = content.indexOf(comment) + comment.length;
expect(references[i].fileName).toBe(fileName);
expect(references[i].textSpan.start).toBe(start);
expect(references[i].textSpan.length).toBe(varName.length);
}
});
});
});

0 comments on commit a272254

Please sign in to comment.