All files / packages/sds-stylelint-config/src/plugins design-token-global-pattern.ts

94.73% Statements 36/38
84.61% Branches 22/26
100% Functions 6/6
100% Lines 32/32

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                            1x 1x                 78x 49x   24x 24x   24x 24x   22x 1x 1x     21x 21x 1x               1x     20x 78x   78x 3x     20x 78x 1x                       1x   1x 27x 27x 27x   27x 27x   27x 45x 78x                
import stylelint from 'stylelint';
import valueParser from 'postcss-value-parser';
import type { Declaration } from 'postcss';
import metadata, { getMergedMetadata } from '../metadata/metadata.js';
import {
  validateNamespace,
  validateGlobalTokens,
  tokenize,
  shouldValidate,
  formatErrors,
  formatLegacyWarning,
} from '../utils/index.js';
import type { RuleOptions } from '../types.js';
 
const { report, validateOptions } = stylelint.utils;
const ruleName = 'sds-stylelint-plugin/design-token-global-pattern';
 
function processNode(
  node: valueParser.WordNode,
  decl: Declaration,
  result: stylelint.PostcssResult,
  meta: ReturnType<typeof getMergedMetadata>,
  privatePrefix: string,
): void {
  if (node.type !== 'word') return;
  if (!shouldValidate(node.value, privatePrefix)) return;
 
  const tokens = tokenize(node.value);
  Iif (!tokens[1]) return;
 
  const nonGlobalScopes = new Set(['c', 's', 'r']);
  if (nonGlobalScopes.has(tokens[1])) return;
 
  if (tokens[1] !== 'g') {
    report({ ruleName, result, message: `"${node.value}" requires a scope of ["g"]`, node: decl });
    return;
  }
 
  const nsResult = validateNamespace(tokens[0], privatePrefix);
  if (!nsResult.valid) {
    report({
      ruleName,
      result,
      message: formatErrors(node.value, [
        { segment: 'namespace', expected: nsResult.names, received: tokens[0] },
      ]),
      node: decl,
    });
    return;
  }
 
  const aliases = meta.legacyPropertyAliases || {};
  const { errors, warnings } = validateGlobalTokens(tokens, meta, aliases);
 
  if (errors.length > 0) {
    report({ ruleName, result, message: formatErrors(node.value, errors), node: decl });
  }
 
  const legacyWarnings = warnings?.filter((w) => w.type === 'legacy-syntax') ?? [];
  if (legacyWarnings.length > 0) {
    report({
      ruleName,
      result,
      message: formatLegacyWarning(node.value, legacyWarnings),
      node: decl,
      severity: 'warning',
    });
  }
}
 
type Rule = Parameters<typeof stylelint.createPlugin>[1];
 
const messages = stylelint.utils.ruleMessages(ruleName, {});
 
const globalTokens = Object.assign(
  (primary: boolean, options?: RuleOptions) => (root: any, result: stylelint.PostcssResult) => {
    const valid = validateOptions(result, ruleName, { actual: primary, possible: [true] });
    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, globalTokens);