From 81bf977ffc47980ed597e8bcba1315ccc5968226 Mon Sep 17 00:00:00 2001
From: Chris Lloyd <chrislloyd@pinterest.com>
Date: Wed, 11 Jul 2018 09:50:27 -0700
Subject: [PATCH] [no-relative-parent-imports] Resolve paths

This changes the rule to resolve paths before emitting an error. While this means the error will trigger less often (before we could report an error even if the file didn't exist on disk yet) I think it's a fine tradeoff so that it can be useful in more situations.
---
 src/rules/no-relative-parent-imports.js       | 20 ++++++++++++---
 tests/src/rules/no-relative-parent-imports.js | 25 +++++++++++++++++++
 2 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js
index 3153eeb78..6b58c97f5 100644
--- a/src/rules/no-relative-parent-imports.js
+++ b/src/rules/no-relative-parent-imports.js
@@ -1,6 +1,7 @@
 import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
 import docsUrl from '../docsUrl'
-import { basename } from 'path'
+import { basename, dirname, relative } from 'path'
+import resolve from 'eslint-module-utils/resolve'
 
 import importType from '../core/importType'
 
@@ -14,11 +15,24 @@ module.exports = {
 
   create: function noRelativePackages(context) {
     const myPath = context.getFilename()
-    if (myPath === '<text>') return {} // can't cycle-check a non-file
+    if (myPath === '<text>') return {} // can't check a non-file
 
     function checkSourceValue(sourceNode) {
       const depPath = sourceNode.value
-      if (importType(depPath, context) === 'parent') {
+
+      if (importType(depPath, context) === 'external') { // ignore packages
+        return
+      }
+
+      const absDepPath = resolve(depPath, context)
+
+      if (!absDepPath) { // unable to resolve path
+        return
+      }
+
+      const relDepPath = relative(dirname(myPath), absDepPath)
+
+      if (importType(relDepPath, context) === 'parent') {
         context.report({
           node: sourceNode,
           message: 'Relative imports from parent directories are not allowed. ' +
diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js
index 6d7a2c2fa..897823090 100644
--- a/tests/src/rules/no-relative-parent-imports.js
+++ b/tests/src/rules/no-relative-parent-imports.js
@@ -38,9 +38,18 @@ ruleTester.run('no-relative-parent-imports', rule, {
     test({
       code: 'import("./app/index.js")',
     }),
+    test({
+      code: 'import(".")',
+    }),
+    test({
+      code: 'import("path")',
+    }),
     test({
       code: 'import("package")',
     }),
+    test({
+      code: 'import("@scope/package")',
+    }),
   ],
 
   invalid: [
@@ -69,5 +78,21 @@ ruleTester.run('no-relative-parent-imports', rule, {
         column: 8,
       } ],
     }),
+    test({
+      code: 'import foo from "./../plugin.js"',
+      errors: [ {
+        message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `./../plugin.js` or consider making `./../plugin.js` a package.',
+        line: 1,
+        column: 17
+      }]
+    }),
+    test({
+      code: 'import foo from "../../api/service"',
+      errors: [ {
+        message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.',
+        line: 1,
+        column: 17
+      }]
+    })
   ],
 })