1. 引言
代码规范是软件开发领域经久不衰的话题。在前端领域中,说到代码规范,我们会很容易想到检查代码缩进、尾逗号以及分号等等,除此之外,代码规范还包括了针对特殊场景定制化的检查。 JavaScript
代码规范检查工具包括 JSLint
、JSHint
、ESLint
、FECS
等等,样式代码规范检查工具主要为 StyleLint
。
本文涉及的方案只包括 ESLint
以及 Stylelint
两个检查工具,ESlint
以及 StyleLint
都是插件化的代码静态检查工具,其核心都是通过对代码解析得到的 AST
(Abstract Syntax Tree
,抽象语法树)进行处理,定位不符合约定规范的代码。
2. 背景
san-native
是百度 APP 内部的一套动态 NA
视图框架,利用 JS引擎
驱动 NA
端渲染,使得 web
前端工程师可以十分方便的编写原生移动应用,一套代码多端运行。随着百度 APP 中越来越多的业务开始接入 san-native
,在此过程中,经常遇到 h5
中的一些样式属性以及事件在 san-native
中不支持,不按照 san-native
中内置组件嵌套规则的代码导致渲染结果不符合预期。比如下面一段.san
文件中的代码存在多处错误会导致端上渲染不正常甚至导致 crash
:
san-native
中不支持行内样式 flex-basis
san-native
中不支持滚动事件 on-scroll
san-native
中文本节点 span
不允许嵌套 img
lottie-view
必须要有 source
或者 src
属性san-native
中不支持 display:inline
<template>
<div style="background-color:#fff; flex-basis:100px">
<div on-scroll="onScroll" class="{{$style['demolist-wrapper']}}">
内容
</div>
<span><img />san-native中span不允许嵌套img</span>
<lottie-view />
</div>
</template>
<script>
export default {
components: {},
onScroll() {
// do something
},
initData() {
return {};
}
}
</script>
<style lang="less" module>
.demolist-wrapper {
display: inline;
}
</style>
因此,为了能够在编码阶段提前发现这些问题,我们需要对代码进行一些特殊的检测,包括样式,事件,以及嵌套规则。为了实现这样的功能,我们启动了 san-native-linter
项目,该项目中包含了两个相互独立的插件:@baidu/eslint-plugin-san-native
以及 @baidu/stylelint-plugin-san-native
,我们将逐一介绍其实现原理。
3. 抽象语法树(AST)
首先我们需要了解代码检测的主角 —— 抽象语法树 (Abstract Syntax Tree
)。在计算机科学中,抽象语法树简称 AST
,它是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
将字符串源码转换成 AST
的工具称为解析器,常见的 Javascript
解析器有 @babel/parser
,espree
,acorn
等,样式解析器有 postcss
,cssTree
等。AST
的生成有两个步骤:
我们可以通过在线工具 [1] 查看一段代码的 AST
,比如下图所示的 AST
,图中用到的解析器为 @babel/eslint-parsre
,右侧所示的对象为左侧代码对应的 AST
,该 AST
的根节点 type
为 Program
,其 body
中有两个子节点,分别为 import
以及 export
对应的语法节点,其 type
分别为:ImportDeclaration
与 ExportDefaultDeclaration
。每个节点中 range
表示当前节点对应的代码在字符串源码中的开始与结束位置,loc
为开始与结束位置的行列信息。
4. eslint-plugin-san-native
介绍 eslint-plugin-san-native
插件的实现之前,我们会先介绍 ESlint
中的规则 (rule
),ESlint
配置与复用方案以及 ESlint
的运行原理,最后介绍插件如何实现以及关键的技术点。
上文中已经通过简单的例子介绍了抽象语法树的结构,并且在引言部分已经简述了 ESlint
检测代码的核心思想,即对 AST
进行处理从而定位不符合规定的代码,在 ESlint
中对 AST
进行处理的实体就是这里所说的规则(rule
),下面给出了一个规则的示例代码:
module.exports = {
meta: {
type: "problem",
docs: {...},
schema: [],
messages: {readonlyMember: "The members of '{{name}}' are read-only."}
},
create(context) {
return {
ImportDeclaration(node) {
context.report({
node: node,
messageId: "readonlyMember",
data: {name: 'xxx'}
});
}
};
}
};
在这样的一个规则中,我们需要导出一个对象,包括 meta
属性以及 create
方法,前者用于标记该 rule
的一些信息,后者则用于处理 AST
某个节点,并提供错误信息与出错的节点。
meta
中,通过 type
标记规则的类型,docs
包含了规则的文档链接等信息,schema
则表示了配置规则应该遵守的约定,messages
包含了错误信息。create
函数:ImportDeclaration
的方法,我们将该方法称之为 import
语法节点的访问器 (visitor
),即在 ESlint
对整个 AST
遍历的过程中,访问到 import
语法节点的时候会调用所有名称为 ImportDeclaration
的 visitor
。create
函数接收的参数为 context
对象,该对象上挂载了 ESlint
/ 自定义解析器为 rule
提供的方法以及用户配置文件中的自定义配置信息,更多的属性与方法见官方文档。这里我们调用 context.report
将错误信息以及对应的语法节点提供给 ESLint
。上文介绍了 ESlint
中的规则,在实际的工程应用中我们可以通过对规则进行定制化的配置来满足特定的需求,但是如果每启动一个项目,我们都需要进行相同的配置,势必会带来一定的时间成本。ESLint
提供了全面、灵活的配置能力,可以配置解析器、规则、环境、全局变量等等;可以快速引入另一份配置,和当前配置层叠组合为新的配置;还可以将配置好的规则集发布为 npm
包,在工程内快速应用。接下来,我们以 @ecomfe/eslint-config
为例看看如何高效的实现配置的复用,下图为该代码库的目录结构:
在 @ecomfe/eslint-config
中,每个 js
文件都是一份 eslint
的配置,根目录下的入口文件 index.js
为基础配置 (base
),其他文件夹可以看作是对基础配置的扩展,比如 san
文件夹下是关于 san
的一些规则的配置,在实际的项目中可以通过下面的方式引入:
module.exports = {
extends: [
'@ecomfe/eslint-config',
'@ecomfe/eslint-config/san', // 注意顺序
// 或者选择严格模式
// '@ecomfe/eslint-config/san/strict',
],
};
ESlint
会将 extends
字段中的所有配置文件合并起来,每个配置文件包含如下几个内容:
module.exports = {
parser: 'xxx',
parserOptions: {
parser: 'yyy',
sourceType: 'module'
},
plugins: [],
env: {},
rules: {
'indent-legacy': 'off',
}
};
在这样一个配置文件中,各个字段的含义如下:
parser
:用于申明自定义 parser
,该 parser
会将文件内容转换成 AST
parserOptions
:自定义 parser
的配置项plugins
:申明使用的 ESlint
插件,这部分会在后面 ESlint 工作原理介绍env
:申明检测所处的环境,该选项用于引入一组预定义的全局变量rules
:对规则的配置上文中依次介绍了 ESlint
的规则的实现以及 ESlint
配置的复用,本节我们说明插件与配置文件之间的区别,ESlint
插件的入口文件示例代码如下:
module.exports = {
rules: {
'no-style-float': require('./rules/no-style-float'),
// ...
},
processors: {
'.san': require('./processor'),
// ...
},
configs: {
always: {
plugins: ['@baidu/san-native'],
rules: {
'@baidu/san-native/no-style-float': 'error',
}
},
// ...
}
};
在这个入口文件中,我们向 ESlint
提供了一个对象,该对象中包含的属性有:
rules
:包含了该插件所有的规则的具体实现processors
:这里我们定义了专门用于处理.san
文件字符串源码以及检查信息的 processor
configs
:包含了一些配置,可以看到与 @ecomfe/eslint-config
中的配置文件类似,具备 plugins
选项,以及对 rules
的配置。需要说明的是:为了复用上面 configs.always
的配置,我们可以在项目的.eslintrc.*
文件中 extends
选项加上如下代码:
module.exports = {
extends: ['plugin:@baidu/san-native/always']
};
因此 ESlint
插件以及配置文件的区别可以总结如下:
plugin
插件主要涉及自定义规则的具体实现,同时还能够提供配置extend
配置主要涉及规则的具体配置接下来介绍 ESlint
是如何处理各种配置文件的,以及插件与配置文件中各字段在 ESlint
中的作用。ESlint
提供了命令行的方式来检测某个文件的代码,比如,我们想对.san
文件进行检查,那么可以通过下面的命令来实现:
eslint --ext .san src/app/component/animate/index.san
当我们执行命令行的时候,ESlint
的工作原理如下图所示:
从上图当中我们可以看到,文件的字符串内容首先会被插件的 prepocess
处理,然后处理的结果被 parser
解析成对应的 AST
,然后遍历 AST
的同时执行每个规则提供的方法,最后得到的检测结果会被 postprocess
处理。因此 ESlint
插件中的 processors
属性给开发者提供了操作字符串源码以及处理检测结果的能力。接下来分析 ESlint
配置中的一些字段在检测过程中的作用:
extends
:以【配置的复用方案】中的代码为例,其中 @ecomfe/eslint-config
以及 @ecomfe/eslint-config/san
会被合并,按照在数组中的顺序,相同的选项,后者的配置会覆盖前者。比如,前者的配置中 parser: @babel/eslint-parser
会被后者的 parser: 'san-eslint-parser'
替换,plugins
,rules
都会被收集到一个对象当中,收集的规则为当前配置中的 rules
会被当前配置中 extends
,plugins
的配置规则覆盖。
root
:由于在读取配置文件的时候,会根据传入的 filePath
依次往上查找对应的文件,并将配置文件解析出来。当某个配置文件设置了 root
为 true
的时候,会停止继续向上搜索。
plugins
:该字段决定了最终处理某类文件的 processor
是哪个插件提供的,举例来说,当 A
与 B
中都申明了对.san
文件处理的 processor
,如果按照下面的配置,ESlint
最终会用 B
插件提供的 processor
处理.san
文件,如果将 plugins
字段中两个插件互换位置,则 ESlint
最终会用 A
插件提供的 processor
。总的来说,ESlint
会从 plugins
字段申明的插件中从后向前找到第一个用于处理.san
文件的 processor
。
module.exports = {
plugins: [
"A",
"B"
],
extends: [
"plugin:A/base",
"plugin:B/base"
]
};
parser
选项:当使用了自定义的 parser
之后,那么文件的内容将会被自定义 parser
解析成 AST
,否则会使用默认的 parser
,即 eslint
提供的 espree
parserOptions
:该选项是当前 parser
的 option
,每一个 rule
以及自定义 parser
都能够获取到该选项的值。当解析器将字符串解析成 AST
之后,在遍历 AST
的过程中会根据当前的节点类型执行对应的一些列提前注册好的 vistor
。
调用每个 rule
的 create
函数收集 visitor
:create
函数必须返回一个 visitor
,即 key
为 AST
节点类型,value
为一个接收该节点的函数,ESlint
将 create
函数返回的 vistor
注册到内部维护的订阅发布器上。
AST
:遍历的过程中会通过订阅发布器执行执行收集过程中注册的所有 vistor
经过上述 ESlint
的工作原理的了解之后,我们开始介绍如何实现 eslint-plugin-san-native
来解决我们的问题,以下面的单文件组件为例:
<template>
<div></div>
</template>
<script>
import Test from './index.san';
export default {}
</script>
<style lang="less">
.a {}
</style>
有以下两点需要考虑的地方
.san
文件代码解析成 AST
:显然无法直接使用 @babel/eslint-parser
或者 @typescript-eslint/parser
来进行解析,因此我们需要用自定义解析器来处理,这部分的工作 san-eslint-parser
已经帮我们处理了,后续在规则实现的时候进行分析。<style>
中的样式:由于 san-eslint-parser
不会处理.san
文件中 <style>
部分代码,因此我们需要单独处理,这里借助 postcss
对 <style>
部分进行解析。构建的项目目录结构如下
入口文件 eslint-plugin-san-native/lib/index.js
的代码如下:
module.exports = {
rules: {
'no-style-float': require('./rules/no-style-float'),
// ...
},
processors: {
'.san': require('./processor'),
// ...
},
configs: {
always: {
plugins: ['@baidu/san-native'],
rules: {
'@baidu/san-native/no-style-float': 'error',
}
},
// ...
}
};
下面我们分别实现 processor
以及 rule
。
根据 ESlint
工作原理可知,ESlint
在获取到字符串源码的时候,会先利用插件提供的 preprocess
处理字符串源码,接着利用 parser
解析成 ast
,然后将各个 ast
节点交给 rule
处理,接着处理后的检测结果交给 postprocess
处理,最后再执行 fix
。因此,从 preprocess
到 postprocess
的过程中,处理的文件内容是不变的(ast
会被 Object.freeze
处理),因此,我们可以在 preprocess
中将.san
中的 <style>
获取之后,利用 postcss
将其解析成 ast
,并存储起来供后续所有 rule
共享。
<style>
对应的 ASTconst postcss = require('postcss');
const syntaxs = {
less: require('postcss-less'),
sass: require('postcss-sass'),
scss: require('postcss-scss')
};
const processor = postcss([() => {}]);
module.exports = {
getAst(syntax, content, plugins) {
let ast = null;
try {
ast = syntax ? processor.process(content, {syntax}).sync(): processor.process(content).sync();
} catch (error) {}
return ast;
},
getStyleContentInfo(text) {
const lines = text.split('\n');
const content = /()([\s\S]*?)<\/style>/gi.exec(text);
const langMatch = /\slang\s*=\s*("[^"]*"|'[^']*')/.exec(content[1]);
const lang = langMatch[1].replace(/["'\s]/gi, '');
const astFn = lang ? this.getAst : this.getAst.bind(null, syntaxs[lang]);
return {
startLine: lines.indexOf(content[1]),
ast: astFn(content[3]),
startOffset: text.indexOf(content[3])
};
}
};
上面的代码根据 <style>
中的 lang
字段,调用不同的 parser
对样式内容进行解析,并获取到代码块 <style>
所在的行数 startLine
以及所处文件的位置 startOffset
,这些数据都是用来修正样式 ast
节点位置的,这样 eslint
才能在输出错误信息的时候找到样式在文件中的真实位置。当然这里也可以直接利用 postcss-syntax
提供的 syntax
传入 postcss(defaultPlugins).process
函数中,该 syntax
可以自动根据文件名称或者代码内容自动选择正确的语法解析器。
const {styleAstCache} = require('./utils/cache');
module.exports = {
preprocess(code, filename) {
// 所有.san 都会处理
styleAstCache.storeAst(styleHelper.getStyleContentInfo(code));
return [code];
},
postprocess(messages) {
// 清除数据
styleAstCache.storeAst(null);
return messages[0];
}
};
在各个规则中只需要引入 styleAstCache
,并调用 styleAstCache.getAst
即可获取到样式代码的 AST
,styleAstCache
在这里只是用于存储 ast
而已。
由于规则的实现依赖于自定义 parser
提供的 ast
,因此我们需要先对 san-eslint-parser
的原理有一定的了解,那么我们将现分析其原理,然后介绍几类规则的实现方案。
自定义 parser
需要提供 parseForESLint
方法,我们这里只关注该方法返回结果中的部分属性 (更多属性见官方):
ast
:AST
根节点services
:自定义 parser
为 rule
提供的服务,每条规则可以通过 context.parserServices
访问到ast
san-eslint-parser
会将我们.san
的文件内容利用分成三个 block
,其中利用 parserOPtions.parser
指定的解析器来处理 script
部分的内容,script
中如果是 JavaScript
代码则 parserOPtions.parser
为 @babel/eslint-parser
,如果是 Typescript
代码则为 @typescript-eslint/parser
。style
部分不会处理,template
部分当作 HTML
来解析。
上图所示为自定义 parser
生成的 AST
,根节点的 type
为 Program
,根节点的 body
属性存储了 script
代码的 ast
,根节点上的 templateBody
为 template
部分的 ast
。由于 ESlint
只会遍历根节点以及 body
上的节点,因此如果我们想为 templateBody
注册 visitor
,那么可以通过 services
来实现。
services
san-eslint-parser
会在 services
属性上定义三个方法,我们只关注其中一个,简化后的代码如下:
let emitter = null; // 发布订阅器
function defineTemplateBodyVisitor(templateBodyVisitor) {
let scriptVisitor = {};
if (!emitter) {
emitter = new EventEmitter();
scriptVisitor["Program"] = node => {
traverseNodes(rootAST.templateBody)
};
}
for (const selector of Object.keys(templateBodyVisitor)) {
emitter.on(selector, templateBodyVisitor[selector]);
}
return scriptVisitor;
}
该方法主要完成了两部分的工作:
rule
中针对 templateBody
的 visitor
:该方法接收的参数为 templateBodyVisitor
,该对象存储了所有针对 templateBody
的 visitor
,这些 visitor
会注册到自定义 parser
内部的发布订阅器上。script
对应的 AST
的 visitor
:该 visitor
的 type
为 Program
,根据上文中 ESlint
工作原理一节,我们可以知道,ESlint
在遍历 AST
的过程中,当遇到类型为 Program
的根节点时,会执行该 visitor
,并且该 visitor
会调用自定义 parser
内部方法对 templateBody
存储的 AST
进行遍历。因此,我们可以利用上述方法在每条 rule
中编写相关的 visitor
来处理 templateBody
中不同 type
类型语法节点,如下代码所示:
module.exports = {
meta: {...},
create(context) {
return context.parserServices.defineTemplateBodyVisitor(context, {
'VElement'(node) {
// do something
},
'VText'(node) {
// do something
}
});
}
};
在 template
模板中,我们需要检测某个标签上的事件,内联样式,必选属性三种内容,为了避免重复代码,希望通过配置的方式实现规则。首先定义内置组件的描述信息,举例来说:
{
"name": "lottie-view", // 标签名称
"events": [ // 支持的事件
"click",
"touchstart",
"touchmove",
"touchend",
"touchcancel",
"layout",
"longclick",
"pressin",
"pressout",
"firstmeaningfulpaint",
"animationfinish",
"downloadfinish"
],
"attributes": {
"required": [],
"oneOf": [["src", "source"]], // 必须的可选属性
"content": {}
},
"style": {
"required": [],
"notsurpport": []
},
"nestedTag": [] // 允许的子标签
}
上面的描述信息中依次定义了标签名称,支持的事件,必选的属性,不支持 / 必须的内联样式,以及允许的子标签名称。描述信息的另一个优势是当组件库更新或者添加组件的时候,只需要在组件中维护这样的信息,则可以在不发布新版本 ESlint
插件的时候应用到组件新的规则。
在上文中已经介绍了如何在规则中通过编写相关的 visitor
来处理 templateBody
中不同 type
类型语法节点,因此我们只需要对节点的相关数据进行一些判断,就可以实现代码检测。判断的逻辑这里不再介绍,只贴上一个 VElement
中需要关注的节点属性:
上图中是下面标签对应的节点数据,为了获取标签的属性,我们可以从 startTag.attributes
中获取,可以看到其中属性名称为 style
的节点数据。
<div style="background-color:#fff; flex:1">...</div>
对于样式规则来说,我们需要同时检测 tempate
上的内联样式,也需要检测 <style>
块中的样式代码,简化后的规则代码如下:
module.exports = {
meta: {...},
create(context) {
return context.parserServices.defineTemplateBodyVisitor(context, {
'VElement[name="template"]'(node) {
const {ast: result, startLine, startOffset} = styleAstCache.getAst();
if (result && result.root) {
result.root.walkDecls(decl => {
// do something
});
}
},
VAttribute(node) {
const name = utils.getName(node);
if (name == null) {
return;
}
if (name === 'style' && node.value && node.value.value) {
let styleValArr = inlineStyleParser(node.value.value);
styleValArr.forEach(decl => {
// do something
});
}
}
});
}
};
可以看到我们对 template
对应的 AST
定义了两个 visitor
,第一个 visitor
用于获取 VElement
并且节点名称是 template
的语法节点,在该节点的 visitor
中,利用 postcss
提供的 API 遍历 <style>
对应的 ast。第二个 visitor
用于检测 template
中每个标签上 style
属性中的内联样式。
在每一条规则中,当发现不符合规则的代码时,我们可以通过 context.report
将对应的 ast
语法节点 / 位置信息以及错误信息提供给 ESlint
// 提供节点
context.report({
node: node,
messageId: "..."
});
// 提供位置信息:loc
context.report({
loc: node.loc,
message: "..."
});
这样 ESlint
能够通过 vscode
中的插件对错误代码进行高亮,从而实现在编译前提示代码中不支持的样式,事件,嵌套规则等等。
5. stylelint-plugin-san-native
到此,我们介绍了如何开发 ESlint 插件检测.san/.js/.ts
文件中的 san
组件,下面介绍如何开发 StyleLint
插件来检测.less/.sass/.scss/.styl
文件中的样式代码。StyleLint
提供了类似 ESlint
的配置方式,可以在配置文件.stylelintrc.*
中 extends
多个配置文件,对单个 rule
进行配置,支持通过编写插件实现自定义的规则,支持使用 processor
在开始检测之前对源码字符串进行修改,并在结果输出之前对检测结果进行修改。
下图为 StyleLint
的工作流程图,这里的 processor.code
相当于 ESlint
中的 preprocess
,而 processor.result
相当于 ESlint
中的 postprocess
。StyleLint
与 ESlint
的工作原理非常相似,从整体上来说,processor.code
与 processor.result
之间的过程与 ESLint
有区别,StyleLint
中会遍历所有 rules
,然后将 AST
根节点交给每个 rule
进行遍历,而不像 ESlint
中需要自己遍历 AST
。
从上文 StyleLint
的工作流程分析可以知道,StyleLint
的规则接收一个 AST
根节点以及配置数据,因此其规则示例代码如下:
module.exports = function rule(primary, secondary, context) {
return (root, result) => {};
};
其中,primary
以及 secondary
为 rule
配置的时候填写的配置,举例来说:
"rules": {
"block-no-empty": null, // primary 为 null
"color-no-invalid-hex": true, // primary 为 true
"comment-empty-line-before": [
"always", // primary 为 always
{"ignore": ["stylelint-commands", "between-comments"]} // secondary
]
}
只需要将 rule
利用 StyleLint
提供的方法处理后即可生成一个插件,并且需要提供 ruleName
以及 messages
const stylelint = require("stylelint");
const ruleName = "plugin/xxx";
const messages = stylelint.utils.ruleMessages(ruleName, {
expected: "Expected ..."
});
module.exports = stylelint.createPlugin(
ruleName,
function (primary, secondary, context) {
return function (root, result) {
// ...
stylelint.utils.report({/* .o. */});
};
}
);
module.exports.ruleName = ruleName;
module.exports.messages = messages;
构建的项目目录结构如下:
其中入口文件 index.js
的简化代码如下:
module.exports = [
stylelint.createPlugin(...),
stylelint.createPlugin(...),
// ...
]
同时提供了两份配置文件分别为:always.js
以及 temporary.js
,下面为 always.js
的代码:
module.exports = {
plugins: ['.'],
rules: {
'@baidu/stylelint-plugin-san-native/no-flex-basis': true,
// ...
}
};
在实际工程项目的.stylelintrc.js
中可以通过 extends
字段复用配置文件,比如:
module.exports = {
extends: [
'@baidu/stylelint-plugin-san-native/always',
'@baidu/stylelint-plugin-san-native/temporary'
],
rules: {}
}
由于篇幅有限,我们只对一个具体的规则实现进行介绍,比如在 san-native
中并不支持样式属性 justify-content
的值被设置为 baseline
,因此我们需要对该属性的值进行检测以及报错处理,规则的部分关键代码如下:
const {utils} = require('stylelint');
const getDeclarationValue = require('stylelint/lib/utils/getDeclarationValue');
const declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex');
const valueParser = require('postcss-value-parser');
const meta = {
styleName: 'justify-content',
message: `Only some values of '${styleName}' are supported in sna-native`,
surrpportValue: ['flex-start', 'flex-end', 'center', 'space-between', 'space-around']
};
const ruleName = `stylelint-plugin-san-native/valid-justify-content`;
const messages = utils.ruleMessages(ruleName, {
expected: () => meta.message
});
module.exports = function rule(primary) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, {primary});
if (!validOptions || !primary) { return; }
root.walkDecls(decl => {
// 将declaration语法节点上属性键值对解析成AST
const parsed = valueParser(getDeclarationValue(decl));
// 遍历每个属性值对应的节点
parsed.walk(node => {
if (meta.surrpportValue.indexOf(node.value) < 0) {
utils.report({
// 获取declaration语法节点中属性值部分在与declaration语法节点开始位置的偏移量
index: declarationValueIndex(decl) + node.sourceIndex,
message: messages.expected(),
node: decl,
ruleName,
result
});
}
});
});
};
};
module.exports.ruleName = ruleName;
module.exports.messages = messages;
在对代码进行分析之前,我们需要了解 postcss
返回的 AST
的两个关键点:
declaration
的语法节点,举例如下:justify-content: baseline;
AST
上的 walkDecls
方法获取 AST
树中的每个类型为 declaration
的语法节点,该方法是由 postcss 提供,更多的方法可见 postcss 官方文档上面代码中 rule
函数利用 root.walkDecls
遍历语法树中的 declaration
语法节点,并且每个 declaration
语法节点会被传入 root.walkDecls
接收的回调函数中,在该回调函数中如果发现属性值在 san-native
中不支持,则需要通过 stylelint.utils.report
将错误信息,发生错误的节点,以及属性值偏移量,当前规则名称传递给 StyleLint
,这样 StyleLint
才能够定位到不规范代码的位置。同时借助编辑器插件将不符合代码规范的代码高亮出来,以 vscode
为例,进行如下的高亮提示:
6. 总结
至此,我们介绍了如何实现 ESlint
以及 StyleLint
的插件来检测 san-native
项目中不符合规定的代码,并从底层原理的角度上介绍了插件里各个字段以及方法在检测过程中的作用,希望能对大家有所帮助。
7. 参考资料
[1] astexplorer: https://astexplorer.net/
EOF
作者:焕宇
2021 年 06 月 22 日
招聘信息
百度APP大Feed跨端团队致力于大前端基础技术研发,负责开源生态建设、工具链、平台化建设和跨端解决方案,服务于亿级用户。我们正在寻找优秀的前端/Android/iOS工程师,期待你的加入。
简历投递:bdapp-recruit@baidu.com