Skip to content

Commit a0c7689

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(design): distinguish border geometry and conditional controls
1 parent 86bfd56 commit a0c7689

5 files changed

Lines changed: 76 additions & 19 deletions

‎scripts/check-design-conformance-colour-assignments.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,3 +568,16 @@ test('imperative paint properties inspect literals, tokens and unresolved values
568568
).toBeGreaterThan(0)
569569
}
570570
})
571+
572+
test('imperative neutral paint and border geometry do not become colour findings', () => {
573+
for (const expression of [
574+
"node.style.background='transparent'",
575+
"node.style.border='0'",
576+
"node.style.border='2px solid var(--text-body)'",
577+
]) {
578+
expect(flagged({ [ui]: `export function update(node){${expression}}` })).toEqual([])
579+
}
580+
expect(
581+
flagged({ [ui]: `export function update(node){node.style.border='2px solid #ff00ff'}` }).length
582+
).toBeGreaterThan(0)
583+
})

‎scripts/check-design-conformance-controls.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,18 @@ test('native image inputs require an accessible name from alt or ARIA', () => {
274274
)
275275
).toEqual([])
276276
})
277+
278+
test('finite image and text input branches still inspect the image action name', () => {
279+
expect(
280+
matching(
281+
run('<input type={condition ? "image" : "text"} src="/action.png"/>'),
282+
'control-accessible-name'
283+
)
284+
).toHaveLength(1)
285+
expect(
286+
matching(
287+
run('<input type={condition ? "image" : "text"} src="/action.png" alt="Submit"/>'),
288+
'control-accessible-name'
289+
)
290+
).toEqual([])
291+
})

‎scripts/design-conformance/control-colour-assignments.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { productScope } from '#control-analysis/scope'
1313
import { type Expr, expression, type StaticInputs, unknown } from '#control-analysis/static-inputs'
1414
import { centralFile } from '#design-conformance/contracts'
1515
import { canonical, type Finding, hash, TOKEN_FILE } from '#design-conformance/model'
16-
import { rawColours, utility, variablesIn } from '#design-conformance/normalize'
16+
import { borderDeclarations, rawColours, utility, variablesIn } from '#design-conformance/normalize'
1717

1818
interface Site {
1919
file: string
@@ -638,6 +638,23 @@ export class ColourAssignments {
638638
}
639639
const shadowSink = assignment.direct && /^(?:box|text)-shadow$/.test(assignment.name)
640640
const checks = assignment.values.map((value): Check => {
641+
const border = assignment.direct ? borderDeclarations(assignment.name, value) : null
642+
if (border) {
643+
const paint = border.filter((declaration) => declaration.category === 'colours')
644+
if (!paint.length)
645+
return {
646+
status: 'verified',
647+
reason: 'Border shorthand authors no colour',
648+
references: [],
649+
}
650+
return combine(
651+
paint.map((declaration) =>
652+
checkColourValue(declaration.value, (name, context) =>
653+
checkVariable(name, seen, context)
654+
)
655+
)
656+
)
657+
}
641658
const shadow = shadowSink ? /^var\((--shadow-[\w-]+)\)$/.exec(value.trim()) : null
642659
if (shadow) {
643660
const name = shadow[1]
@@ -848,6 +865,8 @@ function checkColourValue(
848865
shadowGeometry = false,
849866
channelContext = false
850867
): Check {
868+
if (/^(?:none|transparent)$/i.test(text.trim()))
869+
return { status: 'verified', reason: 'No authored paint colour', references: [] }
851870
if (/^(?:currentColor|inherit|unset|revert|revert-layer)$/i.test(text.trim()))
852871
return {
853872
status: 'unresolved',

‎scripts/design-conformance/control-simplifications.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ export function inspectSimplifications(
780780
use.target === 'native:input' &&
781781
!use.inputs.type?.unresolved &&
782782
!!use.inputs.type?.values?.length &&
783-
use.inputs.type.values.every((value) => value === 'image')
783+
use.inputs.type.values.some((value) => value === 'image')
784784
const isButton =
785785
use.target === 'native:button' ||
786786
imageInput ||

‎scripts/design-conformance/normalize.ts‎

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ const internal: Record<string, string> = {
9999
'--tw-scale-x': 'scale',
100100
'--tw-scale-y': 'scale',
101101
}
102+
/** Split authored border geometry and style from its colour input. */
103+
export function borderDeclarations(property: string, value: string): Declaration[] | null {
104+
if (!/^(?:border(?:-(?:top|bottom|left|right))?|outline)$/.test(property)) return null
105+
const prefix = property.startsWith('outline') ? 'outline' : 'border'
106+
return valueParser(value)
107+
.nodes.filter((node) => node.type !== 'space' && node.type !== 'comment')
108+
.map((node) => {
109+
const part = valueParser.stringify(node)
110+
const suffix = /^(?:none|hidden|solid|dashed|dotted|double|groove|ridge|inset|outset)$/.test(
111+
part
112+
)
113+
? 'style'
114+
: /^(?:[\d.]+(?:px|em)|0|thin|medium|thick)$/.test(part) || part.includes('--border-width')
115+
? 'width'
116+
: 'color'
117+
return {
118+
property: `${prefix}-${suffix}`,
119+
value: part,
120+
category: suffix === 'color' ? 'colours' : 'borders',
121+
}
122+
})
123+
}
102124
export function declarations(
103125
atom: Atom,
104126
system: Compiler,
@@ -163,23 +185,11 @@ export function declarations(
163185
if (/^(?:padding|margin|gap|border-radius)$/.test(family(prop))) {
164186
for (const part of parts.filter((x) => x !== '/'))
165187
found.push({ property: family(prop), value: part, category: cat })
166-
} else if (/^(?:border(?:-(?:top|bottom|left|right))?|outline)$/.test(prop)) {
167-
const prefix = prop.startsWith('outline') ? 'outline' : 'border'
168-
for (const part of parts) {
169-
const suffix =
170-
/^(?:none|hidden|solid|dashed|dotted|double|groove|ridge|inset|outset)$/.test(part)
171-
? 'style'
172-
: /^(?:[\d.]+(?:px|em)|0|thin|medium|thick)$/.test(part) ||
173-
part.includes('--border-width')
174-
? 'width'
175-
: 'color'
176-
found.push({
177-
property: `${prefix}-${suffix}`,
178-
value: part,
179-
category: suffix === 'color' ? 'colours' : 'borders',
180-
})
181-
}
182-
} else found.push({ property: family(prop), value, category: cat })
188+
} else {
189+
const border = borderDeclarations(prop, value)
190+
if (border) found.push(...border)
191+
else found.push({ property: family(prop), value, category: cat })
192+
}
183193
})
184194
return [...new Map(found.map((x) => [JSON.stringify(x), x])).values()]
185195
}

0 commit comments

Comments
 (0)