【问题标题】:Typescript AST : How to get the asserted type?Typescript AST:如何获得断言的类型?
【发布时间】:2021-10-06 21:38:27
【问题描述】:

我正在尝试解析一个类并创建声明中没有值的属性声明的默认值。

例如来自这个:

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'
}

我从functionCall&lt;MyClass&gt; 得到MyClass 作为type: ts.InterfaceType 节点,然后我从那里解析属性:

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
});

我遇到的问题是,一些symbolssymbol.valueDeclaration 定义为未定义,我不知道该用什么来了解

  1. 属性的类型
  2. 如果作业完成。

我虽然检查 symbol.valueDeclaration 是否存在,以及他们的 symbol.valueDeclaration.kind 是否是某个关键字(如 SyntaxKind.StringKeyword),但它不适用于类似的东西

class SomeClass {
  noKeyword; // probably any?
}

任何帮助或指点将不胜感激。

【问题讨论】:

  • 我真的很想看看是否有人可以解决这个问题。我刚玩了一场,但我无法破解它。我怀疑如果没有这些类属性是只读的,TS 将无法推断它是否未设置(但我可能是错的)
  • 我写了一个脚本,可以从 typescript 生成运行时类型,但是 id 必须寻找它。你能多写一点代码让我们得到更多的上下文吗?

标签: typescript abstract-syntax-tree


【解决方案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 && 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.NumericLiteraltext 的类型,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) && node.typeArguments && 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;
            };
        };
    }
    
    

    【讨论】:

    • 嗨!谢谢@pytness 我试过你的代码,但我得到的一切都是StringLiteral,因为来自getTypeDescriptor(type, typeChecker)type总是undefined(也从来没有父母)。我添加了更多关于如何获取Symbol 的代码
    猜你喜欢
    • 2016-04-16
    • 1970-01-01
    • 2012-09-23
    • 2019-08-21
    • 2021-12-29
    • 2018-03-08
    • 2021-09-21
    • 2020-03-22
    相关资源
    最近更新 更多