<p align="center">
  <a href="https://eslint.org"><img src="https://eslint.org/icon.svg" height="50"></a>
  <a href="#readme"><img src="https://rx-ts.github.io/assets/heart.svg" height="50"></a>
  <a href="https://github.com/mdx-js/mdx"><img src="https://avatars.githubusercontent.com/u/37453691"  height="50"></a>
</p>

[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/mdx-js/eslint-mdx/ci.yml?branch=master)](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml?query=branch%3Amaster)
[![Codecov](https://img.shields.io/codecov/c/gh/mdx-js/eslint-mdx)](https://codecov.io/gh/mdx-js/eslint-mdx)
[![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fmdx-js%2Feslint-mdx%2Fmaster%2Fpackage.json)](https://github.com/plantain-00/type-coverage)
[![GitHub release](https://img.shields.io/github/release/mdx-js/eslint-mdx)](https://github.com/mdx-js/eslint-mdx/releases)

[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com)
[![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![changesets](https://img.shields.io/badge/maintained%20with-changesets-176de3.svg)](https://github.com/changesets/changesets)

> [ESLint][] Parser/Plugin for [MDX][], helps you lint all ES syntaxes.
> Linting `code` blocks can be enabled with `mdx/code-blocks` setting too!
> Work perfectly with `eslint-plugin-import`, `eslint-plugin-prettier` or any other eslint plugins.
> And also can be integrated with [remark-lint][] plugins to lint markdown syntaxes.

## TOC <!-- omit in toc -->

- [VSCode Extension](#vscode-extension)
- [Packages](#packages)
- [Install](#install)
- [Notice](#notice)
- [Usage](#usage)
  - [Classic Config](#classic-config)
  - [Flat Config](#flat-config)
- [Parser Options](#parser-options)
- [Parser API](#parser-api)
  - [Markdown content nodes](#markdown-content-nodes)
  - [MDX expression nodes](#mdx-expression-nodes)
  - [Typings](#typings)
- [Rules](#rules)
  - [mdx/remark](#mdxremark)
- [Prettier Integration](#prettier-integration)
- [Sponsors and Backers](#sponsors-and-backers)
  - [Sponsors](#sponsors)
  - [Backers](#backers)
- [Changelog](#changelog)
- [License](#license)

## VSCode Extension

[![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/unifiedjs.vscode-mdx)](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx)

[VSCode MDX][]: Integrates with [VSCode ESLint][], syntaxes highlighting and error reporting.

## Packages

This repository is a monorepo managed by [changesets][] what means we actually publish several packages to npm from same codebase, including:

| Package                                            | Description                                    | Version                                                                                                       |
| -------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [`eslint-mdx`](/packages/eslint-mdx)               | ESLint Parser for MDX                          | [![npm](https://img.shields.io/npm/v/eslint-mdx.svg)](https://www.npmjs.com/package/eslint-mdx)               |
| [`eslint-plugin-mdx`](/packages/eslint-plugin-mdx) | ESLint Plugin, Configuration and Rules for MDX | [![npm](https://img.shields.io/npm/v/eslint-plugin-mdx.svg)](https://www.npmjs.com/package/eslint-plugin-mdx) |

## Install

```sh
# yarn
yarn add -D eslint-plugin-mdx

# npm
npm i -D eslint-plugin-mdx
```

## Notice

1. If you're using multi languages, `js/jsx/ts/tsx/vue`, etc for example, you'd better to always use [`overrides`](https://eslint.org/docs/user-guide/configuring/configuration-files#how-do-overrides-work) ([Classic Config](#classic-config)) or [`files`](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores) ([Flag Config](#flat-config)) feature of ESLint, because configs may be overridden by following configs.

   See [#251](https://github.com/mdx-js/eslint-mdx/issues/251#issuecomment-736139224) for more details.

2. If you're using `{/* eslint-disable-line mdx/remark */}` with `prettier`, this won't work because `prettier` will add a blank line after the comment, which makes it invalid. You can use `{/* eslint-disable mdx/remark */}` paired with `{/* eslint-enable mdx/remark */}` instead:

   <!-- eslint-skip -->

   ```mdx
   {/* eslint-disable mdx/remark */}

   # Heading

   {/* eslint-enable mdx/remark */}
   ```

## Usage

### Classic Config

`.eslintrc` file:

```jsonc
{
  "extends": ["plugin:mdx/recommended"],
  // optional, if you want to lint code blocks at the same time
  "settings": {
    "mdx/code-blocks": true,
    // optional, if you want to disable language mapper, set it to `false`
    // if you want to override the default language mapper inside, you can provide your own
    "mdx/language-mapper": {},
    // optional, same as the `parserOptions.ignoreRemarkConfig`, you have to specify it twice unfortunately
    "mdx/ignore-remark-config": true,
    // optional, same as the `parserOptions.remarkConfigPath`, you have to specify it twice unfortunately
    "mdx/remark-config-path": "path/to/your/remarkrc",
  },
}
```

### Flat Config

`eslint.config.js` file:

```js
import * as mdx from 'eslint-plugin-mdx'

export default [
  {
    ...mdx.flat,
    // optional, if you want to lint code blocks at the same
    processor: mdx.createRemarkProcessor({
      lintCodeBlocks: true,
      // optional, if you want to disable language mapper, set it to `false`
      // if you want to override the default language mapper inside, you can provide your own
      languageMapper: {},
      // optional, same as the `parserOptions.ignoreRemarkConfig`, you have to specify it twice unfortunately
      ignoreRemarkConfig: true,
      // optional, same as the `parserOptions.remarkConfigPath`, you have to specify it twice unfortunately
      remarkConfigPath: 'path/to/your/remarkrc',
    }),
  },
  {
    ...mdx.flatCodeBlocks,
    rules: {
      ...mdx.flatCodeBlocks.rules,
      // if you want to override some rules for code blocks
      'no-var': 'error',
      'prefer-const': 'error',
    },
  },
]
```

Then, make sure ESLint knows to run on `.md` or `.mdx` files:

```sh
eslint . --ext js,md,mdx
```

## Parser Options

1. `extensions` (`string | string[]`): `eslint-mdx` will only resolve `.mdx` files by default, if you want to resolve other extensions as like `.mdx`, you can use this option.

2. `markdownExtensions` (`string | string[]`): `eslint-mdx` will only treat `.md` files as plain markdown by default, and will lint them via remark plugins. If you want to resolve other extensions as like `.md`, you can use this option.

3. `ignoreRemarkConfig` (`boolean`): Ignore the `remark` configuration defined in the project.

4. `remarkConfigPath` (`string`): Specify the path to the `remark` configuration file, could be relative to `CWD` or absolute path.

## Parser API

`eslint-mdx` returns an ESTree `Program`. In addition to standard ESTree and JSX nodes, it emits custom nodes for Markdown content and MDX expressions.

### Markdown content nodes

Markdown content uses the `MD` prefix, while MDX-specific nodes use `MDX`. The prefix avoids collisions with ESTree/JSX nodes. All nodes are exposed in `Program.body` or as children of other nodes and are traversed through the parser visitor keys.

| ESTree node            | mdast node           | Notes                |
| ---------------------- | -------------------- | -------------------- |
| `MDBlockquote`         | `blockquote`         |                      |
| `MDBreak`              | `break`              |                      |
| `MDCode`               | `code`               |                      |
| `MDDefinition`         | `definition`         |                      |
| `MDDelete`             | `delete`             | `remark-gfm`         |
| `MDEmphasis`           | `emphasis`           |                      |
| `MDFootnoteDefinition` | `footnoteDefinition` | `remark-gfm`         |
| `MDFootnoteReference`  | `footnoteReference`  | `remark-gfm`         |
| `MDHeading`            | `heading`            |                      |
| `MDImage`              | `image`              |                      |
| `MDImageReference`     | `imageReference`     |                      |
| `MDInlineCode`         | `inlineCode`         |                      |
| `MDInlineMath`         | `inlineMath`         | `remark-math`        |
| `MDLink`               | `link`               |                      |
| `MDLinkReference`      | `linkReference`      |                      |
| `MDList`               | `list`               |                      |
| `MDListItem`           | `listItem`           |                      |
| `MDMath`               | `math`               | `remark-math`        |
| `MDParagraph`          | `paragraph`          |                      |
| `MDStrong`             | `strong`             |                      |
| `MDTable`              | `table`              | `remark-gfm`         |
| `MDTableCell`          | `tableCell`          | `remark-gfm`         |
| `MDTableRow`           | `tableRow`           | `remark-gfm`         |
| `MDText`               | `text`               |                      |
| `MDThematicBreak`      | `thematicBreak`      |                      |
| `MDToml`               | `toml`               | `remark-frontmatter` |
| `MDYaml`               | `yaml`               | `remark-frontmatter` |

For example:

````mdx
# Heading

Paragraph with **strong**, _emphasis_, `inlineCode`, [link](https://example.com), and ![image](https://example.com/image.png).

> Blockquote

- list item

```js
const value = 1
```
````

> `MDXCode` and `MDXHeading` were renamed to `MDCode` and `MDHeading`; the old names are no longer emitted.

### MDX expression nodes

MDX expressions use the `MDX` prefix and expose their ESTree `expression`:

| ESTree node         | mdast node          |
| ------------------- | ------------------- |
| `MDXFlowExpression` | `mdxFlowExpression` |
| `MDXTextExpression` | `mdxTextExpression` |

When these expressions are used as JSX children, they are wrapped in `JSXExpressionContainer`. Empty or comment-only expressions use `JSXEmptyExpression`.

For example:

```mdx
{value}

> {value}

The value is {value}.
```

### Typings

```ts
import type { BaseNode, Expression } from 'estree'
import type {
  JSXElement,
  JSXEmptyExpression,
  JSXExpressionContainer,
  JSXFragment,
  JSXSpreadChild,
  JSXText,
} from 'estree-jsx'

export interface MDCode extends BaseNode {
  type: 'MDCode'
  value: string
  lang?: string | null
  meta?: string | null
}

export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6

export interface MDHeading extends BaseNode {
  type: 'MDHeading'
  depth: HeadingDepth
  children: MDContentNode[]
}

export interface MDXFlowExpression extends BaseNode {
  type: 'MDXFlowExpression'
  expression: Expression | JSXEmptyExpression
}

export interface MDXTextExpression extends BaseNode {
  type: 'MDXTextExpression'
  expression: Expression | JSXEmptyExpression
}

export interface MDText extends BaseNode {
  type: 'MDText'
  value: string
}

export interface MDParagraph extends BaseNode {
  type: 'MDParagraph'
  children: MDContentNode[]
}

export interface MDBlockquote extends BaseNode {
  type: 'MDBlockquote'
  children: MDContentNode[]
}

export interface MDList extends BaseNode {
  type: 'MDList'
  ordered?: boolean | null
  start?: number | null
  spread?: boolean | null
  children: MDListItem[]
}

export interface MDListItem extends BaseNode {
  type: 'MDListItem'
  checked?: boolean | null
  spread?: boolean | null
  children: MDContentNode[]
}

export interface MDEmphasis extends BaseNode {
  type: 'MDEmphasis'
  children: MDContentNode[]
}

export interface MDStrong extends BaseNode {
  type: 'MDStrong'
  children: MDContentNode[]
}

export interface MDInlineCode extends BaseNode {
  type: 'MDInlineCode'
  value: string
}

export interface MDLink extends BaseNode {
  type: 'MDLink'
  url: string
  title?: string | null
  children: MDContentNode[]
}

export interface MDImage extends BaseNode {
  type: 'MDImage'
  url: string
  title?: string | null
  alt?: string | null
}

export interface MDBreak extends BaseNode {
  type: 'MDBreak'
}

export interface MDThematicBreak extends BaseNode {
  type: 'MDThematicBreak'
}

export interface MDDefinition extends BaseNode {
  type: 'MDDefinition'
  identifier: string
  label?: string | null
  url: string
  title?: string | null
}

export interface MDDelete extends BaseNode {
  type: 'MDDelete'
  children: MDContentNode[]
}

export interface MDFootnoteDefinition extends BaseNode {
  type: 'MDFootnoteDefinition'
  identifier: string
  label?: string | null
  children: MDContentNode[]
}

export interface MDFootnoteReference extends BaseNode {
  type: 'MDFootnoteReference'
  identifier: string
  label?: string | null
}

export interface MDImageReference extends BaseNode {
  type: 'MDImageReference'
  identifier: string
  label?: string | null
  referenceType: 'collapsed' | 'full' | 'shortcut'
  alt?: string | null
}

export interface MDInlineMath extends BaseNode {
  type: 'MDInlineMath'
  value: string
}

export interface MDLinkReference extends BaseNode {
  type: 'MDLinkReference'
  identifier: string
  label?: string | null
  referenceType: 'collapsed' | 'full' | 'shortcut'
  children: MDContentNode[]
}

export interface MDMath extends BaseNode {
  type: 'MDMath'
  value: string
  meta?: string | null
}

export interface MDTable extends BaseNode {
  type: 'MDTable'
  align?: Array<'center' | 'left' | 'right' | null> | null
  children: MDTableRow[]
}

export interface MDTableCell extends BaseNode {
  type: 'MDTableCell'
  children: MDContentNode[]
}

export interface MDTableRow extends BaseNode {
  type: 'MDTableRow'
  children: MDTableCell[]
}

export interface MDToml extends BaseNode {
  type: 'MDToml'
  value: string
}

export interface MDYaml extends BaseNode {
  type: 'MDYaml'
  value: string
}

export type MDContentNode =
  | JSXElement
  | JSXExpressionContainer
  | JSXFragment
  | JSXSpreadChild
  | JSXText
  | MDBlockquote
  | MDBreak
  | MDCode
  | MDDefinition
  | MDDelete
  | MDEmphasis
  | MDFootnoteDefinition
  | MDFootnoteReference
  | MDHeading
  | MDImage
  | MDImageReference
  | MDInlineCode
  | MDInlineMath
  | MDLink
  | MDLinkReference
  | MDList
  | MDListItem
  | MDMath
  | MDParagraph
  | MDStrong
  | MDTable
  | MDTableCell
  | MDTableRow
  | MDText
  | MDThematicBreak
  | MDToml
  | MDXFlowExpression
  | MDXTextExpression
  | MDYaml
```

## Rules

### mdx/remark

_possible fixable depends on your remark plugins_:

Integration with [remark-lint][] plugins, it will read [remark's configuration](https://github.com/remarkjs/remark/tree/master/packages/remark-cli#remark-cli) automatically via [unified-engine][]. But `.remarkignore` will not be respected, you should use `.eslintignore` instead.

If you want to disable or change severity of some related rules, it won't work by setting rules in eslint config like `'remark-lint-no-duplicate-headings': 0`, you should change your remark config instead like following:

```jsonc
{
  "plugins": [
    "@1stg/remark-config",
    // change to error severity, notice `[]` is required
    ["lint-no-duplicate-headings", [2]],
    // disable following plugin
    [
      "lint-no-multiple-toplevel-headings",
      [0], // or false
    ],
  ],
}
```

## Prettier Integration

If you're using [remark-lint][] feature with [Prettier][] both together, you can try [remark-preset-prettier][] which helps you to _turn off all rules that are unnecessary or might conflict with [Prettier][]_.

```json
{
  "plugins": [
    "preset-lint-consistent",
    "preset-lint-recommended",
    "preset-lint-markdown-style-guide",
    "preset-prettier"
  ]
}
```

## Sponsors and Backers

[![Sponsors](https://raw.githubusercontent.com/1stG/static/master/sponsors.svg)](https://github.com/sponsors/JounQin)

### Sponsors

| 1stG                                                                                                                   | RxTS                                                                                                                   | UnRS                                                                                                                   | UnTS                                                                                                                   |
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| [![1stG Open Collective sponsors](https://opencollective.com/1stG/organizations.svg)](https://opencollective.com/1stG) | [![RxTS Open Collective sponsors](https://opencollective.com/rxts/organizations.svg)](https://opencollective.com/rxts) | [![UnRS Open Collective sponsors](https://opencollective.com/unrs/organizations.svg)](https://opencollective.com/unrs) | [![UnTS Open Collective sponsors](https://opencollective.com/unts/organizations.svg)](https://opencollective.com/unts) |

[![unified Open Collective backers and sponsors](https://opencollective.com/unified/organizations.svg)](https://opencollective.com/unified)

### Backers

| 1stG                                                                                                                | RxTS                                                                                                                | UnRS                                                                                                                | UnTS                                                                                                                |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [![1stG Open Collective backers](https://opencollective.com/1stG/individuals.svg)](https://opencollective.com/1stG) | [![RxTS Open Collective backers](https://opencollective.com/rxts/individuals.svg)](https://opencollective.com/rxts) | [![UnRS Open Collective backers](https://opencollective.com/unrs/individuals.svg)](https://opencollective.com/unrs) | [![UnTS Open Collective backers](https://opencollective.com/unts/individuals.svg)](https://opencollective.com/unts) |

[![unified Open Collective backers and sponsors](https://opencollective.com/unified/individuals.svg)](https://opencollective.com/unified)

## Changelog

Detailed changes for each release are documented in [CHANGELOG.md](./CHANGELOG.md).

## License

[MIT][] © [JounQin][]@[1stG.me][]

[1stg.me]: https://www.1stG.me
[changesets]: https://github.com/changesets/changesets
[eslint]: https://eslint.org
[jounqin]: https://github.com/JounQin
[mdx]: https://github.com/mdx-js/mdx
[mit]: http://opensource.org/licenses/MIT
[prettier]: https://prettier.io
[remark-lint]: https://github.com/remarkjs/remark-lint
[remark-preset-prettier]: https://github.com/un-ts/remark-preset-prettier
[unified-engine]: https://github.com/unifiedjs/unified-engine
[vscode eslint]: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
[vscode mdx]: https://github.com/mdx-js/mdx-analyzer/tree/main/packages/vscode-mdx
