Eslint has a number of built-in rules, one of which is called no-fun-assign, which means that you cannot assign a value to a function. The purpose of Eslint is to prevent the function from being reassigned and causing errors when called.

There are two ways to implement this rule, using the Babel plugin and Eslint plugin respectively.

Thought analysis

The goal is to check for reassignment of declared functions in two ways:

The first way to think about it is to find all the function declarations in the scope, analyze where it is referenced, and report an error if it is an assignment.

The second way to think about it is to go the other way, find all the assignments and report an error if the variable on the left is declared as a function in scope.

The source code for Eslint’s rule is implemented in the first way, and we use the Babel plugin to implement the second way.

The Babel plugin implements Lint

The second way to do this is to find all the assignments:

const noFuncAssignLint = (api, options) = > {

    return {
        visitor: {
            AssignmentExpression(path, state){}}}};module.exports = noFuncAssignLint;
Copy the code

Then use path.scope’s scope-related API to find the left side of the declaration in scope, that is, binding, and determine if the declared type is a function. If so, an error is reported.

const noFuncAssignLint = (api, options) = > {

    return {
        visitor: {
            AssignmentExpression(path, state) {
                const assignTarget = path.get('left').toString();
                const binding = path.scope.getBinding(assignTarget);

                if (binding) {
                    if (binding.path.isFunctionDeclaration() || binding.path.isFunctionExpression()) {
                        / / an error..}}}}}};module.exports = noFuncAssignLint;
Copy the code

The Babel plug-in can declare an AST type visitor for processing, which is called during traversal, where the AST can be analyzed and transformed. A PATH API is provided for adding, deleting, and modifying AST, and a path.scope API is provided for scope analysis. Apis based on path and path.scope can perform various analysis and transformation functions.

The Eslint plugin implements Lint

This rule is originally implemented by ESLint and is based on the first idea. That is, find all the function declarations, parse the references, and report an error if an assignment is made.

Eslint’s rule has two parts:

  • Meta is the original information, including documents and error messages

  • The create part is an implementation of lint functionality

module.exports = {
    meta: {
        type: "problem".docs: {
            description: "disallow reassigning `function` declarations".recommended: true.url: "https://eslint.org/docs/rules/no-func-assign"
        },

        schema: [].messages: {
            isAFunction: "'{{name}}' is a function."}},create(context) {

        function checkForFunction(node) {}

        return {
            FunctionDeclaration: checkForFunction,
            FunctionExpression: checkForFunction }; }};Copy the code

We declare the processing of FunctionDeclaration and FunctionExpression, that is, we get the declaration in scope through the context’s API, and then we judge the reference, and if the reference is an assignment, we get an error.

function checkForFunction(node) {
    context.getDeclapanredVariables(node).forEach(checkVariable);
}

function checkVariable(variable) {
    if (variable.defs[0].type === "FunctionName") { checkReference(variable.references); }}function checkReference(references) {
    // If it is an assignment statement
    astUtils.getModifyingReferences(references).forEach(reference= > {
        context.report({
            node: reference.identifier,
            messageId: "isAFunction".data: {
                name: reference.identifier.name
            }
        });
    });
}
Copy the code

The Eslint plugin can declare listeners of the AST type that it handles, which will be called during traversal, and which can perform various analyses on the AST and then report errors. Context apis are provided for analyzing ast, such as scoping, and context.report is provided for reporting errors.

The difference between the Babel plugin and Eslint plugin

Both Babel and Eslint parse the source code into an AST and iterate through the AST for processing.

The AST handler in Babel is called visitor, which can be used to analyze and modify the AST, or listEnter in Eslint, because only the AST can be analyzed, not modified.

The Babel plugin provides the PATH API for adding, deleting, and modifying the AST, and the Path-. Scope API for analyzing scopes, including declarations and references. The Eslint plugin provides an API for context analysis, scoping, etc.

Eslint’s AST contains tokens for format checking, such as Spaces and newlines. Babel’s AST does not, so format checking can only be done with Eslint.

The Eslint plugin supports fixes to modify code, but rather than modifying the AST, it does this by specifying how a range should be modified, via string substitution.

conclusion

We analyzed two approaches around the no-fun-assign rule built into Eslint, using the Babel plugin and Eslint plugin respectively. The main thing is scoping, which is supported in both Eslint and Babel.

The functionality of both Eslint and Babel is AST based, except that Babel does AST analysis and conversion, while Eslint only does AST analysis (including formatting checking).

Note that Eslint’s fix functionality is not a modification of the AST implementation, but a simple string substitution.

The Eslint plugin, the Babel plugin, etc., are all AST based implementations and have many homogeneous parts to learn from.