Create custom eslint

Grewer
中文

cause

In multi-person cooperative projects, eslint and prettier are not necessary. They can help you to unify and standardize the use of a certain thing and a certain method.

However, sometimes it is not satisfactory. Some specifications are not provided by the official package, but we also need to carry out certain specifications. At this time, relying on manual code review is unreliable.

So what we need is to customize eslint to standardize our development

principle

ast syntax tree

In fact, the principle of eslint is based on the ast syntax tree. You can refer to my previous article about him: babel and ast

initialization

First we need to create a warehouse to store our code, I named it: @grewer/eslint-plugin-rn

Use instructions to initialize the project:

npx -p yo -p generator-eslint yo eslint:plugin

Here we can package.json , for example, I will change the project name to this:
eslint-plugin-rn => @grewer/eslint-plugin-rn

Then execute the yarn instruction (or npm i ) to download the dependencies we need

At this time, you can put generator-eslint into the devDependencies of the project for our convenience.

yarn add -D generator-eslint

So far, the initialization of the project is officially completed

create

Put the instruction npx yo eslint:plugin into scripts :

{
  "scripts": {
    "test": "mocha tests --recursive",
+    "create:rule": "npx yo eslint:rule"
  }
}

Start execution command: yarn create:rule :

The effect is as follows:

  1. What is your name? author name, just fill in your own name
  2. Where will this rule be published? (Use arrow keys) Which generated file to choose is the same, so press Enter
  3. What is the rule ID? rule, such as: no-inner-style
  4. Type a short description of this rule: just fill in whatever you want, or you can press enter directly
  5. Type a short example of the code that will fail: failed code situation, because it is troublesome to type in the terminal, directly press Enter to skip, add later

File directory after creation:

├── README.md
├── docs
│   └── rules
│       └── no-inner-style.md
├── lib
│   ├── index.js
│   └── rules
│       └── no-inner-style.js
├── package.json
├── tests
│   └── lib
│       └── rules
│           └── no-inner-style.js
└── yarn.lock

example

We need to fill in our logic lib/rules/no-inner-style.js

module.exports = {
    meta: {
        docs: {
            description: "jsx render cannot write style",
            category: "Fill me in",
            recommended: false
        },
        fixable: null,  // or "code" or "whitespace"
        schema: [
            // fill in your schema
        ]
    },
    create: function (context) {

        // variables should be defined here

        //----------------------------------------------------------------------
        // Helpers
        //----------------------------------------------------------------------

        // any helper functions should go here or else delete this section

        //----------------------------------------------------------------------
        // Public
        //----------------------------------------------------------------------

        return {

            // give me methods

        };
    }
};

First of all, we must have an idea. My purpose is that the render in 060d59b1b171a0 cannot write inline styles. What we have to do is to check style the attribute 060d59b1b171a2 exists in the attributes of jsx and whether it is a object format.

Of course, there are special cases, such as style in width will change according to the variable {width:flag?100:0} , or some changes in animation, so you need to pay attention when judging

So the question is, how do we find the style attribute in jsx?

Here we need some tools, below I recommend two URLs (their functions are the same)

Use of tools

First, we need two pieces of code that fit the scene:

// 这就是特殊的情况, 这种情况下我们不会禁止行内样式
class TestView extends Component {
    render() {
        const mode = 'dark';
        return (
            <View style={{flex: 1, width: 200, color: mode === 'dark' ? '#000' : '#fff'}}>
            </View>
        )
    }
}

// 这是需要禁止的情况
class TestView extends Component {
    render() {
        const mode = 'dark';
        return (
            <View style={{flex: 1, width: 200}}>
            </View>
        )
    }
}

Put the situation that needs to be prohibited into the above website (if you report an error, you need to modify the configuration to make it support jsx)

Place the mouse cursor on the style label, as shown in the figure:

Through the analysis of the website, we can see that the attribute of style in ast is called JSXAttribute

So we use it as a key and add a method:


