1
头图

AST (Abstract Syntax Tree)

Why talk about AST?

devDependencies in any current mainstream project, you will find that countless plug-ins were born in the past few years. We can summarize: ES6 translation, code compression, css preprocessor, eslint , prettier etc. Many of these modules will not be used in the production environment, but they play a very important role in the development environment. The birth of these tools is built on the shoulders of the giant AST

image

What is AST?

It is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code.

abstract syntax tree ( abstract syntax code,AST ) is a tree representation of the abstract syntax structure of the source code. , each node on the tree represents a structure in the source code, so it is abstract because the abstract syntax tree does not It will show every detail of the actual grammar. For example, nested parentheses are implicit in the structure of the tree and are not presented in the form of nodes. The abstract syntax tree does not depend on the grammar of the source language, that is to say, the context used in the grammatical analysis stage has no grammar, because when writing a grammar, the grammar is often equivalently transformed (elimination of left recursion, backtracking, and ambiguity) Sex, etc.), this will introduce some extra elements to the grammar analysis, adversely affect the subsequent stages, and even make the combined stage become chaotic. Because of this, many compilers often have to independently construct a parse tree to establish a clear interface for the front end and the back end.
image

The data converted from plain text into a tree structure, that is, AST , each entry corresponds to a node in the tree.

AST process

This part will let you understand the whole process from source code to lexical analysis to generate tokens and then to grammatical analysis to generate AST

AST from the source code? The current compiler helps to do this, how does the compiler do it?

image

The compilation process of a compiler (translating high-level language into binary bits) is very complicated, but we only need to focus on lexical analysis and grammatical analysis. These two steps are the key to AST

1158910-20201203142840253-1876335044.png

first step, the lexical analyzer, also known as the scanner , will scan the entire code first. When it encounters spaces, operators, or special symbols, it decides that a word is completed, and the identified one Words, operators, symbols, etc. are recorded in the tokens {type, value, range, loc } ), and the comments are additionally stored in a comments array.

16750e43f19f6f39_tplv-t2oaga2asx-watermark-1628559456392.awebp

For example, var a = 1; , @typescript-eslint/parser generated by the tokens parser is as follows:

tokens: [
    {
      "type": "Keyword",
      "value": "var",
      "range": [112, 115],
      "loc": {
        "start": {
          "line": 11,
          "column": 0
        },
        "end": {
          "line": 11,
          "column": 3
        }
      }
    },
    {
      "type": "Identifier",
      "value": "a",
      "range": [116, 117],
      "loc": {
        "start": {
          "line": 11,
          "column": 4
        },
        "end": {
          "line": 11,
          "column": 5
        }
      }
    },
    {
      "type": "Punctuator",
      "value": "=",
      "range": [118, 119],
      "loc": {
        "start": {
          "line": 11,
          "column": 6
        },
        "end": {
          "line": 11,
          "column": 7
        }
      }
    },
    {
      "type": "Numeric",
      "value": "1",
      "range": [120, 121],
      "loc": {
        "start": {
          "line": 11,
          "column": 8
        },
        "end": {
          "line": 11,
          "column": 9
        }
      }
    },
    {
      "type": "Punctuator",
      "value": ";",
      "range": [121, 122],
      "loc": {
        "start": {
          "line": 11,
          "column": 9
        },
        "end": {
          "line": 11,
          "column": 10
        }
      }
    }
]

second step, the syntax analyzer, also known as the parser tokens array obtained from lexical analysis into a tree structure representation, verifies the language grammar and throws a grammatical error (if this happens)

16750e44ca7e6d2d_tplv-t2oaga2asx-watermark-1628559456393.awebp

var a = 1; converted from the tokens array to a tree structure as shown below:

