| import { EOL } from 'node:os'; |
| import process from 'node:process'; |
| |
| import { DEFAULT_SEVERITY, RULE_NAME_ALL } from './constants.mjs'; |
| import { DEFAULT_CONFIGURATION_COMMENT } from './utils/configurationComment.mjs'; |
| import assignDisabledRanges from './assignDisabledRanges.mjs'; |
| import { emitDeprecationWarning } from './utils/emitWarning.mjs'; |
| import getStylelintRule from './utils/getStylelintRule.mjs'; |
| import reportUnknownRuleNames from './reportUnknownRuleNames.mjs'; |
| import rules from './rules/index.mjs'; |
| |
| /** @import {Config, LinterOptions, PostcssResult} from 'stylelint' */ |
| |
| /** |
| * @param {LinterOptions} stylelintOptions |
| * @param {PostcssResult} postcssResult |
| * @param {Config} config |
| * @returns {Promise<any>} |
| */ |
| export default async function lintPostcssResult(stylelintOptions, postcssResult, config) { |
| const { abortSignal } = stylelintOptions; |
| |
| postcssResult.stylelint.stylelintError = false; |
| postcssResult.stylelint.stylelintWarning = false; |
| postcssResult.stylelint.quiet = config.quiet; |
| postcssResult.stylelint.quietDeprecationWarnings = stylelintOptions.quietDeprecationWarnings; |
| postcssResult.stylelint.config = config; |
| |
| const postcssDoc = postcssResult.root; |
| |
| if (!('type' in postcssDoc)) { |
| throw new Error('Unexpected Postcss root object!'); |
| } |
| |
| const newlineMatch = postcssDoc.source?.input.css.match(/\r?\n/); |
| const newline = newlineMatch ? newlineMatch[0] : EOL; |
| const configurationComment = config.configurationComment || DEFAULT_CONFIGURATION_COMMENT; |
| const ctx = { configurationComment, newline }; |
| |
| assignDisabledRanges(postcssDoc, postcssResult); |
| |
| const postcssRoots = /** @type {import('postcss').Root[]} */ ( |
| postcssDoc && postcssDoc.constructor.name === 'Document' ? postcssDoc.nodes : [postcssDoc] |
| ); |
| |
| // Promises for the rules. Although the rule code runs synchronously now, |
| // the use of Promises makes it compatible with the possibility of async |
| // rules down the line. |
| /** @type {Array<Promise<any>>} */ |
| const performRules = []; |
| |
| /** @type {string[]} */ |
| let ruleNames; |
| |
| if (config.rules) { |
| const rulesOrder = new Map(Object.keys(rules).map((ruleName, index) => [ruleName, index])); |
| |
| ruleNames = Object.keys(config.rules).sort( |
| (a, b) => (rulesOrder.get(a) ?? -1) - (rulesOrder.get(b) ?? -1), |
| ); |
| } else { |
| ruleNames = []; |
| } |
| |
| // Load all rules in parallel. |
| /** @type {Array<[string, import('stylelint').Rule | undefined]>} */ |
| const ruleEntries = await Promise.all( |
| ruleNames.map(async (ruleName) => { |
| const ruleFunction = await getStylelintRule(ruleName, config); |
| |
| return [ruleName, ruleFunction]; |
| }), |
| ); |
| |
| const timing = process.env.TIMING ? (await import('./timing.mjs')).default : undefined; |
| |
| for (const [ruleName, ruleFunction] of ruleEntries) { |
| if (ruleFunction === undefined) { |
| performRules.push( |
| Promise.all( |
| postcssRoots.map((postcssRoot) => |
| reportUnknownRuleNames(ruleName, postcssRoot, postcssResult), |
| ), |
| ), |
| ); |
| |
| continue; |
| } |
| |
| const ruleSettings = config.rules?.[ruleName]; |
| |
| if (ruleSettings === null || ruleSettings[0] === null) continue; |
| |
| if (ruleFunction.meta?.deprecated && !stylelintOptions.quietDeprecationWarnings) { |
| warnDeprecatedRule(postcssResult, ruleName); |
| } |
| |
| const primaryOption = ruleSettings[0]; |
| const secondaryOptions = ruleSettings[1]; |
| |
| // Log the rule's severity in the PostCSS result |
| const defaultSeverity = config.defaultSeverity || DEFAULT_SEVERITY; |
| |
| postcssResult.stylelint.ruleSeverities[ruleName] = |
| (secondaryOptions && secondaryOptions.severity) || defaultSeverity; |
| postcssResult.stylelint.customMessages[ruleName] = secondaryOptions && secondaryOptions.message; |
| postcssResult.stylelint.customUrls[ruleName] = secondaryOptions && secondaryOptions.url; |
| postcssResult.stylelint.ruleMetadata[ruleName] = ruleFunction.meta || {}; |
| |
| const shouldWarn = ruleFunction.meta?.fixable && !stylelintOptions.quietDeprecationWarnings; |
| const disableFix = secondaryOptions?.disableFix === true; |
| const fix = !disableFix && config.fix && isFixCompatible(postcssResult, ruleName); |
| const context = { |
| ...ctx, |
| // context.fix is unlikely to be removed in the foreseeable future |
| // due to the sheer number of rules in the wild that rely on it |
| get fix() { |
| if (shouldWarn) { |
| emitDeprecationWarning( |
| '`context.fix` is deprecated.', |
| 'CONTEXT_FIX', |
| `Pass a \`fix\` callback to the \`report\` utility of "${ruleName}" instead.`, |
| ); |
| } |
| |
| return fix; |
| }, |
| }; |
| |
| const ruleFn = ruleFunction(primaryOption, secondaryOptions, context); |
| |
| /** |
| * @param {import('postcss').Root} postcssRoot |
| */ |
| async function runRule(postcssRoot) { |
| if (timing?.enabled) { |
| return timing.time(ruleName, () => ruleFn(postcssRoot, postcssResult))(); |
| } |
| |
| return ruleFn(postcssRoot, postcssResult); |
| } |
| |
| performRules.push( |
| Promise.all(postcssRoots.map(runRule)).then((result) => { |
| if (abortSignal?.aborted) { |
| throw abortSignal.reason; |
| } |
| |
| return result; |
| }), |
| ); |
| } |
| |
| return Promise.all(performRules); |
| } |
| |
| /** |
| * using context.fix instead of the fix callback has the drawback |
| * of not honouring the configuration comments in subtle ways |
| * @see file://./../docs/user-guide/options.md#fix for details |
| * @param {PostcssResult} postcssResult |
| * @param {string} name |
| * @returns {boolean} |
| */ |
| function isFixCompatible({ stylelint: { disabledRanges } }, name) { |
| return !disabledRanges[RULE_NAME_ALL]?.length && !disabledRanges[name]; |
| } |
| |
| /** |
| * @param {PostcssResult} result |
| * @param {string} ruleName |
| * @returns {void} |
| */ |
| function warnDeprecatedRule(result, ruleName) { |
| const message = `The "${ruleName}" rule is deprecated.`; |
| |
| emitDeprecationWarning( |
| message, |
| 'RULE', |
| `Please be aware that the "${ruleName}" rule will soon be either removed or renamed.`, |
| ); |
| |
| result.warn(message, { stylelintType: 'deprecation' }); |
| } |