Repository: eslint/eslint (js)

Issue:
The camelcase rule does not allow leading/trailing underscores for variable names
**Tell us about your environment**

* **ESLint Version:** v4.13.0
* **Node Version:** v8.9.3
* **npm Version:** v5.5.1

**What parser (default, Babel-ESLint, etc.) are you using?**
default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
{
    "parserOptions": {
        "ecmaVersion": 8,
        "sourceType": "module",
        "ecmaFeatures": {}
    },
    "rules": {
        "camelcase": 2
    },
    "env": {}
}
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

Contents of `test.js`:
<!-- Paste the source code below: -->
```js
function shouldAllowUndercoreVars() {
	let { _value } = this;
	return _value;
}
shouldAllowUndercoreVars()
```

Shell command used:
<!-- Paste the command you used to run ESLint: -->
```bash
eslint test.js
```

This is currently reproducible on the eslint demo site:
https://eslint.org/demo/#eyJ0ZXh0IjoiZnVuY3Rpb24gc2hvdWxkQWxsb3dVbmRlcmNvcmVWYXJzKCkge1xuXHRsZXQgeyBfdmFsdWUgfSA9IHRoaXM7XG5cdHJldHVybiBfdmFsdWU7XG59XG5zaG91bGRBbGxvd1VuZGVyY29yZVZhcnMoKSIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6OCwic291cmNlVHlwZSI6Im1vZHVsZSIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6eyJjYW1lbGNhc2UiOjJ9LCJlbnYiOnt9fX0=


