@mittwald/flow-codemods 1.1.0 → 1.1.2

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 (29) hide show
  1. package/dist/migrations/accent-box-color-to-background-color/transform.js +175 -0
  2. package/dist/migrations/action-prop-to-on-action/transform.js +82 -0
  3. package/dist/migrations/align-to-combine/transform.js +166 -0
  4. package/dist/migrations/button-color-accent-to-success/transform.js +126 -0
  5. package/dist/migrations/button-props-interfaces/transform.js +178 -0
  6. package/dist/migrations/color-primary-to-default/transform.js +135 -0
  7. package/dist/migrations/imports-to-package-root/transform.js +93 -0
  8. package/dist/migrations/muted-action-error-to-abort-action-error/transform.js +198 -0
  9. package/dist/migrations/package.json +3 -0
  10. package/dist/migrations/password-tools-rule/transform.js +173 -0
  11. package/dist/migrations/password-tools-subpath-renamed/transform.js +51 -0
  12. package/dist/migrations.generated.js +1 -1
  13. package/dist/run/jscodeshift.d.ts +4 -1
  14. package/dist/run/jscodeshift.d.ts.map +1 -1
  15. package/dist/run/jscodeshift.js +60 -22
  16. package/dist/tools/package.json +3 -0
  17. package/dist/tools/to-remote-package.js +29 -0
  18. package/package.json +4 -6
  19. package/src/migrations/accent-box-color-to-background-color/transform.ts +0 -218
  20. package/src/migrations/action-prop-to-on-action/transform.ts +0 -110
  21. package/src/migrations/align-to-combine/transform.ts +0 -212
  22. package/src/migrations/button-color-accent-to-success/transform.ts +0 -164
  23. package/src/migrations/button-props-interfaces/transform.ts +0 -229
  24. package/src/migrations/color-primary-to-default/transform.ts +0 -173
  25. package/src/migrations/imports-to-package-root/transform.ts +0 -107
  26. package/src/migrations/muted-action-error-to-abort-action-error/transform.ts +0 -260
  27. package/src/migrations/password-tools-rule/transform.ts +0 -219
  28. package/src/migrations/password-tools-subpath-renamed/transform.ts +0 -61
  29. package/src/tools/to-remote-package.ts +0 -37
