@mittwald/flow-codemods 1.1.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/catalog/entries.d.ts +17 -0
- package/dist/catalog/entries.d.ts.map +1 -0
- package/dist/catalog/entries.js +9 -0
- package/dist/catalog/select.d.ts +28 -0
- package/dist/catalog/select.d.ts.map +1 -0
- package/dist/catalog/select.js +27 -0
- package/dist/catalog/types.d.ts +48 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +0 -0
- package/dist/checks/context.d.ts +16 -0
- package/dist/checks/context.d.ts.map +1 -0
- package/dist/checks/context.js +72 -0
- package/dist/checks/load.d.ts +13 -0
- package/dist/checks/load.d.ts.map +1 -0
- package/dist/checks/load.js +53 -0
- package/dist/checks/types.d.ts +49 -0
- package/dist/checks/types.d.ts.map +1 -0
- package/dist/checks/types.js +0 -0
- package/dist/cli/args.d.ts +22 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +56 -0
- package/dist/cli/choose.d.ts +31 -0
- package/dist/cli/choose.d.ts.map +1 -0
- package/dist/cli/choose.js +31 -0
- package/dist/cli/codemod.d.ts +24 -0
- package/dist/cli/codemod.d.ts.map +1 -0
- package/dist/cli/codemod.js +76 -0
- package/dist/cli/detect.d.ts +35 -0
- package/dist/cli/detect.d.ts.map +1 -0
- package/dist/cli/detect.js +62 -0
- package/dist/cli/list.d.ts +71 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +293 -0
- package/dist/cli/upgrade.d.ts +34 -0
- package/dist/cli/upgrade.d.ts.map +1 -0
- package/dist/cli/upgrade.js +217 -0
- package/dist/cli/verify.d.ts +77 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +148 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/detect/align-to-combine.d.ts +8 -0
- package/dist/detect/align-to-combine.d.ts.map +1 -0
- package/dist/detect/align-to-combine.js +9 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.d.ts +9 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.d.ts.map +1 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.js +10 -0
- package/dist/detect/tooltip-trigger-delay-type.d.ts +11 -0
- package/dist/detect/tooltip-trigger-delay-type.d.ts.map +1 -0
- package/dist/detect/tooltip-trigger-delay-type.js +12 -0
- package/dist/flowPackages.generated.d.ts +3 -0
- package/dist/flowPackages.generated.d.ts.map +1 -0
- package/dist/flowPackages.generated.js +18 -0
- package/dist/git.d.ts +13 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +41 -0
- package/dist/install.d.ts +37 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +71 -0
- package/dist/manifest.d.ts +45 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +88 -0
- package/dist/migrations.generated.d.ts +4 -0
- package/dist/migrations.generated.d.ts.map +1 -0
- package/dist/migrations.generated.js +213 -0
- package/dist/resolve/range.d.ts +50 -0
- package/dist/resolve/range.d.ts.map +1 -0
- package/dist/resolve/range.js +173 -0
- package/dist/resolve/registry.d.ts +33 -0
- package/dist/resolve/registry.d.ts.map +1 -0
- package/dist/resolve/registry.js +69 -0
- package/dist/resolve/target.d.ts +63 -0
- package/dist/resolve/target.d.ts.map +1 -0
- package/dist/resolve/target.js +115 -0
- package/dist/run/jscodeshift.d.ts +42 -0
- package/dist/run/jscodeshift.d.ts.map +1 -0
- package/dist/run/jscodeshift.js +88 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/verify/align-to-combine.d.ts +11 -0
- package/dist/verify/align-to-combine.d.ts.map +1 -0
- package/dist/verify/align-to-combine.js +22 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.d.ts +14 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.d.ts.map +1 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.js +21 -0
- package/dist/verify/tooltip-trigger-delay-type.d.ts +13 -0
- package/dist/verify/tooltip-trigger-delay-type.d.ts.map +1 -0
- package/dist/verify/tooltip-trigger-delay-type.js +18 -0
- package/package.json +41 -0
- package/src/migrations/accent-box-color-to-background-color/transform.ts +218 -0
- package/src/migrations/action-prop-to-on-action/transform.ts +110 -0
- package/src/migrations/align-to-combine/transform.ts +212 -0
- package/src/migrations/button-color-accent-to-success/transform.ts +164 -0
- package/src/migrations/button-props-interfaces/transform.ts +229 -0
- package/src/migrations/color-primary-to-default/transform.ts +173 -0
- package/src/migrations/imports-to-package-root/transform.ts +107 -0
- package/src/migrations/muted-action-error-to-abort-action-error/transform.ts +260 -0
- package/src/migrations/password-tools-rule/transform.ts +219 -0
- package/src/migrations/password-tools-subpath-renamed/transform.ts +61 -0
- package/src/tools/to-remote-package.ts +37 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ConditionalExpression, Transform } from "jscodeshift";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An expression that can be a prop's value. Deliberately not
|
|
5
|
+
* `JSXExpressionContainer["expression"]`: that also admits `JSXEmptyExpression`
|
|
6
|
+
* (`color={/* a comment *\/}`), which cannot be written back into a ternary
|
|
7
|
+
* branch. The call site filters it out instead.
|
|
8
|
+
*/
|
|
9
|
+
type ValueNode = ConditionalExpression["consequent"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renames the `color="primary"` prop value to `color="default"` on the five
|
|
13
|
+
* components changed by the alpha.837 design update: `Breadcrumb`,
|
|
14
|
+
* `HeaderNavigation`, `Heading`, `IllustratedMessage` and `Link`.
|
|
15
|
+
*
|
|
16
|
+
* The scope is deliberately narrow. Only JSX elements that resolve to one of
|
|
17
|
+
* those components — imported (named or as a namespace) from
|
|
18
|
+
* `@mittwald/flow-react-components` or
|
|
19
|
+
* `@mittwald/flow-remote-react-components`, including their subpath entries —
|
|
20
|
+
* are touched. Components that still accept `color="primary"` (e.g. `Button`)
|
|
21
|
+
* and same-named elements from other packages (e.g. a router `Link`) are left
|
|
22
|
+
* untouched.
|
|
23
|
+
*
|
|
24
|
+
* Only literal `"primary"` values are rewritten — either as the whole value
|
|
25
|
+
* (`color="primary"`, `color={"primary"}`) or in a value position inside a
|
|
26
|
+
* dynamic one (`color={cond ? "secondary" : "primary"}`, `color={override ??
|
|
27
|
+
* "primary"}`). A value the transform cannot see into (`color={someVariable}`)
|
|
28
|
+
* is left alone.
|
|
29
|
+
*/
|
|
30
|
+
const colorPrimaryToDefaultTransform: Transform = (fileInfo, { j }) => {
|
|
31
|
+
const flowPackages = [
|
|
32
|
+
"@mittwald/flow-react-components",
|
|
33
|
+
"@mittwald/flow-remote-react-components",
|
|
34
|
+
];
|
|
35
|
+
const affectedComponents = new Set([
|
|
36
|
+
"Breadcrumb",
|
|
37
|
+
"HeaderNavigation",
|
|
38
|
+
"Heading",
|
|
39
|
+
"IllustratedMessage",
|
|
40
|
+
"Link",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const isFlowImport = (source: string): boolean =>
|
|
44
|
+
flowPackages.some((pkg) => source === pkg || source.startsWith(`${pkg}/`));
|
|
45
|
+
|
|
46
|
+
const isPrimaryLiteral = (node: unknown): boolean => {
|
|
47
|
+
if (!node || typeof node !== "object" || !("type" in node)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const { type } = node as { type: string };
|
|
51
|
+
return (
|
|
52
|
+
(type === "StringLiteral" || type === "Literal") &&
|
|
53
|
+
(node as { value?: unknown }).value === "primary"
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Rewrites every `"primary"` sitting in a position whose value can reach the
|
|
59
|
+
* prop: the expression itself, both branches of a ternary, and the operands
|
|
60
|
+
* of `??`/`||` — plus the right operand of `&&`, since `&&` yields its left
|
|
61
|
+
* operand only when that operand is falsy and a non-empty string never is. A
|
|
62
|
+
* `"primary"` anywhere else is not a value that reaches the prop (an object
|
|
63
|
+
* key, an index into a lookup table) and stays as it is.
|
|
64
|
+
*
|
|
65
|
+
* This cannot move into a shared module: the CLI loads this file straight out
|
|
66
|
+
* of the published package, which ships only
|
|
67
|
+
* `src/migrations/**\/transform.ts` — see this package's AGENTS.md.
|
|
68
|
+
*/
|
|
69
|
+
const rewriteValuePositions = (node: ValueNode): ValueNode => {
|
|
70
|
+
if (isPrimaryLiteral(node)) {
|
|
71
|
+
return j.stringLiteral("default");
|
|
72
|
+
}
|
|
73
|
+
if (node?.type === "ConditionalExpression") {
|
|
74
|
+
node.consequent = rewriteValuePositions(node.consequent);
|
|
75
|
+
node.alternate = rewriteValuePositions(node.alternate);
|
|
76
|
+
return node;
|
|
77
|
+
}
|
|
78
|
+
if (node?.type === "LogicalExpression") {
|
|
79
|
+
if (node.operator !== "&&") {
|
|
80
|
+
node.left = rewriteValuePositions(node.left);
|
|
81
|
+
}
|
|
82
|
+
node.right = rewriteValuePositions(node.right);
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
return node;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const root = j(fileInfo.source, { parser: "tsx" });
|
|
89
|
+
|
|
90
|
+
// Local JSX identifier -> canonical component name (resolves `as` aliases).
|
|
91
|
+
const localToComponent = new Map<string, string>();
|
|
92
|
+
// Local names of `import * as Flow` namespace imports from a Flow package.
|
|
93
|
+
const flowNamespaces = new Set<string>();
|
|
94
|
+
|
|
95
|
+
root
|
|
96
|
+
.find(j.ImportDeclaration)
|
|
97
|
+
.filter((path) => isFlowImport(String(path.node.source.value)))
|
|
98
|
+
.forEach((path) => {
|
|
99
|
+
for (const specifier of path.node.specifiers ?? []) {
|
|
100
|
+
if (
|
|
101
|
+
specifier.type === "ImportSpecifier" &&
|
|
102
|
+
specifier.imported.type === "Identifier" &&
|
|
103
|
+
affectedComponents.has(specifier.imported.name)
|
|
104
|
+
) {
|
|
105
|
+
localToComponent.set(
|
|
106
|
+
String(specifier.local?.name ?? specifier.imported.name),
|
|
107
|
+
String(specifier.imported.name),
|
|
108
|
+
);
|
|
109
|
+
} else if (
|
|
110
|
+
specifier.type === "ImportNamespaceSpecifier" &&
|
|
111
|
+
specifier.local
|
|
112
|
+
) {
|
|
113
|
+
flowNamespaces.add(String(specifier.local.name));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (localToComponent.size === 0 && flowNamespaces.size === 0) {
|
|
119
|
+
return fileInfo.source;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
root.find(j.JSXOpeningElement).forEach((path) => {
|
|
123
|
+
const name = path.node.name;
|
|
124
|
+
|
|
125
|
+
let isAffected = false;
|
|
126
|
+
if (name.type === "JSXIdentifier") {
|
|
127
|
+
isAffected = localToComponent.has(name.name);
|
|
128
|
+
} else if (
|
|
129
|
+
name.type === "JSXMemberExpression" &&
|
|
130
|
+
name.object.type === "JSXIdentifier" &&
|
|
131
|
+
name.property.type === "JSXIdentifier"
|
|
132
|
+
) {
|
|
133
|
+
isAffected =
|
|
134
|
+
flowNamespaces.has(name.object.name) &&
|
|
135
|
+
affectedComponents.has(name.property.name);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!isAffected) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const attribute of path.node.attributes ?? []) {
|
|
143
|
+
if (
|
|
144
|
+
attribute.type !== "JSXAttribute" ||
|
|
145
|
+
attribute.name.type !== "JSXIdentifier" ||
|
|
146
|
+
attribute.name.name !== "color"
|
|
147
|
+
) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const value = attribute.value;
|
|
152
|
+
|
|
153
|
+
// color="primary"
|
|
154
|
+
if (isPrimaryLiteral(value)) {
|
|
155
|
+
attribute.value = j.stringLiteral("default");
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// color={"primary"}, and every value position inside a dynamic value:
|
|
160
|
+
// color={cond ? "primary" : "x"}, color={fallback ?? "primary"}.
|
|
161
|
+
if (
|
|
162
|
+
value?.type === "JSXExpressionContainer" &&
|
|
163
|
+
value.expression.type !== "JSXEmptyExpression"
|
|
164
|
+
) {
|
|
165
|
+
value.expression = rewriteValuePositions(value.expression);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return root.toSource();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export default colorPrimaryToDefaultTransform;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Transform } from "jscodeshift";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Subpaths this transform must leave alone. Two groups, and they are different
|
|
5
|
+
* kinds of fact.
|
|
6
|
+
*
|
|
7
|
+
* **Subpaths the package still exports.** alpha.28 collapsed 94 subpath exports
|
|
8
|
+
* onto the package root, and performing that collapse is this transform's whole
|
|
9
|
+
* job — but it did it with a catch-all `else`, so every subpath introduced
|
|
10
|
+
* _after_ alpha.28 was collateral. That stayed invisible while selection had a
|
|
11
|
+
* lower bound; it no longer has one (see `selectEntries`), so the transform now
|
|
12
|
+
* reaches current code, where flattening `all-layered.css` silently turns a
|
|
13
|
+
* stylesheet import into a JS one and flattening `mittwald-password-tools-js`
|
|
14
|
+
* moves `Rule` onto a root that does not export it.
|
|
15
|
+
*
|
|
16
|
+
* **Subpaths another entry owns.** `password-tools` is renamed to
|
|
17
|
+
* `mittwald-password-tools-js` by `password-tools-subpath-renamed`, whose
|
|
18
|
+
* `since` sorts it _after_ this entry. Flattening it first leaves that
|
|
19
|
+
* migration nothing to find, and both report success.
|
|
20
|
+
*
|
|
21
|
+
* Matched exactly, not by prefix: `react-hook-form/useFoo` still collapses onto
|
|
22
|
+
* `react-hook-form`, which is what alpha.28 did to it.
|
|
23
|
+
*
|
|
24
|
+
* `transform.test.ts` beside this file holds the first group against the
|
|
25
|
+
* package's real `exports` map, so a new subpath cannot quietly become
|
|
26
|
+
* collateral again.
|
|
27
|
+
*/
|
|
28
|
+
const keptSubpaths = new Set([
|
|
29
|
+
// the current export surface
|
|
30
|
+
"internal",
|
|
31
|
+
"flr-universal",
|
|
32
|
+
"nextjs",
|
|
33
|
+
"react-hook-form",
|
|
34
|
+
"mittwald-password-tools-js",
|
|
35
|
+
"all.css",
|
|
36
|
+
"all-layered.css",
|
|
37
|
+
"component-index",
|
|
38
|
+
"doc-properties",
|
|
39
|
+
// owned by password-tools-subpath-renamed
|
|
40
|
+
"password-tools",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const importsToPackageRootTransform: Transform = (fileInfo, { j }) => {
|
|
44
|
+
const flowPackage = "@mittwald/flow-react-components";
|
|
45
|
+
|
|
46
|
+
const root = j(fileInfo.source, {
|
|
47
|
+
parser: "ts",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
root
|
|
51
|
+
.find(j.ImportDeclaration)
|
|
52
|
+
.filter((i) => String(i.node.source.value).startsWith(`${flowPackage}/`))
|
|
53
|
+
.forEach((i) => {
|
|
54
|
+
const specifiers = i.node.specifiers ?? [];
|
|
55
|
+
const importPath = String(i.node.source.value);
|
|
56
|
+
const importRelativePath = importPath.slice(flowPackage.length + 1);
|
|
57
|
+
|
|
58
|
+
if (keptSubpaths.has(importRelativePath)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
importRelativePath === "all.css" ||
|
|
64
|
+
importRelativePath === "globals.css" ||
|
|
65
|
+
importRelativePath === "global.css"
|
|
66
|
+
) {
|
|
67
|
+
i.node.source.value = `${flowPackage}/all.css`;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (importRelativePath.startsWith("react-hook-form")) {
|
|
72
|
+
i.node.source.value = `${flowPackage}/react-hook-form`;
|
|
73
|
+
} else if (importRelativePath.startsWith("nextjs")) {
|
|
74
|
+
i.node.source.value = `${flowPackage}/nextjs`;
|
|
75
|
+
} else {
|
|
76
|
+
i.node.source.value = flowPackage;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
specifiers.forEach((s, i) => {
|
|
80
|
+
if (
|
|
81
|
+
s.type === "ImportDefaultSpecifier" ||
|
|
82
|
+
s.type === "ImportSpecifier"
|
|
83
|
+
) {
|
|
84
|
+
// `name` is typed `string | Identifier` in ast-types; for the
|
|
85
|
+
// specifiers handled here it is always the plain name.
|
|
86
|
+
const name = String(
|
|
87
|
+
(s.type === "ImportDefaultSpecifier"
|
|
88
|
+
? s.local?.name
|
|
89
|
+
: s.imported?.name) ?? "",
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
specifiers[i] = {
|
|
93
|
+
...s,
|
|
94
|
+
type: "ImportSpecifier",
|
|
95
|
+
imported: {
|
|
96
|
+
type: "Identifier",
|
|
97
|
+
name,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return root.toSource();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default importsToPackageRootTransform;
|
|
@@ -0,0 +1,260 @@
|
|
|
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;
|