feat(language-service): completions support for tuple array (#33928)
PR Close #33928
This commit is contained in:
parent
de7713d6ea
commit
5a227d8d7c
@ -241,7 +241,8 @@ export class AstType implements AstVisitor {
|
|||||||
visitKeyedRead(ast: KeyedRead): Symbol {
|
visitKeyedRead(ast: KeyedRead): Symbol {
|
||||||
const targetType = this.getType(ast.obj);
|
const targetType = this.getType(ast.obj);
|
||||||
const keyType = this.getType(ast.key);
|
const keyType = this.getType(ast.key);
|
||||||
const result = targetType.indexed(keyType);
|
const result = targetType.indexed(
|
||||||
|
keyType, ast.key instanceof LiteralPrimitive ? ast.key.value : undefined);
|
||||||
return result || this.anyType;
|
return result || this.anyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,9 +116,12 @@ export interface Symbol {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the type of the expression if this symbol is indexed by `argument`.
|
* Return the type of the expression if this symbol is indexed by `argument`.
|
||||||
|
* Sometimes we need the key of arguments to get the type of the expression, for example
|
||||||
|
* in the case of tuples (`type Example = [string, number]`).
|
||||||
|
* [string, number]).
|
||||||
* If the symbol cannot be indexed, this method should return `undefined`.
|
* If the symbol cannot be indexed, this method should return `undefined`.
|
||||||
*/
|
*/
|
||||||
indexed(argument: Symbol): Symbol|undefined;
|
indexed(argument: Symbol, key?: any): Symbol|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,4 +352,4 @@ export interface SymbolQuery {
|
|||||||
* Return the span of the narrowest non-token node at the given location.
|
* Return the span of the narrowest non-token node at the given location.
|
||||||
*/
|
*/
|
||||||
getSpanAt(line: number, column: number): Span|undefined;
|
getSpanAt(line: number, column: number): Span|undefined;
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ class TypeWrapper implements Symbol {
|
|||||||
return selectSignature(this.tsType, this.context, types);
|
return selectSignature(this.tsType, this.context, types);
|
||||||
}
|
}
|
||||||
|
|
||||||
indexed(argument: Symbol): Symbol|undefined {
|
indexed(argument: Symbol, value: any): Symbol|undefined {
|
||||||
const type = argument instanceof TypeWrapper ? argument : argument.type;
|
const type = argument instanceof TypeWrapper ? argument : argument.type;
|
||||||
if (!(type instanceof TypeWrapper)) return;
|
if (!(type instanceof TypeWrapper)) return;
|
||||||
|
|
||||||
@ -292,7 +292,14 @@ class TypeWrapper implements Symbol {
|
|||||||
switch (typeKind) {
|
switch (typeKind) {
|
||||||
case BuiltinType.Number:
|
case BuiltinType.Number:
|
||||||
const nType = this.tsType.getNumberIndexType();
|
const nType = this.tsType.getNumberIndexType();
|
||||||
return nType && new TypeWrapper(nType, this.context);
|
if (nType) {
|
||||||
|
// get the right tuple type by value, like 'var t: [number, string];'
|
||||||
|
if (nType.isUnion()) {
|
||||||
|
return new TypeWrapper(nType.types[value], this.context);
|
||||||
|
}
|
||||||
|
return new TypeWrapper(nType, this.context);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
case BuiltinType.String:
|
case BuiltinType.String:
|
||||||
const sType = this.tsType.getStringIndexType();
|
const sType = this.tsType.getStringIndexType();
|
||||||
return sType && new TypeWrapper(sType, this.context);
|
return sType && new TypeWrapper(sType, this.context);
|
||||||
|
@ -125,6 +125,13 @@ describe('completions', () => {
|
|||||||
expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']);
|
expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with numeric index signatures (tuple arrays)', () => {
|
||||||
|
mockHost.override(TEST_TEMPLATE, `{{ tupleArray[1].~{tuple-array-number-index}}}`);
|
||||||
|
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'tuple-array-number-index');
|
||||||
|
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start);
|
||||||
|
expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']);
|
||||||
|
});
|
||||||
|
|
||||||
describe('with string index signatures', () => {
|
describe('with string index signatures', () => {
|
||||||
it('should work with index notation', () => {
|
it('should work with index notation', () => {
|
||||||
mockHost.override(TEST_TEMPLATE, `{{ heroesByName['Jacky'].~{heroes-string-index}}}`);
|
mockHost.override(TEST_TEMPLATE, `{{ heroesByName['Jacky'].~{heroes-string-index}}}`);
|
||||||
|
@ -150,6 +150,15 @@ describe('diagnostics', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should produce diagnostics for invalid tuple type property access', () => {
|
||||||
|
mockHost.override(TEST_TEMPLATE, `
|
||||||
|
{{tupleArray[1].badProperty}}`);
|
||||||
|
const diags = ngLS.getDiagnostics(TEST_TEMPLATE);
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText)
|
||||||
|
.toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not produce errors on function.bind()', () => {
|
it('should not produce errors on function.bind()', () => {
|
||||||
mockHost.override(TEST_TEMPLATE, `
|
mockHost.override(TEST_TEMPLATE, `
|
||||||
<test-comp (test)="myClick.bind(this)">
|
<test-comp (test)="myClick.bind(this)">
|
||||||
|
@ -189,6 +189,7 @@ export class TemplateReference {
|
|||||||
title = 'Some title';
|
title = 'Some title';
|
||||||
hero: Hero = {id: 1, name: 'Windstorm'};
|
hero: Hero = {id: 1, name: 'Windstorm'};
|
||||||
heroes: Hero[] = [this.hero];
|
heroes: Hero[] = [this.hero];
|
||||||
|
tupleArray: [string, Hero] = ['test', this.hero];
|
||||||
league: Hero[][] = [this.heroes];
|
league: Hero[][] = [this.heroes];
|
||||||
heroesByName: {[name: string]: Hero} = {};
|
heroesByName: {[name: string]: Hero} = {};
|
||||||
anyValue: any;
|
anyValue: any;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user