-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use this rule to prevent importing packages through relative paths. It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling package using `../package` relative path, while direct `package` is the correct one. Co-authored-by: Rafal Lindemann <rl@stamina.pl> Co-authored-by: Tom Payne <tom@tompayne.dev> Co-authored-by: Jordan Harband <ljharb@gmail.com>
- Loading branch information
1 parent
319d0ca
commit 6f5c52c
Showing
11 changed files
with
246 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# import/no-relative-packages | ||
|
||
Use this rule to prevent importing packages through relative paths. | ||
|
||
It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling | ||
package using `../package` relative path, while direct `package` is the correct one. | ||
|
||
|
||
### Examples | ||
|
||
Given the following folder structure: | ||
|
||
``` | ||
my-project | ||
├── packages | ||
│ ├── foo | ||
│ │ ├── index.js | ||
│ │ └── package.json | ||
│ └── bar | ||
│ ├── index.js | ||
│ └── package.json | ||
└── entry.js | ||
``` | ||
|
||
And the .eslintrc file: | ||
``` | ||
{ | ||
... | ||
"rules": { | ||
"import/no-relative-packages": "error" | ||
} | ||
} | ||
``` | ||
|
||
The following patterns are considered problems: | ||
|
||
```js | ||
/** | ||
* in my-project/packages/foo.js | ||
*/ | ||
|
||
import bar from '../bar'; // Import sibling package using relative path | ||
import entry from '../../entry.js'; // Import from parent package using relative path | ||
|
||
/** | ||
* in my-project/entry.js | ||
*/ | ||
|
||
import bar from './packages/bar'; // Import child package using relative path | ||
``` | ||
|
||
The following patterns are NOT considered problems: | ||
|
||
```js | ||
/** | ||
* in my-project/packages/foo.js | ||
*/ | ||
|
||
import bar from 'bar'; // Import sibling package using package name | ||
|
||
/** | ||
* in my-project/entry.js | ||
*/ | ||
|
||
import bar from 'bar'; // Import sibling package using package name | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import path from 'path'; | ||
import readPkgUp from 'read-pkg-up'; | ||
|
||
import resolve from 'eslint-module-utils/resolve'; | ||
import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; | ||
import importType from '../core/importType'; | ||
import docsUrl from '../docsUrl'; | ||
|
||
function findNamedPackage(filePath) { | ||
const found = readPkgUp.sync({ cwd: filePath, normalize: false }); | ||
if (found.pkg && !found.pkg.name) { | ||
return findNamedPackage(path.join(found.path, '../..')); | ||
} | ||
return found; | ||
} | ||
|
||
function checkImportForRelativePackage(context, importPath, node) { | ||
const potentialViolationTypes = ['parent', 'index', 'sibling']; | ||
if (potentialViolationTypes.indexOf(importType(importPath, context)) === -1) { | ||
return; | ||
} | ||
|
||
const resolvedImport = resolve(importPath, context); | ||
const resolvedContext = context.getFilename(); | ||
|
||
if (!resolvedImport || !resolvedContext) { | ||
return; | ||
} | ||
|
||
const importPkg = findNamedPackage(resolvedImport); | ||
const contextPkg = findNamedPackage(resolvedContext); | ||
|
||
if (importPkg.pkg && contextPkg.pkg && importPkg.pkg.name !== contextPkg.pkg.name) { | ||
const importBaseName = path.basename(importPath); | ||
const importRoot = path.dirname(importPkg.path); | ||
const properPath = path.relative(importRoot, resolvedImport); | ||
const properImport = path.join( | ||
importPkg.pkg.name, | ||
path.dirname(properPath), | ||
importBaseName === path.basename(importRoot) ? '' : importBaseName | ||
); | ||
context.report({ | ||
node, | ||
message: `Relative import from another package is not allowed. Use \`${properImport}\` instead of \`${importPath}\``, | ||
}); | ||
} | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
url: docsUrl('no-relative-packages'), | ||
}, | ||
schema: [makeOptionsSchema()], | ||
}, | ||
|
||
create(context) { | ||
return moduleVisitor((source) => checkImportForRelativePackage(context, source.value, source), context.options[0]); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default function () {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "package-named", | ||
"description": "Standard, named package", | ||
"main": "index.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default function () {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "@scope/package-named", | ||
"description": "Scoped, named package", | ||
"main": "index.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default function () {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"description": "Unnamed package for reaching through main field - rxjs style", | ||
"main": "index.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { RuleTester } from 'eslint'; | ||
import rule from 'rules/no-relative-packages'; | ||
import { normalize } from 'path'; | ||
|
||
import { test, testFilePath } from '../utils'; | ||
|
||
const ruleTester = new RuleTester(); | ||
|
||
ruleTester.run('no-relative-packages', rule, { | ||
valid: [ | ||
test({ | ||
code: 'import foo from "./index.js"', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
test({ | ||
code: 'import bar from "../bar"', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
test({ | ||
code: 'import {foo} from "a"', | ||
filename: testFilePath('./package-named/index.js'), | ||
}), | ||
test({ | ||
code: 'const bar = require("../bar.js")', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
test({ | ||
code: 'const bar = require("../not/a/file/path.js")', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
test({ | ||
code: 'import "package"', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
test({ | ||
code: 'require("../bar.js")', | ||
filename: testFilePath('./package/index.js'), | ||
}), | ||
], | ||
|
||
invalid: [ | ||
test({ | ||
code: 'import foo from "./package-named"', | ||
filename: testFilePath('./bar.js'), | ||
errors: [ { | ||
message: 'Relative import from another package is not allowed. Use `package-named` instead of `./package-named`', | ||
line: 1, | ||
column: 17, | ||
} ], | ||
}), | ||
test({ | ||
code: 'import foo from "../package-named"', | ||
filename: testFilePath('./package/index.js'), | ||
errors: [ { | ||
message: 'Relative import from another package is not allowed. Use `package-named` instead of `../package-named`', | ||
line: 1, | ||
column: 17, | ||
} ], | ||
}), | ||
test({ | ||
code: 'import foo from "../package-scoped"', | ||
filename: testFilePath('./package/index.js'), | ||
errors: [ { | ||
message: `Relative import from another package is not allowed. Use \`${normalize('@scope/package-named')}\` instead of \`../package-scoped\``, | ||
line: 1, | ||
column: 17, | ||
} ], | ||
}), | ||
test({ | ||
code: 'import bar from "../bar"', | ||
filename: testFilePath('./package-named/index.js'), | ||
errors: [ { | ||
message: `Relative import from another package is not allowed. Use \`${normalize('eslint-plugin-import/tests/files/bar')}\` instead of \`../bar\``, | ||
line: 1, | ||
column: 17, | ||
} ], | ||
}), | ||
], | ||
}); |