@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,11 @@
|
|
|
1
|
+
import type { Verifier } from "../checks/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shape "residual pattern + typecheck" (~14 of the 23 entries). Translated from
|
|
4
|
+
* the catalogue's own verify prose: "tsc --noEmit passes, and `rg '\bAlign\b'`
|
|
5
|
+
* finds no Flow import." The pattern half is decidable — `search` runs it, and
|
|
6
|
+
* `ok` reflects it fully — so there is no separate judgement call left for a
|
|
7
|
+
* person and `hints` stays empty. The typecheck half is not decidable here
|
|
8
|
+
* (this package wraps no subprocess), so it stays a `typecheckHint`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const verifier: Verifier;
|
|
11
|
+
//# sourceMappingURL=align-to-combine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"align-to-combine.d.ts","sourceRoot":"","sources":["../../src/verify/align-to-combine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,EAAE,QActB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape "residual pattern + typecheck" (~14 of the 23 entries). Translated from
|
|
3
|
+
* the catalogue's own verify prose: "tsc --noEmit passes, and `rg '\bAlign\b'`
|
|
4
|
+
* finds no Flow import." The pattern half is decidable — `search` runs it, and
|
|
5
|
+
* `ok` reflects it fully — so there is no separate judgement call left for a
|
|
6
|
+
* person and `hints` stays empty. The typecheck half is not decidable here
|
|
7
|
+
* (this package wraps no subprocess), so it stays a `typecheckHint`.
|
|
8
|
+
*/
|
|
9
|
+
export const verifier = {
|
|
10
|
+
verify: async (context) => {
|
|
11
|
+
// Unrestricted extensions, translated from `rg '\bAlign\b'` (no `-t ts`):
|
|
12
|
+
// the codemod also rewrites the CSS class name and component tokens, so a
|
|
13
|
+
// residual `Align` can show up outside a `.ts`/`.tsx` file too.
|
|
14
|
+
const findings = await context.search(/\bAlign\b/);
|
|
15
|
+
return {
|
|
16
|
+
ok: findings.length === 0,
|
|
17
|
+
findings,
|
|
18
|
+
hints: [],
|
|
19
|
+
typecheckHint: "Run `tsc --noEmit` too: `Align`/`AlignProps` still resolve (this rename is a deprecation, not a removal), so a missed usage does not fail to compile — the residual-usage search above is the real check for that, not the type check.",
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Verifier } from "../checks/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shape "a person must judge it" (~6 of the 23 entries). Translated from the
|
|
4
|
+
* catalogue's own verify prose, which says outright that no compiler check
|
|
5
|
+
* catches this: the return type widened from `() => void` to `() => unknown`,
|
|
6
|
+
* so every previous handler stays assignable and `tsc --noEmit` passes
|
|
7
|
+
* unchanged whether or not a handler now vetoes a close/open it never meant to.
|
|
8
|
+
* `ok: true` here means only "nothing this module can decide is wrong" — not
|
|
9
|
+
* that the review happened, which is why the review instruction stays in
|
|
10
|
+
* `hints` rather than moving to `typecheckHint` alongside the (here, negative)
|
|
11
|
+
* fact about the typechecker.
|
|
12
|
+
*/
|
|
13
|
+
export declare const verifier: Verifier;
|
|
14
|
+
//# sourceMappingURL=overlay-controller-add-on-close-return-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-controller-add-on-close-return-type.d.ts","sourceRoot":"","sources":["../../src/verify/overlay-controller-add-on-close-return-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,EAAE,QAUtB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape "a person must judge it" (~6 of the 23 entries). Translated from the
|
|
3
|
+
* catalogue's own verify prose, which says outright that no compiler check
|
|
4
|
+
* catches this: the return type widened from `() => void` to `() => unknown`,
|
|
5
|
+
* so every previous handler stays assignable and `tsc --noEmit` passes
|
|
6
|
+
* unchanged whether or not a handler now vetoes a close/open it never meant to.
|
|
7
|
+
* `ok: true` here means only "nothing this module can decide is wrong" — not
|
|
8
|
+
* that the review happened, which is why the review instruction stays in
|
|
9
|
+
* `hints` rather than moving to `typecheckHint` alongside the (here, negative)
|
|
10
|
+
* fact about the typechecker.
|
|
11
|
+
*/
|
|
12
|
+
export const verifier = {
|
|
13
|
+
verify: async () => ({
|
|
14
|
+
ok: true,
|
|
15
|
+
findings: [],
|
|
16
|
+
hints: [
|
|
17
|
+
"Review each handler by hand for one that can return `false` — `executeHandlers` now treats that as a veto and cancels the close/open, even for a handler that returned `false` incidentally with no intent to block anything.",
|
|
18
|
+
],
|
|
19
|
+
typecheckHint: "`tsc --noEmit` passes before and after either way: `() => unknown` accepts every previous `addOnClose`/`addOnOpen` handler, so no compiler check catches this one — the review above is the real check.",
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Verifier } from "../checks/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shape "typecheck alone suffices" (~3 of the 23 entries). Translated from the
|
|
4
|
+
* catalogue's own verify prose: "tsc --noEmit passes — a numeric `delay` is a
|
|
5
|
+
* type error, so no separate `rg` check is needed." There is nothing for
|
|
6
|
+
* `search` to run here — a residual numeric literal is a type-level fact, not a
|
|
7
|
+
* decidable text pattern — so this always returns `ok: true` with no further
|
|
8
|
+
* judgement call, and leans entirely on `typecheckHint`. That is not "done":
|
|
9
|
+
* the compiler run itself is outside what this package executes (no
|
|
10
|
+
* subprocess), so it stays a person's job.
|
|
11
|
+
*/
|
|
12
|
+
export declare const verifier: Verifier;
|
|
13
|
+
//# sourceMappingURL=tooltip-trigger-delay-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-trigger-delay-type.d.ts","sourceRoot":"","sources":["../../src/verify/tooltip-trigger-delay-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,EAAE,QAQtB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape "typecheck alone suffices" (~3 of the 23 entries). Translated from the
|
|
3
|
+
* catalogue's own verify prose: "tsc --noEmit passes — a numeric `delay` is a
|
|
4
|
+
* type error, so no separate `rg` check is needed." There is nothing for
|
|
5
|
+
* `search` to run here — a residual numeric literal is a type-level fact, not a
|
|
6
|
+
* decidable text pattern — so this always returns `ok: true` with no further
|
|
7
|
+
* judgement call, and leans entirely on `typecheckHint`. That is not "done":
|
|
8
|
+
* the compiler run itself is outside what this package executes (no
|
|
9
|
+
* subprocess), so it stays a person's job.
|
|
10
|
+
*/
|
|
11
|
+
export const verifier = {
|
|
12
|
+
verify: async () => ({
|
|
13
|
+
ok: true,
|
|
14
|
+
findings: [],
|
|
15
|
+
hints: [],
|
|
16
|
+
typecheckHint: 'Run `tsc --noEmit` — a numeric `delay` on `TooltipTrigger` is a type error under the new signature (only `"default"` and `"long"` are accepted), so a clean typecheck is sufficient on its own.',
|
|
17
|
+
}),
|
|
18
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mittwald/flow-codemods",
|
|
3
|
+
"version": "1.1.0-next.10",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Codemods and an upgrade CLI for consumers of Flow, mittwald's design system",
|
|
6
|
+
"homepage": "https://flow.mittwald.de",
|
|
7
|
+
"repository": "https://github.com/mittwald/flow",
|
|
8
|
+
"bin": {
|
|
9
|
+
"flow-codemods": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src/migrations/**/transform.ts",
|
|
14
|
+
"src/tools/to-remote-package.ts"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsx dev/generateCli.ts && tsc -p tsconfig.build.json",
|
|
21
|
+
"test:compile": "tsc --noEmit",
|
|
22
|
+
"test:unit": "vitest run"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@inquirer/prompts": "^7.9.0",
|
|
26
|
+
"jscodeshift": "^17.3.0",
|
|
27
|
+
"picocolors": "^1.1.1",
|
|
28
|
+
"registry-url": "^7.2.0",
|
|
29
|
+
"semver": "^7.8.5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/jscodeshift": "^17.3.0",
|
|
33
|
+
"@types/node": "^25.9.3",
|
|
34
|
+
"@types/semver": "^7.7.3",
|
|
35
|
+
"prettier": "^3.9.6",
|
|
36
|
+
"tsx": "^4.23.12",
|
|
37
|
+
"typescript": "^6.0.3",
|
|
38
|
+
"vitest": "^4.1.11",
|
|
39
|
+
"yaml": "^2.8.1"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { Transform } from "jscodeshift";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Moves the background value of `AccentBox` from `color` to `backgroundColor`
|
|
5
|
+
* (alpha.786).
|
|
6
|
+
*
|
|
7
|
+
* `color` did not go away, it changed meaning: it used to set the background
|
|
8
|
+
* (`"blue" | "green" | "gradient" | "neutral"`) and now sets the content color
|
|
9
|
+
* (`"default" | "dark" | "light" | "dark-static" | "light-static"`). A blanket
|
|
10
|
+
* rename would therefore break every element that already uses the new meaning.
|
|
11
|
+
* The transform decides per value instead: a value from the new content-color
|
|
12
|
+
* union stays on `color`, every other literal moves to `backgroundColor`.
|
|
13
|
+
*
|
|
14
|
+
* The scope is deliberately narrow. Only JSX elements that resolve to
|
|
15
|
+
* `AccentBox` — imported (named or as a namespace) from
|
|
16
|
+
* `@mittwald/flow-react-components` or
|
|
17
|
+
* `@mittwald/flow-remote-react-components`, including their subpath entries —
|
|
18
|
+
* are touched.
|
|
19
|
+
*
|
|
20
|
+
* Two cases are left alone, because both are undecidable without knowing the
|
|
21
|
+
* value:
|
|
22
|
+
*
|
|
23
|
+
* - Values it cannot read (`color={expression}`), and expressions that mix both
|
|
24
|
+
* meanings (`color={flag ? "blue" : "dark"}`). The same expression means the
|
|
25
|
+
* background in old code and the content color in new code, and a mix has no
|
|
26
|
+
* single correct answer. An expression whose every value position is a
|
|
27
|
+
* literal on the same side of the split _is_ decidable and gets renamed:
|
|
28
|
+
* `color={flag ? "blue" : "green"}`.
|
|
29
|
+
* - An element that already carries `backgroundColor`. Moving `color` there would
|
|
30
|
+
* silently overwrite the explicit value.
|
|
31
|
+
*
|
|
32
|
+
* Both keep their `color` prop and need a look by hand.
|
|
33
|
+
*/
|
|
34
|
+
const accentBoxColorToBackgroundColorTransform: Transform = (
|
|
35
|
+
fileInfo,
|
|
36
|
+
{ j },
|
|
37
|
+
) => {
|
|
38
|
+
const flowPackages = [
|
|
39
|
+
"@mittwald/flow-react-components",
|
|
40
|
+
"@mittwald/flow-remote-react-components",
|
|
41
|
+
];
|
|
42
|
+
const affectedComponents = new Set(["AccentBox"]);
|
|
43
|
+
/** Values `color` still accepts — everything else was a background color. */
|
|
44
|
+
const contentColors = new Set([
|
|
45
|
+
"default",
|
|
46
|
+
"dark",
|
|
47
|
+
"light",
|
|
48
|
+
"dark-static",
|
|
49
|
+
"light-static",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const isFlowImport = (source: string): boolean =>
|
|
53
|
+
flowPackages.some((pkg) => source === pkg || source.startsWith(`${pkg}/`));
|
|
54
|
+
|
|
55
|
+
/** The value of a string literal, or `undefined` for anything dynamic. */
|
|
56
|
+
const literalValue = (node: unknown): string | undefined => {
|
|
57
|
+
if (!node || typeof node !== "object" || !("type" in node)) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const { type } = node as { type: string };
|
|
61
|
+
if (type !== "StringLiteral" && type !== "Literal") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const { value } = node as { value?: unknown };
|
|
65
|
+
return typeof value === "string" ? value : undefined;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Every literal that could become this attribute's value, or `undefined` when
|
|
70
|
+
* any of those positions is something we cannot read. The positions are the
|
|
71
|
+
* expression itself, both ternary branches, and the operands of `??`/`||` —
|
|
72
|
+
* plus only the right operand of `&&`, since `&&` yields its left operand
|
|
73
|
+
* only when that operand is falsy and a non-empty string never is.
|
|
74
|
+
*
|
|
75
|
+
* A list, not a single value, because this transform decides per attribute,
|
|
76
|
+
* not per literal: `color={flag ? "blue" : "dark"}` mixes a background with a
|
|
77
|
+
* content colour, and no single rename is right for both.
|
|
78
|
+
*/
|
|
79
|
+
const valueLiterals = (node: unknown): string[] | undefined => {
|
|
80
|
+
const literal = literalValue(node);
|
|
81
|
+
if (literal !== undefined) {
|
|
82
|
+
return [literal];
|
|
83
|
+
}
|
|
84
|
+
if (!node || typeof node !== "object" || !("type" in node)) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const typed = node as {
|
|
88
|
+
type: string;
|
|
89
|
+
operator?: string;
|
|
90
|
+
consequent?: unknown;
|
|
91
|
+
alternate?: unknown;
|
|
92
|
+
left?: unknown;
|
|
93
|
+
right?: unknown;
|
|
94
|
+
};
|
|
95
|
+
if (typed.type === "ConditionalExpression") {
|
|
96
|
+
const consequent = valueLiterals(typed.consequent);
|
|
97
|
+
const alternate = valueLiterals(typed.alternate);
|
|
98
|
+
return consequent && alternate
|
|
99
|
+
? [...consequent, ...alternate]
|
|
100
|
+
: undefined;
|
|
101
|
+
}
|
|
102
|
+
if (typed.type === "LogicalExpression") {
|
|
103
|
+
const right = valueLiterals(typed.right);
|
|
104
|
+
if (right === undefined) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (typed.operator === "&&") {
|
|
108
|
+
return right;
|
|
109
|
+
}
|
|
110
|
+
const left = valueLiterals(typed.left);
|
|
111
|
+
return left ? [...left, ...right] : undefined;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const root = j(fileInfo.source, { parser: "tsx" });
|
|
117
|
+
|
|
118
|
+
// Local JSX identifier -> canonical component name (resolves `as` aliases).
|
|
119
|
+
const localToComponent = new Map<string, string>();
|
|
120
|
+
// Local names of `import * as Flow` namespace imports from a Flow package.
|
|
121
|
+
const flowNamespaces = new Set<string>();
|
|
122
|
+
|
|
123
|
+
root
|
|
124
|
+
.find(j.ImportDeclaration)
|
|
125
|
+
.filter((path) => isFlowImport(String(path.node.source.value)))
|
|
126
|
+
.forEach((path) => {
|
|
127
|
+
for (const specifier of path.node.specifiers ?? []) {
|
|
128
|
+
if (
|
|
129
|
+
specifier.type === "ImportSpecifier" &&
|
|
130
|
+
specifier.imported.type === "Identifier" &&
|
|
131
|
+
affectedComponents.has(specifier.imported.name)
|
|
132
|
+
) {
|
|
133
|
+
localToComponent.set(
|
|
134
|
+
String(specifier.local?.name ?? specifier.imported.name),
|
|
135
|
+
String(specifier.imported.name),
|
|
136
|
+
);
|
|
137
|
+
} else if (
|
|
138
|
+
specifier.type === "ImportNamespaceSpecifier" &&
|
|
139
|
+
specifier.local
|
|
140
|
+
) {
|
|
141
|
+
flowNamespaces.add(String(specifier.local.name));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (localToComponent.size === 0 && flowNamespaces.size === 0) {
|
|
147
|
+
return fileInfo.source;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
root.find(j.JSXOpeningElement).forEach((path) => {
|
|
151
|
+
const name = path.node.name;
|
|
152
|
+
|
|
153
|
+
let isAffected = false;
|
|
154
|
+
if (name.type === "JSXIdentifier") {
|
|
155
|
+
isAffected = localToComponent.has(name.name);
|
|
156
|
+
} else if (
|
|
157
|
+
name.type === "JSXMemberExpression" &&
|
|
158
|
+
name.object.type === "JSXIdentifier" &&
|
|
159
|
+
name.property.type === "JSXIdentifier"
|
|
160
|
+
) {
|
|
161
|
+
isAffected =
|
|
162
|
+
flowNamespaces.has(name.object.name) &&
|
|
163
|
+
affectedComponents.has(name.property.name);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!isAffected) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const attributes = path.node.attributes ?? [];
|
|
171
|
+
|
|
172
|
+
const isNamed = (attribute: unknown, key: string): boolean =>
|
|
173
|
+
!!attribute &&
|
|
174
|
+
typeof attribute === "object" &&
|
|
175
|
+
(attribute as { type?: string }).type === "JSXAttribute" &&
|
|
176
|
+
(attribute as { name?: { type?: string; name?: string } }).name?.type ===
|
|
177
|
+
"JSXIdentifier" &&
|
|
178
|
+
(attribute as { name?: { name?: string } }).name?.name === key;
|
|
179
|
+
|
|
180
|
+
// An explicit `backgroundColor` is the new API already — never overwrite it.
|
|
181
|
+
if (attributes.some((attribute) => isNamed(attribute, "backgroundColor"))) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const attribute of attributes) {
|
|
186
|
+
if (!isNamed(attribute, "color") || attribute.type !== "JSXAttribute") {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const value = attribute.value;
|
|
191
|
+
|
|
192
|
+
const literals =
|
|
193
|
+
value?.type === "JSXExpressionContainer"
|
|
194
|
+
? valueLiterals(value.expression)
|
|
195
|
+
: valueLiterals(value);
|
|
196
|
+
|
|
197
|
+
if (literals === undefined || literals.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// All-or-nothing. Every value has to be a background colour for the
|
|
202
|
+
// rename to be right; all content colours means the element already uses
|
|
203
|
+
// the new meaning, and a mix has no single correct answer.
|
|
204
|
+
const backgrounds = literals.filter(
|
|
205
|
+
(literal) => !contentColors.has(literal),
|
|
206
|
+
);
|
|
207
|
+
if (backgrounds.length !== literals.length) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
attribute.name = j.jsxIdentifier("backgroundColor");
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return root.toSource();
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default accentBoxColorToBackgroundColorTransform;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Transform } from "jscodeshift";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renames the `action` prop to `onAction` on `Action`.
|
|
5
|
+
*
|
|
6
|
+
* The scope is deliberately narrow. Only JSX elements that resolve to `Action`
|
|
7
|
+
* — imported (named or as a namespace) from `@mittwald/flow-react-components`
|
|
8
|
+
* or `@mittwald/flow-remote-react-components`, including their subpath entries
|
|
9
|
+
* — are touched. `Action` is not one of the generated remote components, but
|
|
10
|
+
* the remote package re-exports the `flr-universal` surface, which carries it.
|
|
11
|
+
* Same-named components from other packages are left untouched, and so is the
|
|
12
|
+
* `action` attribute of a plain `<form>`.
|
|
13
|
+
*
|
|
14
|
+
* An element that already carries `onAction` keeps it and only loses the stale
|
|
15
|
+
* `action` prop, which mirrors what the runtime fallback does: an explicit
|
|
16
|
+
* `onAction` wins.
|
|
17
|
+
*/
|
|
18
|
+
const actionPropToOnActionTransform: Transform = (fileInfo, { j }) => {
|
|
19
|
+
const flowPackages = [
|
|
20
|
+
"@mittwald/flow-react-components",
|
|
21
|
+
"@mittwald/flow-remote-react-components",
|
|
22
|
+
];
|
|
23
|
+
const affectedComponents = new Set(["Action"]);
|
|
24
|
+
|
|
25
|
+
const isFlowImport = (source: string): boolean =>
|
|
26
|
+
flowPackages.some((pkg) => source === pkg || source.startsWith(`${pkg}/`));
|
|
27
|
+
|
|
28
|
+
const root = j(fileInfo.source, { parser: "tsx" });
|
|
29
|
+
|
|
30
|
+
// Local JSX identifier -> canonical component name (resolves `as` aliases).
|
|
31
|
+
const localToComponent = new Map<string, string>();
|
|
32
|
+
// Local names of `import * as Flow` namespace imports from a Flow package.
|
|
33
|
+
const flowNamespaces = new Set<string>();
|
|
34
|
+
|
|
35
|
+
root
|
|
36
|
+
.find(j.ImportDeclaration)
|
|
37
|
+
.filter((path) => isFlowImport(String(path.node.source.value)))
|
|
38
|
+
.forEach((path) => {
|
|
39
|
+
for (const specifier of path.node.specifiers ?? []) {
|
|
40
|
+
if (
|
|
41
|
+
specifier.type === "ImportSpecifier" &&
|
|
42
|
+
specifier.imported.type === "Identifier" &&
|
|
43
|
+
affectedComponents.has(specifier.imported.name)
|
|
44
|
+
) {
|
|
45
|
+
localToComponent.set(
|
|
46
|
+
String(specifier.local?.name ?? specifier.imported.name),
|
|
47
|
+
String(specifier.imported.name),
|
|
48
|
+
);
|
|
49
|
+
} else if (
|
|
50
|
+
specifier.type === "ImportNamespaceSpecifier" &&
|
|
51
|
+
specifier.local
|
|
52
|
+
) {
|
|
53
|
+
flowNamespaces.add(String(specifier.local.name));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (localToComponent.size === 0 && flowNamespaces.size === 0) {
|
|
59
|
+
return fileInfo.source;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
root.find(j.JSXOpeningElement).forEach((path) => {
|
|
63
|
+
const name = path.node.name;
|
|
64
|
+
|
|
65
|
+
let isAffected = false;
|
|
66
|
+
if (name.type === "JSXIdentifier") {
|
|
67
|
+
isAffected = localToComponent.has(name.name);
|
|
68
|
+
} else if (
|
|
69
|
+
name.type === "JSXMemberExpression" &&
|
|
70
|
+
name.object.type === "JSXIdentifier" &&
|
|
71
|
+
name.property.type === "JSXIdentifier"
|
|
72
|
+
) {
|
|
73
|
+
isAffected =
|
|
74
|
+
flowNamespaces.has(name.object.name) &&
|
|
75
|
+
affectedComponents.has(name.property.name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!isAffected) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const attributes = path.node.attributes ?? [];
|
|
83
|
+
|
|
84
|
+
const isNamed = (attribute: (typeof attributes)[number], key: string) =>
|
|
85
|
+
attribute.type === "JSXAttribute" &&
|
|
86
|
+
attribute.name.type === "JSXIdentifier" &&
|
|
87
|
+
attribute.name.name === key;
|
|
88
|
+
|
|
89
|
+
const hasOnAction = attributes.some((attribute) =>
|
|
90
|
+
isNamed(attribute, "onAction"),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (hasOnAction) {
|
|
94
|
+
path.node.attributes = attributes.filter(
|
|
95
|
+
(attribute) => !isNamed(attribute, "action"),
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const attribute of attributes) {
|
|
101
|
+
if (isNamed(attribute, "action") && attribute.type === "JSXAttribute") {
|
|
102
|
+
attribute.name.name = "onAction";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return root.toSource();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default actionPropToOnActionTransform;
|