Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

49. eslint 多语言规则 #50

Open
funfish opened this issue Apr 14, 2022 · 0 comments
Open

49. eslint 多语言规则 #50

funfish opened this issue Apr 14, 2022 · 0 comments

Comments

@funfish
Copy link
Owner

funfish commented Apr 14, 2022

盛夏到初秋,这次没有坐在窗边看不到磅礴的雨,只是这个夏天也少了烈日的暴晒,就这么过去了。去年台风天的黄昏,很好看,还拍了不少照片,到了今年,左等右等的,结果台风一刮,夏天就走了。2021好像也快要过去了。今年没有专门学习新的技术,中途换工作的,从2月拖到了6月,中间又忙了工作,梳理业务的,难得现在又空总结一下自己做的多语言。

多语言与 eslint

多语言为什么和 eslint 相关?通过 eslint 规则来限制业务开发,凡是涉及到中文的,都应该有多语言的约束,否则提示 error。这样就很强约束了,在多人协作的时候尤其重要,保持团队的统一风格。

这里说一下这个功能: 凡是涉及到中文的,都应该有多语言的约束。在 eslint 里面,这样的规则称之为 rule,比如我们经常看到的 no-debugger 禁止使用 debugger,就是这样的。最后实现效果是这样的:当输入中文变量包括 jsx 里面存在非法中文的时候,都会提示报红:

加上代码提交之前的 lint-staged 检查就可以统一规范了。

通用规则实现

在看多语言的 eslint 的规则实现之前可以看一下普通规则是如何的,我们就按上面的 no-debugger 来看看,这个是 eslint 内部的实现,用户只需要配置就好了,代码如下:

module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "disallow the use of `debugger`",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-debugger"
        },

        fixable: null,
        schema: [],

        messages: {
            unexpected: "Unexpected 'debugger' statement."
        }
    },

    create(context) {

        return {
            DebuggerStatement(node) {
                context.report({
                    node,
                    messageId: "unexpected"
                });
            }
        };

    }
};

eslint/lib/rules/no-debugger.js 这个文件蛮简单的,可以看出下面有个 metacreate,前者不难猜出是配置信息,包括提示信息以及是否推荐这些;后面的则是出现 debugger 语句的时候就提示报错 context.report。是不是很简单?

回过头来可以看一下 eslint rule 官方教程,可以看到 recommended 就是是否在 eslint:recommended 扩展里面启用。最重要的 create 方法,eslint 会去遍历代码的抽象语法树 AST,调用 rule 里面注册的方法检测节点,上面则是 degugger 节点。

函数名是自己随便定义的?不是的,需要是对应的节点,可以通过 esprima 来解析来获得需要的函数名,比如下图,先有变量声明 VariableDeclaration 后有 DebuggerStatement

如果你把 no-debugger 中的 DebuggerStatement 换成 VariableDeclaration,那每次声明变量都会异常。提到 AST 解析还是要说一下,espreeeslint 的官方解析器,基于 esprima,并且输出结构相似。

钩子实现上面的 no-debugger 比较简单,遇到 DebuggerStatement 直接 context.report,就完成了整个上报过程了。

其他需要主要的是规则代码的位置需要在 lib/rules/ 下面,同时要有 tests/lib/rules 以及对应的文档。

多语言规则实现

通过上文 create 提示的实现,先是梳理常见的中文都是哪些节点,比如最常见的 let a = '中文',在 esprima 里面查询到的节点类型为 Literal;其他的还有两大块一个是 jsx 里面的文字,比如 <div>中文</div> 这样的,还有一个是字符串模版变量,比如 let a = 中${name}文。前者可以通 JSXText 获取,后则稍微麻烦点,下面重点介绍一下:

模版字符串

模版字符串解析的下图:

