routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
import {isSemicolonToken} from '@eslint-community/eslint-utils';
|
||||
import getIndentString from './utils/get-indent-string.js';
|
||||
|
||||
const MESSAGE_ID_ERROR = 'prefer-class-fields/error';
|
||||
const MESSAGE_ID_SUGGESTION = 'prefer-class-fields/suggestion';
|
||||
const messages = {
|
||||
[MESSAGE_ID_ERROR]:
|
||||
'Prefer class field declaration over `this` assignment in constructor for static values.',
|
||||
[MESSAGE_ID_SUGGESTION]:
|
||||
'Encountered same-named class field declaration and `this` assignment in constructor. Replace the class field declaration with the value from `this` assignment.',
|
||||
};
|
||||
|
||||
/**
|
||||
@param {import('eslint').Rule.Node} node
|
||||
@param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode
|
||||
@param {import('eslint').Rule.RuleFixer} fixer
|
||||
*/
|
||||
const removeFieldAssignment = (node, sourceCode, fixer) => {
|
||||
const {line} = sourceCode.getLoc(node).start;
|
||||
const nodeText = sourceCode.getText(node);
|
||||
const lineText = sourceCode.lines[line - 1];
|
||||
const isOnlyNodeOnLine = lineText.trim() === nodeText;
|
||||
|
||||
return isOnlyNodeOnLine
|
||||
? fixer.removeRange([
|
||||
sourceCode.getIndexFromLoc({line, column: 0}),
|
||||
sourceCode.getIndexFromLoc({line: line + 1, column: 0}),
|
||||
])
|
||||
: fixer.remove(node);
|
||||
};
|
||||
|
||||
/**
|
||||
@type {import('eslint').Rule.RuleModule['create']}
|
||||
*/
|
||||
const create = context => {
|
||||
const {sourceCode} = context;
|
||||
|
||||
context.on('ClassBody', classBody => {
|
||||
const constructor = classBody.body.find(node =>
|
||||
node.kind === 'constructor'
|
||||
&& !node.computed
|
||||
&& !node.static
|
||||
&& node.type === 'MethodDefinition'
|
||||
&& node.value.type === 'FunctionExpression');
|
||||
|
||||
if (!constructor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = constructor.value.body.body.find(node => node.type !== 'EmptyStatement');
|
||||
|
||||
if (!(
|
||||
node?.type === 'ExpressionStatement'
|
||||
&& node.expression.type === 'AssignmentExpression'
|
||||
&& node.expression.operator === '='
|
||||
&& node.expression.left.type === 'MemberExpression'
|
||||
&& node.expression.left.object.type === 'ThisExpression'
|
||||
&& !node.expression.left.computed
|
||||
&& ['Identifier', 'PrivateIdentifier'].includes(node.expression.left.property.type)
|
||||
&& node.expression.right.type === 'Literal'
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const propertyName = node.expression.left.property.name;
|
||||
const propertyValue = node.expression.right.raw;
|
||||
const propertyType = node.expression.left.property.type;
|
||||
const existingProperty = classBody.body.find(node =>
|
||||
node.type === 'PropertyDefinition'
|
||||
&& !node.computed
|
||||
&& !node.static
|
||||
&& node.key.type === propertyType
|
||||
&& node.key.name === propertyName);
|
||||
|
||||
const problem = {
|
||||
node,
|
||||
messageId: MESSAGE_ID_ERROR,
|
||||
};
|
||||
|
||||
/**
|
||||
@param {import('eslint').Rule.RuleFixer} fixer
|
||||
*/
|
||||
function * fix(fixer) {
|
||||
yield removeFieldAssignment(node, sourceCode, fixer);
|
||||
|
||||
if (existingProperty) {
|
||||
if (existingProperty.value) {
|
||||
yield fixer.replaceText(existingProperty.value, propertyValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const text = ` = ${propertyValue}`;
|
||||
const lastToken = sourceCode.getLastToken(existingProperty);
|
||||
if (isSemicolonToken(lastToken)) {
|
||||
yield fixer.insertTextBefore(lastToken, text);
|
||||
return;
|
||||
}
|
||||
|
||||
yield fixer.insertTextAfter(existingProperty, `${text};`);
|
||||
return;
|
||||
}
|
||||
|
||||
const closingBrace = sourceCode.getLastToken(classBody);
|
||||
const indent = getIndentString(constructor, context);
|
||||
|
||||
let text = `${indent}${propertyName} = ${propertyValue};\n`;
|
||||
|
||||
const characterBefore = sourceCode.getText()[sourceCode.getRange(closingBrace)[0] - 1];
|
||||
if (characterBefore !== '\n') {
|
||||
text = `\n${text}`;
|
||||
}
|
||||
|
||||
const lastProperty = classBody.body.at(-1);
|
||||
if (
|
||||
lastProperty.type === 'PropertyDefinition'
|
||||
&& sourceCode.getLastToken(lastProperty).value !== ';'
|
||||
) {
|
||||
text = `;${text}`;
|
||||
}
|
||||
|
||||
yield fixer.insertTextBefore(closingBrace, text);
|
||||
}
|
||||
|
||||
if (existingProperty?.value) {
|
||||
problem.suggest = [
|
||||
{
|
||||
messageId: MESSAGE_ID_SUGGESTION,
|
||||
fix,
|
||||
},
|
||||
];
|
||||
return problem;
|
||||
}
|
||||
|
||||
problem.fix = fix;
|
||||
return problem;
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Prefer class field declarations over `this` assignments in constructors.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user