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 | 5x 5x 5x 5x 5x 14x 6x 6x 14x 14x 14x 3x 3x 11x 11x 4x 4x 3x 3x 5x 20x 20x 15x 5x 10x 14x 10x 5x 5x | /**
* Rule: no private hooks in the theme layer.
*
* Private hooks are any custom property whose name starts with `--_`.
* They are an internal indirection mechanism a stylesheet can use to
* pass a value from one declaration to another without exposing it as a
* customer-facing API. The compliance package treats them as
* out-of-bounds for the customization surface — the theme layer
* (`role: 'theme' | 'theme-base'`) — for two reasons:
*
* 1. Customers cannot address a private hook. Any value routed through
* one is invisible to the customization engine, so the routing
* decision becomes a "wall" the customer cannot reach over.
* 2. Private hooks sidestep the other structural rules. The four
* "no hook write on X" checks all match `--slds-c-*`, so a private
* hook used as a workaround silently passes those checks while
* reproducing the same bypass they were written to prevent.
*
* Both *writes* (`--_foo: value`) and *consumption* (`var(--_foo)`
* inside any declaration's value) are flagged. A theme file may legitimately
* route a single value through several declarations; do that with a public
* `--slds-c-*` hook (if the routing decision deserves a customer API), a
* preprocessor variable, or by inlining the value at each paint site.
*
* Legacy `<component>.css` files (`role: 'base'`) and auxiliary CSS
* (`role: 'aux'`) are out of scope — same boundary the other authoring
* rules apply. They are the existing paint surface SLDS already ships,
* not the customer-facing theme layer the compliance package gates.
*/
import type { Declaration } from 'postcss';
import type { ComplianceCheck, ComplianceRow, ComponentSourceFile, Offender } from '../../types.js';
import { declLocation, failRow, notRunYetRow, passRow, themeLayerFilesFor } from './internals.js';
const ID = 'structural-no-private-hooks';
const LABEL = 'No private hooks';
const CATEGORY = 'customer-reach' as const;
const PRIVATE_HOOK_PREFIX = '--_';
const PRIVATE_HOOK_REF_RE = /var\(\s*(--_[A-Za-z0-9_-]+)/g;
function isPrivateHookDecl(decl: Declaration): boolean {
return decl.prop.startsWith(PRIVATE_HOOK_PREFIX);
}
function selectorOf(decl: Declaration): string {
const parent = decl.parent;
return parent?.type === 'rule' ? (parent as { selector: string }).selector : '<root>';
}
function collectPrivateHookOffenders(file: ComponentSourceFile, into: Offender[]): void {
file.root.walkDecls((decl) => {
// Reset the stateful /g regex unconditionally before any branch
// returns. The regex remembers `lastIndex` between `.exec()` calls,
// so a previous decl's read scan would otherwise carry over and
// skip matches in later decls.
PRIVATE_HOOK_REF_RE.lastIndex = 0;
if (isPrivateHookDecl(decl)) {
into.push({
selector: selectorOf(decl),
hook: decl.prop,
location: declLocation(file, decl),
note: 'Private hook write',
fix: `Convert \`${decl.prop}\` to a public \`--slds-c-*\` hook (if the routing decision deserves a customer API), or inline the value at each paint site.`,
});
return;
}
const seen = new Set<string>();
for (let m = PRIVATE_HOOK_REF_RE.exec(decl.value); m; m = PRIVATE_HOOK_REF_RE.exec(decl.value)) {
const ref = m[1];
if (seen.has(ref)) continue;
seen.add(ref);
into.push({
selector: selectorOf(decl),
hook: ref,
prop: decl.prop,
location: declLocation(file, decl),
note: `Private hook read in \`${decl.prop}\``,
fix: `Replace \`var(${ref})\` with a public \`--slds-c-*\` hook reference or the literal value.`,
});
}
});
}
export const noPrivateHooks: ComplianceCheck = (input): ComplianceRow => {
const files = themeLayerFilesFor(input);
if (!files) return notRunYetRow(ID, LABEL, CATEGORY);
if (files.length === 0) {
return passRow(ID, LABEL, 'Component has no theme or theme-base files; nothing to check.', CATEGORY);
}
const offenders: Offender[] = [];
for (const file of files) collectPrivateHookOffenders(file, offenders);
if (offenders.length === 0) {
return passRow(
ID,
LABEL,
"No private hooks (`--_*`) are written or consumed in this component's theme layer.",
CATEGORY,
);
}
return failRow(
ID,
LABEL,
`${offenders.length} private hook reference${offenders.length === 1 ? '' : 's'} in theme-layer CSS. ` +
`Private hooks (\`--_*\`) bypass the customization surface and recreate the wall the structural rules ` +
`are written to prevent. Replace each with a public \`--slds-c-*\` hook or inline the value at the paint site.`,
offenders,
);
};
|