AST应用-Eslint

127 阅读5分钟

介绍Eslint的实现原理和一些检查算法

=========

1. EsLint介绍

ESLint 是一个开源的 JavaScript 代码检查工具,主要功能包括:

  • 代码质量检查:发现代码中可能的错误或者不规范的写法
  • 代码风格检查:保证团队代码风格的一致性
  • 自动修复:能够自动修复一些简单的错误和风格问题
  • 可配置性:通过配置文件可以自定义检查规则
  • 插件机制:支持扩展新的规则和功能

2. Eslint的实现原理

  1. 代码解析:使用 Parser (默认是 espree) 将代码解析成 AST
  2. 规则引擎
    • 基于访问者模式实现规则检查
    • 每条规则都是一个访问者,可以访问不同类型的 AST 节点
    • 发现问题时通过 context.report() 报告
  3. AST 遍历
    • 深度优先遍历 AST 树
    • 对每个节点调用对应的规则访问者函数
    • 收集所有规则报告的问题

3. Code实现


const esprima = require('esprima');

class CodeAnalyzer {
    constructor() {
        this.errors = [];
    }

    // 分析代码
    analyze(code) {
        this.errors = [];
        try {
            // 使用 esprima 解析代码
            const ast = esprima.parseScript(code, { loc: true });
            
            // 执行各种检查
            this.checkVariableDeclarations(ast);
            this.checkFunctionLength(ast);
            this.checkComplexity(ast);
            
            return {
                success: this.errors.length === 0,
                errors: this.errors
            };
        } catch (error) {
            return {
                success: false,
                errors: [{
                    type: 'SyntaxError',
                    message: error.message,
                    line: error.lineNumber,
                    column: error.column
                }]
            };
        }
    }

    // 检查变量声明
    checkVariableDeclarations(ast) {
        this.traverse(ast, (node) => {
            if (node.type === 'VariableDeclaration') {
                // 检查是否使用 var
                if (node.kind === 'var') {
                    this.addError('VariableError', 'Avoid using var, prefer let/const', node.loc);
                }
                
                // 检查变量是否初始化
                node.declarations.forEach(declaration => {
                    if (!declaration.init) {
                        this.addError('VariableError', 'Variables should be initialized when declared', node.loc);
                    }
                });
            }
        });
    }

    // 检查函数长度
    checkFunctionLength(ast) {
        this.traverse(ast, (node) => {
            if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
                const lines = node.loc.end.line - node.loc.start.line;
                if (lines > 20) {
                    this.addError('FunctionError', `Function is too long (${lines} lines)`, node.loc);
                }
            }
        });
    }

    // 检查代码复杂度
    checkComplexity(ast) {
        this.traverse(ast, (node) => {
            if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
                let complexity = this.calculateComplexity(node);
                if (complexity > 10) {
                    this.addError('ComplexityError', `Function complexity is too high (${complexity})`, node.loc);
                }
            }
        });
    }

    // 计算杂度
    calculateComplexity(node) {
        let complexity = 1; // 基础复杂度

        this.traverse(node, (subNode) => {
            switch (subNode.type) {
                case 'IfStatement':
                case 'WhileStatement':
                case 'ForStatement':
                case 'ForInStatement':
                case 'ForOfStatement':
                case 'ConditionalExpression':
                    complexity++;
                    break;
            }
        });

        return complexity;
    }

    // 遍历 AST
    traverse(node, callback) {
        callback(node);
        
        for (let key in node) {
            if (node[key] && typeof node[key] === 'object') {
                if (Array.isArray(node[key])) {
                    node[key].forEach(child => this.traverse(child, callback));
                } else {
                    this.traverse(node[key], callback);
                }
            }
        }
    }

    // 添加错误
    addError(type, message, location) {
        this.errors.push({
            type,
            message,
            line: location.start.line,
            column: location.start.column
        });
    }
}

3.1 遍历算法

采用深度优先遍历算法

3.2 禁用var变量声明算法

如果节点是VariableDeclaration,并且kind是var则违规。

3.3 函数长度检查算法

如果节点是FunctionExpression或者FunctionExpression,则计算node.loc.end.line - node.loc.start.line

3.4 函数代码复杂度

如果节点是FunctionExpression或者FunctionExpression,从当前节点开始统计语句的长度

// 计算杂度
calculateComplexity(node) {
    let complexity = 1; // 基础复杂度

    this.traverse(node, (subNode) => {
        switch (subNode.type) {
            case 'IfStatement':
            case 'WhileStatement':
            case 'ForStatement':
            case 'ForInStatement':
            case 'ForOfStatement':
            case 'ConditionalExpression':
                complexity++;
                break;
        }
    });

    return complexity;
}

3.5 测试代码

const analyzer = new CodeAnalyzer();
const code = `
  function longFunction() {
      let x = 1;
      let y = 2;
      // ... repeat many times ...
  }
`.repeat(10); // 创建一个很长的函数
const result = analyzer.analyze(code);
assert.strictEqual(result.success, false);
assert.strictEqual(result.errors[0].type, 'FunctionError');
assert(result.errors[0].message.includes('Function is too long'));

4. 未定义变量检查

4.1 实现原理

模拟了JavaScript引擎查找变量的作用域链机制,通过维护作用域栈来准确判断变量的可见性范围,从而识别未定义的变量使用。

4.2 数据结构

class CodeAnalyzer {
    constructor() {
        // 使用数组模拟作用域链,每个元素是一个Set存储该作用域的变量
        // [globalScope, functionScope, blockScope, ...]
        this.scopeChain = [new Set()]; 
    }
}

4.3 算法步骤

4.3.1 作用域链管理

  • 维护一个作用域栈,栈底是全局作用域,向上是逐层嵌套的局部作用域
  • 进入新的作用域(函数/块级)时压栈
  • 离开作用域时出栈
