#typescript #abstract-syntax-tree
Вопрос:
Я пытаюсь проанализировать класс и создать значения по умолчанию для объявлений свойств, которые не имеют значений в объявлении.
Например, из этого:
class MyClass {
name = 'My Class';
id: string;
}
functionCall<MyClass>() // <-- I get the hold of the type class here
Я хочу создать что-то вроде этого:
{
name: 'My Class',
id: 'I am generated'
}
Я получаю MyClass
из functionCall<MyClass>
type: ts.InterfaceType
узла as и оттуда анализирую свойства:
const typeChecker = program.getTypeChecker();
const type = typeChecker.getTypeAtLocation(callExpression); // This is the node where `functioCall<MyClass>()` is
type.getProperties().forEach(symbol => {
// I am stuck here
});
Проблема, с которой я столкнулся, заключается в том, что некоторые symbols
symbol.valueDeclaration
из них не определены, и я не знаю, что использовать, чтобы узнать
- Тип объекта недвижимости
- Если задание выполнено.
Я хотя и проверяю symbol.valueDeclaration
, существует ли a и есть ли у них symbol.valueDeclaration.kind
какое-то ключевое слово (например SyntaxKind.StringKeyword
), но это не сработало бы для чего-то вроде
class SomeClass {
noKeyword; // probably any?
}
Любая помощь или указания будут оценены по достоинству.
Комментарии:
1. Мне действительно интересно посмотреть, сможет ли кто-нибудь решить эту проблему. У меня только что была пьеса, но я не смог ее разгадать. Я подозреваю, что без того, чтобы эти свойства класса были доступны только для чтения, TS не сможет сделать вывод, если он не установлен (но я могу ошибаться).
2. я написал сценарий, который может генерировать типы среды выполнения из typescript, но я должен его искать. Не могли бы вы написать немного больше кода, чтобы мы могли получить больше контекста?
Ответ №1:
Я был почти на месте, мне нужно было проверить, определен ли в дочерних элементах valueDeclaration
буквальный
const propertyDeclaration = symbol.valueDeclaration;
if (ts.isPropertyDeclaration(propertyDeclaration)) {
const children = propertyDeclaration.getChildren();
const assignationLiteral = children.find(child => {
ts.isLiteralTypeNode;
return [
ts.SyntaxKind.NumericLiteral,
ts.SyntaxKind.StringLiteral,
ts.SyntaxKind.ObjectLiteralExpression,
].includes(child.kind);
});
if (assignationLiteral) {
// A value es defined!
} {
// I have a keyword, I have to check which one is it :)
}
}
Ответ №2:
Это из сценария для получения типов времени выполнения в качестве объектов:
/*
Used to handle types from:
InterfaceDeclaration,
ClassDeclaration,
TypeLiteral
*/
function getTypeDescriptor(
property: ts.PropertyDeclaration | ts.PropertySignature,
typeChecker: ts.TypeChecker,
): ts.Expression {
const { type } = property;
if (type !== undefined) {
if (property.initializer) {
switch (property.initializer.kind) {
case ts.SyntaxKind.StringLiteral:
return ts.createLiteral('string');
case ts.SyntaxKind.FirstLiteralToken:
return ts.createLiteral('number');
}
}
return ts.createLiteral('any');
}
return getDescriptor(type, typeChecker);
}
// Create expression based on objects or property types
export function getDescriptor(type: ts.Node | undefined, typeChecker: ts.TypeChecker): ts.Expression {
if (!type) {
return ts.createLiteral('unknown');
}
switch (type.kind) {
case ts.SyntaxKind.PropertySignature:
return getDescriptor((type as ts.PropertySignature).type, typeChecker);
case ts.SyntaxKind.StringKeyword:
return ts.createLiteral('string');
case ts.SyntaxKind.NumberKeyword:
return ts.createLiteral('number');
case ts.SyntaxKind.BooleanKeyword:
return ts.createLiteral('boolean');
case ts.SyntaxKind.AnyKeyword:
return ts.createLiteral('any');
case ts.SyntaxKind.TypeAliasDeclaration:
case ts.SyntaxKind.TypeReference:
const symbol = typeChecker.getSymbolAtLocation((type as ts.TypeReferenceNode).typeName);
const declaration = ((symbol amp;amp; symbol.declarations) || [])[0];
return getDescriptor(declaration, typeChecker);
case ts.SyntaxKind.ArrayType:
return ts.createArrayLiteral([getDescriptor((type as ts.ArrayTypeNode).elementType, typeChecker)]);
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.TypeLiteral:
return ts.createObjectLiteral(
(type as ts.TypeLiteralNode).members.map(member =>
ts.createPropertyAssignment(
member.name || '',
getTypeDescriptor(member as ts.PropertySignature, typeChecker),
),
),
);
default:
throw new Error('Unknown type ' ts.SyntaxKind[type.kind]);
}
}
Пример
С этим классом:
class X {
public id: number = 1;
}
Инициализатор свойства будет выглядеть следующим образом:
<ref *2> TokenObject {
parent: <ref *1> NodeObject {
parent: NodeObject {
parent: [SourceFileObject],
kind: SyntaxKind.ClassDeclaration,
name: [IdentifierObject],
typeParameters: undefined,
heritageClauses: undefined,
members: [Array],
symbol: [SymbolObject]
},
kind: SyntaxKind.PropertyDeclaration,
decorators: undefined,
modifiers: [ [TokenObject], pos: 154, end: 162, transformFlags: 536870913 ],
name: IdentifierObject {},
questionToken: undefined,
type: TokenObject {
kind: SyntaxKind.NumberKeyword
},
initializer: [Circular *2],
symbol: SymbolObject {
escapedName: 'id',
declarations: [Array],
valueDeclaration: [Circular *1],
parent: [SymbolObject],
}
},
kind: SyntaxKind.NumericLiteral,
text: '1',
}
С text: '1'
тем, чтобы быть значением свойства, kind: SyntaxKind.NumericLiteral
быть типом text
и parent.type.kind: SyntaxKind.NumberKeyword
быть фактическим определенным типом свойства.
P.S.: Я знаю, что это немного, но я надеюсь, что вы найдете это несколько полезным
РЕДАКТИРОВАТЬ: Добавление того, как я получаю типы с помощью трансформатора:
export function transformer(program: ts.Program, opts?: TransformerOptions) {
function visitor(ctx: ts.TransformationContext, sourcefile: ts.SourceFile, result: { seen: boolean }) {
const typeChecker = program.getTypeChecker();
const _visitor: ts.Visitor = (node: ts.Node) => {
if (ts.isCallExpression(node) amp;amp; node.typeArguments amp;amp; node.expression.getText(sourcefile) == 'generateRtti') {
const [type] = node.typeArguments;
const [argument] = node.arguments;
const fn = ts.createIdentifier(nameOfGenerateFunction);
const typeName = type.getText();
const typeSource = getDescriptor(type, typeChecker);
result.seen = true;
return ts.createCall(fn, undefined, [argument || ts.createStringLiteral(typeName), typeSource]);
}
return ts.visitEachChild(node, _visitor, ctx);
};
return _visitor;
}
return (ctx: ts.TransformationContext) => {
return (sourcefile: ts.SourceFile) => {
const result = { seen: false };
const newSourcefile = ts.visitNode(sourcefile, visitor(ctx, sourcefile, result));
if (result.seen) {
const generated_function = createGenerateFunction();
const statements: Array<ts.Statement> = [generated_function];
for (const statement of newSourcefile.statements) {
if (ts.isImportDeclaration(statement))
statements.splice(statements.length - 1, 0, statement);
else
statements.push(statement);
}
return ts.updateSourceFileNode(newSourcefile, statements);
}
return newSourcefile;
};
};
}
Комментарии:
1. Привет! спасибо @pytness Я попробовал ваш код, но я получаю все, как
StringLiteral
потомуtype
, что отgetTypeDescriptor(type, typeChecker)
всегдаundefined
(также никогда нет родителя). Я добавил больше кода о том, как я получаю доступ кSymbol