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

[camelcase] false positive when parsing object pattern with a default value, and using a non-default parser. #13021

Closed
bradzacher opened this issue Mar 9, 2020 · 7 comments · Fixed by #14591
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 blocked This change can't be completed until another issue is resolved bug ESLint is working incorrectly rule Relates to ESLint's core rules

Comments

@bradzacher
Copy link
Contributor

Tell us about your environment

  • ESLint Version: 6.7.0
  • Node Version: 12

What parser (default, Babel-ESLint, etc.) are you using?
default, typescript-eslint

Please show your full configuration:

Configuration
{
  rules: {
    camelcase: ['error', {
      "properties": "never",
      "ignoreDestructuring": true
    }],
  },
}

What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.

Context: typescript-eslint/typescript-eslint#1686
A user of typescript-eslint received a linting error with the following code, even though it should be covered by the options above.

  const res = {a_b:123}
  const {a_b=0} = res
//       ^^^ Error Identifier 'a_b' is not in camel case

This error occurs when parsing with typescript-eslint, and not with the default parser.
Thinking this was a bug in the typescript-eslint parser, I did some digging, and it turns out that the acorn reuses the AST node in two places (i.e. the two a_b Identifiers are referentially equal).
acornjs/acorn#928

This causes the parent references to be set incorrectly, as the node's parent will be that of whichever node was traversed last.

Logging this here because:

  • the rule doesn't work with the "correct" parent pointers,
  • the rule will have to be updated for when the bug in acorn is fixed.
@bradzacher bradzacher added bug ESLint is working incorrectly triage An ESLint team member will look at this issue soon labels Mar 9, 2020
@bradzacher bradzacher changed the title [camelcase] Bug when [camelcase] false positive when parsing object pattern with a default value, and using a non-default parser. Mar 9, 2020
@mdjermanovic mdjermanovic added evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion rule Relates to ESLint's core rules and removed triage An ESLint team member will look at this issue soon labels Mar 9, 2020
@kaicataldo kaicataldo added accepted There is consensus among the team that this change meets the criteria for inclusion and removed evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion labels Mar 9, 2020
@kaicataldo
Copy link
Member

@bradzacher Thanks for the report. I was able to verify this.

@kaicataldo kaicataldo added the blocked This change can't be completed until another issue is resolved label Mar 9, 2020
@mdjermanovic
Copy link
Member

We should probably check some other rules, too.

Also, what's expected AST for each of these cases:

({ a });

({ a } = {});

import { a } from 'foo';

export { a };

Referentially equal identifier nodes, or not referentially equal identifier nodes (i.e. two different nodes), or the parser can choose either.

@bradzacher
Copy link
Contributor Author

bradzacher commented Mar 9, 2020

IMO there should never be referentially equal identifier nodes - every AST node should always be a brand new object, regardless of similarities to other nodes in the AST.

If you share nodes, then it means you have to be weary of mutating the nodes in case you put incorrect data in the AST (as is what's happening with the parent right now).

Allowing the parser to choose either just creates a headache for consumers, as they don't know what will happen if they mutate the object.

I believe that's the direction they're going to go toward in the acorn issue.

@mdjermanovic
Copy link
Member

I think it's reasonable to always have different objects.

But I'm not sure what the spec for ImportSpecifier actually says:

If it is a basic named import, such as in import {foo} from "mod", both imported and local are equivalent Identifier nodes; in this case an Identifier node representing foo. If it is an aliased import, such as in import {foo as bar} from "mod", the imported field is an Identifier node representing foo, and the local field is an Identifier node representing bar.

Sounds like it should be the same object ("an Identifier node representing foo")? Though, "equivalent" usually doesn't mean "same". Does typescript-eslint parser create two nodes in this case?

@bradzacher
Copy link
Contributor Author

bradzacher commented Mar 9, 2020

Does typescript-eslint parser create two nodes in this case

yes, it does.

> ast = tsestree.parse("import { a } from 'foo';")
{
  type: 'Program',
  body: [
    {
      type: 'ImportDeclaration',
      source: [Object],
      specifiers: [Array],
      importKind: 'value',
      range: [Array],
      loc: [Object]
    }
  ],
  sourceType: 'module',
  range: [ 0, 24 ],
  loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 24 } }
}
> ast.body[0].specifiers[0].imported === ast.body[0].specifiers[0].local
false

babel also creates two separate nodes:

> ast = require('@babel/core').parse("import { a } from 'foo';")
Node {
  type: 'File',
  start: 0,
  end: 24,
  loc: SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 24 }
  },
  errors: [],
  program: Node {
    type: 'Program',
    start: 0,
    end: 24,
    loc: SourceLocation { start: [Position], end: [Position] },
    sourceType: 'module',
    interpreter: null,
    body: [ [Node] ],
    directives: []
  },
  comments: []
}
> ast.program.body[0].specifiers[0].imported === ast.program.body[0].specifiers[0].local
false

