@panneau/codemods 0.0.0-stage → 5.0.0-alpha.6

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.
Files changed (50) hide show
  1. package/README.md +28 -2
  2. package/bin/panneau-codemods.js +5 -0
  3. package/dist/cli.d.ts +7 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/context.d.ts +80 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +117 -0
  10. package/dist/context.js.map +1 -0
  11. package/dist/index.d.ts +5 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +4 -0
  14. package/dist/mappings.d.ts +49 -0
  15. package/dist/mappings.d.ts.map +1 -0
  16. package/dist/mappings.js +574 -0
  17. package/dist/mappings.js.map +1 -0
  18. package/dist/names.d.ts +5 -0
  19. package/dist/names.d.ts.map +1 -0
  20. package/dist/names.js +13 -0
  21. package/dist/names.js.map +1 -0
  22. package/dist/run.d.ts +23 -0
  23. package/dist/run.d.ts.map +1 -0
  24. package/dist/run.js +65 -0
  25. package/dist/run.js.map +1 -0
  26. package/dist/transform.d.ts +28 -0
  27. package/dist/transform.d.ts.map +1 -0
  28. package/dist/transform.js +192 -0
  29. package/dist/transform.js.map +1 -0
  30. package/dist/transforms/bootstrap.d.ts +6 -0
  31. package/dist/transforms/bootstrap.d.ts.map +1 -0
  32. package/dist/transforms/bootstrap.js +65 -0
  33. package/dist/transforms/bootstrap.js.map +1 -0
  34. package/dist/transforms/entry.d.ts +15 -0
  35. package/dist/transforms/entry.d.ts.map +1 -0
  36. package/dist/transforms/entry.js +239 -0
  37. package/dist/transforms/entry.js.map +1 -0
  38. package/dist/transforms/fieldEntries.d.ts +20 -0
  39. package/dist/transforms/fieldEntries.d.ts.map +1 -0
  40. package/dist/transforms/fieldEntries.js +238 -0
  41. package/dist/transforms/fieldEntries.js.map +1 -0
  42. package/dist/transforms/fields.d.ts +3 -0
  43. package/dist/transforms/fields.d.ts.map +1 -0
  44. package/dist/transforms/fields.js +241 -0
  45. package/dist/transforms/fields.js.map +1 -0
  46. package/dist/transforms/imports.d.ts +3 -0
  47. package/dist/transforms/imports.d.ts.map +1 -0
  48. package/dist/transforms/imports.js +60 -0
  49. package/dist/transforms/imports.js.map +1 -0
  50. package/package.json +44 -5
