Skip to content

Commit 9a811b4

Browse files
committed
feat: add no-parent-barrel-import rule
1 parent 328064a commit 9a811b4

File tree

10 files changed

+190
-0
lines changed

10 files changed

+190
-0
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- Add [`no-parent-barrel-import`] rule: forbids a module from importing from parent barrel file
12+
913
### Fixed
1014
- [`no-duplicates`]: remove duplicate identifiers in duplicate imports ([#2577], thanks [@joe-matsec])
1115
- [`consistent-type-specifier-style`]: fix accidental removal of comma in certain cases ([#2754], thanks [@bradzacher])
@@ -1057,6 +1061,7 @@ for info on changes for earlier releases.
10571061
[`no-relative-packages`]: ./docs/rules/no-relative-packages.md
10581062
[`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md
10591063
[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md
1064+
[`no-parent-barrel-import`]: ./docs/rules/no-parent-barrel-import.md
10601065
[`no-self-import`]: ./docs/rules/no-self-import.md
10611066
[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md
10621067
[`no-unresolved`]: ./docs/rules/no-unresolved.md

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
6060
| [no-cycle](docs/rules/no-cycle.md) | Forbid a module from importing a module with a dependency path back to itself. | | | | | | |
6161
| [no-dynamic-require](docs/rules/no-dynamic-require.md) | Forbid `require()` calls with expressions. | | | | | | |
6262
| [no-internal-modules](docs/rules/no-internal-modules.md) | Forbid importing the submodules of other modules. | | | | | | |
63+
| [no-parent-barrel-import](docs/rules/no-parent-barrel-import.md) | Forbid a module from importing from parent barrel file. | | | | | | |
6364
| [no-relative-packages](docs/rules/no-relative-packages.md) | Forbid importing packages through relative paths. | | | | 🔧 | | |
6465
| [no-relative-parent-imports](docs/rules/no-relative-parent-imports.md) | Forbid importing modules from parent directories. | | | | | | |
6566
| [no-restricted-paths](docs/rules/no-restricted-paths.md) | Enforce which files can be imported in a given folder. | | | | | | |

docs/rules/no-parent-barrel-import.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# import/no-parent-barrel-import
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Forbid a module from importing from parent barrel file, as it often leads to runtime error `Cannot read ... of undefined`.
6+
7+
It resolves the missing circular import check from [`no-self-import`], while being computationally cheap (see [`no-cycle`]).
8+
9+
## Rule Details
10+
11+
### Fail
12+
13+
```js
14+
// @foo/index.ts
15+
export * from "./bar";
16+
17+
// @foo/bar/index.ts
18+
export * from "./baz";
19+
export * from "./qux";
20+
21+
// @foo/bar/baz.ts (cannot read property `X` of undefined)
22+
import { T } from '../..'; // relative
23+
import { T } from '..'; // relative
24+
import { T } from '@foo'; // absolute
25+
import { T } from '@foo/bar'; // absolute
26+
27+
export const X = T.X;
28+
29+
// @foo/bar/qux.ts
30+
export enum T {
31+
X = "..."
32+
}
33+
```
34+
35+
### Pass
36+
37+
```js
38+
// @foo/index.ts
39+
export * from "./bar";
40+
41+
// @foo/bar/index.ts
42+
export * from "./baz";
43+
export * from "./qux";
44+
45+
// @foo/bar/baz.ts (relative import for code in `@foo`)
46+
import { T } from "./baz"; // relative
47+
import { T } from "@foo/bar/qux"; // absolute
48+
49+
export const X = T.X;
50+
51+
// @foo/bar/qux.ts
52+
export enum T {
53+
X = "..."
54+
}
55+
```
56+
57+
## Further Reading
58+
59+
- [Related Discussion](https://github.com/import-js/eslint-plugin-import/pull/2318#issuecomment-1027807460)
60+
- Rule to detect that module imports itself: [`no-self-import`], [`no-cycle`]
61+
62+
[`no-self-import`]: ./no-self-import.md
63+
[`no-cycle`]: ./no-cycle.md

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const rules = {
1515
'consistent-type-specifier-style': require('./rules/consistent-type-specifier-style'),
1616

1717
'no-self-import': require('./rules/no-self-import'),
18+
'no-parent-barrel-import': require('./rules/no-parent-barrel-import'),
1819
'no-cycle': require('./rules/no-cycle'),
1920
'no-named-default': require('./rules/no-named-default'),
2021
'no-named-as-default': require('./rules/no-named-as-default'),

src/rules/no-parent-barrel-import.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @fileOverview Forbids a module from importing from parent barrel file
3+
* @author jonioni
4+
*/
5+
6+
const { parse } = require('path');
7+
const resolve = require('eslint-module-utils/resolve').default;
8+
const moduleVisitor = require('eslint-module-utils/moduleVisitor').default;
9+
const docsUrl = require('../docsUrl').default;
10+
11+
function isImportingFromParentBarrel(context, node, requireName) {
12+
let filePath;
13+
if (context.getPhysicalFilename) {
14+
filePath = context.getPhysicalFilename();
15+
} else if (context.getFilename) {
16+
filePath = context.getFilename();
17+
}
18+
const importPath = resolve(requireName, context);
19+
console.info(`File Path: ${filePath} and ${importPath}`);
20+
const importDetails = parse(importPath);
21+
if (importDetails.name === 'index' && filePath.startsWith(importDetails.dir)) {
22+
context.report({
23+
node,
24+
message: 'Module imports from parent barrel file.',
25+
});
26+
}
27+
}
28+
29+
module.exports = {
30+
meta: {
31+
type: 'problem',
32+
docs: {
33+
category: 'Static analysis',
34+
description: 'Forbid a module from importing from parent barrel file.',
35+
recommended: true,
36+
url: docsUrl('no-parent-barrel-import'),
37+
},
38+
schema: [],
39+
},
40+
create(context) {
41+
return isImportingFromParentBarrel((source, node) => {
42+
moduleVisitor(context, node, source.value);
43+
});
44+
},
45+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Used in `no-parent-barrel-import` tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Used in `no-parent-barrel-import` tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Used in `no-parent-barrel-import` tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Used in `no-parent-barrel-import` tests
2+
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { test, testFilePath } from '../utils';
2+
3+
import { RuleTester } from 'eslint';
4+
5+
const ruleTester = new RuleTester();
6+
const rule = require('rules/no-parent-barrel-import');
7+
8+
const error = {
9+
message: 'Module imports from parent barrel file.',
10+
};
11+
12+
ruleTester.run('no-parent-barrel-import', rule, {
13+
valid: [
14+
test({
15+
code: 'import _ from "lodash"',
16+
filename: testFilePath('./no-parent-barrel-import/index.js'),
17+
}),
18+
test({
19+
code: 'import baz from "./baz"',
20+
filename: testFilePath('./no-parent-barrel-import/foo/bar.js'),
21+
}),
22+
test({
23+
code: 'import bar from "./bar"',
24+
filename: testFilePath('./no-parent-barrel-import/foo/baz.js'),
25+
}),
26+
],
27+
invalid: [
28+
test({
29+
code: 'import baz from "."',
30+
errors: [error],
31+
filename: testFilePath('./no-parent-barrel-import/foo/bar.js'),
32+
}),
33+
test({
34+
code: 'import baz from "../.."',
35+
errors: [error],
36+
filename: testFilePath('./no-parent-barrel-import/foo/bar.js'),
37+
}),
38+
test({
39+
code: 'var foo = require("./index.js")',
40+
errors: [error],
41+
filename: testFilePath('./no-parent-barrel-import/foo/bar.js'),
42+
}),
43+
test({
44+
code: 'var foo = require(".")',
45+
errors: [error],
46+
filename: testFilePath('./no-parent-barrel-import/foo/baz.js'),
47+
}),
48+
test({
49+
code: 'var foo = require("..")',
50+
errors: [error],
51+
filename: testFilePath('./no-parent-barrel-import/foo/bar.js'),
52+
}),
53+
test({
54+
code: 'var foo = require("././././")',
55+
errors: [error],
56+
filename: testFilePath('./no-parent-barrel-import/foo/index.js'),
57+
}),
58+
test({
59+
code: 'var root = require("../../..")',
60+
errors: [error],
61+
filename: testFilePath('./no-parent-barrel-import/foo/index.js'),
62+
}),
63+
test({
64+
code: 'var root = require("..")',
65+
errors: [error],
66+
filename: testFilePath('./no-parent-barrel-import/index.js'),
67+
}),
68+
69+
],
70+
});

0 commit comments

Comments
 (0)