**What did you expect to happen?**
I would have expected it to allow the variable name based on the [camelcase docs](https://eslint.org/docs/rules/camelcase) docs quoted below:
> It ignores leading and trailing underscores and only checks those in the middle of a variable name.

**What actually happened? Please include the actual, raw output from ESLint.**
```log
/Users/kyle/tmp/test.js
  2:8  error  Identifier '_value' is not in camel case  camelcase

✖ 1 problem (1 error, 0 warnings)
```

Pull request:
Fix: camelcase destructure leading/trailing underscore (fixes #9700)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[x] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

See #9700.

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

The camelcase rule has a subtle requirement that we keep track of both the original property/identifier name, as well as a version of the name with leading and trailing underscores stripped out. During review, we missed that the original property/identifier name was being checked in the new destructuring case.

This pull request should be followed with a chore to ensure that all underscore checks will strip leading/trailing underscores, to make future contributions to this rule easier.

**Is there anything you'd like reviewers to focus on?**

I've added a few different test cases. Have I missed anything?

Patch:
diff --git a/README.md b/README.md
index 5a23d6295..160e72e10 100644
--- a/README.md
+++ b/README.md
@@ -239,7 +239,7 @@ Once a language feature has been adopted into the ECMAScript standard (stage 4 a
 
 ### Where to ask for help?
 
-Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)
+Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint).
 
 
 [npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index aafa876f6..e3b764f7c 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -109,7 +109,7 @@ module.exports = {
 
                     if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
 
-                        if (node.parent.shorthand && node.parent.value.left && isUnderscored(node.parent.value.left.name)) {
+                        if (node.parent.shorthand && node.parent.value.left && isUnderscored(name)) {
 
                             report(node);
                         }
@@ -119,7 +119,7 @@ module.exports = {
                             return;
                         }
 
-                        if (node.parent.value.name && isUnderscored(node.parent.value.name)) {
+                        if (node.parent.value.name && isUnderscored(name)) {
                             report(node);
                         }
                     }
diff --git a/tools/internal-rules/no-invalid-meta.js b/tools/internal-rules/no-invalid-meta.js
index d13df358b..8082d1000 100644
--- a/tools/internal-rules/no-invalid-meta.js
+++ b/tools/internal-rules/no-invalid-meta.js
@@ -175,13 +175,20 @@ module.exports = {
                 }
             },
 
-            "Program:exit"() {
-                if (!isCorrectExportsFormat(exportsNode)) {
-                    context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." });
-                    return;
+            "Program:exit"(node) {
+                if (!exportsNode) {
+                    context.report({
+                        node,
+                        message: "Rule does not export anything. Make sure rule exports an object according to new rule format."
+                    });
+                } else if (!isCorrectExportsFormat(exportsNode)) {
+                    context.report({
+                        node: exportsNode,
+                        message: "Rule does not export an Object. Make sure the rule follows the new rule format."
+                    });
+                } else {
+                    checkMetaValidity(context, exportsNode);
                 }
-
-                checkMetaValidity(context, exportsNode);
             }
         };
     }


Test patch:
diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js
index 12716b5db..81251ff2c 100644
--- a/tests/lib/rules/camelcase.js
+++ b/tests/lib/rules/camelcase.js
@@ -43,14 +43,46 @@ ruleTester.run("camelcase", rule, {
             code: "var o = {key: 1}",
             options: [{ properties: "always" }]
         },
+        {
+            code: "var o = {_leading: 1}",
+            options: [{ properties: "always" }]
+        },
+        {
+            code: "var o = {trailing_: 1}",
+            options: [{ properties: "always" }]
+        },
         {
             code: "var o = {bar_baz: 1}",
             options: [{ properties: "never" }]
         },
+        {
+            code: "var o = {_leading: 1}",
+            options: [{ properties: "never" }]
+        },
+        {
+            code: "var o = {trailing_: 1}",
+            options: [{ properties: "never" }]
+        },
         {
             code: "obj.a_b = 2;",
             options: [{ properties: "never" }]
         },
+        {
+            code: "obj._a = 2;",
+            options: [{ properties: "always" }]
+        },
+        {
+            code: "obj.a_ = 2;",
+            options: [{ properties: "always" }]
+        },
+        {
+            code: "obj._a = 2;",
+            options: [{ properties: "never" }]
+        },
+        {
+            code: "obj.a_ = 2;",
+            options: [{ properties: "never" }]
+        },
         {
             code: "var obj = {\n a_a: 1 \n};\n obj.a_b = 2;",
             options: [{ properties: "never" }]
@@ -63,14 +95,38 @@ ruleTester.run("camelcase", rule, {
             code: "var { category_id: category } = query;",
             parserOptions: { ecmaVersion: 6 }
         },
+        {
+            code: "var { _leading } = query;",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var { trailing_ } = query;",
+            parserOptions: { ecmaVersion: 6 }
+        },
         {
             code: "import { camelCased } from \"external module\";",
             parserOptions: { ecmaVersion: 6, sourceType: "module" }
         },
+        {
+            code: "import { _leading } from \"external module\";",
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "import { trailing_ } from \"external module\";",
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
         {
             code: "import { no_camelcased as camelCased } from \"external-module\";",
             parserOptions: { ecmaVersion: 6, sourceType: "module" }
         },
+        {
+            code: "import { no_camelcased as _leading } from \"external-module\";",
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "import { no_camelcased as trailing_ } from \"external-module\";",
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
         {
             code: "import { no_camelcased as camelCased, anoterCamelCased } from \"external-module\";",
             parserOptions: { ecmaVersion: 6, sourceType: "module" }
@@ -79,13 +135,37 @@ ruleTester.run("camelcase", rule, {
             code: "function foo({ no_camelcased: camelCased }) {};",
             parserOptions: { ecmaVersion: 6 }
         },
+        {
+            code: "function foo({ no_camelcased: _leading }) {};",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ no_camelcased: trailing_ }) {};",
+            parserOptions: { ecmaVersion: 6 }
+        },
         {
             code: "function foo({ camelCased = 'default value' }) {};",
             parserOptions: { ecmaVersion: 6 }
         },
+        {
+            code: "function foo({ _leading = 'default value' }) {};",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ trailing_ = 'default value' }) {};",
+            parserOptions: { ecmaVersion: 6 }
+        },
         {
             code: "function foo({ camelCased }) {};",
             parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ _leading }) {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ trailing_ }) {}",
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
     invalid: [
diff --git a/tests/tools/internal-rules/no-invalid-meta.js b/tests/tools/internal-rules/no-invalid-meta.js
index 54b2c9628..5d675e1e3 100644
--- a/tests/tools/internal-rules/no-invalid-meta.js
+++ b/tests/tools/internal-rules/no-invalid-meta.js
@@ -241,6 +241,40 @@ ruleTester.run("no-invalid-meta", rule, {
                 line: 2,
                 column: 5
             }]
+        },
+
+        /*
+         * Rule doesn't export anything: Should warn on the Program node.
+         * See https://github.com/eslint/eslint/issues/9534
+         */
+
+        /*
+         * Should be invalid, but will currently show as valid due to #9534.
+         * FIXME: Uncomment when #9534 is fixed in major release.
+         * {
+         *     code: "",
+         *     errors: [{
+         *         message: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
+         *         line: 1,
+         *         column: 1
+         *     }]
+         * },
+         */
+        {
+            code: "foo();",
+            errors: [{
+                message: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
+                line: 1,
+                column: 1
+            }]
+        },
+        {
+            code: "foo = bar;",
+            errors: [{
+                message: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
+                line: 1,
+                column: 1
+            }]
         }
     ]
 });