@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
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getCssTokens, fillFitRule, FORBIDDEN_IMPORT_SPECIFIERS } from '@ggui-ai
|
|
|
6
6
|
import { classifyUi, MODEL_REGISTRY, listContractGadgets, HOOK_NAME_RE, modelRefOfRoute, isGeneratorId, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, APP_GENERATION_PROFILE_BOUNDS, STDLIB_GADGETS_PACKAGE, resolveAppGadgets } from '@ggui-ai/protocol';
|
|
7
7
|
import fs, { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync, rm, createReadStream, createWriteStream, readdirSync, statSync } from 'fs';
|
|
8
8
|
import { contentHash } from '@ggui-ai/protocol/content-hash';
|
|
9
|
-
import
|
|
9
|
+
import ts4 from 'typescript';
|
|
10
10
|
import path, { dirname, resolve, join, basename } from 'path';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
import { Linter } from 'eslint';
|
|
@@ -1935,53 +1935,53 @@ var init_validation = __esm({
|
|
|
1935
1935
|
}
|
|
1936
1936
|
});
|
|
1937
1937
|
function extractWireCallSites(code) {
|
|
1938
|
-
const sf =
|
|
1938
|
+
const sf = ts4.createSourceFile(
|
|
1939
1939
|
"component.tsx",
|
|
1940
1940
|
code,
|
|
1941
|
-
|
|
1941
|
+
ts4.ScriptTarget.Latest,
|
|
1942
1942
|
/*setParentNodes*/
|
|
1943
1943
|
true,
|
|
1944
|
-
|
|
1944
|
+
ts4.ScriptKind.TSX
|
|
1945
1945
|
);
|
|
1946
1946
|
const sites = [];
|
|
1947
1947
|
function visit(node) {
|
|
1948
|
-
if (
|
|
1948
|
+
if (ts4.isCallExpression(node)) {
|
|
1949
1949
|
const callee = node.expression;
|
|
1950
|
-
if (
|
|
1950
|
+
if (ts4.isIdentifier(callee)) {
|
|
1951
1951
|
const kind = HOOK_TO_KIND[callee.text];
|
|
1952
1952
|
if (kind) {
|
|
1953
1953
|
const firstArg = node.arguments[0];
|
|
1954
|
-
if (firstArg &&
|
|
1954
|
+
if (firstArg && ts4.isStringLiteral(firstArg)) {
|
|
1955
1955
|
sites.push({ kind, name: firstArg.text });
|
|
1956
1956
|
}
|
|
1957
1957
|
}
|
|
1958
1958
|
}
|
|
1959
1959
|
}
|
|
1960
|
-
|
|
1960
|
+
ts4.forEachChild(node, visit);
|
|
1961
1961
|
}
|
|
1962
1962
|
visit(sf);
|
|
1963
1963
|
return sites;
|
|
1964
1964
|
}
|
|
1965
1965
|
function extractWireImports(code) {
|
|
1966
|
-
const sf =
|
|
1966
|
+
const sf = ts4.createSourceFile(
|
|
1967
1967
|
"component.tsx",
|
|
1968
1968
|
code,
|
|
1969
|
-
|
|
1969
|
+
ts4.ScriptTarget.Latest,
|
|
1970
1970
|
/*setParentNodes*/
|
|
1971
1971
|
true,
|
|
1972
|
-
|
|
1972
|
+
ts4.ScriptKind.TSX
|
|
1973
1973
|
);
|
|
1974
1974
|
const imported = /* @__PURE__ */ new Set();
|
|
1975
1975
|
for (const stmt of sf.statements) {
|
|
1976
|
-
if (!
|
|
1976
|
+
if (!ts4.isImportDeclaration(stmt)) continue;
|
|
1977
1977
|
const moduleSpecifier = stmt.moduleSpecifier;
|
|
1978
|
-
if (!
|
|
1978
|
+
if (!ts4.isStringLiteral(moduleSpecifier)) continue;
|
|
1979
1979
|
if (moduleSpecifier.text !== "@ggui-ai/wire") continue;
|
|
1980
1980
|
const clause = stmt.importClause;
|
|
1981
1981
|
if (!clause) continue;
|
|
1982
1982
|
const bindings = clause.namedBindings;
|
|
1983
1983
|
if (!bindings) continue;
|
|
1984
|
-
if (!
|
|
1984
|
+
if (!ts4.isNamedImports(bindings)) continue;
|
|
1985
1985
|
for (const el of bindings.elements) {
|
|
1986
1986
|
imported.add(el.name.text);
|
|
1987
1987
|
}
|
|
@@ -2133,11 +2133,11 @@ var init_json_schema_ts = __esm({
|
|
|
2133
2133
|
}
|
|
2134
2134
|
});
|
|
2135
2135
|
function extractPropsInterface(code) {
|
|
2136
|
-
const sf =
|
|
2136
|
+
const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
2137
2137
|
const props = [];
|
|
2138
2138
|
function visitMembers(members) {
|
|
2139
2139
|
for (const member of members) {
|
|
2140
|
-
if (
|
|
2140
|
+
if (ts4.isPropertySignature(member) && member.name) {
|
|
2141
2141
|
const name = member.name.getText(sf);
|
|
2142
2142
|
const type = member.type ? member.type.getText(sf) : "unknown";
|
|
2143
2143
|
const optional = !!member.questionToken;
|
|
@@ -2146,17 +2146,17 @@ function extractPropsInterface(code) {
|
|
|
2146
2146
|
}
|
|
2147
2147
|
}
|
|
2148
2148
|
function visit(node) {
|
|
2149
|
-
if (
|
|
2149
|
+
if (ts4.isInterfaceDeclaration(node) && node.name.text === "Props") {
|
|
2150
2150
|
visitMembers(node.members);
|
|
2151
2151
|
return;
|
|
2152
2152
|
}
|
|
2153
|
-
if (
|
|
2154
|
-
if (
|
|
2153
|
+
if (ts4.isTypeAliasDeclaration(node) && node.name.text === "Props") {
|
|
2154
|
+
if (ts4.isTypeLiteralNode(node.type)) {
|
|
2155
2155
|
visitMembers(node.type.members);
|
|
2156
2156
|
}
|
|
2157
2157
|
return;
|
|
2158
2158
|
}
|
|
2159
|
-
|
|
2159
|
+
ts4.forEachChild(node, visit);
|
|
2160
2160
|
}
|
|
2161
2161
|
visit(sf);
|
|
2162
2162
|
return props.length > 0 ? props : null;
|
|
@@ -2369,12 +2369,12 @@ function generateFix(code, message, sourceLine) {
|
|
|
2369
2369
|
}
|
|
2370
2370
|
}
|
|
2371
2371
|
function parseAndStore(vfs, virtualPath, content) {
|
|
2372
|
-
const sourceFile =
|
|
2372
|
+
const sourceFile = ts4.createSourceFile(
|
|
2373
2373
|
virtualPath,
|
|
2374
2374
|
content,
|
|
2375
|
-
|
|
2375
|
+
ts4.ScriptTarget.ES2020,
|
|
2376
2376
|
true,
|
|
2377
|
-
virtualPath.endsWith(".tsx") ?
|
|
2377
|
+
virtualPath.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS
|
|
2378
2378
|
);
|
|
2379
2379
|
vfs.set(virtualPath, { content, sourceFile });
|
|
2380
2380
|
}
|
|
@@ -2516,12 +2516,12 @@ function resolveModuleName(vfs, name, containingFile) {
|
|
|
2516
2516
|
return { resolvedModule: void 0 };
|
|
2517
2517
|
}
|
|
2518
2518
|
function createVfsHost(vfs, componentCode) {
|
|
2519
|
-
const componentFile =
|
|
2519
|
+
const componentFile = ts4.createSourceFile(
|
|
2520
2520
|
"Component.tsx",
|
|
2521
2521
|
componentCode,
|
|
2522
|
-
|
|
2522
|
+
ts4.ScriptTarget.ES2020,
|
|
2523
2523
|
true,
|
|
2524
|
-
|
|
2524
|
+
ts4.ScriptKind.TSX
|
|
2525
2525
|
);
|
|
2526
2526
|
const host = {
|
|
2527
2527
|
getSourceFile(fileName) {
|
|
@@ -2576,7 +2576,7 @@ function resolved(resolvedFileName) {
|
|
|
2576
2576
|
resolvedModule: {
|
|
2577
2577
|
resolvedFileName,
|
|
2578
2578
|
isExternalLibraryImport: true,
|
|
2579
|
-
extension:
|
|
2579
|
+
extension: ts4.Extension.Dts
|
|
2580
2580
|
}
|
|
2581
2581
|
};
|
|
2582
2582
|
}
|
|
@@ -2598,18 +2598,18 @@ async function typecheck(code, dtsMap) {
|
|
|
2598
2598
|
const prefixedCode = SYNTHETIC_PREFIX + code;
|
|
2599
2599
|
const lineOffset = SYNTHETIC_PREFIX.split("\n").length - 1;
|
|
2600
2600
|
const host = createVfsHost(effectiveVfs, prefixedCode);
|
|
2601
|
-
const program =
|
|
2602
|
-
const diagnostics =
|
|
2601
|
+
const program = ts4.createProgram(["Component.tsx"], COMPILER_OPTIONS, host);
|
|
2602
|
+
const diagnostics = ts4.getPreEmitDiagnostics(program);
|
|
2603
2603
|
const errors = [];
|
|
2604
2604
|
const warnings = [];
|
|
2605
2605
|
for (const diag of diagnostics) {
|
|
2606
2606
|
if (diag.file && diag.file.fileName !== "Component.tsx") continue;
|
|
2607
2607
|
if (!diag.file) continue;
|
|
2608
|
-
const diagLine =
|
|
2608
|
+
const diagLine = ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;
|
|
2609
2609
|
if (diagLine < lineOffset) continue;
|
|
2610
|
-
const rawLine = diag.file ?
|
|
2610
|
+
const rawLine = diag.file ? ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1 : 0;
|
|
2611
2611
|
const line = Math.max(1, rawLine - lineOffset);
|
|
2612
|
-
const message =
|
|
2612
|
+
const message = ts4.flattenDiagnosticMessageText(diag.messageText, "\n");
|
|
2613
2613
|
const code_ = diag.code;
|
|
2614
2614
|
const sourceLines = diag.file.text.split("\n");
|
|
2615
2615
|
const sourceLine = rawLine >= 1 && rawLine <= sourceLines.length ? sourceLines[rawLine - 1]?.trim() ?? "" : "";
|
|
@@ -2656,8 +2656,8 @@ var init_type_checker = __esm({
|
|
|
2656
2656
|
]);
|
|
2657
2657
|
vfsCache = null;
|
|
2658
2658
|
COMPILER_OPTIONS = {
|
|
2659
|
-
target:
|
|
2660
|
-
module:
|
|
2659
|
+
target: ts4.ScriptTarget.ES2020,
|
|
2660
|
+
module: ts4.ModuleKind.ESNext,
|
|
2661
2661
|
// Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)
|
|
2662
2662
|
// supplies `import React from 'react'` plus a `declare global` JSX
|
|
2663
2663
|
// namespace shim: `@types/react` v19 removed the *global* `JSX`
|
|
@@ -2665,8 +2665,8 @@ var init_type_checker = __esm({
|
|
|
2665
2665
|
// for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without
|
|
2666
2666
|
// the shim, `<div>` degrades to `any` and every typed component
|
|
2667
2667
|
// falsely rejects the intrinsic `key` prop.
|
|
2668
|
-
jsx:
|
|
2669
|
-
moduleResolution:
|
|
2668
|
+
jsx: ts4.JsxEmit.React,
|
|
2669
|
+
moduleResolution: ts4.ModuleResolutionKind.Bundler,
|
|
2670
2670
|
strict: false,
|
|
2671
2671
|
strictNullChecks: true,
|
|
2672
2672
|
// Catch undefined.foo() errors that cause runtime crashes
|
|
@@ -3166,17 +3166,17 @@ Only list ACTUAL composition problems. Use 'fail' only for a fundamentally incoh
|
|
|
3166
3166
|
}
|
|
3167
3167
|
});
|
|
3168
3168
|
function stripComments(code) {
|
|
3169
|
-
const scanner =
|
|
3170
|
-
|
|
3169
|
+
const scanner = ts4.createScanner(
|
|
3170
|
+
ts4.ScriptTarget.Latest,
|
|
3171
3171
|
/* skipTrivia */
|
|
3172
3172
|
false,
|
|
3173
|
-
|
|
3173
|
+
ts4.LanguageVariant.JSX,
|
|
3174
3174
|
code
|
|
3175
3175
|
);
|
|
3176
3176
|
let out = "";
|
|
3177
|
-
for (let token = scanner.scan(); token !==
|
|
3177
|
+
for (let token = scanner.scan(); token !== ts4.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
3178
3178
|
const text = scanner.getTokenText();
|
|
3179
|
-
out += token ===
|
|
3179
|
+
out += token === ts4.SyntaxKind.SingleLineCommentTrivia || token === ts4.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
|
|
3180
3180
|
}
|
|
3181
3181
|
return out;
|
|
3182
3182
|
}
|
|
@@ -3199,20 +3199,20 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3199
3199
|
"Link"
|
|
3200
3200
|
]);
|
|
3201
3201
|
const HANDLER_ATTRS = ["onClick", "onChange", "onPress", "onSelect"];
|
|
3202
|
-
const sf =
|
|
3202
|
+
const sf = ts4.createSourceFile(
|
|
3203
3203
|
"source.tsx",
|
|
3204
3204
|
sourceCode,
|
|
3205
|
-
|
|
3205
|
+
ts4.ScriptTarget.Latest,
|
|
3206
3206
|
/* setParentNodes */
|
|
3207
3207
|
true,
|
|
3208
|
-
|
|
3208
|
+
ts4.ScriptKind.TSX
|
|
3209
3209
|
);
|
|
3210
3210
|
function tagNameText(node) {
|
|
3211
|
-
return
|
|
3211
|
+
return ts4.isIdentifier(node.tagName) ? node.tagName.text : void 0;
|
|
3212
3212
|
}
|
|
3213
3213
|
function attrExpression(node, attrName) {
|
|
3214
3214
|
for (const attr of node.attributes.properties) {
|
|
3215
|
-
if (
|
|
3215
|
+
if (ts4.isJsxAttribute(attr) && ts4.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts4.isJsxExpression(attr.initializer)) {
|
|
3216
3216
|
return attr.initializer.expression;
|
|
3217
3217
|
}
|
|
3218
3218
|
}
|
|
@@ -3224,19 +3224,19 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3224
3224
|
if (anyElementIsInteractive) return true;
|
|
3225
3225
|
if (!TRAIT_HOST_TAGS.has(tag)) return false;
|
|
3226
3226
|
const asExpr = attrExpression(node, "as");
|
|
3227
|
-
return asExpr !== void 0 &&
|
|
3227
|
+
return asExpr !== void 0 && ts4.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
|
|
3228
3228
|
}
|
|
3229
3229
|
function extractCalleeName(expr) {
|
|
3230
3230
|
if (expr === void 0) return void 0;
|
|
3231
|
-
if (
|
|
3232
|
-
if (
|
|
3231
|
+
if (ts4.isIdentifier(expr)) return expr.text;
|
|
3232
|
+
if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) {
|
|
3233
3233
|
const body = expr.body;
|
|
3234
|
-
if (
|
|
3234
|
+
if (ts4.isCallExpression(body) && ts4.isIdentifier(body.expression)) {
|
|
3235
3235
|
return body.expression.text;
|
|
3236
3236
|
}
|
|
3237
|
-
if (
|
|
3237
|
+
if (ts4.isBlock(body)) {
|
|
3238
3238
|
for (const stmt of body.statements) {
|
|
3239
|
-
if (
|
|
3239
|
+
if (ts4.isExpressionStatement(stmt) && ts4.isCallExpression(stmt.expression) && ts4.isIdentifier(stmt.expression.expression)) {
|
|
3240
3240
|
return stmt.expression.expression.text;
|
|
3241
3241
|
}
|
|
3242
3242
|
}
|
|
@@ -3248,7 +3248,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3248
3248
|
let found;
|
|
3249
3249
|
function walk(node) {
|
|
3250
3250
|
if (found !== void 0) return;
|
|
3251
|
-
const opening =
|
|
3251
|
+
const opening = ts4.isJsxElement(node) ? node.openingElement : ts4.isJsxSelfClosingElement(node) ? node : void 0;
|
|
3252
3252
|
if (opening !== void 0) {
|
|
3253
3253
|
const tag = tagNameText(opening);
|
|
3254
3254
|
if (tag !== void 0 && (anyElementIsInteractive || INTERACTIVE_DESCENDANTS.has(tag))) {
|
|
@@ -3264,13 +3264,13 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3264
3264
|
}
|
|
3265
3265
|
}
|
|
3266
3266
|
}
|
|
3267
|
-
|
|
3267
|
+
ts4.forEachChild(node, walk);
|
|
3268
3268
|
}
|
|
3269
|
-
|
|
3269
|
+
ts4.forEachChild(outer, walk);
|
|
3270
3270
|
return found;
|
|
3271
3271
|
}
|
|
3272
3272
|
function visit(node) {
|
|
3273
|
-
if (
|
|
3273
|
+
if (ts4.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
|
|
3274
3274
|
const outerCallee = extractCalleeName(attrExpression(node.openingElement, "onClick")) ?? extractCalleeName(attrExpression(node.openingElement, "onPress"));
|
|
3275
3275
|
if (outerCallee !== void 0 && bindings.has(outerCallee)) {
|
|
3276
3276
|
const match = findMatchingInteractiveDescendant(node, outerCallee);
|
|
@@ -3291,7 +3291,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
|
|
|
3291
3291
|
}
|
|
3292
3292
|
}
|
|
3293
3293
|
}
|
|
3294
|
-
|
|
3294
|
+
ts4.forEachChild(node, visit);
|
|
3295
3295
|
}
|
|
3296
3296
|
visit(sf);
|
|
3297
3297
|
return issues;
|
|
@@ -3310,6 +3310,63 @@ function isGadgetExportImported(source, pkg, exportName) {
|
|
|
3310
3310
|
}
|
|
3311
3311
|
return false;
|
|
3312
3312
|
}
|
|
3313
|
+
function detectComponentNeverReturns(sourceCode) {
|
|
3314
|
+
const sf = ts4.createSourceFile(
|
|
3315
|
+
"source.tsx",
|
|
3316
|
+
sourceCode,
|
|
3317
|
+
ts4.ScriptTarget.Latest,
|
|
3318
|
+
/* setParentNodes */
|
|
3319
|
+
true,
|
|
3320
|
+
ts4.ScriptKind.TSX
|
|
3321
|
+
);
|
|
3322
|
+
const isFunctionLike = (n) => ts4.isFunctionDeclaration(n) || ts4.isFunctionExpression(n) || ts4.isArrowFunction(n) || ts4.isMethodDeclaration(n) || ts4.isClassDeclaration(n) || ts4.isClassExpression(n);
|
|
3323
|
+
function resolveTopLevel(name) {
|
|
3324
|
+
for (const st of sf.statements) {
|
|
3325
|
+
if (ts4.isFunctionDeclaration(st) && st.name?.text === name) return st;
|
|
3326
|
+
if (ts4.isVariableStatement(st)) {
|
|
3327
|
+
for (const decl of st.declarationList.declarations) {
|
|
3328
|
+
if (ts4.isIdentifier(decl.name) && decl.name.text === name && decl.initializer !== void 0 && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer))) {
|
|
3329
|
+
return decl.initializer;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
return void 0;
|
|
3335
|
+
}
|
|
3336
|
+
let component;
|
|
3337
|
+
for (const st of sf.statements) {
|
|
3338
|
+
if (ts4.isFunctionDeclaration(st) && st.modifiers?.some((m) => m.kind === ts4.SyntaxKind.DefaultKeyword)) {
|
|
3339
|
+
component = st;
|
|
3340
|
+
break;
|
|
3341
|
+
}
|
|
3342
|
+
if (ts4.isExportAssignment(st) && st.isExportEquals !== true) {
|
|
3343
|
+
const expr = st.expression;
|
|
3344
|
+
if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) component = expr;
|
|
3345
|
+
else if (ts4.isIdentifier(expr)) component = resolveTopLevel(expr.text);
|
|
3346
|
+
break;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
if (component === void 0) return null;
|
|
3350
|
+
const body = component.body;
|
|
3351
|
+
if (body === void 0 || !ts4.isBlock(body)) return null;
|
|
3352
|
+
let returnsValue = false;
|
|
3353
|
+
let jsxStatementLine;
|
|
3354
|
+
const visit = (node) => {
|
|
3355
|
+
if (returnsValue) return;
|
|
3356
|
+
if (ts4.isReturnStatement(node)) {
|
|
3357
|
+
if (node.expression !== void 0) returnsValue = true;
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
if (jsxStatementLine === void 0 && ts4.isExpressionStatement(node) && (ts4.isJsxElement(node.expression) || ts4.isJsxFragment(node.expression) || ts4.isJsxSelfClosingElement(node.expression))) {
|
|
3361
|
+
jsxStatementLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
3362
|
+
}
|
|
3363
|
+
node.forEachChild((child) => {
|
|
3364
|
+
if (!isFunctionLike(child)) visit(child);
|
|
3365
|
+
});
|
|
3366
|
+
};
|
|
3367
|
+
for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
|
|
3368
|
+
return returnsValue ? null : { jsxStatementLine };
|
|
3369
|
+
}
|
|
3313
3370
|
async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
|
|
3314
3371
|
const issues = [];
|
|
3315
3372
|
const lines = sourceCode.split("\n");
|
|
@@ -3729,6 +3786,19 @@ ${errorDetail}`,
|
|
|
3729
3786
|
fix: "Add export default function Component(props: Props) { ... }"
|
|
3730
3787
|
});
|
|
3731
3788
|
}
|
|
3789
|
+
const neverReturns = detectComponentNeverReturns(sourceCode);
|
|
3790
|
+
if (neverReturns !== null) {
|
|
3791
|
+
issues.push({
|
|
3792
|
+
tier: 0,
|
|
3793
|
+
result: "fail",
|
|
3794
|
+
category: "compile",
|
|
3795
|
+
subcategory: "renders-nothing",
|
|
3796
|
+
severity: "critical",
|
|
3797
|
+
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`,
|
|
3798
|
+
fix: "Wrap the tree in the return the boilerplate gave you: `return (` before the root element and `);` after it.",
|
|
3799
|
+
...neverReturns.jsxStatementLine === void 0 ? {} : { line: neverReturns.jsxStatementLine }
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3732
3802
|
if (contract) {
|
|
3733
3803
|
try {
|
|
3734
3804
|
const contractIssues = validateAllContracts(sourceCode, contract);
|
|
@@ -11987,18 +12057,18 @@ ${context}
|
|
|
11987
12057
|
}
|
|
11988
12058
|
var LABELED_COMPONENTS = /* @__PURE__ */ new Set(["Input", "TextArea", "Select"]);
|
|
11989
12059
|
function checkReactRules(code) {
|
|
11990
|
-
const sf =
|
|
12060
|
+
const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
11991
12061
|
const issues = [];
|
|
11992
12062
|
function visit(node) {
|
|
11993
|
-
if (
|
|
12063
|
+
if (ts4.isJsxSelfClosingElement(node) || ts4.isJsxOpeningElement(node)) {
|
|
11994
12064
|
const tagName = node.tagName.getText(sf);
|
|
11995
12065
|
if (LABELED_COMPONENTS.has(tagName)) {
|
|
11996
12066
|
const propNames = /* @__PURE__ */ new Set();
|
|
11997
12067
|
let hasSpread = false;
|
|
11998
12068
|
for (const attr of node.attributes.properties) {
|
|
11999
|
-
if (
|
|
12069
|
+
if (ts4.isJsxAttribute(attr)) {
|
|
12000
12070
|
propNames.add(attr.name.getText(sf));
|
|
12001
|
-
} else if (
|
|
12071
|
+
} else if (ts4.isJsxSpreadAttribute(attr)) {
|
|
12002
12072
|
hasSpread = true;
|
|
12003
12073
|
}
|
|
12004
12074
|
}
|
|
@@ -12013,7 +12083,7 @@ function checkReactRules(code) {
|
|
|
12013
12083
|
}
|
|
12014
12084
|
}
|
|
12015
12085
|
}
|
|
12016
|
-
|
|
12086
|
+
ts4.forEachChild(node, visit);
|
|
12017
12087
|
}
|
|
12018
12088
|
visit(sf);
|
|
12019
12089
|
return issues;
|
|
@@ -13182,6 +13252,50 @@ function runViewportSized(input) {
|
|
|
13182
13252
|
)
|
|
13183
13253
|
];
|
|
13184
13254
|
}
|
|
13255
|
+
var TERMINAL_WORDS = /* @__PURE__ */ new Set([
|
|
13256
|
+
"submit",
|
|
13257
|
+
"confirm",
|
|
13258
|
+
"approve",
|
|
13259
|
+
"schedule",
|
|
13260
|
+
"reserve",
|
|
13261
|
+
"book",
|
|
13262
|
+
"pay",
|
|
13263
|
+
"checkout",
|
|
13264
|
+
"purchase",
|
|
13265
|
+
"order"
|
|
13266
|
+
]);
|
|
13267
|
+
var TERMINAL_LABEL_RX = /\b(submit|confirm|approve|schedule|reserve|book|pay|checkout|purchase|place\s+order)\b/i;
|
|
13268
|
+
var GUARD_RX = /\b(disabled|aria-disabled)\s*=/;
|
|
13269
|
+
function nameWords(name) {
|
|
13270
|
+
return name.split(/(?=[A-Z])|[_\-\s]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
|
|
13271
|
+
}
|
|
13272
|
+
function findUnguardedTerminalControls(sourceCode) {
|
|
13273
|
+
if (GUARD_RX.test(sourceCode)) return [];
|
|
13274
|
+
const names = /* @__PURE__ */ new Set();
|
|
13275
|
+
for (const m of sourceCode.matchAll(/useAction(?:<[^>]*>)?\(\s*['"`]([^'"`]+)['"`]/g)) {
|
|
13276
|
+
const name = m[1] ?? "";
|
|
13277
|
+
if (nameWords(name).some((w) => TERMINAL_WORDS.has(w))) names.add(name);
|
|
13278
|
+
}
|
|
13279
|
+
for (const m of sourceCode.matchAll(/>([^<>{}]{1,60})<\/Button>/g)) {
|
|
13280
|
+
const label = (m[1] ?? "").replace(/\s+/g, " ").trim();
|
|
13281
|
+
if (label.length > 0 && TERMINAL_LABEL_RX.test(label)) names.add(label.slice(0, 40));
|
|
13282
|
+
}
|
|
13283
|
+
return [...names];
|
|
13284
|
+
}
|
|
13285
|
+
function runTerminalActionUnguarded(input) {
|
|
13286
|
+
if (input.compiledCode === null) return [];
|
|
13287
|
+
const found = findUnguardedTerminalControls(input.sourceCode);
|
|
13288
|
+
if (found.length === 0) return [];
|
|
13289
|
+
const list = found.map((t) => `"${t}"`).join(", ");
|
|
13290
|
+
return [
|
|
13291
|
+
mkIssue(
|
|
13292
|
+
"universal.terminal_action_unguarded",
|
|
13293
|
+
`${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.`,
|
|
13294
|
+
'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.',
|
|
13295
|
+
"warn"
|
|
13296
|
+
)
|
|
13297
|
+
];
|
|
13298
|
+
}
|
|
13185
13299
|
var UNIVERSAL_CHECKS = [
|
|
13186
13300
|
{
|
|
13187
13301
|
id: "universal.icon_name_known",
|
|
@@ -13201,6 +13315,12 @@ var UNIVERSAL_CHECKS = [
|
|
|
13201
13315
|
values: ALL_RENDER_VALUES,
|
|
13202
13316
|
run: runViewportSized
|
|
13203
13317
|
},
|
|
13318
|
+
{
|
|
13319
|
+
id: "universal.terminal_action_unguarded",
|
|
13320
|
+
axis: "render",
|
|
13321
|
+
values: ALL_RENDER_VALUES,
|
|
13322
|
+
run: runTerminalActionUnguarded
|
|
13323
|
+
},
|
|
13204
13324
|
{
|
|
13205
13325
|
id: "universal.accent_text_ink",
|
|
13206
13326
|
axis: "render",
|
|
@@ -15066,7 +15186,8 @@ type ${typeName} = ${tsType};`
|
|
|
15066
15186
|
);
|
|
15067
15187
|
const callSig = tsType === "void" ? "() => void \u2014 fire and forget" : `(data: ${tsType}) => void`;
|
|
15068
15188
|
const toolHint = tool ? ` \u2192 nextStep: ${tool}` : "";
|
|
15069
|
-
|
|
15189
|
+
const onceHint = " \u2014 if this is meant ONCE, disable its control after it fires";
|
|
15190
|
+
actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
|
|
15070
15191
|
}
|
|
15071
15192
|
const streamChannels = contract?.streamSpec ?? {};
|
|
15072
15193
|
const streamChannelEntries = Object.entries(streamChannels);
|
|
@@ -15412,12 +15533,33 @@ Rules:
|
|
|
15412
15533
|
3. Default values come from the current request \u2014 so the component renders correctly standalone.
|
|
15413
15534
|
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.
|
|
15414
15535
|
5. Props interface must be typed and exported.`;
|
|
15415
|
-
var
|
|
15536
|
+
var TERMINAL_ACTIONS = `## Actions the user means once
|
|
15537
|
+
|
|
15538
|
+
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.
|
|
15539
|
+
|
|
15540
|
+
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".
|
|
15541
|
+
|
|
15542
|
+
**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.
|
|
15543
|
+
|
|
15544
|
+
When you do guard one:
|
|
15545
|
+
- Hold a small piece of state for it and set it in the handler: \`const [submitted, setSubmitted] = useState(false)\`.
|
|
15546
|
+
- \`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.
|
|
15547
|
+
- A card re-rendered with new props starts fresh; the state above lives with the component, not with the request.
|
|
15548
|
+
|
|
15549
|
+
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.`;
|
|
15550
|
+
var FRAME_SIZING = `## Sizing \u2014 the frame owns the height and the width
|
|
15416
15551
|
|
|
15417
15552
|
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:
|
|
15418
15553
|
- 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.
|
|
15419
15554
|
- 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.
|
|
15420
|
-
- 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
|
|
15555
|
+
- 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.
|
|
15556
|
+
|
|
15557
|
+
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.**
|
|
15558
|
+
|
|
15559
|
+
- 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.
|
|
15560
|
+
- 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.
|
|
15561
|
+
- Centre that inner column with the container's own centring, not by shrinking the root.
|
|
15562
|
+
- **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.`;
|
|
15421
15563
|
var COMPONENT_STRUCTURE = `## Component Structure
|
|
15422
15564
|
|
|
15423
15565
|
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.
|
|
@@ -15629,6 +15771,7 @@ ${axisSection}
|
|
|
15629
15771
|
CONTRACT_SURFACE,
|
|
15630
15772
|
DEFENSIVE_CODING,
|
|
15631
15773
|
GESTURE_ROUTING,
|
|
15774
|
+
TERMINAL_ACTIONS,
|
|
15632
15775
|
FREE_GESTURE_SURFACE,
|
|
15633
15776
|
ANTI_PATTERNS,
|
|
15634
15777
|
CROSS_REFERENCE_RULES,
|
|
@@ -15659,11 +15802,11 @@ ${inputs.primitivesDoc}`
|
|
|
15659
15802
|
}
|
|
15660
15803
|
var VIRTUAL_DTS_PATH = "/__gadget__.d.ts";
|
|
15661
15804
|
var EXTRACTOR_COMPILER_OPTIONS = {
|
|
15662
|
-
target:
|
|
15663
|
-
module:
|
|
15664
|
-
moduleResolution:
|
|
15805
|
+
target: ts4.ScriptTarget.ESNext,
|
|
15806
|
+
module: ts4.ModuleKind.ESNext,
|
|
15807
|
+
moduleResolution: ts4.ModuleResolutionKind.Bundler,
|
|
15665
15808
|
allowJs: false,
|
|
15666
|
-
jsx:
|
|
15809
|
+
jsx: ts4.JsxEmit.ReactJSX,
|
|
15667
15810
|
noEmit: true,
|
|
15668
15811
|
esModuleInterop: true,
|
|
15669
15812
|
// skipLibCheck so an unresolvable wrapper-internal import (the
|
|
@@ -15675,31 +15818,31 @@ var EXTRACTOR_COMPILER_OPTIONS = {
|
|
|
15675
15818
|
var callSignatureCache = /* @__PURE__ */ new Map();
|
|
15676
15819
|
var componentPropsCache = /* @__PURE__ */ new Map();
|
|
15677
15820
|
function buildExtractorContext(dtsContent, names) {
|
|
15678
|
-
const sourceFile =
|
|
15821
|
+
const sourceFile = ts4.createSourceFile(
|
|
15679
15822
|
VIRTUAL_DTS_PATH,
|
|
15680
15823
|
dtsContent,
|
|
15681
|
-
|
|
15824
|
+
ts4.ScriptTarget.ESNext,
|
|
15682
15825
|
/* setParentNodes */
|
|
15683
15826
|
true,
|
|
15684
|
-
|
|
15827
|
+
ts4.ScriptKind.TS
|
|
15685
15828
|
);
|
|
15686
|
-
const defaultLibName =
|
|
15829
|
+
const defaultLibName = ts4.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
|
|
15687
15830
|
const host = {
|
|
15688
15831
|
getSourceFile(fileName) {
|
|
15689
15832
|
if (fileName === VIRTUAL_DTS_PATH) return sourceFile;
|
|
15690
|
-
const libContent =
|
|
15691
|
-
|
|
15833
|
+
const libContent = ts4.sys.readFile(
|
|
15834
|
+
ts4.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
|
|
15692
15835
|
/[^/\\]+$/,
|
|
15693
15836
|
fileName
|
|
15694
15837
|
)
|
|
15695
15838
|
);
|
|
15696
15839
|
if (libContent !== void 0) {
|
|
15697
|
-
return
|
|
15840
|
+
return ts4.createSourceFile(
|
|
15698
15841
|
fileName,
|
|
15699
15842
|
libContent,
|
|
15700
|
-
|
|
15843
|
+
ts4.ScriptTarget.ESNext,
|
|
15701
15844
|
true,
|
|
15702
|
-
|
|
15845
|
+
ts4.ScriptKind.TS
|
|
15703
15846
|
);
|
|
15704
15847
|
}
|
|
15705
15848
|
return void 0;
|
|
@@ -15723,14 +15866,14 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
15723
15866
|
},
|
|
15724
15867
|
fileExists(f) {
|
|
15725
15868
|
if (f === VIRTUAL_DTS_PATH) return true;
|
|
15726
|
-
return
|
|
15869
|
+
return ts4.sys.fileExists(f);
|
|
15727
15870
|
},
|
|
15728
15871
|
readFile(f) {
|
|
15729
15872
|
if (f === VIRTUAL_DTS_PATH) return dtsContent;
|
|
15730
|
-
return
|
|
15873
|
+
return ts4.sys.readFile(f);
|
|
15731
15874
|
}
|
|
15732
15875
|
};
|
|
15733
|
-
const program =
|
|
15876
|
+
const program = ts4.createProgram(
|
|
15734
15877
|
[VIRTUAL_DTS_PATH],
|
|
15735
15878
|
EXTRACTOR_COMPILER_OPTIONS,
|
|
15736
15879
|
host
|
|
@@ -15748,9 +15891,9 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
15748
15891
|
}
|
|
15749
15892
|
const MAX_DEPTH2 = 4;
|
|
15750
15893
|
function renderType(t, depth) {
|
|
15751
|
-
const plain = () => checker.typeToString(t, void 0,
|
|
15894
|
+
const plain = () => checker.typeToString(t, void 0, ts4.TypeFormatFlags.NoTruncation);
|
|
15752
15895
|
if (depth >= MAX_DEPTH2) return plain();
|
|
15753
|
-
if ((t.getFlags() &
|
|
15896
|
+
if ((t.getFlags() & ts4.TypeFlags.Boolean) !== 0) return "boolean";
|
|
15754
15897
|
if (t.isUnion()) {
|
|
15755
15898
|
return t.types.map((member) => renderType(member, depth)).join(" | ");
|
|
15756
15899
|
}
|
|
@@ -15764,13 +15907,13 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
15764
15907
|
const params = cs.getParameters().map((p) => {
|
|
15765
15908
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
15766
15909
|
const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
|
|
15767
|
-
const optional = decl !== void 0 &&
|
|
15910
|
+
const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
|
|
15768
15911
|
return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
|
|
15769
15912
|
}).join(", ");
|
|
15770
15913
|
const ret = renderType(cs.getReturnType(), depth + 1);
|
|
15771
15914
|
return `(${params}) => ${ret}`;
|
|
15772
15915
|
}
|
|
15773
|
-
const isObject = (t.getFlags() &
|
|
15916
|
+
const isObject = (t.getFlags() & ts4.TypeFlags.Object) !== 0;
|
|
15774
15917
|
const isArrayOrTuple = checker.isArrayType(t) || checker.isTupleType(t);
|
|
15775
15918
|
const typeArgs = t.typeArguments;
|
|
15776
15919
|
if (symbol !== void 0 && isObject && !isArrayOrTuple && !isWrapperLocal(symbol) && typeArgs !== void 0 && typeArgs.length > 0) {
|
|
@@ -15783,7 +15926,7 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
15783
15926
|
const body = props.map((p) => {
|
|
15784
15927
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
15785
15928
|
const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
|
|
15786
|
-
const optional = (p.getFlags() &
|
|
15929
|
+
const optional = (p.getFlags() & ts4.SymbolFlags.Optional) !== 0;
|
|
15787
15930
|
return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
|
|
15788
15931
|
}).join("; ");
|
|
15789
15932
|
return `{ ${body} }`;
|
|
@@ -15798,7 +15941,7 @@ function buildExtractorContext(dtsContent, names) {
|
|
|
15798
15941
|
const name = symbol.getName();
|
|
15799
15942
|
if (!requested.has(name)) continue;
|
|
15800
15943
|
if (signaturesByName.has(name)) continue;
|
|
15801
|
-
const resolved2 = (symbol.flags &
|
|
15944
|
+
const resolved2 = (symbol.flags & ts4.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
|
|
15802
15945
|
const declarations = resolved2.getDeclarations();
|
|
15803
15946
|
if (declarations === void 0 || declarations.length === 0) continue;
|
|
15804
15947
|
const declaration = declarations[0];
|
|
@@ -15813,7 +15956,7 @@ function printCallSignature(sig, ctx) {
|
|
|
15813
15956
|
const params = sig.getParameters().map((p) => {
|
|
15814
15957
|
const decl = p.valueDeclaration ?? p.declarations?.[0];
|
|
15815
15958
|
const pType = decl !== void 0 ? ctx.checker.getTypeOfSymbolAtLocation(p, decl) : ctx.checker.getDeclaredTypeOfSymbol(p);
|
|
15816
|
-
const optional = decl !== void 0 &&
|
|
15959
|
+
const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
|
|
15817
15960
|
return `${p.getName()}${optional ? "?" : ""}: ${ctx.renderType(pType, 0)}`;
|
|
15818
15961
|
}).join(", ");
|
|
15819
15962
|
const ret = ctx.renderType(sig.getReturnType(), 0);
|
|
@@ -16027,6 +16170,7 @@ ${axisSection}
|
|
|
16027
16170
|
CONTRACT_SURFACE,
|
|
16028
16171
|
DEFENSIVE_CODING,
|
|
16029
16172
|
GESTURE_ROUTING,
|
|
16173
|
+
TERMINAL_ACTIONS,
|
|
16030
16174
|
INTERACTIVE_TRAITS,
|
|
16031
16175
|
ANTI_PATTERNS,
|
|
16032
16176
|
CROSS_REFERENCE_RULES,
|
|
@@ -17085,7 +17229,7 @@ var HOST_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a", "div", "span", "li
|
|
|
17085
17229
|
var HOST_CHANGE_TAGS = /* @__PURE__ */ new Set(["select", "input", "textarea"]);
|
|
17086
17230
|
function findWiring(input) {
|
|
17087
17231
|
const { sourceCode, hookName, hookArg } = input;
|
|
17088
|
-
const sf =
|
|
17232
|
+
const sf = ts4.createSourceFile("Component.tsx", sourceCode, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
17089
17233
|
const hookBindings = findHookBindings(sf, hookName, hookArg);
|
|
17090
17234
|
if (hookBindings.length === 0) {
|
|
17091
17235
|
return {
|
|
@@ -17108,7 +17252,7 @@ function findWiring(input) {
|
|
|
17108
17252
|
const customPropElements = [];
|
|
17109
17253
|
const submitButton = { found: false };
|
|
17110
17254
|
function visit(node) {
|
|
17111
|
-
if (
|
|
17255
|
+
if (ts4.isJsxAttribute(node) && node.initializer) {
|
|
17112
17256
|
const attrName = node.name.getText(sf);
|
|
17113
17257
|
const initRefs = findReferencedNames(node.initializer, callableNames);
|
|
17114
17258
|
if (initRefs.size > 0) {
|
|
@@ -17146,19 +17290,19 @@ function findWiring(input) {
|
|
|
17146
17290
|
}
|
|
17147
17291
|
}
|
|
17148
17292
|
}
|
|
17149
|
-
if (
|
|
17293
|
+
if (ts4.isJsxOpeningLikeElement(node)) {
|
|
17150
17294
|
const tag = getTagName(node);
|
|
17151
17295
|
if ((tag ?? "").toLowerCase() === "button") {
|
|
17152
17296
|
const typeAttr = node.attributes.properties.find(
|
|
17153
|
-
(p) =>
|
|
17297
|
+
(p) => ts4.isJsxAttribute(p) && p.name.getText(sf) === "type"
|
|
17154
17298
|
);
|
|
17155
|
-
if (typeAttr &&
|
|
17299
|
+
if (typeAttr && ts4.isJsxAttribute(typeAttr) && typeAttr.initializer) {
|
|
17156
17300
|
const v = typeAttr.initializer.getText(sf).toLowerCase();
|
|
17157
17301
|
if (v.includes("submit")) submitButton.found = true;
|
|
17158
17302
|
}
|
|
17159
17303
|
}
|
|
17160
17304
|
}
|
|
17161
|
-
|
|
17305
|
+
ts4.forEachChild(node, visit);
|
|
17162
17306
|
}
|
|
17163
17307
|
visit(sf);
|
|
17164
17308
|
if (sawClickOnHost) {
|
|
@@ -17195,32 +17339,32 @@ function findWiring(input) {
|
|
|
17195
17339
|
function findHookBindings(sf, hookName, hookArg) {
|
|
17196
17340
|
const bindings = [];
|
|
17197
17341
|
function visit(node) {
|
|
17198
|
-
if (
|
|
17342
|
+
if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isCallExpression(node.initializer)) {
|
|
17199
17343
|
const call = node.initializer;
|
|
17200
17344
|
const callee = call.expression.getText(sf);
|
|
17201
17345
|
if (callee !== hookName) {
|
|
17202
|
-
|
|
17346
|
+
ts4.forEachChild(node, visit);
|
|
17203
17347
|
return;
|
|
17204
17348
|
}
|
|
17205
17349
|
const firstArg = call.arguments[0];
|
|
17206
|
-
if (!firstArg || !
|
|
17207
|
-
|
|
17350
|
+
if (!firstArg || !ts4.isStringLiteral(firstArg)) {
|
|
17351
|
+
ts4.forEachChild(node, visit);
|
|
17208
17352
|
return;
|
|
17209
17353
|
}
|
|
17210
17354
|
if (firstArg.text !== hookArg) {
|
|
17211
|
-
|
|
17355
|
+
ts4.forEachChild(node, visit);
|
|
17212
17356
|
return;
|
|
17213
17357
|
}
|
|
17214
|
-
if (
|
|
17358
|
+
if (ts4.isIdentifier(node.name)) {
|
|
17215
17359
|
bindings.push(node.name.text);
|
|
17216
|
-
} else if (
|
|
17360
|
+
} else if (ts4.isObjectBindingPattern(node.name)) {
|
|
17217
17361
|
for (const elem of node.name.elements) {
|
|
17218
17362
|
const target = elem.name;
|
|
17219
|
-
if (
|
|
17363
|
+
if (ts4.isIdentifier(target)) bindings.push(target.text);
|
|
17220
17364
|
}
|
|
17221
17365
|
}
|
|
17222
17366
|
}
|
|
17223
|
-
|
|
17367
|
+
ts4.forEachChild(node, visit);
|
|
17224
17368
|
}
|
|
17225
17369
|
visit(sf);
|
|
17226
17370
|
return bindings;
|
|
@@ -17228,24 +17372,24 @@ function findHookBindings(sf, hookName, hookArg) {
|
|
|
17228
17372
|
function expandAliases(sf, bindings) {
|
|
17229
17373
|
const all = new Set(bindings);
|
|
17230
17374
|
function visit(node) {
|
|
17231
|
-
if (
|
|
17375
|
+
if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isIdentifier(node.name)) {
|
|
17232
17376
|
const newName = node.name.text;
|
|
17233
17377
|
const init = node.initializer;
|
|
17234
|
-
if (
|
|
17378
|
+
if (ts4.isIdentifier(init) && all.has(init.text)) {
|
|
17235
17379
|
all.add(newName);
|
|
17236
17380
|
}
|
|
17237
|
-
if (
|
|
17381
|
+
if (ts4.isArrowFunction(init) || ts4.isFunctionExpression(init)) {
|
|
17238
17382
|
if (bodyReferences(init.body, all)) {
|
|
17239
17383
|
all.add(newName);
|
|
17240
17384
|
}
|
|
17241
17385
|
}
|
|
17242
|
-
if (
|
|
17386
|
+
if (ts4.isCallExpression(init)) {
|
|
17243
17387
|
if (bodyReferences(init, all)) {
|
|
17244
17388
|
all.add(newName);
|
|
17245
17389
|
}
|
|
17246
17390
|
}
|
|
17247
17391
|
}
|
|
17248
|
-
|
|
17392
|
+
ts4.forEachChild(node, visit);
|
|
17249
17393
|
}
|
|
17250
17394
|
visit(sf);
|
|
17251
17395
|
return all;
|
|
@@ -17254,11 +17398,11 @@ function bodyReferences(body, names) {
|
|
|
17254
17398
|
let found = false;
|
|
17255
17399
|
function walk(n) {
|
|
17256
17400
|
if (found) return;
|
|
17257
|
-
if (
|
|
17401
|
+
if (ts4.isIdentifier(n) && names.has(n.text)) {
|
|
17258
17402
|
found = true;
|
|
17259
17403
|
return;
|
|
17260
17404
|
}
|
|
17261
|
-
|
|
17405
|
+
ts4.forEachChild(n, walk);
|
|
17262
17406
|
}
|
|
17263
17407
|
walk(body);
|
|
17264
17408
|
return found;
|
|
@@ -17266,16 +17410,16 @@ function bodyReferences(body, names) {
|
|
|
17266
17410
|
function findReferencedNames(node, names) {
|
|
17267
17411
|
const found = /* @__PURE__ */ new Set();
|
|
17268
17412
|
function walk(n) {
|
|
17269
|
-
if (
|
|
17413
|
+
if (ts4.isIdentifier(n) && names.has(n.text)) {
|
|
17270
17414
|
found.add(n.text);
|
|
17271
17415
|
}
|
|
17272
|
-
|
|
17416
|
+
ts4.forEachChild(n, walk);
|
|
17273
17417
|
}
|
|
17274
17418
|
walk(node);
|
|
17275
17419
|
return found;
|
|
17276
17420
|
}
|
|
17277
17421
|
function getTagName(el) {
|
|
17278
|
-
if (
|
|
17422
|
+
if (ts4.isJsxOpeningLikeElement(el)) {
|
|
17279
17423
|
return el.tagName.getText();
|
|
17280
17424
|
}
|
|
17281
17425
|
return null;
|
|
@@ -21019,6 +21163,10 @@ interface Props ${tsInterface}
|
|
|
21019
21163
|
if (entry.nextStep) {
|
|
21020
21164
|
line += `
|
|
21021
21165
|
Next-step hint: agent intends to call \`${entry.nextStep}\``;
|
|
21166
|
+
}
|
|
21167
|
+
if (entry.confirm === true) {
|
|
21168
|
+
line += `
|
|
21169
|
+
Author flag: \`confirm\` \u2014 the author marked this action grave enough to confirm before it fires, so a second press is a mistake.`;
|
|
21022
21170
|
}
|
|
21023
21171
|
return line;
|
|
21024
21172
|
}).join("\n");
|