Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominik-Petrik committed Mar 26, 2024
1 parent a42bcf8 commit 25fbd5a
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,53 @@ import { Rule } from 'eslint';
import {
JSXOpeningElement,
ImportSpecifier,
JSXIdentifier,
JSXAttribute,
JSXIdentifier,
} from 'estree-jsx';
import { renameSinglePropOnNode } from './renameSinglePropOnNode';

interface RenameConfig {
export interface RenameConfig {
newName: string;
message?: string | ((node: any) => string);
replace?: boolean;
}

interface ComponentRenames {
export interface ComponentRenames {
[propName: string]: RenameConfig | string | Record<PropertyKey, never>;
}

export interface Renames {
[componentName: string]: ComponentRenames;
}

function isRenameConfig(obj: any): obj is RenameConfig {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.newName === 'string' &&
(obj.message === undefined ||
typeof obj.message === 'string' ||
typeof obj.message === 'function') &&
(obj.replace === undefined || typeof obj.replace === 'boolean')
);
}

export function renamePropsOnNode(
context: Rule.RuleContext,
imports: ImportSpecifier[],
node: JSXOpeningElement,
renames: Renames
) {
node.name = node.name as JSXIdentifier;
const componentName = imports.find(
(imp) =>
node.name.type === 'JSXIdentifier' && imp.local.name === node.name.name
)?.imported.name;

if (componentName) {
const renamedProps = renames[componentName];
node.attributes
.filter(
(attribute) =>
attribute.type === 'JSXAttribute' &&
attribute.name.type === 'JSXIdentifier' &&
renamedProps.hasOwnProperty(attribute.name?.name)
)
.forEach((attribute) => {
attribute = attribute as JSXAttribute;
attribute.name = attribute.name as JSXIdentifier;
const newPropObject = renamedProps[attribute.name.name];
if (!componentName) return;

const renamedProps = renames[componentName];

const message =
isRenameConfig(newPropObject) && newPropObject.message
? newPropObject.message instanceof Function
? newPropObject.message(node)
: newPropObject.message
: undefined;
const JSXAttributes = node.attributes.filter(
(attribute) =>
attribute.type === 'JSXAttribute' &&
attribute.name.type === 'JSXIdentifier' &&
renamedProps.hasOwnProperty(attribute.name?.name)
) as JSXAttribute[];

if (
(isRenameConfig(newPropObject) && newPropObject.newName !== '') ||
(typeof newPropObject === 'string' && newPropObject !== '')
) {
const newName = isRenameConfig(newPropObject)
? newPropObject.newName
: newPropObject;
context.report({
node,
message:
message ||
`${attribute.name.name} prop for ${
(node.name as JSXIdentifier).name
} has been ${
newPropObject.replace ? 'replaced with' : 'renamed to'
} ${newName}`,
fix(fixer) {
return fixer.replaceText(
newPropObject.replace
? attribute
: (attribute as JSXAttribute).name,
newName
);
},
});
} else {
context.report({
node,
message:
message ||
`${attribute.name.name} prop for ${
(node.name as JSXIdentifier).name
} has been removed`,
fix(fixer) {
return fixer.replaceText(attribute, '');
},
});
}
});
}
JSXAttributes.forEach((attribute) =>
renameSinglePropOnNode(
context,
attribute,
node,
renamedProps[(attribute.name as JSXIdentifier).name]
)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { JSXAttribute, JSXIdentifier, JSXOpeningElement } from 'estree-jsx';
import { RenameConfig } from './renamePropsOnNode';
import { Rule } from 'eslint';

function isRenameConfig(obj: any): obj is RenameConfig {
const hasValidObjectType = typeof obj === 'object' && obj !== null;

const hasValidNewNameType = typeof obj.newName === 'string';

const isValidMessage =
obj.message === undefined ||
typeof obj.message === 'string' ||
typeof obj.message === 'function';

const isValidReplace =
obj.replace === undefined || typeof obj.replace === 'boolean';

return (
hasValidObjectType &&
hasValidNewNameType &&
isValidMessage &&
isValidReplace
);
}

const getNewName = (
removed: boolean,
propRename: RenameConfig | string | Record<PropertyKey, never>
) => {
if (removed) return '';
return isRenameConfig(propRename) ? propRename.newName : propRename;
};

const getAction = (
removed: boolean,
propRename: RenameConfig | string | Record<PropertyKey, never>
) => {
if (removed) return 'removed';
return propRename.replace ? 'replaced with ' : 'renamed to ';
};

const getMessage = (
node: JSXOpeningElement,
propRename: RenameConfig | string | Record<PropertyKey, never>,
defaultMessage: string
) => {
if (!(isRenameConfig(propRename) && propRename.message))
return defaultMessage;

return propRename.message instanceof Function
? propRename.message(node)
: propRename.message;
};

export function renameSinglePropOnNode(
context: Rule.RuleContext,
attribute: JSXAttribute,
node: JSXOpeningElement,
propRename: RenameConfig | string | Record<PropertyKey, never>
) {
if (attribute.name.type !== 'JSXIdentifier') return;

const isConfig = isRenameConfig(propRename);

const isRemoved = !(
(isConfig && propRename.newName !== '') ||
(typeof propRename === 'string' && propRename !== '')
);

const action = getAction(isRemoved, propRename);

const newName = getNewName(isRemoved, propRename);

const defaultMessage = `${attribute.name.name} prop for ${
(node.name as JSXIdentifier).name
} has been ${action}${newName}`;
const message = getMessage(node, propRename, defaultMessage);

if (!isRemoved) {
context.report({
node,
message,
fix(fixer) {
return fixer.replaceText(
propRename.replace ? attribute : (attribute as JSXAttribute).name,
newName as string
);
},
});
} else {
context.report({
node,
message,
fix(fixer) {
return fixer.replaceText(attribute, '');
},
});
}
}

0 comments on commit 25fbd5a

Please sign in to comment.