Typescript:如何基于数据结构生成和打印 AST

2024-01-08

我正在开始一个新项目,作为其界面的一部分,我们有一大堆“令牌”,一个带有字符串值的递归对象,如下所示:

const colors = {
  accent: '#f90',
  primary: {
    active: '#fff',
    inactive: 'silver'
  }
};

我们提供了一个实用程序,用于通过基于字符串的路径(例如,primary.active for #fff在这种情况下)。将所有可能的路径提取到数组中非常容易,但我们希望为该包的使用者提供更好的自动完成功能,因此不是“字符串”,而是这些可能路径的联合或枚举。有人可能有这方面的经验吗?我最初的方法是编写一个简单的脚本,它接受一个数组并使用模板或类似的东西将其作为联合打印,但考虑到我们希望更频繁地执行此操作并且我们的用例会增加复杂性,我认为生成打印 AST 可能是更好的方法。我之前写过 babel 和 recast codemods,我只是在寻找一些关于现有工具集、示例等的指导。我快速谷歌了一下,但找不到任何东西。理想情况下,这些将与我正常的“观看”过程一起重新编译,但这是一个延伸目标^_^。


您可以使用编译器 API 提取对象类型并创建联合类型

import * as ts from 'typescript'
import * as fs from 'fs'

var cmd = ts.parseCommandLine(['test.ts']); // replace with target file
// Create the program
let program = ts.createProgram(cmd.fileNames, cmd.options);


type ObjectDictionary = { [key: string]: string | ObjectDictionary}
function extractAllObjects(program: ts.Program, file: ts.SourceFile): ObjectDictionary {
    let empty = ()=> {};
    // Dummy transformation context
    let context: ts.TransformationContext = {
        startLexicalEnvironment: empty,
        suspendLexicalEnvironment: empty,
        resumeLexicalEnvironment: empty,
        endLexicalEnvironment: ()=> [],
        getCompilerOptions: ()=> program.getCompilerOptions(),
        hoistFunctionDeclaration: empty,
        hoistVariableDeclaration: empty,
        readEmitHelpers: ()=>undefined,
        requestEmitHelper: empty,
        enableEmitNotification: empty,
        enableSubstitution: empty,
        isEmitNotificationEnabled: ()=> false,
        isSubstitutionEnabled: ()=> false,
        onEmitNode: empty,
        onSubstituteNode: (hint, node)=>node,
    };
    let typeChecker =  program.getTypeChecker();

    function extractObject(node: ts.ObjectLiteralExpression): ObjectDictionary {
        var result : ObjectDictionary = {};
        for(let propDeclaration of node.properties){            
            if(!ts.isPropertyAssignment( propDeclaration )) continue;
            const propName = propDeclaration.name.getText()
            if(!propName) continue;
            if(ts.isObjectLiteralExpression(propDeclaration.initializer)) {
                result[propName] = extractObject(propDeclaration.initializer);
            }else{
                result[propName] = propDeclaration.initializer.getFullText()
            }
        }
        return result;
    }
    let foundVariables: ObjectDictionary = {};
    function visit(node: ts.Node, context: ts.TransformationContext): ts.Node {
        if(ts.isVariableDeclarationList(node)) {
            let triviaWidth = node.getLeadingTriviaWidth()
            let sourceText = node.getSourceFile().text;
            let trivia = sourceText.substr(node.getFullStart(), triviaWidth);
            if(trivia.indexOf("Generate_Union") != -1) // Will generate fro variables with a comment Generate_Union above them
            {
                for(let declaration of node.declarations) {
                    if(declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)){
                        foundVariables[declaration.name.getText()] = extractObject(declaration.initializer)
                    }
                }
            }
        }
        return ts.visitEachChild(node, child => visit(child, context), context);
    }
    ts.visitEachChild(file, child => visit(child, context), context);
    return foundVariables;
}



let result = extractAllObjects(program, program.getSourceFile("test.ts")!); // replace with file name 

function generateUnions(dic: ObjectDictionary) {
    function toPaths(dic: ObjectDictionary) : string[] {
        let result: string[] = []
        function extractPath(parent: string, object: ObjectDictionary) {
            for (const key of  Object.keys(object)) {
                let value = object[key]; 
                if(typeof value === "string") {
                    result.push(parent + key);
                }else{
                    extractPath(key + ".", value);
                }
            }
        }
        extractPath("", dic);
        return result;
    }

    return Object.entries(dic)
        .map(([name, values])=> 
        {
            let paths = toPaths(values as ObjectDictionary)
                .map(ts.createStringLiteral)
                .map(ts.createLiteralTypeNode);

            let unionType = ts.createUnionTypeNode(paths);
            return ts.createTypeAliasDeclaration(undefined, undefined, name + "Paths", undefined, unionType);
        })

}

var source = ts.createSourceFile("d.ts", "", ts.ScriptTarget.ES2015);
source = ts.updateSourceFileNode(source, generateUnions(result));

var printer = ts.createPrinter({ });
let r = printer.printFile(source);
fs.writeFileSync("union.ts", r);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript:如何基于数据结构生成和打印 AST 的相关文章

