Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid naming collision with default array element variable in autofix for no-for-loop rule #749

Merged
merged 6 commits into from May 27, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion rules/no-for-loop.js
Expand Up @@ -2,6 +2,7 @@
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const {flatten} = require('lodash');
const avoidCapture = require('./utils/avoid-capture');

const defaultElementName = 'element';
const isLiteralZero = node => isLiteralValue(node, 0);
Expand Down Expand Up @@ -261,6 +262,13 @@ const getReferencesInChildScopes = (scope, name) => {
];
};

const getChildScopesRecursive = scope => {
return [
scope,
...flatten(scope.childScopes.map(s => getChildScopesRecursive(s)))
];
};

const create = context => {
const sourceCode = context.getSourceCode();
const {scopeManager} = sourceCode;
Expand Down Expand Up @@ -335,7 +343,7 @@ const create = context => {
const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope, arrayIdentifierName);

const index = indexIdentifierName;
const element = elementIdentifierName || defaultElementName;
const element = elementIdentifierName || avoidCapture(defaultElementName, getChildScopesRecursive(bodyScope), context.parserOptions.ecmaVersion);
Copy link
Collaborator

@fisker fisker May 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not right.

			for (let i = 0; i < arr.length; i += 1) {
				console.log(arr[1]);
				function foo(element) {
					console.log(element);
				}
			}

It should allow use variable element.

this foo function scope should not included, because it didn't use arr[1], this is why avoidCapture didn't recursive automaticlly.

We need collect scopes from references.


BTW: don't squash commits, this makes review harder.

Copy link
Sponsor Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions for you.

  1. By arr[1], do you mean arr[i]? The rule doesn't report a violation with arr[1].
  2. In your example, I see that we technically can use element as our new array variable name, but wouldn't that be unnecessarily confusing? The no-shadow rule suggests this is bad practice.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes, arr[1], sorry.
  2. Yes no-shadow suggest no variable shadowing, it's default to function, do you use all option? My example is about function scope, but there are other scopes.

This example

for (let i = 0; i < errors.length; i++) {
  console.log(errors[i]);

  try {
    doSomethingThrowError();
  } catch(error) {}
}

After #745 we will fix it to

for (const error_ of errors) {
  console.log(error_);

  try {
    doSomethingThrowError();
  } catch(error) {}
}

I don't want use error_, I can use error.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, this is not very important, let's keep it this way now.

I'm going to check this PR again.

Copy link
Sponsor Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I will follow-up with your recommendation if I have a chance.

const array = arrayIdentifierName;

let declarationElement = element;
Expand Down
128 changes: 128 additions & 0 deletions test/no-for-loop.js
Expand Up @@ -482,6 +482,134 @@ ruleTester.run('no-for-loop', rule, {
const [ a, b ] = element;
console.log(a, b, element);
}
`),

// Avoid naming collision when using default element name.
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
console.log(arr[i]);
const element = foo();
console.log(element);
}
`, outdent`
for (const element_ of arr) {
console.log(element_);
const element = foo();
console.log(element);
}
`),

// Avoid naming collision when using default element name (different scope).
testCase(outdent`
function element(element_) {
for (let i = 0; i < arr.length; i += 1) {
console.log(arr[i], element);
}
}
`, outdent`
function element(element_) {
for (const element__ of arr) {
console.log(element__, element);
}
}
`),
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
function element__(element) {
console.log(arr[i], element);
}
}
`, outdent`
for (const element_ of arr) {
function element__(element) {
console.log(element_, element);
}
}
`),
bmish marked this conversation as resolved.
Show resolved Hide resolved
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
function element_(element) {
console.log(arr[i], element);
}
}
`, outdent`
for (const element__ of arr) {
function element_(element) {
console.log(element__, element);
}
}
`),
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
function element() {
console.log(arr[i], element);
}
}
`, outdent`
for (const element_ of arr) {
function element() {
console.log(element_, element);
}
}
`),
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
console.log(arr[i], element);
}
`, outdent`
for (const element_ of arr) {
console.log(element_, element);
}
`),
testCase(outdent`
for (let element = 0; element < arr.length; element += 1) {
console.log(element, arr[element]);
}
`, outdent`
for (const [element, element_] of arr.entries()) {
console.log(element, element_);
}
`),
testCase(outdent`
for (let element = 0; element < arr.length; element += 1) {
console.log(arr[element]);
}
`, outdent`
for (const element_ of arr) {
console.log(element_);
}
`),
testCase(outdent`
for (const element of arr) {
for (let j = 0; j < arr2.length; j += 1) {
console.log(element, arr2[j]);
}
}
`, outdent`
for (const element of arr) {
for (const element_ of arr2) {
console.log(element, element_);
}
}
`),

// Avoid naming collision when using default element name (multiple collisions).
testCase(outdent`
for (let i = 0; i < arr.length; i += 1) {
const element = foo();
console.log(arr[i]);
const element_ = foo();
console.log(element);
console.log(element_);
}
`, outdent`
for (const element__ of arr) {
const element = foo();
console.log(element__);
const element_ = foo();
console.log(element);
console.log(element_);
}
`)
]
});
Expand Down