@ggui-ai/ui-gen 0.17.0 → 0.18.0
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/adapters/generation-dispatch.js +252 -108
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.js +261 -113
- package/dist/advanced/index.js.map +1 -1
- package/dist/boilerplate.js +27 -3
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.js +70 -0
- package/dist/check/index.js.map +1 -1
- package/dist/evaluation/axis-checks/index.js +50 -0
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.js +50 -0
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.js +50 -0
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/index.js +27 -3
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js +4 -0
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/runtime.js +27 -3
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.js +27 -3
- package/dist/harness/types-public.js.map +1 -1
- package/dist/index.js +261 -113
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/boilerplate/free-design-prompt.ts +2 -0
- package/src/boilerplate/generate.ts +8 -1
- package/src/boilerplate/hard-sections.ts +31 -2
- package/src/boilerplate/system-prompt.ts +2 -0
- package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +5 -3
- package/src/boilerplate/templates/layouts/fullscreen-mobile.tsx.tmpl +3 -3
- package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +5 -3
- package/src/check/run-tier0.ts +152 -0
- package/src/contract-context.ts +13 -0
- package/src/evaluation/axis-checks/checks/universal.ts +64 -0
|
@@ -9,7 +9,7 @@ import { contentHash } from '@ggui-ai/protocol/content-hash';
|
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
11
|
import { runSandboxed } from '@ggui-ai/sandbox';
|
|
12
|
-
import
|
|
12
|
+
import ts6 from 'typescript';
|
|
13
13
|
import path, { dirname, resolve, join, basename } from 'path';
|
|
14
14
|
import { Linter } from 'eslint';
|
|
15
15
|
import { consumedTokenManifest } from '@ggui-ai/design/themes';
|
|
@@ -2933,53 +2933,53 @@ var init_render_check_host = __esm({
|
|
|
2933
2933
|
}
|
|
2934
2934
|
});
|
|
2935
2935
|
function extractWireCallSites(code) {
|
|
2936
|
-
const sf =
|
|
2936
|
+
const sf = ts6.createSourceFile(
|
|
2937
2937
|
"component.tsx",
|
|
2938
2938
|
code,
|
|
2939
|
-
|
|
2939
|
+
ts6.ScriptTarget.Latest,
|
|
2940
2940
|
/*setParentNodes*/
|
|
2941
2941
|
true,
|
|
2942
|
-
|
|
2942
|
+
ts6.ScriptKind.TSX
|
|
2943
2943
|
);
|
|
2944
2944
|
const sites = [];
|
|
2945
2945
|
function visit(node) {
|
|
2946
|
-
if (
|
|
2946
|
+
if (ts6.isCallExpression(node)) {
|
|
2947
2947
|
const callee = node.expression;
|
|
2948
|
-
if (
|
|
2948
|
+
if (ts6.isIdentifier(callee)) {
|
|
2949
2949
|
const kind = HOOK_TO_KIND[callee.text];
|
|
2950
2950
|
if (kind) {
|
|
2951
2951
|
const firstArg = node.arguments[0];
|
|
2952
|
-
if (firstArg &&
|
|
2952
|
+
if (firstArg && ts6.isStringLiteral(firstArg)) {
|
|
2953
2953
|
sites.push({ kind, name: firstArg.text });
|
|
2954
2954
|
}
|
|
2955
2955
|
}
|
|
2956
2956
|
}
|
|
2957
2957
|
}
|
|
2958
|
-
|
|
2958
|
+
ts6.forEachChild(node, visit);
|
|
2959
2959
|
}
|
|
2960
2960
|
visit(sf);
|
|
2961
2961
|
return sites;
|
|
2962
2962
|
}
|
|
2963
2963
|
function extractWireImports(code) {
|
|
2964
|
-
const sf =
|
|
2964
|
+
const sf = ts6.createSourceFile(
|
|
2965
2965
|
"component.tsx",
|
|
2966
2966
|
code,
|
|
2967
|
-
|
|
2967
|
+
ts6.ScriptTarget.Latest,
|
|
2968
2968
|
/*setParentNodes*/
|
|
2969
2969
|
true,
|
|
2970
|
-
|
|
2970
|
+
ts6.ScriptKind.TSX
|
|
2971
2971
|
);
|
|
2972
2972
|
const imported = /* @__PURE__ */ new Set();
|
|
2973
2973
|
for (const stmt of sf.statements) {
|
|
2974
|
-
if (!
|
|
2974
|
+
if (!ts6.isImportDeclaration(stmt)) continue;
|
|
2975
2975
|
const moduleSpecifier = stmt.moduleSpecifier;
|
|
2976
|
-
if (!
|
|
2976
|
+
if (!ts6.isStringLiteral(moduleSpecifier)) continue;
|
|
2977
2977
|
if (moduleSpecifier.text !== "@ggui-ai/wire") continue;
|
|
2978
2978
|
const clause = stmt.importClause;
|
|
2979
2979
|
if (!clause) continue;
|
|
2980
2980
|
const bindings = clause.namedBindings;
|
|
2981
2981
|
if (!bindings) continue;
|
|
2982
|
-
if (!
|
|
2982
|
+
if (!ts6.isNamedImports(bindings)) continue;
|
|
2983
2983
|
for (const el of bindings.elements) {
|
|
2984
2984
|
imported.add(el.name.text);
|
|
2985
2985
|
}
|
|
@@ -3057,11 +3057,11 @@ var init_extract_wire_calls = __esm({
|
|
|
3057
3057
|
}
|
|
3058
3058
|
});
|
|
3059
3059
|
function extractPropsInterface(code) {
|
|
3060
|
-
const sf =
|
|
3060
|
+
const sf = ts6.createSourceFile("component.tsx", code, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TSX);
|
|
3061
3061
|
const props = [];
|
|
3062
3062
|
function visitMembers(members) {
|
|
3063
3063
|
for (const member of members) {
|
|
3064
|
-
if (
|
|
3064
|
+
if (ts6.isPropertySignature(member) && member.name) {
|
|
3065
3065
|
const name = member.name.getText(sf);
|
|
3066
3066
|
const type = member.type ? member.type.getText(sf) : "unknown";
|
|
3067
3067
|
const optional = !!member.questionToken;
|
|
@@ -3070,17 +3070,17 @@ function extractPropsInterface(code) {
|
|
|
3070
3070
|
}
|
|
3071
3071
|
}
|
|
3072
3072
|
function visit(node) {
|
|
3073
|
-
if (
|
|
3073
|
+
if (ts6.isInterfaceDeclaration(node) && node.name.text === "Props") {
|
|
3074
3074
|
visitMembers(node.members);
|
|
3075
3075
|
return;
|
|
3076
3076
|
}
|
|
3077
|
-
if (
|
|
3078
|
-
if (
|
|
3077
|
+
if (ts6.isTypeAliasDeclaration(node) && node.name.text === "Props") {
|
|
3078
|
+
if (ts6.isTypeLiteralNode(node.type)) {
|
|
3079
3079
|
visitMembers(node.type.members);
|
|
3080
3080
|
}
|
|
3081
3081
|
return;
|
|
3082
3082
|
}
|
|
3083
|
-
|
|
3083
|
+
ts6.forEachChild(node, visit);
|
|
3084
3084
|
}
|
|
3085
3085
|
visit(sf);
|
|
3086
3086
|
return props.length > 0 ? props : null;
|
|
@@ -3293,12 +3293,12 @@ function generateFix(code, message, sourceLine) {
|
|
|
3293
3293
|
}
|
|
3294
3294
|
}
|
|
3295
3295
|
function parseAndStore(vfs, virtualPath, content) {
|
|
3296
|
-
const sourceFile =
|
|
3296
|
+
const sourceFile = ts6.createSourceFile(
|
|
3297
3297
|
virtualPath,
|
|
3298
3298
|
content,
|
|
3299
|
-
|
|
3299
|
+
ts6.ScriptTarget.ES2020,
|
|
3300
3300
|
true,
|
|
3301
|
-
virtualPath.endsWith(".tsx") ?
|
|
3301
|
+
virtualPath.endsWith(".tsx") ? ts6.ScriptKind.TSX : ts6.ScriptKind.TS
|
|
3302
3302
|
);
|
|
3303
3303
|
vfs.set(virtualPath, { content, sourceFile });
|
|
3304
3304
|
}
|
|
@@ -3440,12 +3440,12 @@ function resolveModuleName(vfs, name, containingFile) {
|
|
|
3440
3440
|
return { resolvedModule: void 0 };
|
|
3441
3441
|
}
|
|
3442
3442
|
function createVfsHost(vfs, componentCode) {
|
|
3443
|
-
const componentFile =
|
|
3443
|
+
const componentFile = ts6.createSourceFile(
|
|
3444
3444
|
"Component.tsx",
|
|
3445
3445
|
componentCode,
|
|
3446
|
-
|
|
3446
|
+
ts6.ScriptTarget.ES2020,
|
|
3447
3447
|
true,
|
|
3448
|
-
|
|
3448
|
+
ts6.ScriptKind.TSX
|
|
3449
3449
|
);
|
|
3450
3450
|
const host = {
|
|
3451
3451
|
getSourceFile(fileName) {
|
|
@@ -3500,7 +3500,7 @@ function resolved(resolvedFileName) {
|
|
|
3500
3500
|
resolvedModule: {
|
|
3501
3501
|
resolvedFileName,
|
|
3502
3502
|
isExternalLibraryImport: true,
|
|
3503
|
-
extension:
|
|
3503
|
+
extension: ts6.Extension.Dts
|
|
3504
3504
|
}
|
|
3505
3505
|
};
|
|
3506
3506
|
}
|
|
@@ -3522,18 +3522,18 @@ async function typecheck(code, dtsMap) {
|
|
|
3522
3522
|
const prefixedCode = SYNTHETIC_PREFIX + code;
|
|
3523
3523
|
const lineOffset = SYNTHETIC_PREFIX.split("\n").length - 1;
|
|
3524
3524
|
const host = createVfsHost(effectiveVfs, prefixedCode);
|
|
3525
|
-
const program =
|
|
3526
|
-
const diagnostics =
|
|
3525
|
+
const program = ts6.createProgram(["Component.tsx"], COMPILER_OPTIONS, host);
|
|
3526
|
+
const diagnostics = ts6.getPreEmitDiagnostics(program);
|
|
3527
3527
|
const errors = [];
|
|
3528
3528
|
const warnings = [];
|
|
3529
3529
|
for (const diag of diagnostics) {
|
|
3530
3530
|
if (diag.file && diag.file.fileName !== "Component.tsx") continue;
|
|
3531
3531
|
if (!diag.file) continue;
|
|
3532
|
-
const diagLine =
|
|
3532
|
+
const diagLine = ts6.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;
|
|
3533
3533
|
if (diagLine < lineOffset) continue;
|
|
3534
|
-
const rawLine = diag.file ?
|
|
3534
|
+
const rawLine = diag.file ? ts6.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1 : 0;
|
|
3535
3535
|
const line = Math.max(1, rawLine - lineOffset);
|
|
3536
|
-
const message =
|
|
3536
|
+
const message = ts6.flattenDiagnosticMessageText(diag.messageText, "\n");
|
|
3537
3537
|
const code_ = diag.code;
|
|
3538
3538
|
const sourceLines = diag.file.text.split("\n");
|
|
3539
3539
|
const sourceLine = rawLine >= 1 && rawLine <= sourceLines.length ? sourceLines[rawLine - 1]?.trim() ?? "" : "";
|
|
@@ -3580,8 +3580,8 @@ var init_type_checker = __esm({
|
|
|
3580
3580
|
]);
|
|
3581
3581
|
vfsCache = null;
|
|
3582
3582
|
COMPILER_OPTIONS = {
|
|
3583
|
-
target:
|
|
3584
|
-
module:
|
|
3583
|
+
target: ts6.ScriptTarget.ES2020,
|
|
3584
|
+
module: ts6.ModuleKind.ESNext,
|
|
3585
3585
|
// Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)
|
|
3586
3586
|
// supplies `import React from 'react'` plus a `declare global` JSX
|
|
3587
3587
|
// namespace shim: `@types/react` v19 removed the *global* `JSX`
|
|
@@ -3589,8 +3589,8 @@ var init_type_checker = __esm({
|
|
|
3589
3589
|
// for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without
|
|
3590
3590
|
// the shim, `<div>` degrades to `any` and every typed component
|
|
3591
3591
|
// falsely rejects the intrinsic `key` prop.
|
|
3592
|
-
jsx:
|
|
3593
|
-
moduleResolution:
|
|
3592
|
+
jsx: ts6.JsxEmit.React,
|
|
3593
|
+
moduleResolution: ts6.ModuleResolutionKind.Bundler,
|
|
3594
3594
|
strict: false,
|
|
3595
3595
|
strictNullChecks: true,
|
|
3596
3596
|
// Catch undefined.foo() errors that cause runtime crashes
|
|
@@ -3732,17 +3732,17 @@ var init_react_linter = __esm({
|
|
|
3732
3732
|
}
|
|
3733
3733
|
});
|
|
3734
3734
|
function stripComments(code) {
|
|
3735
|
-
const scanner =
|
|
3736
|
-
|
|
3735
|
+
const scanner = ts6.createScanner(
|
|
3736
|
+
ts6.ScriptTarget.Latest,
|
|
3737
3737
|
/* skipTrivia */
|
|
3738
3738
|
false,
|
|
3739
|
-
|
|
3739
|
+
ts6.LanguageVariant.JSX,
|
|
3740
3740
|
code
|
|
3741
3741
|
);
|
|
3742
3742
|
let out = "";
|
|
3743
|
-
for (let token = scanner.scan(); token !==
|
|
3743
|
+
for (let token = scanner.scan(); token !== ts6.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
3744
3744
|
const text = scanner.getTokenText();
|
|
3745
|
-
out += token ===
|
|
3745
|
+
out += token === ts6.SyntaxKind.SingleLineCommentTrivia || token === ts6.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
|
|
3746
3746
|
}
|
|
3747
3747
|
return out;
|
|
3748
3748
|
}
|
|
@@ -3765,20 +3765,20 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3765
3765
|
"Link"
|
|
3766
3766
|
]);
|
|
3767
3767
|
const HANDLER_ATTRS = ["onClick", "onChange", "onPress", "onSelect"];
|
|
3768
|
-
const sf =
|
|
3768
|
+
const sf = ts6.createSourceFile(
|
|
3769
3769
|
"source.tsx",
|
|
3770
3770
|
sourceCode,
|
|
3771
|
-
|
|
3771
|
+
ts6.ScriptTarget.Latest,
|
|
3772
3772
|
/* setParentNodes */
|
|
3773
3773
|
true,
|
|
3774
|
-
|
|
3774
|
+
ts6.ScriptKind.TSX
|
|
3775
3775
|
);
|
|
3776
3776
|
function tagNameText(node) {
|
|
3777
|
-
return
|
|
3777
|
+
return ts6.isIdentifier(node.tagName) ? node.tagName.text : void 0;
|
|
3778
3778
|
}
|
|
3779
3779
|
function attrExpression(node, attrName) {
|
|
3780
3780
|
for (const attr of node.attributes.properties) {
|
|
3781
|
-
if (
|
|
3781
|
+
if (ts6.isJsxAttribute(attr) && ts6.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts6.isJsxExpression(attr.initializer)) {
|
|
3782
3782
|
return attr.initializer.expression;
|
|
3783
3783
|
}
|
|
3784
3784
|
}
|
|
@@ -3790,19 +3790,19 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3790
3790
|
if (anyElementIsInteractive) return true;
|
|
3791
3791
|
if (!TRAIT_HOST_TAGS.has(tag)) return false;
|
|
3792
3792
|
const asExpr = attrExpression(node, "as");
|
|
3793
|
-
return asExpr !== void 0 &&
|
|
3793
|
+
return asExpr !== void 0 && ts6.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
|
|
3794
3794
|
}
|
|
3795
3795
|
function extractCalleeName(expr) {
|
|
3796
3796
|
if (expr === void 0) return void 0;
|
|
3797
|
-
if (
|
|
3798
|
-
if (
|
|
3797
|
+
if (ts6.isIdentifier(expr)) return expr.text;
|
|
3798
|
+
if (ts6.isArrowFunction(expr) || ts6.isFunctionExpression(expr)) {
|
|
3799
3799
|
const body = expr.body;
|
|
3800
|
-
if (
|
|
3800
|
+
if (ts6.isCallExpression(body) && ts6.isIdentifier(body.expression)) {
|
|
3801
3801
|
return body.expression.text;
|
|
3802
3802
|
}
|
|
3803
|
-
if (
|
|
3803
|
+
if (ts6.isBlock(body)) {
|
|
3804
3804
|
for (const stmt of body.statements) {
|
|
3805
|
-
if (
|
|
3805
|
+
if (ts6.isExpressionStatement(stmt) && ts6.isCallExpression(stmt.expression) && ts6.isIdentifier(stmt.expression.expression)) {
|
|
3806
3806
|
return stmt.expression.expression.text;
|
|
3807
3807
|
}
|
|
3808
3808
|
}
|
|
@@ -3814,7 +3814,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3814
3814
|
let found;
|
|
3815
3815
|
function walk(node) {
|
|
3816
3816
|
if (found !== void 0) return;
|
|
3817
|
-
const opening =
|
|
3817
|
+
const opening = ts6.isJsxElement(node) ? node.openingElement : ts6.isJsxSelfClosingElement(node) ? node : void 0;
|
|
3818
3818
|
if (opening !== void 0) {
|
|
3819
3819
|
const tag = tagNameText(opening);
|
|
3820
3820
|
if (tag !== void 0 && (anyElementIsInteractive || INTERACTIVE_DESCENDANTS.has(tag))) {
|
|
@@ -3830,13 +3830,13 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3830
3830
|
}
|
|
3831
3831
|
}
|
|
3832
3832
|
}
|
|
3833
|
-
|
|
3833
|
+
ts6.forEachChild(node, walk);
|
|
3834
3834
|
}
|
|
3835
|
-
|
|
3835
|
+
ts6.forEachChild(outer, walk);
|
|
3836
3836
|
return found;
|
|
3837
3837
|
}
|
|
3838
3838
|
function visit(node) {
|
|
3839
|
-
if (
|
|
3839
|
+
if (ts6.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
|
|
3840
3840
|
const outerCallee = extractCalleeName(attrExpression(node.openingElement, "onClick")) ?? extractCalleeName(attrExpression(node.openingElement, "onPress"));
|
|
3841
3841
|
if (outerCallee !== void 0 && bindings.has(outerCallee)) {
|
|
3842
3842
|
const match = findMatchingInteractiveDescendant(node, outerCallee);
|
|
@@ -3857,7 +3857,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3857
3857
|
}
|
|
3858
3858
|
}
|
|
3859
3859
|
}
|
|
3860
|
-
|
|
3860
|
+
ts6.forEachChild(node, visit);
|
|
3861
3861
|
}
|
|
3862
3862
|
visit(sf);
|
|
3863
3863
|
return issues;
|
|
@@ -3876,6 +3876,63 @@ function isGadgetExportImported(source, pkg, exportName) {
|
|
|
3876
3876
|
}
|
|
3877
3877
|
return false;
|
|
3878
3878
|
}
|
|
3879
|
+
function detectComponentNeverReturns(sourceCode) {
|
|
3880
|
+
const sf = ts6.createSourceFile(
|
|
3881
|
+
"source.tsx",
|
|
3882
|
+
sourceCode,
|
|
3883
|
+
ts6.ScriptTarget.Latest,
|
|
3884
|
+
/* setParentNodes */
|
|
3885
|
+
true,
|
|
3886
|
+
ts6.ScriptKind.TSX
|
|
3887
|
+
);
|
|
3888
|
+
const isFunctionLike = (n) => ts6.isFunctionDeclaration(n) || ts6.isFunctionExpression(n) || ts6.isArrowFunction(n) || ts6.isMethodDeclaration(n) || ts6.isClassDeclaration(n) || ts6.isClassExpression(n);
|
|
3889
|
+
function resolveTopLevel(name) {
|
|
3890
|
+
for (const st of sf.statements) {
|
|
3891
|
+
if (ts6.isFunctionDeclaration(st) && st.name?.text === name) return st;
|
|
3892
|
+
if (ts6.isVariableStatement(st)) {
|
|
3893
|
+
for (const decl of st.declarationList.declarations) {
|
|
3894
|
+
if (ts6.isIdentifier(decl.name) && decl.name.text === name && decl.initializer !== void 0 && (ts6.isArrowFunction(decl.initializer) || ts6.isFunctionExpression(decl.initializer))) {
|
|
3895
|
+
return decl.initializer;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
return void 0;
|
|
3901
|
+
}
|
|
3902
|
+
let component;
|
|
3903
|
+
for (const st of sf.statements) {
|
|
3904
|
+
if (ts6.isFunctionDeclaration(st) && st.modifiers?.some((m) => m.kind === ts6.SyntaxKind.DefaultKeyword)) {
|
|
3905
|
+
component = st;
|
|
3906
|
+
break;
|
|
3907
|
+
}
|
|
3908
|
+
if (ts6.isExportAssignment(st) && st.isExportEquals !== true) {
|
|
3909
|
+
const expr = st.expression;
|
|
3910
|
+
if (ts6.isArrowFunction(expr) || ts6.isFunctionExpression(expr)) component = expr;
|
|
3911
|
+
else if (ts6.isIdentifier(expr)) component = resolveTopLevel(expr.text);
|
|
3912
|
+
break;
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
if (component === void 0) return null;
|
|
3916
|
+
const body = component.body;
|
|
3917
|
+
if (body === void 0 || !ts6.isBlock(body)) return null;
|
|
3918
|
+
let returnsValue = false;
|
|
3919
|
+
let jsxStatementLine;
|
|
3920
|
+
const visit = (node) => {
|
|
3921
|
+
if (returnsValue) return;
|
|
3922
|
+
if (ts6.isReturnStatement(node)) {
|
|
3923
|
+
if (node.expression !== void 0) returnsValue = true;
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
if (jsxStatementLine === void 0 && ts6.isExpressionStatement(node) && (ts6.isJsxElement(node.expression) || ts6.isJsxFragment(node.expression) || ts6.isJsxSelfClosingElement(node.expression))) {
|
|
3927
|
+
jsxStatementLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
3928
|
+
}
|
|
3929
|
+
node.forEachChild((child) => {
|
|
3930
|
+
if (!isFunctionLike(child)) visit(child);
|
|
3931
|
+
});
|
|
3932
|
+
};
|
|
3933
|
+
for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
|
|
3934
|
+
return returnsValue ? null : { jsxStatementLine };
|
|
3935
|
+
}
|
|
3879
3936
|
async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
|
|
3880
3937
|
const issues = [];
|
|
3881
3938
|
const lines = sourceCode.split("\n");
|
|
@@ -4295,6 +4352,19 @@ ${errorDetail}`,
|
|
|
4295
4352
|
fix: "Add export default function Component(props: Props) { ... }"
|
|
4296
4353
|
});
|
|
4297
4354
|
}
|
|
4355
|
+
const neverReturns = detectComponentNeverReturns(sourceCode);
|
|
4356
|
+
if (neverReturns !== null) {
|
|
4357
|
+
issues.push({
|
|
4358
|
+
tier: 0,
|
|
4359
|
+
result: "fail",
|
|
4360
|
+
category: "compile",
|
|
4361
|
+
subcategory: "renders-nothing",
|
|
4362
|
+
severity: "critical",
|
|
4363
|
+
description: neverReturns.jsxStatementLine === void 0 ? "The component never returns its tree \u2014 it renders a blank frame" : `The component never returns its tree: the JSX at line ${neverReturns.jsxStatementLine} sits at statement position, so it is built and thrown away and the frame renders blank`,
|
|
4364
|
+
fix: "Wrap the tree in the return the boilerplate gave you: `return (` before the root element and `);` after it.",
|
|
4365
|
+
...neverReturns.jsxStatementLine === void 0 ? {} : { line: neverReturns.jsxStatementLine }
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4298
4368
|
if (contract) {
|
|
4299
4369
|
try {
|
|
4300
4370
|
const contractIssues = validateAllContracts(sourceCode, contract);
|
|
@@ -10855,6 +10925,50 @@ function runViewportSized(input) {
|
|
|
10855
10925
|
)
|
|
10856
10926
|
];
|
|
10857
10927
|
}
|
|
10928
|
+
var TERMINAL_WORDS = /* @__PURE__ */ new Set([
|
|
10929
|
+
"submit",
|
|
10930
|
+
"confirm",
|
|
10931
|
+
"approve",
|
|
10932
|
+
"schedule",
|
|
10933
|
+
"reserve",
|
|
10934
|
+
"book",
|
|
10935
|
+
"pay",
|
|
10936
|
+
"checkout",
|
|
10937
|
+
"purchase",
|
|
10938
|
+
"order"
|
|
10939
|
+
]);
|
|
10940
|
+
var TERMINAL_LABEL_RX = /\b(submit|confirm|approve|schedule|reserve|book|pay|checkout|purchase|place\s+order)\b/i;
|
|
10941
|
+
var GUARD_RX = /\b(disabled|aria-disabled)\s*=/;
|
|
10942
|
+
function nameWords(name) {
|
|
10943
|
+
return name.split(/(?=[A-Z])|[_\-\s]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
|
|
10944
|
+
}
|
|
10945
|
+
function findUnguardedTerminalControls(sourceCode) {
|
|
10946
|
+
if (GUARD_RX.test(sourceCode)) return [];
|
|
10947
|
+
const names = /* @__PURE__ */ new Set();
|
|
10948
|
+
for (const m of sourceCode.matchAll(/useAction(?:<[^>]*>)?\(\s*['"`]([^'"`]+)['"`]/g)) {
|
|
10949
|
+
const name = m[1] ?? "";
|
|
10950
|
+
if (nameWords(name).some((w) => TERMINAL_WORDS.has(w))) names.add(name);
|
|
10951
|
+
}
|
|
10952
|
+
for (const m of sourceCode.matchAll(/>([^<>{}]{1,60})<\/Button>/g)) {
|
|
10953
|
+
const label = (m[1] ?? "").replace(/\s+/g, " ").trim();
|
|
10954
|
+
if (label.length > 0 && TERMINAL_LABEL_RX.test(label)) names.add(label.slice(0, 40));
|
|
10955
|
+
}
|
|
10956
|
+
return [...names];
|
|
10957
|
+
}
|
|
10958
|
+
function runTerminalActionUnguarded(input) {
|
|
10959
|
+
if (input.compiledCode === null) return [];
|
|
10960
|
+
const found = findUnguardedTerminalControls(input.sourceCode);
|
|
10961
|
+
if (found.length === 0) return [];
|
|
10962
|
+
const list = found.map((t) => `"${t}"`).join(", ");
|
|
10963
|
+
return [
|
|
10964
|
+
mkIssue(
|
|
10965
|
+
"universal.terminal_action_unguarded",
|
|
10966
|
+
`${list} reads as an action the user means once, and nothing in this component disables the control after it fires \u2014 a double-click sends it twice.`,
|
|
10967
|
+
'Hold the fired state (`const [submitted, setSubmitted] = useState(false)`), set it in the handler, and pass `disabled={submitted}` to that control with a word that says so ("Submitted"). If the action IS meant to repeat \u2014 sending a message, adding an item \u2014 leave it armed and ignore this.',
|
|
10968
|
+
"warn"
|
|
10969
|
+
)
|
|
10970
|
+
];
|
|
10971
|
+
}
|
|
10858
10972
|
var UNIVERSAL_CHECKS = [
|
|
10859
10973
|
{
|
|
10860
10974
|
id: "universal.icon_name_known",
|
|
@@ -10874,6 +10988,12 @@ var UNIVERSAL_CHECKS = [
|
|
|
10874
10988
|
values: ALL_RENDER_VALUES,
|
|
10875
10989
|
run: runViewportSized
|
|
10876
10990
|
},
|
|
10991
|
+
{
|
|
10992
|
+
id: "universal.terminal_action_unguarded",
|
|
10993
|
+
axis: "render",
|
|
10994
|
+
values: ALL_RENDER_VALUES,
|
|
10995
|
+
run: runTerminalActionUnguarded
|
|
10996
|
+
},
|
|
10877
10997
|
{
|
|
10878
10998
|
id: "universal.accent_text_ink",
|
|
10879
10999
|
axis: "render",
|
|
@@ -12739,7 +12859,8 @@ type ${typeName} = ${tsType};`
|
|
|
12739
12859
|
);
|
|
12740
12860
|
const callSig = tsType === "void" ? "() => void \u2014 fire and forget" : `(data: ${tsType}) => void`;
|
|
12741
12861
|
const toolHint = tool ? ` \u2192 nextStep: ${tool}` : "";
|
|
12742
|
-
|
|
12862
|
+
const onceHint = " \u2014 if this is meant ONCE, disable its control after it fires";
|
|
12863
|
+
actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
|
|
12743
12864
|
}
|
|
12744
12865
|
const streamChannels = contract?.streamSpec ?? {};
|
|
12745
12866
|
const streamChannelEntries = Object.entries(streamChannels);
|
|
@@ -13085,12 +13206,33 @@ Rules:
|
|
|
13085
13206
|
3. Default values come from the current request \u2014 so the component renders correctly standalone.
|
|
13086
13207
|
4. Copy comes from the props. NEVER invent display text the contract does not carry \u2014 no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. A caps label exists only when a prop supplies its text (\`<Text caps>{props.section}</Text>\`); a heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render.
|
|
13087
13208
|
5. Props interface must be typed and exported.`;
|
|
13088
|
-
var
|
|
13209
|
+
var TERMINAL_ACTIONS = `## Actions the user means once
|
|
13210
|
+
|
|
13211
|
+
A second press is a mistake only when it would make a SECOND THING HAPPEN IN THE WORLD \u2014 a second booking, a second charge, a second order, a second application sent. If pressing again would only write the SAME STATE AGAIN \u2014 saving, updating, renaming, setting a preference, toggling \u2014 nothing is duplicated and the control stays armed.
|
|
13212
|
+
|
|
13213
|
+
Guard a control only where you have evidence, not a feeling about the verb: the request itself names a one-time commitment \u2014 "submit the application", "place the order", "confirm the booking".
|
|
13214
|
+
|
|
13215
|
+
**When in doubt, leave it armed.** A control the user cannot press a second time has lost them something that worked \u2014 save, edit, save again is ordinary. A control left armed is only what every interface does by default.
|
|
13216
|
+
|
|
13217
|
+
When you do guard one:
|
|
13218
|
+
- Hold a small piece of state for it and set it in the handler: \`const [submitted, setSubmitted] = useState(false)\`.
|
|
13219
|
+
- \`disabled={submitted}\` on the control that fires it, and say so in the label or beside it ("Submitted", "Request sent") \u2014 a control that is disabled without explanation reads as broken.
|
|
13220
|
+
- A card re-rendered with new props starts fresh; the state above lives with the component, not with the request.
|
|
13221
|
+
|
|
13222
|
+
Nothing in the wire DECLARES one-shot-ness yet, which is why the test is what a second press would DO \u2014 not how final the word sounds.`;
|
|
13223
|
+
var FRAME_SIZING = `## Sizing \u2014 the frame owns the height and the width
|
|
13089
13224
|
|
|
13090
13225
|
The host sizes the iframe; the component never sizes itself to the viewport. NEVER write \`100vh\` / \`100dvh\` / \`100svh\` (or an \`h-screen\` / \`min-h-screen\` class) as a height on any element:
|
|
13091
13226
|
- An inline card takes its natural height. A content-sized frame is measured FROM the content, so a \`100vh\` root can never shrink and grows the frame on every re-measure.
|
|
13092
13227
|
- Under fullscreen the frame already stretches your root to its full height. Centre inside it with flex (\`display: flex; flex-direction: column; justify-content: center\`), never with a viewport height.
|
|
13093
|
-
- To fill a parent that has a height use \`height: 100%\` or \`flex: 1\`; a viewport unit is only ever a \`max-height\` cap on a scroll region
|
|
13228
|
+
- To fill a parent that has a height use \`height: 100%\` or \`flex: 1\`; a viewport unit is only ever a \`max-height\` cap on a scroll region.
|
|
13229
|
+
|
|
13230
|
+
The width works the same way, and the mistake is the mirror image: **your outermost element FILLS the frame; a width cap belongs INSIDE it, on the column that holds text.**
|
|
13231
|
+
|
|
13232
|
+
- Never put \`maxWidth\` (or a \`max-w-*\` class) on the element you return at the top. On a wide canvas that turns your card into a narrow strip with the frame's ground on either side of it, and on a narrow one it does nothing \u2014 the same composition reading differently at two sizes.
|
|
13233
|
+
- DO cap the reading measure inside: a column of prose is easiest to read at 60\u201370 characters, so wrap the TEXT in a \`<Container maxWidth="sm">\` (or your own \`max-width\`) and leave the root filling. A hero band, a toolbar, a table or an image then still uses the whole width, which is what makes a wide card look composed rather than padded.
|
|
13234
|
+
- Centre that inner column with the container's own centring, not by shrinking the root.
|
|
13235
|
+
- **They are two different elements.** Even when the whole card is one column of text, the root still fills and the column sits INSIDE it \u2014 \`<Box padding="lg"><Container maxWidth="sm">\u2026</Container></Box>\`, never a root that is itself the container. A centred card is centring inside a filled frame, not a shrunken frame.`;
|
|
13094
13236
|
var COMPONENT_STRUCTURE = `## Component Structure
|
|
13095
13237
|
|
|
13096
13238
|
Keep JSX nesting depth to 3\u20135 levels. When deeper, extract repeated/complex sections into helper components \u2014 named functions defined above the main Component in the same file. Helpers take data + callbacks via props; they don't own state.
|
|
@@ -13302,6 +13444,7 @@ ${axisSection}
|
|
|
13302
13444
|
CONTRACT_SURFACE,
|
|
13303
13445
|
DEFENSIVE_CODING,
|
|
13304
13446
|
GESTURE_ROUTING,
|
|
13447
|
+
TERMINAL_ACTIONS,
|
|
13305
13448
|
FREE_GESTURE_SURFACE,
|
|
13306
13449
|
ANTI_PATTERNS,
|
|
13307
13450
|
CROSS_REFERENCE_RULES,
|
|
@@ -13332,11 +13475,11 @@ ${inputs.primitivesDoc}`
|
|
|
13332
13475
|
}
|
|
13333
13476
|
var VIRTUAL_DTS_PATH = "/__gadget__.d.ts";
|
|
13334
13477
|
var EXTRACTOR_COMPILER_OPTIONS = {
|
|
13335
|
-
target:
|
|
13336
|
-
module:
|
|
13337
|
-
moduleResolution:
|
|
13478
|
+
target: ts6.ScriptTarget.ESNext,
|
|
13479
|
+
module: ts6.ModuleKind.ESNext,
|
|
13480
|
+
moduleResolution: ts6.ModuleResolutionKind.Bundler,
|
|
13338
13481
|
allowJs: false,
|
|
13339
|
-
jsx:
|
|
13482
|
+
jsx: ts6.JsxEmit.ReactJSX,
|
|
13340
13483
|
noEmit: true,
|
|
13341
13484
|
esModuleInterop: true,
|
|
13342
13485
|
// skipLibCheck so an unresolvable wrapper-internal import (the
|
|
@@ -13348,31 +13491,31 @@ var EXTRACTOR_COMPILER_OPTIONS = {
|
|
|
13348
13491
|
var callSignatureCache = /* @__PURE__ */ new Map();
|
|
13349
13492
|
var componentPropsCache = /* @__PURE__ */ new Map();
|
|
13350
13493
|
function buildExtractorContext(dtsContent, names) {
|
|
13351
|
-
const sourceFile =
|
|
13494
|
+
const sourceFile = ts6.createSourceFile(
|
|
13352
13495
|
VIRTUAL_DTS_PATH,
|
|
13353
13496
|
dtsContent,
|
|
13354
|
-
|
|
13497
|
+
ts6.ScriptTarget.ESNext,
|
|
13355
13498
|
/* setParentNodes */
|
|
13356
13499
|
true,
|
|
13357
|
-
|
|
13500
|
+
ts6.ScriptKind.TS
|
|
13358
13501
|
);
|
|
13359
|
-
const defaultLibName =
|
|
13502
|
+
const defaultLibName = ts6.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
|
|
13360
13503
|
const host = {
|
|
13361
13504
|
getSourceFile(fileName) {
|
|
13362
13505
|
if (fileName === VIRTUAL_DTS_PATH) return sourceFile;
|
|
13363
|
-
const libContent =
|
|
13364
|
-
|
|
13506
|
+
const libContent = ts6.sys.readFile(
|
|
13507
|
+
ts6.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
|
|
13365
13508
|
/[^/\\]+$/,
|
|
13366
13509
|
fileName
|
|
13367
13510
|
)
|
|
13368
13511
|
);
|
|
13369
13512
|
if (libContent !== void 0) {
|
|
13370
|
-
return
|
|
13513
|
+
return ts6.createSourceFile(
|
|
13371
13514
|
fileName,
|
|
13372
13515
|
libContent,
|
|
13373
|
-
|
|
13516
|
+
ts6.ScriptTarget.ESNext,
|
|
13374
13517
|
true,
|
|
13375
|
-
|
|
13518
|
+
ts6.ScriptKind.TS
|
|
13376
13519
|
);
|
|
13377
13520
|
}
|
|
13378
13521
|
return void 0;
|
|
@@ -13396,14 +13539,14 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
13396
13539
|
},
|
|
13397
13540
|
fileExists(f) {
|
|
13398
13541
|
if (f === VIRTUAL_DTS_PATH) return true;
|
|
13399
|
-
return
|
|
13542
|
+
return ts6.sys.fileExists(f);
|
|
13400
13543
|
},
|
|
13401
13544
|
readFile(f) {
|
|
13402
13545
|
if (f === VIRTUAL_DTS_PATH) return dtsContent;
|
|
13403
|
-
return
|
|
13546
|
+
return ts6.sys.readFile(f);
|
|
13404
13547
|
}
|
|
13405
13548
|
};
|
|
13406
|
-
const program =
|
|
13549
|
+
const program = ts6.createProgram(
|
|
13407
13550
|
[VIRTUAL_DTS_PATH],
|
|
13408
13551
|
EXTRACTOR_COMPILER_OPTIONS,
|
|
13409
13552
|
host
|
|
@@ -13421,9 +13564,9 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
13421
13564
|
}
|
|
13422
13565
|
const MAX_DEPTH2 = 4;
|
|
13423
13566
|
function renderType(t, depth) {
|
|
13424
|
-
const plain = () => checker.typeToString(t, void 0,
|
|
13567
|
+
const plain = () => checker.typeToString(t, void 0, ts6.TypeFormatFlags.NoTruncation);
|
|
13425
13568
|
if (depth >= MAX_DEPTH2) return plain();
|
|
13426
|
-
if ((t.getFlags() &
|
|
13569
|
+
if ((t.getFlags() & ts6.TypeFlags.Boolean) !== 0) return "boolean";
|
|
13427
13570
|
if (t.isUnion()) {
|
|
13428
13571
|
return t.types.map((member) => renderType(member, depth)).join(" | ");
|
|
13429
13572
|
}
|
|
@@ -13437,13 +13580,13 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
13437
13580
|
const params = cs.getParameters().map((p) => {
|
|
13438
13581
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
13439
13582
|
const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
|
|
13440
|
-
const optional = decl !== void 0 &&
|
|
13583
|
+
const optional = decl !== void 0 && ts6.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
|
|
13441
13584
|
return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
|
|
13442
13585
|
}).join(", ");
|
|
13443
13586
|
const ret = renderType(cs.getReturnType(), depth + 1);
|
|
13444
13587
|
return `(${params}) => ${ret}`;
|
|
13445
13588
|
}
|
|
13446
|
-
const isObject = (t.getFlags() &
|
|
13589
|
+
const isObject = (t.getFlags() & ts6.TypeFlags.Object) !== 0;
|
|
13447
13590
|
const isArrayOrTuple = checker.isArrayType(t) || checker.isTupleType(t);
|
|
13448
13591
|
const typeArgs = t.typeArguments;
|
|
13449
13592
|
if (symbol !== void 0 && isObject && !isArrayOrTuple && !isWrapperLocal(symbol) && typeArgs !== void 0 && typeArgs.length > 0) {
|
|
@@ -13456,7 +13599,7 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
13456
13599
|
const body = props.map((p) => {
|
|
13457
13600
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
13458
13601
|
const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
|
|
13459
|
-
const optional = (p.getFlags() &
|
|
13602
|
+
const optional = (p.getFlags() & ts6.SymbolFlags.Optional) !== 0;
|
|
13460
13603
|
return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
|
|
13461
13604
|
}).join("; ");
|
|
13462
13605
|
return `{ ${body} }`;
|
|
@@ -13471,7 +13614,7 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
13471
13614
|
const name = symbol.getName();
|
|
13472
13615
|
if (!requested.has(name)) continue;
|
|
13473
13616
|
if (signaturesByName.has(name)) continue;
|
|
13474
|
-
const resolved2 = (symbol.flags &
|
|
13617
|
+
const resolved2 = (symbol.flags & ts6.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
|
|
13475
13618
|
const declarations = resolved2.getDeclarations();
|
|
13476
13619
|
if (declarations === void 0 || declarations.length === 0) continue;
|
|
13477
13620
|
const declaration = declarations[0];
|
|
@@ -13486,7 +13629,7 @@ function printCallSignature(sig, ctx) {
|
|
|
13486
13629
|
const params = sig.getParameters().map((p) => {
|
|
13487
13630
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
13488
13631
|
const pType = decl !== void 0 ? ctx.checker.getTypeOfSymbolAtLocation(p, decl) : ctx.checker.getDeclaredTypeOfSymbol(p);
|
|
13489
|
-
const optional = decl !== void 0 &&
|
|
13632
|
+
const optional = decl !== void 0 && ts6.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
|
|
13490
13633
|
return `${p.getName()}${optional ? "?" : ""}: ${ctx.renderType(pType, 0)}`;
|
|
13491
13634
|
}).join(", ");
|
|
13492
13635
|
const ret = ctx.renderType(sig.getReturnType(), 0);
|
|
@@ -13700,6 +13843,7 @@ ${axisSection}
|
|
|
13700
13843
|
CONTRACT_SURFACE,
|
|
13701
13844
|
DEFENSIVE_CODING,
|
|
13702
13845
|
GESTURE_ROUTING,
|
|
13846
|
+
TERMINAL_ACTIONS,
|
|
13703
13847
|
INTERACTIVE_TRAITS,
|
|
13704
13848
|
ANTI_PATTERNS,
|
|
13705
13849
|
CROSS_REFERENCE_RULES,
|
|
@@ -15091,7 +15235,7 @@ var HOST_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a", "div", "span", "li
|
|
|
15091
15235
|
var HOST_CHANGE_TAGS = /* @__PURE__ */ new Set(["select", "input", "textarea"]);
|
|
15092
15236
|
function findWiring(input) {
|
|
15093
15237
|
const { sourceCode, hookName, hookArg } = input;
|
|
15094
|
-
const sf =
|
|
15238
|
+
const sf = ts6.createSourceFile("Component.tsx", sourceCode, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TSX);
|
|
15095
15239
|
const hookBindings = findHookBindings(sf, hookName, hookArg);
|
|
15096
15240
|
if (hookBindings.length === 0) {
|
|
15097
15241
|
return {
|
|
@@ -15114,7 +15258,7 @@ function findWiring(input) {
|
|
|
15114
15258
|
const customPropElements = [];
|
|
15115
15259
|
const submitButton = { found: false };
|
|
15116
15260
|
function visit(node) {
|
|
15117
|
-
if (
|
|
15261
|
+
if (ts6.isJsxAttribute(node) && node.initializer) {
|
|
15118
15262
|
const attrName = node.name.getText(sf);
|
|
15119
15263
|
const initRefs = findReferencedNames(node.initializer, callableNames);
|
|
15120
15264
|
if (initRefs.size > 0) {
|
|
@@ -15152,19 +15296,19 @@ function findWiring(input) {
|
|
|
15152
15296
|
}
|
|
15153
15297
|
}
|
|
15154
15298
|
}
|
|
15155
|
-
if (
|
|
15299
|
+
if (ts6.isJsxOpeningLikeElement(node)) {
|
|
15156
15300
|
const tag = getTagName(node);
|
|
15157
15301
|
if ((tag ?? "").toLowerCase() === "button") {
|
|
15158
15302
|
const typeAttr = node.attributes.properties.find(
|
|
15159
|
-
(p) =>
|
|
15303
|
+
(p) => ts6.isJsxAttribute(p) && p.name.getText(sf) === "type"
|
|
15160
15304
|
);
|
|
15161
|
-
if (typeAttr &&
|
|
15305
|
+
if (typeAttr && ts6.isJsxAttribute(typeAttr) && typeAttr.initializer) {
|
|
15162
15306
|
const v = typeAttr.initializer.getText(sf).toLowerCase();
|
|
15163
15307
|
if (v.includes("submit")) submitButton.found = true;
|
|
15164
15308
|
}
|
|
15165
15309
|
}
|
|
15166
15310
|
}
|
|
15167
|
-
|
|
15311
|
+
ts6.forEachChild(node, visit);
|
|
15168
15312
|
}
|
|
15169
15313
|
visit(sf);
|
|
15170
15314
|
if (sawClickOnHost) {
|
|
@@ -15201,32 +15345,32 @@ function findWiring(input) {
|
|
|
15201
15345
|
function findHookBindings(sf, hookName, hookArg) {
|
|
15202
15346
|
const bindings = [];
|
|
15203
15347
|
function visit(node) {
|
|
15204
|
-
if (
|
|
15348
|
+
if (ts6.isVariableDeclaration(node) && node.initializer && ts6.isCallExpression(node.initializer)) {
|
|
15205
15349
|
const call = node.initializer;
|
|
15206
15350
|
const callee = call.expression.getText(sf);
|
|
15207
15351
|
if (callee !== hookName) {
|
|
15208
|
-
|
|
15352
|
+
ts6.forEachChild(node, visit);
|
|
15209
15353
|
return;
|
|
15210
15354
|
}
|
|
15211
15355
|
const firstArg = call.arguments[0];
|
|
15212
|
-
if (!firstArg || !
|
|
15213
|
-
|
|
15356
|
+
if (!firstArg || !ts6.isStringLiteral(firstArg)) {
|
|
15357
|
+
ts6.forEachChild(node, visit);
|
|
15214
15358
|
return;
|
|
15215
15359
|
}
|
|
15216
15360
|
if (firstArg.text !== hookArg) {
|
|
15217
|
-
|
|
15361
|
+
ts6.forEachChild(node, visit);
|
|
15218
15362
|
return;
|
|
15219
15363
|
}
|
|
15220
|
-
if (
|
|
15364
|
+
if (ts6.isIdentifier(node.name)) {
|
|
15221
15365
|
bindings.push(node.name.text);
|
|
15222
|
-
} else if (
|
|
15366
|
+
} else if (ts6.isObjectBindingPattern(node.name)) {
|
|
15223
15367
|
for (const elem of node.name.elements) {
|
|
15224
15368
|
const target = elem.name;
|
|
15225
|
-
if (
|
|
15369
|
+
if (ts6.isIdentifier(target)) bindings.push(target.text);
|
|
15226
15370
|
}
|
|
15227
15371
|
}
|
|
15228
15372
|
}
|
|
15229
|
-
|
|
15373
|
+
ts6.forEachChild(node, visit);
|
|
15230
15374
|
}
|
|
15231
15375
|
visit(sf);
|
|
15232
15376
|
return bindings;
|
|
@@ -15234,24 +15378,24 @@ function findHookBindings(sf, hookName, hookArg) {
|
|
|
15234
15378
|
function expandAliases(sf, bindings) {
|
|
15235
15379
|
const all = new Set(bindings);
|
|
15236
15380
|
function visit(node) {
|
|
15237
|
-
if (
|
|
15381
|
+
if (ts6.isVariableDeclaration(node) && node.initializer && ts6.isIdentifier(node.name)) {
|
|
15238
15382
|
const newName = node.name.text;
|
|
15239
15383
|
const init = node.initializer;
|
|
15240
|
-
if (
|
|
15384
|
+
if (ts6.isIdentifier(init) && all.has(init.text)) {
|
|
15241
15385
|
all.add(newName);
|
|
15242
15386
|
}
|
|
15243
|
-
if (
|
|
15387
|
+
if (ts6.isArrowFunction(init) || ts6.isFunctionExpression(init)) {
|
|
15244
15388
|
if (bodyReferences(init.body, all)) {
|
|
15245
15389
|
all.add(newName);
|
|
15246
15390
|
}
|
|
15247
15391
|
}
|
|
15248
|
-
if (
|
|
15392
|
+
if (ts6.isCallExpression(init)) {
|
|
15249
15393
|
if (bodyReferences(init, all)) {
|
|
15250
15394
|
all.add(newName);
|
|
15251
15395
|
}
|
|
15252
15396
|
}
|
|
15253
15397
|
}
|
|
15254
|
-
|
|
15398
|
+
ts6.forEachChild(node, visit);
|
|
15255
15399
|
}
|
|
15256
15400
|
visit(sf);
|
|
15257
15401
|
return all;
|
|
@@ -15260,11 +15404,11 @@ function bodyReferences(body, names) {
|
|
|
15260
15404
|
let found = false;
|
|
15261
15405
|
function walk(n) {
|
|
15262
15406
|
if (found) return;
|
|
15263
|
-
if (
|
|
15407
|
+
if (ts6.isIdentifier(n) && names.has(n.text)) {
|
|
15264
15408
|
found = true;
|
|
15265
15409
|
return;
|
|
15266
15410
|
}
|
|
15267
|
-
|
|
15411
|
+
ts6.forEachChild(n, walk);
|
|
15268
15412
|
}
|
|
15269
15413
|
walk(body);
|
|
15270
15414
|
return found;
|
|
@@ -15272,16 +15416,16 @@ function bodyReferences(body, names) {
|
|
|
15272
15416
|
function findReferencedNames(node, names) {
|
|
15273
15417
|
const found = /* @__PURE__ */ new Set();
|
|
15274
15418
|
function walk(n) {
|
|
15275
|
-
if (
|
|
15419
|
+
if (ts6.isIdentifier(n) && names.has(n.text)) {
|
|
15276
15420
|
found.add(n.text);
|
|
15277
15421
|
}
|
|
15278
|
-
|
|
15422
|
+
ts6.forEachChild(n, walk);
|
|
15279
15423
|
}
|
|
15280
15424
|
walk(node);
|
|
15281
15425
|
return found;
|
|
15282
15426
|
}
|
|
15283
15427
|
function getTagName(el) {
|
|
15284
|
-
if (
|
|
15428
|
+
if (ts6.isJsxOpeningLikeElement(el)) {
|
|
15285
15429
|
return el.tagName.getText();
|
|
15286
15430
|
}
|
|
15287
15431
|
return null;
|