{"record":{"id":"40650df08ba1f576","repo":"CherryHQ/cherry-studio","slug":"theme-contract-sourcename-declares-invalid-cu","errorCode":null,"errorMessage":"[theme-contract] ${sourceName} declares invalid custom property ${name}","messagePattern":"\\[theme-contract\\] (.+?) declares invalid custom property (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/scripts/validate-theme-contract.ts","lineNumber":61,"sourceCode":"}\n\ntype SourceEntry = readonly [source: string, css: string]\n\nfunction stripComments(source: string): string {\n  return source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n}\n\nfunction escapeRegExp(value: string): string {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction extractDeclarations(source: string, sourceName: string): Declaration[] {\n  const declarations = [...stripComments(source).matchAll(/(?=(?:^|[;{])\\s*(--[^\\s:;{}]+)\\s*:\\s*([^;{}]+);)/g)]\n\n  return declarations.map((match) => {\n    const name = match[1]\n    if (!CUSTOM_PROPERTY_NAME_PATTERN.test(name)) {\n      throw new Error(`[theme-contract] ${sourceName} declares invalid custom property ${name}`)\n    }\n\n    return {\n      name,\n      value: match[2].trim(),\n      source: sourceName\n    }\n  })\n}\n\nfunction extractModeDeclarations(source: string, sourceName: string, selector: ':root' | '.dark'): Declaration[] {\n  const declarations: Declaration[] = []\n  const blockPattern = new RegExp(`${escapeRegExp(selector)}\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}`, 'g')\n\n  for (const match of stripComments(source).matchAll(blockPattern)) {\n    declarations.push(...extractDeclarations(match[1], sourceName))\n  }\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/scripts/validate-theme-contract.ts#L43-L79","documentation":"Thrown by the theme contract validator (validate-theme-contract.ts) when a CSS source file declares a custom property whose name does not match the pattern ^--[a-z0-9-]+$. This means the property name contains uppercase letters, underscores, special characters, or doesn't start with --. The validator enforces a strict kebab-case naming convention for all CSS custom properties in the theme system.","triggerScenarios":"Any CSS file processed by extractDeclarations contains a declaration like --myVar: red; (underscore), --My-Color: red; (uppercase), --color!: red; (special char), or ---triple: red; (extra dash). The regex extracts all custom property declarations and validates each name against CUSTOM_PROPERTY_NAME_PATTERN.","commonSituations":"A developer adds a CSS custom property using camelCase or snake_case instead of kebab-case. Copy-pasting CSS from an external library that uses a different naming convention. A typo introducing an invalid character. Mixing CSS naming conventions from different projects.","solutions":["Rename the custom property to use kebab-case matching ^--[a-z0-9-]+$: only lowercase letters, digits, and hyphens after the leading --.","If the property came from an external source, adapt it to the contract naming convention when bringing it into the theme system.","Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts."],"exampleFix":"/* before */\n--primary_Color: #3b82f6;\n--MyBackground: #ffffff;\n\n/* after — kebab-case only */\n--primary-color: #3b82f6;\n--my-background: #ffffff;","handlingStrategy":"validation","validationCode":"// Validate custom property names in CSS before committing\nimport { readFileSync } from 'node:fs'\n\nconst VALID_NAME = /^--[a-z0-9-]+$/\n\nfunction checkPropertyNames(cssPath: string): void {\n  const source = readFileSync(cssPath, 'utf8').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n  const decls = [...source.matchAll(/(?=(?:^|[;{])\\s*(--[^\\s:;{}]+)\\s*:)/g)]\n  for (const match of decls) {\n    if (!VALID_NAME.test(match[1])) {\n      throw new Error(`${cssPath}: invalid property name ${match[1]} — use kebab-case`)\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always use kebab-case for CSS custom property names: --my-variable, not --myVariable or --my_variable.","Run the theme contract validator (validate-theme-contract.ts) in CI to enforce naming.","When bringing CSS from external sources, rename properties to match the kebab-case convention.","Avoid uppercase letters, underscores, and special characters in custom property names."],"tags":["theme-contract","build-validation","css","naming-convention","custom-properties"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}