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 | 6x 6x 6x 6x 15x 15x 10x 10x 21x 12x 12x 11x 4x 3x 10x 8x 2x | /**
* Structural rule: no layout properties in rules that also write color hooks.
*
* A CSS rule that simultaneously writes cosmetic (color) hooks and controls
* layout couples theme changes to layout side-effects. When a customer or
* a future theme overrides the color hook, the layout declaration is not
* in scope for that override, creating unexpected interactions. Color-hook
* writes must live in their own rules, isolated from structural declarations.
*/
import type { ComplianceCheck, ComplianceRow, Offender } from '../../types.js';
import { classifyHook } from '../helpers/classifyHook.js';
import {
declLocation,
notRunYetRow,
passRow,
reviewRow,
sourceFilesFor,
visitHookWriteRules,
} from './internals.js';
const ID = 'structural-no-layout-properties-in-cosmetic-hook-scope';
const LABEL = 'No layout properties mixed with cosmetic color-hook writes';
/**
* CSS properties that control box-model placement, sizing, or flow.
* When authored in the same rule as a color hook, they couple visual
* theming to layout behaviour.
*/
const LAYOUT_PROPERTIES = new Set([
'display',
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'float',
'clear',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'grid',
'grid-template',
'grid-template-columns',
'grid-template-rows',
'grid-column',
'grid-row',
'flex',
'flex-basis',
'flex-grow',
'flex-shrink',
'order',
'align-items',
'justify-content',
'gap',
'place-items',
'overflow',
]);
export const noLayoutInCosmeticScope: ComplianceCheck = (input): ComplianceRow => {
const files = sourceFilesFor(input);
if (!files) return notRunYetRow(ID, LABEL);
const offenders: Offender[] = [];
for (const file of files) {
visitHookWriteRules(file, ({ rule, hookDecls, nonHookDecls }) => {
const hasColorHook = hookDecls.some((decl) => classifyHook(decl.prop, input.componentName).isColor);
if (!hasColorHook) return;
for (const decl of nonHookDecls) {
if (!LAYOUT_PROPERTIES.has(decl.prop.toLowerCase())) continue;
offenders.push({
selector: rule.selector,
prop: decl.prop,
location: declLocation(file, decl),
note: `\`${decl.prop}\` appears in a rule that also writes a color hook; layout and cosmetic paint are coupled.`,
fix: `Move \`${decl.prop}\` into a separate rule so color-hook writes are isolated from layout declarations.`,
});
}
});
}
if (offenders.length === 0) {
return passRow(ID, LABEL, 'No layout properties appear in rules that also write cosmetic color hooks.');
}
return reviewRow(
ID,
LABEL,
`${offenders.length} layout declaration${offenders.length === 1 ? '' : 's'} appear alongside color-hook writes. ` +
`Mixing layout and cosmetic paint in the same rule couples theme overrides to layout side-effects; split them into separate rules.`,
offenders,
);
};
|