All files / packages/icons/.claude/skills/icons-release/scripts/lib validate.js

0% Statements 0/64
0% Branches 0/45
0% Functions 0/9
0% Lines 0/62

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                                                                                                                                                                                                                                         
'use strict'
 
const fs = require('fs')
const path = require('path')
const { resolveColorHex } = require('./colors')
const { checkDimensions } = require('./dimensions')
const { REPO_TYPES } = require('./types')
 
function validateName(name) {
  const errors = []
  if (name !== name.toLowerCase()) {
    errors.push('Icon name should be in lowercase.')
    return errors
  }
  if (name.includes(' ')) {
    errors.push('Icon name should not contain spaces.')
    return errors
  }
  if (!/^[a-z_]+$/.test(name)) {
    errors.push('Only underscores are allowed as special characters in icon name.')
  }
  return errors
}
 
function validateSynonyms(synonyms) {
  const errors = []
  ;(synonyms || []).forEach(syn => {
    if (!/^[a-z\s]+$/.test(syn)) {
      errors.push(`Synonym "${syn}" must be lowercase without special characters.`)
    }
  })
  return errors
}
 
function validateColor(icon) {
  const errors = []
  const warnings = []
  const color = String(icon.color || '')
  const isUtilityOrDoctype = icon.icon_type === 'utility' || icon.icon_type === 'doctype'
 
  if (isUtilityOrDoctype && color.length !== 0) {
    warnings.push('Color is present for Utility/Doctype icon but will be ignored.')
  }
  if (!isUtilityOrDoctype) {
    const isStandardOrAction = icon.icon_type === 'standard' || icon.icon_type === 'action'
    if (isStandardOrAction && color.length === 0) {
      errors.push('Color is required for Standard or Action icons.')
    }
    if (color && !resolveColorHex(color)) {
      errors.push('Color reference not found in color palette.')
    }
  }
  return { errors, warnings }
}
 
function validateSize(icon, svgContent) {
  if (!svgContent) return []
  return checkDimensions(icon.icon_type, svgContent).map(
    msg => `${icon.icon_name}.svg: ${msg}`
  )
}
 
/**
 * Validate one icon record against its SVG content and the repo state.
 *
 * @param {object} icon  { icon_name, icon_type, color, synonyms }
 * @param {object} ctx   { svgContent, repoRoot }
 * @returns {{ errors: string[], warnings: string[] }}
 */
function validateIcon(icon, ctx = {}) {
  const { svgContent = null, repoRoot = '.' } = ctx
  let errors = []
  let warnings = []
 
  if (!REPO_TYPES.includes(icon.icon_type)) {
    return { errors: [`Unsupported icon type "${icon.icon_type}".`], warnings }
  }
 
  errors = errors.concat(validateName(icon.icon_name))
  errors = errors.concat(validateSynonyms(icon.synonyms))
 
  const colorResult = validateColor(icon)
  errors = errors.concat(colorResult.errors)
  warnings = warnings.concat(colorResult.warnings)
 
  if (!svgContent) {
    errors.push('Missing SVG.')
  } else {
    errors = errors.concat(validateSize(icon, svgContent))
  }
 
  // Already present in the repo? (we should not re-add an existing icon)
  const repoCommon = path.join(repoRoot, 'svg', icon.icon_type, 'common', `${icon.icon_name}.svg`)
  const repoDirect = path.join(repoRoot, 'svg', icon.icon_type, `${icon.icon_name}.svg`)
  if (fs.existsSync(repoCommon) || fs.existsSync(repoDirect)) {
    errors.push(`Already present in repo at svg/${icon.icon_type}/.`)
  }
 
  return { errors, warnings }
}
 
/**
 * Return the set of "<name>--<type>" ids that appear more than once.
 */
function findDuplicates(icons) {
  const seen = new Set()
  const dupes = new Set()
  icons.forEach(icon => {
    const key = `${icon.icon_name}--${icon.icon_type}`
    if (seen.has(key)) dupes.add(key)
    else seen.add(key)
  })
  return dupes
}
 
module.exports = { validateIcon, findDuplicates }