Repository: eslint/eslint
Pull request #10006 (2018-02-23 19:26:52)

Chore: refactor populating configs with defaults in linter
<!--
    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)**

[x] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    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)**

This refactors some of the config-handling logic in `linter` to be simpler and avoid mutating its arguments.

I'm working on generally cleaning up this code as part of implementing https://github.com/eslint/eslint/issues/9998.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular



Patch:
diff --git a/conf/default-config-options.js b/conf/default-config-options.js
deleted file mode 100644
index 96fe25ce6..000000000
--- a/conf/default-config-options.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @fileoverview Default config options
- * @author Teddy Katz
- */
-
-"use strict";
-
-/**
- * Freezes an object and all its nested properties
- * @param {Object} obj The object to deeply freeze
- * @returns {Object} `obj` after freezing it
- */
-function deepFreeze(obj) {
-    if (obj === null || typeof obj !== "object") {
-        return obj;
-    }
-
-    Object.keys(obj).map(key => obj[key]).forEach(deepFreeze);
-    return Object.freeze(obj);
-}
-
-module.exports = deepFreeze({
-    env: {},
-    globals: {},
-    rules: {},
-    settings: {},
-    parser: "espree",
-    parserOptions: {}
-});
diff --git a/lib/linter.js b/lib/linter.js
index 8e95c3a71..207e2cad0 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -14,7 +14,6 @@ const eslintScope = require("eslint-scope"),
     levn = require("levn"),
     lodash = require("lodash"),
     blankScriptAST = require("../conf/blank-script.json"),
-    defaultConfig = require("../conf/default-config-options.js"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
     ConfigOps = require("./config/config-ops"),
     validator = require("./config/config-validator"),
@@ -33,6 +32,7 @@ const eslintScope = require("eslint-scope"),
 
 const debug = require("debug")("eslint:linter");
 const MAX_AUTOFIX_PASSES = 10;
+const DEFAULT_PARSER_NAME = "espree";
 
 //------------------------------------------------------------------------------
 // Typedefs
@@ -415,16 +415,13 @@ function normalizeEcmaVersion(ecmaVersion, isModule) {
 }
 
 /**
- * Process initial config to make it safe to extend by file comment config
+ * Populates a config file with the default values, and merges any parserOptions stored in an env into
+ * the parserOptions of the populated config.
  * @param  {Object} config Initial config
  * @param  {Environments} envContext Env context
- * @returns {Object}        Processed config
+ * @returns {Object} Processed config
  */
-function prepareConfig(config, envContext) {
-    config.globals = config.globals || {};
-    const copiedRules = {};
-    let parserOptions = {};
-
+function populateConfig(config, envContext) {
     if (typeof config.rules === "object") {
         Object.keys(config.rules).forEach(k => {
             const rule = config.rules[k];
@@ -432,32 +429,30 @@ function prepareConfig(config, envContext) {
             if (rule === null) {
                 throw new Error(`Invalid config for rule '${k}'.`);
             }
-            if (Array.isArray(rule)) {
-                copiedRules[k] = rule.slice();
-            } else {
-                copiedRules[k] = rule;
-            }
         });
     }
 
     // merge in environment parserOptions
-    if (typeof config.env === "object") {
-        Object.keys(config.env).forEach(envName => {
+    const envNamesEnabledInConfig = typeof config.env === "object"
+        ? Object.keys(config.env).filter(envName => config.env[envName])
+        : [];
+
+    const parserOptionsFromEnv = envNamesEnabledInConfig
+        .reduce((parserOptions, envName) => {
             const env = envContext.get(envName);
 
-            if (config.env[envName] && env && env.parserOptions) {
-                parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
-            }
-        });
-    }
+            return env && env.parserOptions
+                ? ConfigOps.merge(parserOptions, env.parserOptions)
+                : parserOptions;
+        }, {});
 
     const preparedConfig = {
-        rules: copiedRules,
-        parser: config.parser || defaultConfig.parser,
-        globals: ConfigOps.merge(defaultConfig.globals, config.globals),
-        env: ConfigOps.merge(defaultConfig.env, config.env || {}),
-        settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}),
-        parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {})
+        rules: config.rules || {},
+        parser: config.parser || DEFAULT_PARSER_NAME,
+        globals: config.globals || {},
+        env: config.env || {},
+        settings: config.settings || {},
+        parserOptions: ConfigOps.merge(parserOptionsFromEnv, config.parserOptions || {})
     };
     const isModule = preparedConfig.parserOptions.sourceType === "module";
 
@@ -810,7 +805,7 @@ module.exports = class Linter {
         }
 
         // process initial config to make it safe to extend
-        config = prepareConfig(config, this.environments);
+        config = populateConfig(config, this.environments);
 
         if (!lastSourceCodes.get(this)) {
 
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
index 35ac803b6..5a6eaa72f 100644
--- a/lib/rules/max-len.js
+++ b/lib/rules/max-len.js
@@ -213,7 +213,8 @@ module.exports = {
          * @returns {ASTNode[]} An array of string nodes.
          */
         function getAllStrings() {
-            return sourceCode.ast.tokens.filter(token => token.type === "String");
+            return sourceCode.ast.tokens.filter(token => (token.type === "String" ||
+                (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute")));
         }
 
         /**


Test patch:
diff --git a/tests/fixtures/config-file/js/.eslintrc.parser3.js b/tests/fixtures/config-file/js/.eslintrc.parser3.js
index fd5f04ff1..9324e0065 100644
--- a/tests/fixtures/config-file/js/.eslintrc.parser3.js
+++ b/tests/fixtures/config-file/js/.eslintrc.parser3.js
@@ -1,7 +1,5 @@
-var defaultOptions = require("../../../../conf/default-config-options");
-
 module.exports = {
-    parser: defaultOptions.parser,
+    parser: "espree",
     rules: {
         semi: [2, "always"]
     }
diff --git a/tests/lib/rules/max-len.js b/tests/lib/rules/max-len.js
index 4180a8879..caa5f3d56 100644
--- a/tests/lib/rules/max-len.js
+++ b/tests/lib/rules/max-len.js
@@ -109,6 +109,11 @@ ruleTester.run("max-len", rule, {
             code: "var str = \"this is a very long string\\\nwith continuation\\\nand with another very very long continuation\\\nand ending\";",
             options: [29, 4, { ignoreStrings: true }]
         },
+        {
+            code: "var foo = <div className=\"this is a very long string\"></div>;",
+            options: [29, 4, { ignoreStrings: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } }
+        },
         {
             code: "var foo = veryLongIdentifier;\nvar bar = `this is a very long string`;",
             options: [29, 4, { ignoreTemplateLiterals: true }],
@@ -553,6 +558,19 @@ ruleTester.run("max-len", rule, {
                 }
             ]
         },
+        {
+            code: "var foo = <div>this is a very very very long string</div>;",
+            options: [29, 4, { ignoreStrings: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } },
+            errors: [
+                {
+                    message: "Line 1 exceeds the maximum line length of 29.",
+                    type: "Program",
+                    line: 1,
+                    column: 1
+                }
+            ]
+        },
 
         // Multi-code-point unicode glyphs
         {