@webto-id/variant-check 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +109 -13
- package/dist/index.js +109 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -176,12 +176,19 @@ var Parser = class {
|
|
|
176
176
|
const lower = tag.toLowerCase();
|
|
177
177
|
if (RAW_TEXT_TAGS.has(lower)) {
|
|
178
178
|
if (selfClose) this.fail(`<${tag}> cannot be self-closing`);
|
|
179
|
-
const
|
|
179
|
+
const boundary = new RegExp(`</${lower}(?=[\\s/>])`, "i");
|
|
180
180
|
const rest = this.src.slice(this.pos);
|
|
181
|
-
const mm =
|
|
181
|
+
const mm = boundary.exec(rest);
|
|
182
182
|
if (!mm) this.fail(`missing </${lower}>`);
|
|
183
|
+
const after = rest.slice(mm.index + mm[0].length);
|
|
184
|
+
const clean = /^\s*>/.exec(after);
|
|
185
|
+
if (!clean) {
|
|
186
|
+
this.fail(
|
|
187
|
+
`malformed </${lower}> \u2014 a raw text block must close with exactly </${lower}>; the browser ends it at '</${lower}' followed by whitespace or '/', so anything else here would execute unchecked`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
183
190
|
const text = rest.slice(0, mm.index);
|
|
184
|
-
this.pos += mm.index + mm[0].length;
|
|
191
|
+
this.pos += mm.index + mm[0].length + clean[0].length;
|
|
185
192
|
const raw = {};
|
|
186
193
|
for (const [k, v] of Object.entries(attrs)) raw[k] = typeof v === "string" || v === true ? v : "{expr}";
|
|
187
194
|
const block = { tag: lower, attrs: raw, text, line: startLine };
|
|
@@ -1216,6 +1223,7 @@ function lintCss(css, sectionType2, attrs = {}) {
|
|
|
1216
1223
|
if ("is:global" in attrs) err("style-global", "<style is:global> is not allowed \u2014 variant CSS is always scoped");
|
|
1217
1224
|
if ("define:vars" in attrs) err("style-define-vars", "<style define:vars> is not supported \u2014 use var(--color-*) / var(--radius) tokens");
|
|
1218
1225
|
if (css.length > 64 * 1024) err("style-size", "style block exceeds 64 KB");
|
|
1226
|
+
if (/<\//.test(css)) err("css-raw-close", "'</' is not allowed inside a <style> block (it can close the block early and inject markup)");
|
|
1219
1227
|
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
1220
1228
|
if (/@import\b/i.test(stripped)) err("css-import", "@import is not allowed");
|
|
1221
1229
|
if (/@font-face\b/i.test(stripped)) err("css-font-face", "@font-face is not allowed \u2014 fonts come from the site theme (var(--font-heading|--font-body))");
|
|
@@ -1316,7 +1324,49 @@ var FORBIDDEN_MEMBERS = /* @__PURE__ */ new Set([
|
|
|
1316
1324
|
"defineProperty",
|
|
1317
1325
|
"getOwnPropertyDescriptor",
|
|
1318
1326
|
"setPrototypeOf",
|
|
1319
|
-
"getPrototypeOf"
|
|
1327
|
+
"getPrototypeOf",
|
|
1328
|
+
// Routes back to a window/document object, from which a forbidden name could
|
|
1329
|
+
// be reached as a property (see the FORBIDDEN_IDENTS-as-member rule below).
|
|
1330
|
+
"defaultView",
|
|
1331
|
+
"ownerDocument",
|
|
1332
|
+
"getRootNode"
|
|
1333
|
+
]);
|
|
1334
|
+
var URL_BEARING_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
1335
|
+
"background",
|
|
1336
|
+
"backgroundImage",
|
|
1337
|
+
"listStyle",
|
|
1338
|
+
"listStyleImage",
|
|
1339
|
+
"borderImage",
|
|
1340
|
+
"borderImageSource",
|
|
1341
|
+
"content",
|
|
1342
|
+
"cursor",
|
|
1343
|
+
"mask",
|
|
1344
|
+
"maskImage",
|
|
1345
|
+
"webkitMaskImage",
|
|
1346
|
+
"webkitMask",
|
|
1347
|
+
"filter",
|
|
1348
|
+
"backdropFilter",
|
|
1349
|
+
"src",
|
|
1350
|
+
"offsetPath",
|
|
1351
|
+
"clipPath",
|
|
1352
|
+
"shapeOutside",
|
|
1353
|
+
"cssText"
|
|
1354
|
+
]);
|
|
1355
|
+
var URL_BEARING_PROPS = /* @__PURE__ */ new Set([
|
|
1356
|
+
"src",
|
|
1357
|
+
"href",
|
|
1358
|
+
"action",
|
|
1359
|
+
"srcset",
|
|
1360
|
+
"poster",
|
|
1361
|
+
"data",
|
|
1362
|
+
"formAction",
|
|
1363
|
+
"formaction",
|
|
1364
|
+
"background",
|
|
1365
|
+
"ping",
|
|
1366
|
+
"cite",
|
|
1367
|
+
"codeBase",
|
|
1368
|
+
"lowsrc",
|
|
1369
|
+
"xlinkHref"
|
|
1320
1370
|
]);
|
|
1321
1371
|
var DYNAMIC_HTML = /* @__PURE__ */ new Set(["innerHTML", "outerHTML"]);
|
|
1322
1372
|
var FORBIDDEN_CALLS = /* @__PURE__ */ new Set(["atob", "btoa", "unescape", "decodeURIComponent", "setTimeout", "setInterval"]);
|
|
@@ -1335,6 +1385,7 @@ function lintScript(src, attrs = {}) {
|
|
|
1335
1385
|
if (/\bwith\s*\(/.test(src)) err("script-with", "`with` is not allowed");
|
|
1336
1386
|
if (/\bimport\s*\(/.test(src) || /\bimport\s+[\w{*]/.test(src) || /\bexport\s+/.test(src)) err("script-import", "import/export are not allowed in inline scripts");
|
|
1337
1387
|
if (/\bdebugger\b/.test(src)) err("script-debugger", "`debugger` is not allowed");
|
|
1388
|
+
if (/<\//.test(src)) err("script-raw-close", "'</' is not allowed inside a <script> block (it can close the block early and inject markup)");
|
|
1338
1389
|
let ast;
|
|
1339
1390
|
try {
|
|
1340
1391
|
ast = acorn.parse(src, { ecmaVersion: 2022, sourceType: "script", allowReturnOutsideFunction: true, locations: true });
|
|
@@ -1367,6 +1418,7 @@ function lintScript(src, attrs = {}) {
|
|
|
1367
1418
|
if (obj.type === "Identifier" && /^(window|document|globalThis|self|Object|Array)$/.test(obj.name)) err("script-forbidden", `computed member access on ${obj.name} is not allowed`, lineOf2(n));
|
|
1368
1419
|
}
|
|
1369
1420
|
if (p && FORBIDDEN_MEMBERS.has(p)) err("script-forbidden", `'.${p}' is not allowed`, lineOf2(n));
|
|
1421
|
+
if (p && FORBIDDEN_IDENTS.has(p)) err("script-forbidden", `'.${p}' is not allowed`, lineOf2(n));
|
|
1370
1422
|
break;
|
|
1371
1423
|
}
|
|
1372
1424
|
case "AssignmentExpression": {
|
|
@@ -1374,8 +1426,15 @@ function lintScript(src, attrs = {}) {
|
|
|
1374
1426
|
if (left.type === "MemberExpression") {
|
|
1375
1427
|
const p = propName(left);
|
|
1376
1428
|
if (p && DYNAMIC_HTML.has(p) && n.right.type !== "Literal") err("script-html", `assigning a non-literal to .${p} is not allowed`, lineOf2(n));
|
|
1377
|
-
if (p
|
|
1429
|
+
if (p && URL_BEARING_PROPS.has(p)) err("script-forbidden", `assigning .${p} is not allowed (it loads a URL \u2014 an outbound channel for page data)`, lineOf2(n));
|
|
1378
1430
|
if (p && /^on[a-z]+$/.test(p)) err("script-forbidden", `assigning .${p} handlers is not allowed \u2014 use addEventListener`, lineOf2(n));
|
|
1431
|
+
if (p && URL_BEARING_STYLE_PROPS.has(p)) {
|
|
1432
|
+
const obj = left.object;
|
|
1433
|
+
if (obj.type === "MemberExpression" && propName(obj) === "style") {
|
|
1434
|
+
err("script-forbidden", `assigning .style.${p} is not allowed (it can load a URL)`, lineOf2(n));
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (p === "style") err("script-forbidden", "assigning .style wholesale is not allowed \u2014 set individual layout properties", lineOf2(n));
|
|
1379
1438
|
}
|
|
1380
1439
|
break;
|
|
1381
1440
|
}
|
|
@@ -1621,7 +1680,11 @@ function getCompiler() {
|
|
|
1621
1680
|
return compilerPromise;
|
|
1622
1681
|
}
|
|
1623
1682
|
__name(getCompiler, "getCompiler");
|
|
1624
|
-
var CANDIDATE_RE = /^[!@]
|
|
1683
|
+
var CANDIDATE_RE = /^[!@]?-?[A-Za-z0-9_][\w:/\[\].%#(),-]*$/;
|
|
1684
|
+
function isCandidate(t) {
|
|
1685
|
+
return !!t && t.length <= 96 && CANDIDATE_RE.test(t) && !/url\(/i.test(t);
|
|
1686
|
+
}
|
|
1687
|
+
__name(isCandidate, "isCandidate");
|
|
1625
1688
|
var MACRO_CLASSES = [
|
|
1626
1689
|
"mx-auto w-full px-6",
|
|
1627
1690
|
"inline-flex items-center justify-center font-medium transition-all duration-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
@@ -1695,7 +1758,7 @@ function deriveSchema(fields, props, base2) {
|
|
|
1695
1758
|
__name(deriveSchema, "deriveSchema");
|
|
1696
1759
|
|
|
1697
1760
|
// ../variant-compiler/src/index.ts
|
|
1698
|
-
var COMPILER_VERSION = "0.1.
|
|
1761
|
+
var COMPILER_VERSION = "0.1.2";
|
|
1699
1762
|
var IR_MAX_BYTES = 256 * 1024;
|
|
1700
1763
|
var CSS_MAX_BYTES = 64 * 1024;
|
|
1701
1764
|
var STRICT_CODES = /* @__PURE__ */ new Set(["dead-text", "hardcoded-color", "hardcoded-radius", "hardcoded-font", "hardcoded-image", "img-alt", "props-optional"]);
|
|
@@ -1768,12 +1831,22 @@ async function compile2(source2, opts) {
|
|
|
1768
1831
|
if (tpl.scripts[0]) lint.push(...lintScript(script, tpl.scripts[0].attrs).map((m) => ({ ...m, line: m.line != null ? m.line + tpl.scripts[0].line : tpl.scripts[0].line })));
|
|
1769
1832
|
lint.push(...lintHtml(tpl.root, opts.sectionType));
|
|
1770
1833
|
const strings = [];
|
|
1834
|
+
const classTokens = /* @__PURE__ */ new Set();
|
|
1771
1835
|
walkNodes(tpl.root, (e) => collectStrings(e, strings), (attrs) => {
|
|
1772
|
-
for (const v of Object.
|
|
1836
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
1837
|
+
if (typeof v !== "string") continue;
|
|
1838
|
+
strings.push(v);
|
|
1839
|
+
if (k === "class" || k === "class:list") {
|
|
1840
|
+
for (const t of v.split(/\s+/)) if (t) classTokens.add(t);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1773
1843
|
});
|
|
1774
1844
|
for (const c of front.consts) collectStrings(c.e, strings);
|
|
1775
1845
|
for (const p of front.props) if (p.def) collectStrings(p.def, strings);
|
|
1776
1846
|
const classCandidates = pickCandidates(strings);
|
|
1847
|
+
for (const t of classTokens) {
|
|
1848
|
+
if (!isCandidate(t)) lint.push({ level: "warning", code: "tailwind-skipped", message: `class token '${t}' is not a shape the compiler accepts, so no CSS is generated for it \u2014 rewrite it (e.g. an arbitrary value like mt-[-4rem]) or drop it` });
|
|
1849
|
+
}
|
|
1777
1850
|
let twCss = "";
|
|
1778
1851
|
if (!opts.skipTailwind) {
|
|
1779
1852
|
try {
|
|
@@ -2012,8 +2085,15 @@ function collectStrings(e, out) {
|
|
|
2012
2085
|
__name(collectStrings, "collectStrings");
|
|
2013
2086
|
|
|
2014
2087
|
// ../variant-compiler/src/runtime/eval.ts
|
|
2088
|
+
var JSX_VALUES = /* @__PURE__ */ new WeakSet();
|
|
2089
|
+
function makeJsxValue(nodes, scope) {
|
|
2090
|
+
const v = { __wvJsx: true, nodes, scope };
|
|
2091
|
+
JSX_VALUES.add(v);
|
|
2092
|
+
return v;
|
|
2093
|
+
}
|
|
2094
|
+
__name(makeJsxValue, "makeJsxValue");
|
|
2015
2095
|
function isJsxValue(v) {
|
|
2016
|
-
return typeof v === "object" && v !== null && v
|
|
2096
|
+
return typeof v === "object" && v !== null && JSX_VALUES.has(v);
|
|
2017
2097
|
}
|
|
2018
2098
|
__name(isJsxValue, "isJsxValue");
|
|
2019
2099
|
var FORBIDDEN_PROPS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -2152,7 +2232,7 @@ function evalExpr(e, scope, budget) {
|
|
|
2152
2232
|
return evalExpr(e.body, s, budget);
|
|
2153
2233
|
};
|
|
2154
2234
|
case "jsx":
|
|
2155
|
-
return
|
|
2235
|
+
return makeJsxValue(e.n, scope);
|
|
2156
2236
|
}
|
|
2157
2237
|
}
|
|
2158
2238
|
__name(evalExpr, "evalExpr");
|
|
@@ -2335,11 +2415,24 @@ function renderVariant(ir, content, ctx, opts = {}) {
|
|
|
2335
2415
|
}
|
|
2336
2416
|
for (const c of ir.consts) scope[c.name] = evalExpr(c.e, scope, budget);
|
|
2337
2417
|
const html = renderNodes(ir.root, scope, ctx, budget);
|
|
2338
|
-
const css = ir.css ? ir.css.replace(/__WV_ROOT__/g, `[data-wv-inst="${uid}"]`) : "";
|
|
2418
|
+
const css = ir.css ? layerize(ir.css.replace(/__WV_ROOT__/g, `[data-wv-inst="${uid}"]`)) : "";
|
|
2339
2419
|
const script = ir.script ? wrapScript(ir.script, uid) : "";
|
|
2340
2420
|
return { html, css, script, uid };
|
|
2341
2421
|
}
|
|
2342
2422
|
__name(renderVariant, "renderVariant");
|
|
2423
|
+
var STATEMENT_AT = /^\s*@[a-zA-Z-]+[^;{}]*;/;
|
|
2424
|
+
function layerize(css) {
|
|
2425
|
+
let rest = css;
|
|
2426
|
+
let head = "";
|
|
2427
|
+
for (; ; ) {
|
|
2428
|
+
const m = rest.match(STATEMENT_AT);
|
|
2429
|
+
if (!m) break;
|
|
2430
|
+
head += m[0].trim();
|
|
2431
|
+
rest = rest.slice(m[0].length);
|
|
2432
|
+
}
|
|
2433
|
+
return rest.trim() ? `${head}@layer wvf{${rest}}` : head;
|
|
2434
|
+
}
|
|
2435
|
+
__name(layerize, "layerize");
|
|
2343
2436
|
function wrapScript(body, uid) {
|
|
2344
2437
|
return `(function(${SHADOWED_GLOBALS.join(",")}){"use strict";var root=document.querySelector('[data-wv-inst="${uid}"]');if(!root)return;
|
|
2345
2438
|
${body}
|
|
@@ -2360,10 +2453,13 @@ __name(isPreviewThemeKey, "isPreviewThemeKey");
|
|
|
2360
2453
|
function buildPreviewDoc(ir, content, themeKey, opts = {}) {
|
|
2361
2454
|
const th = PREVIEW_THEMES[themeKey];
|
|
2362
2455
|
const out = renderVariant(ir, content, { t: /* @__PURE__ */ __name((k) => k, "t"), editChrome: opts.editChrome ?? false }, { uid: "wv-preview" });
|
|
2363
|
-
const vars = `--background:${th.background};--foreground:${th.foreground};--card:${th.card};--card-foreground:${th.foreground};--primary:${th.primary};--primary-foreground:${th.primaryFg};--secondary:${th.secondary};--secondary-foreground:#fff;--muted:${th.muted};--muted-foreground:${th.mutedFg};--accent:${th.accent};--accent-foreground:#000;--border:${th.border};--input:${th.border};--ring:${th.primary};--radius:${th.radius};--font-heading:${th.heading};--font-body:${th.body};--gradient-from:${th.primary};--gradient-to:${th.secondary};--spacing:.25rem;--site-max-width:72rem;`;
|
|
2456
|
+
const vars = `--background:${th.background};--foreground:${th.foreground};--card:${th.card};--card-foreground:${th.foreground};--primary:${th.primary};--primary-foreground:${th.primaryFg};--secondary:${th.secondary};--secondary-foreground:#fff;--muted:${th.muted};--muted-foreground:${th.mutedFg};--accent:${th.accent};--accent-foreground:#000;--border:${th.border};--input:${th.border};--ring:${th.primary};--radius:${th.radius};--font-heading:${th.heading};--font-body:${th.body};--gradient-from:${th.primary};--gradient-to:${th.secondary};--spacing:.25rem;--site-max-width:72rem;--font-heading-size:48px;--font-body-size:16px;`;
|
|
2364
2457
|
const tokens = `--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-primary:var(--primary);--color-primary-foreground:var(--primary-foreground);--color-secondary:var(--secondary);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);`;
|
|
2365
2458
|
return `<!doctype html><html data-theme="${themeKey === "dark" ? "dark" : "light"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex">
|
|
2366
|
-
<style
|
|
2459
|
+
<style>@layer wvfbase{*,::before,::after{box-sizing:border-box;border:0 solid var(--color-border)}html{font-size:var(--font-body-size,16px);-webkit-text-size-adjust:100%}body{margin:0;font-family:var(--font-body);color:var(--foreground);background:var(--background);line-height:1.5}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}h1,h2,h3,h4,p{margin:0}img,video,svg{display:block;max-width:100%;height:auto}a{color:inherit;text-decoration:inherit}button{font:inherit;color:inherit;background:none}ul,ol{margin:0;padding:0;list-style:none}}
|
|
2460
|
+
:root{${vars}${tokens}}
|
|
2461
|
+
h1{font-size:var(--font-heading-size,48px)}h2{font-size:calc(var(--font-heading-size,48px) * 0.75)}h3{font-size:calc(var(--font-heading-size,48px) * 0.6)}h4{font-size:calc(var(--font-heading-size,48px) * 0.5)}
|
|
2462
|
+
.edit-url-pill{display:none!important}</style>
|
|
2367
2463
|
<style>${out.css}</style></head><body><div data-wv-root data-wv-inst="${out.uid}">${out.html}</div>${out.script ? `<script>${out.script}</script>` : ""}</body></html>`;
|
|
2368
2464
|
}
|
|
2369
2465
|
__name(buildPreviewDoc, "buildPreviewDoc");
|
package/dist/index.js
CHANGED
|
@@ -173,12 +173,19 @@ var Parser = class {
|
|
|
173
173
|
const lower = tag.toLowerCase();
|
|
174
174
|
if (RAW_TEXT_TAGS.has(lower)) {
|
|
175
175
|
if (selfClose) this.fail(`<${tag}> cannot be self-closing`);
|
|
176
|
-
const
|
|
176
|
+
const boundary = new RegExp(`</${lower}(?=[\\s/>])`, "i");
|
|
177
177
|
const rest = this.src.slice(this.pos);
|
|
178
|
-
const mm =
|
|
178
|
+
const mm = boundary.exec(rest);
|
|
179
179
|
if (!mm) this.fail(`missing </${lower}>`);
|
|
180
|
+
const after = rest.slice(mm.index + mm[0].length);
|
|
181
|
+
const clean = /^\s*>/.exec(after);
|
|
182
|
+
if (!clean) {
|
|
183
|
+
this.fail(
|
|
184
|
+
`malformed </${lower}> \u2014 a raw text block must close with exactly </${lower}>; the browser ends it at '</${lower}' followed by whitespace or '/', so anything else here would execute unchecked`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
180
187
|
const text = rest.slice(0, mm.index);
|
|
181
|
-
this.pos += mm.index + mm[0].length;
|
|
188
|
+
this.pos += mm.index + mm[0].length + clean[0].length;
|
|
182
189
|
const raw = {};
|
|
183
190
|
for (const [k, v] of Object.entries(attrs)) raw[k] = typeof v === "string" || v === true ? v : "{expr}";
|
|
184
191
|
const block = { tag: lower, attrs: raw, text, line: startLine };
|
|
@@ -1213,6 +1220,7 @@ function lintCss(css, sectionType, attrs = {}) {
|
|
|
1213
1220
|
if ("is:global" in attrs) err("style-global", "<style is:global> is not allowed \u2014 variant CSS is always scoped");
|
|
1214
1221
|
if ("define:vars" in attrs) err("style-define-vars", "<style define:vars> is not supported \u2014 use var(--color-*) / var(--radius) tokens");
|
|
1215
1222
|
if (css.length > 64 * 1024) err("style-size", "style block exceeds 64 KB");
|
|
1223
|
+
if (/<\//.test(css)) err("css-raw-close", "'</' is not allowed inside a <style> block (it can close the block early and inject markup)");
|
|
1216
1224
|
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
1217
1225
|
if (/@import\b/i.test(stripped)) err("css-import", "@import is not allowed");
|
|
1218
1226
|
if (/@font-face\b/i.test(stripped)) err("css-font-face", "@font-face is not allowed \u2014 fonts come from the site theme (var(--font-heading|--font-body))");
|
|
@@ -1313,7 +1321,49 @@ var FORBIDDEN_MEMBERS = /* @__PURE__ */ new Set([
|
|
|
1313
1321
|
"defineProperty",
|
|
1314
1322
|
"getOwnPropertyDescriptor",
|
|
1315
1323
|
"setPrototypeOf",
|
|
1316
|
-
"getPrototypeOf"
|
|
1324
|
+
"getPrototypeOf",
|
|
1325
|
+
// Routes back to a window/document object, from which a forbidden name could
|
|
1326
|
+
// be reached as a property (see the FORBIDDEN_IDENTS-as-member rule below).
|
|
1327
|
+
"defaultView",
|
|
1328
|
+
"ownerDocument",
|
|
1329
|
+
"getRootNode"
|
|
1330
|
+
]);
|
|
1331
|
+
var URL_BEARING_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
1332
|
+
"background",
|
|
1333
|
+
"backgroundImage",
|
|
1334
|
+
"listStyle",
|
|
1335
|
+
"listStyleImage",
|
|
1336
|
+
"borderImage",
|
|
1337
|
+
"borderImageSource",
|
|
1338
|
+
"content",
|
|
1339
|
+
"cursor",
|
|
1340
|
+
"mask",
|
|
1341
|
+
"maskImage",
|
|
1342
|
+
"webkitMaskImage",
|
|
1343
|
+
"webkitMask",
|
|
1344
|
+
"filter",
|
|
1345
|
+
"backdropFilter",
|
|
1346
|
+
"src",
|
|
1347
|
+
"offsetPath",
|
|
1348
|
+
"clipPath",
|
|
1349
|
+
"shapeOutside",
|
|
1350
|
+
"cssText"
|
|
1351
|
+
]);
|
|
1352
|
+
var URL_BEARING_PROPS = /* @__PURE__ */ new Set([
|
|
1353
|
+
"src",
|
|
1354
|
+
"href",
|
|
1355
|
+
"action",
|
|
1356
|
+
"srcset",
|
|
1357
|
+
"poster",
|
|
1358
|
+
"data",
|
|
1359
|
+
"formAction",
|
|
1360
|
+
"formaction",
|
|
1361
|
+
"background",
|
|
1362
|
+
"ping",
|
|
1363
|
+
"cite",
|
|
1364
|
+
"codeBase",
|
|
1365
|
+
"lowsrc",
|
|
1366
|
+
"xlinkHref"
|
|
1317
1367
|
]);
|
|
1318
1368
|
var DYNAMIC_HTML = /* @__PURE__ */ new Set(["innerHTML", "outerHTML"]);
|
|
1319
1369
|
var FORBIDDEN_CALLS = /* @__PURE__ */ new Set(["atob", "btoa", "unescape", "decodeURIComponent", "setTimeout", "setInterval"]);
|
|
@@ -1332,6 +1382,7 @@ function lintScript(src, attrs = {}) {
|
|
|
1332
1382
|
if (/\bwith\s*\(/.test(src)) err("script-with", "`with` is not allowed");
|
|
1333
1383
|
if (/\bimport\s*\(/.test(src) || /\bimport\s+[\w{*]/.test(src) || /\bexport\s+/.test(src)) err("script-import", "import/export are not allowed in inline scripts");
|
|
1334
1384
|
if (/\bdebugger\b/.test(src)) err("script-debugger", "`debugger` is not allowed");
|
|
1385
|
+
if (/<\//.test(src)) err("script-raw-close", "'</' is not allowed inside a <script> block (it can close the block early and inject markup)");
|
|
1335
1386
|
let ast;
|
|
1336
1387
|
try {
|
|
1337
1388
|
ast = acorn.parse(src, { ecmaVersion: 2022, sourceType: "script", allowReturnOutsideFunction: true, locations: true });
|
|
@@ -1364,6 +1415,7 @@ function lintScript(src, attrs = {}) {
|
|
|
1364
1415
|
if (obj.type === "Identifier" && /^(window|document|globalThis|self|Object|Array)$/.test(obj.name)) err("script-forbidden", `computed member access on ${obj.name} is not allowed`, lineOf2(n));
|
|
1365
1416
|
}
|
|
1366
1417
|
if (p && FORBIDDEN_MEMBERS.has(p)) err("script-forbidden", `'.${p}' is not allowed`, lineOf2(n));
|
|
1418
|
+
if (p && FORBIDDEN_IDENTS.has(p)) err("script-forbidden", `'.${p}' is not allowed`, lineOf2(n));
|
|
1367
1419
|
break;
|
|
1368
1420
|
}
|
|
1369
1421
|
case "AssignmentExpression": {
|
|
@@ -1371,8 +1423,15 @@ function lintScript(src, attrs = {}) {
|
|
|
1371
1423
|
if (left.type === "MemberExpression") {
|
|
1372
1424
|
const p = propName(left);
|
|
1373
1425
|
if (p && DYNAMIC_HTML.has(p) && n.right.type !== "Literal") err("script-html", `assigning a non-literal to .${p} is not allowed`, lineOf2(n));
|
|
1374
|
-
if (p
|
|
1426
|
+
if (p && URL_BEARING_PROPS.has(p)) err("script-forbidden", `assigning .${p} is not allowed (it loads a URL \u2014 an outbound channel for page data)`, lineOf2(n));
|
|
1375
1427
|
if (p && /^on[a-z]+$/.test(p)) err("script-forbidden", `assigning .${p} handlers is not allowed \u2014 use addEventListener`, lineOf2(n));
|
|
1428
|
+
if (p && URL_BEARING_STYLE_PROPS.has(p)) {
|
|
1429
|
+
const obj = left.object;
|
|
1430
|
+
if (obj.type === "MemberExpression" && propName(obj) === "style") {
|
|
1431
|
+
err("script-forbidden", `assigning .style.${p} is not allowed (it can load a URL)`, lineOf2(n));
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (p === "style") err("script-forbidden", "assigning .style wholesale is not allowed \u2014 set individual layout properties", lineOf2(n));
|
|
1376
1435
|
}
|
|
1377
1436
|
break;
|
|
1378
1437
|
}
|
|
@@ -1618,7 +1677,11 @@ function getCompiler() {
|
|
|
1618
1677
|
return compilerPromise;
|
|
1619
1678
|
}
|
|
1620
1679
|
__name(getCompiler, "getCompiler");
|
|
1621
|
-
var CANDIDATE_RE = /^[!@]
|
|
1680
|
+
var CANDIDATE_RE = /^[!@]?-?[A-Za-z0-9_][\w:/\[\].%#(),-]*$/;
|
|
1681
|
+
function isCandidate(t) {
|
|
1682
|
+
return !!t && t.length <= 96 && CANDIDATE_RE.test(t) && !/url\(/i.test(t);
|
|
1683
|
+
}
|
|
1684
|
+
__name(isCandidate, "isCandidate");
|
|
1622
1685
|
var MACRO_CLASSES = [
|
|
1623
1686
|
"mx-auto w-full px-6",
|
|
1624
1687
|
"inline-flex items-center justify-center font-medium transition-all duration-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
@@ -1692,7 +1755,7 @@ function deriveSchema(fields, props, base) {
|
|
|
1692
1755
|
__name(deriveSchema, "deriveSchema");
|
|
1693
1756
|
|
|
1694
1757
|
// ../variant-compiler/src/index.ts
|
|
1695
|
-
var COMPILER_VERSION = "0.1.
|
|
1758
|
+
var COMPILER_VERSION = "0.1.2";
|
|
1696
1759
|
var IR_MAX_BYTES = 256 * 1024;
|
|
1697
1760
|
var CSS_MAX_BYTES = 64 * 1024;
|
|
1698
1761
|
var STRICT_CODES = /* @__PURE__ */ new Set(["dead-text", "hardcoded-color", "hardcoded-radius", "hardcoded-font", "hardcoded-image", "img-alt", "props-optional"]);
|
|
@@ -1765,12 +1828,22 @@ async function compile2(source, opts) {
|
|
|
1765
1828
|
if (tpl.scripts[0]) lint.push(...lintScript(script, tpl.scripts[0].attrs).map((m) => ({ ...m, line: m.line != null ? m.line + tpl.scripts[0].line : tpl.scripts[0].line })));
|
|
1766
1829
|
lint.push(...lintHtml(tpl.root, opts.sectionType));
|
|
1767
1830
|
const strings = [];
|
|
1831
|
+
const classTokens = /* @__PURE__ */ new Set();
|
|
1768
1832
|
walkNodes(tpl.root, (e) => collectStrings(e, strings), (attrs) => {
|
|
1769
|
-
for (const v of Object.
|
|
1833
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
1834
|
+
if (typeof v !== "string") continue;
|
|
1835
|
+
strings.push(v);
|
|
1836
|
+
if (k === "class" || k === "class:list") {
|
|
1837
|
+
for (const t of v.split(/\s+/)) if (t) classTokens.add(t);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1770
1840
|
});
|
|
1771
1841
|
for (const c of front.consts) collectStrings(c.e, strings);
|
|
1772
1842
|
for (const p of front.props) if (p.def) collectStrings(p.def, strings);
|
|
1773
1843
|
const classCandidates = pickCandidates(strings);
|
|
1844
|
+
for (const t of classTokens) {
|
|
1845
|
+
if (!isCandidate(t)) lint.push({ level: "warning", code: "tailwind-skipped", message: `class token '${t}' is not a shape the compiler accepts, so no CSS is generated for it \u2014 rewrite it (e.g. an arbitrary value like mt-[-4rem]) or drop it` });
|
|
1846
|
+
}
|
|
1774
1847
|
let twCss = "";
|
|
1775
1848
|
if (!opts.skipTailwind) {
|
|
1776
1849
|
try {
|
|
@@ -2009,8 +2082,15 @@ function collectStrings(e, out) {
|
|
|
2009
2082
|
__name(collectStrings, "collectStrings");
|
|
2010
2083
|
|
|
2011
2084
|
// ../variant-compiler/src/runtime/eval.ts
|
|
2085
|
+
var JSX_VALUES = /* @__PURE__ */ new WeakSet();
|
|
2086
|
+
function makeJsxValue(nodes, scope) {
|
|
2087
|
+
const v = { __wvJsx: true, nodes, scope };
|
|
2088
|
+
JSX_VALUES.add(v);
|
|
2089
|
+
return v;
|
|
2090
|
+
}
|
|
2091
|
+
__name(makeJsxValue, "makeJsxValue");
|
|
2012
2092
|
function isJsxValue(v) {
|
|
2013
|
-
return typeof v === "object" && v !== null && v
|
|
2093
|
+
return typeof v === "object" && v !== null && JSX_VALUES.has(v);
|
|
2014
2094
|
}
|
|
2015
2095
|
__name(isJsxValue, "isJsxValue");
|
|
2016
2096
|
var FORBIDDEN_PROPS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -2149,7 +2229,7 @@ function evalExpr(e, scope, budget) {
|
|
|
2149
2229
|
return evalExpr(e.body, s, budget);
|
|
2150
2230
|
};
|
|
2151
2231
|
case "jsx":
|
|
2152
|
-
return
|
|
2232
|
+
return makeJsxValue(e.n, scope);
|
|
2153
2233
|
}
|
|
2154
2234
|
}
|
|
2155
2235
|
__name(evalExpr, "evalExpr");
|
|
@@ -2332,11 +2412,24 @@ function renderVariant(ir, content, ctx, opts = {}) {
|
|
|
2332
2412
|
}
|
|
2333
2413
|
for (const c of ir.consts) scope[c.name] = evalExpr(c.e, scope, budget);
|
|
2334
2414
|
const html = renderNodes(ir.root, scope, ctx, budget);
|
|
2335
|
-
const css = ir.css ? ir.css.replace(/__WV_ROOT__/g, `[data-wv-inst="${uid}"]`) : "";
|
|
2415
|
+
const css = ir.css ? layerize(ir.css.replace(/__WV_ROOT__/g, `[data-wv-inst="${uid}"]`)) : "";
|
|
2336
2416
|
const script = ir.script ? wrapScript(ir.script, uid) : "";
|
|
2337
2417
|
return { html, css, script, uid };
|
|
2338
2418
|
}
|
|
2339
2419
|
__name(renderVariant, "renderVariant");
|
|
2420
|
+
var STATEMENT_AT = /^\s*@[a-zA-Z-]+[^;{}]*;/;
|
|
2421
|
+
function layerize(css) {
|
|
2422
|
+
let rest = css;
|
|
2423
|
+
let head = "";
|
|
2424
|
+
for (; ; ) {
|
|
2425
|
+
const m = rest.match(STATEMENT_AT);
|
|
2426
|
+
if (!m) break;
|
|
2427
|
+
head += m[0].trim();
|
|
2428
|
+
rest = rest.slice(m[0].length);
|
|
2429
|
+
}
|
|
2430
|
+
return rest.trim() ? `${head}@layer wvf{${rest}}` : head;
|
|
2431
|
+
}
|
|
2432
|
+
__name(layerize, "layerize");
|
|
2340
2433
|
function wrapScript(body, uid) {
|
|
2341
2434
|
return `(function(${SHADOWED_GLOBALS.join(",")}){"use strict";var root=document.querySelector('[data-wv-inst="${uid}"]');if(!root)return;
|
|
2342
2435
|
${body}
|
|
@@ -2357,10 +2450,13 @@ __name(isPreviewThemeKey, "isPreviewThemeKey");
|
|
|
2357
2450
|
function buildPreviewDoc(ir, content, themeKey, opts = {}) {
|
|
2358
2451
|
const th = PREVIEW_THEMES[themeKey];
|
|
2359
2452
|
const out = renderVariant(ir, content, { t: /* @__PURE__ */ __name((k) => k, "t"), editChrome: opts.editChrome ?? false }, { uid: "wv-preview" });
|
|
2360
|
-
const vars = `--background:${th.background};--foreground:${th.foreground};--card:${th.card};--card-foreground:${th.foreground};--primary:${th.primary};--primary-foreground:${th.primaryFg};--secondary:${th.secondary};--secondary-foreground:#fff;--muted:${th.muted};--muted-foreground:${th.mutedFg};--accent:${th.accent};--accent-foreground:#000;--border:${th.border};--input:${th.border};--ring:${th.primary};--radius:${th.radius};--font-heading:${th.heading};--font-body:${th.body};--gradient-from:${th.primary};--gradient-to:${th.secondary};--spacing:.25rem;--site-max-width:72rem;`;
|
|
2453
|
+
const vars = `--background:${th.background};--foreground:${th.foreground};--card:${th.card};--card-foreground:${th.foreground};--primary:${th.primary};--primary-foreground:${th.primaryFg};--secondary:${th.secondary};--secondary-foreground:#fff;--muted:${th.muted};--muted-foreground:${th.mutedFg};--accent:${th.accent};--accent-foreground:#000;--border:${th.border};--input:${th.border};--ring:${th.primary};--radius:${th.radius};--font-heading:${th.heading};--font-body:${th.body};--gradient-from:${th.primary};--gradient-to:${th.secondary};--spacing:.25rem;--site-max-width:72rem;--font-heading-size:48px;--font-body-size:16px;`;
|
|
2361
2454
|
const tokens = `--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-primary:var(--primary);--color-primary-foreground:var(--primary-foreground);--color-secondary:var(--secondary);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);`;
|
|
2362
2455
|
return `<!doctype html><html data-theme="${themeKey === "dark" ? "dark" : "light"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex">
|
|
2363
|
-
<style
|
|
2456
|
+
<style>@layer wvfbase{*,::before,::after{box-sizing:border-box;border:0 solid var(--color-border)}html{font-size:var(--font-body-size,16px);-webkit-text-size-adjust:100%}body{margin:0;font-family:var(--font-body);color:var(--foreground);background:var(--background);line-height:1.5}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}h1,h2,h3,h4,p{margin:0}img,video,svg{display:block;max-width:100%;height:auto}a{color:inherit;text-decoration:inherit}button{font:inherit;color:inherit;background:none}ul,ol{margin:0;padding:0;list-style:none}}
|
|
2457
|
+
:root{${vars}${tokens}}
|
|
2458
|
+
h1{font-size:var(--font-heading-size,48px)}h2{font-size:calc(var(--font-heading-size,48px) * 0.75)}h3{font-size:calc(var(--font-heading-size,48px) * 0.6)}h4{font-size:calc(var(--font-heading-size,48px) * 0.5)}
|
|
2459
|
+
.edit-url-pill{display:none!important}</style>
|
|
2364
2460
|
<style>${out.css}</style></head><body><div data-wv-root data-wv-inst="${out.uid}">${out.html}</div>${out.script ? `<script>${out.script}</script>` : ""}</body></html>`;
|
|
2365
2461
|
}
|
|
2366
2462
|
__name(buildPreviewDoc, "buildPreviewDoc");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webto-id/variant-check",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Lint, compile and preview a Webto Variant Format (.astro subset) section variant with the exact compiler the webto.id marketplace uses.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|