@@ -0,0 +1,239 @@
1
+ import { TODO_PREFIX, insertImport } from "../context.js";
2
+ import { kebabCase } from "../names.js";
3
+ import { NAMESPACES, getNamespaceOfConstant } from "../mappings.js";
4
+ import { dirname, relative } from "node:path";
5
+ //#region src/transforms/entry.ts
6
+ /** The local names of the app of v4 in a file: its import, and the variables set to it. */
7
+ function getAppNames(j, root) {
8
+ const names = root.find(j.ImportDeclaration, { source: { value: "@panneau/app" } }).find(j.ImportDefaultSpecifier).nodes().flatMap((specifier) => specifier.local ? [String(specifier.local.name)] : []);
9
+ if (names.length === 0) return [];
10
+ root.find(j.VariableDeclarator).forEach((path) => {
11
+ let init = path.node.init;
12
+ while (init?.type === "TSAsExpression" || init?.type === "TypeCastExpression") init = init.expression;
13
+ if (init?.type === "Identifier" && names.includes(init.name ?? "") && path.node.id.type === "Identifier") names.push(path.node.id.name);
14
+ });
15
+ return names;
16
+ }
17
+ function findAppElements(j, root) {
18
+ const names = getAppNames(j, root);
19
+ return root.find(j.JSXElement).filter((path) => {
20
+ const { name } = path.node.openingElement;
21
+ return name.type === "JSXIdentifier" && names.includes(name.name);
22
+ });
23
+ }
24
+ function getAttribute(element, name) {
25
+ return (element.openingElement.attributes ?? []).find((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === name) ?? null;
26
+ }
27
+ /** The properties of `components={{ [FIELDS_NAMESPACE]: fields }}`, by v4 namespace. */
28
+ function readComponents(element) {
29
+ const attribute = getAttribute(element, "components");
30
+ const expression = attribute?.value?.type === "JSXExpressionContainer" ? attribute.value.expression : null;
31
+ if (expression?.type !== "ObjectExpression") return [];
32
+ return expression.properties.flatMap((property) => {
33
+ if (property.type !== "ObjectProperty" && property.type !== "Property") return [];
34
+ const { key } = property;
35
+ if (property.computed && key.type === "Identifier") return [{
36
+ namespace: getNamespaceOfConstant(key.name),
37
+ key: key.name,
38
+ value: property.value
39
+ }];
40
+ const name = key.type === "Identifier" ? key.name : (key.type === "StringLiteral" || key.type === "Literal") && typeof key.value === "string" ? key.value : null;
41
+ return name === null ? [] : [{
42
+ namespace: name,
43
+ key: name,
44
+ value: property.value
45
+ }];
46
+ });
47
+ }
48
+ /** The module of a namespace import (`import * as fields from './panneau/fields'`). */
49
+ function getNamespaceModule(j, root, name) {
50
+ const imports = root.find(j.ImportNamespaceSpecifier, { local: { name } }).map((path) => path.parent);
51
+ if (imports.size() === 0) return null;
52
+ const value = imports.get().node.source.value;
53
+ return typeof value === "string" ? value : null;
54
+ }
55
+ /**
56
+ * A plugin module that the codemods wrote from a container: `definePlugin({ fields: { 'page-slug':
57
+ * fields.PageSlug } })`. Its fields are known again when the codemods run a second time.
58
+ */
59
+ function readPluginModule(j, root, namespaces) {
60
+ const definePlugin = root.find(j.ImportDeclaration, { source: { value: "@panneau/core" } }).find(j.ImportSpecifier, { imported: { name: "definePlugin" } }).nodes()[0]?.local?.name;
61
+ if (typeof definePlugin !== "string") return;
62
+ root.find(j.CallExpression, { callee: {
63
+ type: "Identifier",
64
+ name: definePlugin
65
+ } }).forEach((call) => {
66
+ const [options] = call.node.arguments;
67
+ if (options?.type !== "ObjectExpression") return;
68
+ options.properties.forEach((property) => {
69
+ if (property.type !== "ObjectProperty" && property.type !== "Property" || property.key.type !== "Identifier" || property.key.name !== "fields" || property.value.type !== "ObjectExpression") return;
70
+ property.value.properties.forEach((entry) => {
71
+ const value = entry.value;
72
+ if (value?.type === "MemberExpression" && value.object?.type === "Identifier" && value.object.name !== void 0) {
73
+ const module = getNamespaceModule(j, root, value.object.name);
74
+ if (module !== null) namespaces.set("fields", module);
75
+ }
76
+ });
77
+ });
78
+ });
79
+ }
80
+ /** What `analyze` reads about the entry of a file. */
81
+ function readContainer(j, root) {
82
+ const elements = findAppElements(j, root);
83
+ const namespaces = /* @__PURE__ */ new Map();
84
+ if (elements.size() === 0) {
85
+ readPluginModule(j, root, namespaces);
86
+ return {
87
+ isContainer: false,
88
+ namespaces
89
+ };
90
+ }
91
+ readComponents(elements.get().node).forEach(({ namespace, value }) => {
92
+ const node = value;
93
+ if (namespace !== null && node.type === "Identifier" && node.name !== void 0) {
94
+ const module = getNamespaceModule(j, root, node.name);
95
+ if (module !== null) namespaces.set(namespace, module);
96
+ }
97
+ });
98
+ return {
99
+ isContainer: true,
100
+ namespaces
101
+ };
102
+ }
103
+ /** Props of the v4 app that the boot config of the page replaces. */
104
+ var BOOT_PROPS = [
105
+ "definition",
106
+ "routes",
107
+ "user",
108
+ "baseUrl",
109
+ "statusCode",
110
+ "locale",
111
+ "locales",
112
+ "memoryRouter"
113
+ ];
114
+ function quote(key) {
115
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? key : `'${key}'`;
116
+ }
117
+ /** Rewrites a v4 container as a module that exports the project's plugin. */
118
+ function writePlugin(api, element) {
119
+ const { j, root, project, path } = api;
120
+ const todos = [];
121
+ const todo = (node, rule, message) => {
122
+ api.report(node, rule, message);
123
+ todos.push(message);
124
+ };
125
+ const kept = /* @__PURE__ */ new Set();
126
+ const kinds = [];
127
+ readComponents(element.node).forEach(({ namespace, key, value }) => {
128
+ const node = value;
129
+ const definition = namespace !== null ? NAMESPACES[namespace] : void 0;
130
+ if (definition === void 0 || definition.kind === null) {
131
+ todo(element, "entry", `the \`${namespace ?? key}\` components have no place in the plugins of Panneau 5: see the migration guide`);
132
+ return;
133
+ }
134
+ const { kind } = definition;
135
+ if (definition.note !== void 0) todo(element, "entry", `\`${kind}\`: ${definition.note}`);
136
+ const name = node.type === "Identifier" ? node.name ?? null : null;
137
+ const module = name !== null ? getNamespaceModule(j, root, name) : null;
138
+ if (name !== null && module !== null) root.find(j.ImportNamespaceSpecifier, { local: { name } }).forEach((specifier) => {
139
+ kept.add(specifier.parent.node);
140
+ });
141
+ const file = module !== null ? project.resolve(path, module) : null;
142
+ const exports = file !== null ? project.info(file)?.defaultReExports : void 0;
143
+ if (name !== null && exports !== void 0 && exports.size > 0) {
144
+ const entries = [...exports.keys()].map((exported) => ` ${quote(kebabCase(exported))}: ${name}.${exported},`);
145
+ kinds.push(` ${kind}: {\n${entries.join("\n")}\n },`);
146
+ todo(element, "entry", kind === "fields" ? "the keys of `fields` are the types of the definition, in kebab case: check each one against the `type()` of its PHP field (v4 also found `video_or_embed` under `VideoOrEmbed`)" : `the keys of \`${kind}\` are in kebab case: check each one against the name the definition uses`);
147
+ return;
148
+ }
149
+ kinds.push(` ${kind}: ${j(value).toSource()},`);
150
+ todo(element, "entry", `\`${kind}\` must be keyed by the types of the definition (\`page-slug\`), and give registry entries (\`FieldEntryProps\` of \`@panneau/fields\`)`);
151
+ });
152
+ const dropped = (element.node.openingElement.attributes ?? []).flatMap((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" ? [attribute.name.name] : ["..."]);
153
+ if (dropped.includes("uppy")) todo(element, "entry", "`uppy` is dropped: uploads go to the storage of the media libraries, signed by the API (decision 0025); Media and Image fields need `mediaPlugin` of `@panneau/media` in the plugins");
154
+ const others = dropped.filter((prop) => !BOOT_PROPS.includes(prop) && prop !== "uppy" && prop !== "components");
155
+ if (others.length > 0) todo(element, "entry", `${others.map((prop) => `\`${prop}\``).join(", ")} of the app ${others.length > 1 ? "are" : "is"} dropped: Panneau 5 has no such option`);
156
+ root.find(j.JSXOpeningElement).forEach((opening) => {
157
+ const { name } = opening.node;
158
+ if (name.type === "JSXIdentifier" && name.name === "MediasBrowserProvider") todo(opening, "entry", "`MediasBrowserProvider` is dropped: the media field and picker of `@panneau/media` (`mediaPlugin`) browse the libraries through the adapter");
159
+ });
160
+ api.report(element, "entry", `the container is now the plugin of the project: ${BOOT_PROPS.filter((prop) => dropped.includes(prop)).map((prop) => `\`${prop}\``).join(", ")} come from the boot config of the page, and the rest of the container is dropped (it stays in git)`);
161
+ root.find(j.ImportDeclaration).forEach((declaration) => {
162
+ const source = String(declaration.node.source.value);
163
+ if ((declaration.node.specifiers ?? []).length === 0 && !source.startsWith("@panneau/")) {
164
+ if (/\.(css|scss|sass|less)$/.test(source) && source.startsWith(".")) {
165
+ kept.add(declaration.node);
166
+ todo(declaration, "entry", `\`${source}\` was written for v4 and Bootstrap: check what it still styles`);
167
+ } else todo(declaration, "entry", `\`import '${source}'\` is dropped with the container: import it where it is still needed`);
168
+ }
169
+ });
170
+ const imports = root.find(j.ImportDeclaration).filter((declaration) => kept.has(declaration.node)).map((declaration) => declaration).nodes().map((declaration) => {
171
+ const copy = {
172
+ ...declaration,
173
+ comments: null
174
+ };
175
+ return j(copy).toSource({ quote: "single" });
176
+ });
177
+ const modules = imports.filter((line) => !/^import '/.test(line));
178
+ const styles = imports.filter((line) => /^import '/.test(line));
179
+ return [
180
+ "import { definePlugin } from '@panneau/core';",
181
+ "",
182
+ ...modules.length > 0 ? [...modules, ""] : [],
183
+ ...styles.length > 0 ? [...styles, ""] : [],
184
+ "// The plugin of the project for Panneau 5, made by @panneau/codemods from the container of",
185
+ "// v4. The entry passes it to `createPanneauApp({ element, plugins })`.",
186
+ ...todos.map((message) => `// ${TODO_PREFIX} ${message}`),
187
+ "export default definePlugin({",
188
+ " id: 'project',",
189
+ ...kinds,
190
+ "});",
191
+ ""
192
+ ].join("\n");
193
+ }
194
+ /**
195
+ * Rewrites a v4 container as a plugin module. Returns the new source, or `null` when the file
196
+ * isn't a container.
197
+ */
198
+ function transformContainer(api) {
199
+ const { j, root } = api;
200
+ const elements = findAppElements(j, root);
201
+ if (elements.size() === 0) return null;
202
+ if (elements.size() > 1) api.report(elements.paths()[1] ?? null, "entry", "the app of v4 is rendered more than once: only the first one is converted", { todo: true });
203
+ return writePlugin(api, elements.paths()[0]);
204
+ }
205
+ /**
206
+ * Adds the plugins made from containers to the `plugins` of `createPanneauApp()` in the entry of
207
+ * Panneau 5, when it is one of the files.
208
+ */
209
+ function transformAppEntry(api) {
210
+ const { j, root, project, path } = api;
211
+ const create = root.find(j.ImportDeclaration, { source: { value: "@panneau/app" } }).find(j.ImportSpecifier, { imported: { name: "createPanneauApp" } }).nodes()[0]?.local?.name;
212
+ if (create === void 0 || typeof create !== "string") return;
213
+ const modules = project.pluginModules();
214
+ if (modules.length === 0) return;
215
+ root.find(j.CallExpression, { callee: {
216
+ type: "Identifier",
217
+ name: create
218
+ } }).forEach((call) => {
219
+ const [options] = call.node.arguments;
220
+ const list = (options?.type === "ObjectExpression" ? options.properties.find((property) => (property.type === "ObjectProperty" || property.type === "Property") && property.key.type === "Identifier" && property.key.name === "plugins") : void 0)?.value;
221
+ if (list?.type !== "ArrayExpression" || list.elements === void 0) {
222
+ api.report(call, "entry", `add the plugin of the project to the \`plugins\` of \`${create}()\``, { todo: true });
223
+ return;
224
+ }
225
+ modules.forEach((module, index) => {
226
+ let specifier = relative(dirname(path), module).replace(/\\/g, "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
227
+ if (!specifier.startsWith(".")) specifier = `./${specifier}`;
228
+ const existing = root.find(j.ImportDeclaration, { source: { value: specifier } }).find(j.ImportDefaultSpecifier).nodes()[0]?.local?.name;
229
+ const name = (existing !== void 0 ? String(existing) : null) ?? (index === 0 ? "projectPlugin" : `projectPlugin${index + 1}`);
230
+ if (existing === void 0) insertImport(api, j.importDeclaration([j.importDefaultSpecifier(j.identifier(name))], j.literal(specifier)));
231
+ const elements = list.elements;
232
+ if (!elements.some((element) => element.type === "Identifier" && element.name === name)) elements.push(j.identifier(name));
233
+ });
234
+ });
235
+ }
236
+ //#endregion
237
+ export { readContainer, transformAppEntry, transformContainer };
238
+
239
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","names":[],"sources":["../../src/transforms/entry.ts"],"sourcesContent":["// The entry of v4 is a container that renders `<Panneau definition routes user baseUrl uppy\n// components>` of `@panneau/app`. In Panneau 5, the page gives the boot config, and the entry\n// written by `panneau:install` mounts the app with `createPanneauApp({ element, plugins })`. The\n// container becomes a module that exports the project's plugin, and the entry gets the plugin.\nimport type {\n ASTPath,\n Collection,\n ImportDeclaration,\n JSCodeshift,\n JSXAttribute,\n JSXElement,\n ObjectExpression,\n} from 'jscodeshift';\nimport { dirname, relative } from 'node:path';\n\nimport { type FileInfo, TODO_PREFIX, type TransformApi, insertImport } from '../context';\nimport { NAMESPACES, getNamespaceOfConstant } from '../mappings';\nimport { kebabCase } from '../names';\n\n/** The local names of the app of v4 in a file: its import, and the variables set to it. */\nfunction getAppNames(j: JSCodeshift, root: Collection): string[] {\n const names = root\n .find(j.ImportDeclaration, { source: { value: '@panneau/app' } })\n .find(j.ImportDefaultSpecifier)\n .nodes()\n .flatMap((specifier) => (specifier.local ? [String(specifier.local.name)] : []));\n if (names.length === 0) {\n return [];\n }\n // `const PanneauApp = Panneau as ComponentType<any>`\n root.find(j.VariableDeclarator).forEach((path) => {\n let init = path.node.init as { type: string; name?: string; expression?: unknown } | null;\n while (init?.type === 'TSAsExpression' || init?.type === 'TypeCastExpression') {\n init = init.expression as typeof init;\n }\n if (\n init?.type === 'Identifier' &&\n names.includes(init.name ?? '') &&\n path.node.id.type === 'Identifier'\n ) {\n names.push(path.node.id.name);\n }\n });\n return names;\n}\n\nfunction findAppElements(j: JSCodeshift, root: Collection): Collection<JSXElement> {\n const names = getAppNames(j, root);\n return root.find(j.JSXElement).filter((path) => {\n const { name } = path.node.openingElement;\n return name.type === 'JSXIdentifier' && names.includes(name.name);\n });\n}\n\nfunction getAttribute(element: JSXElement, name: string): JSXAttribute | null {\n return (\n (element.openingElement.attributes ?? []).find(\n (attribute): attribute is JSXAttribute =>\n attribute.type === 'JSXAttribute' &&\n attribute.name.type === 'JSXIdentifier' &&\n attribute.name.name === name,\n ) ?? null\n );\n}\n\n/** The properties of `components={{ [FIELDS_NAMESPACE]: fields }}`, by v4 namespace. */\nfunction readComponents(\n element: JSXElement,\n): { namespace: string | null; key: string; value: unknown }[] {\n const attribute = getAttribute(element, 'components');\n const expression =\n attribute?.value?.type === 'JSXExpressionContainer' ? attribute.value.expression : null;\n if (expression?.type !== 'ObjectExpression') {\n return [];\n }\n return (expression as ObjectExpression).properties.flatMap((property) => {\n if (property.type !== 'ObjectProperty' && property.type !== 'Property') {\n return [];\n }\n const { key } = property;\n if (property.computed && key.type === 'Identifier') {\n return [\n {\n namespace: getNamespaceOfConstant(key.name),\n key: key.name,\n value: property.value,\n },\n ];\n }\n const name =\n key.type === 'Identifier'\n ? key.name\n : (key.type === 'StringLiteral' || key.type === 'Literal') &&\n typeof key.value === 'string'\n ? key.value\n : null;\n return name === null ? [] : [{ namespace: name, key: name, value: property.value }];\n });\n}\n\n/** The module of a namespace import (`import * as fields from './panneau/fields'`). */\nfunction getNamespaceModule(j: JSCodeshift, root: Collection, name: string): string | null {\n const imports = root\n .find(j.ImportNamespaceSpecifier, { local: { name } })\n .map((path) => path.parent as ASTPath<ImportDeclaration>);\n if (imports.size() === 0) {\n return null;\n }\n const value = (imports.get().node as ImportDeclaration).source.value;\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * A plugin module that the codemods wrote from a container: `definePlugin({ fields: { 'page-slug':\n * fields.PageSlug } })`. Its fields are known again when the codemods run a second time.\n */\nfunction readPluginModule(j: JSCodeshift, root: Collection, namespaces: Map<string, string>): void {\n const definePlugin = root\n .find(j.ImportDeclaration, { source: { value: '@panneau/core' } })\n .find(j.ImportSpecifier, { imported: { name: 'definePlugin' } })\n .nodes()[0]?.local?.name;\n if (typeof definePlugin !== 'string') {\n return;\n }\n root.find(j.CallExpression, { callee: { type: 'Identifier', name: definePlugin } }).forEach(\n (call) => {\n const [options] = call.node.arguments;\n if (options?.type !== 'ObjectExpression') {\n return;\n }\n options.properties.forEach((property) => {\n if (\n (property.type !== 'ObjectProperty' && property.type !== 'Property') ||\n property.key.type !== 'Identifier' ||\n property.key.name !== 'fields' ||\n property.value.type !== 'ObjectExpression'\n ) {\n return;\n }\n property.value.properties.forEach((entry) => {\n const value = (\n entry as {\n value?: { type: string; object?: { type: string; name?: string } };\n }\n ).value;\n if (\n value?.type === 'MemberExpression' &&\n value.object?.type === 'Identifier' &&\n value.object.name !== undefined\n ) {\n const module = getNamespaceModule(j, root, value.object.name);\n if (module !== null) {\n namespaces.set('fields', module);\n }\n }\n });\n });\n },\n );\n}\n\n/** What `analyze` reads about the entry of a file. */\nexport function readContainer(\n j: JSCodeshift,\n root: Collection,\n): Pick<FileInfo, 'isContainer' | 'namespaces'> {\n const elements = findAppElements(j, root);\n const namespaces = new Map<string, string>();\n if (elements.size() === 0) {\n readPluginModule(j, root, namespaces);\n return { isContainer: false, namespaces };\n }\n readComponents(elements.get().node as JSXElement).forEach(({ namespace, value }) => {\n const node = value as { type: string; name?: string };\n if (namespace !== null && node.type === 'Identifier' && node.name !== undefined) {\n const module = getNamespaceModule(j, root, node.name);\n if (module !== null) {\n namespaces.set(namespace, module);\n }\n }\n });\n return { isContainer: true, namespaces };\n}\n\n/** Props of the v4 app that the boot config of the page replaces. */\nconst BOOT_PROPS = [\n 'definition',\n 'routes',\n 'user',\n 'baseUrl',\n 'statusCode',\n 'locale',\n 'locales',\n 'memoryRouter',\n];\n\nfunction quote(key: string): string {\n return /^[A-Za-z_$][\\w$]*$/.test(key) ? key : `'${key}'`;\n}\n\n/** Rewrites a v4 container as a module that exports the project's plugin. */\nfunction writePlugin(api: TransformApi, element: ASTPath<JSXElement>): string {\n const { j, root, project, path } = api;\n const todos: string[] = [];\n const todo = (node: ASTPath | null, rule: string, message: string) => {\n api.report(node, rule, message);\n todos.push(message);\n };\n const kept = new Set<ImportDeclaration>();\n const kinds: string[] = [];\n\n readComponents(element.node).forEach(({ namespace, key, value }) => {\n const node = value as { type: string; name?: string };\n const definition = namespace !== null ? NAMESPACES[namespace] : undefined;\n if (definition === undefined || definition.kind === null) {\n todo(\n element,\n 'entry',\n `the \\`${namespace ?? key}\\` components have no place in the plugins of Panneau 5: see the migration guide`,\n );\n return;\n }\n const { kind } = definition;\n if (definition.note !== undefined) {\n todo(element, 'entry', `\\`${kind}\\`: ${definition.note}`);\n }\n const name = node.type === 'Identifier' ? (node.name ?? null) : null;\n const module = name !== null ? getNamespaceModule(j, root, name) : null;\n if (name !== null && module !== null) {\n root.find(j.ImportNamespaceSpecifier, { local: { name } }).forEach((specifier) => {\n kept.add(specifier.parent.node as ImportDeclaration);\n });\n }\n const file = module !== null ? project.resolve(path, module) : null;\n const exports = file !== null ? project.info(file)?.defaultReExports : undefined;\n if (name !== null && exports !== undefined && exports.size > 0) {\n const entries = [...exports.keys()].map(\n (exported) => ` ${quote(kebabCase(exported))}: ${name}.${exported},`,\n );\n kinds.push(` ${kind}: {\\n${entries.join('\\n')}\\n },`);\n todo(\n element,\n 'entry',\n kind === 'fields'\n ? 'the keys of `fields` are the types of the definition, in kebab case: check each one against the `type()` of its PHP field (v4 also found `video_or_embed` under `VideoOrEmbed`)'\n : `the keys of \\`${kind}\\` are in kebab case: check each one against the name the definition uses`,\n );\n return;\n }\n kinds.push(` ${kind}: ${j(value as never).toSource()},`);\n todo(\n element,\n 'entry',\n `\\`${kind}\\` must be keyed by the types of the definition (\\`page-slug\\`), and give registry entries (\\`FieldEntryProps\\` of \\`@panneau/fields\\`)`,\n );\n });\n\n const dropped = (element.node.openingElement.attributes ?? []).flatMap((attribute) =>\n attribute.type === 'JSXAttribute' && attribute.name.type === 'JSXIdentifier'\n ? [attribute.name.name]\n : ['...'],\n );\n if (dropped.includes('uppy')) {\n todo(\n element,\n 'entry',\n '`uppy` is dropped: uploads go to the storage of the media libraries, signed by the API (decision 0025); Media and Image fields need `mediaPlugin` of `@panneau/media` in the plugins',\n );\n }\n const others = dropped.filter(\n (prop) => !BOOT_PROPS.includes(prop) && prop !== 'uppy' && prop !== 'components',\n );\n if (others.length > 0) {\n todo(\n element,\n 'entry',\n `${others.map((prop) => `\\`${prop}\\``).join(', ')} of the app ${others.length > 1 ? 'are' : 'is'} dropped: Panneau 5 has no such option`,\n );\n }\n root.find(j.JSXOpeningElement).forEach((opening) => {\n const { name } = opening.node;\n if (name.type === 'JSXIdentifier' && name.name === 'MediasBrowserProvider') {\n todo(\n opening,\n 'entry',\n '`MediasBrowserProvider` is dropped: the media field and picker of `@panneau/media` (`mediaPlugin`) browse the libraries through the adapter',\n );\n }\n });\n api.report(\n element,\n 'entry',\n `the container is now the plugin of the project: ${BOOT_PROPS.filter((prop) =>\n dropped.includes(prop),\n )\n .map((prop) => `\\`${prop}\\``)\n .join(\n ', ',\n )} come from the boot config of the page, and the rest of the container is dropped (it stays in git)`,\n );\n\n // Stylesheets of the project stay: they may style the plugin's components. Other imports for\n // their side effects (`ckeditor5/ckeditor5.css`) are reported.\n root.find(j.ImportDeclaration).forEach((declaration) => {\n const source = String(declaration.node.source.value);\n if ((declaration.node.specifiers ?? []).length === 0 && !source.startsWith('@panneau/')) {\n if (/\\.(css|scss|sass|less)$/.test(source) && source.startsWith('.')) {\n kept.add(declaration.node);\n todo(\n declaration,\n 'entry',\n `\\`${source}\\` was written for v4 and Bootstrap: check what it still styles`,\n );\n } else {\n todo(\n declaration,\n 'entry',\n `\\`import '${source}'\\` is dropped with the container: import it where it is still needed`,\n );\n }\n }\n });\n\n const imports = root\n .find(j.ImportDeclaration)\n .filter((declaration) => kept.has(declaration.node))\n .map((declaration) => declaration)\n .nodes()\n .map((declaration) => {\n const copy = { ...declaration, comments: null };\n return j(copy as never).toSource({ quote: 'single' });\n });\n const modules = imports.filter((line) => !/^import '/.test(line));\n const styles = imports.filter((line) => /^import '/.test(line));\n const lines = [\n \"import { definePlugin } from '@panneau/core';\",\n '',\n ...(modules.length > 0 ? [...modules, ''] : []),\n ...(styles.length > 0 ? [...styles, ''] : []),\n '// The plugin of the project for Panneau 5, made by @panneau/codemods from the container of',\n '// v4. The entry passes it to `createPanneauApp({ element, plugins })`.',\n ...todos.map((message) => `// ${TODO_PREFIX} ${message}`),\n 'export default definePlugin({',\n \" id: 'project',\",\n ...kinds,\n '});',\n '',\n ];\n return lines.join('\\n');\n}\n\n/**\n * Rewrites a v4 container as a plugin module. Returns the new source, or `null` when the file\n * isn't a container.\n */\nexport function transformContainer(api: TransformApi): string | null {\n const { j, root } = api;\n const elements = findAppElements(j, root);\n if (elements.size() === 0) {\n return null;\n }\n if (elements.size() > 1) {\n api.report(\n elements.paths()[1] ?? null,\n 'entry',\n 'the app of v4 is rendered more than once: only the first one is converted',\n {\n todo: true,\n },\n );\n }\n return writePlugin(api, elements.paths()[0] as ASTPath<JSXElement>);\n}\n\n/**\n * Adds the plugins made from containers to the `plugins` of `createPanneauApp()` in the entry of\n * Panneau 5, when it is one of the files.\n */\nexport function transformAppEntry(api: TransformApi): void {\n const { j, root, project, path } = api;\n const create = root\n .find(j.ImportDeclaration, { source: { value: '@panneau/app' } })\n .find(j.ImportSpecifier, { imported: { name: 'createPanneauApp' } })\n .nodes()[0]?.local?.name;\n if (create === undefined || typeof create !== 'string') {\n return;\n }\n const modules = project.pluginModules();\n if (modules.length === 0) {\n return;\n }\n root.find(j.CallExpression, { callee: { type: 'Identifier', name: create } }).forEach(\n (call) => {\n const [options] = call.node.arguments;\n const plugins =\n options?.type === 'ObjectExpression'\n ? options.properties.find(\n (property) =>\n (property.type === 'ObjectProperty' ||\n property.type === 'Property') &&\n property.key.type === 'Identifier' &&\n property.key.name === 'plugins',\n )\n : undefined;\n const list = (plugins as { value?: { type: string; elements?: unknown[] } } | undefined)\n ?.value;\n if (list?.type !== 'ArrayExpression' || list.elements === undefined) {\n api.report(\n call,\n 'entry',\n `add the plugin of the project to the \\`plugins\\` of \\`${create}()\\``,\n { todo: true },\n );\n return;\n }\n modules.forEach((module, index) => {\n let specifier = relative(dirname(path), module)\n .replace(/\\\\/g, '/')\n .replace(/\\.(tsx?|jsx?|mjs)$/, '');\n if (!specifier.startsWith('.')) {\n specifier = `./${specifier}`;\n }\n const existing = root\n .find(j.ImportDeclaration, { source: { value: specifier } })\n .find(j.ImportDefaultSpecifier)\n .nodes()[0]?.local?.name;\n const name =\n (existing !== undefined ? String(existing) : null) ??\n (index === 0 ? 'projectPlugin' : `projectPlugin${index + 1}`);\n if (existing === undefined) {\n insertImport(\n api,\n j.importDeclaration(\n [j.importDefaultSpecifier(j.identifier(name))],\n j.literal(specifier),\n ),\n );\n }\n const elements = list.elements as { type: string; name?: string }[];\n if (\n !elements.some(\n (element) => element.type === 'Identifier' && element.name === name,\n )\n ) {\n elements.push(j.identifier(name));\n }\n });\n },\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { ASTPath, Collection, JSCodeshift } from 'jscodeshift';
2
+ import { type TransformApi } from '../context';
3
+ /** A component whose props the codemods read. */
4
+ export interface Component {
5
+ name: string;
6
+ /** The statement to insert the entry after. */
7
+ statement: ASTPath;
8
+ /** The props it destructures, or `null` when it doesn't. */
9
+ props: readonly string[] | null;
10
+ /** Whether it takes the rest of its props (`...props`). */
11
+ hasRest: boolean;
12
+ /** The type of its props, when it names one: `ClientFieldProps`. */
13
+ propsType: string | null;
14
+ }
15
+ /** The component that a file exports by default, when the codemods can read it. */
16
+ export declare function findDefaultComponent(j: JSCodeshift, root: Collection): Component | null;
17
+ /** The name of the entry of a component. */
18
+ export declare function getEntryName(component: string): string;
19
+ export declare function transformFieldEntries(api: TransformApi): void;
20
+ //# sourceMappingURL=fieldEntries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldEntries.d.ts","sourceRoot":"","sources":["../../src/transforms/fieldEntries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,OAAO,EACP,UAAU,EAGV,WAAW,EAEd,MAAM,aAAa,CAAC;AAErB,OAAO,EAAe,KAAK,YAAY,EAAkB,MAAM,YAAY,CAAC;AAE5E,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAoFD,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CA+BvF;AAED,4CAA4C;AAC5C,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAsOD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAK7D"}
@@ -0,0 +1,238 @@
1
+ import { TODO_PREFIX, addNamedImport } from "../context.js";
2
+ //#region src/transforms/fieldEntries.ts
3
+ function isFunction(node) {
4
+ return node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression";
5
+ }
6
+ function getTypeName(node) {
7
+ const annotation = node.typeAnnotation?.typeAnnotation;
8
+ return annotation?.type === "TSTypeReference" && annotation.typeName?.type === "Identifier" ? annotation.typeName.name ?? null : null;
9
+ }
10
+ function readPattern(pattern) {
11
+ const props = [];
12
+ let hasRest = false;
13
+ pattern.properties.forEach((property) => {
14
+ if (property.type === "RestElement" || property.type === "RestProperty") hasRest = true;
15
+ else if ((property.type === "ObjectProperty" || property.type === "Property") && !property.computed) {
16
+ const { key } = property;
17
+ if (key.type === "Identifier") props.push(key.name);
18
+ else if ((key.type === "StringLiteral" || key.type === "Literal") && typeof key.value === "string") props.push(key.value);
19
+ }
20
+ });
21
+ return {
22
+ props,
23
+ hasRest
24
+ };
25
+ }
26
+ /** The props a function component reads from its first parameter. */
27
+ function readProps(fn) {
28
+ const [param] = fn.params;
29
+ if (param === void 0) return {
30
+ props: [],
31
+ hasRest: false,
32
+ propsType: null
33
+ };
34
+ const propsType = getTypeName(param);
35
+ if (param.type === "ObjectPattern") return {
36
+ ...readPattern(param),
37
+ propsType
38
+ };
39
+ if (param.type === "Identifier" && fn.body.type === "BlockStatement") for (const statement of fn.body.body ?? []) {
40
+ const declaration = statement;
41
+ const declarator = declaration.declarations?.find((candidate) => candidate.id.type === "ObjectPattern" && candidate.init?.type === "Identifier" && candidate.init.name === param.name);
42
+ if (declaration.type === "VariableDeclaration" && declarator !== void 0) return {
43
+ ...readPattern(declarator.id),
44
+ propsType
45
+ };
46
+ }
47
+ return {
48
+ props: null,
49
+ hasRest: false,
50
+ propsType
51
+ };
52
+ }
53
+ /** The component that a file exports by default, when the codemods can read it. */
54
+ function findDefaultComponent(j, root) {
55
+ const exports = root.find(j.ExportDefaultDeclaration);
56
+ if (exports.size() !== 1) return null;
57
+ const exportPath = exports.get();
58
+ const declaration = exportPath.node.declaration;
59
+ if (isFunction(declaration)) {
60
+ if (declaration.id === null || declaration.id === void 0) return null;
61
+ return {
62
+ name: declaration.id.name,
63
+ statement: exportPath,
64
+ ...readProps(declaration)
65
+ };
66
+ }
67
+ if (declaration.type !== "Identifier" || declaration.name === void 0) return null;
68
+ const name = declaration.name;
69
+ const fn = root.find(j.FunctionDeclaration, { id: { name } });
70
+ if (fn.size() > 0) return {
71
+ name,
72
+ statement: exportPath,
73
+ ...readProps(fn.get().node)
74
+ };
75
+ const variable = root.findVariableDeclarators(name);
76
+ const init = variable.size() > 0 ? variable.get().node.init : null;
77
+ if (isFunction(init)) return {
78
+ name,
79
+ statement: exportPath,
80
+ ...readProps(init)
81
+ };
82
+ return null;
83
+ }
84
+ /** The name of the entry of a component. */
85
+ function getEntryName(component) {
86
+ return `${component}Entry`;
87
+ }
88
+ /** Props of `getFieldProps()`: the label, help, errors and state of the field. */
89
+ var FIELD_PROPS = /* @__PURE__ */ new Set([
90
+ "label",
91
+ "description",
92
+ "errors",
93
+ "required",
94
+ "readOnly",
95
+ "disabled",
96
+ "name",
97
+ "className",
98
+ "onBlur",
99
+ "placeholder"
100
+ ]);
101
+ /** v4 props that `getFieldProps()` gives under another name. */
102
+ var FIELD_PROP_ALIASES = {
103
+ helpText: "description",
104
+ help: "description"
105
+ };
106
+ /** How the entry gives the other v4 props, from `FieldEntryProps`. */
107
+ var PROP_SOURCES = {
108
+ value: "props.value",
109
+ onChange: "props.onChange",
110
+ fieldLocale: "props.locale",
111
+ locale: "props.locale",
112
+ locales: "locales"
113
+ };
114
+ /** Props the entry passes as they are, without a cast to the component's types. */
115
+ var UNCAST = /* @__PURE__ */ new Set(["onChange", "onBlur"]);
116
+ function isIdentifier(name) {
117
+ return /^[A-Za-z_$][\w$]*$/.test(name);
118
+ }
119
+ /** The code of the entry of a component. */
120
+ function writeEntry(api, component, todos) {
121
+ const { name, props, hasRest } = component;
122
+ const entry = getEntryName(name);
123
+ const read = (props ?? []).filter((prop) => ![
124
+ "children",
125
+ "key",
126
+ "ref"
127
+ ].includes(prop));
128
+ const forwarded = hasRest || props === null;
129
+ const cast = (prop) => api.typescript && !UNCAST.has(prop) ? ` as ${component.propsType ?? `Parameters<typeof ${name}>[0]`}['${prop}']` : "";
130
+ const settings = [];
131
+ const fieldProps = new Set(forwarded ? [] : [
132
+ "label",
133
+ "description",
134
+ "errors",
135
+ "required",
136
+ "className"
137
+ ]);
138
+ const implicit = forwarded ? ["value", "onChange"].filter((prop) => !read.includes(prop)) : [];
139
+ const implicitAttributes = implicit.length === 0 ? [] : api.typescript ? [`{...{ ${implicit.map((prop) => prop === "value" ? "value: props.value as never" : "onChange: props.onChange").join(", ")} }}`] : implicit.map((prop) => `${prop}={props.${prop}}`);
140
+ const attributes = read.filter((prop) => !(forwarded && FIELD_PROPS.has(prop))).map((prop) => {
141
+ const source = PROP_SOURCES[prop];
142
+ if (source !== void 0) return `${prop}={${source}${cast(prop)}}`;
143
+ const fieldProp = FIELD_PROPS.has(prop) ? prop : FIELD_PROP_ALIASES[prop];
144
+ if (fieldProp !== void 0) {
145
+ fieldProps.add(fieldProp);
146
+ return `${prop}={${fieldProp}${cast(prop)}}`;
147
+ }
148
+ settings.push(prop);
149
+ return `${prop}={props.field.settings${isIdentifier(prop) ? `?.${prop}` : `?.['${prop}']`}${cast(prop)}}`;
150
+ });
151
+ const usesLocales = read.includes("locales");
152
+ const lines = [];
153
+ const param = api.typescript ? "props: FieldEntryProps" : "props";
154
+ todos(settings).forEach((todo) => lines.push(`// ${TODO_PREFIX} ${todo}`));
155
+ lines.push(`/** The registry entry of \`${name}\` for Panneau 5: its props from the field (written by @panneau/codemods). */`);
156
+ lines.push(`export function ${entry}(${param}) {`);
157
+ if (fieldProps.size > 0) lines.push(` const { ${[...fieldProps].join(", ")} } = getFieldProps(props);`);
158
+ if (usesLocales) lines.push(" const { locales } = useFieldsContext();");
159
+ const element = [
160
+ ...forwarded ? ["{...props.field.settings}", "{...getFieldProps(props)}"] : [],
161
+ ...implicitAttributes,
162
+ ...attributes
163
+ ];
164
+ const indent = forwarded ? " " : " ";
165
+ const open = forwarded ? " " : " ";
166
+ lines.push(" return (");
167
+ if (!forwarded) {
168
+ lines.push(" <Fieldset");
169
+ [
170
+ "legend={label}",
171
+ "description={description}",
172
+ ...read.includes("errors") ? [] : ["errors={errors}"],
173
+ "isRequired={required}",
174
+ ...read.includes("className") ? [] : ["className={className}"]
175
+ ].forEach((attribute) => lines.push(` ${attribute}`));
176
+ lines.push(" >");
177
+ }
178
+ lines.push(`${open}<${name}`);
179
+ element.forEach((attribute) => lines.push(`${indent}${attribute}`));
180
+ lines.push(`${open}/>`);
181
+ if (!forwarded) lines.push(" </Fieldset>");
182
+ lines.push(" );");
183
+ lines.push("}");
184
+ return lines.join("\n");
185
+ }
186
+ /** Adds the entry of the default export of a field file, next to it. */
187
+ function addEntry(api) {
188
+ const { j, root } = api;
189
+ const component = findDefaultComponent(j, root);
190
+ if (component === null) {
191
+ api.report(root.find(j.ExportDefaultDeclaration).paths()[0] ?? null, "field-entry", "the default export is registered as a field, but it is not a function component the codemods can read: write its entry (`FieldEntryProps` of `@panneau/fields`)", { todo: true });
192
+ return;
193
+ }
194
+ const entry = getEntryName(component.name);
195
+ if (root.find(j.FunctionDeclaration, { id: { name: entry } }).size() > 0) return;
196
+ const statement = component.statement;
197
+ const statements = j(writeEntry(api, component, (settings) => {
198
+ const todos = [];
199
+ if (component.props === null) todos.push(`\`${component.name}\` doesn't destructure its props: \`${entry}\` passes it \`value\`, \`onChange\`, the settings of the field and \`getFieldProps()\`; check the props it reads`);
200
+ if ((component.props ?? []).includes("locales")) todos.push(`\`${component.name}\` renders every locale itself: its PHP field must not be \`localized()\`, which renders a field once per locale`);
201
+ if (settings.length > 0) todos.push(`\`${entry}\` reads ${settings.map((prop) => `\`${prop}\``).join(", ")} from the \`settings\` of the field: give ${settings.length > 1 ? "them" : "it"} with \`settings()\` of the PHP field`);
202
+ todos.forEach((todo) => api.report(statement, "field-entry", todo));
203
+ return todos;
204
+ })).find(j.Program).get().node.body;
205
+ if (statement.node.type === "ExportDefaultDeclaration" && statement.node.declaration.type === "Identifier") j(statement).insertBefore(statements);
206
+ else j(statement).insertAfter(statements);
207
+ if (component.hasRest) api.report(statement, "field-entry", `\`${component.name}\` forwards \`...props\`, which was the field's definition in v4: \`${entry}\` passes it \`value\`, \`onChange\`, the settings of the field and \`getFieldProps()\``);
208
+ if (api.typescript) addNamedImport(api, "@panneau/fields", "FieldEntryProps", { type: true });
209
+ addNamedImport(api, "@panneau/fields", "getFieldProps");
210
+ if (!component.hasRest && component.props !== null) addNamedImport(api, "@panneau/ui", "Fieldset");
211
+ if ((component.props ?? []).includes("locales")) addNamedImport(api, "@panneau/fields", "useFieldsContext");
212
+ }
213
+ /**
214
+ * `export { default as Agency } from './Agency'` re-exports the entry of a field file instead, so
215
+ * that the plugin registers entries.
216
+ */
217
+ function reExportEntries(api) {
218
+ const { j, root, project, path } = api;
219
+ root.find(j.ExportNamedDeclaration).forEach((declaration) => {
220
+ const source = declaration.node.source?.value;
221
+ if (typeof source !== "string") return;
222
+ const file = project.resolve(path, source);
223
+ const entry = file !== null && project.isField(file) ? project.info(file)?.fieldEntry : null;
224
+ if (entry === null || entry === void 0) return;
225
+ (declaration.node.specifiers ?? []).forEach((specifier) => {
226
+ const local = specifier.local;
227
+ if (specifier.type === "ExportSpecifier" && local?.name === "default") specifier.local = j.identifier(entry);
228
+ });
229
+ });
230
+ }
231
+ function transformFieldEntries(api) {
232
+ if (api.project.isField(api.path)) addEntry(api);
233
+ reExportEntries(api);
234
+ }
235
+ //#endregion
236
+ export { findDefaultComponent, getEntryName, transformFieldEntries };
237
+
238
+ //# sourceMappingURL=fieldEntries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldEntries.js","names":[],"sources":["../../src/transforms/fieldEntries.ts"],"sourcesContent":["// Custom field components of v4 got the field's definition as props. Panneau 5 renders a field\n// type with a registry entry, which gets `FieldEntryProps`. The codemods keep the component, and\n// add its entry next to it: the entry gives the component the props it read in v4.\nimport type {\n ASTPath,\n Collection,\n ExportDefaultDeclaration,\n ExportNamedDeclaration,\n JSCodeshift,\n ObjectPattern,\n} from 'jscodeshift';\n\nimport { TODO_PREFIX, type TransformApi, addNamedImport } from '../context';\n\n/** A component whose props the codemods read. */\nexport interface Component {\n name: string;\n /** The statement to insert the entry after. */\n statement: ASTPath;\n /** The props it destructures, or `null` when it doesn't. */\n props: readonly string[] | null;\n /** Whether it takes the rest of its props (`...props`). */\n hasRest: boolean;\n /** The type of its props, when it names one: `ClientFieldProps`. */\n propsType: string | null;\n}\n\ntype FunctionNode = {\n type: string;\n id?: { name: string } | null;\n params: { type: string; name?: string; typeAnnotation?: unknown }[];\n body: { type: string; body?: unknown[] };\n};\n\nfunction isFunction(node: { type: string } | null | undefined): node is FunctionNode {\n return (\n node?.type === 'FunctionDeclaration' ||\n node?.type === 'FunctionExpression' ||\n node?.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction getTypeName(node: unknown): string | null {\n const annotation = (node as { typeAnnotation?: { typeAnnotation?: unknown } | null })\n .typeAnnotation?.typeAnnotation as\n { type: string; typeName?: { type: string; name?: string } } | undefined;\n return annotation?.type === 'TSTypeReference' && annotation.typeName?.type === 'Identifier'\n ? (annotation.typeName.name ?? null)\n : null;\n}\n\nfunction readPattern(pattern: ObjectPattern): { props: string[]; hasRest: boolean } {\n const props: string[] = [];\n let hasRest = false;\n pattern.properties.forEach((property) => {\n if (property.type === 'RestElement' || property.type === 'RestProperty') {\n hasRest = true;\n } else if (\n (property.type === 'ObjectProperty' || property.type === 'Property') &&\n !property.computed\n ) {\n const { key } = property;\n if (key.type === 'Identifier') {\n props.push(key.name);\n } else if (\n (key.type === 'StringLiteral' || key.type === 'Literal') &&\n typeof key.value === 'string'\n ) {\n props.push(key.value);\n }\n }\n });\n return { props, hasRest };\n}\n\n/** The props a function component reads from its first parameter. */\nfunction readProps(fn: FunctionNode): Pick<Component, 'props' | 'hasRest' | 'propsType'> {\n const [param] = fn.params;\n if (param === undefined) {\n return { props: [], hasRest: false, propsType: null };\n }\n const propsType = getTypeName(param);\n if (param.type === 'ObjectPattern') {\n return { ...readPattern(param as unknown as ObjectPattern), propsType };\n }\n if (param.type === 'Identifier' && fn.body.type === 'BlockStatement') {\n // `function Field(props) { const { value, onChange } = props; … }`\n for (const statement of fn.body.body ?? []) {\n const declaration = statement as {\n type: string;\n declarations?: {\n id: { type: string };\n init?: { type: string; name?: string } | null;\n }[];\n };\n const declarator = declaration.declarations?.find(\n (candidate) =>\n candidate.id.type === 'ObjectPattern' &&\n candidate.init?.type === 'Identifier' &&\n candidate.init.name === param.name,\n );\n if (declaration.type === 'VariableDeclaration' && declarator !== undefined) {\n return { ...readPattern(declarator.id as unknown as ObjectPattern), propsType };\n }\n }\n }\n return { props: null, hasRest: false, propsType };\n}\n\n/** The component that a file exports by default, when the codemods can read it. */\nexport function findDefaultComponent(j: JSCodeshift, root: Collection): Component | null {\n const exports = root.find(j.ExportDefaultDeclaration);\n if (exports.size() !== 1) {\n return null;\n }\n const exportPath = exports.get() as ASTPath<ExportDefaultDeclaration>;\n const declaration = exportPath.node.declaration as {\n type: string;\n name?: string;\n id?: { name: string } | null;\n };\n if (isFunction(declaration)) {\n if (declaration.id === null || declaration.id === undefined) {\n return null;\n }\n return { name: declaration.id.name, statement: exportPath, ...readProps(declaration) };\n }\n if (declaration.type !== 'Identifier' || declaration.name === undefined) {\n return null;\n }\n const name = declaration.name;\n const fn = root.find(j.FunctionDeclaration, { id: { name } });\n if (fn.size() > 0) {\n return { name, statement: exportPath, ...readProps(fn.get().node as FunctionNode) };\n }\n const variable = root.findVariableDeclarators(name);\n const init = variable.size() > 0 ? (variable.get().node.init as FunctionNode | null) : null;\n if (isFunction(init)) {\n return { name, statement: exportPath, ...readProps(init) };\n }\n return null;\n}\n\n/** The name of the entry of a component. */\nexport function getEntryName(component: string): string {\n return `${component}Entry`;\n}\n\n/** Props of `getFieldProps()`: the label, help, errors and state of the field. */\nconst FIELD_PROPS = new Set([\n 'label',\n 'description',\n 'errors',\n 'required',\n 'readOnly',\n 'disabled',\n 'name',\n 'className',\n 'onBlur',\n 'placeholder',\n]);\n\n/** v4 props that `getFieldProps()` gives under another name. */\nconst FIELD_PROP_ALIASES: Readonly<Record<string, string>> = {\n helpText: 'description',\n help: 'description',\n};\n\n/** How the entry gives the other v4 props, from `FieldEntryProps`. */\nconst PROP_SOURCES: Readonly<Record<string, string>> = {\n value: 'props.value',\n onChange: 'props.onChange',\n fieldLocale: 'props.locale',\n locale: 'props.locale',\n locales: 'locales',\n};\n\n/** Props the entry passes as they are, without a cast to the component's types. */\nconst UNCAST = new Set(['onChange', 'onBlur']);\n\nfunction isIdentifier(name: string): boolean {\n return /^[A-Za-z_$][\\w$]*$/.test(name);\n}\n\n/** The code of the entry of a component. */\nfunction writeEntry(\n api: TransformApi,\n component: Component,\n todos: (settings: readonly string[]) => readonly string[],\n): string {\n const { name, props, hasRest } = component;\n const entry = getEntryName(name);\n const read = (props ?? []).filter((prop) => !['children', 'key', 'ref'].includes(prop));\n // A component that forwards its props to a field gets those of `getFieldProps()`, and the\n // field renders the label. Others are wrapped in a fieldset, as v4 wrapped them in a group.\n const forwarded = hasRest || props === null;\n const cast = (prop: string) =>\n api.typescript && !UNCAST.has(prop)\n ? ` as ${component.propsType ?? `Parameters<typeof ${name}>[0]`}['${prop}']`\n : '';\n const settings: string[] = [];\n // The props of `getFieldProps()` the entry reads by name.\n const fieldProps = new Set(\n forwarded ? [] : ['label', 'description', 'errors', 'required', 'className'],\n );\n // The value and its handler always go to a component that forwards its props, which may not\n // name them. In TypeScript, they go in an object spread: an attribute the props don't declare\n // is an error, a spread property isn't, and `never` fits the type of a value they declare.\n const implicit = forwarded ? ['value', 'onChange'].filter((prop) => !read.includes(prop)) : [];\n const implicitAttributes =\n implicit.length === 0\n ? []\n : api.typescript\n ? [\n `{...{ ${implicit\n .map((prop) =>\n prop === 'value'\n ? 'value: props.value as never'\n : 'onChange: props.onChange',\n )\n .join(', ')} }}`,\n ]\n : implicit.map((prop) => `${prop}={props.${prop}}`);\n const attributes = read\n .filter((prop) => !(forwarded && FIELD_PROPS.has(prop)))\n .map((prop) => {\n const source = PROP_SOURCES[prop];\n if (source !== undefined) {\n return `${prop}={${source}${cast(prop)}}`;\n }\n const fieldProp = FIELD_PROPS.has(prop) ? prop : FIELD_PROP_ALIASES[prop];\n if (fieldProp !== undefined) {\n fieldProps.add(fieldProp);\n return `${prop}={${fieldProp}${cast(prop)}}`;\n }\n settings.push(prop);\n const setting = isIdentifier(prop) ? `?.${prop}` : `?.['${prop}']`;\n return `${prop}={props.field.settings${setting}${cast(prop)}}`;\n });\n const usesLocales = read.includes('locales');\n const lines: string[] = [];\n const param = api.typescript ? 'props: FieldEntryProps' : 'props';\n todos(settings).forEach((todo) => lines.push(`// ${TODO_PREFIX} ${todo}`));\n lines.push(\n `/** The registry entry of \\`${name}\\` for Panneau 5: its props from the field (written by @panneau/codemods). */`,\n );\n lines.push(`export function ${entry}(${param}) {`);\n if (fieldProps.size > 0) {\n lines.push(` const { ${[...fieldProps].join(', ')} } = getFieldProps(props);`);\n }\n if (usesLocales) {\n lines.push(' const { locales } = useFieldsContext();');\n }\n const element = [\n ...(forwarded ? ['{...props.field.settings}', '{...getFieldProps(props)}'] : []),\n ...implicitAttributes,\n ...attributes,\n ];\n const indent = forwarded ? ' ' : ' ';\n const open = forwarded ? ' ' : ' ';\n lines.push(' return (');\n if (!forwarded) {\n lines.push(' <Fieldset');\n [\n 'legend={label}',\n 'description={description}',\n ...(read.includes('errors') ? [] : ['errors={errors}']),\n 'isRequired={required}',\n ...(read.includes('className') ? [] : ['className={className}']),\n ].forEach((attribute) => lines.push(` ${attribute}`));\n lines.push(' >');\n }\n lines.push(`${open}<${name}`);\n element.forEach((attribute) => lines.push(`${indent}${attribute}`));\n lines.push(`${open}/>`);\n if (!forwarded) {\n lines.push(' </Fieldset>');\n }\n lines.push(' );');\n lines.push('}');\n return lines.join('\\n');\n}\n\n/** Adds the entry of the default export of a field file, next to it. */\nfunction addEntry(api: TransformApi): void {\n const { j, root } = api;\n const component = findDefaultComponent(j, root);\n if (component === null) {\n api.report(\n root.find(j.ExportDefaultDeclaration).paths()[0] ?? null,\n 'field-entry',\n 'the default export is registered as a field, but it is not a function component the codemods can read: write its entry (`FieldEntryProps` of `@panneau/fields`)',\n { todo: true },\n );\n return;\n }\n const entry = getEntryName(component.name);\n if (root.find(j.FunctionDeclaration, { id: { name: entry } }).size() > 0) {\n return;\n }\n const statement = component.statement;\n // The messages that need a check go over the entry, where the codemods write them.\n const code = writeEntry(api, component, (settings) => {\n const todos: string[] = [];\n if (component.props === null) {\n todos.push(\n `\\`${component.name}\\` doesn't destructure its props: \\`${entry}\\` passes it \\`value\\`, \\`onChange\\`, the settings of the field and \\`getFieldProps()\\`; check the props it reads`,\n );\n }\n if ((component.props ?? []).includes('locales')) {\n todos.push(\n `\\`${component.name}\\` renders every locale itself: its PHP field must not be \\`localized()\\`, which renders a field once per locale`,\n );\n }\n if (settings.length > 0) {\n todos.push(\n `\\`${entry}\\` reads ${settings.map((prop) => `\\`${prop}\\``).join(', ')} from the \\`settings\\` of the field: give ${settings.length > 1 ? 'them' : 'it'} with \\`settings()\\` of the PHP field`,\n );\n }\n todos.forEach((todo) => api.report(statement, 'field-entry', todo));\n return todos;\n });\n const statements = j(code).find(j.Program).get().node.body as ExportNamedDeclaration[];\n if (\n statement.node.type === 'ExportDefaultDeclaration' &&\n (statement.node as ExportDefaultDeclaration).declaration.type === 'Identifier'\n ) {\n j(statement).insertBefore(statements);\n } else {\n j(statement).insertAfter(statements);\n }\n if (component.hasRest) {\n api.report(\n statement,\n 'field-entry',\n `\\`${component.name}\\` forwards \\`...props\\`, which was the field's definition in v4: \\`${entry}\\` passes it \\`value\\`, \\`onChange\\`, the settings of the field and \\`getFieldProps()\\``,\n );\n }\n if (api.typescript) {\n addNamedImport(api, '@panneau/fields', 'FieldEntryProps', { type: true });\n }\n addNamedImport(api, '@panneau/fields', 'getFieldProps');\n if (!component.hasRest && component.props !== null) {\n addNamedImport(api, '@panneau/ui', 'Fieldset');\n }\n if ((component.props ?? []).includes('locales')) {\n addNamedImport(api, '@panneau/fields', 'useFieldsContext');\n }\n}\n\n/**\n * `export { default as Agency } from './Agency'` re-exports the entry of a field file instead, so\n * that the plugin registers entries.\n */\nfunction reExportEntries(api: TransformApi): void {\n const { j, root, project, path } = api;\n root.find(j.ExportNamedDeclaration).forEach((declaration) => {\n const source = declaration.node.source?.value;\n if (typeof source !== 'string') {\n return;\n }\n const file = project.resolve(path, source);\n const entry =\n file !== null && project.isField(file) ? project.info(file)?.fieldEntry : null;\n if (entry === null || entry === undefined) {\n return;\n }\n (declaration.node.specifiers ?? []).forEach((specifier) => {\n const local = (specifier as { local?: { name: string } | null }).local;\n if (specifier.type === 'ExportSpecifier' && local?.name === 'default') {\n specifier.local = j.identifier(entry);\n }\n });\n });\n}\n\nexport function transformFieldEntries(api: TransformApi): void {\n if (api.project.isField(api.path)) {\n addEntry(api);\n }\n reExportEntries(api);\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type TransformApi } from '../context';
2
+ export declare function transformFields(api: TransformApi): void;
3
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/transforms/fields.ts"],"names":[],"mappings":"AAaA,OAAO,EACH,KAAK,YAAY,EAMpB,MAAM,YAAY,CAAC;AAobpB,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAUvD"}