All files / packages/design-tokens/src/validators/cross-platform-colors mobile-parser-utils.js

100% Statements 38/38
96.15% Branches 25/26
100% Functions 5/5
100% Lines 29/29

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                              50x 26x 2x                       59x 4x 3x                         57x 41x 41x 41x                                   19x   18x 18x   11x   19x 19x 8x 8x     3x 3x 1x 1x     2x                       40x     40x 43x 43x 13x   43x      
/**
 * Shared utilities for parsing Android (Kotlin) and iOS (Swift) color token files.
 * Both platforms follow the same structural pattern: a class/object declaration
 * establishes the current light/dark mode, then individual token lines are either
 * direct hex values or references to other tokens.
 */
 
import { MODE } from './color-utils.js';
 
/**
 * Determine MODE.LIGHT / MODE.DARK from a class or object name, or null if indeterminate.
 * @param {string} className
 * @returns {string|null}
 */
export function classNameToMode(className) {
  if (className.includes('Light')) return MODE.LIGHT;
  if (className.includes('Dark')) return MODE.DARK;
  return null;
}
 
/**
 * If the token belongs to a palette class, prefix it with 'palette' so that
 * platform-specific normalizers can produce the correct CSS token name.
 * @param {string} tokenName - raw property name from the source file
 * @param {string|null} currentClass - class/object name currently being parsed
 * @param {RegExp} paletteClassRe - regex that matches a palette class name
 * @returns {string}
 */
export function resolvePaletteTokenName(tokenName, currentClass, paletteClassRe) {
  if (!currentClass?.includes('Palette')) return tokenName;
  if (!paletteClassRe.exec(currentClass)) return tokenName;
  return `palette${tokenName.charAt(0).toUpperCase() + tokenName.slice(1)}`;
}
 
/**
 * Store a directly-resolved color into both the staging directColors map and the output colors map.
 * @param {Map<string, string>} directColors - staging map keyed as `tokenName-mode`
 * @param {Map<string, string>} colors - output map
 * @param {string} fullTokenName
 * @param {string} hexValue
 * @param {string|null} mode
 * @param {(name: string) => string} normalizeTokenName
 */
export function storeDirectToken(directColors, colors, fullTokenName, hexValue, mode, normalizeTokenName) {
  if (!mode) return;
  directColors.set(`${fullTokenName}-${mode}`, hexValue);
  const standardName = normalizeTokenName(fullTokenName);
  Eif (standardName) colors.set(`${standardName}-${mode}`, hexValue);
}
 
/**
 * Attempt to resolve one reference assignment into a concrete hex value.
 * Returns true if the token was newly added to `colors`.
 * @param {{ tokenName: string, refProperty: string, mode: string|null, refMode?: string|null }} assignment
 * @param {Map<string, string>} directColors
 * @param {Map<string, string>} colors
 * @param {(name: string) => string} normalizeTokenName
 * @returns {boolean}
 */
export function tryResolveAssignment(
  { tokenName, refProperty, mode, refMode },
  directColors,
  colors,
  normalizeTokenName,
) {
  if (!mode) return false;
 
  const standardName = `${normalizeTokenName(tokenName)}-${mode}`;
  if (colors.has(standardName)) return false;
 
  const lookupMode = refMode ?? mode;
 
  const refHex = directColors.get(`${refProperty}-${lookupMode}`);
  if (refHex) {
    colors.set(standardName, refHex);
    return true;
  }
 
  const resolvedRefValue = colors.get(`${normalizeTokenName(refProperty)}-${lookupMode}`);
  if (resolvedRefValue) {
    colors.set(standardName, resolvedRefValue);
    return true;
  }
 
  return false;
}
 
/**
 * Iteratively resolve reference assignments until no new tokens can be resolved
 * or the maximum iteration limit is reached (prevents infinite loops on circular refs).
 * @param {Array<{tokenName: string, refProperty: string, mode: string|null, refMode?: string|null}>} assignments
 * @param {Map<string, string>} directColors
 * @param {Map<string, string>} colors - mutated in place
 * @param {(name: string) => string} normalizeTokenName
 */
export function resolveReferences(assignments, directColors, colors, normalizeTokenName) {
  let maxIterations = 10;
  let resolvedCount;
 
  do {
    resolvedCount = 0;
    for (const assignment of assignments) {
      if (tryResolveAssignment(assignment, directColors, colors, normalizeTokenName)) resolvedCount++;
    }
    maxIterations--;
  } while (resolvedCount > 0 && maxIterations > 0);
}