|
|
|
@ -34,10 +34,7 @@ import {
|
|
|
|
type EidosConfig,
|
|
|
|
type EidosConfig,
|
|
|
|
type EidosConfigSnapshot,
|
|
|
|
type EidosConfigSnapshot,
|
|
|
|
type RecipeTokenMap,
|
|
|
|
type RecipeTokenMap,
|
|
|
|
type SizePrimitive,
|
|
|
|
type ThemeDefinition
|
|
|
|
type TextMetric,
|
|
|
|
|
|
|
|
type ThemeDefinition,
|
|
|
|
|
|
|
|
type TypographyPrimitiveSet
|
|
|
|
|
|
|
|
} from './config-types'
|
|
|
|
} from './config-types'
|
|
|
|
import { createEidosCssContract } from './contract'
|
|
|
|
import { createEidosCssContract } from './contract'
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
@ -190,7 +187,8 @@ function validateEidosConfigShape(
|
|
|
|
'motion',
|
|
|
|
'motion',
|
|
|
|
'icon',
|
|
|
|
'icon',
|
|
|
|
'opacity',
|
|
|
|
'opacity',
|
|
|
|
'zIndex'
|
|
|
|
'zIndex',
|
|
|
|
|
|
|
|
'shadow'
|
|
|
|
] as const
|
|
|
|
] as const
|
|
|
|
|
|
|
|
|
|
|
|
for (const key of primitiveKeys) {
|
|
|
|
for (const key of primitiveKeys) {
|
|
|
|
@ -490,13 +488,23 @@ function validateColorScale(
|
|
|
|
scale: ColorScale,
|
|
|
|
scale: ColorScale,
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
|
|
|
|
if (!isPlainRecord(scale)) {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path,
|
|
|
|
|
|
|
|
message: 'color scale must be a plain object'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const step of COLOR_SCALE_STEPS) {
|
|
|
|
for (const step of COLOR_SCALE_STEPS) {
|
|
|
|
if (!hasOwn(scale, step) || scale[step] === '') {
|
|
|
|
if (!hasOwn(scale, step)) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.${step}`,
|
|
|
|
path: `${path}.${step}`,
|
|
|
|
message: 'color scales must define every step from 1 to 12'
|
|
|
|
message: 'color scales must define every step from 1 to 12'
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(`${path}.${step}`, scale[step], issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -591,79 +599,145 @@ function validateColorRoleSlots(
|
|
|
|
function validateTypography(options: EidosConfig, issues: EidosValidationIssue[]): void {
|
|
|
|
function validateTypography(options: EidosConfig, issues: EidosValidationIssue[]): void {
|
|
|
|
const typography = options.primitives.typography
|
|
|
|
const typography = options.primitives.typography
|
|
|
|
if (!typography) return
|
|
|
|
if (!typography) return
|
|
|
|
|
|
|
|
const sizes = readRequiredPlainRecord(
|
|
|
|
|
|
|
|
'primitives.typography.sizes',
|
|
|
|
|
|
|
|
typography.sizes,
|
|
|
|
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
const families = readRequiredPlainRecord(
|
|
|
|
|
|
|
|
'primitives.typography.families',
|
|
|
|
|
|
|
|
typography.families,
|
|
|
|
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
const weights = readRequiredPlainRecord(
|
|
|
|
|
|
|
|
'primitives.typography.weights',
|
|
|
|
|
|
|
|
typography.weights,
|
|
|
|
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
const styles = readRequiredPlainRecord(
|
|
|
|
|
|
|
|
'primitives.typography.styles',
|
|
|
|
|
|
|
|
typography.styles,
|
|
|
|
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for (const size of TEXT_SIZES) {
|
|
|
|
if (sizes) {
|
|
|
|
const metric = typography.sizes[size]
|
|
|
|
for (const size of TEXT_SIZES) {
|
|
|
|
if (!metric) {
|
|
|
|
const metric = sizes[size]
|
|
|
|
issues.push({
|
|
|
|
if (!metric) {
|
|
|
|
path: `primitives.typography.sizes.${size}`,
|
|
|
|
issues.push({
|
|
|
|
message: 'every canonical text size must be defined'
|
|
|
|
path: `primitives.typography.sizes.${size}`,
|
|
|
|
})
|
|
|
|
message: 'every canonical text size must be defined'
|
|
|
|
continue
|
|
|
|
})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
validateTextMetric(`primitives.typography.sizes.${size}`, metric, issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
validateTextMetric(`primitives.typography.sizes.${size}`, metric, issues)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const name of Object.keys(typography.sizes)) {
|
|
|
|
for (const name of Object.keys(sizes)) {
|
|
|
|
validateCssTokenSuffix(`primitives.typography.sizes.${name}`, name, 'typography size', issues)
|
|
|
|
validateCssTokenSuffix(
|
|
|
|
if (!TEXT_SIZE_SET.has(name)) {
|
|
|
|
`primitives.typography.sizes.${name}`,
|
|
|
|
issues.push({
|
|
|
|
name,
|
|
|
|
path: `primitives.typography.sizes.${name}`,
|
|
|
|
'typography size',
|
|
|
|
message: 'unknown typography size key'
|
|
|
|
issues
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!TEXT_SIZE_SET.has(name)) {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path: `primitives.typography.sizes.${name}`,
|
|
|
|
|
|
|
|
message: 'unknown typography size key'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const [name, family] of Object.entries(typography.families)) {
|
|
|
|
if (families) {
|
|
|
|
validateCssTokenSuffix(
|
|
|
|
for (const [name, family] of Object.entries(families)) {
|
|
|
|
`primitives.typography.families.${name}`,
|
|
|
|
validateCssTokenSuffix(
|
|
|
|
name,
|
|
|
|
`primitives.typography.families.${name}`,
|
|
|
|
'typography family',
|
|
|
|
name,
|
|
|
|
issues
|
|
|
|
'typography family',
|
|
|
|
)
|
|
|
|
issues
|
|
|
|
if (family.family.trim() === '') {
|
|
|
|
)
|
|
|
|
issues.push({
|
|
|
|
if (!isPlainRecord(family)) {
|
|
|
|
path: `primitives.typography.families.${name}.family`,
|
|
|
|
issues.push({
|
|
|
|
message: 'font family name cannot be empty'
|
|
|
|
path: `primitives.typography.families.${name}`,
|
|
|
|
})
|
|
|
|
message: 'typography family must be a plain object'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(
|
|
|
|
|
|
|
|
`primitives.typography.families.${name}.family`,
|
|
|
|
|
|
|
|
family.family,
|
|
|
|
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const name of Object.keys(typography.weights)) {
|
|
|
|
if (weights) {
|
|
|
|
validateCssTokenSuffix(
|
|
|
|
for (const name of Object.keys(weights)) {
|
|
|
|
`primitives.typography.weights.${name}`,
|
|
|
|
validateCssTokenSuffix(
|
|
|
|
name,
|
|
|
|
`primitives.typography.weights.${name}`,
|
|
|
|
'typography weight',
|
|
|
|
name,
|
|
|
|
issues
|
|
|
|
'typography weight',
|
|
|
|
)
|
|
|
|
issues
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(`primitives.typography.weights.${name}`, weights[name], issues)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const [name, style] of Object.entries(typography.styles)) {
|
|
|
|
if (styles) {
|
|
|
|
const path = `primitives.typography.styles.${name}`
|
|
|
|
for (const [name, style] of Object.entries(styles)) {
|
|
|
|
validateCssTokenSuffix(path, name, 'typography style', issues)
|
|
|
|
const path = `primitives.typography.styles.${name}`
|
|
|
|
|
|
|
|
validateCssTokenSuffix(path, name, 'typography style', issues)
|
|
|
|
|
|
|
|
|
|
|
|
if (style.family !== undefined && !hasOwn(typography.families, style.family)) {
|
|
|
|
if (!isPlainRecord(style)) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.family`,
|
|
|
|
path,
|
|
|
|
message: `typography style points to missing family "${style.family}"`
|
|
|
|
message: 'typography style must be a plain object'
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
const familyRef = style.family
|
|
|
|
typeof style.weight === 'string' &&
|
|
|
|
if (
|
|
|
|
style.weight.trim() !== '' &&
|
|
|
|
familyRef !== undefined &&
|
|
|
|
!hasOwn(typography.weights, style.weight)
|
|
|
|
(typeof familyRef !== 'string' || !families || !hasOwn(families, familyRef))
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.weight`,
|
|
|
|
path: `${path}.family`,
|
|
|
|
message: `typography style points to missing weight "${style.weight}"`
|
|
|
|
message: `typography style points to missing family "${String(familyRef)}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validateTypographySizeValue(`${path}.size`, style.size, typography, issues)
|
|
|
|
const weightRef = style.weight
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof weightRef === 'string' &&
|
|
|
|
|
|
|
|
weightRef.trim() !== '' &&
|
|
|
|
|
|
|
|
(!weights || !hasOwn(weights, weightRef))
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path: `${path}.weight`,
|
|
|
|
|
|
|
|
message: `typography style points to missing weight "${weightRef}"`
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validateTypographySizeValue(`${path}.size`, style.size, sizes, issues)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function readRequiredPlainRecord(
|
|
|
|
|
|
|
|
path: string,
|
|
|
|
|
|
|
|
value: unknown,
|
|
|
|
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
|
|
|
|
): Record<string, unknown> | undefined {
|
|
|
|
|
|
|
|
if (isPlainRecord(value)) return value
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path,
|
|
|
|
|
|
|
|
message: 'Eidos config entries must be plain objects'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function validateSizePrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void {
|
|
|
|
function validateSizePrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void {
|
|
|
|
const sizes = options.primitives.size
|
|
|
|
const sizes = options.primitives.size
|
|
|
|
if (!sizes) return
|
|
|
|
if (!sizes) return
|
|
|
|
@ -694,36 +768,64 @@ function validateSizePrimitives(options: EidosConfig, issues: EidosValidationIss
|
|
|
|
|
|
|
|
|
|
|
|
function validateSizePrimitive(
|
|
|
|
function validateSizePrimitive(
|
|
|
|
path: string,
|
|
|
|
path: string,
|
|
|
|
size: SizePrimitive,
|
|
|
|
size: unknown,
|
|
|
|
options: EidosConfig,
|
|
|
|
options: EidosConfig,
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
if (!hasOwn(options.primitives.controlHeight ?? {}, size.controlHeight)) {
|
|
|
|
if (!isPlainRecord(size)) {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path,
|
|
|
|
|
|
|
|
message: 'size primitive must be a plain object'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const controlHeight = size.controlHeight
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof controlHeight !== 'string' ||
|
|
|
|
|
|
|
|
controlHeight.trim() === '' ||
|
|
|
|
|
|
|
|
!hasOwn(options.primitives.controlHeight ?? {}, controlHeight)
|
|
|
|
|
|
|
|
) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.controlHeight`,
|
|
|
|
path: `${path}.controlHeight`,
|
|
|
|
message: `size primitive points to missing control height "${size.controlHeight}"`
|
|
|
|
message: `size primitive points to missing control height "${String(controlHeight)}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const typography = options.primitives.typography
|
|
|
|
const typography = options.primitives.typography
|
|
|
|
if (!TEXT_SIZE_SET.has(size.fontSize) || !hasOwn(typography?.sizes ?? {}, size.fontSize)) {
|
|
|
|
const fontSize = size.fontSize
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof fontSize !== 'string' ||
|
|
|
|
|
|
|
|
!TEXT_SIZE_SET.has(fontSize) ||
|
|
|
|
|
|
|
|
!hasOwn(typography?.sizes ?? {}, fontSize)
|
|
|
|
|
|
|
|
) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.fontSize`,
|
|
|
|
path: `${path}.fontSize`,
|
|
|
|
message: `size primitive points to missing font size "${size.fontSize}"`
|
|
|
|
message: `size primitive points to missing font size "${String(fontSize)}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasOwn(options.primitives.icon?.size ?? {}, size.iconSize)) {
|
|
|
|
const iconSize = size.iconSize
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof iconSize !== 'string' ||
|
|
|
|
|
|
|
|
iconSize.trim() === '' ||
|
|
|
|
|
|
|
|
!hasOwn(options.primitives.icon?.size ?? {}, iconSize)
|
|
|
|
|
|
|
|
) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.iconSize`,
|
|
|
|
path: `${path}.iconSize`,
|
|
|
|
message: `size primitive points to missing icon size "${size.iconSize}"`
|
|
|
|
message: `size primitive points to missing icon size "${String(iconSize)}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasOwn(options.primitives.radius ?? {}, size.radius)) {
|
|
|
|
const radius = size.radius
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
typeof radius !== 'string' ||
|
|
|
|
|
|
|
|
radius.trim() === '' ||
|
|
|
|
|
|
|
|
!hasOwn(options.primitives.radius ?? {}, radius)
|
|
|
|
|
|
|
|
) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.radius`,
|
|
|
|
path: `${path}.radius`,
|
|
|
|
message: `size primitive points to missing radius "${size.radius}"`
|
|
|
|
message: `size primitive points to missing radius "${String(radius)}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -981,7 +1083,7 @@ function validateRecordPrimitive(
|
|
|
|
})
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
validateNonEmptyCssValue(`${path}.${key}`, String(value), issues)
|
|
|
|
validateNonEmptyCssValue(`${path}.${key}`, value, issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -995,20 +1097,20 @@ function validateOpenScalePrimitive(
|
|
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(record)) {
|
|
|
|
for (const [key, value] of Object.entries(record)) {
|
|
|
|
validateCssTokenSuffix(`${path}.${key}`, key, label, issues)
|
|
|
|
validateCssTokenSuffix(`${path}.${key}`, key, label, issues)
|
|
|
|
validateNonEmptyCssValue(`${path}.${key}`, String(value), issues)
|
|
|
|
validateNonEmptyCssValue(`${path}.${key}`, value, issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function validateTypographySizeValue(
|
|
|
|
function validateTypographySizeValue(
|
|
|
|
path: string,
|
|
|
|
path: string,
|
|
|
|
value: unknown,
|
|
|
|
value: unknown,
|
|
|
|
typography: TypographyPrimitiveSet,
|
|
|
|
sizes: Record<string, unknown> | undefined,
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
if (value === undefined) return
|
|
|
|
if (value === undefined) return
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
if (!TEXT_SIZE_SET.has(value) || !hasOwn(typography.sizes, value)) {
|
|
|
|
if (!TEXT_SIZE_SET.has(value) || !sizes || !hasOwn(sizes, value)) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path,
|
|
|
|
path,
|
|
|
|
message: `typography style points to missing text size "${value}"`
|
|
|
|
message: `typography style points to missing text size "${value}"`
|
|
|
|
@ -1017,7 +1119,7 @@ function validateTypographySizeValue(
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isTextMetric(value)) {
|
|
|
|
if (isPlainRecord(value) && isTextMetricLike(value)) {
|
|
|
|
validateTextMetric(path, value, issues)
|
|
|
|
validateTextMetric(path, value, issues)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1030,7 +1132,7 @@ function validateTypographySizeValue(
|
|
|
|
message: `unknown responsive breakpoint "${key}"`
|
|
|
|
message: `unknown responsive breakpoint "${key}"`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
validateTypographySizeValue(`${path}.${key}`, item, typography, issues)
|
|
|
|
validateTypographySizeValue(`${path}.${key}`, item, sizes, issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1043,10 +1145,19 @@ function validateTypographySizeValue(
|
|
|
|
|
|
|
|
|
|
|
|
function validateNonEmptyCssValue(
|
|
|
|
function validateNonEmptyCssValue(
|
|
|
|
path: string,
|
|
|
|
path: string,
|
|
|
|
value: string,
|
|
|
|
value: unknown,
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
if (value.trim() === '') {
|
|
|
|
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path,
|
|
|
|
|
|
|
|
message: 'CSS value must be a string or number'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const text = String(value)
|
|
|
|
|
|
|
|
if (text.trim() === '') {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path,
|
|
|
|
path,
|
|
|
|
message: 'CSS value cannot be empty'
|
|
|
|
message: 'CSS value cannot be empty'
|
|
|
|
@ -1054,7 +1165,7 @@ function validateNonEmptyCssValue(
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!/[;{}]/.test(value)) return
|
|
|
|
if (!/[;{}]/.test(text)) return
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path,
|
|
|
|
path,
|
|
|
|
message: 'CSS value cannot contain declaration or block delimiters'
|
|
|
|
message: 'CSS value cannot contain declaration or block delimiters'
|
|
|
|
@ -1063,36 +1174,24 @@ function validateNonEmptyCssValue(
|
|
|
|
|
|
|
|
|
|
|
|
function validateTextMetric(
|
|
|
|
function validateTextMetric(
|
|
|
|
path: string,
|
|
|
|
path: string,
|
|
|
|
metric: TextMetric,
|
|
|
|
metric: unknown,
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
issues: EidosValidationIssue[]
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
if (metric.size.trim() === '') {
|
|
|
|
if (!isPlainRecord(metric)) {
|
|
|
|
issues.push({
|
|
|
|
issues.push({
|
|
|
|
path: `${path}.size`,
|
|
|
|
path,
|
|
|
|
message: 'text metric size cannot be empty'
|
|
|
|
message: 'text metric must be a plain object'
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (metric.lineHeight.trim() === '') {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path: `${path}.lineHeight`,
|
|
|
|
|
|
|
|
message: 'text metric lineHeight cannot be empty'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (metric.letterSpacing.trim() === '') {
|
|
|
|
|
|
|
|
issues.push({
|
|
|
|
|
|
|
|
path: `${path}.letterSpacing`,
|
|
|
|
|
|
|
|
message: 'text metric letterSpacing cannot be empty'
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(`${path}.size`, metric.size, issues)
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(`${path}.lineHeight`, metric.lineHeight, issues)
|
|
|
|
|
|
|
|
validateNonEmptyCssValue(`${path}.letterSpacing`, metric.letterSpacing, issues)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isTextMetric(value: unknown): value is TextMetric {
|
|
|
|
function isTextMetricLike(value: Record<string, unknown>): boolean {
|
|
|
|
return (
|
|
|
|
return hasOwn(value, 'size') || hasOwn(value, 'lineHeight') || hasOwn(value, 'letterSpacing')
|
|
|
|
isPlainRecord(value) &&
|
|
|
|
|
|
|
|
typeof value.size === 'string' &&
|
|
|
|
|
|
|
|
typeof value.lineHeight === 'string' &&
|
|
|
|
|
|
|
|
typeof value.letterSpacing === 'string'
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function validateCssTokenKey(
|
|
|
|
function validateCssTokenKey(
|
|
|
|
|