@mittwald/flow-codemods 1.1.28 → 1.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const oldPath = "@mittwald/flow-react-components/styles";
4
+ const newPath = "@mittwald/flow-react-components/all.css";
5
+ /**
6
+ * Rewrites the module specifier `@mittwald/flow-react-components/styles` to
7
+ * `@mittwald/flow-react-components/all.css` (alpha.292).
8
+ *
9
+ * The same shape as `password-tools-subpath-renamed`: one exact specifier, in
10
+ * every form that names a module — `import`, `import type`, `export … from`, a
11
+ * side-effect import, `import()` and `require()`. In practice it is nearly
12
+ * always the side-effect form (`import "…/styles"`), which is why that case is
13
+ * the one the fixtures lead with.
14
+ *
15
+ * Exact match, not a prefix: `./styles` was a single export with nothing
16
+ * underneath it, so there is no deeper path to rewrite and no risk of touching
17
+ * an unrelated `styles` of someone else's.
18
+ *
19
+ * What this cannot reach is an `@import` in a `.css` or `.scss` file —
20
+ * jscodeshift only walks the JavaScript and TypeScript extensions. `apply` says
21
+ * so.
22
+ */
23
+ const renamedCssExportTransform = (fileInfo, { j }) => {
24
+ const root = j(fileInfo.source, { parser: "tsx" });
25
+ let changed = false;
26
+ const rewrite = (node) => {
27
+ if (node.value === oldPath) {
28
+ node.value = newPath;
29
+ changed = true;
30
+ }
31
+ };
32
+ // import … from "…", import "…"
33
+ root.find(j.ImportDeclaration).forEach((path) => rewrite(path.node.source));
34
+ // export … from "…", export * from "…"
35
+ root
36
+ .find(j.ExportNamedDeclaration)
37
+ .forEach((path) => path.node.source && rewrite(path.node.source));
38
+ root
39
+ .find(j.ExportAllDeclaration)
40
+ .forEach((path) => rewrite(path.node.source));
41
+ // import("…") and require("…")
42
+ root
43
+ .find(j.CallExpression)
44
+ .filter((path) => {
45
+ const callee = path.node.callee;
46
+ return (callee.type === "Import" ||
47
+ (callee.type === "Identifier" && callee.name === "require"));
48
+ })
49
+ .forEach((path) => {
50
+ const [argument] = path.node.arguments;
51
+ if (argument &&
52
+ (argument.type === "StringLiteral" || argument.type === "Literal")) {
53
+ rewrite(argument);
54
+ }
55
+ });
56
+ // Returning the source untouched keeps this file out of the "changed" count.
57
+ return changed ? root.toSource() : fileInfo.source;
58
+ };
59
+ exports.default = renamedCssExportTransform;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Drops `maxWidth` from `TableColumn`, and drops `width`/`minWidth` where they
5
+ * were `null` (alpha.956).
6
+ *
7
+ * Two changes with different decidability, which is why the transform covers
8
+ * both but not equally:
9
+ *
10
+ * - **`maxWidth` always goes.** The prop was removed from the type, so an
11
+ * explicit `maxWidth` attribute is wrong whatever its value is —
12
+ * `maxWidth={400}` and `maxWidth={computed}` alike. Removing it does change
13
+ * the rendered column (there is no maximum any more); that is the migration,
14
+ * not a side effect.
15
+ * - **`width` and `minWidth` only go when the source says `null`.** The type
16
+ * dropped `null`, and `null` meant "no explicit width", which is what
17
+ * omitting the prop means now. Only the literal is decidable:
18
+ * `width={maybeNull}` could be anything at runtime and is left alone, the way
19
+ * `accent-box-color-to-background-color` leaves `color={expression}`.
20
+ *
21
+ * The scope is the same as its neighbours': only JSX elements that resolve to
22
+ * `TableColumn` — imported named, aliased, or through a namespace, from
23
+ * `@mittwald/flow-react-components` or
24
+ * `@mittwald/flow-remote-react-components`, including their subpath entries.
25
+ *
26
+ * A spread (`<TableColumn {...props} />`) carrying `maxWidth` is invisible
27
+ * here. The explicit attributes beside it are still handled; `apply` names the
28
+ * gap.
29
+ */
30
+ const tableColumnWidthPropsTransform = (fileInfo, { j }) => {
31
+ const flowPackages = [
32
+ "@mittwald/flow-react-components",
33
+ "@mittwald/flow-remote-react-components",
34
+ ];
35
+ const affectedComponents = new Set(["TableColumn"]);
36
+ /** Removed from the type — an explicit attribute is wrong at any value. */
37
+ const removedProps = new Set(["maxWidth"]);
38
+ /** Retyped without `null` — remove only where the source literally says so. */
39
+ const nullableProps = new Set(["width", "minWidth"]);
40
+ const isFlowImport = (source) => flowPackages.some((pkg) => source === pkg || source.startsWith(`${pkg}/`));
41
+ /**
42
+ * Whether this JSX attribute value is the literal `null`.
43
+ *
44
+ * Both spellings, because which one appears depends on the parser: babel
45
+ * emits `NullLiteral`, esprima-style parsers a `Literal` whose `value` is
46
+ * `null`. Anything else — an identifier, a call, a ternary — is not decidable
47
+ * from the source and stays.
48
+ */
49
+ const isNullLiteral = (value) => {
50
+ if (!value || typeof value !== "object" || !("type" in value)) {
51
+ return false;
52
+ }
53
+ const container = value;
54
+ if (container.type !== "JSXExpressionContainer") {
55
+ return false;
56
+ }
57
+ const expression = container.expression;
58
+ if (!expression ||
59
+ typeof expression !== "object" ||
60
+ !("type" in expression)) {
61
+ return false;
62
+ }
63
+ const typed = expression;
64
+ return (typed.type === "NullLiteral" ||
65
+ (typed.type === "Literal" && typed.value === null));
66
+ };
67
+ const root = j(fileInfo.source, { parser: "tsx" });
68
+ // Local JSX identifier -> canonical component name (resolves `as` aliases).
69
+ const localToComponent = new Map();
70
+ // Local names of `import * as Flow` namespace imports from a Flow package.
71
+ const flowNamespaces = new Set();
72
+ root
73
+ .find(j.ImportDeclaration)
74
+ .filter((path) => isFlowImport(String(path.node.source.value)))
75
+ .forEach((path) => {
76
+ for (const specifier of path.node.specifiers ?? []) {
77
+ if (specifier.type === "ImportSpecifier" &&
78
+ specifier.imported.type === "Identifier" &&
79
+ affectedComponents.has(specifier.imported.name)) {
80
+ localToComponent.set(String(specifier.local?.name ?? specifier.imported.name), String(specifier.imported.name));
81
+ }
82
+ else if (specifier.type === "ImportNamespaceSpecifier" &&
83
+ specifier.local) {
84
+ flowNamespaces.add(String(specifier.local.name));
85
+ }
86
+ }
87
+ });
88
+ if (localToComponent.size === 0 && flowNamespaces.size === 0) {
89
+ return fileInfo.source;
90
+ }
91
+ let changed = false;
92
+ root.find(j.JSXOpeningElement).forEach((path) => {
93
+ const name = path.node.name;
94
+ let isAffected = false;
95
+ if (name.type === "JSXIdentifier") {
96
+ isAffected = localToComponent.has(name.name);
97
+ }
98
+ else if (name.type === "JSXMemberExpression" &&
99
+ name.object.type === "JSXIdentifier" &&
100
+ name.property.type === "JSXIdentifier") {
101
+ isAffected =
102
+ flowNamespaces.has(name.object.name) &&
103
+ affectedComponents.has(name.property.name);
104
+ }
105
+ if (!isAffected) {
106
+ return;
107
+ }
108
+ const attributes = path.node.attributes ?? [];
109
+ const kept = attributes.filter((attribute) => {
110
+ if (attribute.type !== "JSXAttribute" ||
111
+ attribute.name.type !== "JSXIdentifier") {
112
+ return true;
113
+ }
114
+ const attributeName = attribute.name.name;
115
+ if (removedProps.has(attributeName)) {
116
+ return false;
117
+ }
118
+ if (nullableProps.has(attributeName) && isNullLiteral(attribute.value)) {
119
+ return false;
120
+ }
121
+ return true;
122
+ });
123
+ if (kept.length !== attributes.length) {
124
+ path.node.attributes = kept;
125
+ changed = true;
126
+ }
127
+ });
128
+ // Returning the source untouched keeps this file out of the "changed" count.
129
+ return changed ? root.toSource() : fileInfo.source;
130
+ };
131
+ exports.default = tableColumnWidthPropsTransform;
@@ -98,9 +98,9 @@ export const migrations = [
98
98
  since: "0.2.0-alpha.956",
99
99
  title: "TableColumn: `maxWidth` removed, `width` and `minWidth` retyped",
100
100
  kind: "migration",
101
- action: "manual",
101
+ action: "codemod",
102
102
  remotePackage: true,
103
- apply: "Remove `maxWidth` from every `TableColumn`. Where `width` or `minWidth` was `null`, omit the prop instead — the type no longer accepts `null`, only `number | string`.",
103
+ apply: "Remove `maxWidth` from every `TableColumn`. Where `width` or `minWidth` was `null`, omit the prop instead — the type no longer accepts `null`, only `number | string`. A codemod does both for the cases it can decide from the source. Two it declines: a `width`/`minWidth` whose value is an expression (`width={maybeNull}`), and a spread that might carry `maxWidth` (`<TableColumn {...props} />`). Check those by hand.",
104
104
  },
