Repository: eslint/eslint (js)

Issue:
camelcase: Option to ignore imports
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for requesting a rule change. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**What rule do you want to change?**
`camelcase`

**Does this change cause the rule to produce more or fewer warnings?**
Fewer

**How will the change be implemented? (New option, new default behavior, etc.)?**

New option: `ignoreImports`: `true`/`false`, `default`: Debatable. I would say `true` by default because if the code is in your control, eslint would then only flag the declaration and not where it's imported.
Could possibly accept a string/regexp array to only ignore certain modules.

Existing option: `allow` (string/regexp array) somehow check these against the module token of an import (ex: `import { SomeType } from "react"` -> "react") rather than a check on the variable itself. For generated codes/types/etc this will not work well.

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```js
import { Some_apolloGenerated_Type } from "./__generated__/graphql-types.ts"; // generated code from apollo codegen
```
This could be aliased but this code is generated so it's basically futile. Plus it would be hard to come up with aliases for types corresponding to graphql queries/results and their inner types.

**What does the rule currently do for this code?**
Flags generated types from apollo code gen as violating the rule. These types are dynamically generated based on graphql queries which is the reason for a mix of underscores and camel casing.

**What will the rule do after it's changed?**
Ignore imports, either selectively or fully.

**Are you willing to submit a pull request to implement this change?**
No

Pull request description:
New: add option for camelcase (fixes #12527)
<!--
    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)**

Changes an existing rule

<!--
    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)**
Added a new option `ignoreImports` to rule `camelcase`. The option is `false` by default.
Also, this PR is with some refactorings.

Fixes #12527 .




Patch:
diff --git a/docs/rules/camelcase.md b/docs/rules/camelcase.md
index 1765f1852..5c4efcfe4 100644
--- a/docs/rules/camelcase.md
+++ b/docs/rules/camelcase.md
@@ -14,6 +14,8 @@ This rule has an object option:
 * `"properties": "never"` does not check property names
 * `"ignoreDestructuring": false` (default) enforces camelcase style for destructured identifiers
 * `"ignoreDestructuring": true` does not check destructured identifiers
+* `"ignoreImports": false` (default) enforces camelcase style for ES2015 imports
+* `"ignoreImports": true` does not check ES2015 imports (but still checks any use of the imports later in the code except function arguments)
 * `allow` (`string[]`) list of properties to accept. Accept regex.
 
 ### properties: "always"
@@ -152,6 +154,36 @@ var { category_id = 1 } = query;
 var { category_id: category_id } = query;
 ```
 
+### ignoreImports: false
+
+Examples of **incorrect** code for this rule with the default `{ "ignoreImports": false }` option:
+
+```js
+/*eslint camelcase: "error"*/
+
+import { snake_cased } from 'mod';
+```
+
+### ignoreImports: true
+
+Examples of **incorrect** code for this rule with the `{ "ignoreImports": true }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreImports: true}]*/
+
+import default_import from 'mod';
+
+import * as namespaced_import from 'mod';
+```
+
+Examples of **correct** code for this rule with the `{ "ignoreImports": true }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreImports: true}]*/
+
+import { snake_cased } from 'mod';
+```
+
 ## allow
 
 Examples of **correct** code for this rule with the `allow` option:
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index b457ea940..5e5caa0cb 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -6,31 +6,30 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 * ESLint uses an AST to evaluate patterns in code.
 * ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime.
 
-## Getting Started Tutorial
-
-<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/hppJw2REb8g?rel=0" frameborder="0" allowfullscreen></iframe>
-*Why ESLint* @0:00, *Installing and using ESLint* @2:20.  <a href="https://www.pluralsight.com/courses/eslint-better-code-quality?utm_source=eslint-dot-org&utm_medium=video&utm_campaign=authordemo" target="_blank">Full ESLint Course at Pluralsight</a>
-
 ## Installation and Usage
 
 Prerequisites: [Node.js](https://nodejs.org/en/) (`^8.10.0`, `^10.13.0`, or `>=11.10.1`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
 
-You can install ESLint using npm:
+You can install ESLint using npm or yarn:
 
 ```
-$ npm install eslint --save-dev
+npm install eslint --save-dev
+
+# or
+
+yarn add eslint --dev
 ```
 
 You should then set up a configuration file:
 
 ```
-$ ./node_modules/.bin/eslint --init
+$ npx eslint --init
 ```
 
 After that, you can run ESLint on any file or directory like this:
 
 ```
