diff --git a/src/content-linter/scripts/lint-content.js b/src/content-linter/scripts/lint-content.js index 3c5342a4be2d..679a126875da 100755 --- a/src/content-linter/scripts/lint-content.js +++ b/src/content-linter/scripts/lint-content.js @@ -551,7 +551,8 @@ function getMarkdownLintConfig(errorsOnly, runRules) { continue } - if (runRules && !runRules.includes(ruleName)) continue + // Check if the rule should be included based on user-specified rules + if (runRules && !shouldIncludeRule(ruleName, runRules)) continue // Skip british-english-quotes rule in CI/PRs (only run in pre-commit) if (ruleName === 'british-english-quotes' && !isPrecommit) continue @@ -638,6 +639,29 @@ function getCustomRule(ruleName) { return rule } +// Check if a rule should be included based on user-specified rules +// Handles both short names (e.g., GHD053, MD001) and long names (e.g., header-content-requirement, heading-increment) +export function shouldIncludeRule(ruleName, runRules) { + // First check if the rule name itself is in the list + if (runRules.includes(ruleName)) { + return true + } + + // For custom rules, check if any of the rule's names (short or long) are in the runRules list + const customRule = customRules.find((rule) => rule.names.includes(ruleName)) + if (customRule) { + return customRule.names.some((name) => runRules.includes(name)) + } + + // For built-in markdownlint rules, check if any of the rule's names are in the runRules list + const builtinRule = allRules.find((rule) => rule.names.includes(ruleName)) + if (builtinRule) { + return builtinRule.names.some((name) => runRules.includes(name)) + } + + return false +} + /* The severity of the search-replace custom rule is embedded in each individual search rule. This function returns the severity @@ -681,9 +705,7 @@ function isOptionsValid() { } // rules should only contain existing, correctly spelled rules - const allRulesList = Object.values(allRules) - .map((rule) => rule.names) - .flat() + const allRulesList = [...allRules.map((rule) => rule.names).flat(), ...Object.keys(allConfig)] const rules = program.opts().rules || [] for (const rule of rules) { if (!allRulesList.includes(rule)) { diff --git a/src/content-linter/tests/unit/rule-filtering.js b/src/content-linter/tests/unit/rule-filtering.js new file mode 100644 index 000000000000..4ee5a27020bf --- /dev/null +++ b/src/content-linter/tests/unit/rule-filtering.js @@ -0,0 +1,62 @@ +import { describe, test, expect, vi } from 'vitest' +import { shouldIncludeRule } from '../../scripts/lint-content.js' + +// Mock the get-rules module to provide test data for rule definitions +vi.mock('../../lib/helpers/get-rules', () => ({ + allRules: [ + { + names: ['MD001', 'heading-increment'], + description: 'Heading levels should only increment by one level at a time', + }, + { + names: ['MD002', 'first-heading-h1'], + description: 'First heading should be a top level heading', + }, + ], + customRules: [ + { + names: ['GHD053', 'header-content-requirement'], + description: 'Headers must have content below them', + }, + { + names: ['GHD030', 'code-fence-line-length'], + description: 'Code fence content should not exceed line length limit', + }, + ], + allConfig: {}, +})) + +describe('shouldIncludeRule', () => { + test('includes rule by long name', () => { + expect(shouldIncludeRule('heading-increment', ['heading-increment'])).toBe(true) + expect(shouldIncludeRule('header-content-requirement', ['header-content-requirement'])).toBe( + true, + ) + }) + + test('includes built-in rule by short code', () => { + expect(shouldIncludeRule('heading-increment', ['MD001'])).toBe(true) + expect(shouldIncludeRule('first-heading-h1', ['MD002'])).toBe(true) + }) + + test('includes custom rule by short code', () => { + expect(shouldIncludeRule('header-content-requirement', ['GHD053'])).toBe(true) + expect(shouldIncludeRule('code-fence-line-length', ['GHD030'])).toBe(true) + }) + + test('excludes rule not in list', () => { + expect(shouldIncludeRule('heading-increment', ['MD002'])).toBe(false) + expect(shouldIncludeRule('header-content-requirement', ['GHD030'])).toBe(false) + }) + + test('handles multiple rules', () => { + const runRules = ['MD001', 'GHD053', 'some-other-rule'] + expect(shouldIncludeRule('heading-increment', runRules)).toBe(true) + expect(shouldIncludeRule('header-content-requirement', runRules)).toBe(true) + expect(shouldIncludeRule('first-heading-h1', runRules)).toBe(false) + }) + + test('handles unknown rules gracefully', () => { + expect(shouldIncludeRule('non-existent-rule', ['MD001'])).toBe(false) + }) +})