ESLint Rewrite

repository·main·Indexed 43 days ago

https://github.com/eslint/rewrite

A monorepo for the architectural rewrite of ESLint, featuring modular packages for core logic, configuration management, and compatibility. Includes @eslint/compat for wrapping v8.x/v9.x rules and plugins, @eslint/config-array for glob-based configuration management, @eslint/config-helpers for type-safe configurations via defineConfig(), and @eslint/core for language plugin implementation.

Tokens
25.3K
Snippets
94
Records
112
Agent score
56%

What's inside ESLint Rewrite

  1. Overview of ESLint Rewrite packages

    main

    ESLint Rewrite is a monorepo containing the core components and utilities for the rewritten version of ESLint. The repository is organized into several specialized packages that handle configuration, compatibility, core logic, and plugin interfaces.

    Available packages:
    - `@eslint/compat`: ESLint Compatibility Utilities
    - `@eslint/config-array`: Config Array
    - `@eslint/config-helpers`: @eslint/config-helpers
    - `@eslint/core`: ESLint Core
    - `@eslint/mcp`: ESLint MCP Server
    - `@eslint/migrate-config`: ESLint Configuration Migrator
    - `@eslint/object-schema`: ObjectSchema Package
    - `@eslint/plugin-kit`: @eslint/plugin-kit
  2. Use @eslint/core for implementing language plugins

    main
    The @eslint/core package serves as the foundation for the rewritten, runtime-agnostic ESLint core. Currently, its primary purpose is to export the core types required to implement language plugins. Developers building new language support or plugins should use this package to ensure compatibility with the upcoming ESLint core architecture.
  3. Caching mechanisms in ConfigArray

    main

    To optimize performance, ConfigArray employs two aggressive caching strategies:

    1. File-based Caching: The result of getConfig() is cached against the specific filename provided. This ensures that calling getConfig() with the same filename always returns the exact same object.
    2. Index-based Caching: The system caches the combination of configuration elements used to generate a config. If a new file matches the same set of configuration elements (e.g., elements 1, 5, and 7), the result is retrieved from the cache using the key 1,5,7 instead of being recalculated.
  4. Remove keys during merge by returning undefined

    main

    If a custom merge() function returns undefined, the corresponding key will be removed from the resulting object during a merge operation.

    const schema = new ObjectSchema({
    	date: {
    		merge() {
    			return undefined;
    		},
    		validate(value) {
    			if (isNaN(Date.parse(value))) {
    				throw new Error("Invalid date.");
    			}
    		},
    	},
    });
    
    const object1 = { date: "5/5/2005" };
    const object2 = { date: "6/6/2006" };
    
    const result = schema.merge(object1, object2);
    console.log("date" in result); // false
  5. Implement SourceCode#traverse() using VisitNodeStep and CallMethodStep

    main

    To implement the SourceCode#traverse() method, you can return an array of steps using VisitNodeStep and CallMethodStep.

    • VisitNodeStep: Describes visiting a specific node.

      • target: The node being visited. The node's type determines which method is called on the rule (e.g., a Literal node triggers the Literal() method).
      • phase: 1 for the 'enter' phase, 2 for the 'exit' phase.
      • args: An array of arguments to pass to the rule's visitor method.
    • CallMethodStep: Used to call a specific method on the rule that is not tied to a node visit (e.g., onCodePathStart or custom lifecycle methods like onIteration).

      • target: The name of the method to call.
      • args: An array of arguments to pass to the method.
    import { VisitNodeStep, CallMethodStep } from "@eslint/plugin-kit";
    
    class MySourceCode {
        traverse() {
            const steps = [];
    
            for (const { node, parent, phase } of iterator(this.ast)) {
                // Standard node visit
                steps.push(
                    new VisitNodeStep({
                        target: node,
                        phase: phase === "enter" ? 1 : 2,
                        args: [node, parent],
                    }),
                );
    
                // Call a specific rule method
                steps.push(
                    new CallMethodStep({
                        target: "onIteration",
                        args: [steps.length]
                    });
                )
            }
    
            return steps;
        }
    }
  6. Define subschemas for nested objects

    main

    To validate and merge nested objects, use the schema property within a key definition. This allows you to define a full ObjectSchema structure for that specific key.

    const schema = new ObjectSchema({
    	name: {
    		schema: {
    			first: {
    				merge: "replace",
    				validate: "string",
    			},
    			last: {
    				merge: "replace",
    				validate: "string",
    			},
    		},
    	},
    });
    
    schema.validate({
    	name: {
    		first: "n",
    		last: "z",
    	},
    });
  7. Understanding the ESLint type definition strategy

    main

    ESLint is transitioning its type management strategy to ensure accuracy and control. The core rewrite will move primary type definitions into @eslint/core rather than relying solely on external community-maintained packages.

    Key aspects of this strategy include:

    • Primary Source of Truth: Core types (such as Position, SourceLocation, RuleSeverity, and Language) are defined within @eslint/core to ensure they match the rewritten core API.
    • Interim Convergence: The @types/eslint package is being updated to pull definitions from @eslint/core where applicable to improve consistency.
    • API Evolution: Users should be aware that the core rewrite will likely introduce a significantly different API compared to current versions, and type definitions will reflect these changes.
  8. How Config Arrays work

    main

    A ConfigArray manages configurations based on glob pattern matching of filenames. It represents all configurations (including overrides) as a single array of config objects.

    Key behaviors:

    • Overrides: Config objects appearing later in the array override those appearing earlier.
    • Matching: Matching is determined by files and ignores properties.
    • Resolution: When retrieving a configuration for a file, the ConfigArray traverses the array to find all matching objects and merges them.
    • Generic Nature: This is a generic package and contains no ESLint-specific functionality; it can be used in any project.
    export default [
    	// match all JSON files
    	{
    		name: "JSON Handler",
    		files: ["**/*.json"],
    		handler: jsonHandler,
    	},
    
    	// match only package.json
    	{
    		name: "package.json Handler",
    		files: ["package.json"],
    		handler: packageJsonHandler,
    	},
    ];
  9. Use multidimensional Config Arrays and Config Functions

    main

    By setting extraConfigTypes in the ConfigArray options, you can expand the types of items allowed in your configuration array:

    • "array": Allows nested arrays. During normalization, these are flattened into the main array while preserving order.
    • "function": Allows config functions. A function accepts a context object and must return either a config object or a config array. Functions can be asynchronous. During normalization, functions are executed and replaced by their return values.

    Pattern matching variations in files:

    • Functions: If files contains a function, it is called with the file path. It must return true to match.
    • Logical AND: If files contains an array of patterns (e.g., ["*.test.*", "*.js"]), all patterns must match.
    • Negation: Patterns starting with ! exclude that pattern (e.g., "!*.js").
    export default [
    	// JS config
    	{
    		files: ["**/*.js"],
    		handler: jsHandler,
    	},
    
    	// JSON configs (Nested Array)
    	[
    		{
    			name: "JSON Handler",
    			files: ["**/*.json"],
    			handler: jsonHandler,
    		},
    		{
    			name: "package.json Handler",
    			files: ["package.json"],
    			handler: packageJsonHandler,
    		},
    	],
    
    	// Config Function
    	function (context) {
    		return [
    			{
    				name: context.name + " JSON Handler",
    				files: ["**/*.json"],
    				handler: jsonHandler,
    			},
    		];
    	},
    ];
  10. Require specific keys using the `requires` property

    main

    You can enforce that the presence of one key requires the presence of others by using the requires property. This property accepts an array of strings representing the names of the required keys.

    const schema = new ObjectSchema({
    	date: {
    		merge() {
    			return undefined;
    		},
    		validate(value) {
    			if (isNaN(Date.parse(value))) {
    				throw new Error("Invalid date.");
    			}
    		},
    	},
    	time: {
    		requires: ["date"],
    		merge(first, second) {
    			return second;
    		},
    		validate(value) {
    			// ...
    		},
    	},
    });
    
    // Throws error: Key "time" requires keys "date".
    schema.validate({ time: "13:45" });
  11. Use and target ESM in ESLint Rewrite packages

    main
    All packages within the eslint/eslint/rewrite repository are written in ECMAScript Modules (ESM). When publishing, packages preferentially provide ESM entrypoints and, where possible, also provide CommonJS (CJS) entrypoints to maintain compatibility. Developers working on or consuming these packages should expect an ESM-first environment.
  12. Use JavaScript and JSDoc with tsc for type safety

    main

    The ESLint Rewrite project uses JavaScript combined with JSDoc comments and tsc (TypeScript Compiler) to enforce type checking. This approach provides type safety while allowing the project to continue 'dogfooding' its own JavaScript-based utilities (like Espree and eslint-scope) instead of relying on typescript-eslint.

    Key implementation details:

    • Type Enforcement: tsc is used to check types defined via JSDoc.
    • Interfaces: TypeScript is used specifically for defining interfaces where it is more convenient than JSDoc, but not for implementing functionality.
    • Type Distribution: Each package within the rewrite repository publishes its own types.