{
  type: 'Program',
  body: [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "a",
            "range": [
              116,
              117
            ],
            "loc": {
              "start": {
                "line": 11,
                "column": 4
              },
              "end": {
                "line": 11,
                "column": 5
              }
            }
          },
          "init": {
            "type": "Literal",
            "value": 1,
            "raw": "1",
            "range": [
              120,
              121
            ],
            "loc": {
              "start": {
                "line": 11,
                "column": 8
              },
              "end": {
                "line": 11,
                "column": 9
              }
            }
          },
          "range": [
            116,
            121
          ],
          "loc": {
            "start": {
              "line": 11,
              "column": 4
            },
            "end": {
              "line": 11,
              "column": 9
            }
          }
        }
      ],
      "kind": "var",
      "range": [
        112,
        122
      ],
      "loc": {
        "start": {
          "line": 11,
          "column": 0
        },
        "end": {
          "line": 11,
          "column": 10
        }
      }
    }
  ]
}

When generating the tree, the parser will remove some unnecessary tags (such as redundant parentheses), so the created "abstract syntax tree" is not 100% matching the source code, but it is enough to let us know how to deal with it. On the other hand, the tree generated by the parser that completely covers all code structures is called the "concrete syntax tree"

16750e44cecfbb2d_tplv-t2oaga2asx-watermark-1628559456405.awebp

Compiler extension

Want to learn more about the compiler? the-super-tiny-compiler , this is a compiler written JavaScript About 200 lines of code, the idea behind it is to Lisp compiled into C language, almost every line has a comment.

16750e44d400efea_tplv-t2oaga2asx-watermark-1628559456406.awebp

LangSandbox , a better project, it shows how to create a programming language. Of course, books like design programming languages are also full of books on the market. Therefore, this project is more in-depth, unlike the the-super-tiny-compiler project, which converts Lisp to C language. In this project, you can write your own language and compile it into C language or machine language, and finally Run it.

1_E7dHOaSFxtAnzjl7E2ajEg.png

Can you directly use the tripartite library to generate AST ? sure! There are a bunch of tripartite libraries available. You can visit astexplorer and choose the library you like. astexplorer is a great website, you can play AST online, and besides JavaScript , it also contains many other languages AST library

49005590-0e32b400-f1a2-11e8-8d94-a501140a187f-1628559456783.png

I want to emphasize one of them, in my opinion it is a very good one, babylon

49005704-5225b900-f1a2-11e8-8083-3c73464d5a78-1628559454340.png

It Babel , maybe this is the reason for its popularity. Because it is supported by the Babel project, it will always be JS features and can be used boldly. In addition, its API also very simple and easy to use.

OK, now that you know how to generate code AST , let us continue to discuss real-life use cases.

The first use case I want to talk about is code translation, which is of course Babel .

Babel is not a ‘tool for having ES6 support’. Well, it is, but it is far not only what it is about.

Babel and ES6/7/8 features has many connections, which is why we often use it. But it is just a set of plug-ins, we can also use it for code compression, React related syntax conversion (such as JSX ), Flow plug-in and so on.

49010480-4fc95c00-f1ae-11e8-8aa6-097d16c1c2db-1628559456783.png

Babel is a JavaScript compiler. Its compilation has three stages: parsing ( parsing ), translating ( transforming ), and generating ( generation ). You give Babel some of JavaScript code, which modify the code and generate a new code, how it is to modify the code? That's right! It builds AST , traverses it, babel-plugin , and then generates new code AST

image.png

Let's see this in a simple code example.

49010863-7936b780-f1af-11e8-88f8-1ab083f3eafe-1628559454339.png

As I mentioned before, Babel uses Babylon , so, we first parse the code to generate AST , then traverse AST and reverse all variable names, and finally generate the code. As we have seen, the first (parsing) and third (code generation) phases seem to be very common and will be done every time. So, Babel takes over these two steps, what we are really interested in is the AST conversion ( Babel-plugin modification).

When developing Babel-plugin , you only need to describe the node “visitors” , it will change your AST . Add it to your babel list of plug-ins, set your webpack of babel-loader configuration or .babelrc in plugins to

16750e45d9df4781-1628559456460

If you want to know more about how to create babel-plugin , you can check Babel-handbook .

