All files / packages/sds-stylelint-config/src/validators/component index.ts

100% Statements 41/41
92.85% Branches 26/28
100% Functions 5/5
100% Lines 38/38

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                                  46x               45x 4x                                 102x 19x 19x 11x 11x                   41x 41x 41x   41x 1x         1x     40x 40x 3x     40x 40x 40x 3x 3x     37x 37x 1x 1x     36x 36x 2x 2x   34x 34x   34x 34x 34x   34x 1x             34x        
/**
 * Component and shared token validator.
 *
 * Pattern: --{ns}-{c|s}-{component}[-{element}]-{category}-{property}[-{attribute}][-{state}][-{pseudoState}]
 *
 * Component hooks (--slds-c-*) and shared tokens (--slds-s-*) follow the
 * same naming structure. The scope character is a parameter, not a branching
 * condition.
 *
 * @module validators/component
 */
 
import type { Metadata, ValidationResult, ValidationError, ValidationWarning } from '../../types.js';
import type { TokenMatch } from '../shared/helpers.js';
import { err, matchToken } from '../shared/helpers.js';
 
function findCategoryIndex(rest: string[], categories: string[]): number {
  return rest.findIndex((seg) => categories.includes(seg));
}
 
function addLegacyWarning(
  warnings: ValidationWarning[],
  segment: string,
  m: TokenMatch,
): void {
  if (m.isLegacy && m.match) {
    warnings.push({
      type: 'legacy-syntax',
      segment,
      received: m.received ?? '',
      canonical: m.match,
    });
  }
}
 
function processOptionalSegment(
  rest: string[],
  idx: number,
  validList: string[],
  segment: string,
  warnings: ValidationWarning[],
  aliases: Record<string, string> = {},
): number {
  if (idx >= rest.length) return idx;
  const m = matchToken(rest, idx, validList, aliases);
  if (!m.match) return idx;
  addLegacyWarning(warnings, segment, m);
  return idx + m.consumed;
}
 
export function validateComponentTokens(
  tokens: string[],
  scope: string,
  expectedComponent: string | null,
  meta: Metadata,
  aliases: Record<string, string> = {},
): ValidationResult {
  const errors: ValidationError[] = [];
  const warnings: ValidationWarning[] = [];
  const cv = meta.component.valid;
 
  if (tokens.length < 5) {
    errors.push(err(
      'structure',
      'at least 5 segments (namespace-scope-component-category-property)',
      tokens.join('-'),
    ));
    return { errors, warnings };
  }
 
  const component = tokens[2];
  if (expectedComponent && component !== expectedComponent) {
    errors.push(err('context', [expectedComponent], component));
  }
 
  const rest = tokens.slice(3);
  const categoryIdx = findCategoryIndex(rest, cv.category);
  if (categoryIdx === -1) {
    errors.push(err('category', cv.category, rest.join('-') || '(missing)'));
    return { errors, warnings };
  }
 
  let idx = categoryIdx + 1;
  if (idx >= rest.length) {
    errors.push(err('property', cv.property, '(missing)'));
    return { errors, warnings };
  }
 
  const propMatch = matchToken(rest, idx, cv.property, aliases);
  if (!propMatch.match) {
    errors.push(err('property', cv.property, rest[idx]));
    return { errors, warnings };
  }
  addLegacyWarning(warnings, 'property', propMatch);
  idx += propMatch.consumed;
 
  idx = processOptionalSegment(rest, idx, cv.attribute, 'attribute', warnings, aliases);
  idx = processOptionalSegment(rest, idx, cv.state, 'state', warnings);
  idx = processOptionalSegment(rest, idx, cv.pseudoState, 'pseudoState', warnings);
 
  if (idx < rest.length) {
    errors.push(err(
      'structure',
      'Valid segments after property are: attribute, state, pseudoState',
      rest.slice(idx).join('-'),
    ));
  }
 
  return { errors, warnings };
}
 
export default validateComponentTokens;