@@ -1,260 +0,0 @@
1
- import type { Transform } from "jscodeshift";
2
-
3
- /**
4
- * Renames `MutedActionError` to `AbortActionError`, along with its two static
5
- * helpers (alpha.712).
6
- *
7
- * ```diff
8
- * -throw new MutedActionError();
9
- * +throw new AbortActionError();
10
- * -MutedActionError.isMutedActionError(error);
11
- * +AbortActionError.isAbortActionError(error);
12
- * -MutedActionError.rethrowIfNotMuted(error);
13
- * +AbortActionError.rethrowIfNotAborted(error);
14
- * ```
15
- *
16
- * There is no alias for the old name, so a codebase still using it does not
17
- * compile.
18
- *
19
- * The thrown error's `name` changed too, so a `error.name ===
20
- * "MutedActionError"` comparison is rewritten as well — but only in a file that
21
- * imports the class, and only where the string is compared with `==`, `===`,
22
- * `!=` or `!==`. A check living in a file that never imports the class cannot
23
- * be recognised; grep for the string once when you are done.
24
- *
25
- * Only names imported from `@mittwald/flow-react-components` (including its
26
- * subpath entries) are touched. `@mittwald/flow-remote-react-components`
27
- * exports no error classes, so it cannot be in scope. A local alias (`import {
28
- * MutedActionError as Muted }`) keeps its alias — the static helpers on it are
29
- * renamed all the same. Namespace usages (`Flow.MutedActionError`) are
30
- * rewritten too.
31
- */
32
- const mutedActionErrorToAbortActionErrorTransform: Transform = (
33
- fileInfo,
34
- { j },
35
- ) => {
36
- const flowPackages = ["@mittwald/flow-react-components"];
37
- const oldName = "MutedActionError";
38
- const newName = "AbortActionError";
39
- const memberRenames = new Map([
40
- ["isMutedActionError", "isAbortActionError"],
41
- ["rethrowIfNotMuted", "rethrowIfNotAborted"],
42
- ]);
43
-
44
- const isFlowImport = (source: string): boolean =>
45
- flowPackages.some((pkg) => source === pkg || source.startsWith(`${pkg}/`));
46
-
47
- // ast-types models `importKind` on the declaration only, while babel also
48
- // puts it on the specifier — which is where a per-specifier `type X` lives.
49
- const isTypeOnly = (specifier: object): boolean =>
50
- (specifier as { importKind?: string }).importKind === "type";
51
- const makeValueImport = (specifier: object): void => {
52
- (specifier as { importKind?: string }).importKind = "value";
53
- };
54
-
55
- const root = j(fileInfo.source, { parser: "tsx" });
56
-
57
- /**
58
- * Local names bound to the class, alias or not — the static helpers hang off
59
- * these.
60
- */
61
- const classLocals = new Set<string>();
62
- /** Local names whose identifier itself has to be renamed (no alias in play). */
63
- const localRenames = new Set<string>();
64
- /** Local names of `import * as Flow` namespace imports from a Flow package. */
65
- const flowNamespaces = new Set<string>();
66
-
67
- const flowImports = root
68
- .find(j.ImportDeclaration)
69
- .filter((path) => isFlowImport(String(path.node.source.value)));
70
-
71
- /**
72
- * Local names the file already binds to the new name. Renaming onto one of
73
- * them must not add a second specifier, which would not parse.
74
- */
75
- const claimed = new Set<string>();
76
-
77
- flowImports.forEach((path) => {
78
- for (const specifier of path.node.specifiers ?? []) {
79
- if (
80
- specifier.type === "ImportNamespaceSpecifier" &&
81
- specifier.local?.name
82
- ) {
83
- flowNamespaces.add(String(specifier.local.name));
84
- continue;
85
- }
86
-
87
- if (
88
- specifier.type === "ImportSpecifier" &&
89
- specifier.imported.type === "Identifier" &&
90
- specifier.imported.name === newName
91
- ) {
92
- claimed.add(String(specifier.local?.name ?? newName));
93
- }
94
- }
95
- });
96
-
97
- flowImports.forEach((path) => {
98
- const specifiers = path.node.specifiers ?? [];
99
- const survivors: typeof specifiers = [];
100
-
101
- for (const specifier of specifiers) {
102
- if (
103
- specifier.type !== "ImportSpecifier" ||
104
- specifier.imported.type !== "Identifier" ||
105
- specifier.imported.name !== oldName
106
- ) {
107
- survivors.push(specifier);
108
- continue;
109
- }
110
-
111
- const local = String(specifier.local?.name ?? oldName);
112
- const isAlias = local !== oldName;
113
- const newLocal = isAlias ? local : newName;
114
-
115
- classLocals.add(local);
116
- if (!isAlias) {
117
- localRenames.add(local);
118
- }
119
-
120
- if (claimed.has(newLocal)) {
121
- // The name is already bound by another import — drop this specifier
122
- // instead of declaring it twice.
123
- if (!isTypeOnly(specifier)) {
124
- for (const kept of survivors) {
125
- if (
126
- kept.type === "ImportSpecifier" &&
127
- kept.imported.type === "Identifier" &&
128
- String(kept.local?.name ?? kept.imported.name) === newLocal
129
- ) {
130
- makeValueImport(kept);
131
- }
132
- }
133
- }
134
- continue;
135
- }
136
- claimed.add(newLocal);
137
-
138
- // Mutate in place so an `import type` / `type X` modifier survives.
139
- specifier.imported.name = newName;
140
- if (!isAlias && specifier.local) {
141
- specifier.local.name = newName;
142
- }
143
- survivors.push(specifier);
144
- }
145
-
146
- if (survivors.length === specifiers.length) {
147
- return;
148
- }
149
-
150
- // Leaving an emptied declaration behind would turn it into a side-effect
151
- // import of an entry the file no longer uses.
152
- if (survivors.length === 0) {
153
- j(path).remove();
154
- return;
155
- }
156
-
157
- path.node.specifiers = survivors;
158
- });
159
-
160
- if (classLocals.size === 0 && flowNamespaces.size === 0) {
161
- return fileInfo.source;
162
- }
163
-
164
- /** Does this expression denote the class — `Muted` or `Flow.MutedActionError`? */
165
- const isClassReference = (
166
- node:
167
- | {
168
- type: string;
169
- name?: unknown;
170
- object?: { type: string; name?: unknown } | null;
171
- property?: { type: string; name?: unknown } | null;
172
- }
173
- | null
174
- | undefined,
175
- ): boolean => {
176
- if (!node) {
177
- return false;
178
- }
179
- if (node.type === "Identifier") {
180
- return classLocals.has(String(node.name));
181
- }
182
- return (
183
- node.type === "MemberExpression" &&
184
- node.object?.type === "Identifier" &&
185
- flowNamespaces.has(String(node.object.name)) &&
186
- node.property?.type === "Identifier" &&
187
- String(node.property.name) === oldName
188
- );
189
- };
190
-
191
- // Static helpers first: the check reads the pre-rename object name.
192
- root.find(j.MemberExpression).forEach((path) => {
193
- const { object, property } = path.node;
194
- if (property.type !== "Identifier" || !isClassReference(object)) {
195
- return;
196
- }
197
-
198
- const renamed = memberRenames.get(property.name);
199
- if (renamed) {
200
- property.name = renamed;
201
- }
202
- });
203
-
204
- root.find(j.Identifier).forEach((path) => {
205
- const parent = path.parent.node;
206
-
207
- const isNamespaceMember =
208
- (parent.type === "MemberExpression" ||
209
- parent.type === "JSXMemberExpression") &&
210
- parent.property === path.node;
211
-
212
- if (isNamespaceMember) {
213
- const object = parent.object;
214
- if (
215
- (object.type === "Identifier" || object.type === "JSXIdentifier") &&
216
- path.node.name === oldName &&
217
- flowNamespaces.has(String(object.name))
218
- ) {
219
- path.node.name = newName;
220
- }
221
- return;
222
- }
223
-
224
- if (
225
- parent.type === "ImportSpecifier" ||
226
- parent.type === "JSXAttribute" ||
227
- ((parent.type === "ObjectProperty" || parent.type === "Property") &&
228
- parent.key === path.node &&
229
- !parent.computed)
230
- ) {
231
- return;
232
- }
233
-
234
- if (localRenames.has(path.node.name)) {
235
- path.node.name = newName;
236
- }
237
- });
238
-
239
- // `error.name === "MutedActionError"` — a comparison is the only place where
240
- // the string can be recognised as the error's name rather than free text.
241
- const comparisons = new Set(["===", "!==", "==", "!="]);
242
- root.find(j.BinaryExpression).forEach((path) => {
243
- if (!comparisons.has(path.node.operator)) {
244
- return;
245
- }
246
-
247
- for (const side of [path.node.left, path.node.right]) {
248
- if (
249
- (side.type === "StringLiteral" || side.type === "Literal") &&
250
- side.value === oldName
251
- ) {
252
- side.value = newName;
253
- }
254
- }
255
- });
256
-
257
- return root.toSource();
258
- };
259
-
260
- export default mutedActionErrorToAbortActionErrorTransform;
@@ -1,219 +0,0 @@
1
- import type { Transform } from "jscodeshift";
2
-
3
- /**
4
- * Replaces `AsyncRule` and `SyncRule` with `Rule` (alpha.802).
5
- *
6
- * `@mittwald/password-tools-js` merged both classes into a single abstract
7
- * `Rule`, and the `mittwald-password-tools-js` entry stopped re-exporting the
8
- * old names. A custom rule extends `Rule` and may return its result
9
- * synchronously or as a promise — the distinction the two classes encoded is
10
- * gone, so both names collapse onto the same one.
11
- *
12
- * Only names imported from the `mittwald-password-tools-js` entry of
13
- * `@mittwald/flow-react-components` are touched, so a same-named import from
14
- * another package is left alone. `@mittwald/flow-remote-react-components` has
15
- * no such entry. A name imported under a local alias (`import { AsyncRule as
16
- * Base }`) keeps its alias — only the imported name changes. Namespace usages
17
- * (`Pw.AsyncRule`) are rewritten as well.
18
- *
19
- * Because both names collapse onto `Rule`, a file importing more than one of
20
- * them would end up with a duplicate specifier. Those collapse onto one.
21
- */
22
- const passwordToolsRuleTransform: Transform = (fileInfo, { j }) => {
23
- const flowPackages = [
24
- "@mittwald/flow-react-components/mittwald-password-tools-js",
25
- ];
26
- const renames = new Map([
27
- ["AsyncRule", "Rule"],
28
- ["SyncRule", "Rule"],
29
- ]);
30
-
31
- const isFlowImport = (source: string): boolean =>
32
- flowPackages.includes(source);
33
-
34
- // ast-types models `importKind` on the declaration only, while babel also
35
- // puts it on the specifier — which is where a per-specifier `type X` lives.
36
- const isTypeOnly = (specifier: object): boolean =>
37
- (specifier as { importKind?: string }).importKind === "type";
38
- const makeValueImport = (specifier: object): void => {
39
- (specifier as { importKind?: string }).importKind = "value";
40
- };
41
-
42
- const root = j(fileInfo.source, { parser: "tsx" });
43
-
44
- /** Local identifiers that refer to a renamed export, mapped to the new name. */
45
- const localRenames = new Map<string, string>();
46
- /**
47
- * Whether any import specifier was rewritten. An aliased `Align as Row` adds
48
- * nothing to `localRenames` — its local name stays `Row` — so counting those
49
- * alone would treat an alias-only file as untouched and discard the rewrite.
50
- */
51
- let renamedAnImport = false;
52
- /** Local names of `import * as Flow` namespace imports from a Flow package. */
53
- const flowNamespaces = new Set<string>();
54
-
55
- const flowImports = root
56
- .find(j.ImportDeclaration)
57
- .filter((path) => isFlowImport(String(path.node.source.value)));
58
-
59
- /**
60
- * `imported:local` pairs the file already binds without a rename. A rename
61
- * that lands on one of these must not add a second specifier for it — the
62
- * name is available from the import that already carries it, which is also
63
- * the entry that really exports it.
64
- */
65
- const claimed = new Set<string>();
66
-
67
- flowImports.forEach((path) => {
68
- for (const specifier of path.node.specifiers ?? []) {
69
- if (
70
- specifier.type === "ImportNamespaceSpecifier" &&
71
- specifier.local?.name
72
- ) {
73
- flowNamespaces.add(String(specifier.local.name));
74
- continue;
75
- }
76
-
77
- if (
78
- specifier.type !== "ImportSpecifier" ||
79
- specifier.imported.type !== "Identifier" ||
80
- renames.has(specifier.imported.name)
81
- ) {
82
- continue;
83
- }
84
-
85
- claimed.add(
86
- `${specifier.imported.name}:${String(
87
- specifier.local?.name ?? specifier.imported.name,
88
- )}`,
89
- );
90
- }
91
- });
92
-
93
- flowImports.forEach((path) => {
94
- const specifiers = path.node.specifiers ?? [];
95
- const survivors: typeof specifiers = [];
96
-
97
- for (const specifier of specifiers) {
98
- if (
99
- specifier.type !== "ImportSpecifier" ||
100
- specifier.imported.type !== "Identifier"
101
- ) {
102
- survivors.push(specifier);
103
- continue;
104
- }
105
-
106
- const imported = specifier.imported.name;
107
- const renamed = renames.get(imported);
108
- if (!renamed) {
109
- survivors.push(specifier);
110
- continue;
111
- }
112
-
113
- const local = String(specifier.local?.name ?? imported);
114
- const isAlias = local !== imported;
115
- const newLocal = isAlias ? local : renamed;
116
- const key = `${renamed}:${newLocal}`;
117
-
118
- renamedAnImport = true;
119
- if (!isAlias) {
120
- // An aliased import keeps its local name and needs no further change.
121
- localRenames.set(local, renamed);
122
- }
123
-
124
- // Renaming can collide with a name the file already binds — directly, or
125
- // because a second old name maps onto the same new one. Either way the
126
- // binding already exists, so this specifier goes away instead of
127
- // producing a duplicate declaration, which would not parse.
128
- const collision = claimed.has(key);
129
- claimed.add(key);
130
- if (collision) {
131
- // A value import must not lose out to a type-only one.
132
- if (!isTypeOnly(specifier)) {
133
- for (const kept of survivors) {
134
- if (
135
- kept.type === "ImportSpecifier" &&
136
- kept.imported.type === "Identifier" &&
137
- `${kept.imported.name}:${String(
138
- kept.local?.name ?? kept.imported.name,
139
- )}` === key
140
- ) {
141
- makeValueImport(kept);
142
- }
143
- }
144
- }
145
- continue;
146
- }
147
-
148
- // Mutate in place so an `import type` / `type X` modifier survives.
149
- specifier.imported.name = renamed;
150
- if (!isAlias && specifier.local) {
151
- specifier.local.name = renamed;
152
- }
153
- survivors.push(specifier);
154
- }
155
-
156
- if (survivors.length === specifiers.length) {
157
- return;
158
- }
159
-
160
- // Every specifier moved to an import that already binds the name. Leaving
161
- // the declaration behind would turn it into a side-effect import of an
162
- // entry the file no longer uses.
163
- if (survivors.length === 0) {
164
- j(path).remove();
165
- return;
166
- }
167
-
168
- path.node.specifiers = survivors;
169
- });
170
-
171
- if (!renamedAnImport && flowNamespaces.size === 0) {
172
- return fileInfo.source;
173
- }
174
-
175
- /**
176
- * `JSXIdentifier` extends `Identifier`, so this one pass covers JSX tags,
177
- * value references and type references alike. Positions where the name is not
178
- * a reference to the import — an object key, a member's property, a JSX
179
- * attribute name — are skipped.
180
- */
181
- root.find(j.Identifier).forEach((path) => {
182
- const parent = path.parent.node;
183
-
184
- const isNamespaceMember =
185
- (parent.type === "MemberExpression" ||
186
- parent.type === "JSXMemberExpression") &&
187
- parent.property === path.node;
188
-
189
- if (isNamespaceMember) {
190
- const object = parent.object;
191
- if (object.type === "Identifier" || object.type === "JSXIdentifier") {
192
- const renamed = renames.get(path.node.name);
193
- if (renamed && flowNamespaces.has(String(object.name))) {
194
- path.node.name = renamed;
195
- }
196
- }
197
- return;
198
- }
199
-
200
- if (
201
- parent.type === "ImportSpecifier" ||
202
- parent.type === "JSXAttribute" ||
203
- ((parent.type === "ObjectProperty" || parent.type === "Property") &&
204
- parent.key === path.node &&
205
- !parent.computed)
206
- ) {
207
- return;
208
- }
209
-
210
- const renamed = localRenames.get(path.node.name);
211
- if (renamed) {
212
- path.node.name = renamed;
213
- }
214
- });
215
-
216
- return root.toSource();
217
- };
218
-
219
- export default passwordToolsRuleTransform;
@@ -1,61 +0,0 @@
1
- import type { Transform } from "jscodeshift";
2
-
3
- const oldPath = "@mittwald/flow-react-components/password-tools";
4
- const newPath = "@mittwald/flow-react-components/mittwald-password-tools-js";
5
-
6
- /**
7
- * Rewrites the module specifier
8
- * `@mittwald/flow-react-components/password-tools` to
9
- * `@mittwald/flow-react-components/mittwald-password-tools-js` (alpha.1000).
10
- *
11
- * Every form that names a module is covered: `import`, `import type`, `export …
12
- * from`, a side-effect import, `import()` and `require()`. Only that exact
13
- * specifier is touched — a deeper path under it never existed, so there is
14
- * nothing to prefix-match and no risk of rewriting an unrelated
15
- * `password-tools` of someone else's.
16
- */
17
- const passwordToolsSubpathRenamedTransform: Transform = (fileInfo, { j }) => {
18
- const root = j(fileInfo.source, { parser: "tsx" });
19
- let changed = false;
20
-
21
- const rewrite = (node: { value?: unknown }): void => {
22
- if (node.value === oldPath) {
23
- node.value = newPath;
24
- changed = true;
25
- }
26
- };
27
-
28
- // import … from "…", export … from "…", import "…"
29
- root.find(j.ImportDeclaration).forEach((path) => rewrite(path.node.source));
30
- root
31
- .find(j.ExportNamedDeclaration)
32
- .forEach((path) => path.node.source && rewrite(path.node.source));
33
- root
34
- .find(j.ExportAllDeclaration)
35
- .forEach((path) => rewrite(path.node.source));
36
-
37
- // import("…") and require("…")
38
- root
39
- .find(j.CallExpression)
40
- .filter((path) => {
41
- const callee = path.node.callee;
42
- return (
43
- callee.type === "Import" ||
44
- (callee.type === "Identifier" && callee.name === "require")
45
- );
46
- })
47
- .forEach((path) => {
48
- const [argument] = path.node.arguments;
49
- if (
50
- argument &&
51
- (argument.type === "StringLiteral" || argument.type === "Literal")
52
- ) {
53
- rewrite(argument);
54
- }
55
- });
56
-
57
- // Returning the source untouched keeps this file out of the "changed" count.
58
- return changed ? root.toSource() : fileInfo.source;
59
- };
60
-
61
- export default passwordToolsSubpathRenamedTransform;
@@ -1,37 +0,0 @@
1
- import type { Transform } from "jscodeshift";
2
-
3
- /**
4
- * Ports imports from `@mittwald/flow-react-components` to
5
- * `@mittwald/flow-remote-react-components`.
6
- *
7
- * Not a migration: no version range calls for it, so it has no catalogue entry
8
- * (see `notAMigration` in `src/tests/remoteScope.test.ts`) and lives outside
9
- * `src/migrations`, in `src/tools` alongside any other transform the CLI runs
10
- * by id without a matching entry. It is still resolved and run the same way a
11
- * migration's `transform.ts` is — `transformExists`/`runCodemod` in
12
- * `src/run/jscodeshift.ts` fall back to this directory when an id names no
13
- * migration.
14
- */
15
- const toRemotePackageTransform: Transform = (fileInfo, { j }) => {
16
- const flowPackage = "@mittwald/flow-react-components";
17
-
18
- const root = j(fileInfo.source, {
19
- parser: "ts",
20
- });
21
-
22
- root
23
- .find(j.ImportDeclaration)
24
- .filter((i) => String(i.node.source.value).startsWith(flowPackage))
25
- .forEach((i) => {
26
- const importPath = String(i.node.source.value);
27
-
28
- i.node.source.value = importPath.replace(
29
- flowPackage,
30
- "@mittwald/flow-remote-react-components",
31
- );
32
- });
33
-
34
- return root.toSource();
35
- };
36
-
37
- export default toRemotePackageTransform;