49011621-daf82100-f1b1-11e8-93d1-8da5567c8279-1628559454339.png

Application of AST in ESLint

Before officially writing the ESLint plug-in, you need to understand the working principle ESLint Among them, the ESLint should be familiar to everyone. I will not explain it here. If you don't understand, please click the official document How to configure ESLint in the project.

In project development, the source code written by different developers is different, so ESLint analyze the source code written by each person?

That's right, it is AST ( Abstract Syntax Tree (Abstract Syntax Tree)), and then sacrifice the picture that has been viewed hundreds of times.

16db9a1e630b7329_tplv-t2oaga2asx-watermark-1628559456479.awebp

In ESLint , by default, esprima used to parse Javascript , generate an abstract syntax tree, and then go to intercept check whether it meets our prescribed writing method, and finally let it display an error, a warning or a normal pass. ESLint is the rule ( rules ), and the core of defining the rule is to use AST for verification. Each rule is independent of each other, you can set to disable off , warning warn ⚠️ and error error ❌, of course, there is no need to give any prompts for normal passage.

Teach you how to write Eslint plugin

Goals & knowledge points involved

ESLint plug-in in this article is designed to check whether the code comment :

  • Every declarative function and function expression need to be commented;
  • Comments are required for each interface
  • Every enum header and field need comments;
  • Each type header requires comments;
  • ......

Knowledge points

  • AST abstract syntax tree
  • ESLint
  • Mocha unit test
  • Npm released

Scaffolding construction project

Here we use yeoman and generator-eslint to build the scaffolding code of the plug-in, install:

npm install -g yo generator-eslint

Create a new local folder eslint-plugin-pony-comments :

mkdir eslint-plugin-pony-comments
cd eslint-plugin-pony-comments

Initialize the project structure of the ESLint plug-in from the command line:

yo eslint:plugin

Next enter the command line interactive process, after the process is over, the ESLint plug-in project framework and files will be generated

$ yo eslint:plugin
? What is your name? xxx // 作者
? What is the plugin ID? eslint-plugin-pony-comments // 插件名称
? Type a short description of this plugin: 检查代码注释 // 插件描述
? Does this plugin contain custom ESLint rules? (Y/n) Y
? Does this plugin contain custom ESLint rules? Yes // 这个插件是否包含自定义规则
? Does this plugin contain one or more processors? (y/N) N
? Does this plugin contain one or more processors? No // 该插件是否需要处理器
   create package.json
   create lib\index.js
   create README.md

The directory structure of the file at this time is:

.
├── README.md
├── lib
│   ├── processors // 处理器,选择不需要时没有该目录
│   ├── rules // 自定义规则目录
│   └── index.js // 导出规则、处理器以及配置
├── package.json
└── tests
    ├── processors // 处理器,选择不需要时没有该目录
    └── lib
        └── rules // 编写规则的单元测试用例

Installation dependencies:

npm install // 或者yarn

At this point, the environment is set up.

Create rules

Take the realization of "each interface header and field need to be commented" as an example to create a rule, the terminal executes:

yo eslint:rule // 生成默认 eslint rule 模版文件

Now enter the command line interaction process:

$ yo eslint:rule
? What is your name? xxx // 作者
? Where will this rule be published? ESLint Plugin // 选择生成插件模板
? What is the rule ID? no-interface-comments // 规则名称
? Type a short description of this rule: 校验interface注释 // 规则描述
? Type a short example of the code that will fail: 
   create docs\rules\no-interface-comments.md     
   create lib\rules\no-interface-comments.js      
   create tests\lib\rules\no-interface-comments.js

The project structure at this time is:

.
├── README.md
├── docs // 说明文档
│   └── rules
│       └── no-interface-comments.md
├── lib // eslint 规则开发
│   ├── index.js
│   └── rules // 此目录下可以构建多个规则,本文只拿一个规则来讲解
│       └── no-interface-comments.js
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── no-interface-comments.js

