fix(compiler-cli): Add support for string literal class members (#38226)

The current implementation of the TypeScriptReflectionHost does not account for members that
are string literals, i.e. `class A { 'string-literal-prop': string; }`

PR Close #38226
This commit is contained in:
Andrew Scott 2020-07-24 13:22:28 -07:00 committed by Misko Hevery
parent 03a6252123
commit 65cc0c8bd6
3 changed files with 97 additions and 5 deletions

View File

@ -152,13 +152,13 @@ export interface ClassMember {
name: string; name: string;
/** /**
* TypeScript `ts.Identifier` representing the name of the member, or `null` if no such node * TypeScript `ts.Identifier` or `ts.StringLiteral` representing the name of the member, or `null`
* is present. * if no such node is present.
* *
* The `nameNode` is useful in writing references to this member that will be correctly source- * The `nameNode` is useful in writing references to this member that will be correctly source-
* mapped back to the original file. * mapped back to the original file.
*/ */
nameNode: ts.Identifier|null; nameNode: ts.Identifier|ts.StringLiteral|null;
/** /**
* TypeScript `ts.Expression` which represents the value of the member. * TypeScript `ts.Expression` which represents the value of the member.

View File

@ -363,7 +363,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
let kind: ClassMemberKind|null = null; let kind: ClassMemberKind|null = null;
let value: ts.Expression|null = null; let value: ts.Expression|null = null;
let name: string|null = null; let name: string|null = null;
let nameNode: ts.Identifier|null = null; let nameNode: ts.Identifier|ts.StringLiteral|null = null;
if (ts.isPropertyDeclaration(node)) { if (ts.isPropertyDeclaration(node)) {
kind = ClassMemberKind.Property; kind = ClassMemberKind.Property;
@ -385,6 +385,9 @@ export class TypeScriptReflectionHost implements ReflectionHost {
} else if (ts.isIdentifier(node.name)) { } else if (ts.isIdentifier(node.name)) {
name = node.name.text; name = node.name.text;
nameNode = node.name; nameNode = node.name;
} else if (ts.isStringLiteral(node.name)) {
name = node.name.text;
nameNode = node.name;
} else { } else {
return null; return null;
} }

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getSourceFileOrError} from '../../file_system'; import {absoluteFrom, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing'; import {runInEachFileSystem} from '../../file_system/testing';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
import {CtorParameter} from '../src/host'; import {ClassMember, ClassMemberKind, CtorParameter} from '../src/host';
import {TypeScriptReflectionHost} from '../src/typescript'; import {TypeScriptReflectionHost} from '../src/typescript';
import {isNamedClassDeclaration} from '../src/util'; import {isNamedClassDeclaration} from '../src/util';
@ -450,6 +450,95 @@ runInEachFileSystem(() => {
]); ]);
}); });
}); });
describe('getMembersOfClass()', () => {
it('should get string literal members of class', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
'string-literal-property-member' = 'my value';
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'string-literal-property-member', ClassMemberKind.Property);
});
it('should retrieve method members', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
myMethod(): void {
}
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'myMethod', ClassMemberKind.Method);
});
it('should retrieve constructor as member', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
constructor() {}
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'constructor', ClassMemberKind.Constructor);
});
it('should retrieve decorators of member', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
declare var Input;
class Foo {
@Input()
prop: string;
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expect(members[0].decorators).not.toBeNull();
expect(members[0].decorators![0].name).toBe('Input');
});
it('identifies static members', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
static staticMember = '';
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expect(members[0].isStatic).toBeTrue();
});
function getMembers(program: ts.Program) {
const clazz = getDeclaration(program, _('/entry.ts'), 'Foo', isNamedClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
return host.getMembersOfClass(clazz);
}
function expectMember(member: ClassMember, name: string, kind: ClassMemberKind) {
expect(member.name).toEqual(name);
expect(member.kind).toEqual(kind);
}
});
}); });
function expectParameter( function expectParameter(