routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
import {isParenthesized, hasSideEffect} from '@eslint-community/eslint-utils';
|
||||
import {isMethodCall} from './ast/index.js';
|
||||
import {
|
||||
getParenthesizedText,
|
||||
isNodeValueNotDomNode,
|
||||
isValueNotUsable,
|
||||
needsSemicolon,
|
||||
shouldAddParenthesesToMemberExpressionObject,
|
||||
} from './utils/index.js';
|
||||
|
||||
const ERROR_MESSAGE_ID = 'error';
|
||||
const SUGGESTION_MESSAGE_ID = 'suggestion';
|
||||
const messages = {
|
||||
[ERROR_MESSAGE_ID]: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.',
|
||||
[SUGGESTION_MESSAGE_ID]: 'Replace `parentNode.removeChild(childNode)` with `childNode{{dotOrQuestionDot}}remove()`.',
|
||||
};
|
||||
|
||||
// TODO: Don't check node.type twice
|
||||
const isMemberExpressionOptionalObject = node =>
|
||||
node.parent.type === 'MemberExpression'
|
||||
&& node.parent.object === node
|
||||
&& (
|
||||
node.parent.optional
|
||||
|| (node.type === 'MemberExpression' && isMemberExpressionOptionalObject(node.object))
|
||||
);
|
||||
|
||||
/** @param {import('eslint').Rule.RuleContext} context */
|
||||
const create = context => {
|
||||
const {sourceCode} = context;
|
||||
|
||||
context.on('CallExpression', node => {
|
||||
if (
|
||||
!isMethodCall(node, {
|
||||
method: 'removeChild',
|
||||
argumentsLength: 1,
|
||||
optionalCall: false,
|
||||
})
|
||||
|| isNodeValueNotDomNode(node.callee.object)
|
||||
|| isNodeValueNotDomNode(node.arguments[0])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentNode = node.callee.object;
|
||||
const childNode = node.arguments[0];
|
||||
|
||||
const problem = {
|
||||
node,
|
||||
messageId: ERROR_MESSAGE_ID,
|
||||
};
|
||||
|
||||
const isOptionalParentNode = isMemberExpressionOptionalObject(parentNode);
|
||||
|
||||
const createFix = (optional = false) => fixer => {
|
||||
let childNodeText = getParenthesizedText(childNode, context);
|
||||
if (
|
||||
!isParenthesized(childNode, sourceCode)
|
||||
&& shouldAddParenthesesToMemberExpressionObject(childNode, context)
|
||||
) {
|
||||
childNodeText = `(${childNodeText})`;
|
||||
}
|
||||
|
||||
if (needsSemicolon(sourceCode.getTokenBefore(node), context, childNodeText)) {
|
||||
childNodeText = `;${childNodeText}`;
|
||||
}
|
||||
|
||||
return fixer.replaceText(node, `${childNodeText}${optional ? '?' : ''}.remove()`);
|
||||
};
|
||||
|
||||
if (!hasSideEffect(parentNode, sourceCode) && isValueNotUsable(node)) {
|
||||
if (!isOptionalParentNode) {
|
||||
problem.fix = createFix(false);
|
||||
return problem;
|
||||
}
|
||||
|
||||
// The most common case `foo?.parentNode.remove(foo)`
|
||||
// TODO: Allow case like `foo.bar?.parentNode.remove(foo.bar)`
|
||||
if (
|
||||
node.callee.type === 'MemberExpression'
|
||||
&& !node.callee.optional
|
||||
&& parentNode.type === 'MemberExpression'
|
||||
&& parentNode.optional
|
||||
&& !parentNode.computed
|
||||
&& parentNode.property.type === 'Identifier'
|
||||
&& parentNode.property.name === 'parentNode'
|
||||
&& parentNode.object.type === 'Identifier'
|
||||
&& childNode.type === 'Identifier'
|
||||
&& parentNode.object.name === childNode.name
|
||||
) {
|
||||
problem.fix = createFix(true);
|
||||
return problem;
|
||||
}
|
||||
}
|
||||
|
||||
problem.suggest = (
|
||||
isOptionalParentNode ? [true, false] : [false]
|
||||
).map(optional => ({
|
||||
messageId: SUGGESTION_MESSAGE_ID,
|
||||
data: {dotOrQuestionDot: optional ? '?.' : '.'},
|
||||
fix: createFix(optional),
|
||||
}));
|
||||
|
||||
return problem;
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user