ESLint has three files named after its identifier (for example, no-interface-comments ).

  • In the lib/rules directory: a source file (for example, no-interface-comments.js )
  • In the tests/lib/rules directory: a test file (for example, no-interface-comments.js )
  • In the docs/rules directory: a Markdown document file (for example, no-interface-comments )

Before officially entering the development rules, let’s take a look at the generated rule template no-interface-comments.js :

/**
 * @fileoverview no-interface-comments
 * @author xxx
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        docs: {
            description: "no console.time()",
            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

        };
    }
};

This file gives a template for writing rules. A rule corresponds to an exportable node module, which consists of two parts: meta and create

  • meta : Metadata representing this rule, such as its category, document, schema parameters, and so on.
  • create : If meta expresses what we want to do, then create expresses how this rule will analyze the code;

create returns an object, the most common keys is AST abstract syntax tree selector , the selector, we can get the contents of the corresponding selected, then we can make some judgments on the selected content To see if our rules are met. If you are not satisfied, you can use context.report throw a problem, and ESLint will use our configuration to display the thrown content differently. For details, please refer to: context.report

Before writing the no-interface-comments rule, let’s look at the AST Explorer see how the interface code is parsed into AST .

image-20210810103517405.png

According to the above AST structure, we create two selectors verification code comments, TSInterfaceDeclaration selector check interface whether the head of the Notes, TSPropertySignature whether selector check field annotated. Traverse AST may need to use the following API , please refer to the official website for details:

  • fixer.insertTextAfter(nodeOrToken, text) -Insert text after a given node or tag
  • fixer.insertTextBefore(nodeOrToken, text) -Insert text before a given node or tag
  • sourceCode.getAllComments() -Returns an array of all comments in the source code
  • context.getSourceCode() -Get the source code
/**
 * @fileoverview interface定义类型注释校验
 * @author xxx
 */
'use strict';

const {
  docsUrl,
  getLastEle,
  getAllComments,
  judgeNodeType,
  getComments,
  genHeadComments,
  report,
  isTailLineComments,
  getNodeStartColumn,
  genLineComments,
} = require('../utils');

