fix(language-service): Add global symbol for $any() (#33245)

This commit introduces a "global symbol table" in the language service for symbols that are available in the top level scope,
and add `$any()` to it.

See https://angular.io/guide/template-syntax#the-any-type-cast-function

PR closes https://github.com/angular/vscode-ng-language-service/issues/242

PR Close #33245
This commit is contained in:
Keen Yee Liau
2019-10-17 18:42:27 -07:00
committed by Andrew Kushnir
parent 8bc5fb2ab6
commit 3f257e96c6
7 changed files with 139 additions and 3 deletions

View File

@ -11,6 +11,7 @@ ts_library(
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli",
"//packages/compiler-cli/test:test_utils",
"//packages/language-service",
"@npm//typescript",
@ -21,7 +22,6 @@ jasmine_node_test(
name = "test",
data = [
"//packages/common:npm_package",
"//packages/compiler:npm_package",
"//packages/core:npm_package",
"//packages/forms:npm_package",
],

View File

@ -153,6 +153,12 @@ describe('completions', () => {
expectContain(completions, CompletionKind.PROPERTY, ['title', 'subTitle']);
});
it('should suggest $any() type cast function in an interpolation', () => {
const marker = mockHost.getLocationMarkerFor(APP_COMPONENT, 'sub-start');
const completions = ngLS.getCompletionsAt(APP_COMPONENT, marker.start);
expectContain(completions, CompletionKind.METHOD, ['$any']);
});
describe('in external template', () => {
it('should be able to get entity completions in external template', () => {
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'entity-amp');

View File

@ -26,6 +26,7 @@ import {MockTypescriptHost} from './test_utils';
const EXPRESSION_CASES = '/app/expression-cases.ts';
const NG_FOR_CASES = '/app/ng-for-cases.ts';
const NG_IF_CASES = '/app/ng-if-cases.ts';
const TEST_TEMPLATE = '/app/test.ng';
describe('diagnostics', () => {
const mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts']);
@ -55,6 +56,26 @@ describe('diagnostics', () => {
}
});
// https://github.com/angular/vscode-ng-language-service/issues/242
it('should support $any() type cast function', () => {
mockHost.override(TEST_TEMPLATE, `<div>{{$any(title).xyz}}</div>`);
const diags = ngLS.getDiagnostics(TEST_TEMPLATE);
expect(diags).toEqual([]);
});
it('should report error for $any() with incorrect number of arguments', () => {
const templates = [
'<div>{{$any().xyz}}</div>', // no argument
'<div>{{$any(title, title).xyz}}</div>', // two arguments
];
for (const template of templates) {
mockHost.override(TEST_TEMPLATE, template);
const diags = ngLS.getDiagnostics(TEST_TEMPLATE);
expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe('Unable to resolve signature for call of method $any');
}
});
describe('in expression-cases.ts', () => {
it('should report access to an unknown field', () => {
const diags = ngLS.getDiagnostics(EXPRESSION_CASES).map(d => d.messageText);

View File

@ -0,0 +1,28 @@
/**
* @license
* Copyright Google Inc. 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 {getSymbolQuery} from '@angular/compiler-cli';
import * as ts from 'typescript/lib/tsserverlibrary';
import {EMPTY_SYMBOL_TABLE, createGlobalSymbolTable} from '../src/global_symbols';
import {MockTypescriptHost} from './test_utils';
describe('GlobalSymbolTable', () => {
const mockHost = new MockTypescriptHost([]);
const tsLS = ts.createLanguageService(mockHost);
it(`contains $any()`, () => {
const program = tsLS.getProgram() !;
const typeChecker = program.getTypeChecker();
const source = ts.createSourceFile('foo.ts', '', ts.ScriptTarget.ES2015);
const query = getSymbolQuery(program, typeChecker, source, () => EMPTY_SYMBOL_TABLE);
const table = createGlobalSymbolTable(query);
expect(table.has('$any')).toBe(true);
});
});

View File

@ -13,6 +13,8 @@ import {TypeScriptServiceHost} from '../src/typescript_host';
import {MockTypescriptHost} from './test_utils';
const TEST_TEMPLATE = '/app/test.ng';
describe('hover', () => {
const mockHost = new MockTypescriptHost(['/app/main.ts']);
const tsLS = ts.createLanguageService(mockHost);
@ -190,6 +192,19 @@ describe('hover', () => {
});
expect(toText(displayParts)).toBe('(directive) AppModule.StringModel: class');
});
it('should be able to provide quick info for $any() cast function', () => {
const content = mockHost.override(TEST_TEMPLATE, '<div>{{$any(title)}}</div>');
const position = content.indexOf('$any');
const quickInfo = ngLS.getHoverAt(TEST_TEMPLATE, position);
expect(quickInfo).toBeDefined();
const {textSpan, displayParts} = quickInfo !;
expect(textSpan).toEqual({
start: position,
length: '$any(title)'.length,
});
expect(toText(displayParts)).toBe('(method) $any');
});
});
function toText(displayParts?: ts.SymbolDisplayPart[]): string {