eslint-plugin-diff-flat-config

Run ESLint on your changed lines only. ESLint 9+ flat config support.
This is a fork of eslint-plugin-diff by Patrick Eriksson, modernized to support ESLint's flat configuration format.
Table of Contents
Why Use This Plugin?
The Problem
When adopting new ESLint rules in a large codebase, you face a dilemma:
- Lint entire files: Developers get overwhelmed with hundreds of pre-existing errors they didn't introduce
- Skip linting: New violations slip through code review
The Solution
This plugin filters ESLint output to show only errors on lines you've actually changed. This means:
- Gradual adoption: Introduce new rules without blocking on existing violations
- Focused feedback: Developers see only issues they created
- Reduced noise: No more drowning in a sea of legacy linter errors
Benefits
| Protect your budget | Avoid costly refactoring when updating linter rules or dependencies |
| Boost velocity | Keep your team productive without overwhelming error lists |
| Maintain focus | All linter output is directly relevant to the current changes |
| Easy rule adoption | Roll out new ESLint rules incrementally across your codebase |
Requirements
| ESLint | >= 9.0.0 |
| Node.js | >= 18.0.0 |
| Git | Any modern version |
Installation
npm install --save-dev eslint eslint-plugin-diff-flat-config
yarn add -D eslint eslint-plugin-diff-flat-config
pnpm add -D eslint eslint-plugin-diff-flat-config
Usage
This plugin provides three configurations for different use cases. Add them to your ESLint flat config:
diff.configs.diff (recommended for CI)
Lint only the lines that have changed compared to a base commit/branch. This is ideal for CI pipelines where you want to lint changes in a pull request.
import diff from "eslint-plugin-diff-flat-config";
export default [
diff.configs.diff,
];
When to use: CI pipelines, pull request checks
diff.configs.staged (recommended for pre-commit hooks)
Lint only the lines that are staged for commit (git add). Perfect for use with pre-commit hooks and lint-staged.
import diff from "eslint-plugin-diff-flat-config";
export default [
diff.configs.staged,
];
When to use: Pre-commit hooks, local development
diff.configs.ci
A smart CI-aware configuration that:
- In CI environments (when
CI env var is set): Works like diff.configs.diff
- Locally (when
CI is not set): Does nothing (allows normal linting)
This is useful when you want a single config file that behaves differently in CI vs local development.
import diff from "eslint-plugin-diff-flat-config";
export default [
diff.configs.ci,
];
When to use: Shared config files that work both locally and in CI
Usage with typescript-eslint
The plugin works seamlessly with typescript-eslint:
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import diff from "eslint-plugin-diff-flat-config";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
rules: {
},
},
diff.configs.diff
);
CI Setup
To lint all changes in a pull request, set the ESLINT_PLUGIN_DIFF_COMMIT environment variable to the base branch before running ESLint.
GitHub Actions
name: Lint
on:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint on changed lines
env:
ESLINT_PLUGIN_DIFF_COMMIT: origin/${{ github.base_ref }}
run: npx eslint .
GitLab CI
lint:
stage: test
script:
- npm ci
- export ESLINT_PLUGIN_DIFF_COMMIT="origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
- npx eslint .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
BitBucket Pipelines
pipelines:
pull-requests:
"**":
- step:
name: Lint
script:
- npm ci
- export ESLINT_PLUGIN_DIFF_COMMIT="origin/$BITBUCKET_PR_DESTINATION_BRANCH"
- npx eslint .
Jenkins
pipeline {
agent any
stages {
stage('Lint') {
steps {
sh 'npm ci'
withEnv(["ESLINT_PLUGIN_DIFF_COMMIT=origin/${env.CHANGE_TARGET}"]) {
sh 'npx eslint .'
}
}
}
}
}
Pre-commit Hook Setup
Use with lint-staged to lint only staged lines before each commit.
1. Install lint-staged and husky
npm install --save-dev lint-staged husky
npx husky init
2. Configure lint-staged in package.json
{
"lint-staged": {
"*.{js,ts,tsx,jsx}": "eslint --config eslint.config.staged.mjs --fix"
}
}
3. Create a staged-specific ESLint config
import baseConfig from "./eslint.config.mjs";
import diff from "eslint-plugin-diff-flat-config";
export default [
...baseConfig,
diff.configs.staged,
];
4. Add the pre-commit hook
echo "npx lint-staged" > .husky/pre-commit
Environment Variables
ESLINT_PLUGIN_DIFF_COMMIT | Base commit/branch for diff comparison. Accepts any valid git ref (branch name, commit SHA, tag, etc.) | HEAD |
CI | When set to any value, enables the ci processor. Most CI providers set this automatically. | - |
VSCODE_PID | When set (by VS Code), files are always processed regardless of diff status to ensure real-time linting works. | - |
How It Works
-
Preprocessor: Determines which files have changes. Unchanged files are skipped entirely for performance.
-
Git Diff: Runs git diff to identify which line numbers have been modified:
diff mode: git diff HEAD (all uncommitted changes)
staged mode: git diff HEAD --staged (only staged changes)
-
Postprocessor: After ESLint runs, filters the lint messages to keep only those on changed lines.
-
Output: Only violations on lines you've actually modified are reported.
Edge Cases
The plugin handles these edge cases:
| New/untracked files | Fully linted (all lines) |
| Renamed files | Changes are tracked correctly |
| Binary files | Skipped |
| Deleted files | Not linted |
| Partially staged files | In staged mode, reports an error if a file has both staged and unstaged changes |
| Files outside git repo | Fully linted |
Migrating from eslint-plugin-diff
This package is a fork of the original eslint-plugin-diff v2.x, modernized for ESLint's flat config format.
Breaking Changes
| ESLint version | >= 6.7.0 | >= 9.0.0 |
| Node.js version | >= 14.0.0 | >= 18.0.0 |
| Config format | .eslintrc (legacy) | eslint.config.mjs (flat) |
| Package name | eslint-plugin-diff | eslint-plugin-diff-flat-config |
Migration Steps
Before (ESLint 8 with .eslintrc.json):
{
"extends": ["plugin:diff/diff"]
}
After (ESLint with eslint.config.mjs):
import diff from "eslint-plugin-diff-flat-config";
export default [
diff.configs.diff,
];
Troubleshooting
"File has unstaged changes" error
When using staged mode, if a file has both staged and unstaged changes, the plugin cannot reliably determine which lines to lint. Either:
- Stage all changes:
git add <file>
- Stash unstaged changes:
git stash -k
No output / all files skipped
Ensure you have changes to lint:
- For
diff mode: Make changes to tracked files
- For
staged mode: Stage changes with git add
CI not detecting changes
Make sure:
fetch-depth: 0 is set (GitHub Actions) to fetch full git history
ESLINT_PLUGIN_DIFF_COMMIT points to a valid ref that exists
- The base branch has been fetched:
git fetch origin main
VS Code real-time linting not working
The plugin automatically detects VS Code and processes all files to ensure real-time linting works. If issues persist, check that the ESLint extension is using the correct config file.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT - see LICENSE.md
Original Author: Patrick Eriksson (eslint-plugin-diff)
Fork Maintainer: kirlev