module.exports = {
  meta: {
    /**
     * 规则的类型
     * "problem" 意味着规则正在识别将导致错误或可能导致混淆行为的代码。开发人员应将此视为优先解决的问题。
     * "suggestion" 意味着规则正在确定可以以更好的方式完成的事情,但如果不更改代码,则不会发生错误。
     * "layout" 意味着规则主要关心空格、分号、逗号和括号,程序的所有部分决定了代码的外观而不是它的执行方式。这些规则适用于 AST 中未指定的部分代码。
     */
    type: 'layout',
    docs: {
      description: 'interface定义类型注释校验', // 规则描述
      category: 'Fill me in',
      recommended: true, // 是配置文件中的"extends": "eslint:recommended"属性是否启用规则
      url: 'https://github.com/Revelation2019/eslint-plugin-pony-comments/tree/main/docs/rules/no-interface-comments.md', // 该规则对应在github上的文档介绍
    },
    fixable: 'whitespace',  // or "code" or "whitespace"
    schema: [ // 指定选项,比如'pony-comments/no-interface-comments: [2, 'always', { leadingCommentType: 'Block', propertyComments: { pos: 'lead', commentsType: 'Block'}}]'
      {
        'enum': ['always', 'never'],
      },
      {
        'type': 'object',
        'properties': {
          /** 
           * 是否需要头部注释
           * 'No':表示不需要头部注释
           * 'Line': 表示头部需要单行注释
           * 'Block':表示头部需要多行注释
           */
          'leadingCommentType': {
            'type': 'string',
          },
          /** 字段注释采用单行还是多行注释 */
          'propertyComments': {
            'type': 'object',
            'properties': {
              'pos': {
                'type': 'string', // lead || tail 表示注释位置是行头还是行尾
              },
              'commentsType': {
                'type': 'string', // No || Line || Block 表示注释是单行还是多行,或者不需要注释
              },
            },
          },
        },
        'additionalProperties': false,
      },
    ],
  },

  create: function(context) {
    // 获取选项
    const options = context.options;
    const leadingCommentsType = options.length > 0 ? getLastEle(options).leadingCommentType : null;
    const propertyComments = options.length > 0 ? getLastEle(options).propertyComments : {};
    const { pos, commentsType } = propertyComments;
    /** 获取所有的注释节点 */
    const comments = getAllComments(context);
    // 有效的选项值
    const commentsTypeArr = ['No', 'Line', 'Block'];

    return {
      /** 校验interface定义头部注释 */
      'TSInterfaceDeclaration': (node) => {
        /** 不需要头部注释 */
        if (leadingCommentsType === 'No' || !commentsTypeArr.includes(leadingCommentsType)) return;
        const { id } = node;
        const { name } = id;
        // 判断interface的父节点是否是export
        if (judgeNodeType(node, 'ExportNamedDeclaration')) {
          /** export interface XXX {} */
          const { leading } = getComments(context, node.parent);
          if (!leading.length) {
            // 没有头部注释,抛出断言
            report(context, node.parent, '导出的类型头部没有注释', genHeadComments(node.parent, name, leadingCommentsType));
          }
        } else {
          /** enum interface {} */
          const { leading } = getComments(context, node); // 获取节点头部和尾部注释
          if (!leading.length) {
            // 没有头部注释,抛出断言
            report(context, node, '类型头部没有注释', genHeadComments(node, name, leadingCommentsType));
          }
        }
      },
      /** 校验interface定义字段注释 */
      'TSPropertySignature': (node) => {
        if (commentsType === 'No' || !commentsTypeArr.includes(commentsType)) return;
        /** 避免 export const Main = (props: { name: string }) => {} */
        if (judgeNodeType(node, 'TSInterfaceBody')) {
          const { key } = node;
          const { name } = key;
          const { leading } = getComments(context, node); // 获取节点头部和尾部注释
          const errorMsg = '类型定义的字段没有注释';
          if (isTailLineComments(comments, node) || (leading.length &&  getNodeStartColumn(getLastEle(leading)) === getNodeStartColumn(node))) {
            /** 
             * 节点尾部已有注释 或者 头部有注释并且注释开头与节点开头列数相同 
             * 这里判断节点开始位置column与注释开头位置column是因为getComments获取到的头部注释可能是不是当前节点的,比如
             interface xxx { 
                 id: string; // id
                 name: string; // name
             } 
             leading拿到的是// id,但这个注释不是name字段的
             */
            return;
          }
          // 根据选项报出断言,并自动修复
          if (commentsType === 'Block' || (commentsType === 'Line' && pos === 'lead')) {
            // 自动添加行头多行注释
            report(context, node, errorMsg, genHeadComments(node, name, commentsType));
          } else {
            // 自动添加行尾单行注释
            report(context, node, errorMsg, genLineComments(node, name));
          }
        }
      },
    };
  },
};

Automatic repair function:

/**
 * @description 在函数头部加上注释
 * @param {Object} node 当前节点
 * @param {String} text 注释内容
 * @returns
 */
const genHeadComments = (node, text, commentsType) => {
  if (!text) return null;
  const eol = require('os').EOL; // 获取换行符,window是CRLF,linux是LF
  let content = '';
  if (commentsType && commentsType.toLowerCase === 'line') {
    content = `// ${text}${eol}`;
  } else if (commentsType && commentsType.toLowerCase === 'block') {
    content = `/** ${text} */${eol}`;
  } else {
    content = `/** ${text} */${eol}`;
  }
  return (fixer) => {
    return fixer.insertTextBefore(
      node,
      content,
    );
  };
};
/**
 * @description 生成行尾单行注释
 * @param {Object} node 当前节点
 * @param {String} value 注释内容
 * @returns
 */
const genLineComments = (node, value) => {
  return (fixer) => {
    return fixer.insertTextAfter(
      node,
      `// ${value}`,
    );
  };
};

