Repository: eslint/eslint
Pull request #8100 (2017-02-18 23:06:53)

Update: Improve error reporting when extending missing config (fixes …
…#6115)

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] 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
[X] Add something to the core
[ ] 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 (http://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)**
Improve error message when a base config is not found.

**Is there anything you'd like reviewers to focus on?**
No.



eslintbot: Thanks for the pull request, @alberto! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary must be 72 characters or shorter. Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
mention-bot: @alberto, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas, @mysticatea and @not-an-aardvark to be potential reviewers.
eslintbot: LGTM
eslintbot: LGTM

Patch:
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 9817f8510..864d09d36 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -390,6 +390,13 @@ function applyExtends(config, filePath, relativeTo) {
             debug(`Loading ${parentPath}`);
             return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
         } catch (e) {
+            if (parentPath.indexOf("plugin:") === 0 || parentPath.indexOf("eslint:") === 0) {
+                e.message = `Failed to load config "${parentPath}" to extend from.`;
+                e.messageTemplate = "extend-config-missing";
+                e.messageData = {
+                    configName: parentPath
+                };
+            }
 
             /*
              * If the file referenced by `extends` failed to load, add the path
diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js
index 9c0a7f388..f18381a31 100644
--- a/lib/rules/block-spacing.js
+++ b/lib/rules/block-spacing.js
@@ -74,8 +74,8 @@ module.exports = {
             // Gets braces and the first/last token of content.
             const openBrace = getOpenBrace(node);
             const closeBrace = sourceCode.getLastToken(node);
-            const firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
-            const lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
+            const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
+            const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
 
             // Skip if the node is invalid or empty.
             if (openBrace.type !== "Punctuator" ||
diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js
index b7ed13f3a..df2d794f9 100644
--- a/lib/rules/capitalized-comments.js
+++ b/lib/rules/capitalized-comments.js
@@ -163,8 +163,8 @@ module.exports = {
          * otherwise.
          */
         function isInlineComment(comment) {
-            const previousToken = sourceCode.getTokenOrCommentBefore(comment),
-                nextToken = sourceCode.getTokenOrCommentAfter(comment);
+            const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
+                nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
 
             return Boolean(
                 previousToken &&
@@ -181,7 +181,7 @@ module.exports = {
          * @returns {boolean} True if the comment follows a valid comment.
          */
         function isConsecutiveComment(comment) {
-            const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment);
+            const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
 
             return Boolean(
                 previousTokenOrComment &&
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index 1416e46c2..ce150753b 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -413,8 +413,8 @@ module.exports = {
         function report(property, side, whitespace, expected, mode) {
             const diff = whitespace.length - expected,
                 nextColon = getNextColon(property.key),
-                tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
-                tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
+                tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
+                tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
                 isKeySide = side === "key",
                 locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
                 isExtra = diff > 0,
diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js
index fa2384e88..d630e2447 100644
--- a/lib/rules/line-comment-position.js
+++ b/lib/rules/line-comment-position.js
@@ -77,7 +77,7 @@ module.exports = {
                     return;
                 }
 
-                const previous = sourceCode.getTokenOrCommentBefore(node);
+                const previous = sourceCode.getTokenBefore(node, { includeComments: true });
                 const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
 
                 if (above) {
diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js
index 83751cd6a..89fbc03bf 100644
--- a/lib/rules/lines-around-comment.js
+++ b/lib/rules/lines-around-comment.js
@@ -139,7 +139,7 @@ module.exports = {
 
             token = node;
             do {
-                token = sourceCode.getTokenOrCommentBefore(token);
+                token = sourceCode.getTokenBefore(token, { includeComments: true });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(token, node)) {
@@ -148,7 +148,7 @@ module.exports = {
 
             token = node;
             do {
-                token = sourceCode.getTokenOrCommentAfter(token);
+                token = sourceCode.getTokenAfter(token, { includeComments: true });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(node, token)) {
@@ -300,8 +300,8 @@ module.exports = {
                 return;
             }
 
-            const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
-            const nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
+            const previousTokenOrComment = sourceCode.getTokenBefore(node, { includeComments: true });
+            const nextTokenOrComment = sourceCode.getTokenAfter(node, { includeComments: true });
 
             // check for newline before
             if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
diff --git a/lib/rules/lines-around-directive.js b/lib/rules/lines-around-directive.js
index d5d29435b..7a4b21ea1 100644
--- a/lib/rules/lines-around-directive.js
+++ b/lib/rules/lines-around-directive.js
@@ -57,7 +57,7 @@ module.exports = {
          * @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
          */
         function hasNewlineBefore(node) {
-            const tokenBefore = sourceCode.getTokenOrCommentBefore(node);
+            const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
             const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
 
             return node.loc.start.line - tokenLineBefore >= 2;
@@ -86,7 +86,7 @@ module.exports = {
          */
         function hasNewlineAfter(node) {
             const lastToken = getLastTokenOnLine(node);
-            const tokenAfter = sourceCode.getTokenOrCommentAfter(lastToken);
+            const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true });
 
             return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
         }
@@ -131,7 +131,7 @@ module.exports = {
             }
 
             const firstDirective = directives[0];
-            const hasTokenOrCommentBefore = !!sourceCode.getTokenOrCommentBefore(firstDirective);
+            const hasTokenOrCommentBefore = !!sourceCode.getTokenBefore(firstDirective, { includeComments: true });
 
             // Only check before the first directive if it is preceded by a comment or if it is at the top of
             // the file and expectLineBefore is set to "never". This is to not force a newline at the top of
diff --git a/lib/rules/max-lines.js b/lib/rules/max-lines.js
index 52f19a98a..297c75dc1 100644
--- a/lib/rules/max-lines.js
+++ b/lib/rules/max-lines.js
@@ -90,7 +90,7 @@ module.exports = {
 
             token = comment;
             do {
-                token = sourceCode.getTokenOrCommentBefore(token);
+                token = sourceCode.getTokenBefore(token, { includeComments: true });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(token, comment)) {
@@ -99,7 +99,7 @@ module.exports = {
 
             token = comment;
             do {
-                token = sourceCode.getTokenOrCommentAfter(token);
+                token = sourceCode.getTokenAfter(token, { includeComments: true });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(comment, token)) {
diff --git a/lib/rules/no-unused-labels.js b/lib/rules/no-unused-labels.js
index e230bc022..12f60ca18 100644
--- a/lib/rules/no-unused-labels.js
+++ b/lib/rules/no-unused-labels.js
@@ -59,7 +59,7 @@ module.exports = {
                          * Only perform a fix if there are no comments between the label and the body. This will be the case
                          * when there is exactly one token/comment (the ":") between the label and the body.
                          */
-                        if (sourceCode.getTokenOrCommentAfter(node.label) === sourceCode.getTokenOrCommentBefore(node.body)) {
+                        if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === sourceCode.getTokenBefore(node.body, { includeComments: true })) {
                             return fixer.removeRange([node.range[0], node.body.range[0]]);
                         }
 
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index 88fc79463..a4451154d 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -128,8 +128,8 @@ module.exports = {
             const options = normalizedOptions[node.type];
             const openBrace = sourceCode.getFirstToken(node);
             const closeBrace = sourceCode.getLastToken(node);
-            let first = sourceCode.getTokenOrCommentAfter(openBrace);
-            let last = sourceCode.getTokenOrCommentBefore(closeBrace);
+            let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
+            let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
             const needsLinebreaks = (
                 node.properties.length >= options.minProperties ||
                 (
diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js
index 7edaf0cc4..8253094c5 100644
--- a/lib/rules/operator-linebreak.js
+++ b/lib/rules/operator-linebreak.js
@@ -87,7 +87,7 @@ module.exports = {
                 if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
 
                     // If there is a comment before and after the operator, don't do a fix.
-                    if (sourceCode.getTokenOrCommentBefore(operatorToken) !== tokenBefore && sourceCode.getTokenOrCommentAfter(operatorToken) !== tokenAfter) {
+                    if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
                         return null;
                     }
 
diff --git a/lib/rules/padded-blocks.js b/lib/rules/padded-blocks.js
index 2b4da39b3..094ba0a71 100644
--- a/lib/rules/padded-blocks.js
+++ b/lib/rules/padded-blocks.js
@@ -101,7 +101,7 @@ module.exports = {
             let first = token;
 
             do {
-                first = sourceCode.getTokenOrCommentAfter(first);
+                first = sourceCode.getTokenAfter(first, { includeComments: true });
             } while (isComment(first) && first.loc.start.line === tokenStartLine);
 
             const firstLine = first.loc.start.line;
@@ -120,7 +120,7 @@ module.exports = {
             let last = token;
 
             do {
-                last = sourceCode.getTokenOrCommentBefore(last);
+                last = sourceCode.getTokenBefore(last, { includeComments: true });
             } while (isComment(last) && last.loc.end.line === blockEnd);
 
             const lastLine = last.loc.end.line;
@@ -182,7 +182,7 @@ module.exports = {
                 }
             } else {
                 if (blockHasTopPadding) {
-                    const nextToken = sourceCode.getTokenOrCommentAfter(openBrace);
+                    const nextToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
 
                     context.report({
                         node,
@@ -195,7 +195,7 @@ module.exports = {
                 }
 
                 if (blockHasBottomPadding) {
-                    const previousToken = sourceCode.getTokenOrCommentBefore(closeBrace);
+                    const previousToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
 
                     context.report({
                         node,
diff --git a/messages/extend-config-missing.txt b/messages/extend-config-missing.txt
new file mode 100644
index 000000000..38e645819
--- /dev/null
+++ b/messages/extend-config-missing.txt
@@ -0,0 +1,3 @@
+ESLint couldn't find the config "<%- configName %>" to extend from. Please check that the name of the config is correct.
+
+If you still have problems, please stop by https://gitter.im/eslint/eslint to chat with the team.


Test patch:
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index ee99669be..ed5842779 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -199,7 +199,7 @@ describe("ConfigFile", () => {
 
         });
 
-        it("should throw an error when extends config is not found", () => {
+        it("should throw an error when extends config module is not found", () => {
 
             const configDeps = {
                 "../util/module-resolver": createStubModuleResolver({})
@@ -216,6 +216,40 @@ describe("ConfigFile", () => {
 
         });
 
+        it("should throw an error when an eslint config is not found", () => {
+
+            const configDeps = {
+                "../util/module-resolver": createStubModuleResolver({})
+            };
+
+            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
+
+            assert.throws(() => {
+                StubbedConfigFile.applyExtends({
+                    extends: "eslint:foo",
+                    rules: { eqeqeq: 2 }
+                }, "/whatever");
+            }, /Failed to load config "eslint:foo" to extend from./);
+
+        });
+
+        it("should throw an error when a plugin config is not found", () => {
+
+            const configDeps = {
+                "../util/module-resolver": createStubModuleResolver({})
+            };
+
+            const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
+
+            assert.throws(() => {
+                StubbedConfigFile.applyExtends({
+                    extends: "plugin:foo",
+                    rules: { eqeqeq: 2 }
+                }, "/whatever");
+            }, /Failed to load config "plugin:foo" to extend from./);
+
+        });
+
         it("should apply extensions recursively when specified from package", () => {
 
             const resolvedPaths = [