@react-spectrum/codemods 0.3.0 → 0.4.0

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/dist/index.js CHANGED
File without changes
@@ -1148,5 +1148,113 @@ exports.changes = {
1148
1148
  }
1149
1149
  }
1150
1150
  ]
1151
+ },
1152
+ TableView: {
1153
+ changes: [
1154
+ {
1155
+ description: 'Add columns prop to Row',
1156
+ reason: 'Rows now require a columns prop from TableHeader',
1157
+ function: {
1158
+ name: 'addColumnsPropToRow',
1159
+ args: {}
1160
+ }
1161
+ },
1162
+ {
1163
+ description: 'Leave comment if nested columns are used',
1164
+ reason: 'Nested columns are not supported yet',
1165
+ function: {
1166
+ name: 'commentIfNestedColumns',
1167
+ args: {}
1168
+ }
1169
+ },
1170
+ {
1171
+ description: 'Comment out dragAndDropHooks',
1172
+ reason: 'It has not been implemented yet',
1173
+ function: {
1174
+ name: 'commentOutProp',
1175
+ args: { propToComment: 'dragAndDropHooks' }
1176
+ }
1177
+ },
1178
+ {
1179
+ description: 'Comment out selectionStyle="highlight"',
1180
+ reason: 'It has not been implemented yet',
1181
+ function: {
1182
+ name: 'commentOutProp',
1183
+ args: { propToComment: 'selectionStyle' }
1184
+ }
1185
+ },
1186
+ {
1187
+ description: 'Comment out UNSTABLE_allowsExpandableRows',
1188
+ reason: 'It has not been implemented yet',
1189
+ function: {
1190
+ name: 'commentOutProp',
1191
+ args: { propToComment: 'UNSTABLE_allowsExpandableRows' }
1192
+ }
1193
+ },
1194
+ {
1195
+ description: 'Comment out UNSTABLE_defaultExpandedKeys',
1196
+ reason: 'It has not been implemented yet',
1197
+ function: {
1198
+ name: 'commentOutProp',
1199
+ args: { propToComment: 'UNSTABLE_defaultExpandedKeys' }
1200
+ }
1201
+ },
1202
+ {
1203
+ description: 'Comment out UNSTABLE_expandedKeys',
1204
+ reason: 'It has not been implemented yet',
1205
+ function: {
1206
+ name: 'commentOutProp',
1207
+ args: { propToComment: 'UNSTABLE_expandedKeys' }
1208
+ }
1209
+ },
1210
+ {
1211
+ description: 'Comment out UNSTABLE_onExpandedChange',
1212
+ reason: 'It has not been implemented yet',
1213
+ function: {
1214
+ name: 'commentOutProp',
1215
+ args: { propToComment: 'UNSTABLE_onExpandedChange' }
1216
+ }
1217
+ },
1218
+ {
1219
+ description: 'Add isRowHeader prop to fist Column if one doesn\'t eixst already',
1220
+ reason: 'Updated API',
1221
+ function: {
1222
+ name: 'addRowHeader',
1223
+ args: {}
1224
+ }
1225
+ }
1226
+ ]
1227
+ },
1228
+ Column: {
1229
+ changes: [
1230
+ {
1231
+ description: 'Update key prop to id',
1232
+ reason: 'Updated API',
1233
+ function: {
1234
+ name: 'updateKeyToId',
1235
+ args: {}
1236
+ }
1237
+ }
1238
+ ]
1239
+ },
1240
+ Row: {
1241
+ changes: [
1242
+ {
1243
+ description: 'Update key prop to id',
1244
+ reason: 'Updated API',
1245
+ function: {
1246
+ name: 'updateKeyToId',
1247
+ args: {}
1248
+ }
1249
+ },
1250
+ {
1251
+ description: 'Update child function to receive column object instead of column key',
1252
+ reason: 'Updated API',
1253
+ function: {
1254
+ name: 'updateRowFunctionArg',
1255
+ args: {}
1256
+ }
1257
+ }
1258
+ ]
1151
1259
  }
