modern-cmdk 1.3.0 → 1.5.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/LICENSE +1 -1
- package/README.md +76 -172
- package/THIRD-PARTY-NOTICES.md +303 -0
- package/dist/codemod/bin.mjs +14 -0
- package/dist/codemod/chunk-jscodeshift.mjs +36210 -0
- package/dist/codemod/chunk-print-options.mjs +43 -0
- package/dist/codemod/chunk-worker.mjs +476 -0
- package/dist/codemod/cli.mjs +298 -43659
- package/dist/codemod/transforms/data-attrs.mjs +551 -38
- package/dist/codemod/transforms/forward-ref.mjs +197 -32
- package/dist/codemod/transforms/import-rewrite.mjs +389 -30
- package/dist/codemod/transforms/should-filter.mjs +199 -10
- package/dist/codemod/worker.mjs +1 -0
- package/dist/core/index.d.mts +6 -1
- package/dist/core/index.mjs +12 -4
- package/dist/react/chunk-dialog.mjs +237 -0
- package/dist/react/dialog.d.mts +18 -0
- package/dist/react/dialog.mjs +2 -0
- package/dist/react/index.d.mts +26 -334
- package/dist/react/index.mjs +54 -1377
- package/dist/react/styles.css +2 -2
- package/package.json +12 -18
- package/dist/codemod/cli.d.mts +0 -16
- package/dist/codemod/transforms/data-attrs.d.mts +0 -6
- package/dist/codemod/transforms/forward-ref.d.mts +0 -6
- package/dist/codemod/transforms/import-rewrite.d.mts +0 -6
- package/dist/codemod/transforms/should-filter.d.mts +0 -5
|
@@ -1,42 +1,401 @@
|
|
|
1
|
-
|
|
1
|
+
import { t as printOptions } from "../chunk-print-options.mjs";
|
|
2
|
+
// ../modern-cmdk-codemod/src/transforms/import-rewrite.ts
|
|
2
3
|
const OLD_PACKAGE = "cmdk";
|
|
3
4
|
const NEW_PACKAGE = "modern-cmdk/react";
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* The names modern-cmdk/react exports: the value exports and the `export type` names of
|
|
7
|
+
* src/react/index.ts. tests/unit/codemod-import-rewrite.test.ts compares this set to that file,
|
|
8
|
+
* so it fails when the library changes.
|
|
9
|
+
*/
|
|
10
|
+
const KNOWN_EXPORTS = /* @__PURE__ */ new Set([
|
|
11
|
+
"ActivityMode",
|
|
12
|
+
"Command",
|
|
13
|
+
"CommandActivity",
|
|
14
|
+
"CommandActivityProps",
|
|
15
|
+
"CommandAsyncItems",
|
|
16
|
+
"CommandAsyncItemsProps",
|
|
17
|
+
"CommandBadge",
|
|
18
|
+
"CommandBadgeProps",
|
|
19
|
+
"CommandContextValue",
|
|
20
|
+
"CommandDialog",
|
|
21
|
+
"CommandDialogProps",
|
|
22
|
+
"CommandEmpty",
|
|
23
|
+
"CommandEmptyProps",
|
|
24
|
+
"CommandErrorBoundary",
|
|
25
|
+
"CommandErrorBoundaryProps",
|
|
26
|
+
"CommandGroup",
|
|
27
|
+
"CommandGroupContextValue",
|
|
28
|
+
"CommandGroupProps",
|
|
29
|
+
"CommandHighlight",
|
|
30
|
+
"CommandHighlightProps",
|
|
31
|
+
"CommandInput",
|
|
32
|
+
"CommandInputProps",
|
|
33
|
+
"CommandItem",
|
|
34
|
+
"CommandItemProps",
|
|
35
|
+
"CommandList",
|
|
36
|
+
"CommandListProps",
|
|
37
|
+
"CommandLoading",
|
|
38
|
+
"CommandLoadingProps",
|
|
39
|
+
"CommandPage",
|
|
40
|
+
"CommandPageId",
|
|
41
|
+
"CommandPageProps",
|
|
42
|
+
"CommandRootId",
|
|
43
|
+
"CommandRootProps",
|
|
44
|
+
"CommandSeparator",
|
|
45
|
+
"CommandSeparatorProps",
|
|
46
|
+
"CommandShortcut",
|
|
47
|
+
"CommandShortcutProps",
|
|
48
|
+
"CommandStableContext",
|
|
49
|
+
"CommandStableContextValue",
|
|
50
|
+
"CommandStateContext",
|
|
51
|
+
"CommandStateContextValue",
|
|
52
|
+
"RegisterGroupOptions",
|
|
53
|
+
"RegisterItemOptions",
|
|
54
|
+
"UseCommandReturn",
|
|
55
|
+
"VirtualItem",
|
|
56
|
+
"VirtualizerOptions",
|
|
57
|
+
"VirtualizerReturn",
|
|
58
|
+
"createKeydownHandler",
|
|
59
|
+
"useCommand",
|
|
60
|
+
"useCommandDevtools",
|
|
61
|
+
"useCommandState",
|
|
62
|
+
"useRegisterGroup",
|
|
63
|
+
"useRegisterItem",
|
|
64
|
+
"useVirtualizer"
|
|
65
|
+
]);
|
|
66
|
+
/**
|
|
67
|
+
* cmdk names that modern-cmdk/react exports under another name. cmdk's d.ts ends with
|
|
68
|
+
* `export { pkg as Command, …, Command as CommandRoot, … }`: both are the root component. cmdk has
|
|
69
|
+
* no default export; a default import (and `default as X`) is read as the root too.
|
|
70
|
+
*/
|
|
71
|
+
const RENAMED = /* @__PURE__ */ new Map([["CommandRoot", "Command"], ["default", "Command"]]);
|
|
72
|
+
/** cmdk exports that have no modern-cmdk/react counterpart under the same name. */
|
|
73
|
+
const CMDK_ONLY_NAMES = /* @__PURE__ */ new Set(["CommandRoot", "defaultFilter"]);
|
|
74
|
+
const MOCK_OBJECTS = /* @__PURE__ */ new Set(["vi", "jest"]);
|
|
75
|
+
const MOCK_METHODS = /* @__PURE__ */ new Set([
|
|
76
|
+
"mock",
|
|
77
|
+
"doMock",
|
|
78
|
+
"unmock",
|
|
79
|
+
"doUnmock",
|
|
80
|
+
"importActual",
|
|
81
|
+
"importMock",
|
|
82
|
+
"requireActual",
|
|
83
|
+
"requireMock"
|
|
84
|
+
]);
|
|
85
|
+
/** The modern-cmdk/react name for a cmdk name, or undefined when there is none. */
|
|
86
|
+
function mapName(name) {
|
|
87
|
+
const renamed = RENAMED.get(name);
|
|
88
|
+
if (renamed !== void 0) return renamed;
|
|
89
|
+
return KNOWN_EXPORTS.has(name) ? name : void 0;
|
|
90
|
+
}
|
|
91
|
+
/** A string literal node (Babel StringLiteral or ESTree Literal), or undefined. */
|
|
92
|
+
function asStringLiteral(node) {
|
|
93
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
94
|
+
const lit = node;
|
|
95
|
+
if (lit.type !== "StringLiteral" && lit.type !== "Literal") return void 0;
|
|
96
|
+
return typeof lit.value === "string" ? node : void 0;
|
|
97
|
+
}
|
|
98
|
+
/** A template literal with no expressions and one quasi (`cmdk`), or undefined. */
|
|
99
|
+
function asPlainTemplate(node) {
|
|
100
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
101
|
+
const t = node;
|
|
102
|
+
if (t.type !== "TemplateLiteral" || t.expressions?.length !== 0 || t.quasis?.length !== 1) return;
|
|
103
|
+
return node;
|
|
104
|
+
}
|
|
105
|
+
/** The text of a string-literal or plain-template module source, or undefined. */
|
|
106
|
+
function sourceText(node) {
|
|
107
|
+
const lit = asStringLiteral(node);
|
|
108
|
+
if (lit !== void 0) return lit.value;
|
|
109
|
+
return asPlainTemplate(node)?.quasis[0]?.value.cooked ?? void 0;
|
|
110
|
+
}
|
|
111
|
+
/** The name of an Identifier, JSXIdentifier or string-literal module-export name. */
|
|
112
|
+
function nameOf(node) {
|
|
113
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
114
|
+
const n = node;
|
|
115
|
+
if ((n.type === "Identifier" || n.type === "JSXIdentifier") && typeof n.name === "string") return n.name;
|
|
116
|
+
return asStringLiteral(node)?.value;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* jscodeshift transform — rewrites cmdk imports to modern-cmdk/react, checking every imported
|
|
120
|
+
* name against modern-cmdk/react's exports.
|
|
7
121
|
*/
|
|
8
122
|
function transform(fileInfo, api) {
|
|
123
|
+
if (!fileInfo.source.includes(OLD_PACKAGE)) return fileInfo.source;
|
|
9
124
|
const j = api.jscodeshift;
|
|
10
125
|
const root = j(fileInfo.source);
|
|
11
|
-
let
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
126
|
+
let changed = false;
|
|
127
|
+
let firstRaw;
|
|
128
|
+
const subpathReport = (specifier) => {
|
|
129
|
+
api.report(`'${specifier}' is a cmdk subpath with no modern-cmdk/react counterpart; left unchanged`);
|
|
130
|
+
};
|
|
131
|
+
/** Rewrite a module-specifier literal equal to 'cmdk'; report a 'cmdk/<subpath>' one. */
|
|
132
|
+
const rewriteSite = (node) => {
|
|
133
|
+
const text = sourceText(node);
|
|
134
|
+
if (text === void 0) return;
|
|
135
|
+
if (text === OLD_PACKAGE) {
|
|
136
|
+
const lit = asStringLiteral(node);
|
|
137
|
+
if (lit !== void 0) {
|
|
138
|
+
if (firstRaw === void 0) {
|
|
139
|
+
const raw = lit.extra?.raw ?? lit.raw;
|
|
140
|
+
if (typeof raw === "string") firstRaw = raw;
|
|
141
|
+
}
|
|
142
|
+
lit.value = NEW_PACKAGE;
|
|
143
|
+
} else {
|
|
144
|
+
const quasi = asPlainTemplate(node)?.quasis[0];
|
|
145
|
+
if (quasi === void 0) return;
|
|
146
|
+
quasi.value.raw = NEW_PACKAGE;
|
|
147
|
+
quasi.value.cooked = NEW_PACKAGE;
|
|
148
|
+
}
|
|
149
|
+
changed = true;
|
|
150
|
+
} else if (text.startsWith(`${OLD_PACKAGE}/`)) subpathReport(text);
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Replace a declaration with several. The new nodes carry no source location, so recast prints
|
|
154
|
+
* them on consecutive lines instead of copying the original's trailing blank line between them.
|
|
155
|
+
*/
|
|
156
|
+
const replaceWith = (path, nodes) => {
|
|
157
|
+
nodes[0].comments = path.node.comments;
|
|
158
|
+
path.replace(...nodes);
|
|
159
|
+
changed = true;
|
|
160
|
+
};
|
|
161
|
+
const isCmdkSource = (node) => sourceText(node) === OLD_PACKAGE;
|
|
162
|
+
const handleImport = (path) => {
|
|
163
|
+
const node = path.node;
|
|
164
|
+
if (!isCmdkSource(node.source)) {
|
|
165
|
+
rewriteSite(node.source);
|
|
166
|
+
return;
|
|
20
167
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
168
|
+
const mapped = [];
|
|
169
|
+
const unmapped = [];
|
|
170
|
+
let namespace;
|
|
171
|
+
let rebuilt = false;
|
|
172
|
+
for (const spec of node.specifiers ?? []) {
|
|
173
|
+
if (spec.type === "ImportNamespaceSpecifier") {
|
|
174
|
+
namespace = spec;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
178
|
+
const local = nameOf(spec.local) ?? "Command";
|
|
179
|
+
mapped.push(j.importSpecifier(j.identifier("Command"), j.identifier(local)));
|
|
180
|
+
rebuilt = true;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const imported = nameOf(spec.imported) ?? "";
|
|
184
|
+
const target = mapName(imported);
|
|
185
|
+
if (target === void 0) {
|
|
186
|
+
unmapped.push(spec);
|
|
187
|
+
api.report(`${imported} has no modern-cmdk/react export; left on the cmdk import`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (imported === "useCommandState") api.report(`useCommandState was rewritten, but its state is modern-cmdk's CommandState, not cmdk's; review the selector`);
|
|
191
|
+
if (target === imported) {
|
|
192
|
+
mapped.push(spec);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const renamed = j.importSpecifier(j.identifier(target), j.identifier(nameOf(spec.local) ?? imported));
|
|
196
|
+
const kind = spec.importKind;
|
|
197
|
+
if (kind !== void 0) renamed.importKind = kind;
|
|
198
|
+
mapped.push(renamed);
|
|
199
|
+
rebuilt = true;
|
|
31
200
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
201
|
+
if (namespace === void 0 && mapped.length === 0 && unmapped.length > 0) return;
|
|
202
|
+
rewriteSite(node.source);
|
|
203
|
+
if (namespace !== void 0) {
|
|
204
|
+
if (mapped.length > 0) replaceWith(path, [j.importDeclaration([namespace], j.stringLiteral(NEW_PACKAGE), node.importKind), j.importDeclaration(mapped, j.stringLiteral(NEW_PACKAGE), node.importKind)]);
|
|
205
|
+
const local = nameOf(namespace.local);
|
|
206
|
+
if (local !== void 0) reportNamespaceMembers(path, local, `the namespace import ${local}`);
|
|
207
|
+
} else if (unmapped.length > 0) replaceWith(path, [j.importDeclaration(mapped, j.stringLiteral(NEW_PACKAGE), node.importKind), j.importDeclaration(unmapped, j.stringLiteral(OLD_PACKAGE), node.importKind)]);
|
|
208
|
+
else if (rebuilt) node.specifiers = mapped;
|
|
209
|
+
};
|
|
210
|
+
/** Report each `K.name` member access, `<K.name>` JSX tag and `K.name` type reference on a namespace import whose name has no export. */
|
|
211
|
+
const reportNamespaceMembers = (importPath, local, via) => {
|
|
212
|
+
const declaredHere = importPath.scope?.lookup(local);
|
|
213
|
+
const note = (member, scope) => {
|
|
214
|
+
if (scope?.lookup(local) !== declaredHere) return;
|
|
215
|
+
const name = nameOf(member);
|
|
216
|
+
if (name === void 0 || KNOWN_EXPORTS.has(name)) return;
|
|
217
|
+
api.report(`${name} has no modern-cmdk/react export; used through ${via}`);
|
|
218
|
+
};
|
|
219
|
+
root.find(j.MemberExpression, { object: {
|
|
220
|
+
type: "Identifier",
|
|
221
|
+
name: local
|
|
222
|
+
} }).forEach((p) => {
|
|
223
|
+
if (!p.node.computed) note(p.node.property, p.scope);
|
|
224
|
+
});
|
|
225
|
+
root.find(j.TSQualifiedName, { left: {
|
|
226
|
+
type: "Identifier",
|
|
227
|
+
name: local
|
|
228
|
+
} }).forEach((p) => {
|
|
229
|
+
note(p.node.right, p.scope);
|
|
230
|
+
});
|
|
231
|
+
root.find(j.JSXMemberExpression, { object: {
|
|
232
|
+
type: "JSXIdentifier",
|
|
233
|
+
name: local
|
|
234
|
+
} }).forEach((p) => {
|
|
235
|
+
let tag = p;
|
|
236
|
+
while (tag.parent?.node.type === "JSXMemberExpression") tag = tag.parent;
|
|
237
|
+
if (tag.parent?.node.type === "JSXClosingElement") return;
|
|
238
|
+
note(p.node.property, p.scope);
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* For a require()/import()/requireActual() call on 'cmdk' that is bound to a variable (directly
|
|
243
|
+
* or through await), report destructured names and member reads that modern-cmdk/react lacks.
|
|
244
|
+
*/
|
|
245
|
+
const reportBoundMembers = (callPath) => {
|
|
246
|
+
let p = callPath.parent;
|
|
247
|
+
if (p?.node.type === "AwaitExpression") p = p.parent;
|
|
248
|
+
if (p?.node.type !== "VariableDeclarator") return;
|
|
249
|
+
const id = p.node.id;
|
|
250
|
+
if (id?.type === "ObjectPattern") for (const prop of id.properties ?? []) {
|
|
251
|
+
const pr = prop;
|
|
252
|
+
if (pr.type !== "ObjectProperty" && pr.type !== "Property" || pr.computed === true) continue;
|
|
253
|
+
const name = nameOf(pr.key);
|
|
254
|
+
if (name === void 0 || mapName(name) === name) continue;
|
|
255
|
+
api.report(`${name} has no modern-cmdk/react export under that name; destructured from the cmdk module`);
|
|
256
|
+
}
|
|
257
|
+
else if (id?.type === "Identifier" && id.name !== void 0) reportNamespaceMembers(p, id.name, `${id.name}, bound to the cmdk module`);
|
|
258
|
+
};
|
|
259
|
+
const reportStarReexport = (form) => {
|
|
260
|
+
api.report(`${form} re-exported names that ${NEW_PACKAGE} lacks (${[...CMDK_ONLY_NAMES].join(", ")}); re-export the ${NEW_PACKAGE} names by hand`);
|
|
261
|
+
};
|
|
262
|
+
const handleExportFrom = (path) => {
|
|
263
|
+
const node = path.node;
|
|
264
|
+
if (!isCmdkSource(node.source)) {
|
|
265
|
+
rewriteSite(node.source);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const mapped = [];
|
|
269
|
+
const unmapped = [];
|
|
270
|
+
let rebuilt = false;
|
|
271
|
+
for (const spec of node.specifiers ?? []) {
|
|
272
|
+
if (spec.type !== "ExportSpecifier") {
|
|
273
|
+
if (spec.type === "ExportNamespaceSpecifier") reportStarReexport(`export * as ${nameOf(spec.exported) ?? "*"} from '${OLD_PACKAGE}'`);
|
|
274
|
+
mapped.push(spec);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const exportedName = nameOf(spec.local);
|
|
278
|
+
if (exportedName === void 0) {
|
|
279
|
+
mapped.push(spec);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const target = mapName(exportedName);
|
|
283
|
+
if (target === void 0) {
|
|
284
|
+
unmapped.push(spec);
|
|
285
|
+
api.report(`${exportedName} has no modern-cmdk/react export; left on the cmdk export`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (exportedName === "useCommandState") api.report(`useCommandState was rewritten, but its state is modern-cmdk's CommandState, not cmdk's; review the selector`);
|
|
289
|
+
if (target === exportedName) {
|
|
290
|
+
mapped.push(spec);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const renamed = j.exportSpecifier.from({
|
|
294
|
+
local: j.identifier(target),
|
|
295
|
+
exported: j.identifier(nameOf(spec.exported) ?? exportedName)
|
|
296
|
+
});
|
|
297
|
+
const kind = spec.exportKind;
|
|
298
|
+
if (kind !== void 0) renamed.exportKind = kind;
|
|
299
|
+
mapped.push(renamed);
|
|
300
|
+
rebuilt = true;
|
|
301
|
+
}
|
|
302
|
+
if (mapped.length === 0 && unmapped.length > 0) return;
|
|
303
|
+
rewriteSite(node.source);
|
|
304
|
+
if (unmapped.length > 0) {
|
|
305
|
+
const kept = j.exportNamedDeclaration(null, mapped, j.stringLiteral(NEW_PACKAGE));
|
|
306
|
+
const rest = j.exportNamedDeclaration(null, unmapped, j.stringLiteral(OLD_PACKAGE));
|
|
307
|
+
if (node.exportKind !== void 0) {
|
|
308
|
+
kept.exportKind = node.exportKind;
|
|
309
|
+
rest.exportKind = node.exportKind;
|
|
310
|
+
}
|
|
311
|
+
replaceWith(path, [kept, rest]);
|
|
312
|
+
} else if (rebuilt) node.specifiers = mapped;
|
|
313
|
+
};
|
|
314
|
+
/** vi.mock('cmdk', factory) and friends: rewrite the path, report a factory naming cmdk-only names. */
|
|
315
|
+
const handleMockFactory = (path) => {
|
|
316
|
+
const factory = path.get("arguments", 1);
|
|
317
|
+
const type = factory.node?.type;
|
|
318
|
+
if (type !== "ArrowFunctionExpression" && type !== "FunctionExpression") return;
|
|
319
|
+
const found = /* @__PURE__ */ new Set();
|
|
320
|
+
j(factory).find(j.Identifier).forEach((p) => {
|
|
321
|
+
if (CMDK_ONLY_NAMES.has(p.node.name)) found.add(p.node.name);
|
|
322
|
+
});
|
|
323
|
+
j(factory).find(j.StringLiteral).forEach((p) => {
|
|
324
|
+
if (CMDK_ONLY_NAMES.has(p.node.value)) found.add(p.node.value);
|
|
325
|
+
});
|
|
326
|
+
if (found.size === 0) return;
|
|
327
|
+
api.report(`the mock factory for 'cmdk' names ${[...found].join(", ")}, which modern-cmdk/react does not export under that name; the factory body is not rewritten, check it by hand`);
|
|
328
|
+
};
|
|
329
|
+
root.find(j.Node).forEach((path) => {
|
|
330
|
+
const node = path.node;
|
|
331
|
+
switch (node.type) {
|
|
332
|
+
case "ImportDeclaration":
|
|
333
|
+
handleImport(path);
|
|
334
|
+
break;
|
|
335
|
+
case "ExportNamedDeclaration":
|
|
336
|
+
handleExportFrom(path);
|
|
337
|
+
break;
|
|
338
|
+
case "ExportAllDeclaration":
|
|
339
|
+
if (isCmdkSource(node.source)) reportStarReexport(`export * from '${OLD_PACKAGE}'`);
|
|
340
|
+
rewriteSite(node.source);
|
|
341
|
+
break;
|
|
342
|
+
case "ImportExpression": {
|
|
343
|
+
const isCmdk = isCmdkSource(node.source);
|
|
344
|
+
rewriteSite(node.source);
|
|
345
|
+
if (isCmdk) reportBoundMembers(path);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case "TSImportEqualsDeclaration": {
|
|
349
|
+
const ref = node.moduleReference;
|
|
350
|
+
if (ref?.type === "TSExternalModuleReference") {
|
|
351
|
+
const isCmdk = isCmdkSource(ref.expression);
|
|
352
|
+
rewriteSite(ref.expression);
|
|
353
|
+
const id = node.id;
|
|
354
|
+
if (isCmdk && id?.type === "Identifier" && id.name !== void 0) reportNamespaceMembers(path, id.name, `the namespace import ${id.name}`);
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case "TSImportType": {
|
|
359
|
+
const arg = node.argument;
|
|
360
|
+
const lit = arg?.type === "TSLiteralType" ? arg.literal : arg;
|
|
361
|
+
const isCmdk = isCmdkSource(lit);
|
|
362
|
+
rewriteSite(lit);
|
|
363
|
+
if (isCmdk) {
|
|
364
|
+
let q = node.qualifier;
|
|
365
|
+
while (q?.type === "TSQualifiedName") q = q.left;
|
|
366
|
+
const name = nameOf(q);
|
|
367
|
+
if (name !== void 0 && !KNOWN_EXPORTS.has(name)) api.report(`${name} has no modern-cmdk/react export; used through import('cmdk')`);
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case "TSModuleDeclaration": {
|
|
372
|
+
const id = asStringLiteral(node.id);
|
|
373
|
+
if (id?.value === OLD_PACKAGE) api.report(`declare module '${OLD_PACKAGE}' was left unchanged; move the declaration to '${NEW_PACKAGE}' by hand`);
|
|
374
|
+
else if (id?.value.startsWith(`${OLD_PACKAGE}/`)) subpathReport(id.value);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "CallExpression": {
|
|
378
|
+
const call = path;
|
|
379
|
+
const callee = call.node.callee;
|
|
380
|
+
const arg = call.node.arguments[0];
|
|
381
|
+
const isCmdk = isCmdkSource(arg);
|
|
382
|
+
if (callee.type === "Import") {
|
|
383
|
+
rewriteSite(arg);
|
|
384
|
+
if (isCmdk) reportBoundMembers(path);
|
|
385
|
+
} else if (callee.type === "Identifier" && callee.name === "require") {
|
|
386
|
+
rewriteSite(arg);
|
|
387
|
+
if (isCmdk) reportBoundMembers(path);
|
|
388
|
+
} else if (callee.type === "MemberExpression" && callee.computed !== true && callee.object?.type === "Identifier" && MOCK_OBJECTS.has(callee.object.name ?? "") && callee.property?.type === "Identifier" && MOCK_METHODS.has(callee.property.name ?? "")) {
|
|
389
|
+
const a = arg;
|
|
390
|
+
if (isCmdk || a?.type === "ImportExpression" && isCmdkSource(a.source) || a?.type === "CallExpression" && a.callee?.type === "Import" && isCmdkSource(a.arguments?.[0])) handleMockFactory(call);
|
|
391
|
+
rewriteSite(arg);
|
|
392
|
+
if (isCmdk) reportBoundMembers(path);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
38
396
|
}
|
|
39
397
|
});
|
|
40
|
-
|
|
398
|
+
if (!changed) return fileInfo.source;
|
|
399
|
+
return root.toSource(printOptions(fileInfo.source, firstRaw));
|
|
41
400
|
}
|
|
42
|
-
export { transform as default };
|
|
401
|
+
export { KNOWN_EXPORTS, transform as default };
|
|
@@ -1,18 +1,207 @@
|
|
|
1
|
-
|
|
1
|
+
import { t as printOptions } from "../chunk-print-options.mjs";
|
|
2
|
+
// ../modern-cmdk-codemod/src/transforms/should-filter.ts
|
|
3
|
+
const SOURCES = /* @__PURE__ */ new Map([["cmdk", "cmdk"], ["modern-cmdk/react", "modern-cmdk/react"]]);
|
|
4
|
+
const COMPONENTS = /* @__PURE__ */ new Set([
|
|
5
|
+
"Command",
|
|
6
|
+
"CommandRoot",
|
|
7
|
+
"CommandDialog"
|
|
8
|
+
]);
|
|
9
|
+
const adapterSource = (item, query, score, argCount) => {
|
|
10
|
+
return `(${item}, ${query}) => { const ${score} = __fn(${[
|
|
11
|
+
`${item}.value`,
|
|
12
|
+
query,
|
|
13
|
+
`${item}.keywords === undefined ? undefined : [...${item}.keywords]`
|
|
14
|
+
].slice(0, argCount).join(", ")}); return ${score} > 0 ? ${score} : false; }`;
|
|
15
|
+
};
|
|
16
|
+
function isFunctionLiteral(node) {
|
|
17
|
+
const type = node.type;
|
|
18
|
+
return type === "ArrowFunctionExpression" || type === "FunctionExpression";
|
|
19
|
+
}
|
|
20
|
+
function argCountOf(j, fn) {
|
|
21
|
+
if (!isFunctionLiteral(fn)) return 3;
|
|
22
|
+
if (fn.params.some((param) => param.type === "RestElement")) return 3;
|
|
23
|
+
if (j(fn).find(j.Identifier, { name: "arguments" }).size() > 0) return 3;
|
|
24
|
+
return Math.min(3, fn.params.length);
|
|
25
|
+
}
|
|
26
|
+
function freeName(name, used) {
|
|
27
|
+
let candidate = name;
|
|
28
|
+
while (used.has(candidate)) candidate += "_";
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
2
31
|
/**
|
|
3
|
-
* jscodeshift transform — migrates shouldFilter
|
|
32
|
+
* jscodeshift transform — migrates cmdk's shouldFilter/filter props on cmdk-bound elements.
|
|
4
33
|
*/
|
|
5
34
|
function transform(fileInfo, api) {
|
|
6
|
-
|
|
35
|
+
const hasShouldFilter = fileInfo.source.includes("shouldFilter");
|
|
36
|
+
const hasFilter = fileInfo.source.includes("filter");
|
|
37
|
+
if (!hasShouldFilter && !hasFilter) return fileInfo.source;
|
|
7
38
|
const j = api.jscodeshift;
|
|
8
39
|
const root = j(fileInfo.source);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
41
|
+
let firstRaw;
|
|
42
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
43
|
+
const decl = path.node;
|
|
44
|
+
const origin = SOURCES.get(String(decl.source.value));
|
|
45
|
+
if (origin === void 0 || decl.importKind === "type") return;
|
|
46
|
+
firstRaw ??= decl.source.extra?.raw;
|
|
47
|
+
for (const spec of decl.specifiers ?? []) {
|
|
48
|
+
if (spec.type !== "ImportSpecifier") continue;
|
|
49
|
+
const imported = String(spec.imported.name);
|
|
50
|
+
if (!COMPONENTS.has(imported)) continue;
|
|
51
|
+
if (spec.importKind === "type") continue;
|
|
52
|
+
bindings.set(String(spec.local?.name ?? imported), origin);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (bindings.size === 0) return fileInfo.source;
|
|
56
|
+
const originOf = (path) => {
|
|
57
|
+
const name = path.node.name;
|
|
58
|
+
let n;
|
|
59
|
+
if (name.type === "JSXIdentifier") n = name.name;
|
|
60
|
+
else if (name.type === "JSXMemberExpression" && name.object.type === "JSXIdentifier" && name.property.name === "Dialog") n = name.object.name;
|
|
61
|
+
else return;
|
|
62
|
+
const declaring = path.scope?.lookup(n);
|
|
63
|
+
if (declaring !== null && declaring !== void 0 && !declaring.isGlobal) return void 0;
|
|
64
|
+
return bindings.get(n);
|
|
65
|
+
};
|
|
66
|
+
let changed = false;
|
|
67
|
+
const typed = /.[cm]?tsx?$/.test(fileInfo.path);
|
|
68
|
+
root.find(j.JSXOpeningElement).forEach((path) => {
|
|
69
|
+
const origin = originOf(path);
|
|
70
|
+
if (origin === void 0) return;
|
|
71
|
+
const attrs = path.node.attributes ?? [];
|
|
72
|
+
const named = (n) => attrs.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === n);
|
|
73
|
+
const shouldFilter = named("shouldFilter");
|
|
74
|
+
const filter = named("filter");
|
|
75
|
+
const line = path.node.loc?.start.line;
|
|
76
|
+
const where = `<${elementName(path.node)}> at line ${line ?? "?"}`;
|
|
77
|
+
if (shouldFilter?.type === "JSXAttribute" && filter !== void 0) {
|
|
78
|
+
api.report(`${where} has both shouldFilter and filter; left unchanged for manual migration (filter={false} replaces shouldFilter={false}; a filter takes (item, query) and returns a number or false)`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (shouldFilter?.type === "JSXAttribute") {
|
|
82
|
+
changed = rewriteShouldFilter(j, api, path, shouldFilter, where) || changed;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (filter?.type === "JSXAttribute" && origin === "cmdk") changed = adaptFilter(j, api, filter, where, typed) || changed;
|
|
86
|
+
});
|
|
87
|
+
return changed ? root.toSource(printOptions(fileInfo.source, firstRaw)) : fileInfo.source;
|
|
88
|
+
}
|
|
89
|
+
function elementName(node) {
|
|
90
|
+
const name = node.name;
|
|
91
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
92
|
+
if (name.type === "JSXMemberExpression" && name.object.type === "JSXIdentifier") return `${name.object.name}.${name.property.name}`;
|
|
93
|
+
return "?";
|
|
94
|
+
}
|
|
95
|
+
function rewriteShouldFilter(j, api, path, attr, where) {
|
|
96
|
+
const value = attr.value;
|
|
97
|
+
const manual = (why) => {
|
|
98
|
+
api.report(`${where} has ${why}; left unchanged for manual migration`);
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
if (value === null || value === void 0) return remove(path, attr);
|
|
102
|
+
if (value.type !== "JSXExpressionContainer") return manual("a non-expression shouldFilter value");
|
|
103
|
+
const expr = value.expression;
|
|
104
|
+
if (expr.type === "JSXEmptyExpression") return manual("an empty shouldFilter value");
|
|
105
|
+
if (isBoolean(expr, true)) return remove(path, attr);
|
|
106
|
+
if (isBoolean(expr, false)) {
|
|
107
|
+
attr.name = j.jsxIdentifier("filter");
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
attr.name = j.jsxIdentifier("filter");
|
|
111
|
+
attr.value = j.jsxExpressionContainer(j.conditionalExpression(expr, j.identifier("undefined"), j.booleanLiteral(false)));
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function isBoolean(expr, want) {
|
|
115
|
+
return (expr.type === "BooleanLiteral" || expr.type === "Literal") && expr.value === want;
|
|
116
|
+
}
|
|
117
|
+
function remove(path, attr) {
|
|
118
|
+
path.node.attributes = (path.node.attributes ?? []).filter((a) => a !== attr);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function isShouldFilterOutput(fn) {
|
|
122
|
+
return fn.type === "ConditionalExpression" && fn.consequent.type === "Identifier" && fn.consequent.name === "undefined" && isBoolean(fn.alternate, false);
|
|
123
|
+
}
|
|
124
|
+
function isAdapterOutput(fn) {
|
|
125
|
+
if (fn.type !== "ArrowFunctionExpression") return false;
|
|
126
|
+
const [first, second, ...rest] = fn.body.type === "BlockStatement" ? fn.body.body : [];
|
|
127
|
+
const [p0, p1, ...moreParams] = fn.params;
|
|
128
|
+
if (p0?.type !== "Identifier" || p1?.type !== "Identifier" || moreParams.length > 0) return false;
|
|
129
|
+
if (first?.type !== "VariableDeclaration" || first.kind !== "const") return false;
|
|
130
|
+
if (second?.type !== "ReturnStatement" || rest.length > 0) return false;
|
|
131
|
+
const [decl, ...moreDecls] = first.declarations;
|
|
132
|
+
if (decl?.type !== "VariableDeclarator" || moreDecls.length > 0) return false;
|
|
133
|
+
if (decl.id.type !== "Identifier" || decl.init?.type !== "CallExpression") return false;
|
|
134
|
+
const arg0 = decl.init.arguments[0];
|
|
135
|
+
if (arg0?.type !== "MemberExpression" || arg0.object.type !== "Identifier" || arg0.object.name !== p0.name || arg0.property.type !== "Identifier" || arg0.property.name !== "value") return false;
|
|
136
|
+
const id = decl.id.name;
|
|
137
|
+
const ret = second.argument;
|
|
138
|
+
return ret?.type === "ConditionalExpression" && ret.test.type === "BinaryExpression" && ret.test.operator === ">" && ret.test.left.type === "Identifier" && ret.test.left.name === id && ret.consequent.type === "Identifier" && ret.consequent.name === id && isBoolean(ret.alternate, false);
|
|
139
|
+
}
|
|
140
|
+
function isGuardedAdapterOutput(fn) {
|
|
141
|
+
return fn.type === "ConditionalExpression" && fn.test.type === "BinaryExpression" && fn.test.operator === "===" && fn.test.right.type === "Identifier" && fn.test.right.name === "undefined" && fn.consequent.type === "Identifier" && fn.consequent.name === "undefined" && isAdapterOutput(fn.alternate);
|
|
142
|
+
}
|
|
143
|
+
function isNamePath(node) {
|
|
144
|
+
if (node.type === "Identifier" || node.type === "ThisExpression") return true;
|
|
145
|
+
if (node.type !== "MemberExpression") return false;
|
|
146
|
+
const key = node.property.type;
|
|
147
|
+
if (node.computed && key !== "StringLiteral" && key !== "NumericLiteral") return false;
|
|
148
|
+
return isNamePath(node.object);
|
|
149
|
+
}
|
|
150
|
+
function cloneNamePath(j, node) {
|
|
151
|
+
if (node.type === "Identifier") return j.identifier(node.name);
|
|
152
|
+
if (node.type !== "MemberExpression") return j.thisExpression();
|
|
153
|
+
const key = node.property;
|
|
154
|
+
const property = key.type === "Identifier" ? j.identifier(key.name) : key.type === "PrivateName" ? j.privateName(j.identifier(String(key.id.name))) : key.type === "StringLiteral" ? j.stringLiteral(key.value) : j.numericLiteral(key.value);
|
|
155
|
+
return j.memberExpression(cloneNamePath(j, node.object), property, node.computed);
|
|
156
|
+
}
|
|
157
|
+
function adaptFilter(j, api, attr, where, typed) {
|
|
158
|
+
const value = attr.value;
|
|
159
|
+
if (value?.type !== "JSXExpressionContainer" || value.expression.type === "JSXEmptyExpression") {
|
|
160
|
+
api.report(`${where} has a filter value the transform cannot adapt; left unchanged for manual migration`);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const fn = value.expression;
|
|
164
|
+
if (isBoolean(fn, false) || isBoolean(fn, true)) return false;
|
|
165
|
+
if (isShouldFilterOutput(fn) || isAdapterOutput(fn) || isGuardedAdapterOutput(fn)) return false;
|
|
166
|
+
if (fn.type === "Identifier" && fn.name === "undefined") return false;
|
|
167
|
+
const type = fn.type;
|
|
168
|
+
if (type === "NullLiteral" || type === "Literal" && fn.value === null || type === "ConditionalExpression" || type === "LogicalExpression") {
|
|
169
|
+
api.report(`${where} has a filter expression that may not be a function; left unchanged for manual migration`);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (!isFunctionLiteral(fn) && (fn.type === "ThisExpression" || !isNamePath(fn))) {
|
|
173
|
+
api.report(`${where} has a filter expression that is computed where it stands (a call or other computed value), which the adapter would re-evaluate on every item outside render; left unchanged for manual migration: hoist it to a const and pass the name`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const used = /* @__PURE__ */ new Set();
|
|
177
|
+
if (fn.type === "Identifier") used.add(fn.name);
|
|
178
|
+
j(fn).find(j.Identifier).forEach((id) => {
|
|
179
|
+
used.add(id.node.name);
|
|
15
180
|
});
|
|
16
|
-
|
|
181
|
+
const adapter = j(adapterSource(freeName("item", used), freeName("query", used), freeName("score", used), argCountOf(j, fn))).find(j.ArrowFunctionExpression).get().node;
|
|
182
|
+
j(adapter).find(j.Identifier, { name: "__fn" }).replaceWith(() => typed && !isFunctionLiteral(fn) ? castToCmdkFilter(j, fn) : fn);
|
|
183
|
+
if (isFunctionLiteral(fn)) attr.value = j.jsxExpressionContainer(adapter);
|
|
184
|
+
else {
|
|
185
|
+
const copy = cloneNamePath(j, fn);
|
|
186
|
+
attr.value = j.jsxExpressionContainer(j.conditionalExpression(j.binaryExpression("===", copy, j.identifier("undefined")), j.identifier("undefined"), adapter));
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
function castToCmdkFilter(j, fn) {
|
|
191
|
+
const param = (name, type, optional = false) => {
|
|
192
|
+
const id = j.identifier(name);
|
|
193
|
+
id.typeAnnotation = j.tsTypeAnnotation(type);
|
|
194
|
+
if (optional) id.optional = true;
|
|
195
|
+
return id;
|
|
196
|
+
};
|
|
197
|
+
const signature = j.tsFunctionType([
|
|
198
|
+
param("value", j.tsStringKeyword()),
|
|
199
|
+
param("search", j.tsStringKeyword()),
|
|
200
|
+
param("keywords", j.tsArrayType(j.tsStringKeyword()), true)
|
|
201
|
+
]);
|
|
202
|
+
signature.typeAnnotation = j.tsTypeAnnotation(j.tsNumberKeyword());
|
|
203
|
+
const cast = j.tsAsExpression(fn, signature);
|
|
204
|
+
cast.extra = { parenthesized: true };
|
|
205
|
+
return cast;
|
|
17
206
|
}
|
|
18
207
|
export { transform as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as createApiFactory, n as transformFile } from "./chunk-worker.mjs";
|
package/dist/core/index.d.mts
CHANGED
|
@@ -134,6 +134,10 @@ type CommandEvent = {
|
|
|
134
134
|
} | {
|
|
135
135
|
readonly type: "ITEMS_LOADED";
|
|
136
136
|
readonly items: readonly CommandItem[];
|
|
137
|
+
} | {
|
|
138
|
+
readonly type: "LOADING";
|
|
139
|
+
readonly source: string;
|
|
140
|
+
readonly pending: boolean;
|
|
137
141
|
} | {
|
|
138
142
|
readonly type: "REGISTER_ITEM";
|
|
139
143
|
readonly item: CommandItem;
|
|
@@ -172,7 +176,8 @@ interface FrecencyDecayConfig {
|
|
|
172
176
|
}
|
|
173
177
|
/** Frecency configuration options */
|
|
174
178
|
interface FrecencyOptions {
|
|
175
|
-
|
|
179
|
+
/** Turns frecency tracking and re-ranking on. Default false. */
|
|
180
|
+
readonly enabled?: boolean | undefined;
|
|
176
181
|
readonly storage?: FrecencyStorage | undefined;
|
|
177
182
|
readonly namespace?: string | undefined;
|
|
178
183
|
readonly decayConfig?: FrecencyDecayConfig | undefined;
|