Skip to content

Commit ce747ac

Browse files
committed
Merge branch 'main' into frontmatterTitle
2 parents a0d5905 + 0afc924 commit ce747ac

29 files changed

+1005
-124
lines changed

.github/ISSUE_TEMPLATE/bug-report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ body:
3535
attributes:
3636
label: What did you do?
3737
description: |
38-
Please include a *minimal* reproduction case.
38+
Please include a *minimal* reproduction case. Use this [StackBlitz template](https://stackblitz.com/edit/stackblitz-starters-jqcrssas?file=eslint.config.js) as a starting point.
3939
value: |
4040
<details>
4141
<summary>Configuration</summary>

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export default defineConfig([
102102
| [`no-missing-link-fragments`](./docs/rules/no-missing-link-fragments.md) | Disallow link fragments that do not reference valid headings | yes |
103103
| [`no-multiple-h1`](./docs/rules/no-multiple-h1.md) | Disallow multiple H1 headings in the same document | yes |
104104
| [`no-reversed-media-syntax`](./docs/rules/no-reversed-media-syntax.md) | Disallow reversed link and image syntax | yes |
105+
| [`no-unused-definitions`](./docs/rules/no-unused-definitions.md) | Disallow unused definitions | yes |
105106
| [`require-alt-text`](./docs/rules/require-alt-text.md) | Require alternative text for images | yes |
106107
| [`table-column-count`](./docs/rules/table-column-count.md) | Disallow data rows in a GitHub Flavored Markdown table from having more cells than the header row | yes |
107108
<!-- Rule Table End -->
@@ -257,7 +258,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
257258
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
258259
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a></p><h3>Silver Sponsors</h3>
259260
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/e6d15e1/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301" alt="American Express" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
260-
<p><a href="https://sentry.io"><img src="https://github.com/getsentry.png" alt="Sentry" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nolebase.ayaka.io"><img src="https://avatars.githubusercontent.com/u/11081491" alt="Neko" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
261+
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
261262
<h3>Technology Sponsors</h3>
262263
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
263264
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>

docs/rules/no-empty-definitions.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,27 @@ Disallow empty definitions.
44

55
## Background
66

7-
Markdown allows you to specify a label as a placeholder for a URL in both links and images using square brackets, such as:
7+
Markdown allows you to specify a label as a placeholder for a URL in both links and images, or as a footnote reference, using square brackets. For example:
88

99
```markdown
1010
[ESLint][eslint]
1111

1212
[eslint]: https://eslint.org
13+
14+
[ESLint][^eslint]
15+
16+
[^eslint]: Find and fix problmes in your JavaScript code
1317
```
1418

15-
If the definition's URL is empty or only contains an empty fragment (`#`), then it's not providing any useful information and could be a mistake.
19+
Definitions with an empty URL or only an empty fragment (`#`), as well as footnote definitions with no content, are usually mistakes and do not provide useful information.
1620

1721
## Rule Details
1822

19-
This rule warns when it finds definitions where the URL is either not specified or contains only an empty fragment (`#`).
23+
> [!IMPORTANT] <!-- eslint-disable-line -- This should be fixed in https://github.com/eslint/markdown/issues/294 -->
24+
>
25+
> Footnotes are only supported when using `language` mode [`markdown/gfm`](/README.md#languages).
26+
27+
This rule warns when it finds definitions where the URL is either not specified or contains only an empty fragment (`#`). It also warns for empty footnote definitions by default.
2028

2129
Examples of **incorrect** code for this rule:
2230

@@ -25,15 +33,31 @@ Examples of **incorrect** code for this rule:
2533

2634
[earth]: <>
2735
[moon]: #
36+
[^note]:
2837
```
2938

30-
Examples of correct code:
39+
Examples of **correct** code for this rule:
3140

3241
```markdown
3342
<!-- eslint markdown/no-empty-definitions: "error" -->
3443

3544
[earth]: https://example.com/earth/
3645
[moon]: #section
46+
[^note]: This is a footnote.
47+
```
48+
49+
## Options
50+
51+
The following options are available on this rule:
52+
53+
* `checkFootnoteDefinitions: boolean` - When set to `false`, the rule will not report empty footnote definitions. (default: `true`).
54+
55+
Examples of **correct** code for this rule with `checkFootnoteDefinitions: false`:
56+
57+
```markdown
58+
<!-- eslint markdown/no-empty-definitions: ["error", { checkFootnoteDefinitions: false }] -->
59+
60+
[^note]:
3761
```
3862

3963
## When Not to Use It

docs/rules/no-missing-link-fragments.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Examples of **correct** code for this rule:
5555

5656
[Link](#L2)
5757

58+
# café
59+
60+
[Link to café](#caf%C3%A9)
61+
5862
# <span class="icon-star"></span> Starred Projects
5963

6064
[Link to starred projects](#-starred-projects)

docs/rules/no-unused-definitions.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# no-unused-definitions
2+
3+
Disallow unused definitions.
4+
5+
## Background
6+
7+
In Markdown, you can define reference-style links, images, and footnotes using definitions that appear elsewhere in the document. These definitions consist of an identifier followed by a URL, image source, or footnote content. However, when definitions are created but never referenced in the document, they become unused and potentially confusing.
8+
9+
Unused definitions can cause several issues:
10+
11+
- They add unnecessary clutter to the document.
12+
- They might indicate broken references or content that was intended to be included.
13+
- They can mislead readers who might assume the definitions are being used somewhere.
14+
15+
16+
Cleaning up unused definitions helps maintain a more organized and intentional document structure.
17+
18+
## Rule Details
19+
20+
> [!IMPORTANT] <!-- eslint-disable-line -- This should be fixed in https://github.com/eslint/markdown/issues/294 -->
21+
>
22+
> The footnotes are only supported when using `language` mode [`markdown/gfm`](/README.md#languages).
23+
24+
This rule warns about unused reference definitions in Markdown documents. It detects definition entries (e.g., `[reference-id]: http://example.com`) that aren't used by any links, images, or footnotes in the document, and reports them as violations. Please note that this rule doesn't report definition-style comments (e.g., `[//]: # (This is a comment)`) by default.
25+
26+
Examples of **incorrect** code:
27+
28+
```markdown
29+
<!-- eslint markdown/no-unused-definitions: "error" -->
30+
31+
<!-- definition -->
32+
33+
[mercury]: https://example.com/mercury/
34+
35+
[venus]: https://example.com/venus.jpg
36+
37+
<!-- footnote definition -->
38+
39+
[^mercury]: Hello, Mercury!
40+
```
41+
42+
Examples of **correct** code:
43+
44+
```markdown
45+
<!-- eslint markdown/no-unused-definitions: "error" -->
46+
47+
<!-- definition -->
48+
49+
[Mercury][mercury]
50+
51+
[mercury]: https://example.com/mercury/
52+
53+
![Venus Image][venus]
54+
55+
[venus]: https://example.com/venus.jpg
56+
57+
<!-- footnote definition -->
58+
59+
Mercury[^mercury]
60+
61+
[^mercury]: Hello, Mercury!
62+
63+
<!-- definition-style comment -->
64+
65+
[//]: # (This is a comment 1)
66+
[//]: <> (This is a comment 2)
67+
```
68+
69+
## Options
70+
71+
- `allowDefinitions: Array<string>` - when specified, unused definitions are allowed if they match one of the identifiers in this array. This is useful for ignoring definitions that are intentionally unused. (default: `["//"]`)
72+
73+
Examples of **correct** code when configured as `"no-unused-definitions": ["error", { allowDefinitions: ["mercury"] }]`:
74+
75+
```markdown
76+
<!-- eslint markdown/no-unused-definitions: ["error", { allowDefinitions: ["mercury"] }] -->
77+
78+
[mercury]: https://example.com/mercury/
79+
[mercury]: https://example.com/venus/
80+
```
81+
82+
- `allowFootnoteDefinitions: Array<string>` - when specified, unused footnote definitions are allowed if they match one of the identifiers in this array. This is useful for ignoring footnote definitions that are intentionally unused. (default: `[]`)
83+
84+
Examples of **correct** code when configured as `"no-unused-definitions": ["error", { allowFootnoteDefinitions: ["mercury"] }]`:
85+
86+
```markdown
87+
<!-- eslint markdown/no-unused-definitions: ["error", { allowFootnoteDefinitions: ["mercury"] }] -->
88+
89+
[^mercury]: Hello, Mercury!
90+
[^mercury]: Hello, Venus!
91+
```
92+
93+
## When Not to Use It
94+
95+
You might want to disable this rule if:
96+
97+
- You're maintaining a document with intentionally defined but temporarily unused references.
98+
- You're using reference definitions as a form of comment or placeholder for future content.
99+
100+
## Prior Art
101+
102+
- [MD053 - Link and image reference definitions should be needed](https://github.com/DavidAnson/markdownlint/blob/main/doc/md053.md#md053---link-and-image-reference-definitions-should-be-needed)
103+
- [remark-lint-no-unused-definitions](https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-unused-definitions#remark-lint-no-unused-definitions)

src/index.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @fileoverview Enables the processor for Markdown file extensions.
2+
* @fileoverview Markdown plugin.
33
* @author Brandon Mills
44
*/
55

@@ -17,16 +17,19 @@ import rules from "./build/rules.js";
1717
// Type Definitions
1818
//-----------------------------------------------------------------------------
1919

20-
/** @typedef {import("eslint").Linter.RulesRecord} RulesRecord*/
21-
/** @typedef {import("eslint").Linter.Config} Config*/
22-
/** @typedef {import("eslint").ESLint.Plugin} Plugin */
2320
/**
24-
* @typedef {import("./types.ts").MarkdownRuleDefinition<Options>} MarkdownRuleDefinition<Options>
25-
* @template {Partial<import("./types.ts").MarkdownRuleDefinitionTypeOptions>} [Options={}]
21+
* @import { Linter } from "eslint";
22+
* @import * as Types from "./types.js";
23+
* @typedef {Linter.RulesRecord} RulesRecord
24+
* @typedef {Types.MarkdownRuleDefinition} RuleModule
25+
* @typedef {Types.MarkdownRuleVisitor} MarkdownRuleVisitor
26+
* @typedef {Types.MarkdownRuleDefinitionTypeOptions} MarkdownRuleDefinitionTypeOptions
27+
*/
28+
29+
/**
30+
* @typedef {Types.MarkdownRuleDefinition<Options>} MarkdownRuleDefinition<Options>
31+
* @template {Partial<MarkdownRuleDefinitionTypeOptions>} [Options={}]
2632
*/
27-
/** @typedef {MarkdownRuleDefinition} RuleModule */
28-
/** @typedef {import("./types.ts").MarkdownRuleVisitor} MarkdownRuleVisitor */
29-
/** @typedef {import("@eslint/core").Language} Language */
3033

3134
//-----------------------------------------------------------------------------
3235
// Exports

src/language/markdown-language.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@ import { gfm } from "micromark-extension-gfm";
2020
// Types
2121
//-----------------------------------------------------------------------------
2222

23-
/** @typedef {import("mdast").Root} RootNode */
24-
/** @typedef {import("mdast-util-from-markdown").Options['extensions']} Extensions */
25-
/** @typedef {import("mdast-util-from-markdown").Options['mdastExtensions']} MdastExtensions */
26-
/** @typedef {import("@eslint/core").Language} Language */
27-
/** @typedef {import("@eslint/core").File} File */
28-
/** @typedef {import("@eslint/core").ParseResult<RootNode>} ParseResult */
29-
/** @typedef {import("@eslint/core").OkParseResult<RootNode>} OkParseResult */
30-
/** @typedef {import("../types.ts").MarkdownLanguageOptions} MarkdownLanguageOptions */
31-
/** @typedef {import("../types.ts").MarkdownLanguageContext} MarkdownLanguageContext */
32-
/** @typedef {"commonmark"|"gfm"} ParserMode */
23+
/**
24+
* @import { Language, File, ParseResult, OkParseResult } from "@eslint/core";
25+
* @import { Root } from "mdast";
26+
* @import { Options } from "mdast-util-from-markdown";
27+
* @import { MarkdownLanguageOptions, MarkdownLanguageContext } from "../types.js";
28+
* @typedef {Options['extensions']} Extensions
29+
* @typedef {Options['mdastExtensions']} MdastExtensions
30+
* @typedef {"commonmark"|"gfm"} ParserMode
31+
*/
3332

3433
//-----------------------------------------------------------------------------
3534
// Helpers
@@ -182,7 +181,7 @@ export class MarkdownLanguage {
182181
* Parses the given file into an AST.
183182
* @param {File} file The virtual file to parse.
184183
* @param {MarkdownLanguageContext} context The options to use for parsing.
185-
* @returns {ParseResult} The result of parsing.
184+
* @returns {ParseResult<Root>} The result of parsing.
186185
*/
187186
parse(file, context) {
188187
// Note: BOM already removed
@@ -216,7 +215,7 @@ export class MarkdownLanguage {
216215
/**
217216
* Creates a new `MarkdownSourceCode` object from the given information.
218217
* @param {File} file The virtual file to create a `MarkdownSourceCode` object from.
219-
* @param {OkParseResult} parseResult The result returned from `parse()`.
218+
* @param {OkParseResult<Root>} parseResult The result returned from `parse()`.
220219
* @returns {MarkdownSourceCode} The new `MarkdownSourceCode` object.
221220
*/
222221
createSourceCode(file, parseResult) {

src/language/markdown-source-code.js

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,11 @@ import { findOffsets } from "../util.js";
1919
// Types
2020
//-----------------------------------------------------------------------------
2121

22-
/** @typedef {import("mdast").Root} RootNode */
23-
/** @typedef {import("mdast").Node} MarkdownNode */
24-
/** @typedef {import("mdast").Html} HTMLNode */
25-
/** @typedef {import("@eslint/core").Language} Language */
26-
/** @typedef {import("@eslint/core").File} File */
27-
/** @typedef {import("@eslint/core").TraversalStep} TraversalStep */
28-
/** @typedef {import("@eslint/core").VisitTraversalStep} VisitTraversalStep */
29-
/** @typedef {import("@eslint/core").ParseResult<RootNode>} ParseResult */
30-
/** @typedef {import("@eslint/core").SourceLocation} SourceLocation */
31-
/** @typedef {import("@eslint/core").SourceRange} SourceRange */
32-
/** @typedef {import("@eslint/core").FileProblem} FileProblem */
33-
/** @typedef {import("@eslint/core").DirectiveType} DirectiveType */
34-
/** @typedef {import("@eslint/core").RulesConfig} RulesConfig */
35-
/** @typedef {import("../types.ts").MarkdownLanguageOptions} MarkdownLanguageOptions */
22+
/**
23+
* @import { Root, Node, Html } from "mdast";
24+
* @import { TraversalStep, SourceLocation, FileProblem, DirectiveType, RulesConfig } from "@eslint/core";
25+
* @import { MarkdownLanguageOptions } from "../types.js";
26+
*/
3627

3728
//-----------------------------------------------------------------------------
3829
// Helpers
@@ -73,7 +64,7 @@ class InlineConfigComment {
7364

7465
/**
7566
* Extracts inline configuration comments from an HTML node.
76-
* @param {HTMLNode} node The HTML node to extract comments from.
67+
* @param {Html} node The HTML node to extract comments from.
7768
* @returns {Array<InlineConfigComment>} The inline configuration comments found in the node.
7869
*/
7970
function extractInlineConfigCommentsFromHTML(node) {
@@ -136,7 +127,7 @@ function extractInlineConfigCommentsFromHTML(node) {
136127

137128
/**
138129
* Markdown Source Code Object
139-
* @extends {TextSourceCodeBase<{LangOptions: MarkdownLanguageOptions, RootNode: RootNode, SyntaxElementWithLoc: MarkdownNode, ConfigNode: { value: string; position: SourceLocation }}>}
130+
* @extends {TextSourceCodeBase<{LangOptions: MarkdownLanguageOptions, RootNode: Root, SyntaxElementWithLoc: Node, ConfigNode: { value: string; position: SourceLocation }}>}
140131
*/
141132
export class MarkdownSourceCode extends TextSourceCodeBase {
142133
/**
@@ -147,13 +138,13 @@ export class MarkdownSourceCode extends TextSourceCodeBase {
147138

148139
/**
149140
* Cache of parent nodes.
150-
* @type {WeakMap<MarkdownNode, MarkdownNode>}
141+
* @type {WeakMap<Node, Node>}
151142
*/
152143
#parents = new WeakMap();
153144

154145
/**
155146
* Collection of HTML nodes. Used to find directive comments.
156-
* @type {Array<HTMLNode>}
147+
* @type {Array<Html>}
157148
*/
158149
#htmlNodes = [];
159150

@@ -165,15 +156,15 @@ export class MarkdownSourceCode extends TextSourceCodeBase {
165156

166157
/**
167158
* The AST of the source code.
168-
* @type {RootNode}
159+
* @type {Root}
169160
*/
170161
ast = undefined;
171162

172163
/**
173164
* Creates a new instance.
174165
* @param {Object} options The options for the instance.
175166
* @param {string} options.text The source code text.
176-
* @param {RootNode} options.ast The root AST node.
167+
* @param {Root} options.ast The root AST node.
177168
*/
178169
constructor({ text, ast }) {
179170
super({ ast, text });
@@ -185,8 +176,8 @@ export class MarkdownSourceCode extends TextSourceCodeBase {
185176

186177
/**
187178
* Returns the parent of the given node.
188-
* @param {MarkdownNode} node The node to get the parent of.
189-
* @returns {MarkdownNode|undefined} The parent of the node.
179+
* @param {Node} node The node to get the parent of.
180+
* @returns {Node|undefined} The parent of the node.
190181
*/
191182
getParent(node) {
192183
return this.#parents.get(node);

0 commit comments

Comments
 (0)