@tailor-platform/sdk-codemod 0.3.0-next.3 → 0.3.0-next.5
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 +54 -0
- package/dist/codemods/{ast-grep-helpers-Bfn39biW.js → ast-grep-helpers-CXtWn3RB.js} +1 -1
- package/dist/codemods/v2/auth-connection-token-helper/scripts/transform.js +1 -1
- package/dist/codemods/v2/db-type-to-table/scripts/transform.js +383 -0
- package/dist/codemods/v2/forward-relation-name/scripts/transform.js +115 -0
- package/dist/codemods/v2/runtime-globals-opt-in/scripts/transform.js +1 -1
- package/dist/codemods/v2/runtime-subpath-namespace/scripts/transform.js +792 -0
- package/dist/index.js +218 -0
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @tailor-platform/sdk-codemod
|
|
2
2
|
|
|
3
|
+
## 0.3.0-next.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1719](https://github.com/tailor-platform/sdk/pull/1719) [`4a05aec`](https://github.com/tailor-platform/sdk/commit/4a05aecfb100a1ea7292a6ae5809a2d1e6eddbfe) Thanks [@dqn](https://github.com/dqn)! - Derive default TailorDB forward relation names from the relation field name by removing a trailing `ID`, `Id`, or `id`, instead of deriving them from the target table name.
|
|
8
|
+
|
|
9
|
+
The v2 migration review identifies non-self relations without `toward.as`. Add an explicit name to preserve the v1 GraphQL field name, or update consumers to use the new field-based name.
|
|
10
|
+
|
|
11
|
+
- [#1678](https://github.com/tailor-platform/sdk/pull/1678) [`e0e768d`](https://github.com/tailor-platform/sdk/commit/e0e768d77470d13806ed7b2ee2117fe374d51d40) Thanks [@toiroakr](https://github.com/toiroakr)! - Redesign TailorDB hooks and validators with several breaking changes:
|
|
12
|
+
|
|
13
|
+
- Add shared `now` timestamp to all hooks — multiple fields stamped with the same `Date`
|
|
14
|
+
- Field-level hooks: `{ value, data, invoker }` → create `{ input, invoker, now }` / update `{ input, oldValue, invoker, now }` (`data` removed, `oldValue` added for update only)
|
|
15
|
+
- Type-level hooks: per-field mapping (`Hooks<F>`) → single `{ create, update }` object (`TypeHook<F>`) returning partial field overrides
|
|
16
|
+
- Type-level create hooks no longer receive `oldRecord`; update hooks receive non-nullable `oldRecord`
|
|
17
|
+
- Field-level validators: return type changed from `boolean` to `string | void` (return error message or void to pass); `[fn, message]` tuple form removed
|
|
18
|
+
- Type-level validators: `Validators<F>` per-field record → `TypeValidateFn<F>` single function with `issues(field, message)` callback
|
|
19
|
+
- Add `.default(value)` on fields to set a create-time default (makes required fields optional in create input)
|
|
20
|
+
- Remove exported types: `Hooks<F>`, `Validators<F>`, `ValidateConfig`
|
|
21
|
+
|
|
22
|
+
## 0.3.0-next.4
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [#1693](https://github.com/tailor-platform/sdk/pull/1693) [`4751214`](https://github.com/tailor-platform/sdk/commit/4751214c0923e094a844f9ce322279a47e871075) Thanks [@dqn](https://github.com/dqn)! - Rename the TailorDB schema builder from `db.type()` to `db.table()`.
|
|
27
|
+
|
|
28
|
+
Update TailorDB definitions:
|
|
29
|
+
|
|
30
|
+
```diff
|
|
31
|
+
import { db } from "@tailor-platform/sdk";
|
|
32
|
+
|
|
33
|
+
-export const user = db.type("User", {
|
|
34
|
+
+export const user = db.table("User", {
|
|
35
|
+
name: db.string(),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- [#1704](https://github.com/tailor-platform/sdk/pull/1704) [`9c81d9c`](https://github.com/tailor-platform/sdk/commit/9c81d9c18b1d29b3e9307ea17fe54c8ce55f4dda) Thanks [@dqn](https://github.com/dqn)! - Remove flat value and default exports from `@tailor-platform/sdk/runtime/*` subpath modules. Import each subpath through its self-named namespace export instead, for example `import { iconv } from "@tailor-platform/sdk/runtime/iconv"`.
|
|
40
|
+
|
|
41
|
+
The aggregate `@tailor-platform/sdk/runtime` entry remains named-only, and its deprecated `file.deleteFile` alias is removed in favor of `file.delete`. The v2 codemod rewrites straightforward namespace-star subpath imports, flat named value imports, and aggregate `file.deleteFile` calls to the new namespace-object style.
|
|
42
|
+
|
|
43
|
+
`TailorContextAPI` and `TailorWorkflowAPI` now describe the SDK wrapper objects. Code that types the platform-provided `globalThis.tailor.context` or `globalThis.tailor.workflow` objects directly must use `PlatformContextAPI` or `PlatformWorkflowAPI` instead.
|
|
44
|
+
|
|
3
45
|
## 0.3.0-next.3
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
|
@@ -152,6 +194,18 @@
|
|
|
152
194
|
|
|
153
195
|
- [#1435](https://github.com/tailor-platform/sdk/pull/1435) [`49c0cc9`](https://github.com/tailor-platform/sdk/commit/49c0cc99171d7e317a50a18804a21067d89f9493) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/plugin-cli-import` codemod so `tailor-sdk upgrade` rewrites deprecated plugin imports from `@tailor-platform/sdk/cli` (`kyselyTypePlugin`, `enumConstantsPlugin`, `fileUtilsPlugin`, `seedPlugin`) to their dedicated `@tailor-platform/sdk/plugin/*` subpaths, splitting any non-plugin specifiers onto a separate import.
|
|
154
196
|
|
|
197
|
+
## 0.3.7
|
|
198
|
+
|
|
199
|
+
### Patch Changes
|
|
200
|
+
|
|
201
|
+
- [#1744](https://github.com/tailor-platform/sdk/pull/1744) [`3ca5b48`](https://github.com/tailor-platform/sdk/commit/3ca5b48fca4e8e1f0878350afc11d42d36bf3cab) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency politty to v0.11.2
|
|
202
|
+
|
|
203
|
+
## 0.3.6
|
|
204
|
+
|
|
205
|
+
### Patch Changes
|
|
206
|
+
|
|
207
|
+
- [#1688](https://github.com/tailor-platform/sdk/pull/1688) [`7f67996`](https://github.com/tailor-platform/sdk/commit/7f679963adc1df438e49200f1170415629817f44) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency @ast-grep/napi to v0.44.1
|
|
208
|
+
|
|
155
209
|
## 0.3.5
|
|
156
210
|
|
|
157
211
|
### Patch Changes
|
|
@@ -168,4 +168,4 @@ function localDeclarationNames(root) {
|
|
|
168
168
|
return names;
|
|
169
169
|
}
|
|
170
170
|
//#endregion
|
|
171
|
-
export {
|
|
171
|
+
export { importSource as a, localDeclarationNames as c, importBindings as i, stringValue as l, collectBindingNames as n, importSpecNames as o, findImportStatements as r, isTypeOnlyImport as s, buildAddNamedImportEdit as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as importSource, c as localDeclarationNames, i as importBindings, o as importSpecNames, r as findImportStatements, s as isTypeOnlyImport, t as buildAddNamedImportEdit } from "../../../ast-grep-helpers-CXtWn3RB.js";
|
|
2
2
|
import { Lang, parse } from "@ast-grep/napi";
|
|
3
3
|
//#region codemods/v2/auth-connection-token-helper/scripts/transform.ts
|
|
4
4
|
const RUNTIME_MODULE = "@tailor-platform/sdk/runtime";
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { a as importSource, i as importBindings, r as findImportStatements } from "../../../ast-grep-helpers-CXtWn3RB.js";
|
|
2
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
3
|
+
//#region codemods/v2/db-type-to-table/scripts/transform.ts
|
|
4
|
+
const SDK_MODULE = "@tailor-platform/sdk";
|
|
5
|
+
function sourceLang(filePath, source) {
|
|
6
|
+
return filePath.endsWith(".tsx") || filePath.endsWith(".jsx") || source.includes("</") ? Lang.Tsx : Lang.TypeScript;
|
|
7
|
+
}
|
|
8
|
+
function namespaceImportNames(importStmt) {
|
|
9
|
+
return importStmt.findAll({ rule: { kind: "namespace_import" } }).flatMap((node) => node.children().filter((child) => child.kind() === "identifier")).map((node) => node.text());
|
|
10
|
+
}
|
|
11
|
+
function isInsideImportStatement(node) {
|
|
12
|
+
let current = node.parent();
|
|
13
|
+
while (current) {
|
|
14
|
+
if (current.kind() === "import_statement") return true;
|
|
15
|
+
current = current.parent();
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function isBindingLeafKind(kind) {
|
|
20
|
+
return kind === "identifier" || kind === "shorthand_property_identifier_pattern";
|
|
21
|
+
}
|
|
22
|
+
function isBindingPatternKind(kind) {
|
|
23
|
+
return isBindingLeafKind(kind) || kind === "object_pattern" || kind === "array_pattern" || kind === "rest_pattern";
|
|
24
|
+
}
|
|
25
|
+
function collectBindingNodes(node, names, result) {
|
|
26
|
+
if (isBindingLeafKind(node.kind())) {
|
|
27
|
+
if (names.has(node.text())) result.push(node);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const child of node.children()) {
|
|
31
|
+
if (child.kind() === "property_identifier") continue;
|
|
32
|
+
if (child.kind() === "=") break;
|
|
33
|
+
collectBindingNodes(child, names, result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function bindingNodes(node, names) {
|
|
37
|
+
const result = [];
|
|
38
|
+
collectBindingNodes(node, names, result);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function directBindingNodes(node, names) {
|
|
42
|
+
const result = [];
|
|
43
|
+
for (const child of node.children()) {
|
|
44
|
+
if (child.kind() === "=") break;
|
|
45
|
+
if (isBindingPatternKind(child.kind())) collectBindingNodes(child, names, result);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function firstDeclaratorChild(node) {
|
|
50
|
+
return node.children().find((child) => child.kind() !== "=") ?? null;
|
|
51
|
+
}
|
|
52
|
+
function declaratorValue(node) {
|
|
53
|
+
const children = node.children();
|
|
54
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
55
|
+
if (equalsIndex === -1) return null;
|
|
56
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
57
|
+
}
|
|
58
|
+
function assignmentTarget(node) {
|
|
59
|
+
const children = node.children();
|
|
60
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
61
|
+
if (equalsIndex === -1) return null;
|
|
62
|
+
return children.slice(0, equalsIndex).find((child) => child.kind() !== "comment") ?? null;
|
|
63
|
+
}
|
|
64
|
+
function assignmentValue(node) {
|
|
65
|
+
const children = node.children();
|
|
66
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
67
|
+
if (equalsIndex === -1) return null;
|
|
68
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
69
|
+
}
|
|
70
|
+
function parameterDefaultTarget(node) {
|
|
71
|
+
const children = node.children();
|
|
72
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
73
|
+
if (equalsIndex === -1) return null;
|
|
74
|
+
return children.slice(0, equalsIndex).find((child) => isBindingPatternKind(child.kind())) ?? null;
|
|
75
|
+
}
|
|
76
|
+
function parameterDefaultValue(node) {
|
|
77
|
+
const children = node.children();
|
|
78
|
+
const equalsIndex = children.findIndex((child) => child.kind() === "=");
|
|
79
|
+
if (equalsIndex === -1) return null;
|
|
80
|
+
return children.slice(equalsIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
81
|
+
}
|
|
82
|
+
function addShadowedRange(shadowedRanges, name, scopeNode) {
|
|
83
|
+
const range = scopeNode.range();
|
|
84
|
+
if (!shadowedRanges.has(name)) shadowedRanges.set(name, []);
|
|
85
|
+
shadowedRanges.get(name).push({
|
|
86
|
+
start: range.start.index,
|
|
87
|
+
end: range.end.index
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function nearestScope(node) {
|
|
91
|
+
let current = node.parent();
|
|
92
|
+
while (current) {
|
|
93
|
+
const kind = current.kind();
|
|
94
|
+
if (kind === "statement_block" || kind === "program" || kind === "switch_body" || kind === "for_statement" || kind === "for_in_statement") return current;
|
|
95
|
+
current = current.parent();
|
|
96
|
+
}
|
|
97
|
+
return node;
|
|
98
|
+
}
|
|
99
|
+
function functionScope(node) {
|
|
100
|
+
let current = node.parent();
|
|
101
|
+
while (current) {
|
|
102
|
+
const kind = current.kind();
|
|
103
|
+
if (kind === "function_declaration" || kind === "function_expression" || kind === "arrow_function" || kind === "method_definition" || kind === "program") return current;
|
|
104
|
+
current = current.parent();
|
|
105
|
+
}
|
|
106
|
+
return node;
|
|
107
|
+
}
|
|
108
|
+
function variableDeclarationScope(node) {
|
|
109
|
+
const declaration = node.parent();
|
|
110
|
+
if (/^var\b/.test(declaration?.text().trimStart() ?? "")) return functionScope(node);
|
|
111
|
+
return nearestScope(node);
|
|
112
|
+
}
|
|
113
|
+
function parameterScope(node) {
|
|
114
|
+
let current = node.parent();
|
|
115
|
+
while (current) {
|
|
116
|
+
const kind = current.kind();
|
|
117
|
+
if (kind === "formal_parameters") {
|
|
118
|
+
current = current.parent();
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (kind === "function_declaration" || kind === "function_expression" || kind === "arrow_function" || kind === "method_definition") return current;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return nearestScope(node);
|
|
125
|
+
}
|
|
126
|
+
function buildShadowedRanges(root, names) {
|
|
127
|
+
const shadowedRanges = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
129
|
+
if (isInsideImportStatement(decl)) continue;
|
|
130
|
+
const binding = firstDeclaratorChild(decl);
|
|
131
|
+
if (!binding) continue;
|
|
132
|
+
for (const name of bindingNodes(binding, names)) addShadowedRange(shadowedRanges, name.text(), variableDeclarationScope(decl));
|
|
133
|
+
}
|
|
134
|
+
for (const decl of root.findAll({ rule: { any: [
|
|
135
|
+
{ kind: "function_declaration" },
|
|
136
|
+
{ kind: "class_declaration" },
|
|
137
|
+
{ kind: "enum_declaration" }
|
|
138
|
+
] } })) {
|
|
139
|
+
const name = decl.children().find((child) => child.kind() === "identifier" && names.has(child.text()));
|
|
140
|
+
if (name) addShadowedRange(shadowedRanges, name.text(), nearestScope(decl));
|
|
141
|
+
}
|
|
142
|
+
for (const param of root.findAll({ rule: { any: [{ kind: "required_parameter" }, { kind: "optional_parameter" }] } })) for (const name of directBindingNodes(param, names)) addShadowedRange(shadowedRanges, name.text(), parameterScope(param));
|
|
143
|
+
for (const arrow of root.findAll({ rule: { kind: "arrow_function" } })) {
|
|
144
|
+
const children = arrow.children();
|
|
145
|
+
const arrowIndex = children.findIndex((child) => child.kind() === "=>");
|
|
146
|
+
if (arrowIndex === -1) continue;
|
|
147
|
+
for (const child of children.slice(0, arrowIndex)) {
|
|
148
|
+
if (child.kind() === "=") break;
|
|
149
|
+
if (!isBindingPatternKind(child.kind())) continue;
|
|
150
|
+
for (const name of bindingNodes(child, names)) addShadowedRange(shadowedRanges, name.text(), arrow);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const catchClause of root.findAll({ rule: { kind: "catch_clause" } })) for (const name of directBindingNodes(catchClause, names)) addShadowedRange(shadowedRanges, name.text(), catchClause);
|
|
154
|
+
for (const loop of root.findAll({ rule: { kind: "for_in_statement" } })) {
|
|
155
|
+
const children = loop.children();
|
|
156
|
+
const keywordIndex = children.findIndex((child) => child.kind() === "in" || child.kind() === "of");
|
|
157
|
+
if (keywordIndex === -1) continue;
|
|
158
|
+
for (const child of children.slice(0, keywordIndex)) for (const name of bindingNodes(child, names)) addShadowedRange(shadowedRanges, name.text(), loop);
|
|
159
|
+
}
|
|
160
|
+
return shadowedRanges;
|
|
161
|
+
}
|
|
162
|
+
function isShadowed(node, shadowedRanges) {
|
|
163
|
+
const ranges = shadowedRanges.get(node.text());
|
|
164
|
+
if (!ranges) return false;
|
|
165
|
+
const position = node.range().start.index;
|
|
166
|
+
return ranges.some((range) => position >= range.start && position < range.end);
|
|
167
|
+
}
|
|
168
|
+
function unwrapExpression(node) {
|
|
169
|
+
let current = node;
|
|
170
|
+
while (current) {
|
|
171
|
+
const kind = current.kind();
|
|
172
|
+
if (kind === "parenthesized_expression") {
|
|
173
|
+
current = current.children().find((child) => child.kind() !== "(" && child.kind() !== ")") ?? null;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (kind === "as_expression" || kind === "satisfies_expression" || kind === "non_null_expression") {
|
|
177
|
+
current = current.children()[0] ?? null;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (kind === "type_assertion") {
|
|
181
|
+
current = current.children().find((child) => child.kind() !== "type_arguments") ?? null;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
return current;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function isSdkDbMember(object, dbNames, namespaceNames, shadowedRanges) {
|
|
189
|
+
const unwrapped = unwrapExpression(object);
|
|
190
|
+
if (!unwrapped) return false;
|
|
191
|
+
if (unwrapped.kind() === "identifier") return dbNames.has(unwrapped.text()) && !isShadowed(unwrapped, shadowedRanges);
|
|
192
|
+
if (unwrapped.kind() !== "member_expression") return false;
|
|
193
|
+
const base = unwrapExpression(unwrapped.field("object"));
|
|
194
|
+
const property = memberProperty(unwrapped);
|
|
195
|
+
return base?.kind() === "identifier" && namespaceNames.has(base.text()) && !isShadowed(base, shadowedRanges) && property?.text() === "db";
|
|
196
|
+
}
|
|
197
|
+
function memberProperty(member) {
|
|
198
|
+
const children = member.children();
|
|
199
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
200
|
+
const child = children[index];
|
|
201
|
+
if (child.kind() === "property_identifier") return child;
|
|
202
|
+
}
|
|
203
|
+
return member.field("property");
|
|
204
|
+
}
|
|
205
|
+
function typeStringLiteral(node) {
|
|
206
|
+
if (!node) return null;
|
|
207
|
+
const kind = node.kind();
|
|
208
|
+
if (kind !== "string" && kind !== "template_string") return null;
|
|
209
|
+
const fragments = node.children().filter((child) => child.kind() === "string_fragment");
|
|
210
|
+
return fragments.length === 1 && fragments[0].text() === "type" ? node : null;
|
|
211
|
+
}
|
|
212
|
+
function replaceStringLiteralValue(node, value) {
|
|
213
|
+
const text = node.text();
|
|
214
|
+
const quote = text.startsWith("'") ? "'" : text.startsWith("`") ? "`" : "\"";
|
|
215
|
+
return node.replace(`${quote}${value}${quote}`);
|
|
216
|
+
}
|
|
217
|
+
function hasTypeBuilderUse(root, name, afterIndex) {
|
|
218
|
+
for (const member of root.findAll({ rule: { kind: "member_expression" } })) {
|
|
219
|
+
if (member.range().start.index <= afterIndex) continue;
|
|
220
|
+
if (memberProperty(member)?.text() !== "type") continue;
|
|
221
|
+
const object = unwrapExpression(member.field("object"));
|
|
222
|
+
if (object?.kind() === "identifier" && object.text() === name) return true;
|
|
223
|
+
}
|
|
224
|
+
for (const subscript of root.findAll({ rule: { kind: "subscript_expression" } })) {
|
|
225
|
+
if (subscript.range().start.index <= afterIndex) continue;
|
|
226
|
+
if (!typeStringLiteral(subscript.field("index"))) continue;
|
|
227
|
+
const object = unwrapExpression(subscript.field("object"));
|
|
228
|
+
if (object?.kind() === "identifier" && object.text() === name) return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
function transform(source, filePath) {
|
|
233
|
+
if (!source.includes("type") || !source.includes(SDK_MODULE)) return null;
|
|
234
|
+
let root;
|
|
235
|
+
try {
|
|
236
|
+
root = parse(sourceLang(filePath, source), source).root();
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const imports = findImportStatements(root).filter((importStmt) => importSource(importStmt) === SDK_MODULE);
|
|
241
|
+
if (imports.length === 0) return null;
|
|
242
|
+
const dbNames = /* @__PURE__ */ new Set();
|
|
243
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
244
|
+
for (const importStmt of imports) {
|
|
245
|
+
for (const binding of importBindings(importStmt)) if (binding.importedName === "db") dbNames.add(binding.localName);
|
|
246
|
+
for (const name of namespaceImportNames(importStmt)) namespaceNames.add(name);
|
|
247
|
+
}
|
|
248
|
+
if (dbNames.size === 0 && namespaceNames.size === 0) return null;
|
|
249
|
+
const shadowedRanges = buildShadowedRanges(root, /* @__PURE__ */ new Set([...dbNames, ...namespaceNames]));
|
|
250
|
+
const edits = [];
|
|
251
|
+
for (const member of root.findAll({ rule: { kind: "member_expression" } })) {
|
|
252
|
+
const property = memberProperty(member);
|
|
253
|
+
if (property?.text() !== "type") continue;
|
|
254
|
+
if (!isSdkDbMember(member.field("object"), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
255
|
+
edits.push(property.replace("table"));
|
|
256
|
+
}
|
|
257
|
+
for (const subscript of root.findAll({ rule: { kind: "subscript_expression" } })) {
|
|
258
|
+
const index = typeStringLiteral(subscript.field("index"));
|
|
259
|
+
if (!index) continue;
|
|
260
|
+
if (!isSdkDbMember(subscript.field("object"), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
261
|
+
edits.push(replaceStringLiteralValue(index, "table"));
|
|
262
|
+
}
|
|
263
|
+
return edits.length > 0 ? root.commitEdits(edits) : null;
|
|
264
|
+
}
|
|
265
|
+
function lineForIndex(source, index) {
|
|
266
|
+
return source.slice(0, index).split(/\r\n|\r|\n/).length;
|
|
267
|
+
}
|
|
268
|
+
function excerptAtIndex(source, index) {
|
|
269
|
+
const lineStart = Math.max(source.lastIndexOf("\n", index - 1) + 1, 0);
|
|
270
|
+
const lineEnd = source.indexOf("\n", index);
|
|
271
|
+
return source.slice(lineStart, lineEnd === -1 ? source.length : lineEnd).trim();
|
|
272
|
+
}
|
|
273
|
+
function objectPatternHasTypeProperty(pattern) {
|
|
274
|
+
return pattern.findAll({ rule: { any: [{
|
|
275
|
+
kind: "property_identifier",
|
|
276
|
+
regex: "^type$"
|
|
277
|
+
}, {
|
|
278
|
+
kind: "shorthand_property_identifier_pattern",
|
|
279
|
+
regex: "^type$"
|
|
280
|
+
}] } }).some((node) => node.text() === "type");
|
|
281
|
+
}
|
|
282
|
+
function namespaceDbAliasBindings(pattern) {
|
|
283
|
+
const aliases = [];
|
|
284
|
+
for (const child of pattern.children()) {
|
|
285
|
+
if (child.kind() === "shorthand_property_identifier_pattern" && child.text() === "db") {
|
|
286
|
+
aliases.push(child);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (child.kind() !== "pair_pattern") continue;
|
|
290
|
+
if (child.field("key")?.text() !== "db") continue;
|
|
291
|
+
const value = child.field("value");
|
|
292
|
+
if (value?.kind() === "identifier") aliases.push(value);
|
|
293
|
+
}
|
|
294
|
+
return aliases;
|
|
295
|
+
}
|
|
296
|
+
function isSdkNamespaceMember(node, namespaceNames, shadowedRanges) {
|
|
297
|
+
const unwrapped = unwrapExpression(node);
|
|
298
|
+
return unwrapped?.kind() === "identifier" && namespaceNames.has(unwrapped.text()) && !isShadowed(unwrapped, shadowedRanges);
|
|
299
|
+
}
|
|
300
|
+
function reviewFindings(source, filePath, relativePath) {
|
|
301
|
+
if (!source.includes("type") || !source.includes(SDK_MODULE)) return [];
|
|
302
|
+
let root;
|
|
303
|
+
try {
|
|
304
|
+
root = parse(sourceLang(filePath, source), source).root();
|
|
305
|
+
} catch {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const imports = findImportStatements(root).filter((importStmt) => importSource(importStmt) === SDK_MODULE);
|
|
309
|
+
if (imports.length === 0) return [];
|
|
310
|
+
const dbNames = /* @__PURE__ */ new Set();
|
|
311
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
312
|
+
for (const importStmt of imports) {
|
|
313
|
+
for (const binding of importBindings(importStmt)) if (binding.importedName === "db") dbNames.add(binding.localName);
|
|
314
|
+
for (const name of namespaceImportNames(importStmt)) namespaceNames.add(name);
|
|
315
|
+
}
|
|
316
|
+
if (dbNames.size === 0 && namespaceNames.size === 0) return [];
|
|
317
|
+
const shadowedRanges = buildShadowedRanges(root, /* @__PURE__ */ new Set([...dbNames, ...namespaceNames]));
|
|
318
|
+
const findings = [];
|
|
319
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
320
|
+
const binding = firstDeclaratorChild(decl);
|
|
321
|
+
if (binding?.kind() !== "object_pattern" || !objectPatternHasTypeProperty(binding)) continue;
|
|
322
|
+
if (!isSdkDbMember(declaratorValue(decl), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
323
|
+
findings.push({
|
|
324
|
+
file: relativePath,
|
|
325
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
326
|
+
message: "Review destructured db.type builder usage and migrate it to db.table.",
|
|
327
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
331
|
+
const binding = firstDeclaratorChild(decl);
|
|
332
|
+
if (binding?.kind() !== "object_pattern") continue;
|
|
333
|
+
if (!isSdkNamespaceMember(declaratorValue(decl), namespaceNames, shadowedRanges)) continue;
|
|
334
|
+
for (const alias of namespaceDbAliasBindings(binding)) {
|
|
335
|
+
if (!hasTypeBuilderUse(root, alias.text(), decl.range().end.index)) continue;
|
|
336
|
+
findings.push({
|
|
337
|
+
file: relativePath,
|
|
338
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
339
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
340
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const decl of root.findAll({ rule: { kind: "variable_declarator" } })) {
|
|
345
|
+
const binding = firstDeclaratorChild(decl);
|
|
346
|
+
if (binding?.kind() !== "identifier") continue;
|
|
347
|
+
if (!isSdkDbMember(declaratorValue(decl), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
348
|
+
if (!hasTypeBuilderUse(root, binding.text(), decl.range().end.index)) continue;
|
|
349
|
+
findings.push({
|
|
350
|
+
file: relativePath,
|
|
351
|
+
line: lineForIndex(source, binding.range().start.index),
|
|
352
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
353
|
+
excerpt: excerptAtIndex(source, binding.range().start.index)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
for (const assignment of root.findAll({ rule: { kind: "assignment_expression" } })) {
|
|
357
|
+
const target = assignmentTarget(assignment);
|
|
358
|
+
if (target?.kind() !== "identifier") continue;
|
|
359
|
+
if (!isSdkDbMember(assignmentValue(assignment), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
360
|
+
if (!hasTypeBuilderUse(root, target.text(), assignment.range().end.index)) continue;
|
|
361
|
+
findings.push({
|
|
362
|
+
file: relativePath,
|
|
363
|
+
line: lineForIndex(source, assignment.range().start.index),
|
|
364
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
365
|
+
excerpt: excerptAtIndex(source, assignment.range().start.index)
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
for (const param of root.findAll({ rule: { any: [{ kind: "required_parameter" }, { kind: "optional_parameter" }] } })) {
|
|
369
|
+
const target = parameterDefaultTarget(param);
|
|
370
|
+
if (target?.kind() !== "identifier") continue;
|
|
371
|
+
if (!isSdkDbMember(parameterDefaultValue(param), dbNames, namespaceNames, shadowedRanges)) continue;
|
|
372
|
+
if (!hasTypeBuilderUse(root, target.text(), param.range().end.index)) continue;
|
|
373
|
+
findings.push({
|
|
374
|
+
file: relativePath,
|
|
375
|
+
line: lineForIndex(source, param.range().start.index),
|
|
376
|
+
message: "Review SDK db alias usage and migrate db.type builder calls to db.table.",
|
|
377
|
+
excerpt: excerptAtIndex(source, param.range().start.index)
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return findings;
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
export { transform as default, reviewFindings };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { l as stringValue } from "../../../ast-grep-helpers-CXtWn3RB.js";
|
|
2
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
3
|
+
//#region codemods/v2/forward-relation-name/scripts/transform.ts
|
|
4
|
+
function sourceLang(filePath, source) {
|
|
5
|
+
const lowerPath = filePath.toLowerCase();
|
|
6
|
+
if (/\.(?:ts|mts|cts)$/u.test(lowerPath)) return Lang.TypeScript;
|
|
7
|
+
if (/\.(?:tsx|jsx|js)$/u.test(lowerPath)) return Lang.Tsx;
|
|
8
|
+
return source.includes("</") ? Lang.Tsx : Lang.TypeScript;
|
|
9
|
+
}
|
|
10
|
+
function isRelationCall(call, aliases) {
|
|
11
|
+
const callee = call.children()[0];
|
|
12
|
+
if (!callee) return false;
|
|
13
|
+
if (callee.kind() === "identifier") return aliases.has(callee.text());
|
|
14
|
+
if (callee.kind() === "subscript_expression") {
|
|
15
|
+
const property = literalStringValue(callee.field("index"));
|
|
16
|
+
return property === null || property === "relation";
|
|
17
|
+
}
|
|
18
|
+
if (callee.kind() !== "member_expression") return false;
|
|
19
|
+
return callee.children().findLast((child) => child.kind() === "property_identifier" || child.kind() === "identifier")?.text() === "relation";
|
|
20
|
+
}
|
|
21
|
+
function relationBindingName(pattern) {
|
|
22
|
+
if (pattern.kind() !== "object_pattern") return null;
|
|
23
|
+
for (const child of pattern.children()) {
|
|
24
|
+
if (child.kind() === "shorthand_property_identifier_pattern" && child.text() === "relation") return child.text();
|
|
25
|
+
if (child.kind() === "pair_pattern" && stringValue(child.field("key")) === "relation") {
|
|
26
|
+
const value = child.field("value");
|
|
27
|
+
return value?.kind() === "identifier" ? value.text() : null;
|
|
28
|
+
}
|
|
29
|
+
if (child.kind() === "object_assignment_pattern") {
|
|
30
|
+
const binding = child.children().find((node) => node.kind() === "shorthand_property_identifier_pattern");
|
|
31
|
+
if (binding?.text() === "relation") return binding.text();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function relationAliases(root) {
|
|
37
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
38
|
+
for (const pattern of root.findAll({ rule: { kind: "object_pattern" } })) {
|
|
39
|
+
const name = relationBindingName(pattern);
|
|
40
|
+
if (name) aliases.add(name);
|
|
41
|
+
}
|
|
42
|
+
return aliases;
|
|
43
|
+
}
|
|
44
|
+
function callArgument(call) {
|
|
45
|
+
const args = call.children().find((child) => child.kind() === "arguments");
|
|
46
|
+
if (!args) return null;
|
|
47
|
+
const values = args.children().filter((child) => {
|
|
48
|
+
const kind = child.kind();
|
|
49
|
+
return kind !== "(" && kind !== ")" && kind !== "," && kind !== "comment";
|
|
50
|
+
});
|
|
51
|
+
return values.length === 1 ? values[0] : null;
|
|
52
|
+
}
|
|
53
|
+
function pairKey(pair) {
|
|
54
|
+
const key = pair.children()[0];
|
|
55
|
+
return stringValue(key ?? null);
|
|
56
|
+
}
|
|
57
|
+
function pairValue(pair) {
|
|
58
|
+
const children = pair.children();
|
|
59
|
+
const colonIndex = children.findIndex((child) => child.kind() === ":");
|
|
60
|
+
if (colonIndex === -1) return null;
|
|
61
|
+
return children.slice(colonIndex + 1).find((child) => child.kind() !== "comment") ?? null;
|
|
62
|
+
}
|
|
63
|
+
function objectPair(object, key) {
|
|
64
|
+
return object.children().find((child) => child.kind() === "pair" && pairKey(child) === key) ?? null;
|
|
65
|
+
}
|
|
66
|
+
function literalStringValue(node) {
|
|
67
|
+
if (node?.kind() !== "string") return null;
|
|
68
|
+
return stringValue(node);
|
|
69
|
+
}
|
|
70
|
+
function hasDynamicProperties(object) {
|
|
71
|
+
return object.children().some((child) => {
|
|
72
|
+
const kind = child.kind();
|
|
73
|
+
if (kind === "{" || kind === "}" || kind === "," || kind === "comment") return false;
|
|
74
|
+
if (kind !== "pair") return true;
|
|
75
|
+
const keyKind = child.children()[0]?.kind();
|
|
76
|
+
return keyKind !== "property_identifier" && keyKind !== "string";
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function needsReview(call) {
|
|
80
|
+
const config = callArgument(call);
|
|
81
|
+
if (config?.kind() !== "object") return config != null;
|
|
82
|
+
if (hasDynamicProperties(config)) return true;
|
|
83
|
+
const relationType = objectPair(config, "type");
|
|
84
|
+
const toward = objectPair(config, "toward");
|
|
85
|
+
if (!relationType || !toward) return false;
|
|
86
|
+
if (literalStringValue(pairValue(relationType)) === "keyOnly") return false;
|
|
87
|
+
const towardConfig = pairValue(toward);
|
|
88
|
+
if (towardConfig?.kind() !== "object") return towardConfig != null;
|
|
89
|
+
if (hasDynamicProperties(towardConfig)) return true;
|
|
90
|
+
const targetType = objectPair(towardConfig, "type");
|
|
91
|
+
if (targetType && literalStringValue(pairValue(targetType)) === "self") return false;
|
|
92
|
+
const as = objectPair(towardConfig, "as");
|
|
93
|
+
if (as) {
|
|
94
|
+
const explicitName = literalStringValue(pairValue(as));
|
|
95
|
+
return explicitName === null || explicitName.length === 0;
|
|
96
|
+
}
|
|
97
|
+
if (!targetType) return false;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function transform(_source, _filePath) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function reviewFindings(source, filePath, relativePath) {
|
|
104
|
+
if (!source.includes("relation")) return [];
|
|
105
|
+
const root = parse(sourceLang(filePath, source), source).root();
|
|
106
|
+
const aliases = relationAliases(root);
|
|
107
|
+
return root.findAll({ rule: { kind: "call_expression" } }).filter((call) => isRelationCall(call, aliases) && needsReview(call)).map((call) => ({
|
|
108
|
+
file: relativePath,
|
|
109
|
+
line: call.range().start.line + 1,
|
|
110
|
+
message: "Review the v2 forward GraphQL field name or add an explicit toward.as.",
|
|
111
|
+
excerpt: call.text().split("\n", 1)[0].trim()
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { transform as default, reviewFindings };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as localDeclarationNames, i as importBindings, r as findImportStatements, t as buildAddNamedImportEdit } from "../../../ast-grep-helpers-CXtWn3RB.js";
|
|
2
2
|
import { Lang, parse } from "@ast-grep/napi";
|
|
3
3
|
//#region codemods/v2/runtime-globals-opt-in/scripts/transform.ts
|
|
4
4
|
const RUNTIME_MODULE = "@tailor-platform/sdk/runtime";
|