随机推荐

  • 如何从命令 Visual Studio Extension 2017 获取 IWpfTextView

    我需要使用 TextViewAdornment 显示弹出窗口 它需要 IWpfTextView 有旧代码 private IWpfTextView GetWpfTextView IVsTextView vTextView IWpfTextV
  • 如何在代理后面使用捆绑器?

    我从以下输出中得到须藤捆绑安装命令 Fetching source index for http rubygems org Could not reach rubygems repository http rubygems org Coul
  • 在 Matlab 中交换 x 和 y 轴

    这里可能是简单的 matlab 问题 但我已经搜索过但无法弄清楚如何做到这一点 I ve got a variables which plot like this 我只是希望 x 轴成为 y 轴 反之亦然 我该如何交换它们 预先感谢您的帮助
  • 使用 ASP.NET MVC 5 和 Bootstrap 3 默认安装时,为什么样式会出现 403 错误,而字体会抛出 404 错误?

    使用默认安装的 ASP NET MVC 5 和更新的 Bootstrap 3 加载我的项目主页不会显示 CSS 样式或包含的字体字形 404 文件未找到错误 CSS 样式在 Content css 地址处收到 403 禁止查看文件夹内容 错
  • Hibernate二级缓存<>

    我想在我的 hibernate 项目中使用二级缓存 但我只了解一点关于 hibernate 二级缓存的知识 任何人都可以解释我应该如何在我的代码中使用它以及我需要什么配置和 jar 文件吗 我将这些设置设置为 hibernate cfg x
  • Scala 任务返回映射

    我有一种方法 readHeader 接受一个参数并返回任务 列表 标题 另一个方法调用多个 id 并返回列表 任务 列表 设备标题 如何退货任务 列表 列表 标题 兼容多id读取功能 trait M1 def readHeader id S
  • 当元素启用/禁用时触发函数

    这似乎是一件相对简单的事情 但我在任何地方都找不到关于如何做到这一点的任何信息 我有一个模式 在等待异步数据时打开并禁用输入 我想知道该输入何时启用 以便我可以集中输入 这就是我正在努力实现的目标 将其视为全局模式打开处理程序 modal
  • C语言中当前的微秒时间?

    如何在 Unix 平台上用 C 打印当前微秒时间 在 Linux 和 BSD 中 您可以使用gettimeofday 功能 这填充了一个timevalstruct 它有一个自纪元以来的秒字段和一个微秒字段 该函数已被弃用 分辨率越高时钟获取
  • PyQt5 图像和 QGridlayout

    我有一个小部件 它想要显示图像QLabel and QCheckBox 创建了 4 个类 每个类都包含一些要放在最终屏幕上的信息 Class Grid对齐和网格图像 文本和复选框 脚本运行后获取当前屏幕 当前小部件中没有出现图像 图像在哪里
  • 为什么以下操作数不能一起广播?

    数组的维度如下 dists 500 5000 train 5000 test 500 为什么前两个语句会抛出错误 而第三个语句却可以正常工作 dists train test Error ValueError operands could
  • 如何测试一个点是否在二维整数坐标中的凸多边形内部?

    多边形以 Vector2I 对象列表的形式给出 二维 整数坐标 如何测试给定点是否在内部 我在网上找到的所有实现都因一些微不足道的反例而失败 编写正确的实现似乎确实很难 语言并不重要 因为我会自己移植 如果它是凸的 检查它的一个简单方法是该
  • 有没有办法挂钩 Webpack 的 AST 使其识别新的模块格式?

    简洁版本 我们如何操作最终输出包的 AST 以及加载器内部文件的 AST 在这两种情况下 我都想操作现有的 AST 而不是解析源代码并创建新的 AST 我正在做的事情很慢 而且我知道 Webpack 一定已经做了 AST 所以我想避免重复工
  • PendingIntent 不适用于 Android O

    我的应用程序中有下载通知 我添加了 取消 按钮NotificationCompat Builder通过致电addAction 方法 但按钮在 Android O 设备上不起作用 当我按 取消 按钮时 什么也没有发生 但按钮在 Android
  • 在 Hive 中将字符串转换为时间戳

    我有一个值 2017 09 27T19 25 15 927 07 00 有什么方法可以将其转换为时间戳吗 我使用 Hive 1 1 0 select unix timestamp 2017 09 27T19 25 15 927 07 00
  • 使用 HaskellDB 取消映射表记录的基本示例

    假设我有以下 PostgreSQL 表定义 CREATE TABLE books id serial NOT NULL title character varying NOT NULL PRIMARY KEY id 以及以下记录定义 dat
  • jquery wcf 肥皂调用失败

    有人知道我可以对 wcf 服务进行 jquerysoap 调用吗 我的 JQuery 代码 ajax url http localhost oseop orderingservice svc HelloWorld data txtTestR
  • Android 上的 Facebook 集成 fbconnect 断开的链接

    我正在尝试将 Facebook 集成到我的 Android 应用程序中 它可以正常登录 Facebook 但当它尝试将访问令牌传递回应用程序时 它只会返回 网页位于 fbconnect success access token 访问令牌 可
  • 使用 Caliburn.Micro 视图模型优先方法时如何在设计器中显示内容控制?

    我在 WPF 应用程序中使用 ViewModel first 方法使用 Caliburn Micro CM 我正在用命令栏和活动项目组成主视图 主 viewModel 设置命令栏 viewModel 的属性 并正确导航到活动项目 运行时一切
  • 如何缩放 UIButton 的 imageView?

    我使用以下命令创建了一个名为 button 的 UIButton 实例 其中包含图像 UIButton setImage forState Button frame 大于图像的大小 现在我想缩小这个按钮的图像 我尝试改变button ima
  • Typescript:如何基于数据结构生成和打印 AST

    我正在开始一个新项目 作为其界面的一部分 我们有一大堆 令牌 一个带有字符串值的递归对象 如下所示 const colors accent f90 primary active fff inactive silver 我们提供了一个实用程序