1152
1260
  };
@@ -24,6 +24,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.functionMap = void 0;
27
+ exports.commentIfNestedColumns = commentIfNestedColumns;
27
28
  const utils_1 = require("./utils");
28
29
  const dimensions_1 = require("./dimensions");
29
30
  const getComponents_1 = require("../getComponents");
@@ -331,32 +332,13 @@ function updateComponentWithinCollection(path, options) {
331
332
  t.isJSXIdentifier(closestParentCollection.node.openingElement.name) &&
332
333
  (0, utils_1.getName)(path, closestParentCollection.node.openingElement.name) === parentComponent) {
333
334
  // If closest parent collection component matches parentComponent, replace with newComponent
334
- let attributes = path.node.openingElement.attributes;
335
- let keyProp = attributes.find((attr) => t.isJSXAttribute(attr) && attr.name.name === 'key');
336
- if (keyProp &&
337
- t.isJSXAttribute(keyProp)) {
338
- // Update key prop to be id
339
- keyProp.name = t.jsxIdentifier('id');
340
- }
341
- if (t.isArrowFunctionExpression(path.parentPath.node) &&
342
- path.parentPath.parentPath &&
343
- t.isCallExpression(path.parentPath.parentPath.node) &&
344
- path.parentPath.parentPath.node.callee.type === 'MemberExpression' &&
345
- path.parentPath.parentPath.node.callee.property.type === 'Identifier' &&
346
- path.parentPath.parentPath.node.callee.property.name === 'map') {
347
- // If Array.map is used, keep the key prop
348
- if (keyProp &&
349
- t.isJSXAttribute(keyProp)) {
350
- let newKeyProp = t.jsxAttribute(t.jsxIdentifier('key'), keyProp.value);
351
- attributes.push(newKeyProp);
352
- }
353
- }
335
+ updateKeyToId(path);
354
336
  let localName = newComponent;
355
337
  if (availableComponents.has(newComponent)) {
356
338
  let program = path.findParent((p) => t.isProgram(p.node));
357
339
  localName = (0, utils_1.addComponentImport)(program, newComponent);
358
340
  }
359
- let newNode = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(localName), attributes), t.jsxClosingElement(t.jsxIdentifier(localName)), path.node.children);
341
+ let newNode = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(localName), path.node.openingElement.attributes), t.jsxClosingElement(t.jsxIdentifier(localName)), path.node.children);
360
342
  path.replaceWith(newNode);
361
343
  }
362
344
  }
@@ -694,6 +676,144 @@ function updateLegacyLink(path) {
694
676
  }
695
677
  }
696
678
  }
