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

Update: support more options in prefer-destructuring #8796

Merged

Conversation

VictorHom
Copy link
Member

@VictorHom VictorHom commented Jun 24, 2017

[X ] Changes an existing rule (template)

What rule do you want to change?
prefer-destructuring.js
This pr is in reference to the issue #7886

Does this change cause the rule to produce more or fewer warnings?
less warnings since you have more flexibility to control destructuring errors.

How will the change be implemented? (New option, new default behavior, etc.)?
The first parameter of destructuring has been updated to also take a VariableDeclarator value or an AssignmentExpression value. Each value takes an object that takes an object of optional keys array and/or object.

The old parameters of {object: Boolean, array: Boolean} is still supported

Please provide some example code that this change will affect:

{
  "rules": {
    "prefer-destructuring": ["error", {
      "VariableDeclaration": {
        "array": false,
        "object": true
      },
     "AssignmentExpression": {
        "array": true,
        "object": true
      }
    }, {
      "enforceForRenamedProperties": false
    }]
  }
}

What does the rule currently do for this code?
In the above example, prefer-destructuring will be enforced for variable declarations from objects but not from arrays. Prefer-destructuring will be enforced for assignment expressions for both arrays and objects

What will the rule do after it's changed?
It will let users have more flexibility and control over when prefer-destructuring should be enforced.

@eslintbot
Copy link

LGTM

@mention-bot
Copy link

@VictorHom, thanks for your PR! By analyzing the history of the files in this pull request, we identified @alexlafroscia, @not-an-aardvark and @flowmemo to be potential reviewers.

@eslintbot
Copy link

LGTM

1 similar comment
@eslintbot
Copy link

LGTM

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! I left a few comments.

@@ -33,7 +33,9 @@ This rule takes two sets of configuration objects; the first controls the types

The first has two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are `true`.

The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
The first has two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for each of those types independently. Each property accepts an object that accpets two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.
Copy link
Member

Choose a reason for hiding this comment

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

Sorry if this was unclear before, but I think we should continue to allow the array and object top-level options. Making breaking changes to rule schemas is generally a pretty big deal -- in addition to breaking users, it also tends to make shareable configs invalid, which makes upgrading very tedious -- so we try to almost never do it. (Changing the default behavior of a rule schema while keeping all previous schemas valid is a bit better, but that's a separate topic.)

To avoid extra complexity, we can disallow mixing the old and new behaviors -- for example, this would still be an illegal config:

rules:
  prefer-destructuring: [error, {array: true, VariableDeclarator: {array: false}}]

The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
The first has two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for each of those types independently. Each property accepts an object that accpets two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.

The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed. If `object` in `AssignmentExpression` is set to false, `enforceForRenamedProperties` property is ignored.
Copy link
Member

Choose a reason for hiding this comment

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

Why is enforceForRenamedProperties ignored in this case? It seems like it would still be useful in variable declarations for something like

var foo = bar.baz

// ->

var {baz: foo} = bar;

report(reportNode, "object");
return;
}

