fix: prevent false CSS class matches in AI guard

This commit is contained in:
shkim4 committed 2026-10-11 15:07:50 +09:00
1 parent 26d78763cc
commit d6d6eb53af
3 files changed
+434 -10

No files matched your search

+161
View File
@@ -7,6 +7,9 @@ import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { performance } from 'node:perf_hooks';
import test from 'node:test';
import { parse } from '@babel/parser';
import postcss from 'postcss';
import { styleAnalysis } from './guard-style-analysis.mjs';
test('64. finite conditional templates and concatenation validate every Token branch', () => {
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name = location.hash ? "fg-primary" : "bg-content"; export default () => <Button style={{ color: `var(--hm-sem-${name})` }}/>' }, root => {
@@ -861,3 +864,161 @@ test('86. Component implementation classes and unrelated graph mutations stay pr
assert.equal(r.styleEvidence.checks.find(c => c.selector.startsWith('.legacy')).applicability.classInputsComplete, false);
});
});
// Exercise implementation binding analysis without editing the real packages.
function implementationFixture(source, callback, extra = {}) {
fixture({
'src/App.tsx': 'export default () => <Fixture/>;',
'src/styles.css': '.legacy [data-slot="button"] { padding: 17px; }',
'packages/ui/src/index.ts': 'export { Fixture } from "./fixture";',
'packages/ui/src/fixture.tsx': source,
...extra,
}, root => {
const parseJS = (text, file) => parse(text, { sourceType: 'unambiguous', plugins: [...(/\.[cm]?tsx?$/.test(file) ? ['typescript'] : []), 'jsx'] });
const walk = (node, fn, parent = null) => {
if (!node || typeof node !== 'object') return;
if (node.type) fn(node, parent);
for (const [key, value] of Object.entries(node)) {
if (['loc', 'start', 'end', 'comments', 'tokens', 'extra'].includes(key)) continue;
if (Array.isArray(value)) value.forEach(x => walk(x, fn, node));
else if (value && typeof value === 'object') walk(value, fn, node);
}
};
const file = path.join(root, 'src/App.tsx'), ast = parseJS(fs.readFileSync(file, 'utf8'), file), renders = [];
walk(ast, n => {
if (n.type === 'JSXOpeningElement') renders.push({ name: n.name.name, attrs: new Map(n.attributes.filter(a => a.type === 'JSXAttribute').map(a => [a.name.name, a.value?.type === 'JSXExpressionContainer' ? a.value.expression : a.value])) });
});
const implementationFiles = dir => fs.readdirSync(dir, { withFileTypes: true }).flatMap(e => e.isDirectory() ? implementationFiles(path.join(dir, e.name)) : [path.join(dir, e.name)]);
const issues = [];
const checks = styleAnalysis({ records: [{ file: path.join(root, 'src/styles.css'), css: true }], scopeRecords: [{ file, ast, renders }], target: root, postcss, walk, parseJS, selectedFile: () => true, knownSlot: s => s === 'button', origin: (_, name) => name === 'Fixture' ? { pkg: '@hm-design/ui', name } : null, add: (severity, code) => issues.push({ severity, code }), storybook: false, scopeIncomplete: false, entries: [path.join(root, 'index.html')], implementationFiles: implementationFiles(path.join(root, 'packages/ui/src')) });
callback({ issues, checks });
});
}
test('87. implementation comments non-class strings and partial tokens never prove an ancestor', () => {
for (const source of [
'// legacy remains documented\nexport const Fixture = () => <div className="work"/>;',
'const title = "legacy"; export const Fixture = () => <div title={title} className="work"/>;',
'export const Fixture = () => <div className="legacyish pre-legacy legacy/post"/>;',
'import { cva } from "class-variance-authority"; const unused = cva("legacy"); export const Fixture = () => <div className="work"/>;',
'import { cva } from "class-variance-authority"; const variants = cva("work", {variants:{mode:{legacy:"work"}}, defaultVariants:{mode:"legacy"}}); export const Fixture = () => <div className={variants()}/>;',
]) implementationFixture(source, ({ issues, checks }) => {
assert.ok(!issues.some(i => i.code === 'STYLE-001'));
assert.equal(checks[0].applicability.implementationClassPossible, false);
assert.equal(checks[0].applicability.status, 'NOT APPLICABLE');
assert.equal(checks[0].staticStatus, 'PASS');
assert.equal(checks[0].runtimeStatus, 'NOT VERIFIED');
assert.equal(checks[0].designIntentStatus, 'NOT VERIFIED');
});
});
test('88. implementation exact JSX factory joiner and variant class tokens remain protected', () => {
for (const source of [
'export const Fixture = () => <div className="work legacy"/>;',
'export const Fixture = () => <div class="legacy"/>;',
'import React from "react"; export const Fixture = () => React.createElement("div", { className: "legacy" });',
'import { cn } from "cn"; export const Fixture = () => <div className={cn("work", enabled && "legacy")}/>;',
'import cx from "clsx"; export const Fixture = () => <div className={cx(["work", {legacy:enabled}])}/>;',
'import { cva as variants, type VariantProps } from "class-variance-authority"; import { cn } from "cn"; const styles = variants("work", {variants:{mode:{on:"legacy",off:"work"}}, compoundVariants:[{mode:"off",className:"legacy"}],defaultVariants:{mode:"off"}}); export const Fixture = ({className,mode}: VariantProps<typeof styles>) => <div className={cn(styles({mode,className}))}/>;',
'import { cva } from "class-variance-authority"; const styles = cva("work", {compoundVariants:[{class:"legacy"}]}); export const Fixture = () => <div className={styles()}/>;',
'export const Fixture = ({className="legacy"}) => <div className={className}/>;',
]) implementationFixture(source, ({ issues, checks }) => {
assert.ok(issues.some(i => i.code === 'STYLE-001' && i.severity === 'FAIL'));
assert.equal(checks[0].applicability.implementationClassPossible, true);
assert.equal(checks[0].staticStatus, 'FAIL');
});
});
test('89. imported implementation constants variants aliases and barrels retain class evidence', () => {
for (const [source, helper] of [
['import { name as applied } from "./barrel"; export const Fixture = () => <div className={applied}/>;', 'export const name = "legacy";'],
['import { styles } from "./barrel"; export const Fixture = () => <div className={styles()}/>;', 'import { cva } from "class-variance-authority"; export const styles = cva("legacy");'],
['import { name } from "./nested"; export const Fixture = () => <div className={name}/>;', 'export const name = "legacy";'],
]) implementationFixture(source, ({ issues, checks }) => {
assert.ok(issues.some(i => i.code === 'STYLE-001'));
assert.equal(checks[0].applicability.implementationClassPossible, true);
}, { 'packages/ui/src/barrel.ts': 'export * from "./helper";', 'packages/ui/src/nested/index.ts': 'export { name } from "../helper.js";', 'packages/ui/src/helper.ts': helper });
});
test('90. unresolved connected implementation inputs cannot prove scope exclusion', () => {
for (const source of [
'export const Fixture = ({value}) => <div className={value}/>;',
'export const Fixture = () => <div className={computeClass()}/>;',
'function cn(){return location.hash;} export const Fixture = () => <div className={cn()}/>;',
'import { cn } from "cn"; export const Fixture = ({cn}) => <div className={cn()}/>;',
'import { cn } from "cn"; export function Fixture(){ function cn(){return location.hash;} return <div className={cn()}/>; }',
'import { cn } from "cn"; export function Fixture(){ try { throw makeClass(); } catch(cn){ return <div className={cn()}/>; } }',
'export const Fixture = ({value}) => <div {...value}/>;',
'export const Fixture = ({props}) => <div className={props.className}/>;',
'export const Fixture = ({legacy:className}) => <div className={className}/>;',
'export const Fixture = (props=getProps()) => <div className={props.className}/>;',
'const Other=({className}) => <div className={className}/>; export const Fixture = ({legacy:className}) => <div className={className}/>;',
'const Other=(props) => <div className={props.className}/>; export const Fixture = ({props}) => <div className={props.className}/>;',
'export const Fixture = () => <div className={`prefix-${location.hash}`}/>;',
'const opaque = getProps(); export const Fixture = () => <div {...opaque}/>;',
'document.body.classList.add(location.hash); export const Fixture = () => <div/>;',
'import "./mutation"; export const Fixture = () => <div/>;',
'import("./mutation"); export const Fixture = () => <div/>;',
'import { value } from "./missing"; export const Fixture = () => <div className={value}/>;',
'export const Fixture = () => <div',
]) implementationFixture(source, ({ issues, checks }) => {
assert.ok(issues.some(i => i.code === 'STYLE-001'));
assert.equal(checks[0].applicability.status, 'POSSIBLE');
assert.equal(checks[0].applicability.classInputsComplete, false);
assert.ok(checks[0].applicability.unresolvedImplementationFiles.length);
}, { 'packages/ui/src/mutation.ts': 'document.body.className = location.hash;' });
});
test('91. missing ancestors retain CSS inventory and unverified intent in the real Guard', () => {
for (const ancestor of ['legacy', 'hm_unused_screen_audit', 'hm_guard_absent_9e4b']) fixture({
'src/App.tsx': 'import { Button } from "@hm-design/ui"; const title="legacy"; export default () => <div title={title} className="legacyish"><Button/></div>;',
'src/styles.css': '@import "./tokens.css"; /* legacy */ .'+ancestor+' [data-slot="button"] { padding:17px; }',
}, root => {
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
assert.ok(!r.issues.some(i => i.code === 'STYLE-001'));
const c = r.styleEvidence.checks.find(c => c.component === 'button');
assert.equal(c.applicability.status, 'NOT APPLICABLE');
assert.equal(r.styleEvidence.staticStatus, 'PASS');
assert.equal(r.styleEvidence.finalStatus, 'NOT VERIFIED');
assert.equal(r.status, 'NOT VERIFIED');
assert.ok(r.issues.some(i => i.code === 'REUSE-003' && i.severity === 'NOT VERIFIED'));
});
});
test('92. unique dynamic ancestor controls cannot pass because of library substring coincidences', () => {
const ancestor = 'hm_guard_dynamic_9e4b';
for (const app of [
'const name=location.hash?"'+ancestor+'":"work"; export default () => <div className={name}><Button/></div>;',
'export default ({className}) => <div className={className}><Button/></div>;',
'export default ({props}) => <div {...props}><Button/></div>;',
'document.body["classList"].add(location.hash); export default () => <Button/>;',
'import "./mutation"; export default () => <Button/>;',
'function cn(){return location.hash;} export default () => <div className={cn()}><Button/></div>;',
'import { cn } from "cn"; export default ({cn}) => <div className={cn()}><Button/></div>;',
'import { cn } from "cn"; export default function App(){function cn(){return location.hash;} return <div className={cn()}><Button/></div>}',
]) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; '+app, 'src/mutation.ts': 'document.body.className = location.hash;', 'src/styles.css': '@import "./tokens.css"; .'+ancestor+' [data-slot="button"] { padding:17px; }' }, root => {
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
assert.ok(r.issues.some(i => i.code === 'STYLE-001' && i.severity === 'FAIL'));
assert.equal(r.styleEvidence.checks.find(c => c.component === 'button').applicability.implementationClassPossible, false);
});
});
test('93. actual internal flex and public variant classes remain exact tokens', () => {
for (const ancestor of ['flex', 'inline-flex', 'group', 'h-8']) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;', 'src/styles.css': '@import "./tokens.css"; .'+ancestor+' [data-slot="button"] { padding:17px; }' }, root => {
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
assert.ok(r.issues.some(i => i.code === 'STYLE-001'));
assert.equal(r.styleEvidence.checks.find(c => c.component === 'button').applicability.implementationClassPossible, true);
});
});
test('94. consumer class joiners match exact tokens and preserve unknown-class review', () => {
for (const present of [false, true]) fixture({
'src/App.tsx': 'import { Button } from "@hm-design/ui"; import { clsx as cx } from "clsx"; export default () => <div className={cx("work", enabled && "'+(present ? 'legacy' : 'legacyish')+'")}><Button/></div>;',
'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding:17px; }',
}, root => {
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
assert.equal(r.issues.some(i => i.code === 'STYLE-001'), present);
assert.equal(r.styleEvidence.checks.find(c => c.component === 'button').applicability.status, present ? 'POSSIBLE' : 'NOT APPLICABLE');
assert.ok(r.issues.some(i => i.code === 'TOKEN-002' && i.severity === 'NOT VERIFIED'));
});
});