At this point, the no-interface-comments rule is basically completed

Configuration in the plugin

You can configs , which is useful when you want to provide some custom rules that support it. Refer to official website

// lib/index.js
module.exports = {
    configs: {
        recommended: {
            plugins: ['pony-comments'],
            parserOptions: {
                sourceType: 'module',
                ecmaVersion: 2018,
            },
            rules: {
                'pony-comments/no-interface-comments': [2, 'always', { leadingCommentType: 'Block', propertyComments: { pos: 'tail', commentsType: 'Line' } }],
            }
        },
    }
};

Plug-in rules will be inherited through the extends configuration:

{
    "extends": ["plugin:pony-comments/recommended"]
}

Note: Please note that the configuration by default will not enable any plugin rules, but should be treated as an independent configuration. This means that you must specify your plugin name and any rules you want to enable plugins Any plugin rules must be prefixed with a short or long plugin name

Create a processor

The processor can tell ESLint how to handle files other than JavaScript, such as extracts from other types of files in JavaScript code, and then let ESLint to JavaScript code lint , or for any purpose processor can convert pretreatment JavaScript code. Refer to official website

// 在lib/index.js中导出自定义处理器,或者将其抽离
module.exports = {
    processors: {
        "markdown": {
            // takes text of the file and filename
            preprocess: function(text, filename) {
                // here, you can strip out any non-JS content
                // and split into multiple strings to lint

                return [ // return an array of code blocks to lint
                    { text: code1, filename: "0.js" },
                    { text: code2, filename: "1.js" },
                ];
            },

            // takes a Message[][] and filename
            postprocess: function(messages, filename) {
                // `messages` argument contains two-dimensional array of Message objects
                // where each top-level array item contains array of lint messages related
                // to the text that was returned in array from preprocess() method

                // you need to return a one-dimensional array of the messages you want to keep
                return [].concat(...messages);
            },

            supportsAutofix: true // (optional, defaults to false)
        }
    }
};

To specify the processor in the configuration file, use processor with the key of the connection string of the plug-in name and the processor name (by slashes). For example, the following enable pony-comments provided by the plug markdown processors:

{
    "plugins": ["pony-comments"],
    "processor": "pony-comments/markdown"
}

To specify a processor for a specific type of file, use a combination of the overrides key and the processor For example, the following uses the processor pony-comments/markdown process the *.md file.

{
    "plugins": ["pony-comments"],
    "overrides": [
        {
            "files": ["*.md"],
            "processor": "pony-comments/markdown"
        }
    ]
}

The processor may generate named code blocks, such as 0.js and 1.js . ESLint treats such named code blocks as sub-files of the original file. You can overrides in config specify additional configuration as a named code block section. For example, the following strict disables the .js that markdown ends with 0616bd472a743a.

{
    "plugins": ["pony-comments"],
    "overrides": [
        {
            "files": ["*.md"],
            "processor": "pony-comments/markdown"
        },
        {
            "files": ["**/*.md/*.js"],
            "rules": {
                "strict": "off"
            }
        }
    ]
}

ESLint checks the file path of the named code block, and if any overrides entries do not match the file path, ignore those. Be sure to add overrides if you want lint other than the entry for the named code block *.js .

File extension processor

If the processor name starts with . , then ESLint treats the processor as a file extension processor, and automatically applies the processor to the file type. People don't need to specify the file extension processor in their configuration file. E.g:

module.exports = {
    processors: {
        // This processor will be applied to `*.md` files automatically.
        // Also, people can use this processor as "plugin-id/.md" explicitly.
        ".md": {
            preprocess(text, filename) { /* ... */ },
            postprocess(messageLists, filename) { /* ... */ }
        }
    }
}

Write unit tests

eslint.RuleTester is a utility for writing tests ESLint RuleTester constructor accepts an optional object parameter, which can be used to specify the default value of the test case ( official website ). For example, if you can specify @typescript-eslint/parser parse your test case:

