将 Golang 的 enum 转换成 Typescript

619 阅读3分钟

Playground: golang-enum-to-ts-playground.vercel.app/

Code: github.com/anc95/golan…

看到这个标题,你也许会觉得很奇怪,我为什么要把 Golangenum 转换成 Typescript 呢?它的实际价值是什么?下面我会介绍一下最初要做这个的动机和想法

动机

在应用开发中,enum 数据类型是被大量使用的,我们可以用 enum 来表示一个任务的状态(todo、pending、done),也可以用 enum 来表示一些有限的选项(如表示学历),也可以用 enum 来表示简单的二元值(如性别、或者 Y/N)

在后端开发中,会在代码里直接使用 enum 变量,同时数据库里存储的往往是整形(如 0、1、2),作为开发者无需直接和 0、1 等这样的语言的数据打交道,而是使用 enum ,这使得代码可读性提高, 如对于任务状态用 Golang 可以这样表示

package task

type TaskStatus int

const (
  Todo TaskStatus = iota
  Pending
  Done
)

现在,我们引入前端,因为数据库里存的是 0、1这样的数据,那前端拿到的 API 返回值往往也是这样子的,如

{
  todos: [
    {name: "Learning", status: 0}
  ]
}

这里的 status 0 表示的是什么状态呢?前端往往需要自己写一个 enum 类型,来进行转换

enum TaskStatus {
  Todo,
  Pending,
  Done
}

也就是说,前后端需要各自维护一个 enum 来表达对于 0、1 这样的数据值正确的理解

基于这样重复劳动的问题,所以就有了本文要所讲的这个小工具

转换

进行语言的转换,必须理解源语言所表达的含义,这里也就用到了传统的编译相关的一些技术

Untitled.png

分词

分词就是将一句话分成一个个单词

如对应英文 how are you? 我们可以拆分成 how are you ?

对于程序化语言也是类似的,对于上面的 GolangTaskStatus 表达式,我们最终会生成如下的 token 数据结构

[]token.Token{
    {
        Value: "package",
        Type:  "Package",
        Start: {0, 0},
        End:   {0, 6},
    },
    {
        Value: "task",
        Type:  "Identifier",
        Start: {0, 8},
        End:   {0, 11},
    },
    {
        Value: "type",
        Type:  "Type",
        Start: {2, 0},
        End:   {2, 3},
    },
    {
        Value: "TaskStatus",
        Type:  "Identifier",
        Start: {2, 5},
        End:   {2, 14},
    },
    {
        Value: "int",
        Type:  "IntType",
        Start: {2, 16},
        End:   {2, 18},
    },
    {
        Value: "const",
        Type:  "Const",
        Start: {4, 0},
        End:   {4, 5},
    },
    {
        Value: "(",
        Type:  "LeftParentheses",
        Start: {4, 6},
        End:   {4, 6},
    },
    {
        Value: "Todo",
        Type:  "Identifier",
        Start: {5, 1},
        End:   {5, 4},
    },
    {
        Value: "TaskStatus",
        Type:  "Identifier",
        Start: {5, 6},
        End:   {5, 15},
    },
    {
        Value: "=",
        Type:  "Assignment",
        Start: {5, 17},
        End:   {5, 17},
    },
    {
        Value: "iota",
        Type:  "IOTA",
        Start: {5, 19},
        End:   {5, 22},
    },
    {
        Value: "Pending",
        Type:  "Identifier",
        Start: {6, 1},
        End:   {6, 7},
    },
    {
        Value: "Done",
        Type:  "Identifier",
        Start: {7, 1},
        End:   {7, 4},
    },
    {
        Value: ")",
        Type:  "RightParentheses",
        Start: {8, 0},
        End:   {8, 0},
    },
}

分词的 workflow 如下

Untitled (1).png

生成 AST

根据上一步分析出来的 token,生成对应的抽象语法树

ast.File{
    Name: "task",
    Body: {
        ast.TypeDeclaration{
            Id:   "TaskStatus",
            Kind: "int",
        },
        ast.ConstDeclaration{
            BaseDeclaration: ast.BaseDeclaration{},
            Declarators:     {
                {
                    Kind:  "TaskStatus",
                    Id:    "Todo",
                    Value: "iota",
                },
                {
                    Kind:  "",
                    Id:    "Pending",
                    Value: "",
                },
                {
                    Kind:  "",
                    Id:    "Done",
                    Value: "",
                },
            },
        },
    },
}

token 本身是上下文无关的,AST 是对于 token 的理解,组合成一个含有语法信息的数据结构,如一步我们分离出的 type Status int 三个 token,在 AST 中表示为 ****TypeDeclaration

func (a *AstGenerator) readTypeDeclaration() TypeDeclaration {
    d := TypeDeclaration{}

    next, _ := a.nextToken(true)
    d.Id = next.Value

    next, _ = a.nextToken(true)

    if next.Type == token.IntType {
	d.Kind = Int
    } else {
	d.Kind = String
    }

    return d
}

执行 AST

计算 enum

遍历上一步生成的 AST,生成相关的计算值

{
  TaskStatus: {
    Todo: 0,
    Pending: 1,
    Done: 2
  }
}

根据 enum 值 生成 TS

因为只针对 enum 类型,比较简单,所以我们无需把 Golang AST 转换成 Typescript AST 进行处理,直接根据当前计算的 enum 值生成即可

namespace task {
  export enum TaskStatus {
    Done = 2,
    Pending = 1,
    Todo = 0,
  }
}

Playground

地址:golang-enum-to-ts-playground.vercel.app/

因为转换的源代码是用 Golang 写的,所以这里是将 Golang 编译成 wasm 运行在浏览器的

截屏2022-02-28 下午5.49.05.png