Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/examples/eslint-plugin-test/docs/rules/require-baz.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Require using baz (`test/require-baz`)

❌ This rule is deprecated. It was replaced by [`test/prefer-bar`](prefer-bar.md).
❌ This rule has been [deprecated](https://example.com) since v1.0.0 and will be available until v2.0.0. It was replaced by [`test/prefer-bar`](prefer-bar.md) (custom message about this replacement) ([read more](https://example.com)). Custom message about overall deprecation.

🚫 This rule is _disabled_ in the ⌨️ `typescript` config.

Expand Down
106 changes: 102 additions & 4 deletions lib/rule-doc-notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@
import { findConfigEmoji, getConfigsForRule } from './plugin-configs.js';
import {
RuleModule,
DeprecatedInfo,
Plugin,
ConfigsToRules,
ConfigEmojis,
SEVERITY_TYPE,
NOTICE_TYPE,
UrlRuleDocFunction,
PathRuleDocFunction,
ReplacedByInfo,
} from './types.js';
import { RULE_TYPE, RULE_TYPE_MESSAGES_NOTICES } from './rule-type.js';
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
import { hasOptions } from './rule-options.js';
import { getLinkToRule, replaceRulePlaceholder } from './rule-link.js';
import {
getLinkToRule,
getMarkdownLink,
replaceRulePlaceholder,
} from './rule-link.js';
import {
toSentenceCase,
removeTrailingPeriod,
Expand Down Expand Up @@ -82,6 +88,63 @@
return sentence;
}

function replacedByToNoticeSentence(
deprecated: DeprecatedInfo,
plugin: Plugin,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string | PathRuleDocFunction,
urlRuleDoc: string | UrlRuleDocFunction | undefined,
): string | undefined {
if (!deprecated.replacedBy || deprecated.replacedBy.length === 0) {
return undefined;
}

function replacedByIterator(
info: ReplacedByInfo,
idx: number,
arr: ReplacedByInfo[],
): string | undefined {
// A plugin maintainer should at least specify a rule name
if (!info.rule?.name) {
return undefined;
}

const conjunction = arr.length > 1 && idx === arr.length - 1 ? 'and ' : '';

// @todo - generate (deprecated rules)
// using prefix ahead of replacement rule name uses correct replacement rule link 4
// with nested rule names has the correct links, especially replacement rule link 5
// with --path-rule-doc has the correct links, especially replacement rule link 5
const replacementRule = info.rule.url
? getMarkdownLink(info.rule.name, true, info.rule.url)
: getLinkToRule(
info.rule.name,
plugin,
pluginPrefix,
pathPlugin,
pathRuleDoc,
replaceRulePlaceholder(pathRuleDoc, info.rule.name),
true,
true,
urlRuleDoc,
);

const externalPlugin = info.plugin?.name && info.plugin.name !== 'eslint'

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 24.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 22.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 20.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 18.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 23.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 20.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 18.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 23.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 24.x)

Insert `⏎·····`

Check failure on line 133 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 22.x)

Insert `⏎·····`
? ` from ${getMarkdownLink(info.plugin.name, false, info.plugin.url)}`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 24.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 22.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 20.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 18.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 23.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 20.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 18.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 23.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 24.x)

Insert `··`

Check failure on line 134 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 22.x)

Insert `··`
: '';

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 24.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 20.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 22.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 18.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu, 23.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 20.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 18.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 23.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 24.x)

Insert `··`

Check failure on line 135 in lib/rule-doc-notices.ts

View workflow job for this annotation

GitHub Actions / build (windows, 22.x)

Insert `··`

return `${conjunction}${replacementRule}${externalPlugin}${
info.message ? ` (${info.message})` : ''
}${info.url ? ` (${getMarkdownLink('read more', false, info.url)})` : ''}`;
}

return `It was replaced by ${deprecated.replacedBy
.map((item, index, array) => replacedByIterator(item, index, array))
.filter(Boolean)
.join(', ')}.`;
}

// A few individual notices declared here just so they can be reused in multiple notices.
const NOTICE_FIXABLE = `${EMOJI_FIXABLE} This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).`;
const NOTICE_HAS_SUGGESTIONS = `${EMOJI_HAS_SUGGESTIONS} This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).`;
Expand All @@ -104,6 +167,7 @@
fixable: boolean;
hasSuggestions: boolean;
urlConfigs?: string;
deprecated: boolean | DeprecatedInfo | undefined;
replacedBy: readonly string[] | undefined;
plugin: Plugin;
pluginPrefix: string;
Expand Down Expand Up @@ -187,8 +251,8 @@
return `${emojis.join('')} ${sentences}`;
},

