ESLint rules/extends/plugins
rules
Some of the core rules listed on the website start with ☑️, some with 🔧, some with ☑️ 🔧, and some with nothing at all.
☑️ : represents a rule included in extends: ‘ESLint :recommended’.
🔧 : indicates that you can use –fix to correct the modified rule.
"no-console": 0."no-debugger": 1."no-trailing-spaces": 2."quotes": [2."single"].// Indicates that characters should use single quotation marks. If the rule is violated, an error will be reported.
Copy the code
off
Or 0: disables the rulewarn
Or 1: turn on the rule,warn
Level of error (does not cause program exit)error
Or 2: turn on the rule,error
Level of error (the program will exit when triggered)
extends
None of the rules take effect by default, and extends specifies which rules to apply. For example extends: ‘ESLint :recommended’ means rules that are checked in applying ESLint Rules.
It can be configured as:
- String: indicates the path or of a configuration fileShareable configurationThe name of the (
eslint:recommended
或eslint:all
). - Array[String] : multiple configuration combinations. The later configuration inherits and overwrites the previous configuration.
Shareable Configuration: This is an NPM package of configuration objects that is used to ensure that they are already installed in a directory that ESLint can reference. Eslint-config – can be omitted when used with extends. For example, if you need to validate React style code, extends: ‘eslint-config-react’ is also written as extends: ‘React’.
// .eslintrc.js
module.exports = {
extends: [ // Array[String]
'eslint:recommended'.// The name of the shareable configuration
'./path-to-config'.// Path to the configuration file
'eslint-config-react'./ / full name
'react'./ / abbreviation].extends: 'eslint:recommended' // String
};
Copy the code
plugins
There are hundreds of rules to choose from, but that’s not enough because the official rules only check standard JavaScript syntax, and if you’re writing JSX or TypeScript, ESLint rules start to fall into disarray.
In this case, you need to install the ESLint plugin to customize certain rules for checking. ESLint plugins, like extends, have a fixed naming format, starting with eslint-plugin-, which can also be omitted when used.
For example, if we want to use TypeScript in a project, we need to change the parser to @typescript-eslint/parser and install the @typescript-eslint/eslint-plugin to extend the rule. Rules in plugins are disabled by default, so we need to enable the rules to be used in rules. In other words, plugins should be used in conjunction with rules. As follows:
// NPM I --save-dev @typescript-eslint/eslint-plugin // Register the plugin
{
"parser": "@typescript-eslint/parser"."plugins": ["@typescript-eslint"].// Import plug-ins
"rules": {
"@typescript-eslint/rule-name": "error" // Use plug-in rules
'@typescript-eslint/adjacent-overload-signatures': 'error'.'@typescript-eslint/ban-ts-comment': 'error'. }}Copy the code
Extends comes in handy when writing a bunch of configurations in rules to enable the @typescript-eslint/eslint-plugin plugin rules can be cumbersome.
{
extends: 'plugin:@typescript-eslint/recommended'
}
Copy the code
Create a new ESLint plug-in
Plugin goal: Disallow the second parameter of setTimeout in a project to be a number. For example, setTimeout(() => {}, 2) is a violation, const num = 2; SetTimeout (() => {}, num) is OK.
Project initialization
-
Eslint provides the Yeoman template (generator-ESLint) for developers to build plugins.
npm install -g yo generator-eslint Copy the code
-
Initialize the project directory
mkdir eslint-plugin-irenePugin cd eslint-plugin-irenePugin yo eslint:plugin Copy the code
The command line interaction flow is displayed. After that, the project directory of the customized plug-in is generated
? What is your name? irene ? What is the plugin ID? Irenelint // Plug-in ID? Type a short description of this plugin: for testing creating an eslint plugin? Does this plugin contain custom ESLint rules? Yes // Do you include custom ESLint rules? Does this plugin contain one or more processors? No // Whether to contain one or more processors create package.json create lib/index.js create readme.mdCopy the code
-
Create rules
yo eslint:rule Copy the code
The next step is to enter the command line interaction process. After that, a rule file template is generated
? What is your name? irene ? Where will this rule be published? Where will the ESLint Plugin // rules be published ❯ ESLint Core // The official ESLint Plugin // ESLint Plugin? What is the rule ID? Settimeout-no-number // Rule ID? Type a short description of this rule: The second param of setTimeout is forbidden to use number? Type a short example of the code that will fail: setTimeout(() => {}, Create docs/rules/settimeout-no-number.md create lib/rules/settimeout-no-number.js create tests/lib/rules/settimeout-no-number.jsCopy the code
-
The generated project directory is as follows
├ ─ ─ the README. Md ├ ─ ─ docs / / using document │ └ ─ ─ rules / / all rules of document │ └ ─ ─ settimeout - no - number. Specific rules md / / document ├ ─ ─ lib / / eslint │ rules of development ├ ─ ─ index. Introduced js + export rules folder rule │ └ ─ ─ rules / / this directory can build multiple rules │ └ ─ ─ settimeout - no - number. Js / / rules details ├ ─ ─ package. The json └ ─ ─ Tests // Unit tests└ ─ ─ lib └ ─ ─ rules └ ─ ─ settimeout - no - number. Js / / test file to the ruleCopy the code
-
Installation project dependencies
npm install Copy the code
A rule template
Open lib/rules/settimeout-no-number.js and you can see the template generated after the preceding command line operations.
module.exports = {
meta: {
docs: {
description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
},
fixable: null.// or "code" or "whitespace"
schema: [
// fill in your schema]},create: function(context) {
return {
// give me methods}; }};Copy the code
The create method returns an object whose key is a selector and value is a callback function (with an AST node as an argument), for example: {‘CallExpression’: (node) => {}}, ESLint will collect all selectors listened by the rule and the corresponding callback function, and trigger the corresponding callback whenever a selector is matched while traversing the AST.
AST: Abstract Syntax Tree
ESLint implements code verification and formatting by parsing code into an AST and iterating through it, as discussed below. Now let’s take a look at the AST resolved by setTimeout(() => {}, 2). Online AST
Write rules
By observing the generated AST, filter out the code we want to select and judge the value of the code.
// lib/rules/settimeout-no-number.js
module.exports = {
meta: {
docs: {
description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
},
fixable: null.// or "code" or "whitespace"
schema: [
// fill in your schema]},create: function(context) {
return {
// give me methods
'CallExpression': (node) = > {
if(node.callee.name ! = ='setTimeout') return // Not setTimeout
const timeNode = node.arguments && node.arguments[1] // Get the second argument
if(! timeNode)return
if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
context.report({
node,
message: 'setTimeout the second argument is forbidden to be a number '}}}}; }};Copy the code
The test case
Provide some test code that violates and passes the rules
// tests/lib/rules/settimeout-no-number.js
var rule = require(".. /.. /.. /lib/rules/settimeout-no-number"), RuleTester = require("eslint").RuleTester;
var ruleTester = new RuleTester();
ruleTester.run("settimeout-no-number", rule, {
valid: [{code: "let num = 1000; setTimeout(() => { console.log(2) }, num)"],},invalid: [{code: "setTimeout(() => {}, 2)".errors: [{message: "SetTimeout the second argument must not be a number".// Consistent with the error thrown by rule
type: "CallExpression".// rule listens to the corresponding hook},],},],});Copy the code
Auto repair
- Fixable: ‘code’ starts fixing functionality;
- Context.report () provides a fix function;
// lib/rules/settimeout-no-number.js
module.exports = {
meta: {
docs: {
description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
},
fixable: 'code',},create: function(context) {
return {
// give me methods
'CallExpression': (node) = > {
if(node.callee.name ! = ='setTimeout') return // Not setTimeout
const timeNode = node.arguments && node.arguments[1] // Get the second argument
if(! timeNode)return
if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
context.report({
node,
message: 'setTimeout the second argument is forbidden to be a number '.fix(fixer) {
const numberValue = timeNode.vlaue;
const statementString = `const num = ${numberValue}\n`;
return [
fixer.replaceTextRange(node.arguments[1].range, 'num'), fixer.insertTextBeforeRange(node.range, statementString) ] } }) } } }; }};Copy the code
debugging
Click Debug, and then select the project.
Click Settings to open a launch.json file with the program field to debug.
Open the debugger in lib/rules/settimeout-no-number.js and click start program.
Release the plugin
-
NPM: NPM login
-
Publish NPM package: NPM publish
use
-
Installing a plug-in
npm install --save-dev eslint-plugin-irenelint Copy the code
-
Introduce plug-ins and enable rules
-
Through the plugins
// .eslintrc.js module.exports = { plugins: [ 'irenelint'].rules: { 'irenelint/settimeout-no-number': 'error'}}Copy the code
-
Through the road,
Since rules in plugins are not enabled by default, they need to be enabled one by one in rules. When rules are too numerous to write, extends is used.
First, we need to modify lib/index.js
// lib/index.js var requireIndex = require("requireindex"); const output = { rules: requireIndex(__dirname + "/rules"), // All rules configs: { recommended: { plugins: ['irenelint'].// Import plug-ins rules: { 'irenelint/settimeout-no-number': 'error' // Enable the rule}}}}module.exports = output; Copy the code
And then use extends
// .eslintrc.js module.exports = { extends: [ 'plugin:irenelint/recommended']}Copy the code
-
test
Before repair: The first prompt is automatic repair prompt
After repair: If auto-repair on save is configured, it will be automatically corrected on save.
ESLint principle
eslint/lib/linter/lint.js
Assume that the content of the file to be verified is
console.log('irene');
Copy the code
The following AST is generated based on the file content, and each node is passed to the nodeQueue twice. Online AST
nodeQueue = [
{
isEntering: true.node: {
type: 'Program'.body: [Array].sourceType: 'module'.range: [Array].loc: [Object].tokens: [Array].comments: [].parent: null}}, {isEntering: true.node: {
type: 'ExpressionStatement'.expression: [Object].range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
type: 'CallExpression'.callee: [Object].arguments: [Array].optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
type: 'MemberExpression'.object: [Object].property: [Object].computed: false.optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
type: 'Identifier'.name: 'console'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'Identifier'.name: 'console'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
type: 'Identifier'.name: 'log'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'Identifier'.name: 'log'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'MemberExpression'.object: [Object].property: [Object].computed: false.optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
type: 'Literal'.raw: "'irene'".value: 'irene'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'Literal'.raw: "'irene'".value: 'irene'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'CallExpression'.callee: [Object].arguments: [Array].optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'ExpressionStatement'.expression: [Object].range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
type: 'Program'.body: [Array].sourceType: 'module'.range: [Array].loc: [Object].tokens: [Array].comments: [].parent: null}}]Copy the code
Iterate over all consolidated rules. If the rule is not 0 or ‘off’ (that is, if the rule is on), get the rule object for that rule and execute create to return the listener, which indicates which AST nodes the rule listens on. The corresponding callback function is executed when iterating over these nodes.
// Assume that the consolidated rules are as follows
configuredRules = {
'@typescript-eslint/no-explicit-any': [ 0].// ruleId: [severity]
'@typescript-eslint/explicit-module-boundary-types': [ 0].'prettier/prettier': [ 'error'].'@typescript-eslint/no-unused-vars': [ 'warn'],... } ruleObj = {meta:
create: (context) = > {
return {
'CallExpression:exit': func1,
'Identifier': func2
}
}
}
Copy the code
Iterate over the listener objects for this rule, registering listener functions for each AST node
listeners: {
'CallExpression:exit': [func1],
Identifier: [func2]
}
Copy the code
At the end of the process, we have a Listeners object where the KEY is the AST node and the value is an array of callbacks.
Iterating through the nodeQueue of the first step triggers the listeners’ corresponding callback functions, such as CallExpression, to execute the functions in the gallilisteners.CallExpression array, which detect whether the current node is violating the rules. If it is violated, warn/error is reported and stored in lintingProblems;
Return lintingProblems
reference
- Blog.csdn.net/obkoro1/art…
- Juejin. Cn/post / 690978…
other
- ESLint resolves package name
- ESLint is used with Prettier