Global Scope    [Set(全局变量)]
Function Scope  [Set(全局变量), Set(函数内变量)]
Block Scope     [Set(全局变量), Set(函数内变量), Set(块级变量)]

4.3.2 变量查找机制

  • 从当前作用域开始查找变量
  • 如果找不到,向外层作用域继续查找
  • 直到全局作用域都找不到,则判定为未定义
function isDefinedInScope(name) {
    // 从内向外查找
    for (let i = this.scopeChain.length - 1; i >= 0; i--) {
        if (this.scopeChain[i].has(name)) return true;
    }
    return false;
}

4.3.3 变量收集规则

  1. 需要收集的变量声明类型
- 变量声明: let/const/var 
- 函数声明: function 
- 函数参数 
- 类声明 
  1. 需要检查的变量使用场景
- 变量引用 
- 函数调用 
- 属性访问

4.3.4 示例分析

let a = 1;        // 全局作用域: [a]
function foo() {  // 全局作用域: [a, foo]
    let b = 2;    // 函数作用域: [b]
    {
        let c = 3;  // 块级作用域: [c]
        console.log(a);  // 查找: 块级->函数->全局 √
        console.log(b);  // 查找: 块级->函数 √
        console.log(c);  // 查找: 块级 √
        console.log(d);  // 查找: 块级->函数->全局 × 报错
    }
}

4.3.5 核心代码实例

checkUndefinedVariables(ast) {
    // 1. 作用域管理
    const enterScope = () => {
        this.scopeChain.push(new Set()); // 进入新作用域
    };
    
    const exitScope = () => {
        this.scopeChain.pop(); // 退出当前作用域
    };

    // 2. 变量查找
    const isDefinedInScope = (name) => {
        // 从内层作用域向外层查找
        for (let i = this.scopeChain.length - 1; i >= 0; i--) {
            if (this.scopeChain[i].has(name)) {
                return true;
            }
        }
        return false;
    };

    // 3. 变量注册
    const addToScope = (name) => {
        // 将变量添加到当前作用域
        this.scopeChain[this.scopeChain.length - 1].add(name);
    };

    // 4. AST遍历和检查
    this.traverse(ast, (node) => {
        // 处理作用域
        if (isBlockScope(node)) {
            enterScope();
        }

        // 记录变量声明
        if (isVariableDeclaration(node)) {
            addToScope(node.name);
        }

        // 检查变量使用
        if (isVariableUsage(node)) {
            if (!isDefinedInScope(node.name)) {
                this.addError(...);
            }
        }
    });
}

4.4 完整代码

const esprima = require('esprima');
const fs = require('fs');

class CodeAnalyzer {
    constructor() {
        this.errors = [];
        this.scopeChain = [new Set()]; // 作用域链
    }

    // 添加新的检查方法
    checkUndefinedVariables(ast) {
        this.scopeChain = [new Set()]; // 重置作用域链
        
        const addToScope = (name) => {
            this.scopeChain[this.scopeChain.length - 1].add(name);
        };

        const isDefinedInScope = (name) => {
            for (let i = this.scopeChain.length - 1; i >= 0; i--) {
                if (this.scopeChain[i].has(name)) {
                    return true;
                }
            }
            return false;
        };

        this.traverse(ast, (node) => {
            // 进入新的作用域
            if (node.type === 'FunctionDeclaration' || 
                node.type === 'BlockStatement' || 
                node.type === 'Program') {
                this.scopeChain.push(new Set());
            }

            // 记录变量声明
            if (node.type === 'VariableDeclarator') {
                addToScope(node.id.name);
            }

            // 记录函数声明
            if (node.type === 'FunctionDeclaration') {
                addToScope(node.id.name);
            }

            // 检查变量引用
            if (node.type === 'Identifier' && 
                node.parent && 
                node.parent.type !== 'VariableDeclarator' && 
                node.parent.type !== 'FunctionDeclaration' &&
                node.parent.type !== 'Property') {
                if (!isDefinedInScope(node.name)) {
                    this.addError(
                        'UndefinedVariableError',
                        `Use of undefined variable: ${node.name}`,
                        node.loc
                    );
                }
            }
        }, true); // 添加 parent 参数

        // 离开作用域
        this.traverse(ast, (node) => {
            if (node.type === 'FunctionDeclaration' || 
                node.type === 'BlockStatement' || 
                node.type === 'Program') {
                this.scopeChain.pop();
            }
        });
    }

    // 修改 traverse 方法以支持父节点
    traverse(node, callback, addParent = false) {
        callback(node);
        
        for (let key in node) {
            if (node[key] && typeof node[key] === 'object') {
                if (Array.isArray(node[key])) {
                    node[key].forEach(child => {
                        if (child && typeof child === 'object') {
                            if (addParent) child.parent = node;
                            this.traverse(child, callback, addParent);
                        }
                    });
                } else {
                    if (addParent) node[key].parent = node;
                    this.traverse(node[key], callback, addParent);
                }
            }
        }
    }

    // 修改 analyze 方法,添加新的检查
    analyze(code) {
        this.errors = [];
        try {
            const ast = esprima.parseScript(code, { loc: true });
            
            this.checkVariableDeclarations(ast);
            this.checkFunctionLength(ast);
            this.checkComplexity(ast);
            this.checkUndefinedVariables(ast); // 添加新的检查
            
            return {
                success: this.errors.length === 0,
                errors: this.errors
            };
        } catch (error) {
            return {
                success: false,
                errors: [{
                    type: 'SyntaxError',
                    message: error.message,
                    line: error.lineNumber,
                    column: error.column
                }]
            };
        }
    }
}