module.exports = {
    meta: {
        // 省略, 不改变
    },
    create: function (context) {
        // 省略这里的注释
        return {
            // 将 JSXAttribute 作为键
            JSXAttribute: node => {
                // 注意 如果这里写不下去了, 可以直接跳到下一步(调试)中

                const propName = node.name && node.name.name;
                // console.log(propName)

                // 先判断 style 是否存在
                if (propName === 'style') {

                    // node.value.expression.type 这个路径, 在 ast 网站中可以快速找到, ObjectExpression也一样
                    // 意思是 当 style 的值是一个 Object 的时候, 抛出信息
                    if (node.value.expression.type === 'ObjectExpression') {

                        // context.report 的作用就是抛出信息, node参数 就是抛出信息的节点在那个位置
                        context.report({
                            node,
                            message: "不要使用行内样式",
                        });
                        // TODO 注意!  这里我们还没考虑特殊情况
                    }
                }
            }
        }
    }
};

At this point we have grasped the essence of ast

Debug & test

Here we need a test file to debug our rules, open the file tests/lib/rules/no-inner-style.js :

"use strict";

var rule = require("../../../lib/rules/no-inner-style"),

    RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester();
ruleTester.run("no-inner-style", rule, {

    valid: [
        // give me some code that won't trigger a warning
    ],

    // 先将我们刚刚的例子代码上到这里的 code 中
    invalid: [
        {
            code: `
            class TestView extends Component{
                render() {
                    const mode = 'dark';
                    return (
                        <View style={{flex:1, width: 200}} >
                        </View>
                    )
                }
            }
            `,
            errors: [{
                message: "不要使用行内样式",
            }]
        }
    ]
});

Filling code and errors in message

Run the node command in the terminal: node tests/lib/rules/no-inner-style.js

but , she will report an error:
AssertionError [ERR_ASSERTION]: A fatal parsing error occurred: Parsing error: The keyword 'class' is reserved
The reason lies in the execution environment

test configuration

In tests/lib add file config.js , path ( tests/lib/config.js )

const testConfig = {
    env: {
        es6: true
    },
    parserOptions: {
        ecmaFeatures: {
            jsx: true,

        },
        ecmaVersion: 2021,
        sourceType: 'module',
    },
}

module.exports = {
    testConfig
}

Introduced and used in tests/lib/rules/no-inner-style.js

var rule = require("../../../lib/rules/no-inner-style"),

    RuleTester = require("eslint").RuleTester;

// 引入
const {testConfig} = require("../config.js");

// 使用
var ruleTester = new RuleTester(testConfig);
ruleTester.run("no-inner-style", rule, {

    valid: [],
    invalid: [
        {
            // 因为没变化所以此处省略
        }
    ]
});

At this point, we execute the command again: node tests/lib/rules/no-inner-style.js

If the verification is successful (that is, context.report thrown message message test case), there will be no return information

If the verification fails, the reason will be printed out

Add special case

Earlier we talked about the special case of style. In this case of variables, we will no longer throw error messages.

Now copy the code to valid in the test case file:

var rule = require("../../../lib/rules/no-inner-style"),

    RuleTester = require("eslint").RuleTester;

// 引入
const {testConfig} = require("../config.js");

// 使用
var ruleTester = new RuleTester(testConfig);
ruleTester.run("no-inner-style", rule, {

    valid: [
        `
        class TestView extends Component {
            render() {
                const mode = 'dark';
                return (
                    <View style={{flex: 1, width: 200, color: mode === 'dark' ? '#000' : '#fff'}}>
                    </View>
                )
            }
        }
        `
    ],
    invalid: [
        {
            // 因为没变化所以此处省略
        }
    ]
});

At this time, we will execute the command: node tests/lib/rules/no-inner-style.js we will find that the error is reported. Of course, this is a special situation we have anticipated:

AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [
  {
    ruleId: 'no-inner-style',
    severity: 1,
    message: '不要使用行内样式',
    line: 6,
    column: 27,
    nodeType: 'JSXAttribute',
    endLine: 6,
    endColumn: 98
  }
]

At this time, let's modify our rule file lib/rules/no-inner-style.js :


