Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 1x 1x 25x 5x 20x 25x 20x 20x 27x 27x 2x 2x 25x 25x 25x 27x 27x 5x 25x 27x 2x 64x 32x 32x 3x 3x 3x 1x 1x 3x 29x 26x 26x 26x 1x 1x 29x 29x 29x 29x 29x 29x 30x 64x | import path from 'node:path';
import stylelint from 'stylelint';
import valueParser from 'postcss-value-parser';
import type { Declaration } from 'postcss';
import metadata, { getMergedMetadata } from '../metadata/metadata.js';
import {
validateNamespace,
validateComponentTokens,
tokenize,
shouldValidate,
formatErrors,
formatLegacyWarning,
formatPrivateHookWarning,
} from '../utils/index.js';
import { isPrivateHook, stripPrivatePrefix, isPrivateComponentHook } from '../deprecated/private-hooks.js';
import type { RuleOptions } from '../types.js';
const { report, validateOptions } = stylelint.utils;
const ruleName = 'sds-stylelint-plugin/design-token-pattern';
function deriveContext(decl: Declaration): string | null {
if (decl.source?.input?.file) {
return path.parse(decl.source.input.file).name.split('.')[0];
}
const sel = (decl.parent as any)?.selector as string | undefined;
Iif (!sel) return null;
const match = /\.([a-z][a-z0-9]*)/.exec(sel);
return match ? match[1] : null;
}
function runValidation(
displayValue: string,
tokens: string[],
decl: Declaration,
result: stylelint.PostcssResult,
meta: ReturnType<typeof getMergedMetadata>,
privatePrefix: string,
): void {
const nsResult = validateNamespace(tokens[0], privatePrefix);
if (!nsResult.valid) {
report({
ruleName,
result,
message: formatErrors(displayValue, [
{ segment: 'namespace', expected: nsResult.names, received: tokens[0] },
]),
node: decl,
});
return;
}
const scope = tokens[1];
const expectedComponent = deriveContext(decl);
const aliases = meta.legacyPropertyAliases || {};
const { errors, warnings } = validateComponentTokens(tokens, scope, expectedComponent, meta, aliases);
if (errors.length > 0) {
report({ ruleName, result, message: formatErrors(displayValue, errors), node: decl });
}
const legacyWarnings = warnings?.filter((w) => w.type === 'legacy-syntax') ?? [];
if (legacyWarnings.length > 0) {
report({
ruleName,
result,
message: formatLegacyWarning(displayValue, legacyWarnings),
node: decl,
severity: 'warning',
});
}
}
function processNode(
node: any,
decl: Declaration,
result: stylelint.PostcssResult,
meta: ReturnType<typeof getMergedMetadata>,
privatePrefix: string,
): void {
if (node.type !== 'word') return;
const rawValue: string = node.value;
// --- Deprecated: private hooks (--_slds-c-*) ---
if (isPrivateHook(rawValue)) {
const strippedValue = stripPrivatePrefix(rawValue);
const strippedTokens = tokenize(strippedValue);
if (isPrivateComponentHook(strippedTokens)) {
report({
ruleName,
result,
message: formatPrivateHookWarning(rawValue),
node: decl,
severity: 'warning',
});
runValidation(strippedValue, strippedTokens, decl, result, meta, privatePrefix);
}
return;
}
// --- End deprecated block ---
if (!shouldValidate(rawValue, privatePrefix)) return;
const tokens = tokenize(rawValue);
Iif (!tokens[1] || (tokens[1] !== 'c' && tokens[1] !== 's')) return;
runValidation(rawValue, tokens, decl, result, meta, privatePrefix);
}
type Rule = Parameters<typeof stylelint.createPlugin>[1];
const messages = stylelint.utils.ruleMessages(ruleName, {});
const componentPlugin = Object.assign(
(primary: boolean, options?: RuleOptions) => (root: any, result: stylelint.PostcssResult) => {
const valid = validateOptions(result, ruleName, { actual: primary });
Iif (!valid) return;
const meta = getMergedMetadata(metadata, options ?? {});
const privatePrefix = options?.privateSyntax || metadata.privateSyntax[0];
root.walkDecls((decl: Declaration) => {
valueParser(decl.value).walk((node: any) => {
processNode(node, decl, result, meta, privatePrefix);
});
});
},
{ ruleName, messages },
) satisfies Rule;
export default stylelint.createPlugin(ruleName, componentPlugin);
|