fix(language-service): infer type of elements of array-like objects (#36312)
Currently the language service only provides support for determining the type of array-like members when the array-like object is an `Array`. However, there are other kinds of array-like objects, including `ReadonlyArray`s and `readonly`-property arrays. This commit adds support for retrieving the element type of arbitrary array-like objects. Closes #36191 PR Close #36312
This commit is contained in:
parent
38ad1d97ab
commit
fe2b6923ba
|
@ -127,10 +127,12 @@ class TypeScriptSymbolQuery implements SymbolQuery {
|
|||
|
||||
getElementType(type: Symbol): Symbol|undefined {
|
||||
if (type instanceof TypeWrapper) {
|
||||
const tSymbol = type.tsType.symbol;
|
||||
const tArgs = type.typeArguments();
|
||||
if (!tSymbol || tSymbol.name !== 'Array' || !tArgs || tArgs.length != 1) return;
|
||||
return tArgs[0];
|
||||
const ty = type.tsType;
|
||||
const tyArgs = type.typeArguments();
|
||||
// TODO(ayazhafiz): Track https://github.com/microsoft/TypeScript/issues/37711 to expose
|
||||
// `isArrayLikeType` as a public method.
|
||||
if (!(this.checker as any).isArrayLikeType(ty) || tyArgs?.length !== 1) return;
|
||||
return tyArgs[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,17 +96,49 @@ describe('hover', () => {
|
|||
expect(documentation).toBe('This is the title of the `TemplateReference` Component.');
|
||||
});
|
||||
|
||||
it('should work for property reads', () => {
|
||||
describe('property reads', () => {
|
||||
it('should work for class members', () => {
|
||||
mockHost.override(TEST_TEMPLATE, `<div>{{«title»}}</div>`);
|
||||
const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title');
|
||||
const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start);
|
||||
expect(quickInfo).toBeTruthy();
|
||||
const {textSpan, displayParts} = quickInfo !;
|
||||
expect(textSpan).toEqual(marker);
|
||||
expect(textSpan.length).toBe('title'.length);
|
||||
expect(toText(displayParts)).toBe('(property) TemplateReference.title: string');
|
||||
});
|
||||
|
||||
it('should work for array members', () => {
|
||||
mockHost.override(TEST_TEMPLATE, `<div *ngFor="let hero of heroes">{{«hero»}}</div>`);
|
||||
const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'hero');
|
||||
const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start);
|
||||
expect(quickInfo).toBeTruthy();
|
||||
const {textSpan, displayParts} = quickInfo !;
|
||||
expect(textSpan).toEqual(marker);
|
||||
expect(toText(displayParts)).toBe('(variable) hero: Hero');
|
||||
});
|
||||
|
||||
it('should work for ReadonlyArray members (#36191)', () => {
|
||||
mockHost.override(
|
||||
TEST_TEMPLATE, `<div *ngFor="let hero of readonlyHeroes">{{«hero»}}</div>`);
|
||||
const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'hero');
|
||||
const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start);
|
||||
expect(quickInfo).toBeTruthy();
|
||||
const {textSpan, displayParts} = quickInfo !;
|
||||
expect(textSpan).toEqual(marker);
|
||||
expect(toText(displayParts)).toBe('(variable) hero: Readonly<Hero>');
|
||||
});
|
||||
|
||||
it('should work for const array members (#36191)', () => {
|
||||
mockHost.override(TEST_TEMPLATE, `<div *ngFor="let name of constNames">{{«name»}}</div>`);
|
||||
const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'name');
|
||||
const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start);
|
||||
expect(quickInfo).toBeTruthy();
|
||||
const {textSpan, displayParts} = quickInfo !;
|
||||
expect(textSpan).toEqual(marker);
|
||||
expect(toText(displayParts)).toBe('(variable) name: { readonly name: "name"; }');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for method calls', () => {
|
||||
mockHost.override(TEST_TEMPLATE, `<div (click)="«ᐱmyClickᐱ($event)»"></div>`);
|
||||
const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'myClick');
|
||||
|
|
|
@ -174,6 +174,8 @@ export class TemplateReference {
|
|||
index = null;
|
||||
myClick(event: any) {}
|
||||
birthday = new Date();
|
||||
readonlyHeroes: ReadonlyArray<Readonly<Hero>> = this.heroes;
|
||||
constNames = [{name: 'name'}] as const ;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
|
Loading…
Reference in New Issue