679
+ /**
680
+ * Copies the columns prop from the TableHeader to the Row component.
681
+ */
682
+ function addColumnsPropToRow(path) {
683
+ const tableHeaderPath = path.get('children').find((child) => t.isJSXElement(child.node) &&
684
+ t.isJSXIdentifier(child.node.openingElement.name) &&
685
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'TableHeader');
686
+ if (!tableHeaderPath) {
687
+ (0, utils_1.addComment)(path.node, ' TODO(S2-upgrade): Could not find TableHeader within Table to retrieve columns prop.');
688
+ return;
689
+ }
690
+ const columnsProp = tableHeaderPath
691
+ .get('openingElement')
692
+ .get('attributes')
693
+ .find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'columns');
694
+ if (columnsProp) {
695
+ path.traverse({
696
+ JSXElement(innerPath) {
697
+ if (t.isJSXElement(innerPath.node) &&
698
+ t.isJSXIdentifier(innerPath.node.openingElement.name) &&
699
+ (0, utils_1.getName)(innerPath, innerPath.node.openingElement.name) === 'Row') {
700
+ let rowPath = innerPath;
701
+ rowPath.node.openingElement.attributes.push(columnsProp.node);
702
+ // If Row doesn't contain id prop, leave a comment for the user to check manually
703
+ let idProp = rowPath.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'id');
704
+ if (!idProp) {
705
+ (0, utils_1.addComment)(rowPath.node, ' TODO(S2-upgrade): If the items do not have id properties, you\'ll need to add an id prop to the Row.');
706
+ }
707
+ }
708
+ }
709
+ });
710
+ }
711
+ }
712
+ /**
713
+ * Updates the function signature of the Row component.
714
+ */
715
+ function updateRowFunctionArg(path) {
716
+ // Find the function passed as a child
717
+ let functionChild = path.get('children').find(childPath => childPath.isJSXExpressionContainer() &&
718
+ childPath.get('expression').isArrowFunctionExpression());
719
+ let tablePath = path.findParent((p) => t.isJSXElement(p.node) &&
720
+ t.isJSXIdentifier(p.node.openingElement.name) &&
721
+ (0, utils_1.getName)(path, p.node.openingElement.name) === 'TableView');
722
+ let tableHeaderPath = tablePath?.get('children').find((child) => t.isJSXElement(child.node) &&
723
+ t.isJSXIdentifier(child.node.openingElement.name) &&
724
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'TableHeader');
725
+ function findColumnKeyProp(path) {
726
+ let columnKeyProp = 'id';
727
+ path.traverse({
728
+ JSXElement(columnPath) {
729
+ if (t.isArrowFunctionExpression(columnPath.parentPath.node) &&
730
+ t.isJSXElement(columnPath.node) &&
731
+ t.isJSXIdentifier(columnPath.node.openingElement.name) &&
732
+ (0, utils_1.getName)(columnPath, columnPath.node.openingElement.name) === 'Column') {
733
+ let openingElement = columnPath.get('openingElement');
734
+ let keyPropPath = openingElement.get('attributes').find(attr => t.isJSXAttribute(attr.node) &&
735
+ (attr.node.name.name === 'key' || attr.node.name.name === 'id'));
736
+ keyPropPath?.traverse({
737
+ Identifier(innerPath) {
738
+ if (innerPath.node.name === 'column' &&
739
+ innerPath.parentPath.node.type === 'MemberExpression' &&
740
+ t.isIdentifier(innerPath.parentPath.node.property)) {
741
+ columnKeyProp = innerPath.parentPath.node.property.name;
742
+ }
743
+ }
744
+ });
745
+ }
746
+ }
747
+ });
748
+ return columnKeyProp || 'id';
749
+ }
750
+ let columnKey = findColumnKeyProp(tableHeaderPath);
751
+ if (functionChild && functionChild.isJSXExpressionContainer()) {
752
+ let arrowFuncPath = functionChild.get('expression');
753
+ if (arrowFuncPath.isArrowFunctionExpression()) {
754
+ let params = arrowFuncPath.node.params;
755
+ if (params.length === 1 && t.isIdentifier(params[0])) {
756
+ let paramName = params[0].name;
757
+ // Rename parameter to 'column'
758
+ params[0].name = 'column';
759
+ // Replace references to the old parameter name inside the function body
760
+ arrowFuncPath.get('body').traverse({
761
+ Identifier(innerPath) {
762
+ if (innerPath.node.name === paramName &&
763
+ // Ensure we're not replacing the parameter declaration
764
+ innerPath.node !== params[0]) {
765
+ // Replace with column key
766
+ innerPath.replaceWith(t.memberExpression(t.identifier('column'), t.identifier(columnKey ?? 'id')));
767
+ }
768
+ }
769
+ });
770
+ }
771
+ }
772
+ }
773
+ }
774
+ /**
775
+ * Updates the key prop to id. Keeps the key prop if it's used in an array.map function.
776
+ */
777
+ function updateKeyToId(path) {
778
+ let attributes = path.node.openingElement.attributes;
779
+ let keyProp = attributes.find((attr) => t.isJSXAttribute(attr) && attr.name.name === 'key');
780
+ if (keyProp &&
781
+ t.isJSXAttribute(keyProp)) {
782
+ // Update key prop to be id
783
+ keyProp.name = t.jsxIdentifier('id');
784
+ }
785
+ if (t.isArrowFunctionExpression(path.parentPath.node) &&
786
+ path.parentPath.parentPath &&
787
+ t.isCallExpression(path.parentPath.parentPath.node) &&
788
+ path.parentPath.parentPath.node.callee.type === 'MemberExpression' &&
789
+ path.parentPath.parentPath.node.callee.property.type === 'Identifier' &&
790
+ path.parentPath.parentPath.node.callee.property.name === 'map') {
791
+ // If Array.map is used, keep the key prop
792
+ if (keyProp &&
793
+ t.isJSXAttribute(keyProp)) {
794
+ let newKeyProp = t.jsxAttribute(t.jsxIdentifier('key'), keyProp.value);
795
+ attributes.push(newKeyProp);
796
+ }
797
+ }
798
+ }
799
+ function commentIfNestedColumns(path) {
800
+ const headerPath = path.get('children').find((child) => t.isJSXElement(child.node) &&
801
+ t.isJSXIdentifier(child.node.openingElement.name) &&
802
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'TableHeader');
803
+ const columns = headerPath?.get('children') || [];
804
+ let hasNestedColumns = false;
805
+ columns.forEach(column => {
806
+ let columnChildren = column.get('children');
807
+ if (columnChildren.find(child => t.isJSXElement(child.node) &&
808
+ t.isJSXIdentifier(child.node.openingElement.name) &&
809
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'Column')) {
810
+ hasNestedColumns = true;
811
+ }
812
+ });
813
+ if (hasNestedColumns) {
814
+ (0, utils_1.addComment)(path.node, ' TODO(S2-upgrade): Nested Column components are not supported yet.');
815
+ }
816
+ }
697
817
  /**
698
818
  * Updates DialogTrigger and DialogContainer to the new API.
699
819
  *
@@ -846,6 +966,53 @@ function updateActionGroup(path) {
846
966
  path.node.closingElement.name = t.jsxIdentifier(localName);
847
967
  }
848
968
  }
969
+ /**
970
+ * Adds isRowHeader to the first Column in a table if there isn't already a row header.
971
+ * @param path
972
+ */
973
+ function addRowHeader(path) {
974
+ let tableHeaderPath = path.get('children').find((child) => t.isJSXElement(child.node) &&
975
+ t.isJSXIdentifier(child.node.openingElement.name) &&
976
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'TableHeader');
977
+ // Check if isRowHeader is already set on a Column
978
+ let hasRowHeader = false;
979
+ tableHeaderPath?.get('children').forEach((child) => {
980
+ if (t.isJSXElement(child.node) &&
981
+ t.isJSXIdentifier(child.node.openingElement.name) &&
982
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'Column') {
983
+ let isRowHeaderProp = child.get('openingElement').get('attributes').find((attr) => t.isJSXAttribute(attr.node) && attr.node.name.name === 'isRowHeader');
984
+ if (isRowHeaderProp) {
985
+ hasRowHeader = true;
986
+ }
987
+ }
988
+ });
989
+ // If there isn't already a row header, add one to the first Column if possible
990
+ if (!hasRowHeader) {
991
+ tableHeaderPath?.get('children').forEach((child) => {
992
+ // Add to first Column if static
993
+ if (!hasRowHeader &&
994
+ t.isJSXElement(child.node) &&
995
+ t.isJSXIdentifier(child.node.openingElement.name) &&
996
+ (0, utils_1.getName)(child, child.node.openingElement.name) === 'Column') {
997
+ child.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier('isRowHeader'), t.jsxExpressionContainer(t.booleanLiteral(true))));
998
+ hasRowHeader = true;
999
+ }
1000
+ // If render function is used, leave a comment to update manually
1001
+ if (t.isJSXExpressionContainer(child.node) &&
1002
+ t.isArrowFunctionExpression(child.node.expression)) {
1003
+ (0, utils_1.addComment)(child.node, ' TODO(S2-upgrade): You\'ll need to add isRowHeader to one of the columns manually.');
1004
+ }
1005
+ // If array.map is used, leave a comment to update manually
1006
+ if (t.isJSXExpressionContainer(child.node) &&
1007
+ t.isCallExpression(child.node.expression) &&
1008
+ t.isMemberExpression(child.node.expression.callee) &&
1009
+ t.isIdentifier(child.node.expression.callee.property) &&
1010
+ child.node.expression.callee.property.name === 'map') {
1011
+ (0, utils_1.addComment)(child.node, ' TODO(S2-upgrade): You\'ll need to add isRowHeader to one of the columns manually.');
1012
+ }
1013
+ });
1014
+ }
1015
+ }
849
1016
  exports.functionMap = {
850
1017
  updatePropNameAndValue,
851
1018
  updatePropValueAndAddNewProp,
@@ -866,6 +1033,11 @@ exports.functionMap = {
866
1033
  removeComponentIfWithinParent,
867
1034
  updateAvatarSize,
868
1035
  updateLegacyLink,
1036
+ addColumnsPropToRow,
1037
+ updateRowFunctionArg,
869
1038
  updateDialogChild,
870
- updateActionGroup
1039
+ updateActionGroup,
1040
+ updateKeyToId,
1041
+ commentIfNestedColumns,
1042
+ addRowHeader
871
1043
  };
@@ -14,12 +14,7 @@ const skipped = new Set([
14
14
  'Accordion',
15
15
  'Card',
16
16
  'CardView',
17
- 'TableView',
18
- 'TableHeader',
19
- 'Column',
20
- 'TableBody',
21
- 'Row',
22
- 'Cell'
17
+ 'ActionBar'
23
18
  ]);
24
19
  function getComponents() {
25
20
  // Determine list of available components in S2 from index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-spectrum/codemods",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "dist/index.js",
5
5
  "source": "src/index.ts",
6
6
  "bin": "dist/index.js",
@@ -24,29 +24,31 @@
24
24
  "@babel/parser": "^7.24.5",
25
25
  "@babel/traverse": "^7.24.5",
26
26
  "@babel/types": "^7.24.5",
27
- "@react-spectrum/s2": "^0.5.0",
28
- "@react-types/shared": "^3.26.0",
27
+ "@react-spectrum/s2": "^0.6.0",
28
+ "@react-types/shared": "^3.27.0",
29
29
  "@types/node": "^20",
30
30
  "boxen": "^5.1.2",
31
31
  "build": "^0.1.4",
32
32
  "chalk": "^4.0.0",
33
33
  "execa": "^5.1.1",
34
34
  "jscodeshift": "^0.15.2",
35
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
36
35
  "recast": "^0.23.9",
37
36
  "ts-node": "^10.9.2",
38
37
  "uuid": "^9.0.1"
39
38
  },
40
39
  "devDependencies": {
41
40
  "@types/jscodeshift": "^0.11.11",
42
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
43
41
  "typescript": "^5.5.0"
44
42
  },
43
+ "peerDependencies": {
44
+ "react": "^18.0.0 || ^19.0.0-rc.1",
45
+ "react-dom": "^18.0.0 || ^19.0.0-rc.1"
46
+ },
45
47
  "rsp": {
46
48
  "type": "cli"
47
49
  },
48
50
  "publishConfig": {
49
51
  "access": "public"
50
52
  },
51
- "gitHead": "71f0ef23053f9e03ee7e97df736e8b083e006849"
53
+ "gitHead": "09e7f44bebdc9d89122926b2b439a0a38a2814ea"
52
54
  }