可以看到存在 quasisexpressions 两个数组,前者是字符串信息,后面是表达式。i18next 本身是支持变量传入的,比如 i18next.t('中{{value}}文', { value: name }),变量从第二个参数里面传入,生成文案替换。由于表达式不一定是变量,可以采用 value 名字自动生成名称。

字段名称是生成了,后续还要获取到字段名称对应的 value 也就是 name 这个字符串。存在变量名的可以直接获取,其他的比如 i18next.t('中{{value}}文', { value: 1 + 1 }),要如何获取到后面的 1 + 1 呢?可以有如下写法

const value = context.getSourceCode().getText().slice(expression.range[0], expression.range[1]);

eslint 规则里面会传入 context 上下文,通过上下文可以获取到非常多的信息,比如上面的获取全文字符串,再通过 range,定位到具体的内容。

忽略与修复

正常代码里面中文也是会有需要的地方,比如上报日志,埋点信息这些,甚至更加根本的如何识别 i18next.t('中文') 这样的表达式不报错呢?这样的表达式已经是正常写法了。所以这里需要 ignore 正常表达式。

进入节点通过匹配白名单的形式可以过滤掉,只是就上文的 i18next.t('中文') 匹配了函数,但是文案又是另外一个节点,如何做到两个关联到一起呢?这里就需要额外数组来记录当前关系,比如进入 i18next.t 函数的时候,标记为 true,后面进入函数形参的时候,检查标记,为 true 就忽略了。只是什么时候退出呢?这里有另外一个钩子 CallExpression:exit。由于存在多个嵌套的可能,所以用的是一个数组来维护,当 exit 的时候退出。

上面的例子,是遇到 debugger 的时候报错,那如何 fix 呢,eslint 有修复功能。context.report 里面的传参 fixer 函数,提供了不少方法。

context.report({
    node,
    message,
    fix(fixer) {
        return fixer.replaceText(
            node,
            `${useCallee}('${textReplacer(node.value)}')`
        );
    }
});

还用到了 fixer.replaceTextRange,来精准替换。

存量代码处理

对于老项目,直接用多语言的主要问题是,现有文案如何处理,要一个个替换显然不合理。最先的 i18next 提供了 i18next-scanner 工具,只是起更多的是将现有的 i18n._('Loading...') 这样的实现提出文案到 json 文件里面。还有其他思路比如通过 babel 的形式来解析,还有通过正则的形式来处理,两个方案都比较麻烦,容易出问题,于是我们这边有新的方案,本来也是通过 eslint 的规则来处理,那为什么不用 eslint 的解析器来提取词条呢?

require('eslint').CLIEngine(options).executeOnFiles 获取到 eslint 的分析结果,当然需要提前配置好规则,比如 i18next/no-literal-string 这个。后续获取到结果后,将其输出为转换为 json 文件输出就可以了,可以说这个规则一举两得。

单测

用的是 require("eslint").RuleTester 比如下面:

var ruleTester = new RuleTester({
  parser: require.resolve("babel-eslint"),
  parserOptions: {
    sourceType: "module",
    ecmaFeatures: {
      jsx: true
    }
  }
});
ruleTester.run("多语言规则测试", rule, {
    // 写 case valid 和 invalid
})

debug 的时候采用 create javascript debug terminal 的方式就可以了。

总结

这次算是深入的研究 eslint 了,从单个规则到引擎扫描,还有测试,打开了自己了新天地,尤其是 eslint 对团队规范的统一还是有很大帮助,后续的还可以添加 typescript 帮助,将 json 文件改为 ts,在文案里面缺少该词条就报错的,建立更加完整的机制。

在三亚的酒店的窗边,眺望着远处的海岸线,觉得写写博客,旅游,生活可好了,只是心中想的还是想要进击,想要不断的提升自己,偶得半天安宁,还是好好偷闲。

惭愧这边文章是 2021.10 就写好了,结果拖到今天才发表,不过说来也奇怪感觉时间好像没有过多少一样,好像去三亚还是昨天的事情。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant