modern-cmdk-codemod 0.0.0-stage → 1.0.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.
@@ -0,0 +1,214 @@
1
+ import { t as printOptions } from "../chunk-print-options.mjs";
2
+ import jscodeshift from "jscodeshift";
3
+ // src/transforms/forward-ref.ts
4
+ /**
5
+ * jscodeshift transform — unwraps forwardRef and binds the ref parameter as a `ref` prop.
6
+ */
7
+ function transform(fileInfo, api) {
8
+ if (!fileInfo.source.includes("forwardRef")) return fileInfo.source;
9
+ const j = api.jscodeshift;
10
+ const root = j(fileInfo.source);
11
+ const reactImports = root.find(j.ImportDeclaration).filter((path) => {
12
+ return path.node.source.value === "react" && path.node.importKind !== "type";
13
+ });
14
+ const programScope = root.find(j.Program).get().scope;
15
+ const bindings = {
16
+ forwardRefs: /* @__PURE__ */ new Set(),
17
+ namespaces: []
18
+ };
19
+ reactImports.forEach((path) => {
20
+ for (const specifier of path.node.specifiers ?? []) if (specifier.type === "ImportSpecifier") {
21
+ if (specifier.imported.name === "forwardRef") bindings.forwardRefs.add((specifier.local ?? specifier.imported).name);
22
+ } else if (specifier.local) bindings.namespaces.push(specifier.local.name);
23
+ });
24
+ if (bindings.forwardRefs.size === 0 && bindings.namespaces.length === 0) return fileInfo.source;
25
+ /** Whether a declaration other than a react import specifier binds `name` as a type anywhere in the file. */
26
+ const redeclaresType = (name) => root.find(j.TSTypeParameter, { name }).size() > 0 || root.find(j.TSTypeAliasDeclaration, { id: { name } }).size() > 0 || root.find(j.TSInterfaceDeclaration, { id: { name } }).size() > 0 || root.find(j.TSEnumDeclaration, { id: { name } }).size() > 0 || root.find(j.TSModuleDeclaration, { id: { name } }).size() > 0 || root.find(j.ClassDeclaration, { id: { name } }).size() > 0 || root.find(j.ClassExpression, { id: { name } }).size() > 0 || root.find(j.TSImportEqualsDeclaration, { id: { name } }).size() > 0;
27
+ /**
28
+ * `Ref` as a type name: `<Namespace>.Ref` when react has a default or namespace import, else a
29
+ * `Ref` specifier (existing, or a type-only one added once) on the react named import.
30
+ */
31
+ let refResolved = false;
32
+ let refLocal = "Ref";
33
+ const refTypeName = () => {
34
+ const namespace = bindings.namespaces[0];
35
+ if (namespace !== void 0) return j.tsQualifiedName(j.identifier(namespace), j.identifier("Ref"));
36
+ if (!refResolved) {
37
+ refResolved = true;
38
+ const existing = root.find(j.ImportDeclaration, { source: { value: "react" } }).find(j.ImportSpecifier, { imported: { name: "Ref" } }).paths()[0];
39
+ const existingLocal = existing && (existing.node.local ?? existing.node.imported).name;
40
+ if (existingLocal !== void 0 && !redeclaresType(existingLocal)) refLocal = existingLocal;
41
+ else {
42
+ let suffix = 1;
43
+ while (root.find(j.Identifier, { name: refLocal }).size() > 0) {
44
+ suffix += 1;
45
+ refLocal = suffix === 2 ? "ReactRef" : `ReactRef${suffix - 1}`;
46
+ }
47
+ reactImports.filter((path) => (path.node.specifiers ?? []).some((s) => s.type === "ImportSpecifier")).paths()[0]?.node.specifiers?.push(Object.assign(j.importSpecifier(j.identifier("Ref"), refLocal === "Ref" ? null : j.identifier(refLocal)), { importKind: "type" }));
48
+ }
49
+ }
50
+ return j.identifier(refLocal);
51
+ };
52
+ let hasChanges = false;
53
+ const manual = (message) => {
54
+ api.report(message);
55
+ };
56
+ const isForwardRefCall = (node) => {
57
+ const callee = node.callee;
58
+ if (callee.type === "Identifier") return bindings.forwardRefs.has(callee.name);
59
+ return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && bindings.namespaces.includes(callee.object.name) && callee.property.type === "Identifier" && callee.property.name === "forwardRef";
60
+ };
61
+ const calls = root.find(j.CallExpression).filter((path) => {
62
+ if (!isForwardRefCall(path.node)) return false;
63
+ const callee = path.node.callee;
64
+ const name = callee.type === "Identifier" ? callee.name : callee.object.name;
65
+ return path.scope.lookup(name) === programScope;
66
+ }).paths();
67
+ for (const path of calls) {
68
+ const call = path.node;
69
+ const renderFn = call.arguments[0];
70
+ if (renderFn === void 0) {
71
+ manual("forwardRef() without a render function: unwrap by hand");
72
+ continue;
73
+ }
74
+ if (renderFn.type !== "ArrowFunctionExpression" && renderFn.type !== "FunctionExpression") {
75
+ manual(renderFn.type === "Identifier" ? `forwardRef(${renderFn.name}): unwrap by hand` : "forwardRef(<expression>): unwrap by hand");
76
+ continue;
77
+ }
78
+ const params = renderFn.params;
79
+ if (params.length > 2) {
80
+ manual("forwardRef render function with more than two parameters: unwrap by hand");
81
+ continue;
82
+ }
83
+ const typeArgs = ((call.typeArguments ?? call.typeParameters)?.params ?? []).filter(isTsType);
84
+ const typeArgRef = typeArgs[0];
85
+ const typeArgProps = typeArgs[1];
86
+ const propsParam = params[0];
87
+ const refParam = params[1];
88
+ if (propsParam === void 0) {
89
+ if (typeArgRef !== void 0) {
90
+ manual("forwardRef<T>() render function without parameters: unwrap by hand");
91
+ continue;
92
+ }
93
+ unwrapCall(path, renderFn);
94
+ hasChanges = true;
95
+ continue;
96
+ }
97
+ const target = propsParam.type === "AssignmentPattern" ? propsParam.left : propsParam;
98
+ if (target.type !== "Identifier" && target.type !== "ObjectPattern") {
99
+ manual("forwardRef props parameter is not an identifier or object pattern: unwrap by hand");
100
+ continue;
101
+ }
102
+ const propsAnnotation = target.typeAnnotation;
103
+ const propsType = tsTypeOf(propsAnnotation) ?? typeArgProps;
104
+ if (target.type === "ObjectPattern" && target.properties.some((p) => (p.type === "ObjectProperty" || p.type === "Property") && p.computed && p.key.type !== "StringLiteral" && p.key.type !== "NumericLiteral" && p.key.type !== "BigIntLiteral" && !(p.key.type === "TemplateLiteral" && p.key.expressions.length === 0))) {
105
+ manual("forwardRef props pattern has a computed key that may be 'ref': unwrap by hand");
106
+ continue;
107
+ }
108
+ if (target.type === "ObjectPattern" && target.properties.some((p) => (p.type === "ObjectProperty" || p.type === "Property") && (!p.computed && p.key.type === "Identifier" && p.key.name === "ref" || p.key.type === "StringLiteral" && p.key.value === "ref" || p.computed && p.key.type === "TemplateLiteral" && p.key.expressions.length === 0 && p.key.quasis[0]?.value.cooked === "ref"))) {
109
+ manual("forwardRef props pattern already has a 'ref' key: unwrap by hand");
110
+ continue;
111
+ }
112
+ if (refParam === void 0) {
113
+ if (typeArgRef === void 0 && tsTypeOf(propsAnnotation) !== void 0 && j(renderFn).find(j.JSXSpreadAttribute).size() > 0) {
114
+ manual("forwardRef render function has a JSX spread and no ref type argument: unwrap by hand");
115
+ continue;
116
+ }
117
+ const singleRef = typeArgRef ?? (tsTypeOf(propsAnnotation) !== void 0 ? j.tsUnknownKeyword() : void 0);
118
+ if (singleRef !== void 0) {
119
+ const member = j.tsTypeLiteral([Object.assign(j.tsPropertySignature(j.identifier("ref"), j.tsTypeAnnotation(j.tsTypeReference(refTypeName(), j.tsTypeParameterInstantiation([singleRef])))), { optional: true })]);
120
+ target.typeAnnotation = j.tsTypeAnnotation(propsType !== void 0 ? j.tsIntersectionType([intersectable(j, propsType), member]) : member);
121
+ } else if (propsAnnotation == null && typeArgProps !== void 0) target.typeAnnotation = j.tsTypeAnnotation(typeArgProps);
122
+ unwrapCall(path, renderFn);
123
+ hasChanges = true;
124
+ continue;
125
+ }
126
+ if (refParam.type !== "Identifier") {
127
+ manual("forwardRef ref parameter is not a plain identifier: unwrap by hand");
128
+ continue;
129
+ }
130
+ const refAnnotation = refParam.typeAnnotation;
131
+ let refType;
132
+ const annotatedRef = tsTypeOf(refAnnotation);
133
+ if (annotatedRef !== void 0) refType = annotatedRef;
134
+ else if (typeArgRef !== void 0) refType = j.tsTypeReference(refTypeName(), j.tsTypeParameterInstantiation([typeArgRef]));
135
+ if (refType === void 0 && propsType !== void 0) refType = j.tsTypeReference(refTypeName(), j.tsTypeParameterInstantiation([j.tsUnknownKeyword()]));
136
+ let annotation;
137
+ if (refType !== void 0) {
138
+ const refMember = j.tsTypeLiteral([Object.assign(j.tsPropertySignature(j.identifier("ref"), j.tsTypeAnnotation(refType)), { optional: true })]);
139
+ if (propsType !== void 0) annotation = j.tsIntersectionType([intersectable(j, propsType), refMember]);
140
+ else if (typeArgs.length > 0) annotation = refMember;
141
+ else {
142
+ manual("forwardRef ref is annotated but the props type is unknown: unwrap by hand");
143
+ continue;
144
+ }
145
+ } else if (propsType !== void 0) annotation = propsType;
146
+ const refProperty = j.objectProperty(j.identifier("ref"), j.identifier(refParam.name));
147
+ refProperty.shorthand = refParam.name === "ref";
148
+ let pattern;
149
+ if (target.type === "ObjectPattern") {
150
+ target.properties.unshift(refProperty);
151
+ pattern = target;
152
+ } else pattern = j.objectPattern.from({ properties: [refProperty, j.restElement(j.identifier(target.name))] });
153
+ if (annotation !== void 0) pattern.typeAnnotation = j.tsTypeAnnotation(annotation);
154
+ else if (target.type === "Identifier" && target.typeAnnotation) pattern.typeAnnotation = target.typeAnnotation;
155
+ if (propsParam.type === "AssignmentPattern") propsParam.left = pattern;
156
+ else renderFn.params[0] = pattern;
157
+ renderFn.params = [renderFn.params[0]];
158
+ unwrapCall(path, renderFn);
159
+ hasChanges = true;
160
+ }
161
+ if (!hasChanges) return fileInfo.source;
162
+ reactImports.forEach((declaration) => {
163
+ const specifiers = declaration.node.specifiers ?? [];
164
+ const before = specifiers.length;
165
+ const kept = specifiers.filter((specifier) => {
166
+ if (specifier.type !== "ImportSpecifier" || specifier.imported.name !== "forwardRef") return true;
167
+ const local = (specifier.local ?? specifier.imported).name;
168
+ return isReferenced(j, root, programScope, local);
169
+ });
170
+ if (kept.length === before) return;
171
+ if (kept.length === 0) {
172
+ const comments = declaration.node.comments;
173
+ const siblings = declaration.parent.node.body;
174
+ const next = siblings[siblings.indexOf(declaration.node) + 1];
175
+ if (comments && next) next.comments = [...comments, ...next.comments ?? []];
176
+ j(declaration).remove();
177
+ } else declaration.node.specifiers = kept;
178
+ });
179
+ return root.toSource(printOptions(fileInfo.source));
180
+ }
181
+ /** Replace the forwardRef call with its render function, keeping the call's own comments. */
182
+ function unwrapCall(path, renderFn) {
183
+ const comments = path.node.comments;
184
+ if (comments) renderFn.comments = [...comments, ...renderFn.comments ?? []];
185
+ path.replace(renderFn);
186
+ }
187
+ /** Flow type arguments (a .js file with Flow) are not TypeScript types and are never used. */
188
+ const isTsType = (type) => jscodeshift.TSType.check(type);
189
+ /** The TypeScript type inside a `: T` annotation, if the annotation is one. */
190
+ function tsTypeOf(annotation) {
191
+ return annotation?.type === "TSTypeAnnotation" && isTsType(annotation.typeAnnotation) ? annotation.typeAnnotation : void 0;
192
+ }
193
+ /** A type that binds looser than `&` gets parentheses before it joins an intersection. */
194
+ function intersectable(j, type) {
195
+ switch (type.type) {
196
+ case "TSUnionType":
197
+ case "TSFunctionType":
198
+ case "TSConstructorType":
199
+ case "TSConditionalType": return j.tsParenthesizedType(type);
200
+ default: return type;
201
+ }
202
+ }
203
+ /** Whether an Identifier named `name` is a reference outside any import specifier. */
204
+ function isReferenced(j, root, programScope, name) {
205
+ return root.find(j.Identifier, { name }).filter((path) => {
206
+ if (path.scope.lookup(name) !== programScope) return false;
207
+ const parent = path.parent.node;
208
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") return false;
209
+ if ((parent.type === "MemberExpression" || parent.type === "OptionalMemberExpression") && parent.property === path.node && !parent.computed) return false;
210
+ if ((parent.type === "ObjectProperty" || parent.type === "Property") && parent.key === path.node && !parent.computed && !parent.shorthand) return false;
211
+ return true;
212
+ }).size() > 0;
213
+ }
214
+ export { transform as default };
@@ -0,0 +1,401 @@
1
+ import { t as printOptions } from "../chunk-print-options.mjs";
2
+ // src/transforms/import-rewrite.ts
3
+ const OLD_PACKAGE = "cmdk";
4
+ const NEW_PACKAGE = "modern-cmdk/react";
5
+ /**
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.
121
+ */
122
+ function transform(fileInfo, api) {
123
+ if (!fileInfo.source.includes(OLD_PACKAGE)) return fileInfo.source;
124
+ const j = api.jscodeshift;
125
+ const root = j(fileInfo.source);
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;
167
+ }
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;
200
+ }
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
+ }
396
+ }
397
+ });
398
+ if (!changed) return fileInfo.source;
399
+ return root.toSource(printOptions(fileInfo.source, firstRaw));
400
+ }
401
+ export { KNOWN_EXPORTS, transform as default };