Skip to content

Commit 4c100c0

Browse files
authored
Merge pull request #170 from bookedsolidtech/feat/preflight-full-validation
feat: enhance styling_preflight to run all 7 CSS validators
2 parents 65084c8 + a68fdf2 commit 4c100c0

File tree

6 files changed

+277
-22
lines changed

6 files changed

+277
-22
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'helixir': patch
3+
'@helixir/core': patch
4+
---
5+
6+
Enhance styling_preflight to run all 7 CSS validators in a single call — adds token fallback, scope, shorthand, color contrast, and specificity checks alongside existing shadow DOM and theme compatibility checks

custom-elements.json

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4164,6 +4164,36 @@
41644164
"kind": "javascript-module",
41654165
"path": "packages/core/src/handlers/scope-checker.ts",
41664166
"declarations": [
4167+
{
4168+
"kind": "function",
4169+
"name": "checkCssScopeFromMeta",
4170+
"return": {
4171+
"type": {
4172+
"text": "ScopeCheckResult"
4173+
}
4174+
},
4175+
"parameters": [
4176+
{
4177+
"name": "css",
4178+
"type": {
4179+
"text": "string"
4180+
}
4181+
},
4182+
{
4183+
"name": "tagName",
4184+
"type": {
4185+
"text": "string"
4186+
}
4187+
},
4188+
{
4189+
"name": "cssProperties",
4190+
"type": {
4191+
"text": "Array<{ name: string }>"
4192+
}
4193+
}
4194+
],
4195+
"description": "Core implementation accepting a pre-built set of known tokens.\nUsed by both the CEM-based entry point and the preflight (which already has metadata)."
4196+
},
41674197
{
41684198
"kind": "function",
41694199
"name": "checkCssScope",
@@ -4191,10 +4221,19 @@
41914221
"text": "Cem"
41924222
}
41934223
}
4194-
]
4224+
],
4225+
"description": "CEM-based entry point — parses the CEM to extract known tokens,\nthen delegates to the core implementation."
41954226
}
41964227
],
41974228
"exports": [
4229+
{
4230+
"kind": "js",
4231+
"name": "checkCssScopeFromMeta",
4232+
"declaration": {
4233+
"name": "checkCssScopeFromMeta",
4234+
"module": "packages/core/src/handlers/scope-checker.ts"
4235+
}
4236+
},
41984237
{
41994238
"kind": "js",
42004239
"name": "checkCssScope",
@@ -4888,6 +4927,30 @@
48884927
"kind": "javascript-module",
48894928
"path": "packages/core/src/handlers/token-fallback-checker.ts",
48904929
"declarations": [
4930+
{
4931+
"kind": "function",
4932+
"name": "checkTokenFallbacksFromMeta",
4933+
"return": {
4934+
"type": {
4935+
"text": "TokenFallbackResult"
4936+
}
4937+
},
4938+
"parameters": [
4939+
{
4940+
"name": "cssText",
4941+
"type": {
4942+
"text": "string"
4943+
}
4944+
},
4945+
{
4946+
"name": "knownTokens",
4947+
"type": {
4948+
"text": "Set<string>"
4949+
}
4950+
}
4951+
],
4952+
"description": "Core implementation accepting a pre-built set of known tokens.\nUsed by both the CEM-based entry point and the preflight (which already has metadata)."
4953+
},
48914954
{
48924955
"kind": "function",
48934956
"name": "checkTokenFallbacks",
@@ -4915,10 +4978,19 @@
49154978
"text": "Cem"
49164979
}
49174980
}
4918-
]
4981+
],
4982+
"description": "CEM-based entry point — parses the CEM to extract known tokens,\nthen delegates to the core implementation."
49194983
}
49204984
],
49214985
"exports": [
4986+
{
4987+
"kind": "js",
4988+
"name": "checkTokenFallbacksFromMeta",
4989+
"declaration": {
4990+
"name": "checkTokenFallbacksFromMeta",
4991+
"module": "packages/core/src/handlers/token-fallback-checker.ts"
4992+
}
4993+
},
49224994
{
49234995
"kind": "js",
49244996
"name": "checkTokenFallbacks",

packages/core/src/handlers/scope-checker.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,16 @@ function parseCssBlocks(css: string): CssBlock[] {
8484

8585
// ─── Main Entry Point ───────────────────────────────────────────────────────
8686

87-
export function checkCssScope(css: string, tagName: string, cem: Cem): ScopeCheckResult {
88-
const meta = parseCem(tagName, cem);
89-
const knownTokens = new Set(meta.cssProperties.map((p) => p.name));
87+
/**
88+
* Core implementation accepting a pre-built set of known tokens.
89+
* Used by both the CEM-based entry point and the preflight (which already has metadata).
90+
*/
91+
export function checkCssScopeFromMeta(
92+
css: string,
93+
tagName: string,
94+
cssProperties: Array<{ name: string }>,
95+
): ScopeCheckResult {
96+
const knownTokens = new Set(cssProperties.map((p) => p.name));
9097

9198
const blocks = parseCssBlocks(css);
9299
const issues: ScopeIssue[] = [];
@@ -114,3 +121,12 @@ export function checkCssScope(css: string, tagName: string, cem: Cem): ScopeChec
114121
clean: issues.length === 0,
115122
};
116123
}
124+
125+
/**
126+
* CEM-based entry point — parses the CEM to extract known tokens,
127+
* then delegates to the core implementation.
128+
*/
129+
export function checkCssScope(css: string, tagName: string, cem: Cem): ScopeCheckResult {
130+
const meta = parseCem(tagName, cem);
131+
return checkCssScopeFromMeta(css, tagName, meta.cssProperties);
132+
}

packages/core/src/handlers/styling-preflight.ts

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import type { ComponentMetadata } from './cem.js';
1616
import { resolveCssApi, type CssApiResolution } from './css-api-resolver.js';
1717
import { checkShadowDomUsage } from './shadow-dom-checker.js';
1818
import { checkThemeCompatibility } from './theme-checker.js';
19+
import { checkCssShorthand } from './shorthand-checker.js';
20+
import { checkColorContrast } from './color-contrast-checker.js';
21+
import { checkCssSpecificity } from './specificity-checker.js';
1922
import { buildCssSnippet } from './styling-diagnostics.js';
23+
import { checkTokenFallbacksFromMeta } from './token-fallback-checker.js';
24+
import { checkCssScopeFromMeta } from './scope-checker.js';
2025

2126
// ─── Types ───────────────────────────────────────────────────────────────────
2227

@@ -60,9 +65,10 @@ export function runStylingPreflight(input: PreflightInput): PreflightResult {
6065
// 1. Resolve CSS references against CEM
6166
const resolution = resolveCssApi(css, meta, html);
6267

63-
// 2. Run shadow DOM validation (if CSS is non-empty)
68+
// 2. Run all CSS validators (if CSS is non-empty)
6469
if (css.trim()) {
65-
try {
70+
// Shadow DOM anti-patterns
71+
safeRun(() => {
6672
const shadowResult = checkShadowDomUsage(css, meta.tagName, meta);
6773
for (const issue of shadowResult.issues) {
6874
issues.push({
@@ -73,12 +79,10 @@ export function runStylingPreflight(input: PreflightInput): PreflightResult {
7379
suggestion: issue.suggestion,
7480
});
7581
}
76-
} catch {
77-
// Shadow DOM check failed — skip
78-
}
82+
});
7983

80-
// 3. Run theme compatibility check
81-
try {
84+
// Theme compatibility (hardcoded colors, mixed token sources, dark mode shadows)
85+
safeRun(() => {
8286
const themeResult = checkThemeCompatibility(css);
8387
for (const issue of themeResult.issues) {
8488
issues.push({
@@ -88,9 +92,74 @@ export function runStylingPreflight(input: PreflightInput): PreflightResult {
8892
line: issue.line,
8993
});
9094
}
91-
} catch {
92-
// Theme check failed — skip
93-
}
95+
});
96+
97+
// Token fallbacks (var() without fallbacks, hardcoded colors on theme properties)
98+
safeRun(() => {
99+
const knownTokens = new Set(meta.cssProperties.map((p) => p.name));
100+
const fallbackResult = checkTokenFallbacksFromMeta(css, knownTokens);
101+
for (const issue of fallbackResult.issues) {
102+
issues.push({
103+
severity: 'warning',
104+
category: 'tokenFallbacks',
105+
message: issue.message,
106+
line: issue.line,
107+
});
108+
}
109+
});
110+
111+
// Scope validation (component tokens on :root instead of host)
112+
safeRun(() => {
113+
const scopeResult = checkCssScopeFromMeta(css, meta.tagName, meta.cssProperties);
114+
for (const issue of scopeResult.issues) {
115+
issues.push({
116+
severity: 'warning',
117+
category: 'scope',
118+
message: issue.message,
119+
line: issue.line,
120+
});
121+
}
122+
});
123+
124+
// Shorthand + var() risky combinations
125+
safeRun(() => {
126+
const shorthandResult = checkCssShorthand(css);
127+
for (const issue of shorthandResult.issues) {
128+
issues.push({
129+
severity: 'warning',
130+
category: 'shorthand',
131+
message: issue.message,
132+
line: issue.line,
133+
suggestion: issue.suggestion,
134+
});
135+
}
136+
});
137+
138+
// Color contrast issues (low-contrast pairs, mixed sources)
139+
safeRun(() => {
140+
const contrastResult = checkColorContrast(css);
141+
for (const issue of contrastResult.issues) {
142+
issues.push({
143+
severity: 'warning',
144+
category: 'colorContrast',
145+
message: issue.message,
146+
line: issue.line,
147+
});
148+
}
149+
});
150+
151+
// CSS specificity anti-patterns (!important, ID selectors, deep nesting)
152+
safeRun(() => {
153+
const specResult = checkCssSpecificity(css);
154+
for (const issue of specResult.issues) {
155+
issues.push({
156+
severity: 'info',
157+
category: 'specificity',
158+
message: issue.message,
159+
line: issue.line,
160+
});
161+
}
162+
});
94163
}
95164

96165
// 4. Build the component API summary
@@ -118,6 +187,16 @@ export function runStylingPreflight(input: PreflightInput): PreflightResult {
118187
};
119188
}
120189

190+
// ─── Safe Runner ─────────────────────────────────────────────────────────────
191+
192+
function safeRun(fn: () => void): void {
193+
try {
194+
fn();
195+
} catch {
196+
// Individual checker failed — skip and continue with other checks
197+
}
198+
}
199+
121200
// ─── Verdict Builder ────────────────────────────────────────────────────────
122201

123202
function buildVerdict(resolution: CssApiResolution, issues: PreflightIssue[]): string {

packages/core/src/handlers/token-fallback-checker.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,14 @@ function parseCssDeclarations(css: string): CssPropertyDecl[] {
171171

172172
// ─── Main Entry Point ───────────────────────────────────────────────────────
173173

174-
export function checkTokenFallbacks(
174+
/**
175+
* Core implementation accepting a pre-built set of known tokens.
176+
* Used by both the CEM-based entry point and the preflight (which already has metadata).
177+
*/
178+
export function checkTokenFallbacksFromMeta(
175179
cssText: string,
176-
tagName: string,
177-
cem: Cem,
180+
knownTokens: Set<string>,
178181
): TokenFallbackResult {
179-
const meta = parseCem(tagName, cem);
180-
const knownTokens = new Set(meta.cssProperties.map((p) => p.name));
181-
182182
const declarations = parseCssDeclarations(cssText);
183183
const issues: TokenFallbackIssue[] = [];
184184
let totalVarCalls = 0;
@@ -228,3 +228,17 @@ export function checkTokenFallbacks(
228228
clean: issues.length === 0,
229229
};
230230
}
231+
232+
/**
233+
* CEM-based entry point — parses the CEM to extract known tokens,
234+
* then delegates to the core implementation.
235+
*/
236+
export function checkTokenFallbacks(
237+
cssText: string,
238+
tagName: string,
239+
cem: Cem,
240+
): TokenFallbackResult {
241+
const meta = parseCem(tagName, cem);
242+
const knownTokens = new Set(meta.cssProperties.map((p) => p.name));
243+
return checkTokenFallbacksFromMeta(cssText, knownTokens);
244+
}

0 commit comments

Comments
 (0)