const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser') });

When you need to parse the .tsx file, you need to specify a specific parser, such as @typescript-eslint/parser , because the default parser used by the eslint esprima , which does not support typescript and react

If the following error is reported during the execution of the test case:

AssertionError [ERR_ASSERTION]: Parsers provided as strings to RuleTester must be absolute paths

This is because the parser need to use absolute paths configuration ESLint RuleTester Parser to use Typescript

/**
 * @fileoverview interface定义类型注释校验
 * @author xxx
 */
'use strict';

const rule = require('../../../lib/rules/no-interface-comments');

const RuleTester = require('eslint').RuleTester;
const ruleTester = new RuleTester({
  parser: require.resolve('@typescript-eslint/parser'),
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
    comment: true,
    useJSXTextNode: true,
  },
});
ruleTester.run('no-interface-comments', rule, {
  // 有效测试用例
  valid: [
    {
      code: `
        export const Main = (props: { name: string }) => {}
      `,
      options: ['always', { leadingCommentType: 'Block', propertyComments: { pos: 'lead', commentsType: 'Block' } }],
    },
    {
      code: `
        /** 类型 */
        export interface IType {
          id: string; // id
          name: string; // 姓名
          age: number; // 年龄
        }
      `,
      options: ['always', { leadingCommentType: 'Block', propertyComments: { pos: 'tail', commentsType: 'Line' } }],
    },
    {
      code: `
        /** 类型 */
        interface IType {
          /** id */
          id: string;
          /** 姓名 */
          name: string;
          /** 年龄 */
          age: number;
        }
      `,
      options: ['always', { leadingCommentType: 'Block', propertyComments: { pos: 'lead', commentsType: 'Block' } }],
    },
  ],
  // 无效测试用例
  invalid: [
    {
      code: `
        export interface IType {
          /** id */
          id: string;
          /** 姓名 */
          name: string;
          /** 年龄 */
          age: number;
        }
      `,
      errors: [{
        message: 'interface头部必须加上注释',
        type: 'TSInterfaceDeclaration',
      }],
      options: ['always', { leadingCommentType: 'Block', propertyComments: { pos: 'lead', commentsType: 'Block' } }],
      output: `
        /** 类型 */
        export interface IType {
          /** id */
          id: string;
          /** 姓名 */
          name: string;
          /** 年龄 */
          age: number;
        }
      `,
    },
    {
      code: `
        /** 类型 */
        interface IType {
          id: string;
          name: string;
          age: number;
        }
      `,
      errors: [{
        message: 'interface字段必须加上注释',
        type: 'TSPropertySignature',
      }],
      options: ['always', { leadingCommentType: 'Block', propertyComments: { pos: 'lead', commentsType: 'Block' } }],
      output: `
        /** 类型 */
        interface IType {
          /** id */
          id: string;
          /** 姓名 */
          name: string;
          /** 年龄 */
          age: number;
        }
      `,
    },
  ],
});

When yarn test executes the test case, the console output:

image-20210810235011763.png

github Portal: https://github.com/Revelation2019/eslint-plugin-pony-comments

Debug test cases in VSCode

Follow the procedure below to create launch.json .vscode directory will be generated in the root directory

image.png

launch.json default content of 0616bd472a764d is as follows:

{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "program": "${workspaceFolder}\\lib\\index.js" // debugger默认启动的脚本
    }
  ]
}

Here we want to debug no-interface-comments rule, we need to make the following changes:

"program": "${workspaceFolder}\\tests\\lib\\rules\\no-interface-comments.js"

Then, you can debug debugger

image.png

Used in the project

(1) Install eslint-plugin-pony-comments

yarn add eslint-plugin-pony-comments -D

(2) Configuration .eslintrc.json

{
  "extends": "plugin:@casstime/inquiry-eslint/recommended",
  "parser": "@typescript-eslint/parser",
}

(3) Restart the project, restart the eslint service, and check the results as follows

image.png