if (checkObjects) {
if ((checkObjectsInVariableDeclarator && reportNode.type === "VariableDeclarator") || (checkObjectsInAssignmentExpression && reportNode.type === "AssignmentExpression")) {
Copy link
Member

Choose a reason for hiding this comment

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

I think the value in this if statement is redundant, because reportNode.type will always be either "VariableDeclarator" or "AssignmentExpression". It can be simplified to

if (checkObjectsInVariableDeclarator || checkObjectsInAssignmentExpression) {
  // ...
}

const assignmentExpression = enabledTypes.AssignmentExpression;

checkArraysInAssignmentExpression = assignmentExpression.hasOwnProperty("array") ? assignmentExpression.array : checkArraysInAssignmentExpression;
checkObjectsInAssignmentExpression = assignmentExpression.hasOwnProperty("object") ? assignmentExpression.object : checkObjectsInAssignmentExpression;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This config-checking logic is getting kind of complicated. I think having a variable for each config case won't scale very well as more options are added, especially if we restore support for the old top-level "array" option. Can we extract this into a function instead? For example:

/**
 * @param {string} nodeType "AssignmentExpression" or "VariableDeclaration"
 * @param {string} destructuringType "array" or "object"
 * @returns {boolean} `true` if the destructuring type should be checked for the given node
 */
function shouldCheck(nodeType, destructuringType) {
  return context.options[0] &&
    context.options[0][nodeType] &&
    context.options[0][nodeType][destructuringType] === false;
}

Then rather than handling four different variables, the performCheck function, it could just use something like

if (isArrayIndexAccess(node) && shouldCheck(reportNode.type, "array")) {
    // ...
}

// ...

@not-an-aardvark not-an-aardvark added accepted There is consensus among the team that this change meets the criteria for inclusion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules labels Jun 25, 2017
@eslintbot
Copy link

LGTM

@VictorHom VictorHom changed the title Breaking: support more options in prefer-destructuring Update: support more options in prefer-destructuring Jun 25, 2017
@VictorHom VictorHom force-pushed the pr/destructure_reassign_variables branch from 9ac42a3 to 08db36e Compare June 25, 2017 16:02
@eslintbot
Copy link

LGTM

@not-an-aardvark not-an-aardvark self-requested a review June 28, 2017 01:12
Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

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

Thanks for contributing! I have a few questions.

Also, I know this is from the proposal, but I wonder if VariableDeclaration might make more sense as an option instead of VariableDeclarator. @not-an-aardvark I know you proposed this originally - what do you think?

{
"rules": {
"prefer-destructuring": ["error", {
"VariableDeclaration": {
Copy link
Member

Choose a reason for hiding this comment

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

Should this be VariableDeclarator to match the other uses? I actually think VariableDeclaration might make more sense.

Copy link
Member Author

Choose a reason for hiding this comment

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

just want to make sure - you mean that you would prefer the attribute to be named VariableDeclaration?

Copy link
Member

@kaicataldo kaicataldo Jul 1, 2017

Choose a reason for hiding this comment

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

I feel like it makes more sense to someone who's not familiar with ESTree and node types, but let's see what others say. I don't want to hold this up over a naming thing, but wanted to bring it up since it is more difficult to change things once they're merged!

Copy link
Member

Choose a reason for hiding this comment

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

I like VariableDeclarator because I feel it's a closer parallel to AssignmentExpression, but I can see the potential for confusion. No strong opinion from me, though.

Copy link
Member

Choose a reason for hiding this comment

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

Let's leave it as VariableDeclarator - I don't want to hold this PR up over a naming thing, especially since I was late to the game in the disxussion. Sorry @VictorHom, looks like you already changed it

Copy link
Member Author

Choose a reason for hiding this comment

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

totally fine! I'm sure there's a few more things that could use some cleaning up too.

@@ -62,6 +62,45 @@ An example configuration, with the defaults filled in, looks like this:
}
```

Alternatively, you can use a different set of configuration objects to have more control on what to enforce destructuring. It also takes two sets of configuration objects.

The first has two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for each of those types independently. Each property accepts an object that accpets two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.
Copy link
Member

Choose a reason for hiding this comment

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

Typo: accpets -> accepts

@@ -110,22 +167,21 @@ module.exports = {
}

if (isArrayIndexAccess(rightNode)) {
if (checkArrays) {
if (shouldCheck("VariableDeclarator", "array") || shouldCheck("AssignmentExpression", "array") || checkArrays) {
Copy link
Member

Choose a reason for hiding this comment

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

Apologies if I'm misunderstanding something here, but I'm curious what happens for all these checks when the node being evaluated is either a VariableDeclarator or AssignmentExpression and the configuration is set to true for the other node type.

Some example test cases to illustrate what I mean:

{
    code: "foo = object.foo;",
    options: [{ VariableDeclarator: { object: true }, AssignmentExpression: { object: false } }]
},
{
    code: "var foo = object.foo;",
    options: [{ VariableDeclarator: { object: false }, AssignmentExpression: { object: true } }]
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. With this configuration, both of these are valid cases, along with a few more cases that I added

@eslintbot
Copy link

LGTM

1 similar comment
@eslintbot
Copy link

LGTM

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

Sorry about the delay. This looks mostly good, I just have a few small suggestions.


The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
The second has a single property, enforceForRenamedProperties, that controls whether or not the object destructuring rules are applied in cases where the variable requires the property being access to be renamed.
Copy link
Member

Choose a reason for hiding this comment

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

Can you surround enforceForRenamedProperties in backticks?


The first has two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.

The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
Copy link
Member

Choose a reason for hiding this comment

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

I think this documentation can be simplified because the enforceForRenamedProperties option is the same in either case. It seems a bit confusing that the description of that option is duplicated.

I'm picturing the documentation looking like this, but feel free to organize it differently:

This rule accepts an object as the first option, which determines what types of destructuring the rule applies to. (...more description about array and object options)
Alternatively, you can use separate configurations for different assignment types. (...more description about VariableDeclarator and AssignmentExpression options)
The rule also has a second object option with a single key ,enforceForRenamedProperties, which determines whether the object destructuring rule applies to renamed variables.

properties: {
array: {
type: "boolean",
default: true
Copy link
Member

Choose a reason for hiding this comment

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

Does the default property have any effect? I hadn't been aware it exists 😃

Copy link
Member Author

Choose a reason for hiding this comment

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

I like it for the clarity, but it has no visible effect from running tests.
I used after checking the examples in https://github.com/json-schema-org/json-schema-org.github.io/search?utf8=%E2%9C%93&q=default%3A&type=
let me know if that’s okay. otherwise, I’m not tied to it and if removing makes more sense, then I can remove.

if (typeof enabledTypes.VariableDeclarator !== "undefined" || typeof enabledTypes.AssignmentExpression !== "undefined") {
checkArrays = false;
checkObjects = false;
}
Copy link
Member

Choose a reason for hiding this comment

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

It's not a blocker for this PR, but having two separate methods of checking the options depending on the type of configuration used seems like a bug hazard if something changes later on. Maybe the old config-checking variables could be eliminated too with something like

const normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof context.options[0].object !== "undefined"
    ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
    : enabledTypes

In other words, the old array and object schema would basically be transformed into a special case of the VariableDeclarator/AssignmentExpression schema where both assignment types happen to have the same configured options.

Copy link
Member Author

Choose a reason for hiding this comment

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

updated to using a normalizedOptions

@@ -110,22 +167,27 @@ module.exports = {
}

if (isArrayIndexAccess(rightNode)) {
if (checkArrays) {
if ((shouldCheck("VariableDeclarator", "array") && reportNode.type === "VariableDeclarator") ||
(shouldCheck("AssignmentExpression", "array") && reportNode.type === "AssignmentExpression") ||
Copy link
Member

Choose a reason for hiding this comment

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

I think this could be simplified by just using if (shouldCheck(reportNode.type, "array")).

Copy link
Member Author

Choose a reason for hiding this comment

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

The main reason why I added the reportNode.type check is to handle cases

        {
            code: "foo = array[0];",
            options: [{ VariableDeclarator: { array: true }, AssignmentExpression: { array: false } }, { enforceForRenamedProperties: false }]
        },
        {
            code: "var foo = array[0];",
            options: [{ VariableDeclarator: { array: false }, AssignmentExpression: { array: true } }, { enforceForRenamedProperties: false }]
        },

where the check for array/object is swapped between variabledeclarator and assignmentexpression

},
{
code: "var foo = object[bar];",
code: "var foo = object['bar'];",
Copy link
Member

Choose a reason for hiding this comment

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

Is this change necessary? (I don't mind the new version, I'm just sometimes worried about changing old tests because that can prevent regressions from being caught.)

Copy link
Member Author

Choose a reason for hiding this comment

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

changed back. not sure why I updated to add quotes.

}]
},
{
code: "var foo = object[bar];",
Copy link
Member

Choose a reason for hiding this comment

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

Can you add some additional invalid tests? It would be nice to see:

  • A test for the array option with the VariableDeclarator/AssignmentExpression option style
  • A test with the AssignmentExpression option
  • A test where the VariableDeclarator and AssignmentExpression options are different (e.g. if the code has an object property access without destructuring in an assignment expression, and the options are {AssignmentExpression: {object: true}, VariableDeclarator: {object: false}}

Copy link
Member Author

Choose a reason for hiding this comment

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

Added more invalid tests

@eslintbot
Copy link

LGTM

1 similar comment
@eslintbot
Copy link

LGTM

@VictorHom
Copy link
Member Author

@not-an-aardvark just want to bring this pr back up for review when you have some time

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

Thanks, I left a few more comments.

@@ -29,11 +29,15 @@ var foo = object.bar;

### Options

This rule takes two sets of configuration objects; the first controls the types that the rule is applied to, and the second controls the way those objects are evaluated.
Copy link
Member

Choose a reason for hiding this comment

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

Can you move this section to before the code samples?

const enabledTypes = context.options[0];
const additionalOptions = context.options[1];
let enforceForRenamedProperties = false;
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: I think this can just be let enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;

@@ -110,22 +159,26 @@ module.exports = {
}

if (isArrayIndexAccess(rightNode)) {
if (checkArrays) {
if ((shouldCheck("VariableDeclarator", "array") && reportNode.type === "VariableDeclarator") ||
(shouldCheck("AssignmentExpression", "array") && reportNode.type === "AssignmentExpression")) {
Copy link
Member

Choose a reason for hiding this comment

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

(Continuing the discussion from #8796 (comment))

I'm not sure I understand -- wouldn't that result in the same behavior? Assuming reportNode.type is always either VariableDeclarator or AssignmentExpression, it seems like shouldCheck(reportNode.type, "array") would result in the same behavior. (The if statement here has two cases, but only one of them is relevant at any given time, so shouldCheck(reportNode.type, "array") would just combine the two cases.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I see what you are saying now. I misinterpreted. sorry for the confusion.

properties: {
array: {
type: "boolean",
default: true
Copy link
Member

Choose a reason for hiding this comment

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

(Continuing the discussion from #8796 (comment))

If it has no effects, then I think we should remove it, because it could be misleading and make people think that it does have an effect. If we want to indicate the default there, I think adding a comment would be better.

Copy link
Member

Choose a reason for hiding this comment

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

Generally, we've let the documentation and code speak for itself in this area

Copy link
Member Author

Choose a reason for hiding this comment

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

I will remove in that case


The first has two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are `true`.
The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true.
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be useful to give more examples for each option format after these descriptions, e.g.

The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true.

For example, the following configuration enforces only object destructuring, but not array destructuring:

```json
{
  "rules": {
    "prefer-destructuring": ["error", {"object": true, "array": false}]
  }
}
```

The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed.
Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`.

One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`.
Copy link
Member

Choose a reason for hiding this comment

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

Can you add some code samples with the VariableDeclarator and AssignmentExpression options?

@eslintbot
Copy link

LGTM

@VictorHom VictorHom force-pushed the pr/destructure_reassign_variables branch from d871981 to bf14aee Compare July 15, 2017 01:04
@eslintbot
Copy link

LGTM

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

I just have one nitpick with the docs, but it shouldn't block this from being merged.


Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced:

```javascript
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a configuration comment to this code block?

/* eslint prefer-destructuring: ["error", {VariableDeclarator: {array: true}}] */

@eslintbot
Copy link

LGTM

@VictorHom
Copy link
Member Author

Awesome! thanks for the code reviews.

@kaicataldo can you also give a review as well?

Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for contributing!

@ilyavolodin ilyavolodin merged commit 91dccdf into eslint:master Jul 21, 2017
@VictorHom VictorHom deleted the pr/destructure_reassign_variables branch July 21, 2017 19:26
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Feb 6, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Feb 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants