@tailor-platform/sdk-codemod 0.3.0-next.2 → 0.3.0-next.4
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/CHANGELOG.md +137 -7
- package/dist/codemods/ast-grep-helpers-D3FXAKNz.js +171 -0
- package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +1 -1
- package/dist/codemods/v2/auth-attributes-rename/scripts/transform.js +213 -0
- package/dist/codemods/v2/auth-connection-token-helper/scripts/transform.js +243 -0
- package/dist/codemods/v2/auth-invoker-call-unwrap/scripts/transform.js +7 -0
- package/dist/codemods/v2/auth-invoker-unwrap/scripts/transform.js +13 -6
- package/dist/codemods/v2/db-type-to-table/scripts/transform.js +383 -0
- package/dist/codemods/v2/define-generators-to-plugins/scripts/transform.js +2 -1
- package/dist/codemods/v2/env-var-rename/scripts/transform.js +88 -0
- package/dist/codemods/v2/principal-unify/scripts/transform.js +428 -5
- package/dist/codemods/v2/rename-bin/scripts/transform.js +1087 -0
- package/dist/codemods/v2/runtime-globals-opt-in/scripts/transform.js +103 -0
- package/dist/codemods/v2/runtime-subpath-namespace/scripts/transform.js +792 -0
- package/dist/codemods/v2/sdk-skills-shim/scripts/transform.js +3 -3
- package/dist/codemods/v2/tailor-output-ignore-dir/scripts/transform.js +14 -0
- package/dist/codemods/v2/wait-point-rename/scripts/transform.js +126 -0
- package/dist/index.js +868 -60
- package/package.json +8 -7
|
@@ -47,7 +47,8 @@ function transform(source) {
|
|
|
47
47
|
if (arg.kind() === "array") {
|
|
48
48
|
const children = arg.children().filter((c) => c.isNamed() && c.kind() !== "comment");
|
|
49
49
|
if (children.length >= 1) {
|
|
50
|
-
const
|
|
50
|
+
const packageName = children[0].text().replace(/^["']|["']$/g, "");
|
|
51
|
+
const mapping = PLUGIN_MAP[packageName];
|
|
51
52
|
if (mapping) {
|
|
52
53
|
migratedArgs++;
|
|
53
54
|
importsToAdd.set(mapping.importPath, mapping.functionName);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
import * as path from "pathe";
|
|
3
|
+
//#region codemods/v2/env-var-rename/scripts/transform.ts
|
|
4
|
+
const ENV_RENAMES = [
|
|
5
|
+
["TAILOR_PLATFORM_SDK_CONFIG_PATH", "TAILOR_CONFIG_PATH"],
|
|
6
|
+
["TAILOR_PLATFORM_SDK_DTS_PATH", "TAILOR_DTS_PATH"],
|
|
7
|
+
["TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION", "TAILOR_CI_ALLOW_ID_INJECTION"],
|
|
8
|
+
["TAILOR_PLATFORM_SDK_BUILD_ONLY", "TAILOR_DEPLOY_BUILD_ONLY"],
|
|
9
|
+
["TAILOR_SDK_OUTPUT_DIR", "TAILOR_BUILD_OUTPUT_DIR"],
|
|
10
|
+
["TAILOR_SDK_SKILLS_SOURCE", "TAILOR_SKILLS_SOURCE"],
|
|
11
|
+
["TAILOR_SDK_VERSION", "TAILOR_TEMPLATE_SDK_VERSION"],
|
|
12
|
+
["TAILOR_ENABLE_INLINE_SOURCEMAP", "TAILOR_INLINE_SOURCEMAP"],
|
|
13
|
+
["TAILOR_PLATFORM_QUERY_NEWLINE_ON_ENTER", "TAILOR_QUERY_NEWLINE_ON_ENTER"],
|
|
14
|
+
["TAILOR_TOKEN", "TAILOR_PLATFORM_TOKEN"]
|
|
15
|
+
];
|
|
16
|
+
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17
|
+
".ts",
|
|
18
|
+
".tsx",
|
|
19
|
+
".mts",
|
|
20
|
+
".cts",
|
|
21
|
+
".js",
|
|
22
|
+
".jsx",
|
|
23
|
+
".mjs",
|
|
24
|
+
".cjs"
|
|
25
|
+
]);
|
|
26
|
+
const ENV_BOUNDARY = "[A-Za-z0-9_]";
|
|
27
|
+
const RENAME_PATTERNS = ENV_RENAMES.map(([from, to]) => ({
|
|
28
|
+
pattern: new RegExp(`(?<!${ENV_BOUNDARY})${from}(?!${ENV_BOUNDARY})`, "g"),
|
|
29
|
+
to
|
|
30
|
+
}));
|
|
31
|
+
function escapeRegExp(value) {
|
|
32
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
}
|
|
34
|
+
function replaceTextTokens(source) {
|
|
35
|
+
let updated = source;
|
|
36
|
+
for (const { pattern, to } of RENAME_PATTERNS) updated = updated.replace(pattern, to);
|
|
37
|
+
return updated;
|
|
38
|
+
}
|
|
39
|
+
function sourceLang(filePath) {
|
|
40
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
41
|
+
return ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : Lang.TypeScript;
|
|
42
|
+
}
|
|
43
|
+
function collectStringFragmentEdits(root, source) {
|
|
44
|
+
const edits = [];
|
|
45
|
+
const visit = (node) => {
|
|
46
|
+
if (node.kind() === "string_fragment") {
|
|
47
|
+
const range = node.range();
|
|
48
|
+
const text = source.slice(range.start.index, range.end.index);
|
|
49
|
+
const replacement = replaceTextTokens(text);
|
|
50
|
+
if (replacement !== text) edits.push([
|
|
51
|
+
range.start.index,
|
|
52
|
+
range.end.index,
|
|
53
|
+
replacement
|
|
54
|
+
]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const child of node.children()) visit(child);
|
|
58
|
+
};
|
|
59
|
+
visit(root);
|
|
60
|
+
return edits;
|
|
61
|
+
}
|
|
62
|
+
function replaceSourceStringFragments(source, filePath) {
|
|
63
|
+
let root;
|
|
64
|
+
try {
|
|
65
|
+
root = parse(sourceLang(filePath), source).root();
|
|
66
|
+
} catch {
|
|
67
|
+
return source;
|
|
68
|
+
}
|
|
69
|
+
let updated = source;
|
|
70
|
+
const edits = collectStringFragmentEdits(root, source).toSorted(([a], [b]) => b - a);
|
|
71
|
+
for (const [start, end, replacement] of edits) updated = `${updated.slice(0, start)}${replacement}${updated.slice(end)}`;
|
|
72
|
+
return updated;
|
|
73
|
+
}
|
|
74
|
+
function replaceSourceTokens(source, filePath) {
|
|
75
|
+
let updated = source;
|
|
76
|
+
for (const [from, to] of ENV_RENAMES) {
|
|
77
|
+
const escaped = escapeRegExp(from);
|
|
78
|
+
updated = updated.replace(new RegExp(`\\bprocess\\.env\\.${escaped}(?![A-Za-z0-9_$])`, "g"), `process.env.${to}`).replace(new RegExp(`\\bprocess\\.env\\[(["'\`])${escaped}\\1\\]`, "g"), `process.env[$1${to}$1]`).replace(new RegExp(`([,{]\\s*)${escaped}(?=\\s*:)`, "g"), `$1${to}`);
|
|
79
|
+
}
|
|
80
|
+
return replaceSourceStringFragments(updated, filePath);
|
|
81
|
+
}
|
|
82
|
+
function transform(source, filePath) {
|
|
83
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
84
|
+
const updated = SOURCE_EXTENSIONS.has(ext) ? replaceSourceTokens(source, filePath) : replaceTextTokens(source);
|
|
85
|
+
return updated === source ? null : updated;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { transform as default };
|
|
@@ -391,7 +391,7 @@ function rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticate
|
|
|
391
391
|
touched: true
|
|
392
392
|
};
|
|
393
393
|
}
|
|
394
|
-
const SCOPE_KINDS = new Set([
|
|
394
|
+
const SCOPE_KINDS = /* @__PURE__ */ new Set([
|
|
395
395
|
"statement_block",
|
|
396
396
|
"function_body",
|
|
397
397
|
"for_statement",
|
|
@@ -1196,6 +1196,21 @@ function isSdkFieldMemberCall(call, sdkFieldRootNames, sdkFieldLocalBindings, ro
|
|
|
1196
1196
|
const object = findMemberCallObject(call);
|
|
1197
1197
|
return object ? isSdkFieldExpression(object, sdkFieldRootNames, sdkFieldLocalBindings, root) : false;
|
|
1198
1198
|
}
|
|
1199
|
+
function collectSdkFieldRootNames(root) {
|
|
1200
|
+
const sdkFieldRootNames = /* @__PURE__ */ new Set();
|
|
1201
|
+
const sdkImports = root.findAll({ rule: {
|
|
1202
|
+
kind: "import_statement",
|
|
1203
|
+
has: {
|
|
1204
|
+
kind: "string",
|
|
1205
|
+
regex: "^[\"']@tailor-platform/sdk(/test)?[\"']$"
|
|
1206
|
+
}
|
|
1207
|
+
} });
|
|
1208
|
+
for (const importStmt of sdkImports) {
|
|
1209
|
+
for (const namespaceName of iterateNamespaceImportLocalNames(importStmt)) sdkFieldRootNames.add(namespaceName);
|
|
1210
|
+
for (const { importedName, localName } of iterateImportSpecs(importStmt)) if (importedName === "t" || importedName === "db") sdkFieldRootNames.add(localName);
|
|
1211
|
+
}
|
|
1212
|
+
return sdkFieldRootNames;
|
|
1213
|
+
}
|
|
1199
1214
|
function localBindingRootScope(root) {
|
|
1200
1215
|
const rootRange = root.range();
|
|
1201
1216
|
return [rootRange.start.index, rootRange.end.index];
|
|
@@ -1342,6 +1357,416 @@ function transformParseArgsObject(call, edits, sdkFieldRootNames, sdkFieldLocalB
|
|
|
1342
1357
|
} else if (kind === "shorthand_property_identifier" && child.text() === "user") edits.push(child.replace("invoker: user"));
|
|
1343
1358
|
}
|
|
1344
1359
|
}
|
|
1360
|
+
const KYSELY_PREDICATE_METHODS = /* @__PURE__ */ new Set([
|
|
1361
|
+
"where",
|
|
1362
|
+
"having",
|
|
1363
|
+
"on"
|
|
1364
|
+
]);
|
|
1365
|
+
function excerptForLine(source, line) {
|
|
1366
|
+
const excerpt = (source.split(/\r?\n/)[line - 1] ?? "").trim();
|
|
1367
|
+
return excerpt.length > 160 ? `${excerpt.slice(0, 157)}...` : excerpt;
|
|
1368
|
+
}
|
|
1369
|
+
function addReviewFinding(findings, seen, source, file, node, message) {
|
|
1370
|
+
const line = node.range().start.line + 1;
|
|
1371
|
+
const excerpt = excerptForLine(source, line);
|
|
1372
|
+
const key = `${file}:${line}:${message}:${excerpt}`;
|
|
1373
|
+
if (seen.has(key)) return;
|
|
1374
|
+
seen.add(key);
|
|
1375
|
+
findings.push({
|
|
1376
|
+
file,
|
|
1377
|
+
line,
|
|
1378
|
+
message,
|
|
1379
|
+
excerpt
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
function resolvesToReviewBinding(node, bindings, root) {
|
|
1383
|
+
if (node.kind() !== "identifier") return false;
|
|
1384
|
+
const pos = node.range().start.index;
|
|
1385
|
+
return bindings.some((binding) => {
|
|
1386
|
+
if (binding.name !== node.text() || !rangeContains(binding.scope, pos)) return false;
|
|
1387
|
+
if (binding.shadowRoot) return !isInsideAnyRange(pos, collectAllShadowRanges(binding.shadowRoot, binding.name));
|
|
1388
|
+
return !isShadowedLocalReference(root, binding.name, pos, binding.bindingStart);
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
function isReviewContextCallerMemberExpression(node, contextBindings, root) {
|
|
1392
|
+
if (node.kind() !== "member_expression") return false;
|
|
1393
|
+
if (node.field("property")?.text() !== "caller") return false;
|
|
1394
|
+
const object = node.field("object");
|
|
1395
|
+
return object ? resolvesToReviewBinding(object, contextBindings, root) : false;
|
|
1396
|
+
}
|
|
1397
|
+
function isPrincipalOptionalMemberExpression(node, principalBindings, contextBindings, root) {
|
|
1398
|
+
if (node.kind() !== "member_expression") return false;
|
|
1399
|
+
if (!node.children().some((child) => child.kind() === "optional_chain")) return false;
|
|
1400
|
+
const object = node.field("object");
|
|
1401
|
+
if (!object) return false;
|
|
1402
|
+
if (object.kind() === "identifier") return resolvesToReviewBinding(object, principalBindings, root);
|
|
1403
|
+
return isReviewContextCallerMemberExpression(object, contextBindings, root);
|
|
1404
|
+
}
|
|
1405
|
+
function isDirectPrincipalExpression(node, principalBindings, contextBindings, root) {
|
|
1406
|
+
if (resolvesToReviewBinding(node, principalBindings, root)) return true;
|
|
1407
|
+
return isReviewContextCallerMemberExpression(node, contextBindings, root);
|
|
1408
|
+
}
|
|
1409
|
+
function nodeContainsArgumentPrincipalOptionalAccess(node, principalBindings, contextBindings, safePrincipalRanges, sdkFieldRootNames, sdkFieldLocalBindings, root) {
|
|
1410
|
+
const nodeSafePrincipalRanges = node.kind() === "call_expression" ? [...safePrincipalRanges, ...collectSafeNullablePrincipalArgumentRanges(node, sdkFieldRootNames, sdkFieldLocalBindings, root)] : safePrincipalRanges;
|
|
1411
|
+
if (isInsideAnyRange(node.range().start.index, nodeSafePrincipalRanges)) return false;
|
|
1412
|
+
if (isFunctionNode(node)) return false;
|
|
1413
|
+
if (isDirectPrincipalExpression(node, principalBindings, contextBindings, root)) return true;
|
|
1414
|
+
if (isPrincipalOptionalMemberExpression(node, principalBindings, contextBindings, root)) return true;
|
|
1415
|
+
return node.children().some((child) => nodeContainsArgumentPrincipalOptionalAccess(child, principalBindings, contextBindings, nodeSafePrincipalRanges, sdkFieldRootNames, sdkFieldLocalBindings, root));
|
|
1416
|
+
}
|
|
1417
|
+
function reviewCallName(call) {
|
|
1418
|
+
const fn = call.field("function");
|
|
1419
|
+
if (!fn) return "a call";
|
|
1420
|
+
if (fn.kind() === "identifier") return `${fn.text()}()`;
|
|
1421
|
+
if (fn.kind() === "member_expression") {
|
|
1422
|
+
const property = fn.field("property");
|
|
1423
|
+
if (property) return `${property.text()}()`;
|
|
1424
|
+
}
|
|
1425
|
+
return "a call";
|
|
1426
|
+
}
|
|
1427
|
+
function collectParseInvokerValueRanges(call) {
|
|
1428
|
+
const objectArg = call.field("arguments")?.children().find((child) => child.kind() === "object");
|
|
1429
|
+
if (!objectArg) return [];
|
|
1430
|
+
const ranges = [];
|
|
1431
|
+
for (const child of objectArg.children()) {
|
|
1432
|
+
if (child.kind() === "shorthand_property_identifier" && child.text() === "invoker") {
|
|
1433
|
+
const range = child.range();
|
|
1434
|
+
ranges.push([range.start.index, range.end.index]);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
if (child.kind() !== "pair") continue;
|
|
1438
|
+
if (child.field("key")?.text() !== "invoker") continue;
|
|
1439
|
+
const value = child.field("value");
|
|
1440
|
+
if (!value) continue;
|
|
1441
|
+
const range = value.range();
|
|
1442
|
+
ranges.push([range.start.index, range.end.index]);
|
|
1443
|
+
}
|
|
1444
|
+
return ranges;
|
|
1445
|
+
}
|
|
1446
|
+
function collectSafeNullablePrincipalArgumentRanges(call, sdkFieldRootNames, sdkFieldLocalBindings, root) {
|
|
1447
|
+
if (findMemberCallName(call) !== "parse") return [];
|
|
1448
|
+
if (!isSdkFieldMemberCall(call, sdkFieldRootNames, sdkFieldLocalBindings, root)) return [];
|
|
1449
|
+
return collectParseInvokerValueRanges(call);
|
|
1450
|
+
}
|
|
1451
|
+
function collectNullableCallerReviewFindings(root, source, file, principalBindings, contextBindings, sdkFieldRootNames, sdkFieldLocalBindings, findings, seen) {
|
|
1452
|
+
const calls = root.findAll({ rule: { kind: "call_expression" } });
|
|
1453
|
+
for (const call of calls) {
|
|
1454
|
+
const args = call.field("arguments");
|
|
1455
|
+
const safePrincipalRanges = collectSafeNullablePrincipalArgumentRanges(call, sdkFieldRootNames, sdkFieldLocalBindings, root);
|
|
1456
|
+
const nullableArg = args?.children().find((child) => nodeContainsArgumentPrincipalOptionalAccess(child, principalBindings, contextBindings, safePrincipalRanges, sdkFieldRootNames, sdkFieldLocalBindings, root));
|
|
1457
|
+
if (!nullableArg) continue;
|
|
1458
|
+
const memberName = findMemberCallName(call);
|
|
1459
|
+
if (memberName && KYSELY_PREDICATE_METHODS.has(memberName)) {
|
|
1460
|
+
addReviewFinding(findings, seen, source, file, nullableArg, "Nullable caller value is used as a Kysely predicate value.");
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
addReviewFinding(findings, seen, source, file, nullableArg, `Nullable caller value is passed as a non-null argument to ${reviewCallName(call)}.`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
function functionIdentifierParamName(fn) {
|
|
1467
|
+
const param = getFirstFunctionParam(fn);
|
|
1468
|
+
const pattern = param ? getFunctionParamPattern(param) : null;
|
|
1469
|
+
return pattern?.kind() === "identifier" ? pattern.text() : null;
|
|
1470
|
+
}
|
|
1471
|
+
function objectPatternTopLevelPropertyBindingName(pattern, propertyName) {
|
|
1472
|
+
if (pattern.kind() !== "object_pattern") return null;
|
|
1473
|
+
for (const child of pattern.children()) {
|
|
1474
|
+
const kind = child.kind();
|
|
1475
|
+
if (kind === "shorthand_property_identifier_pattern" && child.text() === propertyName) return child.text();
|
|
1476
|
+
if (kind === "pair_pattern" && child.field("key")?.text() === propertyName) {
|
|
1477
|
+
const value = child.field("value");
|
|
1478
|
+
return value?.kind() === "identifier" ? value.text() : propertyName;
|
|
1479
|
+
}
|
|
1480
|
+
if (kind === "object_assignment_pattern") {
|
|
1481
|
+
const inner = child.children().find((c) => c.kind() === "shorthand_property_identifier_pattern");
|
|
1482
|
+
if (inner?.text() === propertyName) return inner.text();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
function objectPatternHasTopLevelProperty(pattern, propertyName) {
|
|
1488
|
+
return objectPatternTopLevelPropertyBindingName(pattern, propertyName) !== null;
|
|
1489
|
+
}
|
|
1490
|
+
function functionReadsContextUser(fn, contextName) {
|
|
1491
|
+
const body = fn.field("body");
|
|
1492
|
+
if (!body) return false;
|
|
1493
|
+
const shadowRanges = collectCtxShadowRanges(body, contextName, fn);
|
|
1494
|
+
const userProperties = body.findAll({ rule: {
|
|
1495
|
+
kind: "property_identifier",
|
|
1496
|
+
regex: "^user$"
|
|
1497
|
+
} });
|
|
1498
|
+
for (const propId of userProperties) {
|
|
1499
|
+
const parent = propId.parent();
|
|
1500
|
+
if (!parent || parent.kind() !== "member_expression") continue;
|
|
1501
|
+
const object = parent.field("object");
|
|
1502
|
+
if (!object || object.kind() !== "identifier" || object.text() !== contextName) continue;
|
|
1503
|
+
if (isInsideAnyRange(object.range().start.index, shadowRanges)) continue;
|
|
1504
|
+
return true;
|
|
1505
|
+
}
|
|
1506
|
+
const destructures = body.findAll({ rule: {
|
|
1507
|
+
kind: "variable_declarator",
|
|
1508
|
+
has: {
|
|
1509
|
+
field: "value",
|
|
1510
|
+
kind: "identifier",
|
|
1511
|
+
regex: `^${escapeRegex(contextName)}$`
|
|
1512
|
+
}
|
|
1513
|
+
} });
|
|
1514
|
+
for (const decl of destructures) {
|
|
1515
|
+
const value = decl.field("value");
|
|
1516
|
+
if (!value || isInsideAnyRange(value.range().start.index, shadowRanges)) continue;
|
|
1517
|
+
const name = decl.field("name");
|
|
1518
|
+
if (name && objectPatternHasTopLevelProperty(name, "user")) return true;
|
|
1519
|
+
}
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
function functionContextUserReadExpression(fn) {
|
|
1523
|
+
const param = getFirstFunctionParam(fn);
|
|
1524
|
+
const pattern = param ? getFunctionParamPattern(param) : null;
|
|
1525
|
+
if (!pattern) return null;
|
|
1526
|
+
if (pattern.kind() === "identifier") return functionReadsContextUser(fn, pattern.text()) ? `${pattern.text()}.user` : null;
|
|
1527
|
+
return objectPatternTopLevelPropertyBindingName(pattern, "user");
|
|
1528
|
+
}
|
|
1529
|
+
function collectContextUserHelperBindings(root) {
|
|
1530
|
+
return collectLocalCallbackBindings(root).flatMap((binding) => {
|
|
1531
|
+
const contextUserExpression = functionContextUserReadExpression(binding.fn);
|
|
1532
|
+
if (!contextUserExpression) return [];
|
|
1533
|
+
return [{
|
|
1534
|
+
...binding,
|
|
1535
|
+
contextUserExpression
|
|
1536
|
+
}];
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
function resolveContextUserHelperBinding(node, bindings, root) {
|
|
1540
|
+
if (node.kind() !== "identifier") return null;
|
|
1541
|
+
const pos = node.range().start.index;
|
|
1542
|
+
return bindings.find((binding) => binding.name === node.text() && rangeContains(binding.scope, pos) && !isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) ?? null;
|
|
1543
|
+
}
|
|
1544
|
+
function addResolverContextBody(arrow, bodies) {
|
|
1545
|
+
const paramName = functionIdentifierParamName(arrow);
|
|
1546
|
+
const body = arrow.field("body");
|
|
1547
|
+
if (!paramName || !body) return;
|
|
1548
|
+
bodies.push({
|
|
1549
|
+
arrow,
|
|
1550
|
+
body,
|
|
1551
|
+
contextName: paramName
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
function collectResolverBodyArrows(root) {
|
|
1555
|
+
const sdkImports = root.findAll({ rule: {
|
|
1556
|
+
kind: "import_statement",
|
|
1557
|
+
has: {
|
|
1558
|
+
kind: "string",
|
|
1559
|
+
regex: "^[\"']@tailor-platform/sdk(/test)?[\"']$"
|
|
1560
|
+
}
|
|
1561
|
+
} });
|
|
1562
|
+
const createResolverLocalNames = /* @__PURE__ */ new Set();
|
|
1563
|
+
const sdkNamespaceNames = /* @__PURE__ */ new Set();
|
|
1564
|
+
for (const importStmt of sdkImports) {
|
|
1565
|
+
for (const namespaceName of iterateNamespaceImportLocalNames(importStmt)) sdkNamespaceNames.add(namespaceName);
|
|
1566
|
+
for (const { importedName, localName } of iterateImportSpecs(importStmt)) if (importedName === "createResolver") createResolverLocalNames.add(localName);
|
|
1567
|
+
}
|
|
1568
|
+
const arrows = [];
|
|
1569
|
+
for (const localName of createResolverLocalNames) {
|
|
1570
|
+
const shadowRanges = collectAllShadowRanges(root, localName);
|
|
1571
|
+
const calls = root.findAll({ rule: {
|
|
1572
|
+
kind: "call_expression",
|
|
1573
|
+
has: {
|
|
1574
|
+
field: "function",
|
|
1575
|
+
kind: "identifier",
|
|
1576
|
+
regex: `^${escapeRegex(localName)}$`
|
|
1577
|
+
}
|
|
1578
|
+
} });
|
|
1579
|
+
for (const call of calls) {
|
|
1580
|
+
const callee = call.field("function");
|
|
1581
|
+
if (!callee || isInsideAnyRange(callee.range().start.index, shadowRanges)) continue;
|
|
1582
|
+
const arrow = findResolverBodyArrow(call);
|
|
1583
|
+
if (arrow) arrows.push(arrow);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
for (const namespaceName of sdkNamespaceNames) {
|
|
1587
|
+
const shadowRanges = collectAllShadowRanges(root, namespaceName);
|
|
1588
|
+
const calls = root.findAll({ rule: {
|
|
1589
|
+
kind: "call_expression",
|
|
1590
|
+
has: {
|
|
1591
|
+
field: "function",
|
|
1592
|
+
kind: "member_expression",
|
|
1593
|
+
has: {
|
|
1594
|
+
field: "property",
|
|
1595
|
+
kind: "property_identifier",
|
|
1596
|
+
regex: "^createResolver$"
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
} });
|
|
1600
|
+
for (const call of calls) {
|
|
1601
|
+
const object = call.field("function")?.field("object");
|
|
1602
|
+
if (!object || object.kind() !== "identifier" || object.text() !== namespaceName) continue;
|
|
1603
|
+
if (isInsideAnyRange(object.range().start.index, shadowRanges)) continue;
|
|
1604
|
+
const arrow = findResolverBodyArrow(call);
|
|
1605
|
+
if (arrow) arrows.push(arrow);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return arrows;
|
|
1609
|
+
}
|
|
1610
|
+
function collectResolverContextBodies(resolverBodyArrows) {
|
|
1611
|
+
const bodies = [];
|
|
1612
|
+
for (const arrow of resolverBodyArrows) addResolverContextBody(arrow, bodies);
|
|
1613
|
+
return bodies;
|
|
1614
|
+
}
|
|
1615
|
+
function collectResolverContextBindings(root, resolverBodyArrows) {
|
|
1616
|
+
const bindings = [];
|
|
1617
|
+
const rootRange = root.range();
|
|
1618
|
+
const rootScope = [rootRange.start.index, rootRange.end.index];
|
|
1619
|
+
for (const arrow of resolverBodyArrows) {
|
|
1620
|
+
const param = getFirstFunctionParam(arrow);
|
|
1621
|
+
const pattern = param ? getFunctionParamPattern(param) : null;
|
|
1622
|
+
const body = arrow.field("body");
|
|
1623
|
+
if (!pattern || pattern.kind() !== "identifier" || !body) continue;
|
|
1624
|
+
const bodyRange = body.range();
|
|
1625
|
+
bindings.push({
|
|
1626
|
+
name: pattern.text(),
|
|
1627
|
+
bindingStart: pattern.range().start.index,
|
|
1628
|
+
scope: [bodyRange.start.index, bodyRange.end.index],
|
|
1629
|
+
shadowRoot: body
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
let changed = true;
|
|
1633
|
+
while (changed) {
|
|
1634
|
+
changed = false;
|
|
1635
|
+
const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
|
|
1636
|
+
for (const decl of declarators) {
|
|
1637
|
+
const name = decl.field("name");
|
|
1638
|
+
const value = decl.field("value");
|
|
1639
|
+
if (!name || name.kind() !== "identifier" || !value) continue;
|
|
1640
|
+
const bindingStart = name.range().start.index;
|
|
1641
|
+
if (bindings.some((binding) => binding.bindingStart === bindingStart)) continue;
|
|
1642
|
+
if (!resolvesToReviewBinding(value, bindings, root)) continue;
|
|
1643
|
+
bindings.push({
|
|
1644
|
+
name: name.text(),
|
|
1645
|
+
bindingStart,
|
|
1646
|
+
scope: enclosingScopeRange(decl) ?? rootScope
|
|
1647
|
+
});
|
|
1648
|
+
changed = true;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return bindings;
|
|
1652
|
+
}
|
|
1653
|
+
function collectCallerPatternBindings(pattern, scope, bindings, shadowRoot) {
|
|
1654
|
+
if (pattern.kind() !== "object_pattern") return;
|
|
1655
|
+
for (const child of pattern.children()) {
|
|
1656
|
+
const kind = child.kind();
|
|
1657
|
+
if (kind === "shorthand_property_identifier_pattern" && child.text() === "caller") bindings.push({
|
|
1658
|
+
name: "caller",
|
|
1659
|
+
bindingStart: child.range().start.index,
|
|
1660
|
+
scope,
|
|
1661
|
+
shadowRoot
|
|
1662
|
+
});
|
|
1663
|
+
else if (kind === "pair_pattern") {
|
|
1664
|
+
const key = child.field("key");
|
|
1665
|
+
const value = child.field("value");
|
|
1666
|
+
if (key?.text() === "caller" && value?.kind() === "identifier") bindings.push({
|
|
1667
|
+
name: value.text(),
|
|
1668
|
+
bindingStart: value.range().start.index,
|
|
1669
|
+
scope,
|
|
1670
|
+
shadowRoot
|
|
1671
|
+
});
|
|
1672
|
+
} else if (kind === "object_assignment_pattern") {
|
|
1673
|
+
const inner = child.children().find((c) => c.kind() === "shorthand_property_identifier_pattern");
|
|
1674
|
+
if (inner?.text() === "caller") bindings.push({
|
|
1675
|
+
name: "caller",
|
|
1676
|
+
bindingStart: inner.range().start.index,
|
|
1677
|
+
scope,
|
|
1678
|
+
shadowRoot
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
function collectResolverPrincipalBindings(root, contextBindings, resolverBodyArrows) {
|
|
1684
|
+
const bindings = [];
|
|
1685
|
+
for (const arrow of resolverBodyArrows) {
|
|
1686
|
+
const param = getFirstFunctionParam(arrow);
|
|
1687
|
+
const pattern = param ? getFunctionParamPattern(param) : null;
|
|
1688
|
+
const body = arrow.field("body");
|
|
1689
|
+
if (!pattern || !body) continue;
|
|
1690
|
+
const bodyRange = body.range();
|
|
1691
|
+
const bodyScope = [bodyRange.start.index, bodyRange.end.index];
|
|
1692
|
+
if (pattern.kind() === "object_pattern") {
|
|
1693
|
+
collectCallerPatternBindings(pattern, bodyScope, bindings, body);
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
if (pattern.kind() !== "identifier") continue;
|
|
1697
|
+
const declarators = body.findAll({ rule: { kind: "variable_declarator" } });
|
|
1698
|
+
for (const decl of declarators) {
|
|
1699
|
+
const name = decl.field("name");
|
|
1700
|
+
const value = decl.field("value");
|
|
1701
|
+
if (!name || !value) continue;
|
|
1702
|
+
if (resolvesToReviewBinding(value, contextBindings, root)) {
|
|
1703
|
+
collectCallerPatternBindings(name, enclosingScopeRange(decl) ?? bodyScope, bindings);
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
if (name.kind() !== "identifier") continue;
|
|
1707
|
+
const bindingStart = name.range().start.index;
|
|
1708
|
+
if (bindings.some((binding) => binding.bindingStart === bindingStart)) continue;
|
|
1709
|
+
if (!isReviewContextCallerMemberExpression(value, contextBindings, root) && !resolvesToReviewBinding(value, bindings, root)) continue;
|
|
1710
|
+
bindings.push({
|
|
1711
|
+
name: name.text(),
|
|
1712
|
+
bindingStart,
|
|
1713
|
+
scope: enclosingScopeRange(decl) ?? bodyScope
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return bindings;
|
|
1718
|
+
}
|
|
1719
|
+
function firstIdentifierArgument(call) {
|
|
1720
|
+
const args = call.field("arguments");
|
|
1721
|
+
if (!args) return null;
|
|
1722
|
+
for (const child of args.children()) {
|
|
1723
|
+
if (child.kind() === "(" || child.kind() === ")" || child.kind() === ",") continue;
|
|
1724
|
+
return child.kind() === "identifier" ? child : null;
|
|
1725
|
+
}
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
function collectContextUserHelperReviewFindings(root, source, file, resolverBodyArrows, findings, seen) {
|
|
1729
|
+
const helperBindings = collectContextUserHelperBindings(root);
|
|
1730
|
+
if (helperBindings.length === 0) return;
|
|
1731
|
+
const reportedDefinitions = /* @__PURE__ */ new Set();
|
|
1732
|
+
for (const { arrow, body, contextName } of collectResolverContextBodies(resolverBodyArrows)) {
|
|
1733
|
+
const shadowRanges = collectCtxShadowRanges(body, contextName, arrow);
|
|
1734
|
+
const calls = body.findAll({ rule: { kind: "call_expression" } });
|
|
1735
|
+
for (const call of calls) {
|
|
1736
|
+
if (isInsideAnyRange(call.range().start.index, shadowRanges)) continue;
|
|
1737
|
+
const arg = firstIdentifierArgument(call);
|
|
1738
|
+
if (!arg || arg.text() !== contextName) continue;
|
|
1739
|
+
const callee = call.field("function");
|
|
1740
|
+
if (!callee) continue;
|
|
1741
|
+
const helper = resolveContextUserHelperBinding(callee, helperBindings, root);
|
|
1742
|
+
if (!helper) continue;
|
|
1743
|
+
if (!reportedDefinitions.has(helper.bindingStart)) {
|
|
1744
|
+
reportedDefinitions.add(helper.bindingStart);
|
|
1745
|
+
addReviewFinding(findings, seen, source, file, helper.fn, `Helper adapter ${helper.name} reads ${helper.contextUserExpression} and needs v2 caller/invoker semantics.`);
|
|
1746
|
+
}
|
|
1747
|
+
addReviewFinding(findings, seen, source, file, call, `${helper.name}(${contextName}) passes an SDK resolver context into a helper that reads ${helper.contextUserExpression}.`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function reviewFindings(source, _filePath, relativePath) {
|
|
1752
|
+
if (!source.includes("?.") && !source.includes("user") && !source.includes("caller")) return [];
|
|
1753
|
+
let root;
|
|
1754
|
+
try {
|
|
1755
|
+
root = parse(Lang.TypeScript, source).root();
|
|
1756
|
+
} catch {
|
|
1757
|
+
return [];
|
|
1758
|
+
}
|
|
1759
|
+
const findings = [];
|
|
1760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1761
|
+
const resolverBodyArrows = collectResolverBodyArrows(root);
|
|
1762
|
+
const contextBindings = collectResolverContextBindings(root, resolverBodyArrows);
|
|
1763
|
+
const principalBindings = collectResolverPrincipalBindings(root, contextBindings, resolverBodyArrows);
|
|
1764
|
+
const sdkFieldRootNames = collectSdkFieldRootNames(root);
|
|
1765
|
+
const sdkFieldLocalBindings = collectSdkFieldLocalBindings(root, sdkFieldRootNames);
|
|
1766
|
+
collectNullableCallerReviewFindings(root, source, relativePath, principalBindings, contextBindings, sdkFieldRootNames, sdkFieldLocalBindings, findings, seen);
|
|
1767
|
+
collectContextUserHelperReviewFindings(root, source, relativePath, resolverBodyArrows, findings, seen);
|
|
1768
|
+
return findings;
|
|
1769
|
+
}
|
|
1345
1770
|
/**
|
|
1346
1771
|
* Migrate user/actor/invoker types and identifiers to the unified TailorPrincipal.
|
|
1347
1772
|
*
|
|
@@ -1419,12 +1844,10 @@ function transform(source) {
|
|
|
1419
1844
|
}
|
|
1420
1845
|
const createResolverLocalNames = /* @__PURE__ */ new Set();
|
|
1421
1846
|
const createExecutorLocalNames = /* @__PURE__ */ new Set();
|
|
1422
|
-
const sdkFieldRootNames =
|
|
1423
|
-
for (const namespaceName of sdkNamespaceNames) sdkFieldRootNames.add(namespaceName);
|
|
1847
|
+
const sdkFieldRootNames = collectSdkFieldRootNames(tree);
|
|
1424
1848
|
for (const importStmt of sdkImports) for (const { importedName, localName } of iterateImportSpecs(importStmt)) {
|
|
1425
1849
|
if (importedName === "createResolver") createResolverLocalNames.add(localName);
|
|
1426
1850
|
if (importedName === "createExecutor") createExecutorLocalNames.add(localName);
|
|
1427
|
-
if (importedName === "t" || importedName === "db") sdkFieldRootNames.add(localName);
|
|
1428
1851
|
}
|
|
1429
1852
|
const sdkFieldLocalBindings = collectSdkFieldLocalBindings(tree, sdkFieldRootNames);
|
|
1430
1853
|
const parseContext = {
|
|
@@ -1555,4 +1978,4 @@ function transform(source) {
|
|
|
1555
1978
|
return result;
|
|
1556
1979
|
}
|
|
1557
1980
|
//#endregion
|
|
1558
|
-
export { transform as default };
|
|
1981
|
+
export { transform as default, reviewFindings };
|