@mdjermanovic
Copy link
Member

Then, I misread the estree spec while working on #12831 so we should probably fix that as well. Tests didn't catch it because acorn creates just 1 node.

I was sure it's the same node because there is no something like shorthand attribute, so it isn't possible to distinguish between import { a } from "foo" and import { a as a } from "foo" without comparing locations.

@mdjermanovic
Copy link
Member

Actually, #12831 seems to work well by chance. The name of the function isRenamedImport is wrong if parser always creates two nodes, but the code inside works okay and prevents reporting the same location twice.

It is, however, difficult to think about and cover both variations with same code.

mysticatea added a commit that referenced this issue May 11, 2021
This commit includes a large refactoring.
Previously, the `Identifier` node listener handled all cases by
checking parent node types. But because the `Identifier` node has so
broad meanings, it's confusing about what kind of nodes it's handling.

Now it uses variables and references of `eslint-scope`. And it checks
properties, re-exported identifiers, and labels by detailed esqueries.
The property check newly supports class fields and private identifiers.

This fixes #13021 as well.
nzakas pushed a commit that referenced this issue Jul 23, 2021
This commit includes a large refactoring.
Previously, the `Identifier` node listener handled all cases by
checking parent node types. But because the `Identifier` node has so
broad meanings, it's confusing about what kind of nodes it's handling.

Now it uses variables and references of `eslint-scope`. And it checks
properties, re-exported identifiers, and labels by detailed esqueries.
The property check newly supports class fields and private identifiers.

This fixes #13021 as well.
nzakas added a commit that referenced this issue Aug 5, 2021
…#14591)

* update package.json (temporary)

* update ast-utils

- `getFunctionNameWithKind(node)` ... supports class fields and private
  identifier. And now it uses property names rather than function names
  if named function expressions are methods because the property name
  is exposed.
- `getFunctionHeadLoc(node)` ... supports class fields. And now returns
  the range of property names instead of the arrow locations if arrow
  functions are at method places.
- `isSameReference(l,r)` ... supports `PrivateIdentifier`.

* update camelcase

This commit includes a large refactoring.
Previously, the `Identifier` node listener handled all cases by
checking parent node types. But because the `Identifier` node has so
broad meanings, it's confusing about what kind of nodes it's handling.

Now it uses variables and references of `eslint-scope`. And it checks
properties, re-exported identifiers, and labels by detailed esqueries.
The property check newly supports class fields and private identifiers.

This fixes #13021 as well.

* update accessor-pairs (test-only)

* update class-methods-use-this

* update computed-property-spacing

* update dot-location (test-only)

* update dot-notation (test-only)

* update func-names

Function expressions at field initializers have inferred names.
Therefore the `as-needed` option should not report anonymous functions
at field initializers.

* update getter-return (test-only)

* update grouped-accessor-pairs (test-only)

* update indent

* update keyword-spacing

* update lines-between-class-members (test-only)

* update no-dupe-class-members

* update no-extra-semi

* update no-invalid-this

* update no-multi-assign

* update no-proto (test-only)

* update no-prototype-builtins (test-only)

* update no-restricted-properties (test-only)

* update no-self-assign

* update no-self-compare (test-only)

* update no-setter-return

* update no-shadow (test-only)

* update no-this-before-super (test-only)

* update no-throw-literal (test-only)

* update no-undef-init

* update no-underscore-dangle

* update no-unexpected-multiline (test-only)

* update no-unreachable

* update no-useless-call (test-only)

* update no-useless-computed-key

* update no-eval

* update operator-assignment (test-only)

* update operator-linebreak

* update padded-blocks (test-only)

* update prefer-exponentiation-operator

* update prefer-numeric-literals (test-only)

* update prefer-object-spread (test-only)

* update prefer-promise-reject-errors (test-only)

* update prefer-regex-literals (test-only)

* update prefer-spread (test-only)

* update quotes

* update radix (test-only)

* update require-atomic-updates (test-only)

* update require-unicode-regexp (test-only)

* update semi-spacing

* update semi-style

* update semi

* update space-infix-ops

* update strict (test-only)

* add more tests to no-unexpected-multiline

* fix some tests for 7345747

* fix no-invalid-this

* fix no-eval

* Update eslint-scope

* Upgrade Espree

* Fix eslint-scope references to parser test

* Fix: id-denylist

* Update comments

* Fix: id-match

* Fix: id-length

* Update: id-denylist for class fields

* Update: id-length

* Update: id-denylist code and docs

* Docs: id-denylist

* Update: id-match

Co-authored-by: Nicholas C. Zakas <nicholas@humanwhocodes.com>
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Feb 2, 2022
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Feb 2, 2022
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 blocked This change can't be completed until another issue is resolved bug ESLint is working incorrectly rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants