Repository: eslint/eslint (js)

Issue:
Regression: ESLint is slow generating file lists for directories with large dot folders, e.g. Mercurial repositories, even when not including dot files
**Tell us about your environment**

* **ESLint Version:** 3.12.2 / 3.4.0
* **Node Version:** 7.7.2
* **npm Version:** 4.1.2
MacBook Pro Mid 2015.

**What parser (default, Babel-ESLint, etc.) are you using?** default

**Please show your full configuration:**

Please see https://dxr.mozilla.org/mozilla-central/source/

**What did you do? Please include the actual source code causing the issue.**

Run eslint on the repository, and compared the timings. For a full run on our repository, a git cinnabar checkout runs in 2m 13. For a Mercurial checkout it runs in 3m 45.

**What did you expect to happen?**

The repositories should take about the same time - the repository format shouldn't matter.

This seemed to be regressed in version 3.5.0 by #6844.

**What actually happened? Please include the actual, raw output from ESLint.**

The Mercurial has a large `.hg` folder (320268 files on my checkout). Whereas a git checkout has a much smaller `.git` folder (3857 files).

If the repository is checked out via Mercurial, then for an eslint run, the start of the debug looks like:

```
$ ./tools/lint/eslint/node_modules/.bin/eslint --debug .
  eslint:cli Running on files +0ms
  eslint:glob-util Creating list of files to process. +26ms
  eslint:ignored-paths Looking for ignore file in /Users/mark/dev/mozilla-central +3ms
  eslint:ignored-paths Loaded ignore file /Users/mark/dev/mozilla-central/.eslintignore +0ms
  eslint:ignored-paths Adding /Users/mark/dev/mozilla-central/.eslintignore +0ms
  eslint:cli-engine Processing /Users/mark/dev/mozilla-central/accessible/jsat/content-script.js +52s
```

If the repository is checked out into a git repository (via git-cinnabar), then the eslint run looks like:

```
$ ./tools/lint/eslint/node_modules/.bin/eslint --debug .
  eslint:cli Running on files +0ms
  eslint:glob-util Creating list of files to process. +24ms
  eslint:ignored-paths Looking for ignore file in /Users/mark/dev/gecko +3ms
  eslint:ignored-paths Loaded ignore file /Users/mark/dev/gecko/.eslintignore +0ms
  eslint:ignored-paths Adding /Users/mark/dev/gecko/.eslintignore +0ms
  eslint:cli-engine Processing /Users/mark/dev/gecko/accessible/jsat/content-script.js +7s
```

The +52s -> +7s is quite a big difference. If I include all the extensions that we process via `--ext` then this gets even worse (2m vs 12s).

#6844 enabled parsing of dot files, and enabled searching for them via `GlobSync`. If, as a temporary test, I change the glob options to set `dot` to `false`, then the timings are about the same for both repositories.

https://github.com/eslint/eslint/blob/d6150e300e86e2e55a6a3d048dfb6072ac2b6489/lib/util/glob-util.js#L167