If yarn start start many projects Times have not changed the file checksum error, because create-react-app scaffolding in the default configuration eslint-loader , in start or build when calls eslint go again to view all files, reference: HTTPS: // V4 .webpack.docschina.org/loaders/eslint-loader/

image.png

In view of the above problems, here are two solutions:

  • Delete the node_modules/.cache folder and restart the project. This is because eslint-loader default boot cache , the linting result cache write ./node_modules/.cache directory, which is essential when performing a complete build linting time is particularly useful, but may affect the start or build when eslint viewing


    image.png


    image.png

  • In the root directory config-overrides.js of override add function config.module.rules.splice(1, 1); , because it will review and submit the code when writing code, not at the start of the project or build and then view again, to kill the eslint-loader

release

(1) npm account login

npm login --registry=仓库镜像
# 输入用户名/密码/邮箱

(2) Perform the build

yarn build

(3) Release

yarn publish --registry=仓库镜像

If the following error is reported, the email may be incorrect

image.png

Extracurricular Knowledge: A Brief History of Lint

Lint is a tool to solve various problems caused by imprecise code. For example == and === can cause some strange problems.

JSLint and JSHint

In 2002, Douglas Crockford developed what may be the first for JavaScript syntax detection tools - JSLint , and open source in 2010.

JSLint market, it really helped many JavaScript developers to save a lot of time in troubleshooting code errors. But JSLint is also obvious- almost impossible to configure , all code styles and rules are built-in; plus Douglas Crockford promotes the Taoist tradition of "use and use", and will not open to developers. Configure or modify the rules that he thinks are right. So Anton Kovalyov Tucao: " JSLint is to make your code style is more like Douglas Crockford of it", and in 2011 Fork original project developed JSHint . "Why I forked JSLint to JSHint"

JSHint feature is configurable , while documentation is also relatively well, but also to the developer-friendly. Soon everyone switched JSLint JSHint .

The birth of ESLint

In later years it will JSHint as a code detection tool of choice, but the turning point in 2013, Zakas found JSHint unable to meet their own needs rules and and Anton After discussion, it is impossible to find in JShint achieve the same time Zakas also envisaged invention based AST of lint . So in June 2013, Zakas released the new lint tool- ESLint . "Introducing ESLint"

ESLint early source code :

var ast = esprima.parse(text, { loc: true, range: true }),
    walk = astw(ast);

walk(function(node) {
    api.emit(node.type, node);
});

return messages;

ESLint's counterattack

The appearance of ESLint did not shake the dominance of JSHint Because the former uses the AST processing rules and uses Esprima parse the code, the execution speed is much slower than that of JSHint . 1616bd472a7b1b. Secondly, there are already many editors that support JSHint . What really makes ESLint counterattack is the appearance ECMAScript 6

In June 2015, the ES2015 specification was officially released. However, after the release, the browsers on the market have extremely limited support for the latest standards. If you want to advance to experience the latest standards of grammar, it relies on Babel tools like code compiled into ES5 even lower version, and some experimental features can rely Babel conversion. But at this time, JSHint cannot provide support in the short term, but ESLint only needs a suitable parser to continue to check lint Babel team developed an alternative to the default parser ESLint babel-eslint , which makes ESLint the first lint tool that ES6 syntax.

Also in 2015, React became more and more widespread, and the new JSX became more and more popular. ESLint itself does not support JSX syntax. But because scalability, eslint-plugin-react appear to make ESLint also gave support to React -specific rules.

In 2016, the JSCS development team believed that the ESLint and JSCS too similar, and the problems that needed to be solved were the same, and finally chose to merge to ESLint and stop the maintenance of JSCS

The current mainstream lint tools and trend charts on the market:

16dbe9fe3f3812f5_tplv-t2oaga2asx-watermark.awebp

Since then, ESLint unified the world and has become the mainstream front-end tool to JSHint

refer to:

The transformation of mediocre front-end

[AST] teach you how to write Eslint plug-in

Configure ESLint RuleTester to use Typescript Parser


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。