All files / packages/icons/scripts csv-parser.ts

87.23% Statements 41/47
76.74% Branches 33/43
100% Functions 8/8
90% Lines 36/40

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 132 133 134 135                                                                        8x   8x       40x   8x   10x     10x 10x 10x 2x 2x     10x   10x   50x   50x 10x 40x 10x 10x 2x 1x       30x 10x     11x             11x 10x   20x     50x         10x       10x                           8x                   10x   10x 6x     4x 1x     3x    
/**
 * CSV parser for icon metadata
 * Pure CSV-to-ParsedIcon[] parser with no I/O side effects
 */
 
export type IconType = 'standard' | 'utility' | 'action' | 'doctype' | 'custom';
 
export interface IconRow {
  icon_name: string;
  icon_type: string;
  synonyms: string;
  color: string;
  has_ltr_rtl: string;
}
 
export interface ParsedIcon {
  id: string;
  icon_name: string;
  icon_type: IconType;
  file_name: string;
  synonyms: string[];
  color: string;
  color_hex?: string | null;
  has_ltr_rtl: 'yes' | 'no' | '';
  ltr_svg: string | null;
  rtl_svg: string | null;
  common_svg: string | null;
}
 
/**
 * Parse CSV content into structured icon objects
 * @param csvContent - Raw CSV string content
 * @param colorPalette - Palette for resolving color_hex during parse
 * @returns Array of parsed icon objects
 */
export function parseIconsCsv(csvContent: string): ParsedIcon[] {
  const rows = csvContent.trim().split('\n');
 
  Iif (rows.length === 0) {
    return [];
  }
 
  const headers = rows[0].split(',').map((header) => header.trim());
 
  const result = rows.slice(1).map((row) => {
    // Normalize all quote types to straight quotes
    let normalizedRow = row.replace(/[""]/g, '"');
 
    // Handle comma within quoted synonyms field
    const synonymStart = normalizedRow.indexOf('"');
    const synonymEnd = normalizedRow.lastIndexOf('"');
    if (synonymStart !== -1 && synonymEnd !== -1 && synonymStart < synonymEnd) {
      const synonymSub = normalizedRow.substring(synonymStart, synonymEnd + 1);
      normalizedRow = normalizedRow.replace(synonymSub, synonymSub.replaceAll(',', '|'));
    }
 
    const values = normalizedRow.split(',');
 
    const iconRow = headers.reduce(
      (obj, header, index) => {
        const value = (values[index] || '').trim();
 
        if (header === 'has_ltr_rtl') {
          obj[header] = value.toLowerCase();
        } else if (header === 'icon_type') {
          const lowerValue = value.toLowerCase();
          if (lowerValue.includes('standard')) obj[header] = 'standard';
          else if (lowerValue.includes('utility')) obj[header] = 'utility';
          else if (lowerValue.includes('action')) obj[header] = 'action';
          else Eif (lowerValue.includes('doctype')) obj[header] = 'doctype';
          else if (lowerValue.includes('custom')) obj[header] = 'custom';
          else obj[header] = value;
        } else if (header === 'synonyms') {
          const synonyms = value
            .split('|')
            .map((syn) =>
              syn
                .trim()
                .replace(/[^a-zA-Z0-9_\s]/g, '')
                .toLowerCase()
                .replace(/_/g, ' ')
                .trim(),
            )
            .filter((syn) => syn.length > 0);
          obj[header] = [...new Set(synonyms)];
        } else {
          obj[header] = value;
        }
 
        return obj;
      },
      {} as Record<string, any>,
    );
 
    return iconRow;
  });
 
  // Transform into ParsedIcon format with derived attributes
  const parsedIcons: ParsedIcon[] = result.map((icon) => ({
    id: `${icon.icon_name}--${icon.icon_type}`,
    icon_name: icon.icon_name || '',
    icon_type: icon.icon_type || 'standard',
    file_name: icon.icon_name ? `${icon.icon_name}.svg` : '',
    synonyms: icon.synonyms || [],
    color: icon.color || '',
    color_hex: resolveColor(icon.color || ''),
    has_ltr_rtl: icon.has_ltr_rtl || '',
    ltr_svg: null, // Will be set by caller
    rtl_svg: null, // Will be set by caller
    common_svg: null, // Will be set by caller
  }));
 
  return parsedIcons;
}
 
/**
 * Resolve color string to hex value using color palette
 * @param colorString - Color name from CSV (e.g., "blue-50" or "#1b96ff")
 * @param colorPalette - Color palette mapping
 * @returns Hex color string or null if not found
 */
export function resolveColor(colorString: string): string | null {
  const trimmed = colorString.trim();
 
  if (trimmed.startsWith('#')) {
    return trimmed;
  }
 
  if (!trimmed) {
    return null;
  }
 
  return `var(--slds-g-color-palette-${trimmed})`;
}