module.exports = {
    meta: {
        // 省略没有变化的内容
    },
    create: function (context) {
        return {
            JSXAttribute: node => {
                const propName = node.name && node.name.name;
                if (propName === 'style') {
                    // 可以通过执行  `node tests/lib/rules/no-inner-style.js` 和 console 来调试当前程序
                    // console.log(node.value.expression)
                    if (node.value.expression.type === 'ObjectExpression') {
                        // const arr = node.value.expression.properties
                        // 如果 style 中有表达式, 则不判断
                        for (let i = 0, len = arr.length; i < len; i++) {
                            // ConditionalExpression当然是可以在 ast 网站中找到, 也可以通过 console 慢慢调试出来
                            if (arr[i].value.type === 'ConditionalExpression') {
                                // 如果有表达式则直接返回, 不抛出任何信息
                                return
                            }
                        }
                        context.report({
                            node,
                            message: "不要使用行内样式",
                        });
                    }
                }
            }
        }

    }
};

Execute the instruction again, you can find that it passed normally

Add some functional components to expand the scope of testing to ensure that our rules are foolproof. For this part, you can see the warehouse at the end of me, and I won’t go into details in the article.

test tools

When initializing in our project, he built-in testing tool mocha , we can directly run:

yarn test
// 或者
npm run test
sh: mocha: command not found is reported in the project
You can use this instruction: "test": "node_modules/.bin/mocha tests --recursive", replace the previous old instruction

It can test tests file, of course, debugging is also possible, as long as you don't think it is troublesome

Documentation

When we use the command to create a new rule, he also has a new file that is the document file: docs/rules/no-inner-style.md

In this file, we can write in detail the matters needing attention, and some fields to be filled in can be deleted

# jsx render cannot write style (no-inner-style)

## Rule Details

Examples of **incorrect** code for this rule:

```js

function TestView(){
    return (
        <View style={{flex:1, width: 200}} >
        </View>
    )
}
```

Examples of **correct** code for this rule:

```js

function TestView() {
    const mode = 'dark';
    return (
        <View style={{flex: 1, width: 200, color: mode === 'dark' ? '#000' : '#fff'}}>
        </View>
    )
}
```

## When Not To Use It

规范项目的行内样式, 如果不需要可以关闭

and also need to update the README.md document

Project integration

The current project can be released directly

After the release of my project, you can see that his full name is: @grewer/eslint-plugin-rn

After adding in the main project, package.json added like this:

  "eslintConfig": {
    "extends": [
      // 省略
    ],
+    "plugins": ["@grewer/rn"], // 将我们的插件插入这边
    "rules": {
        // 编写规则的危险等级
+      "@grewer/rn/no-inner-style": 1
    },
    "env": {
      "react-native/react-native": true
    }
  },

In eslint, the value of a rule can be one of the following values:

  • "off" or 0 -close rule
  • "warn" or 1 -open the rule, use the warning level error: warn (will not cause the program to exit)
  • "error" or 2 -open the rule, use the error level of the error: error (when triggered, the program will exit)

Of course, the above configuration is also .eslintrc

If your plugin's full name has no prefix (scoped), add it like this:

(If the full name of the plug-in is: eslint-plugin-rn)

{
  "plugins": [
    "rn"
  ],
  "rules": {
    "rn/rule-name": 2
  }
}

You need to add it like this, and the gap is a prefix

note

After changing the eslint configuration, if you want to take effect, you need to restart eslint
For example, in webstorm, you need to open the eslint module in the configuration, display Disabled Eslint , select ok to close

Open the module again and restore it to its original state, of course, restarting the editor can also be solved

Rule default

The problem is coming: When we have more and more rules, we need to add the attribute rules
Here we need to optimize

In the project, we need to write the default value. One solution is to write directly:
Modify module.exports in the lib/index.js

-module.exports.rules = requireIndex(__dirname + '/rules')

+module.exports = {
+    rules: requireIndex(__dirname + '/rules'),
+    configs: {
+        recommended: {
+            plugins: ["@grewer/rn"],
+            rules: {
+                "@grewer/rn/no-inner-style": 1
+            },
+        }
+    }
+}

