Add codemod for useRBAC import statement (#21023)

* feat: add codemode for useRBAC import statement

* fix: add admin and server as allowed root paths

* fix: preserve aliases and add import statetment after other import statements

* feat: change useAPIErrorHandler import to `@strapi/strapi/admin` codemod (#21025)

* enhancement: abstract changeImportSpecifier as a utility method

* fix: account for multiple aliases
This commit is contained in:
Bassel Kanso 2024-08-20 16:42:03 +03:00 committed by GitHub
parent 1467b8f3ff
commit a2eac9d891
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 139 additions and 1 deletions

View File

@ -0,0 +1,21 @@
import type { Transform } from 'jscodeshift';
import { changeImportSpecifier } from '../../utils/change-import';
/**
* change useAPIErrorHandler import from '@strapi/helper-plugin' to '@strapi/strapi/admin'
*/
const transform: Transform = (file, api) => {
const { j } = api;
const root = j.withParser('tsx')(file.source);
changeImportSpecifier(root, j, {
methodName: 'useAPIErrorHandler',
oldDependency: '@strapi/helper-plugin',
newDependency: '@strapi/strapi/admin',
});
return root.toSource();
};
export default transform;

View File

@ -0,0 +1,21 @@
import type { Transform } from 'jscodeshift';
import { changeImportSpecifier } from '../../utils/change-import';
/**
* change useRBAC import from '@strapi/helper-plugin' to '@strapi/strapi/admin'
*/
const transform: Transform = (file, api) => {
const { j } = api;
const root = j.withParser('tsx')(file.source);
changeImportSpecifier(root, j, {
methodName: 'useRBAC',
oldDependency: '@strapi/helper-plugin',
newDependency: '@strapi/strapi/admin',
});
return root.toSource();
};
export default transform;

View File

@ -0,0 +1,96 @@
import type { ImportDeclaration, JSCodeshift, Collection } from 'jscodeshift';
export const changeImportSpecifier = (
root: Collection,
j: JSCodeshift,
options: { methodName: string; oldDependency: string; newDependency: string }
): void => {
const { methodName, oldDependency, newDependency } = options;
// Flag to check if the method was imported from the old dependency
let methodImportedFromOldDependency = false;
const methodAliases: string[] = [];
// Remove the method from the old dependency and check if it was imported
root
.find(j.ImportDeclaration)
.filter((path) => path.node.source.value === oldDependency)
.forEach((path) => {
const importDeclaration: ImportDeclaration = path.node;
// Check if the method is imported from the old dependency
const methodSpecifiers = importDeclaration.specifiers?.filter(
(specifier) =>
specifier.type === 'ImportSpecifier' && specifier.imported.name === methodName
);
if (methodSpecifiers && methodSpecifiers.length > 0) {
methodImportedFromOldDependency = true;
// Collect all aliases for the method
methodSpecifiers.forEach((specifier) => {
if (specifier.local && specifier.local.name !== methodName) {
methodAliases.push(specifier.local.name);
} else {
methodAliases.push(methodName);
}
});
// Remove the method specifiers from the old import
const updatedSpecifiers = importDeclaration.specifiers?.filter(
(specifier) =>
specifier.type !== 'ImportSpecifier' || specifier.imported.name !== methodName
);
if (updatedSpecifiers && updatedSpecifiers.length > 0) {
// Replace the import with the updated specifiers if there are other imports left
j(path).replaceWith(j.importDeclaration(updatedSpecifiers, j.literal(oldDependency)));
} else {
// Remove the entire import statement if the specified method was the only import
j(path).remove();
}
}
});
// Add new import dependency if the method was imported from the old dependency
if (methodImportedFromOldDependency) {
const dependencies = root
.find(j.ImportDeclaration)
.filter((path) => path.node.source.value === newDependency);
if (dependencies.length > 0) {
dependencies.forEach((path) => {
const importDeclaration: ImportDeclaration = path.node;
methodAliases.forEach((alias) => {
const newSpecifier = j.importSpecifier(j.identifier(methodName), j.identifier(alias));
const specifiersArray = importDeclaration.specifiers || [];
j(path).replaceWith(
j.importDeclaration([...specifiersArray, newSpecifier], j.literal(newDependency))
);
});
});
} else {
const newSpecifiers = methodAliases.map((alias) =>
j.importSpecifier(j.identifier(methodName), j.identifier(alias))
);
const newImportDeclaration = j.importDeclaration(newSpecifiers, j.literal(newDependency));
// Find the index of the first non-import declaration
const body = root.get().node.program.body;
const lastImportIndex = body.findIndex((node) => node.type !== 'ImportDeclaration');
if (lastImportIndex > -1) {
// Insert the new import declaration just before the first non-import node
body.splice(lastImportIndex, 0, newImportDeclaration);
} else {
// Check if 'use strict' exists at the beginning
const hasUseStrict =
body[0]?.type === 'ExpressionStatement' && body[0]?.expression?.value === 'use strict';
// Add the new import after 'use strict' if it exists, otherwise at the beginning
body.splice(hasUseStrict ? 1 : 0, 0, newImportDeclaration);
}
}
}
};

View File

@ -1,6 +1,6 @@
export const PROJECT_PACKAGE_JSON = 'package.json';
export const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ['src', 'config', 'public'];
export const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ['src', 'config', 'public', 'admin', 'server'];
export const PROJECT_DEFAULT_CODE_EXTENSIONS = [
// Source files