跪拜 Guibai
← Back to the summary

ESLint Isn't Magic: How ASTs Turn Code Style Rules Into Machine-Readable Checks

ESLint Code Standards Complete Guide — From AST Principles to Flat Config Line-by-Line Analysis

Abstract: The readme labels eslint as a "code style standard," but these five words hide a whole mechanism. This article covers everything from "why ESLint is needed" to its underlying AST principles, and then breaks down a real eslint.config.mjs and package.json line by line, so you can not only understand the configuration but also write your own rules.


1. Why ESLint is Needed: Code Style Standards

When a team collaborates, if everyone has different habits for indentation, quotes, and semicolons, the code becomes increasingly messy and harder to maintain. Worse, some coding patterns are pitfalls themselves (like var's hoisting, ASI traps from missing semicolons, or console.log leaking debug info into production).

What ESLint does is: lock down "style" and "quality" with rules. Code that doesn't satisfy the rules directly reports errors or warnings. So the readme's definition is accurate—code style standard.

ESLint has a special trait: it only checks, it doesn't change your code (unless you add --fix). It acts like a "code reviewer," pointing out issues and leaving the decision to you.


2. What ESLint Is: A Static Code Analysis Tool

ESLint is a Linter that performs static analysiswithout running your code, it finds problems and style deviations just by "reading the source code."

An analogy:

Because it doesn't run the code, it's fast, can be integrated into editors for real-time hints (you mistype a semicolon, the editor immediately marks it red), and can act as a checkpoint before commits.


3. Underlying Principle: AST (Abstract Syntax Tree)

This is the key to understanding ESLint's foundation. After ESLint receives your source code, the first step it takes is parsing—turning a string of code into an Abstract Syntax Tree (AST).

For example, this line of code:

const a = 1

is parsed into a structured tree (simplified representation):

VariableDeclaration
   └─ VariableDeclarator
        ├─ Identifier: a
        └─ Literal: 1

With this tree, ESLint can traverse every node and apply rules to each node. For instance, the no-var rule works like this: during traversal, if it finds a node of type VariableDeclaration using var, it reports an error.

So the essence of every ESLint rule is: "Find a node of a certain shape in the AST → determine if it violates the rule → report". Once you understand AST, you understand why ESLint can "read" code—it sees structure, not text.

Source code string ──parse──► AST (Abstract Syntax Tree) ──traverse + apply rules──► Error/Warning list

4. Rule Severity Levels: 2 / 1 / 0

This is the key point emphasized in the eslint.config.mjs comments:

Level 2=error 1=warn warning 0 off

Each rule can be assigned a "level" that determines what happens on violation:

Level Numeric String Meaning
Error 2 "error" Violation reports an error, usually blocks commit
Warn 1 "warn" Hint, but does not block
Off 0 "off" Ignore this rule

Numbers and strings are completely equivalent; numbers save characters. So "no-var": 2 and "no-var": "error" mean the same thing.


5. Evolution of Configuration Format: Flat Config

Looking at the configuration file extension reveals a generational shift:

The .mjs suffix indicates this file uses ES modules (import/export), not CommonJS.


6. Line-by-Line Analysis of eslint.config.mjs

import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: { globals: globals.browser },
    rules: {
      // Level 2=error 1=warn warning 0 off
      "no-var": 2, // cannot use var
      "no-console": 1, // use during development, not in production
      "quotes": ["error", "double"],
      "semi": ["error", "always"],
      "indent": ["error", 2] // indent 2 spaces
    }
  },
  { files: ["**/*.js"], languageOptions: { sourceType: "script" } },
  tseslint.configs.recommended,
]);

Understanding block by block:

Now look at the array inside defineConfig([...]), where flat config's "flatness" manifests: each item is an independent configuration object, applied sequentially from top to bottom.

First item (core configuration):

Second item:

Third item:


7. Line-by-Line Analysis of Rules (Key Focus)

rules is the "soul" of the configuration, each corresponding to a requirement in the comments:

"no-var": 2 — Cannot use var

var has historical baggage like hoisting and redeclaration; modern code uses let/const. Level 2 (error), violation directly reports an error. This is a "code quality" type rule—guarding against pitfalls.

"no-console": 1 — Use during development, not in production

console.log is very useful for debugging but shouldn't remain in production. Level 1 (warn), only warns without blocking—because a machine cannot judge whether this console is "intentional"; a human must decide.

This reveals the wisdom in rule design: what the machine can determine (like var should definitely not be used) is set to error; what the machine cannot judge intent for (like console might be left intentionally) is set to warn.

"quotes": ["error", "double"] — Unify double quotes

Array format: the first item is the level, the second is the rule-specific configuration option. "double" means enforce double quotes ("hello"), reject single quotes ('hello').

"semi": ["error", "always"] — Must add semicolons

"always" means always add semicolons at the end of statements. This is a "style" type rule—just unify it across the team.

"indent": ["error", 2] — Indent 2 spaces

2 means the indentation width is 2 spaces (not Tab, not 4 spaces). A style rule.


8. package.json: How to Run ESLint

{
  "name": "eslint-demo",
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  },
  "type": "commonjs",
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "eslint": "^10.8.1",
    "globals": "^17.11.0",
    "typescript-eslint": "^8.66.0"
  }
}

In the future, run npm run lint to check, npm run lint:fix to check + auto-fix.


Summary

A thorough explanation of ESLint from "what it is" to "how to configure it":

  1. Why: Unify code style + discover pitfalls early, defined in the readme as "code style standard".
  2. What: A static analysis tool, doesn't run code, finds problems by "reading source code".
  3. Underlying: Parses source code into an AST (Abstract Syntax Tree), traverses nodes and applies rules—the essence of a rule is "finding a certain AST shape and judging violation".
  4. Levels: 2/1/0 i.e. error/warn/off; what the machine can determine is set to error, what requires human judgment is set to warn.
  5. Configuration: Flat config (eslint.config.mjs) organized as a flat array, files defines scope, rules defines rules, tseslint extends TS.
  6. Implementation: npm run lint checks, npm run lint:fix auto-fixes.

Once you understand AST + rule levels + flat config, ESLint is no longer an "incomprehensible config file" but a code quality tool you can control yourself.