Pull request:
Fix: Ignore hidden folders when resolving globs (fixes #8259)
**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:


**What changes did you make? (Give an overview)**

This is a performance improvement, but should not be a change to linting functionality.  This is similar to avoiding glob resolution within `node_modules`.  With this change, `node-glob` will not attempt to  find all files within `.dotfolder` folders.  Similar to `node_modules`, this can be overridden in a user’s ignore file or in an ignore-pattern, and is also prevented if the `dotfiles` option is `true`.

**Is there anything you'd like reviewers to focus on?**

It would be good to double-check our tests around hidden files and folders, as well as perhaps manually trying a few situations.  From everything I tested, this works exactly like before, except faster. 🐎 

Patch:
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index 2e94e5d66..ffe763c8b 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -58,4 +58,3 @@ If you don't want to restrict your code from using any JavaScript features or sy
 * [no-console](no-console.md)
 * [no-debugger](no-debugger.md)
 * [no-restricted-properties](no-restricted-properties.md)
-* [no-restricted-syntax](no-restricted-syntax.md)
diff --git a/docs/rules/no-unused-expressions.md b/docs/rules/no-unused-expressions.md
index 3b4a065e8..480915ee2 100644
--- a/docs/rules/no-unused-expressions.md
+++ b/docs/rules/no-unused-expressions.md
@@ -29,7 +29,8 @@ Sequence expressions (those using a comma, such as `a = 1, b = 2`) are always co
 This rule, in its default state, does not require any arguments. If you would like to enable one or more of the following you may pass an object with the options set as follows:
 
 * `allowShortCircuit` set to `true` will allow you to use short circuit evaluations in your expressions (Default: `false`).
-* `allowTernary` set to `true` will enable you use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
+* `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
+* `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`).
 
 These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call).
 
@@ -56,6 +57,8 @@ a() && function namedFunctionInExpressionContext () {f();}
 
 (function anIncompleteIIFE () {});
 
+injectGlobal`body{ color: red; }`
+
 ```
 
 Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives.
@@ -140,3 +143,21 @@ Examples of **correct** code for the `{ "allowShortCircuit": true, "allowTernary
 
 a ? b() || (c = d) : e()
 ```
+
+### allowTaggedTemplates
+
+Examples of **incorrect** code for the `{ "allowTaggedTemplates": true }` option:
+
+```js
+/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
+
+`some untagged template string`;
+```
+
+Examples of **correct** code for the `{ "allowTaggedTemplates": true }` option:
+
+```js
+/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
+
+tag`some tagged template string`;
+```
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index bace73db6..cfca7fa4f 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -201,6 +201,12 @@ class IgnoredPaths {
 
         const ig = ignore().add(DEFAULT_IGNORE_DIRS);
 
+        if (this.options.dotfiles !== true) {
+
+            // Ignore hidden folders.  (This cannot be ".*", or else it's not possible to unignore hidden files)
+            ig.add([".*/*", "!../"]);
+        }
+
         if (this.options.ignore) {
             ig.add(this.ig.custom);
         }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 35fdfbfa6..8be8a5a49 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -67,6 +67,7 @@ module.exports = {
         const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
         const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
         const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
+        const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
         const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
 
         /**
@@ -394,12 +395,12 @@ module.exports = {
                 report(node.callee);
             }
             if (node.arguments.length === 1) {
-                if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.arguments[0]);
                 }
             } else {
                 [].forEach.call(node.arguments, arg => {
-                    if (hasExcessParens(arg) && precedence(arg) >= precedence({ type: "AssignmentExpression" })) {
+                    if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(arg);
                     }
                 });
@@ -456,7 +457,11 @@ module.exports = {
          * @returns {void}
          */
         function checkSpreadOperator(node) {
-            if (node.argument && hasExcessParens(node.argument)) {
+            const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
+                ? hasExcessParens(node.argument)
+                : hasDoubleExcessParens(node.argument);
+
+            if (hasExtraParens) {
                 report(node.argument);
             }
         }
@@ -464,7 +469,7 @@ module.exports = {
         return {
             ArrayExpression(node) {
                 [].forEach.call(node.elements, e => {
-                    if (e && hasExcessParens(e) && precedence(e) >= precedence({ type: "AssignmentExpression" })) {
+                    if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(e);
                     }
                 });
@@ -476,7 +481,7 @@ module.exports = {
                 }
 
                 if (node.body.type !== "BlockStatement") {
-                    if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({ type: "AssignmentExpression" })) {
+                    if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(node.body);
                         return;
                     }
@@ -510,11 +515,11 @@ module.exports = {
                     report(node.test);
                 }
 
-                if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.consequent);
                 }
 
-                if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.alternate);
                 }
             },
@@ -615,7 +620,7 @@ module.exports = {
                 [].forEach.call(node.properties, e => {
                     const v = e.value;
 
-                    if (v && hasExcessParens(v) && precedence(v) >= precedence({ type: "AssignmentExpression" })) {
+                    if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(v);
                     }
                 });
@@ -671,7 +676,7 @@ module.exports = {
 
             VariableDeclarator(node) {
                 if (node.init && hasExcessParens(node.init) &&
-                        precedence(node.init) >= precedence({ type: "AssignmentExpression" }) &&
+                        precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
 
                         // RegExp literal is allowed to have parens (#1589)
                         !(node.init.type === "Literal" && node.init.regex)) {
diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js
index 548e02f46..b4e1074d5 100644
--- a/lib/rules/no-unused-expressions.js
+++ b/lib/rules/no-unused-expressions.js
@@ -25,6 +25,9 @@ module.exports = {
                     },
                     allowTernary: {
                         type: "boolean"
+                    },
+                    allowTaggedTemplates: {
+                        type: "boolean"
                     }
                 },
                 additionalProperties: false
@@ -35,7 +38,8 @@ module.exports = {
     create(context) {
         const config = context.options[0] || {},
             allowShortCircuit = config.allowShortCircuit || false,
-            allowTernary = config.allowTernary || false;
+            allowTernary = config.allowTernary || false,
+            allowTaggedTemplates = config.allowTaggedTemplates || false;
 
         /**
          * @param {ASTNode} node - any node
@@ -95,12 +99,17 @@ module.exports = {
                     return isValidExpression(node.consequent) && isValidExpression(node.alternate);
                 }
             }
+
             if (allowShortCircuit) {
                 if (node.type === "LogicalExpression") {
                     return isValidExpression(node.right);
                 }
             }
 
+            if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
+                return true;
+            }
+
             return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
                 (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
         }


Test patch:
diff --git a/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults b/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
index 07f3a42ba..ca93372b2 100644
--- a/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
+++ b/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
@@ -1,2 +1,3 @@
 !/node_modules/package
 !/bower_components/package
+!.hidden/package
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index 5b6521909..c6ecc09ad 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -123,17 +123,15 @@ function createStubModuleResolver(mapping) {
      * @constructor
      * @private
      */
-    function StubModuleResolver() {}
+    return class StubModuleResolver {
+        resolve(name) { // eslint-disable-line class-methods-use-this
+            if (mapping.hasOwnProperty(name)) {
+                return mapping[name];
+            }
 
-    StubModuleResolver.prototype.resolve = function(name) {
-        if (mapping.hasOwnProperty(name)) {
-            return mapping[name];
+            throw new Error(`Cannot find module '${name}'`);
         }
-
-        throw new Error(`Cannot find module '${name}'`);
     };
-
-    return StubModuleResolver;
 }
 
 //------------------------------------------------------------------------------
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 381589883..643a11ada 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -550,9 +550,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
         });
 
-        it("should ignore default folders there is an ignore file without unignored defaults", () => {
+        it("should ignore default folders when there is an ignore file without unignored defaults", () => {
             const cwd = getFixturePath();
             const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath(".eslintignore"), cwd });
 
@@ -564,6 +565,7 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
         });
 
         it("should not ignore things which are re-included in ignore file", () => {
@@ -578,8 +580,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isFalse(shouldIgnore(resolve("node_modules/package")));
             assert.isFalse(shouldIgnore(resolve("bower_components/package")));
+            assert.isFalse(shouldIgnore(resolve(".hidden/package")));
         });
 
         it("should ignore files which we try to re-include in ignore file when ignore option is disabled", () => {
@@ -594,8 +598,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isTrue(shouldIgnore(resolve("node_modules/package")));
             assert.isTrue(shouldIgnore(resolve("bower_components/package")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/package")));
         });
 
         it("should not ignore dirs which are re-included by ignorePattern", () => {
@@ -610,9 +616,21 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isFalse(shouldIgnore(resolve("node_modules/package")));
             assert.isTrue(shouldIgnore(resolve("bower_components/package")));
         });
+
+        it("should not ignore hidden dirs when dotfiles is enabled", () => {
+            const cwd = getFixturePath("no-ignore-file");
+            const ignoredPaths = new IgnoredPaths({ ignore: true, cwd, dotfiles: true });
+
+            const shouldIgnore = ignoredPaths.getIgnoredFoldersGlobChecker();
+            const resolve = createResolve(cwd);
+
+            assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isFalse(shouldIgnore(resolve(".hidden/a")));
+        });
     });
 
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index b1e945e5e..349c14994 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -380,6 +380,21 @@ ruleTester.run("no-extra-parens", rule, {
                 ecmaFeatures: { experimentalObjectRestSpread: true }
             }
         },
+        {
+            code: "let a = [ ...(b, c) ]",
+            parserOptions: { ecmaVersion: 2015 }
+        },
+        {
+            code: "let a = { ...(b, c) }",
+            parserOptions: {
+                ecmaVersion: 2015,
+                ecmaFeatures: { experimentalObjectRestSpread: true }
+            }
+        },
+        {
+            code: "var [x = (1, foo)] = bar",
+            parserOptions: { ecmaVersion: 2015 }
+        },
         {
             code: "class A extends B {}",
             parserOptions: { ecmaVersion: 2015 }
@@ -891,6 +906,25 @@ ruleTester.run("no-extra-parens", rule, {
                 }
             }
         ),
+        invalid(
+            "let a = [...((b, c))]",
+            "let a = [...(b, c)]",
+            "SequenceExpression",
+            1,
+            { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "let a = {...((b, c))}",
+            "let a = {...(b, c)}",
+            "SequenceExpression",
+            1,
+            {
+                parserOptions: {
+                    ecmaVersion: 2015,
+                    ecmaFeatures: { experimentalObjectRestSpread: true }
+                }
+            }
+        ),
         invalid(
             "class A extends (B) {}",
             "class A extends B {}",
diff --git a/tests/lib/rules/no-unused-expressions.js b/tests/lib/rules/no-unused-expressions.js
index 57eb39ed5..520705179 100644
--- a/tests/lib/rules/no-unused-expressions.js
+++ b/tests/lib/rules/no-unused-expressions.js
@@ -60,6 +60,16 @@ ruleTester.run("no-unused-expressions", rule, {
             code: "async function foo() { foo ? await bar : await baz; }",
             options: [{ allowTernary: true }],
             parserOptions: { ecmaVersion: 8 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "shouldNotBeAffectedByAllowTemplateTagsOption()",
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
     invalid: [
@@ -72,6 +82,16 @@ ruleTester.run("no-unused-expressions", rule, {
         { code: "a() || false", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a || (b = c)", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a ? b() || (c = d) : e", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            parserOptions: { ecmaVersion: 6 }
+        },
         { code: "a && b()", options: [{ allowTernary: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a ? b() : c()", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a || b", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
@@ -85,6 +105,24 @@ ruleTester.run("no-unused-expressions", rule, {
         { code: "function foo() {\"directive one\"; f(); \"directive two\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "if (0) { \"not a directive\"; f(); }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "function foo() { var foo = true; \"use strict\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
-        { code: "var foo = () => { var foo = true; \"use strict\"; }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }
+        { code: "var foo = () => { var foo = true; \"use strict\"; }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: false }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: false }],
+            parserOptions: { ecmaVersion: 6 }
+        }
     ]
 });