From 1dff9d03fe977388a62129d4104ddf2e0ee46829 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 12:09:22 +0200 Subject: [PATCH 01/20] add tests --- ...replace-Datagrid-DataTable-Basic.input.tsx | 15 +++++++ ...eplace-Datagrid-DataTable-Basic.output.tsx | 15 +++++++ ...-Datagrid-DataTable-ManyChildren.input.tsx | 36 ++++++++++++++++ ...Datagrid-DataTable-ManyChildren.output.tsx | 37 ++++++++++++++++ ...replace-Datagrid-DataTable-Props.input.tsx | 42 +++++++++++++++++++ ...eplace-Datagrid-DataTable-Props.output.tsx | 41 ++++++++++++++++++ .../replace-Datagrid-DataTable.spec.ts | 25 +++++++++++ 7 files changed, 211 insertions(+) create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.input.tsx create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.output.tsx create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx create mode 100644 packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx create mode 100644 packages/ra-core/codemods/__tests__/replace-Datagrid-DataTable.spec.ts diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.input.tsx new file mode 100644 index 00000000000..6379c0d06b6 --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.input.tsx @@ -0,0 +1,15 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +const PostList = () => ( + + + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.output.tsx new file mode 100644 index 00000000000..97738414c95 --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Basic.output.tsx @@ -0,0 +1,15 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { List, DataTable } from 'react-admin'; + +const PostList = () => ( + + + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx new file mode 100644 index 00000000000..a929c629c0a --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx @@ -0,0 +1,36 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { + List, + Datagrid, + TextField, + EmailField, + NumberField, + EditButton, + ReferenceField, + UrlField, + DateField, +} from 'react-admin'; + +// @ts-ignore +import { MyCustomField } from './MyCustomField'; + +const PostList = () => ( + + + + + + + + + + + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx new file mode 100644 index 00000000000..82aa451bbd0 --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { + List, + DataTable, + TextField, + EmailField, + EditButton, + ReferenceField, + UrlField, + DateField, +} from 'react-admin'; + +// @ts-ignore +import { MyCustomField } from './MyCustomField'; + +const PostList = () => ( + + + + + + + + + + + + + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx new file mode 100644 index 00000000000..07da0345e62 --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx @@ -0,0 +1,42 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { List, Datagrid, TextField, useRecordContext } from 'react-admin'; + +const CustomEmpty = () =>
No posts found
; + +const PostPanel = () => { + const record = useRecordContext(); + return
; +}; + +const postRowStyle = (record, index) => ({ + backgroundColor: record.nb_views >= 500 ? '#efe' : 'white', +}); + +const PostList = () => ( + + } + expand={} + expandSingle + rowClick={false} + optimized + rowStyle={postRowStyle} + sx={{ + '& .RaDatagrid-row': { + backgroundColor: 'white', + }, + '& .RaDatagrid-row:hover': { + backgroundColor: '#f5f5f5', + }, + }} + > + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx new file mode 100644 index 00000000000..0a78fc65d08 --- /dev/null +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx @@ -0,0 +1,41 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react'; +import { List, DataTable, useRecordContext } from 'react-admin'; + +const CustomEmpty = () =>
No posts found
; + +const PostPanel = () => { + const record = useRecordContext(); + return
; +}; + +const postRowStyle = (record, index) => ({ + backgroundColor: record.nb_views >= 500 ? '#efe' : 'white', +}); + +const PostList = () => ( + + } + expand={} + expandSingle + rowClick={false} + rowSx={postRowStyle} + sx={{ + '& .RaDataTable-row': { + backgroundColor: 'white', + }, + '& .RaDataTable-row:hover': { + backgroundColor: '#f5f5f5', + }, + }} + > + + + + + +); + +export default PostList; diff --git a/packages/ra-core/codemods/__tests__/replace-Datagrid-DataTable.spec.ts b/packages/ra-core/codemods/__tests__/replace-Datagrid-DataTable.spec.ts new file mode 100644 index 00000000000..f850e5f41eb --- /dev/null +++ b/packages/ra-core/codemods/__tests__/replace-Datagrid-DataTable.spec.ts @@ -0,0 +1,25 @@ +import { defineTest } from 'jscodeshift/dist/testUtils'; + +jest.autoMockOff(); + +defineTest( + __dirname, + 'replace-Datagrid-DataTable', + null, + 'replace-Datagrid-DataTable-Basic', + { parser: 'tsx' } +); +defineTest( + __dirname, + 'replace-Datagrid-DataTable', + null, + 'replace-Datagrid-DataTable-ManyChildren', + { parser: 'tsx' } +); +defineTest( + __dirname, + 'replace-Datagrid-DataTable', + null, + 'replace-Datagrid-DataTable-Props', + { parser: 'tsx' } +); From f4c497d1dd03118417f5190ec30d685d27276284 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 17:11:11 +0200 Subject: [PATCH 02/20] v1 of the codemod -> replace import --- .../codemods/replace-Datagrid-DataTable.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/ra-core/codemods/replace-Datagrid-DataTable.ts diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts new file mode 100644 index 00000000000..057ac67ec58 --- /dev/null +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -0,0 +1,52 @@ +import j from 'jscodeshift'; + +module.exports = (file, api: j.API) => { + const j = api.jscodeshift; + const root = j(file.source); + + replaceImports(root, j); + + return root.toSource({ quote: 'single', lineTerminator: '\n' }); +}; + +const replaceImports = (root, j) => { + // Check if there is an import from react-admin + const reactAdminImport = root.find(j.ImportDeclaration, { + source: { + value: 'react-admin', + }, + }); + if (!reactAdminImport.length) { + return root.toSource(); + } + + // Check if there is an import of DataGrid from react-admin + const datagridImport = reactAdminImport.filter(path => { + return path.node.specifiers.some( + specifier => + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'Datagrid' + ); + }); + console.log('toto - 4', datagridImport); + if (!datagridImport.length) { + console.log('toto - 5 - OUT'); + return root.toSource(); + } + + // Replace import of DataGrid with DataTable + reactAdminImport.replaceWith(({ node }) => + j.importDeclaration( + node.specifiers.map(specifier => { + if ( + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'Datagrid' + ) { + return j.importSpecifier(j.identifier('DataTable')); + } + return specifier; + }), + node.source + ) + ); +}; From 7e2b2eed0791c3ee79e6eb56f8227082808c78c4 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 17:25:37 +0200 Subject: [PATCH 03/20] stop after import if nothing to change --- .../codemods/replace-Datagrid-DataTable.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 057ac67ec58..7fb48489c63 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -4,12 +4,20 @@ module.exports = (file, api: j.API) => { const j = api.jscodeshift; const root = j(file.source); - replaceImports(root, j); + const continueAfterImport = replaceImport(root, j); + if (!continueAfterImport) { + return root.toSource(); + } + + const continueAfterComponent = replaceComponent(root, j); + if (!continueAfterComponent) { + return root.toSource(); + } return root.toSource({ quote: 'single', lineTerminator: '\n' }); }; -const replaceImports = (root, j) => { +const replaceImport = (root, j) => { // Check if there is an import from react-admin const reactAdminImport = root.find(j.ImportDeclaration, { source: { @@ -17,7 +25,7 @@ const replaceImports = (root, j) => { }, }); if (!reactAdminImport.length) { - return root.toSource(); + return false; } // Check if there is an import of DataGrid from react-admin @@ -28,10 +36,8 @@ const replaceImports = (root, j) => { specifier.imported.name === 'Datagrid' ); }); - console.log('toto - 4', datagridImport); if (!datagridImport.length) { - console.log('toto - 5 - OUT'); - return root.toSource(); + return false; } // Replace import of DataGrid with DataTable @@ -50,3 +56,8 @@ const replaceImports = (root, j) => { ) ); }; + +const replaceComponent = (root, j) => { + // TODO + return true; +}; From bf00c46fc52db64d1b9add37e93ea27784547791 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 17:52:02 +0200 Subject: [PATCH 04/20] codemod -> replace the Datagrid component --- .../codemods/replace-Datagrid-DataTable.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 7fb48489c63..b23209f4b0f 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -55,9 +55,36 @@ const replaceImport = (root, j) => { node.source ) ); + return true; }; const replaceComponent = (root, j) => { - // TODO + // Find all instances of Datagrid + const datagridComponents = root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'Datagrid', + }, + }, + }); + + if (!datagridComponents.length) { + return false; + } + + // Replace Datagrid with DataTable + datagridComponents.replaceWith(({ node }) => { + const openingElement = j.jsxOpeningElement( + j.jsxIdentifier('DataTable'), + node.openingElement.attributes, + node.openingElement.selfClosing + ); + const closingElement = j.jsxClosingElement( + j.jsxIdentifier('DataTable') + ); + return j.jsxElement(openingElement, closingElement, node.children); + }); + return true; }; From 0b7d2c591f8a670ad032f53d735e7be5b0a52ec3 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 18:28:26 +0200 Subject: [PATCH 05/20] codemod -> wrap every fields with a `` --- .../codemods/replace-Datagrid-DataTable.ts | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index b23209f4b0f..9bace009128 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -9,8 +9,13 @@ module.exports = (file, api: j.API) => { return root.toSource(); } - const continueAfterComponent = replaceComponent(root, j); - if (!continueAfterComponent) { + const continueAfterReplaceDatagrid = replaceDatagrid(root, j); + if (!continueAfterReplaceDatagrid) { + return root.toSource(); + } + + const continueAfterWrap = wrapChildren(root, j); + if (!continueAfterWrap) { return root.toSource(); } @@ -58,7 +63,7 @@ const replaceImport = (root, j) => { return true; }; -const replaceComponent = (root, j) => { +const replaceDatagrid = (root, j) => { // Find all instances of Datagrid const datagridComponents = root.find(j.JSXElement, { openingElement: { @@ -88,3 +93,67 @@ const replaceComponent = (root, j) => { return true; }; + +const wrapChildren = (root, j) => { + // Find all instances of Datagrid + const datagridComponents = root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'DataTable', + }, + }, + }); + if (!datagridComponents.length) { + return false; + } + + // For each DataTable component, wrap its children in DataTable.Col + datagridComponents.forEach(dataTableComponent => { + const children = dataTableComponent.value.children.filter(child => + j.JSXElement.check(child) + ); + children.forEach(child => { + wrapChild(root, j, child); + }); + }); +}; + +const wrapChild = (root, j, child) => { + // Wrap the child in a DataTable.Col component + const wrappedChild = j.jsxElement( + j.jsxOpeningElement( + j.jsxIdentifier('DataTable.Col'), + [ + j.jsxAttribute( + j.jsxIdentifier('source'), + j.stringLiteral( + child.openingElement.attributes.find( + attr => + j.JSXAttribute.check(attr) && + attr.name.name === 'source' + )?.value?.value || '' + ) + ), + ], + false + ), + j.jsxClosingElement(j.jsxIdentifier('DataTable.Col')), + [j.jsxText('\n'), child, j.jsxText('\n')] + ); + + // Replace the original child with the wrapped child + root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'DataTable', + }, + }, + }).forEach(dataTableComponent => { + dataTableComponent.value.children = + dataTableComponent.value.children.map(c => + c === child ? wrappedChild : c + ); + }); +}; From f79eb381e26c94aa838ec891c97520ef652f1bc2 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 18:31:12 +0200 Subject: [PATCH 06/20] codemod -> delete the optimized prop of the Datagrid component --- packages/ra-core/codemods/replace-Datagrid-DataTable.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 9bace009128..58baa278119 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -80,9 +80,15 @@ const replaceDatagrid = (root, j) => { // Replace Datagrid with DataTable datagridComponents.replaceWith(({ node }) => { + // remove the `optimized` attribute if it exists + const attributes = node.openingElement.attributes.filter( + attr => + !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') + ); + const openingElement = j.jsxOpeningElement( j.jsxIdentifier('DataTable'), - node.openingElement.attributes, + attributes, node.openingElement.selfClosing ); const closingElement = j.jsxClosingElement( From 63ec8cf05366ab98fb164a28de728aca3ba5ce79 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 18:33:38 +0200 Subject: [PATCH 07/20] codemod -> replace the rowStyle prop of the Datagrid component by rowSx --- .../ra-core/codemods/replace-Datagrid-DataTable.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 58baa278119..894456c1c5c 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -81,11 +81,19 @@ const replaceDatagrid = (root, j) => { // Replace Datagrid with DataTable datagridComponents.replaceWith(({ node }) => { // remove the `optimized` attribute if it exists - const attributes = node.openingElement.attributes.filter( + const filtredAttributes = node.openingElement.attributes.filter( attr => !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') ); + // rename the `rowStyle` attribute to `rowSx` if it exists + const attributes = filtredAttributes.map(attr => { + if (j.JSXAttribute.check(attr) && attr.name.name === 'rowStyle') { + return j.jsxAttribute(j.jsxIdentifier('rowSx'), attr.value); + } + return attr; + }); + const openingElement = j.jsxOpeningElement( j.jsxIdentifier('DataTable'), attributes, From 1ea7b29e1263f3f6686939bc12c003dc226e57d3 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 28 May 2025 18:43:48 +0200 Subject: [PATCH 08/20] codemod -> replace the & .RaDatagrid-xxxx key of the sx prop of the Datagrid component by & .RaDataTable-xxxx --- .../codemods/replace-Datagrid-DataTable.ts | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 894456c1c5c..bf0f0952199 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -80,24 +80,12 @@ const replaceDatagrid = (root, j) => { // Replace Datagrid with DataTable datagridComponents.replaceWith(({ node }) => { - // remove the `optimized` attribute if it exists - const filtredAttributes = node.openingElement.attributes.filter( - attr => - !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') - ); - - // rename the `rowStyle` attribute to `rowSx` if it exists - const attributes = filtredAttributes.map(attr => { - if (j.JSXAttribute.check(attr) && attr.name.name === 'rowStyle') { - return j.jsxAttribute(j.jsxIdentifier('rowSx'), attr.value); - } - return attr; - }); + const attributes = cleanAttributes(node, j); const openingElement = j.jsxOpeningElement( j.jsxIdentifier('DataTable'), attributes, - node.openingElement.selfClosing + [node.openingElement.selfClosing] ); const closingElement = j.jsxClosingElement( j.jsxIdentifier('DataTable') @@ -108,6 +96,55 @@ const replaceDatagrid = (root, j) => { return true; }; +const cleanAttributes = (node, j) => { + // remove the `optimized` attribute if it exists + const filtredAttributes = node.openingElement.attributes.filter( + attr => !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') + ); + + // rename the `rowStyle` attribute to `rowSx` if it exists + const rowSxRenamedAttributes = filtredAttributes.map(attr => { + if (j.JSXAttribute.check(attr) && attr.name.name === 'rowStyle') { + return j.jsxAttribute(j.jsxIdentifier('rowSx'), attr.value); + } + return attr; + }); + + // rename the keys of the "sx" prop from "& .RaDatagrid-xxxx" to "& .RaDataTable-xxxx" + const sxRenamedAttributes = rowSxRenamedAttributes.map(attr => { + if ( + j.JSXAttribute.check(attr) && + attr.name.name === 'sx' && + j.JSXExpressionContainer.check(attr.value) + ) { + const expression = attr.value.expression; + if (j.ObjectExpression.check(expression)) { + const properties = expression.properties.map(prop => { + if ( + j.ObjectProperty.check(prop) && + j.Literal.check(prop.key) && + typeof prop.key.value === 'string' + ) { + const newKey = prop.key.value.replace( + /RaDatagrid-/g, + 'RaDataTable-' + ); + return j.objectProperty(j.literal(newKey), prop.value); + } + return prop; + }); + return j.jsxAttribute( + j.jsxIdentifier('sx'), + j.jsxExpressionContainer(j.objectExpression(properties)) + ); + } + } + return attr; + }); + + return sxRenamedAttributes; +}; + const wrapChildren = (root, j) => { // Find all instances of Datagrid const datagridComponents = root.find(j.JSXElement, { From 1a645b73b849d445545e06d5c743086fc38e9cdf Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 10:36:45 +0200 Subject: [PATCH 09/20] fix the opening element --- packages/ra-core/codemods/replace-Datagrid-DataTable.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index bf0f0952199..47f2ca208b3 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -84,8 +84,7 @@ const replaceDatagrid = (root, j) => { const openingElement = j.jsxOpeningElement( j.jsxIdentifier('DataTable'), - attributes, - [node.openingElement.selfClosing] + attributes ); const closingElement = j.jsxClosingElement( j.jsxIdentifier('DataTable') From 68d88932fff0e1a09c81f36876af3bee43d3ba6f Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 10:50:32 +0200 Subject: [PATCH 10/20] codemod -> don't wrap if it is a TextField or a NumberField --- .../codemods/replace-Datagrid-DataTable.ts | 85 +++++++++++++++---- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 47f2ca208b3..2bf87343520 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -164,14 +164,60 @@ const wrapChildren = (root, j) => { j.JSXElement.check(child) ); children.forEach(child => { - wrapChild(root, j, child); + transformChild(root, j, child); }); }); }; -const wrapChild = (root, j, child) => { +const transformChild = (root, j, child) => { + let newChild; + if ( + j.JSXElement.check(child) && + child.openingElement.name.type === 'JSXIdentifier' && + child.openingElement.name.name === 'TextField' && + !child.openingElement.attributes.some( + attr => + j.JSXAttribute.check(attr) && + !['source', 'label', 'empty'].includes(attr.name.name) + ) + ) { + newChild = replaceTextField(j, child); + } else if ( + j.JSXElement.check(child) && + child.openingElement.name.type === 'JSXIdentifier' && + child.openingElement.name.name === 'NumberField' && + !child.openingElement.attributes.some( + attr => + j.JSXAttribute.check(attr) && + !['source', 'label', 'empty', 'options', 'locales'].includes( + attr.name.name + ) + ) + ) { + newChild = replaceNumberField(j, child); + } else { + newChild = wrapChild(j, child); + } + + // Replace the original child with the new child + root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'DataTable', + }, + }, + }).forEach(dataTableComponent => { + dataTableComponent.value.children = + dataTableComponent.value.children.map(c => + c === child ? newChild : c + ); + }); +}; + +const wrapChild = (j, child) => { // Wrap the child in a DataTable.Col component - const wrappedChild = j.jsxElement( + return j.jsxElement( j.jsxOpeningElement( j.jsxIdentifier('DataTable.Col'), [ @@ -191,19 +237,24 @@ const wrapChild = (root, j, child) => { j.jsxClosingElement(j.jsxIdentifier('DataTable.Col')), [j.jsxText('\n'), child, j.jsxText('\n')] ); +}; - // Replace the original child with the wrapped child - root.find(j.JSXElement, { - openingElement: { - name: { - type: 'JSXIdentifier', - name: 'DataTable', - }, - }, - }).forEach(dataTableComponent => { - dataTableComponent.value.children = - dataTableComponent.value.children.map(c => - c === child ? wrappedChild : c - ); - }); +const replaceTextField = (j, child) => { + return j.jsxElement( + j.jsxOpeningElement( + j.jsxIdentifier('DataTable.Col'), + child.openingElement.attributes, + true + ) + ); +}; + +const replaceNumberField = (j, child) => { + return j.jsxElement( + j.jsxOpeningElement( + j.jsxIdentifier('DataTable.NumberCol'), + child.openingElement.attributes, + true + ) + ); }; From e0834398a290a94cb9497fcf18c8ef2c1c807b1b Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 11:06:37 +0200 Subject: [PATCH 11/20] codemod -> clean imports if you don't still use TextField or NumberField --- .../codemods/replace-Datagrid-DataTable.ts | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 2bf87343520..c806d64cf5e 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -14,10 +14,8 @@ module.exports = (file, api: j.API) => { return root.toSource(); } - const continueAfterWrap = wrapChildren(root, j); - if (!continueAfterWrap) { - return root.toSource(); - } + wrapChildren(root, j); + cleanImports(root, j); return root.toSource({ quote: 'single', lineTerminator: '\n' }); }; @@ -258,3 +256,82 @@ const replaceNumberField = (j, child) => { ) ); }; + +const cleanImports = (root, j) => { + // Check if there is still a use of TextField in the code + const textFieldUsage = root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'TextField', + }, + }, + }); + // Check if there is still a use of NumberField in the code + const numberFieldUsage = root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'NumberField', + }, + }, + }); + + const imports = root.find(j.ImportDeclaration, { + source: { + value: 'react-admin', + }, + }); + // Check if there is an import of TextField from react-admin + const textFieldImport = imports.filter(path => { + return path.node.specifiers.some( + specifier => + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'TextField' + ); + }); + const numberFieldImport = imports.filter(path => { + return path.node.specifiers.some( + specifier => + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'NumberField' + ); + }); + + if (!textFieldUsage.length && textFieldImport.length) { + // Remove the import of TextField from react-admin + textFieldImport.forEach(path => { + path.node.specifiers = path.node.specifiers.filter( + specifier => + !( + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'TextField' + ) + ); + }); + // Remove the import declaration if there are no more specifiers + root.find(j.ImportDeclaration).forEach(path => { + if (path.node.specifiers.length === 0) { + j(path).remove(); + } + }); + } + if (!numberFieldUsage.length && numberFieldImport.length) { + // Remove the import of NumberField from react-admin + numberFieldImport.forEach(path => { + path.node.specifiers = path.node.specifiers.filter( + specifier => + !( + j.ImportSpecifier.check(specifier) && + specifier.imported.name === 'NumberField' + ) + ); + }); + // Remove the import declaration if there are no more specifiers + root.find(j.ImportDeclaration).forEach(path => { + if (path.node.specifiers.length === 0) { + j(path).remove(); + } + }); + } +}; From 5817464f627e6f3d293984d6b52d3ce51b9fbcfb Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 11:23:01 +0200 Subject: [PATCH 12/20] adapt tests --- ...Datagrid-DataTable-ManyChildren.output.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx index 82aa451bbd0..1c5d19e67cb 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx @@ -18,7 +18,9 @@ const PostList = () => ( - + + + @@ -26,10 +28,18 @@ const PostList = () => ( - - - - + + + + + + + + + + + + ); From 6ac08a2d61f6770a3421394101a8394b8f1512f6 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 11:23:20 +0200 Subject: [PATCH 13/20] don't apply an empty source --- .../codemods/replace-Datagrid-DataTable.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index c806d64cf5e..e4760288fd6 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -214,22 +214,22 @@ const transformChild = (root, j, child) => { }; const wrapChild = (j, child) => { + const childSource = child.openingElement.attributes.find( + attr => j.JSXAttribute.check(attr) && attr.name.name === 'source' + )?.value?.value; + // Wrap the child in a DataTable.Col component return j.jsxElement( j.jsxOpeningElement( j.jsxIdentifier('DataTable.Col'), - [ - j.jsxAttribute( - j.jsxIdentifier('source'), - j.stringLiteral( - child.openingElement.attributes.find( - attr => - j.JSXAttribute.check(attr) && - attr.name.name === 'source' - )?.value?.value || '' - ) - ), - ], + !childSource + ? [] + : [ + j.jsxAttribute( + j.jsxIdentifier('source'), + j.stringLiteral(childSource) + ), + ], false ), j.jsxClosingElement(j.jsxIdentifier('DataTable.Col')), From 5df9956cedae8b11035c01c98b4d62120ac45236 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 15:18:00 +0200 Subject: [PATCH 14/20] fix wrong quote -> from `'` to `"` --- .../ra-core/codemods/replace-Datagrid-DataTable.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index e4760288fd6..039e74d1d57 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -214,22 +214,15 @@ const transformChild = (root, j, child) => { }; const wrapChild = (j, child) => { - const childSource = child.openingElement.attributes.find( + const sourceAttribute = child.openingElement.attributes.find( attr => j.JSXAttribute.check(attr) && attr.name.name === 'source' - )?.value?.value; + ); // Wrap the child in a DataTable.Col component return j.jsxElement( j.jsxOpeningElement( j.jsxIdentifier('DataTable.Col'), - !childSource - ? [] - : [ - j.jsxAttribute( - j.jsxIdentifier('source'), - j.stringLiteral(childSource) - ), - ], + !sourceAttribute ? [] : [sourceAttribute], false ), j.jsxClosingElement(j.jsxIdentifier('DataTable.Col')), From d94813251110086f4f890e7f2ca2783280b2b6e0 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 15:31:30 +0200 Subject: [PATCH 15/20] fix object structure (eg for sx definition) --- .../ra-core/codemods/replace-Datagrid-DataTable.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 039e74d1d57..f2745f2399f 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -82,7 +82,8 @@ const replaceDatagrid = (root, j) => { const openingElement = j.jsxOpeningElement( j.jsxIdentifier('DataTable'), - attributes + attributes, + false ); const closingElement = j.jsxClosingElement( j.jsxIdentifier('DataTable') @@ -116,24 +117,20 @@ const cleanAttributes = (node, j) => { ) { const expression = attr.value.expression; if (j.ObjectExpression.check(expression)) { - const properties = expression.properties.map(prop => { + expression.properties.map(prop => { if ( j.ObjectProperty.check(prop) && j.Literal.check(prop.key) && typeof prop.key.value === 'string' ) { - const newKey = prop.key.value.replace( + prop.key.value = prop.key.value.replace( /RaDatagrid-/g, 'RaDataTable-' ); - return j.objectProperty(j.literal(newKey), prop.value); } return prop; }); - return j.jsxAttribute( - j.jsxIdentifier('sx'), - j.jsxExpressionContainer(j.objectExpression(properties)) - ); + return attr; } } return attr; From 61fb0d2efe95f47147a0340c1303a71fada2cee9 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 15:34:13 +0200 Subject: [PATCH 16/20] improve tests to add a new usecase --- .../replace-Datagrid-DataTable-ManyChildren.input.tsx | 5 +++++ .../replace-Datagrid-DataTable-ManyChildren.output.tsx | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx index a929c629c0a..a4502a524ff 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx @@ -21,6 +21,11 @@ const PostList = () => ( + diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx index 1c5d19e67cb..2a134e5764d 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.output.tsx @@ -22,6 +22,12 @@ const PostList = () => ( + + From d70f0a4612554c3a9e07d98bb2502323843eb086 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 16:12:01 +0200 Subject: [PATCH 17/20] codemod -> if exist display label instead of source --- ...-Datagrid-DataTable-ManyChildren.input.tsx | 4 ++ ...Datagrid-DataTable-ManyChildren.output.tsx | 7 +- .../codemods/replace-Datagrid-DataTable.ts | 65 ++++++++----------- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx index a4502a524ff..e4a48b18c4a 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-ManyChildren.input.tsx @@ -20,6 +20,10 @@ const PostList = () => ( + ( + + + - diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index f2745f2399f..387a4c0c4e0 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -14,7 +14,7 @@ module.exports = (file, api: j.API) => { return root.toSource(); } - wrapChildren(root, j); + transformChildren(root, j); cleanImports(root, j); return root.toSource({ quote: 'single', lineTerminator: '\n' }); @@ -139,7 +139,7 @@ const cleanAttributes = (node, j) => { return sxRenamedAttributes; }; -const wrapChildren = (root, j) => { +const transformChildren = (root, j) => { // Find all instances of Datagrid const datagridComponents = root.find(j.JSXElement, { openingElement: { @@ -176,7 +176,7 @@ const transformChild = (root, j, child) => { !['source', 'label', 'empty'].includes(attr.name.name) ) ) { - newChild = replaceTextField(j, child); + child.openingElement.name.name = 'DataTable.Col'; } else if ( j.JSXElement.check(child) && child.openingElement.name.type === 'JSXIdentifier' && @@ -189,28 +189,31 @@ const transformChild = (root, j, child) => { ) ) ) { - newChild = replaceNumberField(j, child); + child.openingElement.name.name = 'DataTable.NumberCol'; } else { newChild = wrapChild(j, child); - } - // Replace the original child with the new child - root.find(j.JSXElement, { - openingElement: { - name: { - type: 'JSXIdentifier', - name: 'DataTable', + // Replace the original child with the new child + root.find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXIdentifier', + name: 'DataTable', + }, }, - }, - }).forEach(dataTableComponent => { - dataTableComponent.value.children = - dataTableComponent.value.children.map(c => - c === child ? newChild : c - ); - }); + }).forEach(dataTableComponent => { + dataTableComponent.value.children = + dataTableComponent.value.children.map(c => + c === child ? newChild : c + ); + }); + } }; const wrapChild = (j, child) => { + const labelAttribute = child.openingElement.attributes.find( + attr => j.JSXAttribute.check(attr) && attr.name.name === 'label' + ); const sourceAttribute = child.openingElement.attributes.find( attr => j.JSXAttribute.check(attr) && attr.name.name === 'source' ); @@ -219,7 +222,11 @@ const wrapChild = (j, child) => { return j.jsxElement( j.jsxOpeningElement( j.jsxIdentifier('DataTable.Col'), - !sourceAttribute ? [] : [sourceAttribute], + labelAttribute + ? [labelAttribute] + : sourceAttribute + ? [sourceAttribute] + : [], false ), j.jsxClosingElement(j.jsxIdentifier('DataTable.Col')), @@ -227,26 +234,6 @@ const wrapChild = (j, child) => { ); }; -const replaceTextField = (j, child) => { - return j.jsxElement( - j.jsxOpeningElement( - j.jsxIdentifier('DataTable.Col'), - child.openingElement.attributes, - true - ) - ); -}; - -const replaceNumberField = (j, child) => { - return j.jsxElement( - j.jsxOpeningElement( - j.jsxIdentifier('DataTable.NumberCol'), - child.openingElement.attributes, - true - ) - ); -}; - const cleanImports = (root, j) => { // Check if there is still a use of TextField in the code const textFieldUsage = root.find(j.JSXElement, { From 865de3552bb7c2cc3e1a5e9ec60840b66a92e588 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Fri, 30 May 2025 18:10:05 +0200 Subject: [PATCH 18/20] simplify the datagrid replace code --- .../codemods/replace-Datagrid-DataTable.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts index 387a4c0c4e0..8202df831c7 100644 --- a/packages/ra-core/codemods/replace-Datagrid-DataTable.ts +++ b/packages/ra-core/codemods/replace-Datagrid-DataTable.ts @@ -78,30 +78,28 @@ const replaceDatagrid = (root, j) => { // Replace Datagrid with DataTable datagridComponents.replaceWith(({ node }) => { - const attributes = cleanAttributes(node, j); - - const openingElement = j.jsxOpeningElement( - j.jsxIdentifier('DataTable'), - attributes, - false - ); - const closingElement = j.jsxClosingElement( - j.jsxIdentifier('DataTable') - ); - return j.jsxElement(openingElement, closingElement, node.children); + return { + ...node, + openingElement: { + ...node.openingElement, + name: j.jsxIdentifier('DataTable'), + attributes: cleanAttributes(node, j), + }, + closingElement: { + ...node.closingElement, + name: j.jsxIdentifier('DataTable'), + }, + }; }); return true; }; const cleanAttributes = (node, j) => { - // remove the `optimized` attribute if it exists - const filtredAttributes = node.openingElement.attributes.filter( - attr => !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') - ); + const initialAttributes = node.openingElement.attributes; // rename the `rowStyle` attribute to `rowSx` if it exists - const rowSxRenamedAttributes = filtredAttributes.map(attr => { + const rowSxRenamedAttributes = initialAttributes.map(attr => { if (j.JSXAttribute.check(attr) && attr.name.name === 'rowStyle') { return j.jsxAttribute(j.jsxIdentifier('rowSx'), attr.value); } @@ -136,7 +134,12 @@ const cleanAttributes = (node, j) => { return attr; }); - return sxRenamedAttributes; + // remove the `optimized` attribute if it exists + const finalAttributes = sxRenamedAttributes.filter( + attr => !(j.JSXAttribute.check(attr) && attr.name.name === 'optimized') + ); + + return finalAttributes; }; const transformChildren = (root, j) => { From 54a4bee32540e0bb05ea79e56996c7fc85b1e771 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 4 Jun 2025 16:56:07 +0200 Subject: [PATCH 19/20] fix test --- .../replace-Datagrid-DataTable-Props.output.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx index 0a78fc65d08..567028a71f0 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.output.tsx @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ /* eslint-disable import/no-extraneous-dependencies */ import * as React from 'react'; import { List, DataTable, useRecordContext } from 'react-admin'; @@ -29,8 +30,7 @@ const PostList = () => ( '& .RaDataTable-row:hover': { backgroundColor: '#f5f5f5', }, - }} - > + }}> From 7d36f5e113f4113f07b27cfb0ac153422579bb4c Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 4 Jun 2025 16:57:54 +0200 Subject: [PATCH 20/20] disable prettier on test --- .../replace-Datagrid-DataTable-Props.input.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx index 07da0345e62..0a556a21985 100644 --- a/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx +++ b/packages/ra-core/codemods/__testfixtures__/replace-Datagrid-DataTable-Props.input.tsx @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ /* eslint-disable import/no-extraneous-dependencies */ import * as React from 'react'; import { List, Datagrid, TextField, useRecordContext } from 'react-admin'; @@ -30,8 +31,7 @@ const PostList = () => ( '& .RaDatagrid-row:hover': { backgroundColor: '#f5f5f5', }, - }} - > + }}>