Skip to content

Commit

Permalink
Merge pull request #279 from bmish/rule-link
Browse files Browse the repository at this point in the history
Ensure deprecated rule replacement link respects `--path-rule-doc`
  • Loading branch information
bmish authored Nov 25, 2022
2 parents b0d5a92 + b76efba commit 29e2ba4
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 103 deletions.
2 changes: 2 additions & 0 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ export async function generate(path: string, options?: GenerateOptions) {
plugin,
configsToRules,
pluginPrefix,
path,
pathRuleDoc,
configEmojis,
ignoreConfig,
ruleDocNotices,
Expand Down
85 changes: 34 additions & 51 deletions lib/rule-doc-notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
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 { replaceRulePlaceholder, goUpLevel, pathToUrl } from './rule-link.js';
import { countOccurrencesInString } from './string.js';
import { getLinkToRule, getUrlToRule } from './rule-link.js';
import { toSentenceCase, removeTrailingPeriod } from './string.js';

function severityToTerminology(severity: SEVERITY_TYPE) {
switch (severity) {
Expand Down Expand Up @@ -84,6 +84,8 @@ const RULE_NOTICES: {
urlConfigs?: string;
replacedBy: readonly string[] | undefined;
pluginPrefix: string;
pathPlugin: string;
pathRuleDoc: string;
type?: RULE_TYPE;
urlRuleDoc?: string;
}) => string);
Expand Down Expand Up @@ -158,21 +160,33 @@ const RULE_NOTICES: {
[NOTICE_TYPE.DEPRECATED]: ({
replacedBy,
pluginPrefix,
pathPlugin,
pathRuleDoc,
ruleName,
urlRuleDoc,
}) => {
// Determine the relative path to the rule doc root so that any replacement rule links can account for this.
const nestingDepthOfCurrentRule = countOccurrencesInString(ruleName, '/'); // Nested rule names always use forward slashes.
const relativePathToRuleDocRoot = goUpLevel(nestingDepthOfCurrentRule);

const urlCurrentPage = getUrlToRule(
ruleName,
pluginPrefix,
pathPlugin,
pathRuleDoc,
pathPlugin,
urlRuleDoc
);
const replacementRuleList = (replacedBy ?? []).map((replacementRuleName) =>
getLinkToRule(
replacementRuleName,
pluginPrefix,
pathPlugin,
pathRuleDoc,
urlCurrentPage,
true,
urlRuleDoc
)
);
return `${EMOJI_DEPRECATED} This rule is deprecated.${
replacedBy && replacedBy.length > 0
? ` It was replaced by ${ruleNamesToList(
replacedBy,
pluginPrefix,
relativePathToRuleDocRoot,
urlRuleDoc
)}.`
? ` It was replaced by ${replacementRuleList}.`
: ''
}`;
},
Expand Down Expand Up @@ -208,35 +222,6 @@ const RULE_NOTICES: {
[NOTICE_TYPE.REQUIRES_TYPE_CHECKING]: `${EMOJI_REQUIRES_TYPE_CHECKING} This rule requires type information.`,
};

/**
* Convert list of rule names to string list of links.
*/
function ruleNamesToList(
ruleNames: readonly string[],
pluginPrefix: string,
relativePathToRuleDocRoot: string,
urlRuleDoc?: string
) {
return ruleNames
.map((ruleName) => {
// Ignore plugin prefix if it's included in rule name.
// While we could display the prefix if we wanted, it definitely cannot be part of the link.
const ruleNameWithoutPluginPrefix = ruleName.startsWith(
`${pluginPrefix}/`
)
? ruleName.slice(pluginPrefix.length + 1)
: ruleName;
return `[\`${ruleNameWithoutPluginPrefix}\`](${
urlRuleDoc
? replaceRulePlaceholder(urlRuleDoc, ruleName)
: `${
pathToUrl(relativePathToRuleDocRoot) + ruleNameWithoutPluginPrefix
}.md`
})`;
})
.join(', ');
}

/**
* Determine which notices should and should not be included at the top of a rule doc.
*/
Expand Down Expand Up @@ -283,6 +268,8 @@ function getRuleNoticeLines(
plugin: Plugin,
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
ruleDocNotices: NOTICE_TYPE[],
Expand Down Expand Up @@ -365,6 +352,8 @@ function getRuleNoticeLines(
urlConfigs,
replacedBy: rule.meta?.replacedBy,
pluginPrefix,
pathPlugin,
pathRuleDoc,
type: rule.meta?.type as RULE_TYPE, // Convert union type to enum.
urlRuleDoc,
})
Expand All @@ -375,16 +364,6 @@ function getRuleNoticeLines(
return lines;
}

function toSentenceCase(str: string) {
return str.replace(/^\w/u, function (txt) {
return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase();
});
}

function removeTrailingPeriod(str: string) {
return str.replace(/\.$/u, '');
}

function makeRuleDocTitle(
name: string,
description: string | undefined,
Expand Down Expand Up @@ -446,6 +425,8 @@ export function generateRuleHeaderLines(
plugin: Plugin,
configsToRules: ConfigsToRules,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
ruleDocNotices: NOTICE_TYPE[],
Expand All @@ -460,6 +441,8 @@ export function generateRuleHeaderLines(
plugin,
configsToRules,
pluginPrefix,
pathPlugin,
pathRuleDoc,
configEmojis,
ignoreConfig,
ruleDocNotices,
Expand Down
71 changes: 68 additions & 3 deletions lib/rule-link.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { join, sep } from 'node:path';
import { countOccurrencesInString } from './string.js';
import { join, sep, relative } from 'node:path';

export function replaceRulePlaceholder(pathOrUrl: string, ruleName: string) {
return pathOrUrl.replace(/\{name\}/gu, ruleName);
Expand All @@ -9,7 +10,7 @@ export function replaceRulePlaceholder(pathOrUrl: string, ruleName: string) {
* @param level
* @returns the relative path to go up the given number of directories
*/
export function goUpLevel(level: number): string {
function goUpLevel(level: number): string {
if (level === 0) {
return '';
}
Expand All @@ -19,6 +20,70 @@ export function goUpLevel(level: number): string {
/**
* Account for how Windows paths use backslashes instead of the forward slashes that URLs require.
*/
export function pathToUrl(path: string): string {
function pathToUrl(path: string): string {
return path.split(sep).join('/');
}

/**
* Get the link to a rule's documentation page.
* Will be relative to the current page.
*/
export function getUrlToRule(
ruleName: string,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
urlCurrentPage: string,
urlRuleDoc?: string
) {
const nestingDepthOfCurrentPage = countOccurrencesInString(
relative(pathPlugin, urlCurrentPage),
sep
);
const relativePathPluginRoot = goUpLevel(nestingDepthOfCurrentPage);

// Ignore plugin prefix if it's included in rule name.
// While we could display the prefix if we wanted, it definitely cannot be part of the link.
const ruleNameWithoutPluginPrefix = ruleName.startsWith(`${pluginPrefix}/`)
? ruleName.slice(pluginPrefix.length + 1)
: ruleName;

return urlRuleDoc
? replaceRulePlaceholder(urlRuleDoc, ruleNameWithoutPluginPrefix)
: pathToUrl(
join(
relativePathPluginRoot,
replaceRulePlaceholder(pathRuleDoc, ruleNameWithoutPluginPrefix)
)
);
}

/**
* Get the markdown link (title and URL) to the rule's documentation.
*/
export function getLinkToRule(
ruleName: string,
pluginPrefix: string,
pathPlugin: string,
pathRuleDoc: string,
urlCurrentPage: string,
includeBackticks: boolean,
urlRuleDoc?: string
) {
// Ignore plugin prefix if it's included in rule name.
// While we could display the prefix if we wanted, it definitely cannot be part of the link.
const ruleNameWithoutPluginPrefix = ruleName.startsWith(`${pluginPrefix}/`)
? ruleName.slice(pluginPrefix.length + 1)
: ruleName;
const urlToRule = getUrlToRule(
ruleName,
pluginPrefix,
pathPlugin,
pathRuleDoc,
urlCurrentPage,
urlRuleDoc
);
return `[${includeBackticks ? '`' : ''}${ruleNameWithoutPluginPrefix}${
includeBackticks ? '`' : ''
}](${urlToRule})`;
}
40 changes: 11 additions & 29 deletions lib/rule-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import { getColumns, COLUMN_HEADER } from './rule-list-columns.js';
import { findSectionHeader, findFinalHeaderLevel } from './markdown.js';
import { getPluginRoot } from './package-json.js';
import { generateLegend } from './rule-list-legend.js';
import { relative, join, sep } from 'node:path';
import { relative } from 'node:path';
import { COLUMN_TYPE, SEVERITY_TYPE } from './types.js';
import { markdownTable } from 'markdown-table';
import camelCase from 'camelcase';
import type {
Plugin,
RuleDetails,
Expand All @@ -26,18 +25,8 @@ import type {
} from './types.js';
import { EMOJIS_TYPE, RULE_TYPE } from './rule-type.js';
import { hasOptions } from './rule-options.js';
import { replaceRulePlaceholder, goUpLevel, pathToUrl } from './rule-link.js';
import { countOccurrencesInString } from './string.js';

// Example: theWeatherIsNice => The Weather Is Nice
function camelCaseStringToTitle(str: string) {
const text = str.replace(/([A-Z])/gu, ' $1');
return text.charAt(0).toUpperCase() + text.slice(1);
}

function isCamelCase(str: string) {
return camelCase(str) === str;
}
import { getLinkToRule } from './rule-link.js';
import { camelCaseStringToTitle, isCamelCase } from './string.js';

function getPropertyFromRule(
plugin: Plugin,
Expand Down Expand Up @@ -140,22 +129,15 @@ function buildRuleRow(
? EMOJI_HAS_SUGGESTIONS
: '',
[COLUMN_TYPE.NAME]() {
// Determine the relative path to the plugin root from the current rule list file so that the rule link can account for this.
const nestingDepthOfCurrentRuleList = countOccurrencesInString(
relative(pathPlugin, pathRuleList),
sep
);
const relativePathPluginRoot = goUpLevel(nestingDepthOfCurrentRuleList);
return `[${rule.name}](${
return getLinkToRule(
rule.name,
pluginPrefix,
pathPlugin,
pathRuleDoc,
pathRuleList,
false,
urlRuleDoc
? replaceRulePlaceholder(urlRuleDoc, rule.name)
: pathToUrl(
join(
relativePathPluginRoot,
replaceRulePlaceholder(pathRuleDoc, rule.name)
)
)
})`;
);
},
[COLUMN_TYPE.OPTIONS]: hasOptions(rule.schema) ? EMOJI_OPTIONS : '',
[COLUMN_TYPE.REQUIRES_TYPE_CHECKING]: rule.requiresTypeChecking
Expand Down
24 changes: 24 additions & 0 deletions lib/string.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
import camelCase from 'camelcase';

export function countOccurrencesInString(str: string, substring: string) {
return str.split(substring).length - 1;
}

export function toSentenceCase(str: string) {
return str.replace(/^\w/u, function (txt) {
return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase();
});
}

export function removeTrailingPeriod(str: string) {
return str.replace(/\.$/u, '');
}

/**
* Example: theWeatherIsNice => The Weather Is Nice
*/
export function camelCaseStringToTitle(str: string) {
const text = str.replace(/([A-Z])/gu, ' $1');
return text.charAt(0).toUpperCase() + text.slice(1);
}

export function isCamelCase(str: string) {
return camelCase(str) === str;
}
Loading

0 comments on commit 29e2ba4

Please sign in to comment.