-$ ./node_modules/.bin/eslint yourfile.js
+$ npx eslint yourfile.js
 ```
 
 It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case.
@@ -61,7 +60,9 @@ The three error levels allow you fine-grained control over how ESLint applies ru
 Your `.eslintrc` configuration file will also include the line:
 
 ```json
+{
     "extends": "eslint:recommended"
+}
 ```
 
 Because of this line, all of the rules marked "(recommended)" on the [rules page](/docs/rules) will be turned on.  Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config).  ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration.
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index cdc16fab9..a06c7f940 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -28,6 +28,10 @@ module.exports = {
                         type: "boolean",
                         default: false
                     },
+                    ignoreImports: {
+                        type: "boolean",
+                        default: false
+                    },
                     properties: {
                         enum: ["always", "never"]
                     },
@@ -56,6 +60,7 @@ module.exports = {
         const options = context.options[0] || {};
         let properties = options.properties || "";
         const ignoreDestructuring = options.ignoreDestructuring;
+        const ignoreImports = options.ignoreImports;
         const allow = options.allow || [];
 
         if (properties !== "always" && properties !== "never") {
@@ -79,7 +84,7 @@ module.exports = {
         function isUnderscored(name) {
 
             // if there's an underscore, it might be A_CONSTANT, which is okay
-            return name.indexOf("_") > -1 && name !== name.toUpperCase();
+            return name.includes("_") && name !== name.toUpperCase();
         }
 
         /**
@@ -89,9 +94,9 @@ module.exports = {
          * @private
          */
         function isAllowed(name) {
-            return allow.findIndex(
+            return allow.some(
                 entry => name === entry || name.match(new RegExp(entry, "u"))
-            ) !== -1;
+            );
         }
 
         /**
@@ -127,7 +132,7 @@ module.exports = {
          * @private
          */
         function report(node) {
-            if (reported.indexOf(node) < 0) {
+            if (!reported.includes(node)) {
                 reported.push(node);
                 context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
             }
@@ -209,10 +214,18 @@ module.exports = {
                     }
 
                 // Check if it's an import specifier
-                } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
+                } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
+
+                    if (node.parent.type === "ImportSpecifier" && ignoreImports) {
+                        return;
+                    }
 
                     // Report only if the local imported identifier is underscored
-                    if (node.parent.local && node.parent.local.name === node.name && nameIsUnderscored) {
+                    if (
+                        node.parent.local &&
+                        node.parent.local.name === node.name &&
+                        nameIsUnderscored
+                    ) {
                         report(node);
                     }
 
diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js
index 86a56803e..8a6690959 100644
--- a/lib/source-code/source-code.js
+++ b/lib/source-code/source-code.js
@@ -78,6 +78,18 @@ function sortedMerge(tokens, comments) {
     return result;
 }
 
+/**
+ * Determines if two nodes or tokens overlap.
+ * @param {ASTNode|Token} first The first node or token to check.
+ * @param {ASTNode|Token} second The second node or token to check.
+ * @returns {boolean} True if the two nodes or tokens overlap.
+ * @private
+ */
+function nodesOrTokensOverlap(first, second) {
+    return (first.range[0] <= second.range[0] && first.range[1] >= second.range[0]) ||
+        (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]);
+}
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -411,19 +423,38 @@ class SourceCode extends TokenStore {
     }
 
     /**
-     * Determines if two tokens have at least one whitespace character
-     * between them. This completely disregards comments in making the
-     * determination, so comments count as zero-length substrings.
-     * @param {Token} first The token to check after.
-     * @param {Token} second The token to check before.
-     * @returns {boolean} True if there is only space between tokens, false
-     *  if there is anything other than whitespace between tokens.
+     * Determines if two nodes or tokens have at least one whitespace character
+     * between them. Order does not matter. Returns false if the given nodes or
+     * tokens overlap.
+     * @param {ASTNode|Token} first The first node or token to check between.
+     * @param {ASTNode|Token} second The second node or token to check between.
+     * @returns {boolean} True if there is a whitespace character between
+     * any of the tokens found between the two given nodes or tokens.
      * @public
      */
     isSpaceBetweenTokens(first, second) {
-        const text = this.text.slice(first.range[1], second.range[0]);
+        if (nodesOrTokensOverlap(first, second)) {
+            return false;
+        }
+
+        const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0]
+            ? [first, second]
+            : [second, first];
+        const firstToken = this.getLastToken(startingNodeOrToken) || startingNodeOrToken;
+        const finalToken = this.getFirstToken(endingNodeOrToken) || endingNodeOrToken;
+        let currentToken = firstToken;
+
+        while (currentToken !== finalToken) {
+            const nextToken = this.getTokenAfter(currentToken, { includeComments: true });
+
+            if (currentToken.range[1] !== nextToken.range[0]) {
+                return true;
+            }
+
+            currentToken = nextToken;
+        }
 
-        return /\s/u.test(text.replace(/\/\*.*?\*\//gus, ""));
+        return false;
     }
 
     /**
diff --git a/package.json b/package.json
index fdadc5d43..ca8426f8d 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "messages"
   ],
   "repository": "eslint/eslint",
+  "funding": "https://opencollective.com/eslint",
   "homepage": "https://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
@@ -73,7 +74,7 @@
     "minimatch": "^3.0.4",
     "mkdirp": "^0.5.1",
     "natural-compare": "^1.4.0",
-    "optionator": "^0.8.2",
+    "optionator": "^0.8.3",
     "progress": "^2.0.0",
     "regexpp": "^2.0.1",
     "semver": "^6.1.2",


Test patch:
diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js
index 4aa77abed..1181561ef 100644
--- a/tests/lib/rules/camelcase.js
+++ b/tests/lib/rules/camelcase.js
@@ -151,6 +151,16 @@ ruleTester.run("camelcase", rule, {
             code: "import { no_camelcased as camelCased, anoterCamelCased } from \"external-module\";",
             parserOptions: { ecmaVersion: 6, sourceType: "module" }
         },
+        {
+            code: "import { snake_cased } from 'mod'",
+            options: [{ ignoreImports: true }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "import { camelCased } from 'mod'",
+            options: [{ ignoreImports: false }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
         {
             code: "function foo({ no_camelcased: camelCased }) {};",
             parserOptions: { ecmaVersion: 6 }
@@ -532,6 +542,54 @@ ruleTester.run("camelcase", rule, {
                 }
             ]
         },
+        {
+            code: "import snake_cased from 'mod'",
+            options: [{ ignoreImports: true }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "import * as snake_cased from 'mod'",
+            options: [{ ignoreImports: true }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "import snake_cased from 'mod'",
+            options: [{ ignoreImports: false }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "import * as snake_cased from 'mod'",
+            options: [{ ignoreImports: false }],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
         {
             code: "function foo({ no_camelcased }) {};",
             parserOptions: { ecmaVersion: 6 },
diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js
index 9e8da4844..d4d1dd4dd 100644
--- a/tests/lib/source-code/source-code.js
+++ b/tests/lib/source-code/source-code.js
@@ -1790,32 +1790,334 @@ describe("SourceCode", () => {
     });
 
     describe("isSpaceBetweenTokens()", () => {
+        describe("should return true when there is at least one whitespace character between two tokens", () => {
+            leche.withData([
+                ["let foo", true],
+                ["let  foo", true],
+                ["let /**/ foo", true],
+                ["let/**/foo", false],
+                ["let/*\n*/foo", false]
+            ], (code, expected) => {
+                describe("when the first given is located before the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[0],
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]
+                            ),
+                            expected
+                        );
+                    });
+                });
 
-        leche.withData([
-            ["let foo = bar;", true],
-            ["let  foo = bar;", true],
-            ["let /**/ foo = bar;", true],
-            ["let/**/foo = bar;", false],
-            ["let/*\n*/foo = bar", false],
-            ["a+b", false],
-            ["a/**/+b", false],
-            ["a/* */+b", false],
-            ["a/**/ +b", true],
-            ["a/**/ /**/+b", true],
-            ["a/**/\n/**/+b", true],
-            ["a +b", true]
-        ], (code, expected) => {
-
-            it("should return true when there is one space between tokens", () => {
-                const ast = espree.parse(code, DEFAULT_CONFIG),
-                    sourceCode = new SourceCode(code, ast);
+                describe("when the first given is located after the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1],
+                                sourceCode.ast.tokens[0]
+                            ),
+                            expected
+                        );
+                    });
+                });
+            });
 
-                assert.strictEqual(
-                    sourceCode.isSpaceBetweenTokens(
-                        sourceCode.ast.tokens[0], sourceCode.ast.tokens[1]
-                    ),
-                    expected
-                );
+            leche.withData([
+                ["a+b", false],
+                ["a +b", true],
+                ["a/**/+b", false],
+                ["a/* */+b", false],
+                ["a/**/ +b", true],
+                ["a/**/ /**/+b", true],
+                ["a/* */ /* */+b", true],
+                ["a/**/\n/**/+b", true],
+                ["a/* */\n/* */+b", true],
+                ["a/**/+b/**/+c", false],
+                ["a/* */+b/* */+c", false],
+                ["a/**/+b /**/+c", true],
+                ["a/* */+b /* */+c", true],
+                ["a/**/ +b/**/+c", true],
+                ["a/* */ +b/* */+c", true],
+                ["a/**/+b\t/**/+c", true],
+                ["a/* */+b\t/* */+c", true],
+                ["a/**/\t+b/**/+c", true],
+                ["a/* */\t+b/* */+c", true],
+                ["a/**/+b\n/**/+c", true],
+                ["a/* */+b\n/* */+c", true],
+                ["a/**/\n+b/**/+c", true],
+                ["a/* */\n+b/* */+c", true],
+                ["a/* */+' /**/ '/* */+c", false],
+                ["a/* */+ ' /**/ '/* */+c", true],
+                ["a/* */+' /**/ ' /* */+c", true],
+                ["a/* */+ ' /**/ ' /* */+c", true],
+                ["a/* */+` /*\n*/ `/* */+c", false],
+                ["a/* */+ ` /*\n*/ `/* */+c", true],
+                ["a/* */+` /*\n*/ ` /* */+c", true],
+                ["a/* */+ ` /*\n*/ ` /* */+c", true]
+            ], (code, expected) => {
+                describe("when the first given is located before the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[0],
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 2]
+                            ),
+                            expected
+                        );
+                    });
+                });
+
+                describe("when the first given is located after the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 2],
+                                sourceCode.ast.tokens[0]
+                            ),
+                            expected
+                        );
+                    });
+                });
+            });
+        });
+
+        describe("should return true when there is at least one whitespace character between a token and a node", () => {
+            leche.withData([
+                [";let foo = bar", false],
+                [";/**/let foo = bar", false],
+                [";/* */let foo = bar", false],
+                ["; let foo = bar", true],
+                ["; let foo = bar", true],
+                ["; /**/let foo = bar", true],
+                ["; /* */let foo = bar", true],
+                [";/**/ let foo = bar", true],
+                [";/* */ let foo = bar", true],
+                ["; /**/ let foo = bar", true],
+                ["; /* */ let foo = bar", true],
+                [";\tlet foo = bar", true],
+                [";\tlet foo = bar", true],
+                [";\t/**/let foo = bar", true],
+                [";\t/* */let foo = bar", true],
+                [";/**/\tlet foo = bar", true],
+                [";/* */\tlet foo = bar", true],
+                [";\t/**/\tlet foo = bar", true],
+                [";\t/* */\tlet foo = bar", true],
+                [";\nlet foo = bar", true],
+                [";\nlet foo = bar", true],
+                [";\n/**/let foo = bar", true],
+                [";\n/* */let foo = bar", true],
+                [";/**/\nlet foo = bar", true],
+                [";/* */\nlet foo = bar", true],
+                [";\n/**/\nlet foo = bar", true],
+                [";\n/* */\nlet foo = bar", true]
+            ], (code, expected) => {
+                describe("when the first given is located before the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[0],
+                                sourceCode.ast.body[sourceCode.ast.body.length - 1]
+                            ),
+                            expected
+                        );
+                    });
+                });
+
+                describe("when the first given is located after the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.body[sourceCode.ast.body.length - 1],
+                                sourceCode.ast.tokens[0]
+                            ),
+                            expected
+                        );
+                    });
+                });
+            });
+        });
+
+        describe("should return true when there is at least one whitespace character between a node and a token", () => {
+            leche.withData([
+                ["let foo = bar;;", false],
+                ["let foo = bar;;;", false],
+                ["let foo = 1; let bar = 2;;", true],
+                ["let foo = bar;/**/;", false],
+                ["let foo = bar;/* */;", false],
+                ["let foo = bar;;;", false],
+                ["let foo = bar; ;", true],
+                ["let foo = bar; /**/;", true],
+                ["let foo = bar; /* */;", true],
+                ["let foo = bar;/**/ ;", true],
+                ["let foo = bar;/* */ ;", true],
+                ["let foo = bar; /**/ ;", true],
+                ["let foo = bar; /* */ ;", true],
+                ["let foo = bar;\t;", true],
+                ["let foo = bar;\t/**/;", true],
+                ["let foo = bar;\t/* */;", true],
+                ["let foo = bar;/**/\t;", true],
+                ["let foo = bar;/* */\t;", true],
+                ["let foo = bar;\t/**/\t;", true],
+                ["let foo = bar;\t/* */\t;", true],
+                ["let foo = bar;\n;", true],
+                ["let foo = bar;\n/**/;", true],
+                ["let foo = bar;\n/* */;", true],
+                ["let foo = bar;/**/\n;", true],
+                ["let foo = bar;/* */\n;", true],
+                ["let foo = bar;\n/**/\n;", true],
+                ["let foo = bar;\n/* */\n;", true]
+            ], (code, expected) => {
+                describe("when the first given is located before the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.body[0],
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]
+                            ),
+                            expected
+                        );
+                    });
+                });
+
+                describe("when the first given is located after the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1],
+                                sourceCode.ast.body[0]
+                            ),
+                            expected
+                        );
+                    });
+                });
+            });
+        });
+
+        describe("should return true when there is at least one whitespace character between two nodes", () => {
+            leche.withData([
+                ["let foo = bar;let baz = qux;", false],
+                ["let foo = bar;/**/let baz = qux;", false],
+                ["let foo = bar;/* */let baz = qux;", false],
+                ["let foo = bar; let baz = qux;", true],
+                ["let foo = bar; /**/let baz = qux;", true],
+                ["let foo = bar; /* */let baz = qux;", true],
+                ["let foo = bar;/**/ let baz = qux;", true],
+                ["let foo = bar;/* */ let baz = qux;", true],
+                ["let foo = bar; /**/ let baz = qux;", true],
+                ["let foo = bar; /* */ let baz = qux;", true],
+                ["let foo = bar;\tlet baz = qux;", true],
+                ["let foo = bar;\t/**/let baz = qux;", true],
+                ["let foo = bar;\t/* */let baz = qux;", true],
+                ["let foo = bar;/**/\tlet baz = qux;", true],
+                ["let foo = bar;/* */\tlet baz = qux;", true],
+                ["let foo = bar;\t/**/\tlet baz = qux;", true],
+                ["let foo = bar;\t/* */\tlet baz = qux;", true],
+                ["let foo = bar;\nlet baz = qux;", true],
+                ["let foo = bar;\n/**/let baz = qux;", true],
+                ["let foo = bar;\n/* */let baz = qux;", true],
+                ["let foo = bar;/**/\nlet baz = qux;", true],
+                ["let foo = bar;/* */\nlet baz = qux;", true],
+                ["let foo = bar;\n/**/\nlet baz = qux;", true],
+                ["let foo = bar;\n/* */\nlet baz = qux;", true],
+                ["let foo = 1;let foo2 = 2; let foo3 = 3;", true]
+            ], (code, expected) => {
+                describe("when the first given is located before the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.body[0],
+                                sourceCode.ast.body[sourceCode.ast.body.length - 1]
+                            ),
+                            expected
+                        );
+                    });
+                });
+
+                describe("when the first given is located after the second", () => {
+                    it(code, () => {
+                        const ast = espree.parse(code, DEFAULT_CONFIG),
+                            sourceCode = new SourceCode(code, ast);
+
+                        assert.strictEqual(
+                            sourceCode.isSpaceBetweenTokens(
+                                sourceCode.ast.body[sourceCode.ast.body.length - 1],
+                                sourceCode.ast.body[0]
+                            ),
+                            expected
+                        );
+                    });
+                });
+            });
+        });
+
+        describe("should return false either of the arguments' location is inside the other one", () => {
+            leche.withData([
+                ["let foo = bar;", false]
+            ], (code, expected) => {
+                it(code, () => {
+                    const ast = espree.parse(code, DEFAULT_CONFIG),
+                        sourceCode = new SourceCode(code, ast);
+
+                    assert.strictEqual(
+                        sourceCode.isSpaceBetweenTokens(
+                            sourceCode.ast.tokens[0],
+                            sourceCode.ast.body[0]
+                        ),
+                        expected
+                    );
+
+                    assert.strictEqual(
+                        sourceCode.isSpaceBetweenTokens(
+                            sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1],
+                            sourceCode.ast.body[0]
+                        ),
+                        expected
+                    );
+
+                    assert.strictEqual(
+                        sourceCode.isSpaceBetweenTokens(
+                            sourceCode.ast.body[0],
+                            sourceCode.ast.tokens[0]
+                        ),
+                        expected
+                    );
+
+                    assert.strictEqual(
+                        sourceCode.isSpaceBetweenTokens(
+                            sourceCode.ast.body[0],
+                            sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]
+                        ),
+                        expected
+                    );
+                });
             });
         });
     });