Of course, the configuration of eslint in the main project also needs to be modified:

{
  "eslintConfig": {
    "extends": [
      "xxx 之前另外的 config",
+      "plugin:@grewer/rn/recommended"
    ],
-    "plugins": ["@grewer/rn"], // 删除 "@grewer/rn"
-    "rules": {
-        "@grewer/rn/no-inner-style": 1
-    },// 删除, 但是我们也可以加上, 来覆盖默认值
    "env": {
      "react-native/react-native": true
    }
  }
}

Of course, because of the increase in rules, it lib/index.js file. We can create a new script to automatically add the default value of the rule:

create.js in the root directory

const requireIndex = require("requireindex");
const fs = require('fs')

const pluginName = '@grewer/rn'

const rules = requireIndex(__dirname + "/lib/rules")

const keys = Object.keys(rules)

const defaultLevel = keys.map(key => {
    // 这里可以进行更加详细的判断
    return `'${pluginName}/${key}': 1`
})


const data = `
const requireIndex = require("requireindex");

module.exports = {
    rules: requireIndex('./rules'),
    configs:{
        recommended: {
          plugins: ['${pluginName}'],
          rules: {
            ${defaultLevel.join(',')}
          },
        }
    }
}`


fs.writeFileSync('./lib/index.js', data, 'utf8')

Run script: node create.js

lib/index.js file generated in this way looks like this:

const requireIndex = require("requireindex");

module.exports = {
    rules: requireIndex('./rules'),
    configs:{
        recommended: {
          plugins: ['@grewer/rn'],
          rules: {
            '@grewer/rn/no-inner-style': 1
          },
        }
    }
}

Further optimization

Now the project relies on requireindex . There are many plug-in libraries that do not depend on this library. At this time, we also need to optimize slightly:

Modify package.json :

{
  "dependencies": {
    // 原来 requireindex 的位置, 删除
-     "requireindex": "~1.1.0",
  },
  "devDependencies": {
+    "requireindex": "~1.1.0", // 现在的位置
    "eslint": "^7.1.0",
    "generator-eslint": "^2.0.0",
    "mocha": "^8.3.0"
  },
}

Modify the create.js script just now:

    const requireIndex = require("requireindex");
    const fs = require('fs')
    
    const pluginName = '@grewer/rn'
    
    const rules = requireIndex(__dirname + "/lib/rules")
    
    const keys = Object.keys(rules)
    
    const defaultLevel = keys.map(key => {
        // 这里可以进行更加详细的判断
        return `'${pluginName}/${key}': 1`
    })
    
    
+    const temp = keys.map(key => {
+        return `'${key}': require('./lib/rules/${key}.js')`
+    })
    
    
    const data = `
-    const requireIndex = require("requireindex");
    
    module.exports = {
        rules:{
+             ${temp.join(',')}
        },
        configs:{
            recommended: {
              plugins: ['${pluginName}'],
              rules: {
                ${defaultLevel.join(',')}
              },
            }
        }
    }`
    
    
    fs.writeFileSync('./lib/index.js', data, 'utf8')

File after running:

module.exports = {
    rules:{
         'no-inner-style': require('./rules/no-inner-style.js')
    },
    configs:{
        recommended: {
          plugins: ['@grewer/rn'],
          rules: {
            '@grewer/rn/no-inner-style': 1
          },
        }
    }
}

The plug-in is now pure , which only depends on node

Finally, modify our delivery instructions:

{
    "scripts": {
        "test": "node_modules/.bin/mocha tests --recursive",
        "create:rule": "npx yo eslint:rule",
-       "pub": "npm publish",
+       "pub": "node create.js && npm publish",          
    },
}

Concluding remarks

This article introduces the eslint plug-in, from project creation to plug-in creation, then to package release and finally optimization

In the team, for the decision of our meeting, the consensus can be implemented into the project, the eslint plug-in is indispensable

The eslint plugin library created in this project: https://github.com/Grewer/eslint-plugin-rn

Citation reference

阅读 893

Developer

838 声望
13 粉丝
0 条评论

Developer

838 声望
13 粉丝
文章目录
宣传栏