ESLint 25 🛡️ Processors — Linting code inside Markdown or Vue files
A processor is the mechanism that lets ESLint lint code that is not inside a .js or .ts file. It is a plugin component that receives the text of a file—a Markdown document, a Vue single-file component, an HTML page—and returns the JavaScript or TypeScript fragments that should be linted as separate blocks . The processor is the bridge between the container format and the code that ESLint knows how to analyze. Without it, a code block inside a Markdown file is just text; with it, the block becomes a virtual file with its own extension, its own lint rules, and its own autofixes.
The processor contract has two mandatory methods and one optional flag. The preprocess method receives the original file text and returns an array of { text, filename } objects, where each object is a code block that ESLint should lint . The postprocess method receives a two-dimensional array of lint messages—one array per block—and must flatten it into a single array while adjusting the line and column numbers to point at the original file, not the extracted block . The supportsAutofix: true flag is what allows eslint --fix to apply corrections inside the processed blocks; without it, the fixes are computed but not written .
Key point: A processor extracts code blocks from a container file and returns them as virtual files with extensions that determine which lint rules apply. preprocess extracts; postprocess flattens and remaps locations; supportsAutofix enables --fix. The processor is configured with the processor property in a config object scoped by files.
Why processors are necessary
The container-format problem. ESLint parses JavaScript and TypeScript. It does not parse Markdown, Vue, or HTML. A README.md file that contains fenced JavaScript blocks is not a JavaScript file, and ESLint cannot lint it directly. The processor changes the unit of linting: the Markdown file is the container, and the fenced blocks are the units. The processor extracts the blocks, ESLint lints each block as if it were a separate file, and the processor maps the results back to the original document .
The virtual filename problem. The extracted block needs a filename that tells ESLint which rules to apply. The processor assigns a virtual filename based on the block’s syntax tag. A ```js block in README.md becomes README.md/*.js, and a ```typescript block becomes README.md/*.ts . The virtual filename is the mechanism that scopes the lint rules: a config object with files: ["**/*.md/*.js"] applies only to the JavaScript blocks inside Markdown files, not to regular .js files .
The location remapping problem. A lint error in an extracted block has a line number relative to the block, not to the original file. If the block starts at line 42 of the Markdown file, an error on line 3 of the block should be reported as line 45 of the file. The postprocess method is responsible for this remapping. The processor knows where each block started, and it adds the offset to the line numbers in the messages . Without the remapping, the editor’s “jump to error” feature points to the wrong line.
The trade-off. A processor adds a dependency and a configuration object. The eslint-plugin-markdown package is the standard processor for Markdown, and it ships a recommended config that enables the processor on **/*.md files . The Vue processor is part of eslint-plugin-vue, and it is configured through the plugin’s recommended config . The processor is necessary only when the code lives inside a container format, and the configuration is the cost of the container.
a. The Markdown processor
The eslint-plugin-markdown package provides the processor for Markdown files. The recommended config enables the processor on all .md files and disables the rules that are not meaningful in documentation contexts.
// eslint.config.js — the Markdown processor
import markdown from "eslint-plugin-markdown";
export default [
// The recommended config enables the processor on **/*.md.
// It also disables no-undef, no-unused-vars, and other rules
// that would flag variables declared outside the snippet.
...markdown.configs.recommended,
// The rest of the flat config follows.
];
The recommended config is the simplest setup. It enables the processor, applies the default rules to the extracted blocks, and disables the rules that produce false positives in documentation . The no-undef rule is disabled because a code snippet often references variables that are declared elsewhere; the no-unused-vars rule is disabled because a snippet is often incomplete. The eol-last and unicode-bom rules are disabled because the Markdown parser trims the trailing newlines and the BOM from the extracted blocks .
The manual configuration is the pattern for a project that wants to customize the rules for the Markdown blocks. The processor property is set on a config object scoped by files: ["**/*.md"], and the virtual filename glob is used for the block-specific rules.
// eslint.config.js — the manual Markdown processor configuration
import markdown from "eslint-plugin-markdown";
export default [
{
files: ["**/*.md"],
plugins: { markdown },
// The processor is referenced as "plugin-name/processor-name".
processor: "markdown/markdown",
},
{
// The virtual filename glob targets the JS blocks in Markdown.
files: ["**/*.md/*.js"],
rules: {
"no-console": "off",
},
},
];
The "markdown/markdown" string is the plugin name and the processor name separated by a slash. The "**/*.md/*.js" glob matches the virtual filenames that the processor assigns to the JavaScript blocks. The rules in that config object apply only to the blocks, not to regular .js files .
b. The Vue processor and the parser distinction
The Vue processor is part of eslint-plugin-vue, but it works differently from the Markdown processor. The Vue parser (vue-eslint-parser) parses the entire single-file component and produces a single AST that contains both the template and the script. The Vue rules traverse the template AST, and the standard ESLint rules traverse the script AST . The processor is not a separate extraction step in the same way as the Markdown processor; the parser is the mechanism that makes the .vue file lintable.
// eslint.config.js — the Vue plugin with the recommended config
import pluginVue from "eslint-plugin-vue";
export default [
// The recommended config includes the Vue parser and the rules.
...pluginVue.configs["flat/recommended"],
{
files: ["**/*.vue"],
languageOptions: {
// The parser is vue-eslint-parser.
// It is set by the recommended config.
},
},
];
The eslint-plugin-vue recommended config sets the parser to vue-eslint-parser and applies the Vue-specific rules to the template. The script block is linted by the standard ESLint rules and by the Vue rules that apply to the script . The processor is not a separate configuration step for the common case; the plugin’s config handles the parser and the rules together.
The distinction matters for a project that uses TypeScript in the script block. The vue-eslint-parser delegates the script parsing to @typescript-eslint/parser, and the parserOptions.parser property is the delegation mechanism.
// eslint.config.js — the Vue parser with TypeScript delegation
import pluginVue from "eslint-plugin-vue";
import tsParser from "@typescript-eslint/parser";
export default [
...pluginVue.configs["flat/recommended"],
{
files: ["**/*.vue"],
languageOptions: {
parserOptions: {
// The script block is parsed by the TypeScript parser.
parser: tsParser,
},
},
},
];
The parserOptions.parser property is the same delegation pattern that the vue-eslint-parser uses for the script block. The Vue parser handles the template, and the TypeScript parser handles the script .
Complete Example Session
// ============================================
// PART 1: THE MARKDOWN PROCESSOR
// ============================================
// eslint.config.js
import markdown from "eslint-plugin-markdown";
export default [
...markdown.configs.recommended,
];
// The recommended config:
// - Enables the processor on **/*.md
// - Disables no-undef, no-unused-vars, no-unused-expressions
// - Disables eol-last and unicode-bom
// - Enables impliedStrict
// ============================================
// PART 2: THE MARKDOWN MANUAL CONFIG
// ============================================
import markdown from "eslint-plugin-markdown";
export default [
{
files: ["**/*.md"],
plugins: { markdown },
processor: "markdown/markdown",
},
{
files: ["**/*.md/*.js"],
rules: { "no-console": "off" },
},
];
// ============================================
// PART 3: THE VIRTUAL FILENAME
// ============================================
// A ```js block in README.md becomes README.md/*.js.
// A ```typescript block in CONTRIBUTING.md becomes
// CONTRIBUTING.md/*.ts.
// The glob "**/*.md/*.js" matches the JavaScript blocks.
// The glob "**/*.md/*.ts" matches the TypeScript blocks.
// ============================================
// PART 4: THE VUE PLUGIN
// ============================================
// eslint.config.js
import pluginVue from "eslint-plugin-vue";
export default [
...pluginVue.configs["flat/recommended"],
];
// The recommended config sets the parser to vue-eslint-parser.
// The Vue rules traverse the template.
// The standard rules traverse the script.
// ============================================
// PART 5: THE VUE TYPESCRIPT DELEGATION
// ============================================
import pluginVue from "eslint-plugin-vue";
import tsParser from "@typescript-eslint/parser";
export default [
...pluginVue.configs["flat/recommended"],
{
files: ["**/*.vue"],
languageOptions: {
parserOptions: { parser: tsParser },
},
},
];
// ============================================
// PART 6: THE AUTOFIX
// ============================================
// The Markdown processor supports autofix.
// eslint --fix . fixes the fenced code blocks.
//
// The processor's supportsAutofix: true flag
// is what enables the fixes to be written.
// ============================================
// PART 7: THE PROCESSOR CONTRACT
// ============================================
// A custom processor must implement:
// - preprocess(text, filename) => { text, filename }[]
// - postprocess(messages, filename) => Message[]
// - supportsAutofix: boolean (optional)
// ============================================
// PART 8: THE LOCATION REMAPPING
// ============================================
// The postprocess method remaps the line numbers.
// An error on line 3 of a block that starts at line 42
// is reported as line 45.
// The processor is responsible for the offset.
// ============================================
// PART 9: THE VUE PARSER VS PROCESSOR
// ============================================
// The Vue parser parses the whole SFC into one AST.
// The Vue rules traverse the template part.
// The standard rules traverse the script part.
// The processor is not a separate extraction step
// for the common Vue case.
// ============================================
// PART 10: THE VERIFICATION
// ============================================
// npx eslint . --print-config README.md
// npx eslint . --print-config Component.vue
//
// The command prints the resolved config.
// It shows the processor and the parser that apply.
The ten parts covered the Markdown processor, the manual config, the virtual filename, the Vue plugin, the Vue TypeScript delegation, the autofix, the processor contract, the location remapping, the Vue parser distinction, and the verification. The through-line is that the processor extracts code from a container, and the virtual filename scopes the rules.
Quick Reference
The processor methods
| Method | Signature | Purpose |
|---|---|---|
preprocess | (text, filename) => Array<{ text, filename }> | Extracts the code blocks |
postprocess | (messages, filename) => Message[] | Flattens and remaps the locations |
supportsAutofix | boolean | Enables --fix on the blocks |
The Markdown plugin
| Aspect | Detail |
|---|---|
| Package | eslint-plugin-markdown |
| Recommended config | markdown.configs.recommended |
| Processor name | "markdown/markdown" |
| Virtual filename | README.md/*.js |
| Autofix | Supported |
The Vue plugin
| Aspect | Detail |
|---|---|
| Package | eslint-plugin-vue |
| Recommended config | pluginVue.configs["flat/recommended"] |
| Parser | vue-eslint-parser |
| Template rules | Provided by the plugin |
| Script parser | Delegated via parserOptions.parser |
Best Practices
✅ Do This:
// Use the recommended config for Markdown
...markdown.configs.recommended // ✅
// Use the virtual filename glob for block-specific rules
files: ["**/*.md/*.js"] // ✅
// Delegate the Vue script to the TypeScript parser
parserOptions: { parser: tsParser } // ✅
// Implement postprocess for a custom processor
postprocess(messages) { return [].concat(...messages); } // ✅
❌ Don’t Do This:
// Lint Markdown without a processor
// The blocks are not JavaScript; ESLint cannot parse them. // ❌
// Apply rules to the whole .md file
files: ["**/*.md"] // the file is not JavaScript // ❌
// Omit the location remapping in postprocess
// The errors point to the wrong lines. // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Markdown blocks are not linted | The processor is not configured | Add ...markdown.configs.recommended |
| The error line number is wrong | The postprocess does not remap | Add the offset in postprocess |
| The Vue template is not linted | The parser is not set | Use the Vue recommended config |
| The Markdown block uses TypeScript | The virtual filename is .js | Use ```typescript and **/*.md/*.ts |
Real-World Examples
1. Markdown recommended config
import markdown from "eslint-plugin-markdown"; ...markdown.configs.recommended
2. Markdown manual config
files: ["**/*.md"], plugins: { markdown }, processor: "markdown/markdown"
3. Markdown block rules
files: ["**/*.md/*.js"], rules: { "no-console": "off" }
4. Vue recommended config
import pluginVue from "eslint-plugin-vue"; ...pluginVue.configs["flat/recommended"]
5. Vue TypeScript delegation
parserOptions: { parser: tsParser }
6. The virtual filename
README.md/*.js
7. The preprocess method
preprocess(text, filename) { return [{ text: code, filename: "0.js" }]; }
8. The postprocess method
postprocess(messages) { return [].concat(...messages); }
9. The autofix flag
supportsAutofix: true
10. The verification
npx eslint . --print-config README.md
Visual
The Processor Flow
┌──────────────────────────────────────────────────────────────┐
│ CONTAINER → BLOCKS → LINT → REMAP → REPORT │
│ │
│ README.md │
│ ┌─────────────────────┐ │
│ │ # Title │ │
│ │ │ │
│ │ ```js │ │
│ │ const x = 1; │ ← extracted block │
│ │ ``` │ │
│ └──────────┬──────────┘ │
│ │ preprocess() │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ { text, filename } │ "README.md/*.js" │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ ESLint lints the │ │
│ │ block as a file │ │
│ └──────────┬──────────┘ │
│ │ postprocess() │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Messages with the │ Line 3 of the block becomes │
│ │ original locations │ line 5 of the file │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
The Virtual Filename
┌──────────────────────────────────────────────────────────────┐
│ THE BLOCK EXTENSION DETERMINES THE RULES │
│ │
│ README.md │
│ ┌─────────────────────┐ │
│ │ ```js │ → README.md/*.js │
│ │ ```typescript │ → README.md/*.ts │
│ │ ```javascript │ → README.md/*.js │
│ └─────────────────────┘ │
│ │
│ The config glob "**/*.md/*.js" matches the JS blocks. │
│ The config glob "**/*.md/*.ts" matches the TS blocks. │
└──────────────────────────────────────────────────────────────┘
Vue vs Markdown
┌──────────────────────────────────────────────────────────────┐
│ THE VUE PARSER VS THE MARKDOWN PROCESSOR │
│ │
│ Vue: Markdown: │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ One AST for the │ │ Multiple virtual │ │
│ │ whole SFC │ │ files extracted │ │
│ │ │ │ │ │
│ │ Template rules │ │ Each block is a │ │
│ │ traverse the │ │ separate lint unit │ │
│ │ template AST │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Processor purpose | Extract code blocks from container files |
preprocess | Returns the blocks |
postprocess | Flattens and remaps the messages |
supportsAutofix | Enables --fix |
| Markdown plugin | eslint-plugin-markdown |
| Markdown config | markdown.configs.recommended |
| Vue plugin | eslint-plugin-vue |
| Vue parser | vue-eslint-parser |
| Virtual filename | README.md/*.js |
Key takeaways:
- A processor extracts code blocks from a container file. The
preprocessmethod returns an array of{ text, filename }objects, and thepostprocessmethod flattens the messages and remaps the locations to the original file . - The virtual filename scopes the lint rules. A
```jsblock inREADME.mdbecomesREADME.md/*.js, and a config object withfiles: ["**/*.md/*.js"]applies only to those blocks, not to regular.jsfiles . - The Markdown plugin ships a recommended config.
eslint-plugin-markdownprovidesmarkdown.configs.recommended, which enables the processor and disables the rules that produce false positives in documentation contexts . - The Vue plugin uses a parser, not a processor, for the common case.
eslint-plugin-vuesets the parser tovue-eslint-parser, which parses the whole SFC into a single AST. The template rules traverse the template part, and the standard rules traverse the script part . - The Vue script can delegate to the TypeScript parser. The
parserOptions.parserproperty tellsvue-eslint-parserto delegate the script block to@typescript-eslint/parser, which is the pattern for a Vue project that uses TypeScript .
Remember: A processor is the bridge between a container format and the code inside it. The preprocess method extracts the blocks, the virtual filename determines the rules, and the postprocess method remaps the locations. The Markdown plugin is the canonical example, and its recommended config is the simplest setup. The Vue plugin is the different case: the parser handles the whole SFC, and the processor is not a separate extraction step for the common configuration. The processor is necessary only when the code lives inside a container, and the configuration is the cost of the container.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!