// Deprecated notice has optional "replaced by" rules list.
[NOTICE_TYPE.DEPRECATED]: ({
deprecated,
replacedBy,
plugin,
pluginPrefix,
Expand All @@ -197,6 +261,37 @@
ruleName,
urlRuleDoc,
}) => {
if (typeof deprecated === 'object') {
const sentenceDeprecated = `${EMOJI_DEPRECATED} This rule ${
deprecated.deprecatedSince ? 'has been' : 'is'
} ${deprecated.url ? `[deprecated](${deprecated.url})` : 'deprecated'}${
deprecated.deprecatedSince
? ` since v${deprecated.deprecatedSince}`
: ''
}${
deprecated.availableUntil
? ` and will be available until v${deprecated.availableUntil}`
: ''
}.`;

const sentenceReplacedBy = replacedByToNoticeSentence(
deprecated,
plugin,
pluginPrefix,
pathPlugin,
pathRuleDoc,
urlRuleDoc,
);

const deprecatedMessage = deprecated.message
? addTrailingPeriod(deprecated.message)
: undefined;

return [sentenceDeprecated, sentenceReplacedBy, deprecatedMessage]
.filter(Boolean)
.join(' ');
}

const replacementRuleList = (replacedBy ?? []).map((replacementRuleName) =>
getLinkToRule(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it seems that the DeprecatedInfo has shifted the responsibility of correct links/data of deprecated rules towards the maintainers of eslint plugins. I think eslint-doc-generator should just display the data of the deprecated rules which makes the usage of getLinkToRule in [NOTICE_TYPE.DEPRECATED] obsolete. Please let me know if this is the desired approach.

To your question about this, I think I would still like to automatically fill in links for rules where we can. Including URLs in rule definitions is burdensome, and while rules theoretically could be responsible for it now, I assume many will omit it. However, we can try to use getLinkToRule as a follow-up if you'd like, doesn't have to be in the first version.

Copy link
Author

@error-four-o-four error-four-o-four Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to give it a shot. But first let me check if I understood it correctly and considered the relevant cases:

In case a user of eslint-doc-generator has set ReplacedByInfo#url we're good to go. Otherwise we'd use getLinkToRule as a fallback. The one case that's bothering me is when only ReplacedByInfo#name is set. If it has a plugin prefix we should compare it with the argument pluginPrefix to determine if it's an internal or external rule because we can't rely on ReplacedByInfo#plugin being set. If the name does not have a plugin prefix, getLinkToRule is used to determine if it is a built-in eslint rule or a internal plugin rule, right?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally sure. getLinkToRule might not handle every situation here.

If any scenarios are ambiguous or overly-complicated, we can just omit the link in them and consider as a follow-up.

replacementRuleName,
Expand All @@ -210,6 +305,7 @@
urlRuleDoc,
),
);

return `${EMOJI_DEPRECATED} This rule is deprecated.${
replacedBy && replacedBy.length > 0
? ` It was replaced by ${replacementRuleList.join(', ')}.`
Expand Down Expand Up @@ -277,7 +373,7 @@
configsError.length > 0 ||
configsWarn.length > 0 ||
configsOff.length > 0,
[NOTICE_TYPE.DEPRECATED]: rule.meta?.deprecated || false,
[NOTICE_TYPE.DEPRECATED]: Boolean(rule.meta?.deprecated) || false,
[NOTICE_TYPE.DESCRIPTION]: Boolean(rule.meta?.docs?.description) || false,

// Fixable/suggestions.
Expand Down Expand Up @@ -359,6 +455,7 @@
configsOff,
ruleDocNotices,
);

let noticeType: keyof typeof notices;

for (noticeType in notices) {
Expand Down Expand Up @@ -392,7 +489,8 @@
fixable: Boolean(rule.meta?.fixable),
hasSuggestions: Boolean(rule.meta?.hasSuggestions),
urlConfigs,
replacedBy: rule.meta?.replacedBy,
deprecated: rule.meta?.deprecated,
replacedBy: rule.meta?.replacedBy, // eslint-disable-line @typescript-eslint/no-deprecated
plugin,
pluginPrefix,
pathPlugin,
Expand Down
17 changes: 13 additions & 4 deletions lib/rule-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ export function getUrlToRule(
);
}

export function getMarkdownLink(
text: string,
includeBackticks: boolean,
url?: string,
) {
const displayedText = includeBackticks ? `\`${text}\`` : text;

return url ? `[${displayedText}](${url})` : displayedText;
}

/**
* Get the markdown link (title and URL) to the rule's documentation.
*/
Expand Down Expand Up @@ -124,11 +134,10 @@ export function getLinkToRule(
urlRuleDoc,
);

const ruleNameToDisplay = `${includeBackticks ? '`' : ''}${
const ruleString =
includePrefix && ruleNameWithPluginPrefix
? ruleNameWithPluginPrefix
: ruleNameWithoutPluginPrefix
}${includeBackticks ? '`' : ''}`;
: ruleNameWithoutPluginPrefix;

return urlToRule ? `[${ruleNameToDisplay}](${urlToRule})` : ruleNameToDisplay;
return getMarkdownLink(ruleString, includeBackticks, urlToRule);
}
4 changes: 4 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export type Rules = TSESLint.Linter.RulesRecord;

export type RuleSeverity = TSESLint.Linter.RuleLevel;

export type DeprecatedInfo = TSESLint.DeprecatedInfo;

export type ReplacedByInfo = TSESLint.ReplacedByInfo;

export type Config = TSESLint.Linter.Config;

export type Plugin = TSESLint.Linter.Plugin;
Expand Down
Loading