-
Notifications
You must be signed in to change notification settings - Fork 80
feat: create no-unused-definitions rule
#425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
07b6cbe
8382c5f
253219b
86db80f
a37da06
28424d5
2c7a2a3
140cfa9
24538d6
e48bd94
fec2ab7
bb6d65b
8e6c1e2
47b2bbb
c19fe99
8a21806
8be30de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # no-unused-definitions | ||
|
|
||
| Disallow unused definitions. | ||
|
|
||
| ## Background | ||
|
|
||
| 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. | ||
|
|
||
| Unused definitions can cause several issues: | ||
|
|
||
| - They add unnecessary clutter to the document. | ||
| - They might indicate broken references or content that was intended to be included. | ||
| - They can mislead readers who might assume the definitions are purposefully being used somewhere. | ||
|
|
||
| Cleaning up unused definitions helps maintain a more organized and intentional document structure. | ||
|
|
||
| ## Rule Details | ||
|
|
||
| > [!IMPORTANT] <!-- eslint-disable-line -- This should be fixed in https://github.com/eslint/markdown/issues/294 --> | ||
| > | ||
| > The footnotes are only supported when using `language` mode [`markdown/gfm`](/README.md#languages). | ||
|
|
||
| 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. | ||
|
|
||
| Examples of **incorrect** code: | ||
|
|
||
| ```markdown | ||
| <!-- eslint markdown/no-unused-definitions: "error" --> | ||
|
|
||
| <!-- definition --> | ||
|
|
||
| [mercury]: https://example.com/mercury/ | ||
|
|
||
| [venus]: https://example.com/venus.jpg | ||
|
|
||
| <!-- footnote definition --> | ||
|
|
||
| [^mercury]: Hello, Mercury! | ||
| ``` | ||
|
|
||
| Examples of **correct** code: | ||
|
|
||
| ```markdown | ||
| <!-- eslint markdown/no-unused-definitions: "error" --> | ||
|
|
||
| <!-- definition --> | ||
|
|
||
| [Mercury][mercury] | ||
|
|
||
| [mercury]: https://example.com/mercury/ | ||
|
|
||
| ![Venus Image][venus] | ||
|
|
||
| [venus]: https://example.com/venus.jpg | ||
|
|
||
| <!-- footnote definition --> | ||
|
|
||
| Mercury[^mercury] | ||
|
|
||
| [^mercury]: Hello, Mercury! | ||
|
|
||
| <!-- definition-style comment --> | ||
|
|
||
| [//]: # (This is a comment 1) | ||
| [//]: <> (This is a comment 2) | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| - `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: `["//"]`) | ||
|
|
||
| Examples of **correct** code when configured as `"no-unused-definitions": ["error", { allowDefinitions: ["mercury"] }]`: | ||
|
|
||
| ```markdown | ||
| <!-- eslint markdown/no-unused-definitions: ["error", { allowDefinitions: ["mercury"] }] --> | ||
|
|
||
| [mercury]: https://example.com/mercury/ | ||
| [mercury]: https://example.com/venus/ | ||
| ``` | ||
|
|
||
| - `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: `[]`) | ||
|
|
||
| Examples of **correct** code when configured as `"no-unused-definitions": ["error", { allowFootnoteDefinitions: ["mercury"] }]`: | ||
|
|
||
| ```markdown | ||
| <!-- eslint markdown/no-unused-definitions: ["error", { allowFootnoteDefinitions: ["mercury"] }] --> | ||
|
|
||
| [^mercury]: Hello, Mercury! | ||
| [^mercury]: Hello, Venus! | ||
| ``` | ||
|
|
||
| ## When Not to Use It | ||
|
|
||
| You might want to disable this rule if: | ||
|
|
||
| - You're maintaining a document with intentionally defined but temporarily unused references. | ||
| - You're using reference definitions as a form of comment or placeholder for future content. | ||
|
|
||
| ## Prior Art | ||
|
|
||
| - [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) | ||
| - [remark-lint-no-unused-definitions](https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-unused-definitions#remark-lint-no-unused-definitions) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,142 @@ | ||||||
| /** | ||||||
| * @fileoverview Rule to prevent unused definitions in Markdown. | ||||||
| * @author 루밀LuMir(lumirlumir) | ||||||
| */ | ||||||
|
|
||||||
| //----------------------------------------------------------------------------- | ||||||
| // Type Definitions | ||||||
| //----------------------------------------------------------------------------- | ||||||
|
|
||||||
| /** | ||||||
| * @import { Definition, FootnoteDefinition } from "mdast"; | ||||||
| * @import { MarkdownRuleDefinition } from "../types.js"; | ||||||
| * @typedef {"unusedDefinition" | "unusedFootnoteDefinition"} NoUnusedDefinitionsMessageIds | ||||||
| * @typedef {[{ allowDefinitions?: string[], allowFootnoteDefinitions?: string[] }]} NoUnusedDefinitionsOptions | ||||||
| * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoUnusedDefinitionsOptions, MessageIds: NoUnusedDefinitionsMessageIds }>} NoUnusedDefinitionsRuleDefinition | ||||||
| */ | ||||||
|
|
||||||
| //----------------------------------------------------------------------------- | ||||||
| // Rule Definition | ||||||
| //----------------------------------------------------------------------------- | ||||||
|
|
||||||
| /** @type {NoUnusedDefinitionsRuleDefinition} */ | ||||||
| export default { | ||||||
| meta: { | ||||||
| type: "problem", | ||||||
|
|
||||||
| docs: { | ||||||
| recommended: true, | ||||||
| description: "Disallow unused definitions", | ||||||
| url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-unused-definitions.md", | ||||||
| }, | ||||||
|
|
||||||
| messages: { | ||||||
| unusedDefinition: | ||||||
| "Unexpected unused definition `{{ identifier }}` found.", | ||||||
| unusedFootnoteDefinition: | ||||||
| "Unexpected unused footnote definition `{{ identifier }}` found.", | ||||||
| }, | ||||||
|
|
||||||
| schema: [ | ||||||
| { | ||||||
| type: "object", | ||||||
| properties: { | ||||||
| allowDefinitions: { | ||||||
| type: "array", | ||||||
| items: { | ||||||
| type: "string", | ||||||
| }, | ||||||
| uniqueItems: true, | ||||||
| }, | ||||||
| allowFootnoteDefinitions: { | ||||||
| type: "array", | ||||||
| items: { | ||||||
| type: "string", | ||||||
| }, | ||||||
| uniqueItems: true, | ||||||
| }, | ||||||
| }, | ||||||
| additionalProperties: false, | ||||||
| }, | ||||||
| ], | ||||||
|
|
||||||
| defaultOptions: [ | ||||||
| { | ||||||
| allowDefinitions: ["//"], | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
References:
|
||||||
| allowFootnoteDefinitions: [], | ||||||
| }, | ||||||
| ], | ||||||
| }, | ||||||
|
|
||||||
| create(context) { | ||||||
| const allowDefinitions = new Set(context.options[0]?.allowDefinitions); | ||||||
| const allowFootnoteDefinitions = new Set( | ||||||
| context.options[0]?.allowFootnoteDefinitions, | ||||||
lumirlumir marked this conversation as resolved.
Show resolved
Hide resolved
lumirlumir marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ); | ||||||
|
|
||||||
| /** @type {Set<string>} Set to track used identifiers */ | ||||||
| const usedIdentifiers = new Set(); | ||||||
| /** @type {Set<string>} Set to track used footnote identifiers */ | ||||||
| const usedFootnoteIdentifiers = new Set(); | ||||||
| /** @type {Set<Definition>} */ | ||||||
| const definitions = new Set(); | ||||||
| /** @type {Set<FootnoteDefinition>} */ | ||||||
| const footnoteDefinitions = new Set(); | ||||||
|
|
||||||
| return { | ||||||
| imageReference(node) { | ||||||
| usedIdentifiers.add(node.identifier); | ||||||
| }, | ||||||
|
|
||||||
| linkReference(node) { | ||||||
| usedIdentifiers.add(node.identifier); | ||||||
| }, | ||||||
|
|
||||||
| footnoteReference(node) { | ||||||
| usedFootnoteIdentifiers.add(node.identifier); | ||||||
| }, | ||||||
|
|
||||||
| definition(node) { | ||||||
| if (allowDefinitions.has(node.identifier)) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| definitions.add(node); | ||||||
| }, | ||||||
|
|
||||||
| footnoteDefinition(node) { | ||||||
| if (allowFootnoteDefinitions.has(node.identifier)) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| footnoteDefinitions.add(node); | ||||||
| }, | ||||||
|
|
||||||
| "root:exit"() { | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
In this PR, unlike the |
||||||
| for (const definition of definitions) { | ||||||
| if (!usedIdentifiers.has(definition.identifier)) { | ||||||
| context.report({ | ||||||
| node: definition, | ||||||
| messageId: "unusedDefinition", | ||||||
| data: { identifier: definition.identifier }, | ||||||
| }); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| for (const footnoteDefinition of footnoteDefinitions) { | ||||||
| if ( | ||||||
| !usedFootnoteIdentifiers.has( | ||||||
| footnoteDefinition.identifier, | ||||||
| ) | ||||||
| ) { | ||||||
| context.report({ | ||||||
| node: footnoteDefinition, | ||||||
| messageId: "unusedFootnoteDefinition", | ||||||
| data: { identifier: footnoteDefinition.identifier }, | ||||||
| }); | ||||||
| } | ||||||
| } | ||||||
| }, | ||||||
| }; | ||||||
| }, | ||||||
| }; | ||||||
Uh oh!
There was an error while loading. Please reload this page.