All files / packages/sds-stylelint-config/src/validators/global color.ts

100% Statements 30/30
92.59% Branches 25/27
100% Functions 1/1
100% Lines 30/30

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                28x 28x   28x 2x 2x       26x 7x 2x 2x   5x 1x   5x       19x 6x 6x 1x 1x   5x 5x 1x   5x       13x 1x 1x     12x 12x 12x   12x 1x     12x    
import type { ValidationResult, ValidationError, ValidationWarning } from '../../types.js';
import { err, hasNumericRange, resolveDescriptor } from './helpers.js';
import { COLOR_SEMANTIC_CONCEPTS } from './__generated-allowlists.js';
 
export default function validateColor(
  segments: string[],
  aliases: Record<string, string> = {},
): ValidationResult {
  const errors: ValidationError[] = [];
  const warnings: ValidationWarning[] = [];
 
  if (segments.length === 0) {
    errors.push(err('color', 'at least one descriptor after color', '(empty)'));
    return { errors, warnings };
  }
 
  // Palette: convention-based (open set)
  if (segments[0] === 'palette') {
    if (segments.length < 3) {
      errors.push(err('palette', 'palette-{color-name}-{range}', segments.join('-')));
      return { errors, warnings };
    }
    if (!hasNumericRange(segments)) {
      errors.push(err('range', 'numeric range as final segment', segments.at(-1) ?? '(missing)'));
    }
    return { errors, warnings };
  }
 
  // Base scale: convention-based (open set)
  if (segments.includes('base')) {
    const baseIdx = segments.indexOf('base');
    if (baseIdx === 0) {
      errors.push(err('base-role', 'a role name before "base"', '(missing)'));
      return { errors, warnings };
    }
    const afterBase = segments.slice(baseIdx + 1);
    if (afterBase.length !== 1 || Number.isNaN(Number(afterBase[0]))) {
      errors.push(err('range', 'numeric range after base', afterBase.join('-') || '(missing)'));
    }
    return { errors, warnings };
  }
 
  // Semantic: closed allowlist
  if (!hasNumericRange(segments)) {
    errors.push(err('range', 'numeric range as final segment', segments.at(-1) ?? '(missing)'));
    return { errors, warnings };
  }
 
  const descriptorSegments = segments.slice(0, -1);
  const { descriptor, warnings: aliasWarnings } = resolveDescriptor(descriptorSegments, aliases);
  warnings.push(...aliasWarnings);
 
  if (!COLOR_SEMANTIC_CONCEPTS.includes(descriptor)) {
    errors.push(err('semantic-concept', COLOR_SEMANTIC_CONCEPTS, descriptor));
  }
 
  return { errors, warnings };
}