Skip to content

Commit

Permalink
feat: singularize variable name in autofix for prefer-array-find rule
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed May 7, 2021
1 parent 2724afa commit 5d01a2c
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 15 deletions.
17 changes: 3 additions & 14 deletions rules/no-for-loop.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use strict';
const {singular} = require('pluralize');
const {isClosingParenToken} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const avoidCapture = require('./utils/avoid-capture');
const getChildScopesRecursive = require('./utils/get-child-scopes-recursive');
const singular = require('./utils/singular');

const MESSAGE_ID = 'no-for-loop';
const messages = {
Expand Down Expand Up @@ -268,18 +269,6 @@ const getReferencesInChildScopes = (scope, name) => {
];
};

const getChildScopesRecursive = scope => [
scope,
...scope.childScopes.flatMap(scope => getChildScopesRecursive(scope))
];

const getSingularName = originalName => {
const singularName = singular(originalName);
if (singularName !== originalName) {
return singularName;
}
};

const create = context => {
const sourceCode = context.getSourceCode();
const {scopeManager, text: sourceCodeText} = sourceCode;
Expand Down Expand Up @@ -361,7 +350,7 @@ const create = context => {

const index = indexIdentifierName;
const element = elementIdentifierName ||
avoidCapture(getSingularName(arrayIdentifierName) || defaultElementName, getChildScopesRecursive(bodyScope), context.parserOptions.ecmaVersion);
avoidCapture(singular(arrayIdentifierName) || defaultElementName, getChildScopesRecursive(bodyScope), context.parserOptions.ecmaVersion);
const array = arrayIdentifierName;

let declarationElement = element;
Expand Down
12 changes: 12 additions & 0 deletions rules/prefer-array-find.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const {isParenthesized, findVariable} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const getVariableIdentifiers = require('./utils/get-variable-identifiers');
const renameVariable = require('./utils/rename-variable');
const avoidCapture = require('./utils/avoid-capture');
const getChildScopesRecursive = require('./utils/get-child-scopes-recursive');
const singular = require('./utils/singular');

const ERROR_ZERO_INDEX = 'error-zero-index';
const ERROR_SHIFT = 'error-shift';
Expand Down Expand Up @@ -288,6 +292,14 @@ const create = context => {
problem.fix = function * (fixer) {
yield fixer.replaceText(node.init.callee.property, 'find');

const singularName = singular(node.id.name);
if (singularName) {
// Rename variable to be singularized now that it refers to a single item in the array instead of the entire array.
const singularizedName = avoidCapture(singularName, getChildScopesRecursive(context.getScope()), context.parserOptions.ecmaVersion);
const scope = context.getScope();
yield * renameVariable(findVariable(scope, node.id), singularizedName, fixer);
}

for (const node of zeroIndexNodes) {
yield fixer.removeRange([node.object.range[1], node.range[1]]);
}
Expand Down
14 changes: 14 additions & 0 deletions rules/utils/get-child-scopes-recursive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

/**
Gather a list of all Scopes starting recursively from the input Scope.
@param {Scope} scope - The Scope to start checking from.
@returns {Scope[]} - The resulting Scopes.
*/
const getChildScopesRecursive = scope => [
scope,
...scope.childScopes.flatMap(scope => getChildScopesRecursive(scope))
];

module.exports = getChildScopesRecursive;
18 changes: 18 additions & 0 deletions rules/utils/singular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const {singular: pluralizeSingular} = require('pluralize');

/**
Singularizes a word/name, i.e. `items` to `item`.
@param {string} original - The word/name to singularize.
@returns {string|undefined} - The singularized result, or `undefined` if attempting singularization resulted in no change.
*/
const singular = original => {
const singularized = pluralizeSingular(original);
if (singularized !== original) {
return singularized;
}
};

module.exports = singular;
45 changes: 44 additions & 1 deletion test/prefer-array-find.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ ruleTester.run('prefer-array-find', rule, {
'function a([foo] = array.filter(bar)) {}',
// Not `ArrayPattern`
'const foo = array.filter(bar)',
'const items = array.filter(bar)', // Plural variable name.
'const {0: foo} = array.filter(bar)',
// `elements`
'const [] = array.filter(bar)',
Expand Down Expand Up @@ -175,6 +176,12 @@ ruleTester.run('prefer-array-find', rule, {
output: 'const foo = array.find(bar)',
errors: [{messageId: ERROR_DESTRUCTURING_DECLARATION}]
},
{
// Plural variable name.
code: 'const [items] = array.filter(bar)',
output: 'const items = array.find(bar)',
errors: [{messageId: ERROR_DESTRUCTURING_DECLARATION}]
},
{
code: 'const [foo] = array.filter(bar, thisArgument)',
output: 'const foo = array.find(bar, thisArgument)',
Expand Down Expand Up @@ -376,6 +383,7 @@ ruleTester.run('prefer-array-find', rule, {
'function a([foo] = array.filter(bar)) {}',
// Not `ArrayPattern`
'foo = array.filter(bar)',
'items = array.filter(bar)', // Plural variable name.
'({foo} = array.filter(bar))',
// `elements`
'[] = array.filter(bar)',
Expand Down Expand Up @@ -626,7 +634,11 @@ ruleTester.run('prefer-array-find', rule, {
// More or less argument(s)
'const foo = array.filter(); const first = foo[0]',
'const foo = array.filter(bar, thisArgument, extraArgument); const first = foo[0]',
'const foo = array.filter(...bar); const first = foo[0]'
'const foo = array.filter(...bar); const first = foo[0]',

// Singularization
'const item = array.find(bar), first = item;', // Already singular variable name.
'let items = array.filter(bar); console.log(items[0]); items = [1,2,3]; console.log(items[0]);' // Reassigning array variable.
],
invalid: [
{
Expand Down Expand Up @@ -669,6 +681,37 @@ ruleTester.run('prefer-array-find', rule, {
output: 'const foo = array.find(bar); ({propOfFirst = unicorn} = foo);',
errors: [{messageId: ERROR_DECLARATION}]
},

// Singularization
{
// Multiple usages and child scope.
code: outdent`
const items = array.filter(bar);
const first = items[0];
console.log(items[0]);
function foo() { return items[0]; }
`,
output: outdent`
const item = array.find(bar);
const first = item;
console.log(item);
function foo() { return item; }
`,
errors: [{messageId: ERROR_DECLARATION}]
},
{
// Variable name collision.
code: 'const item = {}; const items = array.filter(bar); console.log(items[0]);',
output: 'const item = {}; const item_ = array.find(bar); console.log(item_);',
errors: [{messageId: ERROR_DECLARATION}]
},
{
// Variable defined with `let`.
code: 'let items = array.filter(bar); console.log(items[0]);',
output: 'let item = array.find(bar); console.log(item);',
errors: [{messageId: ERROR_DECLARATION}]
},

// Not fixable
{
code: 'const foo = array.filter(bar); const [first = bar] = foo;',
Expand Down

0 comments on commit 5d01a2c

Please sign in to comment.