Skip to content

Commit 6f5c52c

Browse files
panrafaltapayne88ljharb
committed
[New]: add 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. Co-authored-by: Rafal Lindemann <rl@stamina.pl> Co-authored-by: Tom Payne <tom@tompayne.dev> Co-authored-by: Jordan Harband <ljharb@gmail.com>
1 parent 319d0ca commit 6f5c52c

File tree

11 files changed

+246
-16
lines changed

11 files changed

+246
-16
lines changed

CHANGELOG.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
99
### Added
1010
- [`no-commonjs`]: Also detect require calls with expressionless template literals: ``` require(`x`) ``` ([#1958], thanks [@FloEdelmann])
1111
- [`no-internal-modules`]: Add `forbid` option ([#1846], thanks [@guillaumewuip])
12+
- add [`no-relative-packages`] ([#1860], [#966], thanks [@tapayne88] [@panrafal])
1213

1314
### Fixed
1415
- [`export`]/TypeScript: properly detect export specifiers as children of a TS module block ([#1889], thanks [@andreubotella])
@@ -739,6 +740,7 @@ for info on changes for earlier releases.
739740
[`no-named-export`]: ./docs/rules/no-named-export.md
740741
[`no-namespace`]: ./docs/rules/no-namespace.md
741742
[`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md
743+
[`no-relative-packages`]: ./docs/rules/no-relative-packages.md
742744
[`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md
743745
[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md
744746
[`no-self-import`]: ./docs/rules/no-self-import.md
@@ -754,23 +756,19 @@ for info on changes for earlier releases.
754756
[`memo-parser`]: ./memo-parser/README.md
755757

756758
[#1974]: https://github.com/benmosher/eslint-plugin-import/pull/1974
757-
[#1944]: https://github.com/benmosher/eslint-plugin-import/pull/1944
758-
[#1924]: https://github.com/benmosher/eslint-plugin-import/issues/1924
759-
[#1965]: https://github.com/benmosher/eslint-plugin-import/issues/1965
760759
[#1958]: https://github.com/benmosher/eslint-plugin-import/pull/1958
761760
[#1948]: https://github.com/benmosher/eslint-plugin-import/pull/1948
762761
[#1947]: https://github.com/benmosher/eslint-plugin-import/pull/1947
762+
[#1944]: https://github.com/benmosher/eslint-plugin-import/pull/1944
763763
[#1940]: https://github.com/benmosher/eslint-plugin-import/pull/1940
764764
[#1897]: https://github.com/benmosher/eslint-plugin-import/pull/1897
765765
[#1889]: https://github.com/benmosher/eslint-plugin-import/pull/1889
766766
[#1878]: https://github.com/benmosher/eslint-plugin-import/pull/1878
767-
[#1854]: https://github.com/benmosher/eslint-plugin-import/issues/1854
767+
[#1860]: https://github.com/benmosher/eslint-plugin-import/pull/1860
768768
[#1848]: https://github.com/benmosher/eslint-plugin-import/pull/1848
769769
[#1846]: https://github.com/benmosher/eslint-plugin-import/pull/1846
770-
[#1841]: https://github.com/benmosher/eslint-plugin-import/issues/1841
771770
[#1836]: https://github.com/benmosher/eslint-plugin-import/pull/1836
772771
[#1835]: https://github.com/benmosher/eslint-plugin-import/pull/1835
773-
[#1834]: https://github.com/benmosher/eslint-plugin-import/issues/1834
774772
[#1833]: https://github.com/benmosher/eslint-plugin-import/pull/1833
775773
[#1831]: https://github.com/benmosher/eslint-plugin-import/pull/1831
776774
[#1830]: https://github.com/benmosher/eslint-plugin-import/pull/1830
@@ -780,7 +778,6 @@ for info on changes for earlier releases.
780778
[#1820]: https://github.com/benmosher/eslint-plugin-import/pull/1820
781779
[#1819]: https://github.com/benmosher/eslint-plugin-import/pull/1819
782780
[#1802]: https://github.com/benmosher/eslint-plugin-import/pull/1802
783-
[#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801
784781
[#1788]: https://github.com/benmosher/eslint-plugin-import/pull/1788
785782
[#1786]: https://github.com/benmosher/eslint-plugin-import/pull/1786
786783
[#1785]: https://github.com/benmosher/eslint-plugin-import/pull/1785
@@ -794,10 +791,7 @@ for info on changes for earlier releases.
794791
[#1735]: https://github.com/benmosher/eslint-plugin-import/pull/1735
795792
[#1726]: https://github.com/benmosher/eslint-plugin-import/pull/1726
796793
[#1724]: https://github.com/benmosher/eslint-plugin-import/pull/1724
797-
[#1722]: https://github.com/benmosher/eslint-plugin-import/issues/1722
798794
[#1719]: https://github.com/benmosher/eslint-plugin-import/pull/1719
799-
[#1704]: https://github.com/benmosher/eslint-plugin-import/issues/1704
800-
[#1702]: https://github.com/benmosher/eslint-plugin-import/issues/1702
801795
[#1696]: https://github.com/benmosher/eslint-plugin-import/pull/1696
802796
[#1691]: https://github.com/benmosher/eslint-plugin-import/pull/1691
803797
[#1690]: https://github.com/benmosher/eslint-plugin-import/pull/1690
@@ -808,17 +802,12 @@ for info on changes for earlier releases.
808802
[#1664]: https://github.com/benmosher/eslint-plugin-import/pull/1664
809803
[#1658]: https://github.com/benmosher/eslint-plugin-import/pull/1658
810804
[#1651]: https://github.com/benmosher/eslint-plugin-import/pull/1651
811-
[#1635]: https://github.com/benmosher/eslint-plugin-import/issues/1635
812-
[#1631]: https://github.com/benmosher/eslint-plugin-import/issues/1631
813805
[#1626]: https://github.com/benmosher/eslint-plugin-import/pull/1626
814806
[#1620]: https://github.com/benmosher/eslint-plugin-import/pull/1620
815807
[#1619]: https://github.com/benmosher/eslint-plugin-import/pull/1619
816-
[#1616]: https://github.com/benmosher/eslint-plugin-import/issues/1616
817-
[#1613]: https://github.com/benmosher/eslint-plugin-import/issues/1613
818808
[#1612]: https://github.com/benmosher/eslint-plugin-import/pull/1612
819809
[#1611]: https://github.com/benmosher/eslint-plugin-import/pull/1611
820810
[#1605]: https://github.com/benmosher/eslint-plugin-import/pull/1605
821-
[#1589]: https://github.com/benmosher/eslint-plugin-import/issues/1589
822811
[#1586]: https://github.com/benmosher/eslint-plugin-import/pull/1586
823812
[#1572]: https://github.com/benmosher/eslint-plugin-import/pull/1572
824813
[#1569]: https://github.com/benmosher/eslint-plugin-import/pull/1569
@@ -909,6 +898,7 @@ for info on changes for earlier releases.
909898
[#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068
910899
[#1049]: https://github.com/benmosher/eslint-plugin-import/pull/1049
911900
[#1046]: https://github.com/benmosher/eslint-plugin-import/pull/1046
901+
[#966]: https://github.com/benmosher/eslint-plugin-import/pull/966
912902
[#944]: https://github.com/benmosher/eslint-plugin-import/pull/944
913903
[#912]: https://github.com/benmosher/eslint-plugin-import/pull/912
914904
[#908]: https://github.com/benmosher/eslint-plugin-import/pull/908
@@ -985,10 +975,24 @@ for info on changes for earlier releases.
985975
[#211]: https://github.com/benmosher/eslint-plugin-import/pull/211
986976
[#164]: https://github.com/benmosher/eslint-plugin-import/pull/164
987977
[#157]: https://github.com/benmosher/eslint-plugin-import/pull/157
978+
[#1965]: https://github.com/benmosher/eslint-plugin-import/issues/1965
979+
[#1924]: https://github.com/benmosher/eslint-plugin-import/issues/1924
980+
[#1854]: https://github.com/benmosher/eslint-plugin-import/issues/1854
981+
[#1841]: https://github.com/benmosher/eslint-plugin-import/issues/1841
982+
[#1834]: https://github.com/benmosher/eslint-plugin-import/issues/1834
988983
[#1814]: https://github.com/benmosher/eslint-plugin-import/issues/1814
989984
[#1811]: https://github.com/benmosher/eslint-plugin-import/issues/1811
990985
[#1808]: https://github.com/benmosher/eslint-plugin-import/issues/1808
991986
[#1805]: https://github.com/benmosher/eslint-plugin-import/issues/1805
987+
[#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801
988+
[#1722]: https://github.com/benmosher/eslint-plugin-import/issues/1722
989+
[#1704]: https://github.com/benmosher/eslint-plugin-import/issues/1704
990+
[#1702]: https://github.com/benmosher/eslint-plugin-import/issues/1702
991+
[#1635]: https://github.com/benmosher/eslint-plugin-import/issues/1635
992+
[#1631]: https://github.com/benmosher/eslint-plugin-import/issues/1631
993+
[#1616]: https://github.com/benmosher/eslint-plugin-import/issues/1616
994+
[#1613]: https://github.com/benmosher/eslint-plugin-import/issues/1613
995+
[#1589]: https://github.com/benmosher/eslint-plugin-import/issues/1589
992996
[#1565]: https://github.com/benmosher/eslint-plugin-import/issues/1565
993997
[#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366
994998
[#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334
@@ -1324,4 +1328,6 @@ for info on changes for earlier releases.
13241328
[@paztis]: https://github.com/paztis
13251329
[@FloEdelmann]: https://github.com/FloEdelmann
13261330
[@bicstone]: https://github.com/bicstone
1327-
[@guillaumewuip]: https://github.com/guillaumewuip
1331+
[@guillaumewuip]: https://github.com/guillaumewuip
1332+
[@tapayne88]: https://github.com/tapayne88
1333+
[@panrafal]: https://github.com/panrafal

docs/rules/no-relative-packages.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# import/no-relative-packages
2+
3+
Use this rule to prevent importing packages through relative paths.
4+
5+
It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling
6+
package using `../package` relative path, while direct `package` is the correct one.
7+
8+
9+
### Examples
10+
11+
Given the following folder structure:
12+
13+
```
14+
my-project
15+
├── packages
16+
│ ├── foo
17+
│ │ ├── index.js
18+
│ │ └── package.json
19+
│ └── bar
20+
│ ├── index.js
21+
│ └── package.json
22+
└── entry.js
23+
```
24+
25+
And the .eslintrc file:
26+
```
27+
{
28+
...
29+
"rules": {
30+
"import/no-relative-packages": "error"
31+
}
32+
}
33+
```
34+
35+
The following patterns are considered problems:
36+
37+
```js
38+
/**
39+
* in my-project/packages/foo.js
40+
*/
41+
42+
import bar from '../bar'; // Import sibling package using relative path
43+
import entry from '../../entry.js'; // Import from parent package using relative path
44+
45+
/**
46+
* in my-project/entry.js
47+
*/
48+
49+
import bar from './packages/bar'; // Import child package using relative path
50+
```
51+
52+
The following patterns are NOT considered problems:
53+
54+
```js
55+
/**
56+
* in my-project/packages/foo.js
57+
*/
58+
59+
import bar from 'bar'; // Import sibling package using package name
60+
61+
/**
62+
* in my-project/entry.js
63+
*/
64+
65+
import bar from 'bar'; // Import sibling package using package name
66+
```

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const rules = {
1010
'no-restricted-paths': require('./rules/no-restricted-paths'),
1111
'no-internal-modules': require('./rules/no-internal-modules'),
1212
'group-exports': require('./rules/group-exports'),
13+
'no-relative-packages': require('./rules/no-relative-packages'),
1314
'no-relative-parent-imports': require('./rules/no-relative-parent-imports'),
1415

1516
'no-self-import': require('./rules/no-self-import'),

src/rules/no-relative-packages.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import path from 'path';
2+
import readPkgUp from 'read-pkg-up';
3+
4+
import resolve from 'eslint-module-utils/resolve';
5+
import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor';
6+
import importType from '../core/importType';
7+
import docsUrl from '../docsUrl';
8+
9+
function findNamedPackage(filePath) {
10+
const found = readPkgUp.sync({ cwd: filePath, normalize: false });
11+
if (found.pkg && !found.pkg.name) {
12+
return findNamedPackage(path.join(found.path, '../..'));
13+
}
14+
return found;
15+
}
16+
17+
function checkImportForRelativePackage(context, importPath, node) {
18+
const potentialViolationTypes = ['parent', 'index', 'sibling'];
19+
if (potentialViolationTypes.indexOf(importType(importPath, context)) === -1) {
20+
return;
21+
}
22+
23+
const resolvedImport = resolve(importPath, context);
24+
const resolvedContext = context.getFilename();
25+
26+
if (!resolvedImport || !resolvedContext) {
27+
return;
28+
}
29+
30+
const importPkg = findNamedPackage(resolvedImport);
31+
const contextPkg = findNamedPackage(resolvedContext);
32+
33+
if (importPkg.pkg && contextPkg.pkg && importPkg.pkg.name !== contextPkg.pkg.name) {
34+
const importBaseName = path.basename(importPath);
35+
const importRoot = path.dirname(importPkg.path);
36+
const properPath = path.relative(importRoot, resolvedImport);
37+
const properImport = path.join(
38+
importPkg.pkg.name,
39+
path.dirname(properPath),
40+
importBaseName === path.basename(importRoot) ? '' : importBaseName
41+
);
42+
context.report({
43+
node,
44+
message: `Relative import from another package is not allowed. Use \`${properImport}\` instead of \`${importPath}\``,
45+
});
46+
}
47+
}
48+
49+
module.exports = {
50+
meta: {
51+
type: 'suggestion',
52+
docs: {
53+
url: docsUrl('no-relative-packages'),
54+
},
55+
schema: [makeOptionsSchema()],
56+
},
57+
58+
create(context) {
59+
return moduleVisitor((source) => checkImportForRelativePackage(context, source.value, source), context.options[0]);
60+
},
61+
};

tests/files/package-named/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function () {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "package-named",
3+
"description": "Standard, named package",
4+
"main": "index.js"
5+
}

tests/files/package-scoped/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function () {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "@scope/package-named",
3+
"description": "Scoped, named package",
4+
"main": "index.js"
5+
}

tests/files/package/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function () {}

tests/files/package/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Unnamed package for reaching through main field - rxjs style",
3+
"main": "index.js"
4+
}

0 commit comments

Comments
 (0)