105
105
  {
106
106
  id: "table-render-prop-removed",
@@ -233,8 +233,8 @@ export const migrations = [
233
233
  since: "0.1.0-alpha.292",
234
234
  title: "Renamed CSS export",
235
235
  kind: "migration",
236
- action: "manual",
236
+ action: "codemod",
237
237
  remotePackage: false,
238
- apply: "Replace the import `@mittwald/flow-react-components/styles` with `@mittwald/flow-react-components/all.css`.",
238
+ apply: "Replace the import `@mittwald/flow-react-components/styles` with `@mittwald/flow-react-components/all.css`. A codemod does this for JavaScript and TypeScript files. An `@import` of the old path inside a `.css` or `.scss` file is not covered — search for it by hand.",
239
239
  },
240
240
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/flow-codemods",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
4
4
  "type": "module",
5
5
  "description": "Codemods and an upgrade CLI for consumers of Flow, mittwald's design system",
6
6
  "homepage": "https://flow.mittwald.de",
@@ -37,5 +37,5 @@
37
37
  "vitest": "^4.1.11",
38
38
  "yaml": "^2.8.1"
39
39
  },
40
- "gitHead": "620d70dc1c876b78cd6a7e693f79ce315088f757"
40
+ "gitHead": "9c65be4254837ed7a23e6ef79e07ca6717938d19"
41
41
  }