Skip to content

Conversation

@CyberT33N
Copy link

@CyberT33N CyberT33N commented Dec 4, 2025

Summary

This PR adds an opt-in capability for the Markdown processor to materialize fenced code blocks as real temporary files on disk, in addition to the existing virtual child file behavior. It introduces a small runtime configuration API to control this behavior and documents how to integrate it, especially for TypeScript typed-linting scenarios. The default behavior remains unchanged for all existing users.

Motivation

Typed linting via @typescript-eslint/parser with project / projectService requires real files on disk and cannot operate on ESLint’s virtual child files (e.g. file.md/0.ts). Today, @eslint/markdown only provides in-memory virtual children, which limits typed linting for Markdown and .mdc examples. This change introduces an optional materialization mode so that consumers who need typed linting can map Markdown code blocks to stable .ts files on disk (e.g. under .eslint-markdown-temp) while keeping the default behavior unchanged for everyone else.

This PR is designed to be used together with a small core change in eslint/eslint (see https://github.com/eslint/eslint/pull/20375).

  • In @eslint/markdown, this PR adds an opt‑in mode that materializes fenced code blocks as real temp files on disk and returns them to ESLint as children with both a virtual filename and a real physicalFilename.
  • In eslint/eslint, the companion PR teaches the processor pipeline and the JS language to:
    • accept an optional physicalFilename from processors for each child file,
    • thread it through to VFile.physicalPath, and
    • pass it as parserOptions.filePath to the parser while still using the virtual child filename for reporting.

Together, these changes allow tools like @typescript-eslint/parser (with project / projectService) to typecheck Markdown/MDX/.mdc code blocks via the materialized temp files, while keeping the existing virtual child behavior fully intact for all current users.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🔧 Refactoring (no functional changes, no api changes)

Detailed Changes

  • src/processor.js:

    • Introduced a MarkdownProcessorOptions typedef with two optional fields:
      • materializeCodeBlocks?: boolean – when true, fenced code blocks are written to temp files on disk as well as returned as virtual children.
      • tempDir?: string – optional base directory for materialized files.
    • Added a module-local processorOptions object to hold runtime options, defaulting to:
      • materializeCodeBlocks: false
      • tempDir: undefined
    • Implemented helper functions:
      • getMaterializeBaseDir() to resolve the base directory for materialized files, using either tempDir or path.join(os.tmpdir(), "eslint-markdown").
      • getMaterializedFilePath(markdownFilename, index, virtualFilename) to compute a deterministic on-disk path of the form <baseDir>/<sanitizedMarkdownPath>/<index>_<virtualFilename>.
      • materializeCodeBlock(markdownFilename, index, virtualFilename, text) to mkdir recursively and write the block contents to disk.
    • Extended preprocess() to:
      • Compute the existing virtualFilename as before (including filename="..." meta support).
      • Conditionally call materializeCodeBlock(...) when processorOptions.materializeCodeBlocks is true.
      • Return code block descriptors of the form { filename, text, physicalFilename? }, where physicalFilename is only present when materialization is enabled; this preserves full backwards compatibility for existing consumers.
    • Kept all existing mapping and postprocessing behavior (range maps, fix adjustments, unsatisfiable-rule filtering) unchanged.
  • src/index.js:

    • Exported the new configuration function as a named export:
      • export { setMarkdownProcessorOptions } (alongside the existing default plugin export).
    • This allows consumers to configure materialization from their eslint.config.* before running ESLint:
      • import markdown, { setMarkdownProcessorOptions } from "@eslint/markdown";.
  • Runtime configuration API (in src/processor.js):

    • Added export function setMarkdownProcessorOptions(options = {}):
      • Validates options.materializeCodeBlocks is a boolean (or undefined) and updates processorOptions.materializeCodeBlocks when provided.
      • Validates options.tempDir is either a string or undefined and sets processorOptions.tempDir accordingly.
      • Throws descriptive Errors when invalid values are passed, to fail fast on misconfiguration.
  • docs/processors/markdown.md:

    • Added a new section “Materializing code blocks as real files (optional)” that:
      • Explains the current virtual child file behavior and what materialization adds.
      • Shows a full configuration example using setMarkdownProcessorOptions:
        • Enabling materializeCodeBlocks: true.
        • Setting tempDir: ".eslint-markdown-temp" for a project-local temp directory.
      • States that when tempDir is not provided, the base directory defaults to a subdirectory of the OS temp directory (os.tmpdir()/eslint-markdown).
      • Strongly recommends, for TypeScript project services and long-lived setups, using a project-local directory (e.g. .eslint-markdown-temp), adding it explicitly to tsconfig include/files when necessary, and ignoring it in version control (e.g. .gitignore).
      • Explicitly discourages using absolute OS-specific temp paths (e.g. C:/Users/<user>/AppData/Local/Temp/eslint-markdown/**) in tsconfig, calling out their brittleness across machines, users, and CI environments.
  • README.md:

    • Added an “Materializing code blocks as temp files (advanced)” subsection under the processors section.
    • Documented the high-level intent and trade-offs of temp-file materialization.
    • Included an end-to-end eslint.config.js example using:
      • import markdown, { setMarkdownProcessorOptions } from "@eslint/markdown";
      • setMarkdownProcessorOptions({ materializeCodeBlocks: true, tempDir: ".eslint-markdown-temp" });
      • A simple processor configuration for files: ["**/*.md"].
  • Build artifacts (dist/esm/):**

    • Regenerated dist/esm/processor.js, dist/esm/index.js, and related type declarations via the existing npm run build pipeline so that published artifacts reflect the new behavior and exports.
  • Tests & Linting:

    • Ensured the existing Mocha test suite (npm test) passes with the new behavior.
    • Ran ESLint (npm run lint) to confirm there are no new lint errors introduced by these changes (only pre-existing fixture warnings remain).

Testing & Verification

  • Unit Tests added/passed
    • npm test executed successfully after the changes.
  • Manual verification steps:
    • Configure setMarkdownProcessorOptions({ materializeCodeBlocks: true, tempDir: ".eslint-markdown-temp" }) in a consumer project’s eslint.config.*.
    • Run ESLint on Markdown/MDX files containing fenced TypeScript code blocks.
    • Confirm that:
      • Temp .ts files are created under .eslint-markdown-temp/<sanitized-markdown-path>/<index>_<virtualFilename>.ts.
      • Existing virtual child paths (file.md/0.ts, file.mdc/0_0.ts, etc.) continue to be produced as before.
    • Optionally, add .eslint-markdown-temp/**/*.ts to tsconfig include and verify that a TypeScript project service can resolve types for the materialized code blocks.

Breaking Changes (if any)

N/A – this feature is fully opt-in:

  • materializeCodeBlocks defaults to false.
  • The processor continues to return the same virtual filenames and text by default.
  • The new physicalFilename field is optional and only present when materialization is enabled.

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Dec 4, 2025

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: d-demand-priv / name: Dennis Demand (ffcbcac)

@eslint-github-bot
Copy link

Hi @CyberT33N!, thanks for the Pull Request

The pull request title isn't properly formatted. We ask that you update the pull request title to match this format, as we use it to generate changelogs and automate releases.

  • The commit message tag wasn't recognized. Did you mean "docs", "fix", or "feat"?
  • There should be a space following the initial tag and colon, for example 'feat: Message'.
  • The length of the commit message must be less than or equal to 72

To Fix: You can fix this problem by clicking 'Edit' next to the pull request title at the top of this page.

Read more about contributing to ESLint here

@eslintbot eslintbot added this to Triage Dec 4, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Dec 4, 2025
@CyberT33N CyberT33N changed the title feat(processor): add optional temp-file materialization for markdown code blocks feat: add optional temp-file materialization for markdown code blocks Dec 4, 2025
@mdjermanovic
Copy link
Member

Closing for reasons described in eslint/eslint#20375 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

3 participants