778 lines
18 KiB
JavaScript
778 lines
18 KiB
JavaScript
import {
|
|
hasSideEffect,
|
|
isCommaToken,
|
|
isSemicolonToken,
|
|
findVariable,
|
|
} from '@eslint-community/eslint-utils';
|
|
import {
|
|
isMethodCall,
|
|
isMemberExpression,
|
|
isNewExpression,
|
|
} from './ast/index.js';
|
|
import {
|
|
removeExpressionStatement,
|
|
removeArgument,
|
|
} from './fix/index.js';
|
|
import {
|
|
getNextNode,
|
|
getCallExpressionArgumentsText,
|
|
getParenthesizedText,
|
|
getVariableIdentifiers,
|
|
getNewExpressionTokens,
|
|
isNewExpressionWithParentheses,
|
|
} from './utils/index.js';
|
|
|
|
/**
|
|
@import {TSESTree as ESTree} from '@typescript-eslint/types';
|
|
@import * as ESLint from 'eslint';
|
|
*/
|
|
|
|
const MESSAGE_ID_ERROR = 'error';
|
|
const MESSAGE_ID_SUGGESTION_ARRAY = 'suggestion/array';
|
|
const MESSAGE_ID_SUGGESTION_OBJECT = 'suggestion/object';
|
|
const MESSAGE_ID_SUGGESTION_OBJECT_ASSIGN = 'suggestion/object-assign';
|
|
const MESSAGE_ID_SUGGESTION_SET = 'suggestion/set';
|
|
const MESSAGE_ID_SUGGESTION_MAP = 'suggestion/map';
|
|
const messages = {
|
|
[MESSAGE_ID_ERROR]: 'Immediate mutation on {{objectType}} is not allowed.',
|
|
[MESSAGE_ID_SUGGESTION_ARRAY]: '{{operation}} the elements to the {{assignType}}.',
|
|
[MESSAGE_ID_SUGGESTION_OBJECT]: 'Move this property to the {{assignType}}.',
|
|
[MESSAGE_ID_SUGGESTION_OBJECT_ASSIGN]: '{{description}} the {{assignType}}.',
|
|
[MESSAGE_ID_SUGGESTION_SET]: 'Move the element to the {{assignType}}.',
|
|
[MESSAGE_ID_SUGGESTION_MAP]: 'Move the entry to the {{assignType}}.',
|
|
};
|
|
|
|
const hasVariableInNodes = (variable, nodes, context) => {
|
|
const {sourceCode} = context;
|
|
const identifiers = getVariableIdentifiers(variable);
|
|
return nodes.some(node => {
|
|
const range = sourceCode.getRange(node);
|
|
return identifiers.some(identifier => {
|
|
const [start, end] = sourceCode.getRange(identifier);
|
|
return start >= range[0] && end <= range[1];
|
|
});
|
|
});
|
|
};
|
|
|
|
function isCallExpressionWithOptionalArrayExpression(newExpression, names) {
|
|
if (!isNewExpression(
|
|
newExpression,
|
|
{names, maximumArguments: 1},
|
|
)) {
|
|
return false;
|
|
}
|
|
|
|
// `new Set();` and `new Set([]);`
|
|
const [iterable] = newExpression.arguments;
|
|
return (!iterable || iterable.type === 'ArrayExpression');
|
|
}
|
|
|
|
function * removeExpressionStatementAfterAssign(expressionStatement, context, fixer) {
|
|
const tokenBefore = context.sourceCode.getTokenBefore(expressionStatement);
|
|
const shouldPreserveSemiColon = !isSemicolonToken(tokenBefore);
|
|
yield removeExpressionStatement(expressionStatement, context, fixer, shouldPreserveSemiColon);
|
|
}
|
|
|
|
function appendListTextToArrayExpressionOrObjectExpression(
|
|
context,
|
|
fixer,
|
|
arrayOrObjectExpression,
|
|
listText,
|
|
) {
|
|
const {sourceCode} = context;
|
|
const [
|
|
penultimateToken,
|
|
closingBracketToken,
|
|
] = sourceCode.getLastTokens(arrayOrObjectExpression, 2);
|
|
const list = arrayOrObjectExpression.type === 'ArrayExpression'
|
|
? arrayOrObjectExpression.elements
|
|
: arrayOrObjectExpression.properties;
|
|
const shouldInsertComma = list.length > 0 && !isCommaToken(penultimateToken);
|
|
|
|
return fixer.insertTextBefore(
|
|
closingBracketToken,
|
|
`${shouldInsertComma ? ',' : ''} ${listText}`,
|
|
);
|
|
}
|
|
|
|
function * appendElementsTextToSetConstructor({
|
|
context,
|
|
fixer,
|
|
newExpression,
|
|
elementsText,
|
|
nextExpressionStatement,
|
|
}) {
|
|
if (isNewExpressionWithParentheses(newExpression, context)) {
|
|
const [setInitialValue] = newExpression.arguments;
|
|
if (setInitialValue) {
|
|
yield appendListTextToArrayExpressionOrObjectExpression(context, fixer, setInitialValue, elementsText);
|
|
} else {
|
|
const {
|
|
openingParenthesisToken,
|
|
} = getNewExpressionTokens(newExpression, context);
|
|
yield fixer.insertTextAfter(openingParenthesisToken, `[${elementsText}]`);
|
|
}
|
|
} else {
|
|
/*
|
|
The new expression doesn't have parentheses
|
|
```
|
|
const set = (( new (( Set )) ));
|
|
set.add(1);
|
|
```
|
|
*/
|
|
yield fixer.insertTextAfter(newExpression, `([${elementsText}])`);
|
|
}
|
|
|
|
yield * removeExpressionStatementAfterAssign(nextExpressionStatement, context, fixer);
|
|
}
|
|
|
|
function getObjectExpressionPropertiesText(objectExpression, context) {
|
|
const {sourceCode} = context;
|
|
const openingBraceToken = sourceCode.getFirstToken(objectExpression);
|
|
const [penultimateToken, closingBraceToken] = sourceCode.getLastTokens(objectExpression, 2);
|
|
const [, start] = sourceCode.getRange(openingBraceToken);
|
|
const [end] = sourceCode.getRange(isCommaToken(penultimateToken) ? penultimateToken : closingBraceToken);
|
|
return sourceCode.text.slice(start, end);
|
|
}
|
|
|
|
/**
|
|
@typedef {ESTree.VariableDeclarator['init'] | ESTree.AssignmentExpression['right']} ValueNode
|
|
@typedef {(information: ViolationCaseInformation, arguments: any)} GetFix
|
|
@typedef {Parameters<ESLint.Rule.RuleContext['report']>[0]} Problem
|
|
@typedef {(information: ViolationCaseInformation) => ESTree.Node} GetProblematicNode
|
|
@typedef {{
|
|
context: ESLint.Rule.RuleContext,
|
|
variable: ESLint.Scope.Variable,
|
|
variableNode: ESTree.Identifier,
|
|
valueNode: ValueNode,
|
|
statement: ESTree.VariableDeclaration | ESTree.ExpressionStatement,
|
|
nextExpressionStatement: ESTree.ExpressionStatement,
|
|
assignType: 'assignment' | 'declaration',
|
|
getFix: GetFix,
|
|
}} ViolationCaseInformation
|
|
@typedef {{
|
|
testValue: (value: ValueNode) => boolean,
|
|
getProblematicNode: GetProblematicNode,
|
|
getProblem: (node: ReturnType<GetProblematicNode>, information: ViolationCaseInformation) => Problem,
|
|
getFix: GetFix,
|
|
}} ViolationCase
|
|
*/
|
|
|
|
// `Array`
|
|
/** @type {ViolationCase} */
|
|
const arrayMutationSettings = {
|
|
testValue: value => value?.type === 'ArrayExpression',
|
|
getProblematicNode({
|
|
context,
|
|
variable,
|
|
nextExpressionStatement,
|
|
}) {
|
|
const callExpression = nextExpressionStatement.expression;
|
|
|
|
if (!(
|
|
isMethodCall(callExpression, {
|
|
object: variable.name,
|
|
methods: ['push', 'unshift'],
|
|
optionalMember: false,
|
|
optionalCall: false,
|
|
})
|
|
&& callExpression.arguments.length > 0
|
|
)) {
|
|
return;
|
|
}
|
|
|
|
if (hasVariableInNodes(variable, callExpression.arguments, context)) {
|
|
return;
|
|
}
|
|
|
|
return callExpression;
|
|
},
|
|
getProblem(callExpression, information) {
|
|
const {
|
|
context,
|
|
assignType,
|
|
getFix,
|
|
} = information;
|
|
const {sourceCode} = context;
|
|
const memberExpression = callExpression.callee;
|
|
const method = memberExpression.property;
|
|
const problem = {
|
|
node: memberExpression,
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {objectType: 'array'},
|
|
};
|
|
|
|
const isPrepend = method.name === 'unshift';
|
|
const fix = getFix(information, {
|
|
callExpression,
|
|
isPrepend,
|
|
});
|
|
|
|
if (callExpression.arguments.some(element => hasSideEffect(element, sourceCode))) {
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_ARRAY,
|
|
fix,
|
|
data: {operation: isPrepend ? 'Prepend' : 'Append', assignType},
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
getFix: (
|
|
{
|
|
context,
|
|
valueNode: arrayExpression,
|
|
nextExpressionStatement,
|
|
},
|
|
{
|
|
callExpression,
|
|
isPrepend,
|
|
},
|
|
) => function * (fixer) {
|
|
const text = getCallExpressionArgumentsText(context, callExpression, /* includeTrailingComma */ false);
|
|
|
|
yield (
|
|
isPrepend
|
|
? fixer.insertTextAfter(
|
|
context.sourceCode.getFirstToken(arrayExpression),
|
|
`${text}, `,
|
|
)
|
|
: appendListTextToArrayExpressionOrObjectExpression(context, fixer, arrayExpression, text)
|
|
);
|
|
|
|
yield * removeExpressionStatementAfterAssign(
|
|
nextExpressionStatement,
|
|
context,
|
|
fixer,
|
|
);
|
|
},
|
|
};
|
|
|
|
// `Object` + `AssignmentExpression`
|
|
/** @type {ViolationCase} */
|
|
const objectWithAssignmentExpressionSettings = {
|
|
testValue: value => value?.type === 'ObjectExpression',
|
|
getProblematicNode({
|
|
context,
|
|
variable,
|
|
nextExpressionStatement,
|
|
}) {
|
|
const assignmentExpression = nextExpressionStatement.expression;
|
|
if (!(
|
|
assignmentExpression.type === 'AssignmentExpression'
|
|
&& assignmentExpression.operator === '='
|
|
&& isMemberExpression(assignmentExpression.left, {object: variable.name, optional: false})
|
|
)) {
|
|
return;
|
|
}
|
|
|
|
const value = assignmentExpression.right;
|
|
const memberExpression = assignmentExpression.left;
|
|
const {property} = memberExpression;
|
|
|
|
if (
|
|
hasVariableInNodes(
|
|
variable,
|
|
memberExpression.computed ? [property, value] : [value],
|
|
context,
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
return assignmentExpression;
|
|
},
|
|
getProblem(assignmentExpression, information) {
|
|
const {
|
|
context,
|
|
assignType,
|
|
getFix,
|
|
} = information;
|
|
const {sourceCode} = context;
|
|
const {
|
|
left: memberExpression,
|
|
right: value,
|
|
} = assignmentExpression;
|
|
|
|
const {property} = memberExpression;
|
|
const operatorToken = sourceCode.getTokenAfter(memberExpression, token => token.type === 'Punctuator' && token.value === assignmentExpression.operator);
|
|
|
|
const problem = {
|
|
node: assignmentExpression,
|
|
loc: {
|
|
start: sourceCode.getLoc(assignmentExpression).start,
|
|
end: sourceCode.getLoc(operatorToken).end,
|
|
},
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {objectType: 'object'},
|
|
};
|
|
const fix = getFix(information, {
|
|
assignmentExpression,
|
|
memberExpression,
|
|
property,
|
|
value,
|
|
});
|
|
|
|
if (
|
|
(memberExpression.computed && hasSideEffect(property, sourceCode))
|
|
|| hasSideEffect(value, sourceCode)
|
|
) {
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_OBJECT,
|
|
data: {assignType},
|
|
fix,
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
getFix: (
|
|
{
|
|
context,
|
|
valueNode: objectExpression,
|
|
nextExpressionStatement,
|
|
},
|
|
{
|
|
memberExpression,
|
|
property,
|
|
value,
|
|
},
|
|
) => function * (fixer) {
|
|
let propertyText = getParenthesizedText(property, context);
|
|
if (memberExpression.computed) {
|
|
propertyText = `[${propertyText}]`;
|
|
}
|
|
|
|
const valueText = getParenthesizedText(value, context);
|
|
|
|
const text = `${propertyText}: ${valueText},`;
|
|
const [
|
|
penultimateToken,
|
|
closingBraceToken,
|
|
] = context.sourceCode.getLastTokens(objectExpression, 2);
|
|
const shouldInsertComma = objectExpression.properties.length > 0 && !isCommaToken(penultimateToken);
|
|
|
|
yield fixer.insertTextBefore(
|
|
closingBraceToken,
|
|
`${shouldInsertComma ? ',' : ''} ${text}`,
|
|
);
|
|
|
|
yield * removeExpressionStatementAfterAssign(
|
|
nextExpressionStatement,
|
|
context,
|
|
fixer,
|
|
);
|
|
},
|
|
};
|
|
|
|
// `Object` + `Object.assign()`
|
|
/** @type {ViolationCase} */
|
|
const objectWithObjectAssignSettings = {
|
|
testValue: value => value?.type === 'ObjectExpression',
|
|
getProblematicNode({
|
|
context,
|
|
variable,
|
|
nextExpressionStatement,
|
|
}) {
|
|
const callExpression = nextExpressionStatement.expression;
|
|
|
|
if (!isMethodCall(callExpression, {
|
|
object: 'Object',
|
|
method: 'assign',
|
|
minimumArguments: 2,
|
|
optionalMember: false,
|
|
optionalCall: false,
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
const [object, firstValue] = callExpression.arguments;
|
|
|
|
if (
|
|
!(object.type === 'Identifier' && object.name === variable.name)
|
|
|| firstValue.type === 'SpreadElement'
|
|
|| hasVariableInNodes(variable, [firstValue], context)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
return callExpression;
|
|
},
|
|
getProblem(callExpression, information) {
|
|
const {
|
|
context,
|
|
assignType,
|
|
getFix,
|
|
} = information;
|
|
const {sourceCode} = context;
|
|
const [, firstValue] = callExpression.arguments;
|
|
|
|
const problem = {
|
|
node: callExpression.callee,
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {objectType: 'object'},
|
|
};
|
|
const fix = getFix(information, {
|
|
callExpression,
|
|
firstValue,
|
|
});
|
|
|
|
if (hasSideEffect(firstValue, sourceCode)) {
|
|
const description = firstValue.type === 'ObjectExpression'
|
|
? 'Move properties to'
|
|
: 'Spread properties in';
|
|
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_OBJECT_ASSIGN,
|
|
data: {description, assignType},
|
|
fix,
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
getFix: (
|
|
{
|
|
context,
|
|
valueNode: objectExpression,
|
|
nextExpressionStatement,
|
|
},
|
|
{
|
|
callExpression,
|
|
firstValue,
|
|
},
|
|
) => function * (fixer) {
|
|
let text;
|
|
if (firstValue.type === 'ObjectExpression') {
|
|
if (firstValue.properties.length > 0) {
|
|
text = getObjectExpressionPropertiesText(firstValue, context);
|
|
}
|
|
} else {
|
|
text = `...${getParenthesizedText(firstValue, context)}`;
|
|
}
|
|
|
|
if (text) {
|
|
yield appendListTextToArrayExpressionOrObjectExpression(context, fixer, objectExpression, text);
|
|
}
|
|
|
|
if (callExpression.arguments.length !== 2) {
|
|
yield removeArgument(fixer, firstValue, context);
|
|
|
|
return;
|
|
}
|
|
|
|
yield * removeExpressionStatementAfterAssign(
|
|
nextExpressionStatement,
|
|
context,
|
|
fixer,
|
|
);
|
|
},
|
|
};
|
|
|
|
// `Set` and `WeakSet`
|
|
/** @type {ViolationCase} */
|
|
const setMutationSettings = {
|
|
testValue: value => isCallExpressionWithOptionalArrayExpression(value, ['Set', 'WeakSet']),
|
|
getProblematicNode({
|
|
context,
|
|
variable,
|
|
nextExpressionStatement,
|
|
}) {
|
|
let callExpression = nextExpressionStatement.expression;
|
|
if (callExpression.type === 'ChainExpression') {
|
|
callExpression = callExpression.expression;
|
|
}
|
|
|
|
if (!isMethodCall(callExpression, {
|
|
object: variable.name,
|
|
method: 'add',
|
|
argumentsLength: 1,
|
|
optionalMember: false,
|
|
optionalCall: false,
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
if (hasVariableInNodes(variable, callExpression.arguments, context)) {
|
|
return;
|
|
}
|
|
|
|
return callExpression;
|
|
},
|
|
getProblem(callExpression, information) {
|
|
const {
|
|
context,
|
|
assignType,
|
|
valueNode: newExpression,
|
|
getFix,
|
|
} = information;
|
|
const {sourceCode} = context;
|
|
const memberExpression = callExpression.callee;
|
|
const problem = {
|
|
node: memberExpression,
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {objectType: `\`${newExpression.callee.name}\``},
|
|
};
|
|
|
|
const fix = getFix(information, {
|
|
callExpression,
|
|
newExpression,
|
|
});
|
|
|
|
if (callExpression.arguments.some(element => hasSideEffect(element, sourceCode))) {
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_SET,
|
|
data: {assignType},
|
|
fix,
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
getFix: (
|
|
{
|
|
context,
|
|
nextExpressionStatement,
|
|
},
|
|
{
|
|
callExpression,
|
|
newExpression,
|
|
},
|
|
) => fixer => {
|
|
const elementsText = getCallExpressionArgumentsText(
|
|
context,
|
|
callExpression,
|
|
/* IncludeTrailingComma */ false,
|
|
);
|
|
return appendElementsTextToSetConstructor({
|
|
context,
|
|
fixer,
|
|
newExpression,
|
|
elementsText,
|
|
nextExpressionStatement,
|
|
});
|
|
},
|
|
};
|
|
|
|
// `Map` and `WeakMap`
|
|
/** @type {ViolationCase} */
|
|
const mapMutationSettings = {
|
|
testValue: value => isCallExpressionWithOptionalArrayExpression(value, ['Map', 'WeakMap']),
|
|
getProblematicNode({
|
|
context,
|
|
variable,
|
|
nextExpressionStatement,
|
|
}) {
|
|
const callExpression = nextExpressionStatement.expression;
|
|
|
|
if (!isMethodCall(callExpression, {
|
|
object: variable.name,
|
|
method: 'set',
|
|
argumentsLength: 2,
|
|
optionalCall: false,
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
if (hasVariableInNodes(variable, callExpression.arguments, context)) {
|
|
return;
|
|
}
|
|
|
|
return callExpression;
|
|
},
|
|
getProblem(callExpression, information) {
|
|
const {
|
|
context,
|
|
assignType,
|
|
valueNode: newExpression,
|
|
getFix,
|
|
} = information;
|
|
const {sourceCode} = context;
|
|
const memberExpression = callExpression.callee;
|
|
const problem = {
|
|
node: memberExpression,
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {objectType: `\`${newExpression.callee.name}\``},
|
|
};
|
|
|
|
const fix = getFix(information, {
|
|
callExpression,
|
|
newExpression,
|
|
});
|
|
|
|
if (callExpression.arguments.some(element => hasSideEffect(element, sourceCode))) {
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_MAP,
|
|
data: {assignType},
|
|
fix,
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
getFix: (
|
|
{
|
|
context,
|
|
nextExpressionStatement,
|
|
},
|
|
{
|
|
callExpression,
|
|
newExpression,
|
|
},
|
|
) => fixer => {
|
|
const argumentsText = getCallExpressionArgumentsText(
|
|
context,
|
|
callExpression,
|
|
/* IncludeTrailingComma */ false,
|
|
);
|
|
const entryText = `[${argumentsText}]`;
|
|
return appendElementsTextToSetConstructor({
|
|
context,
|
|
fixer,
|
|
newExpression,
|
|
elementsText: entryText,
|
|
nextExpressionStatement,
|
|
});
|
|
},
|
|
};
|
|
|
|
const cases = [
|
|
arrayMutationSettings,
|
|
objectWithAssignmentExpressionSettings,
|
|
objectWithObjectAssignSettings,
|
|
setMutationSettings,
|
|
mapMutationSettings,
|
|
];
|
|
|
|
function isLastDeclarator(variableDeclarator) {
|
|
const variableDeclaration = variableDeclarator.parent;
|
|
return (
|
|
variableDeclaration.type === 'VariableDeclaration'
|
|
&& variableDeclaration.declarations.at(-1) === variableDeclarator
|
|
);
|
|
}
|
|
|
|
const getVariable = (node, context) => {
|
|
if (node.type === 'VariableDeclarator') {
|
|
return context.sourceCode.getDeclaredVariables(node)
|
|
.find(variable => variable.defs.length === 1 && variable.defs[0].name === node.id);
|
|
}
|
|
|
|
return findVariable(context.sourceCode.getScope(node), node.left.name);
|
|
};
|
|
|
|
function getCaseProblem(
|
|
context,
|
|
assignNode,
|
|
{
|
|
testValue,
|
|
getProblematicNode,
|
|
getProblem,
|
|
getFix,
|
|
},
|
|
) {
|
|
const isAssignment = assignNode.type === 'AssignmentExpression';
|
|
const [variableNode, valueNode] = (isAssignment ? ['left', 'right'] : ['id', 'init'])
|
|
.map(property => assignNode[property]);
|
|
|
|
// eslint-disable-next-line no-warning-comments
|
|
// TODO[@fisker]: `AssignmentExpression` should not limit to `Identifier`
|
|
if (!(variableNode.type === 'Identifier' && testValue(valueNode))) {
|
|
return;
|
|
}
|
|
|
|
const statement = assignNode.parent;
|
|
|
|
if (!(
|
|
// eslint-disable-next-line no-warning-comments
|
|
// TODO[@fisker]: `AssignmentExpression` should support `a = b = c` too
|
|
(
|
|
isAssignment
|
|
&& assignNode.operator === '='
|
|
&& statement.type === 'ExpressionStatement'
|
|
&& statement.expression === assignNode)
|
|
|| (!isAssignment && isLastDeclarator(assignNode))
|
|
)) {
|
|
return;
|
|
}
|
|
|
|
const nextExpressionStatement = getNextNode(statement, context);
|
|
if (nextExpressionStatement?.type !== 'ExpressionStatement') {
|
|
return;
|
|
}
|
|
|
|
const variable = getVariable(assignNode, context);
|
|
/* c8 ignore next */
|
|
if (!variable) {
|
|
return;
|
|
}
|
|
|
|
const information = {
|
|
context,
|
|
variable,
|
|
variableNode,
|
|
valueNode,
|
|
statement,
|
|
nextExpressionStatement,
|
|
assignType: isAssignment ? 'assignment' : 'declaration',
|
|
getFix,
|
|
};
|
|
|
|
const problematicNode = getProblematicNode(information);
|
|
|
|
if (!problematicNode) {
|
|
return;
|
|
}
|
|
|
|
return getProblem(problematicNode, information);
|
|
}
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
for (const caseSettings of cases) {
|
|
context.on(
|
|
[
|
|
'VariableDeclarator',
|
|
'AssignmentExpression',
|
|
],
|
|
assignNode => getCaseProblem(context, assignNode, caseSettings),
|
|
);
|
|
}
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
const config = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Disallow immediate mutation after variable assignment.',
|
|
recommended: true,
|
|
},
|
|
fixable: 'code',
|
|
hasSuggestions: true,
|
|
messages,
|
|
},
|
|
};
|
|
|
|
export default config;
|