eslint-plugin-esm 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +60 -0
- package/dist/common.d.ts +24 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +65 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -2
- package/dist/rules/nearest-relative-path.d.ts +5 -0
- package/dist/rules/nearest-relative-path.d.ts.map +1 -0
- package/dist/rules/nearest-relative-path.js +23 -0
- package/dist/rules/no-directory-imports.d.ts +5 -0
- package/dist/rules/no-directory-imports.d.ts.map +1 -0
- package/dist/rules/no-directory-imports.js +31 -0
- package/dist/rules/no-dynamic-imports.d.ts +5 -0
- package/dist/rules/no-dynamic-imports.d.ts.map +1 -0
- package/dist/rules/no-dynamic-imports.js +14 -0
- package/dist/rules/no-git-ignored-imports.d.ts +5 -0
- package/dist/rules/no-git-ignored-imports.d.ts.map +1 -0
- package/dist/rules/no-git-ignored-imports.js +46 -0
- package/dist/rules/no-phantom-dep-imports.d.ts +5 -0
- package/dist/rules/no-phantom-dep-imports.d.ts.map +1 -0
- package/dist/rules/no-phantom-dep-imports.js +73 -0
- package/dist/rules/no-relative-parent-imports.d.ts +5 -0
- package/dist/rules/no-relative-parent-imports.d.ts.map +1 -0
- package/dist/rules/no-relative-parent-imports.js +11 -0
- package/dist/rules/no-rename-exports.d.ts +5 -0
- package/dist/rules/no-rename-exports.d.ts.map +1 -0
- package/dist/rules/no-rename-exports.js +13 -0
- package/dist/rules/no-rename-imports.d.ts +5 -0
- package/dist/rules/no-rename-imports.d.ts.map +1 -0
- package/dist/rules/no-rename-imports.js +13 -0
- package/dist/rules/no-side-effect-imports.d.ts +5 -0
- package/dist/rules/no-side-effect-imports.d.ts.map +1 -0
- package/dist/rules/no-side-effect-imports.js +33 -0
- package/dist/rules/no-ts-file-imports.d.ts +5 -0
- package/dist/rules/no-ts-file-imports.d.ts.map +1 -0
- package/dist/rules/no-ts-file-imports.js +18 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +16 -0
- package/doc/rules/nearest-relative-path.md +47 -0
- package/doc/rules/no-directory-imports.md +30 -0
- package/doc/rules/no-dynamic-imports.md +30 -0
- package/doc/rules/no-git-ignored-imports.md +36 -0
- package/doc/rules/no-phantom-dep-imports.md +26 -0
- package/doc/rules/no-relative-parent-imports.md +33 -0
- package/doc/rules/no-rename-exports.md +29 -0
- package/doc/rules/no-rename-imports.md +28 -0
- package/doc/rules/no-side-effect-imports.md +31 -0
- package/doc/rules/no-ts-file-imports.md +119 -0
- package/package.json +6 -4
- package/src/common.ts +96 -0
- package/src/index.ts +23 -0
- package/src/rules/nearest-relative-path.spec.ts +43 -0
- package/src/rules/nearest-relative-path.ts +24 -0
- package/src/rules/no-directory-imports.spec.ts +31 -0
- package/src/rules/no-directory-imports.ts +34 -0
- package/src/rules/no-dynamic-imports.spec.ts +27 -0
- package/src/rules/no-dynamic-imports.ts +15 -0
- package/src/rules/no-git-ignored-imports.spec.ts +40 -0
- package/src/rules/no-git-ignored-imports.ts +53 -0
- package/src/rules/no-phantom-dep-imports.spec.ts +28 -0
- package/src/rules/no-phantom-dep-imports.ts +90 -0
- package/src/rules/no-relative-parent-imports.spec.ts +28 -0
- package/src/rules/no-relative-parent-imports.ts +13 -0
- package/src/rules/no-rename-exports.spec.ts +22 -0
- package/src/rules/no-rename-exports.ts +13 -0
- package/src/rules/no-rename-imports.spec.ts +22 -0
- package/src/rules/no-rename-imports.ts +13 -0
- package/src/rules/no-side-effect-imports.spec.ts +25 -0
- package/src/rules/no-side-effect-imports.ts +44 -0
- package/src/rules/no-ts-file-imports.spec.ts +39 -0
- package/src/rules/no-ts-file-imports.ts +23 -0
- package/src/test.spec.ts +110 -0
- package/src/utils.ts +15 -0
package/src/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { nearestRelativePath } from "./rules/nearest-relative-path.js";
|
|
2
|
+
import { noDirectoryImports } from "./rules/no-directory-imports.js";
|
|
3
|
+
import { noDynamicImports } from "./rules/no-dynamic-imports.js";
|
|
4
|
+
import { noGitIgnoredImports } from "./rules/no-git-ignored-imports.js";
|
|
5
|
+
import { noPhantomDepImports } from "./rules/no-phantom-dep-imports.js";
|
|
6
|
+
import { noRelativeParentImports } from "./rules/no-relative-parent-imports.js";
|
|
7
|
+
import { noRenameExports } from "./rules/no-rename-exports.js";
|
|
8
|
+
import { noRenameImports } from "./rules/no-rename-imports.js";
|
|
9
|
+
import { noSideEffectImports } from "./rules/no-side-effect-imports.js";
|
|
10
|
+
import { noTsFileImports } from "./rules/no-ts-file-imports.js";
|
|
11
|
+
|
|
12
|
+
export const rules = {
|
|
13
|
+
[nearestRelativePath.name]: nearestRelativePath.rule,
|
|
14
|
+
[noDirectoryImports.name]: noDirectoryImports.rule,
|
|
15
|
+
[noDynamicImports.name]: noDynamicImports.rule,
|
|
16
|
+
[noGitIgnoredImports.name]: noGitIgnoredImports.rule,
|
|
17
|
+
[noPhantomDepImports.name]: noPhantomDepImports.rule,
|
|
18
|
+
[noRelativeParentImports.name]: noRelativeParentImports.rule,
|
|
19
|
+
[noRenameExports.name]: noRenameExports.rule,
|
|
20
|
+
[noRenameImports.name]: noRenameImports.rule,
|
|
21
|
+
[noSideEffectImports.name]: noSideEffectImports.rule,
|
|
22
|
+
[noTsFileImports.name]: noTsFileImports.rule,
|
|
23
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { nearestRelativePath } from "./nearest-relative-path.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
'import xxx from "../a"',
|
|
6
|
+
'import "../a"',
|
|
7
|
+
'import("../a")',
|
|
8
|
+
'require("../a")',
|
|
9
|
+
'import xxx from "./a"',
|
|
10
|
+
'import xxx from "a"',
|
|
11
|
+
'import xxx from ".a"',
|
|
12
|
+
'export * from "a"',
|
|
13
|
+
'export * from "./a"',
|
|
14
|
+
'export {a} from "a"',
|
|
15
|
+
'export {a} from "./a"',
|
|
16
|
+
].map((code) => ({ code, filename: "/a/b/c/d/e.js" }));
|
|
17
|
+
|
|
18
|
+
const invalid = [
|
|
19
|
+
'import xxx from ".././../a"',
|
|
20
|
+
'import ".././../a"',
|
|
21
|
+
'import(".././../a")',
|
|
22
|
+
'export * from ".././../a"',
|
|
23
|
+
'export {a} from ".././../a"',
|
|
24
|
+
|
|
25
|
+
'import xxx from "./../a"',
|
|
26
|
+
'import "./../a"',
|
|
27
|
+
'import("./../a")',
|
|
28
|
+
'export * from "./../a"',
|
|
29
|
+
'export {a} from "./../a"',
|
|
30
|
+
|
|
31
|
+
'import "././foo"',
|
|
32
|
+
'import "./../.././foo"',
|
|
33
|
+
'import("./../.././foo")',
|
|
34
|
+
'export * from "./../.././foo"',
|
|
35
|
+
'export {a} from "./../.././foo"',
|
|
36
|
+
|
|
37
|
+
'import "./../foo"',
|
|
38
|
+
'import("./../foo")',
|
|
39
|
+
'export * from "./../foo"',
|
|
40
|
+
'export {a} from "./../foo"',
|
|
41
|
+
].map((code) => ({ code, filename: "/a/b/c/d/e.js" }));
|
|
42
|
+
|
|
43
|
+
test({ valid, invalid, ...nearestRelativePath });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { create, createRule, getRuleName, getSourceType } from "../common.js";
|
|
3
|
+
|
|
4
|
+
export const nearestRelativePath = createRule({
|
|
5
|
+
name: getRuleName(import.meta.url),
|
|
6
|
+
message: "The relative source path should be a nearest relative path.",
|
|
7
|
+
create: (context) => create(context, check),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
function check(filename: string, source: string) {
|
|
11
|
+
if (getSourceType(source) !== "local" || source.startsWith("/")) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const currentPath = path.dirname(filename);
|
|
15
|
+
const absoluteSource = path.resolve(currentPath, source);
|
|
16
|
+
// compatible with windows
|
|
17
|
+
let resultPath = path
|
|
18
|
+
.relative(currentPath, absoluteSource)
|
|
19
|
+
.replaceAll("\\", "/");
|
|
20
|
+
if (!resultPath.startsWith("./") && !resultPath.startsWith("../")) {
|
|
21
|
+
resultPath = `./${resultPath}`;
|
|
22
|
+
}
|
|
23
|
+
return resultPath !== source;
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { test } from "../test.spec.js";
|
|
4
|
+
import { noDirectoryImports } from "./no-directory-imports.js";
|
|
5
|
+
|
|
6
|
+
const valid = [
|
|
7
|
+
"import foo from 'foo'",
|
|
8
|
+
"import foo from './foo'",
|
|
9
|
+
`import foo from '${process.cwd()}/index.js'`,
|
|
10
|
+
`import foo from '${process.cwd()}/index.ts'`,
|
|
11
|
+
`import foo from '${process.cwd()}/package.json'`,
|
|
12
|
+
].map((code) => ({
|
|
13
|
+
code,
|
|
14
|
+
filename: fileURLToPath(import.meta.url),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const invalid = [
|
|
18
|
+
"import foo from '.'",
|
|
19
|
+
"import foo from './'",
|
|
20
|
+
"import foo from '..'",
|
|
21
|
+
"import foo from '../'",
|
|
22
|
+
"import foo from '../rules'",
|
|
23
|
+
"import foo from '../rules/'",
|
|
24
|
+
"import foo from '../../src'",
|
|
25
|
+
`import foo from '${process.cwd()}'`,
|
|
26
|
+
].map((code) => ({
|
|
27
|
+
code,
|
|
28
|
+
filename: fileURLToPath(import.meta.url),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
test({ valid, invalid, ...noDirectoryImports });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { create, createRule, getRuleName, getSourceType } from "../common.js";
|
|
4
|
+
import { memoize } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
export const noDirectoryImports = createRule({
|
|
7
|
+
name: getRuleName(import.meta.url),
|
|
8
|
+
message: "Disallow importing from a directory.",
|
|
9
|
+
create: (context) => create(context, check),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function check(filename: string, source: string) {
|
|
13
|
+
if (getSourceType(source) !== "local") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (source.endsWith(".") || source.endsWith("./")) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const absolutePath = path.resolve(path.dirname(filename), source);
|
|
20
|
+
if (!absolutePath.startsWith("/")) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`ESLint plugin internal error. Absolute path incorrect: ${absolutePath}.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return isDir(absolutePath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isDir = memoize((filePath: string) => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(filePath).isDirectory();
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noDynamicImports } from "./no-dynamic-imports.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
"import('foo')",
|
|
6
|
+
'import("foo")',
|
|
7
|
+
'import("./foo")',
|
|
8
|
+
'await import("foo")',
|
|
9
|
+
'const foo = await import("foo")',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const invalid = [
|
|
13
|
+
// 'import()',
|
|
14
|
+
// 'await import()',
|
|
15
|
+
"import(false)",
|
|
16
|
+
"import(123)",
|
|
17
|
+
"await import(123)",
|
|
18
|
+
"import(`foo`)",
|
|
19
|
+
"import(foo)",
|
|
20
|
+
"import({})",
|
|
21
|
+
"import([])",
|
|
22
|
+
"const foo = await import(foo)",
|
|
23
|
+
// "import(foo, {})", // only ts support it, not ecmascript
|
|
24
|
+
// 'import("foo", {})', // only ts support it, not ecmascript
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
test({ valid, invalid, ...noDynamicImports });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Node } from "estree";
|
|
2
|
+
import { createRule, DEFAULT_MESSAGE_ID, getRuleName } from "../common.js";
|
|
3
|
+
|
|
4
|
+
export const noDynamicImports = createRule({
|
|
5
|
+
name: getRuleName(import.meta.url),
|
|
6
|
+
message: "`import()` should be called with string literal.",
|
|
7
|
+
create: (context) => ({
|
|
8
|
+
"ImportExpression > :not(Literal)": (node: Node) => {
|
|
9
|
+
context.report({ node, messageId: DEFAULT_MESSAGE_ID });
|
|
10
|
+
},
|
|
11
|
+
"ImportExpression > Literal[raw=/^[^'\"].*[^'\"]$/]": (node: Node) => {
|
|
12
|
+
context.report({ node, messageId: DEFAULT_MESSAGE_ID });
|
|
13
|
+
},
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { test } from "../test.spec.js";
|
|
4
|
+
import { noGitIgnoredImports } from "./no-git-ignored-imports.js";
|
|
5
|
+
|
|
6
|
+
const valid = [
|
|
7
|
+
"import foo from 'foo'",
|
|
8
|
+
"import 'foo'",
|
|
9
|
+
"require('foo')",
|
|
10
|
+
"import('foo')",
|
|
11
|
+
"export * from 'foo'",
|
|
12
|
+
"export {name} from 'foo'",
|
|
13
|
+
|
|
14
|
+
"import foo from '.foo'",
|
|
15
|
+
"import foo from '../../../../for-test'",
|
|
16
|
+
].map((code) => ({
|
|
17
|
+
code,
|
|
18
|
+
filename: fileURLToPath(import.meta.url),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const invalid = [
|
|
22
|
+
"import foo from './dist/foo'",
|
|
23
|
+
"import './dist/foo'",
|
|
24
|
+
"import('./dist/foo')",
|
|
25
|
+
"export * from './dist/foo'",
|
|
26
|
+
"export {name} from './dist/foo'",
|
|
27
|
+
|
|
28
|
+
"import foo from '../dist/index.js'",
|
|
29
|
+
"import foo from '../../node_modules/foo/bar.js'",
|
|
30
|
+
|
|
31
|
+
"import foo from '/foo/tmp'",
|
|
32
|
+
`import foo from '${process.cwd()}/tmp'`,
|
|
33
|
+
"import foo from '../../test/for-test'",
|
|
34
|
+
"import foo from '../../../../../for-test'",
|
|
35
|
+
].map((code) => ({
|
|
36
|
+
code,
|
|
37
|
+
filename: fileURLToPath(import.meta.url),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
test({ valid, invalid, ...noGitIgnoredImports });
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { isNativeError } from "node:util/types";
|
|
5
|
+
import { create, createRule, getRuleName, getSourceType } from "../common.js";
|
|
6
|
+
import { memoize } from "../utils.js";
|
|
7
|
+
|
|
8
|
+
export const noGitIgnoredImports = createRule({
|
|
9
|
+
name: getRuleName(import.meta.url),
|
|
10
|
+
message: "Disallow to import module from a git-ignored path.",
|
|
11
|
+
create: (context) => create(context, checkIgnored),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function checkIgnored(filename: string, source: string) {
|
|
15
|
+
// from node_modules
|
|
16
|
+
if (getSourceType(source) !== "local") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// out side of project root
|
|
20
|
+
if (source.startsWith("/") && !source.startsWith(process.cwd())) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// This file of absolutePath may be a symbolic link
|
|
24
|
+
const absolutePath = path.resolve(path.dirname(filename), source);
|
|
25
|
+
if (!absolutePath.startsWith("/")) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`ESLint plugin internal error. Absolute path incorrect: ${absolutePath}.`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return isIgnored(absolutePath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isIgnored = memoize((filePath: string) => {
|
|
34
|
+
try {
|
|
35
|
+
return (
|
|
36
|
+
childProcess
|
|
37
|
+
.execSync(`git check-ignore ${filePath}`, { encoding: "utf8" })
|
|
38
|
+
.trim() === filePath
|
|
39
|
+
);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (
|
|
42
|
+
isNativeError(e) &&
|
|
43
|
+
"stdout" in e &&
|
|
44
|
+
e.stdout === "" &&
|
|
45
|
+
"stderr" in e &&
|
|
46
|
+
e.stderr === ""
|
|
47
|
+
) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// We cannot throw an error here. So we have to return true to report the filePath is bad.
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { test } from "../test.spec.js";
|
|
5
|
+
import { noPhantomDepImports } from "./no-phantom-dep-imports.js";
|
|
6
|
+
|
|
7
|
+
const valid = [
|
|
8
|
+
{ code: "import foo from '/foo'" },
|
|
9
|
+
{ code: "import foo from './foo'" },
|
|
10
|
+
{ code: "import foo from '../foo'" },
|
|
11
|
+
{ code: "import foo from 'node:foo'" },
|
|
12
|
+
{ code: "import type {Foo} from 'foo'" },
|
|
13
|
+
{
|
|
14
|
+
code: "import eslint from 'eslint'",
|
|
15
|
+
filename: fileURLToPath(import.meta.url),
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const invalid = [
|
|
20
|
+
{ code: "import {type Foo} from 'foo'" },
|
|
21
|
+
{ code: "import foo from 'foo'", filename: fileURLToPath(import.meta.url) },
|
|
22
|
+
{
|
|
23
|
+
code: "import eslint from 'eslint'",
|
|
24
|
+
filename: path.join(process.cwd(), "foo.js"),
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
test({ valid, invalid, ...noPhantomDepImports });
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import {
|
|
5
|
+
create,
|
|
6
|
+
createRule,
|
|
7
|
+
getRuleName,
|
|
8
|
+
getSourceType,
|
|
9
|
+
type ImportationNode,
|
|
10
|
+
} from "../common.js";
|
|
11
|
+
|
|
12
|
+
function isObject(value: unknown) {
|
|
13
|
+
return value !== null && typeof value === "object";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isFile(filePath: string) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.statSync(filePath).isFile();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const cache = new Map<string, { path: string; content: object } | undefined>(); // key is dir, value is package.json
|
|
25
|
+
function getPkgJson(
|
|
26
|
+
dir: string,
|
|
27
|
+
): { path: string; content: object } | undefined {
|
|
28
|
+
if (cache.has(dir)) {
|
|
29
|
+
return cache.get(dir);
|
|
30
|
+
}
|
|
31
|
+
const pkgJsonPath = path.join(dir, "package.json");
|
|
32
|
+
if (isFile(pkgJsonPath)) {
|
|
33
|
+
const content: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
34
|
+
const result = isObject(content)
|
|
35
|
+
? { path: pkgJsonPath, content }
|
|
36
|
+
: undefined;
|
|
37
|
+
cache.set(dir, result);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// if it is a directory
|
|
42
|
+
if (dir === process.cwd() || dir === "/") {
|
|
43
|
+
// stop here
|
|
44
|
+
cache.set(dir, undefined);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return getPkgJson(path.join(dir, ".."));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const noPhantomDepImports = createRule({
|
|
52
|
+
name: getRuleName(import.meta.url),
|
|
53
|
+
message:
|
|
54
|
+
"Disallow importing from a module which the nearest `package.json` doesn't include it.",
|
|
55
|
+
create: (context) => create(context, check),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function check(filename: string, source: string, node: ImportationNode) {
|
|
59
|
+
// ignore `import type {foo} from 'foo'`
|
|
60
|
+
if ("importKind" in node && node.importKind === "type") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
// ignore `import {foo} from './'`
|
|
64
|
+
if (getSourceType(source) !== "module") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const pkgJson = getPkgJson(path.dirname(filename));
|
|
68
|
+
// cannot find package.json file
|
|
69
|
+
if (!pkgJson) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const dep =
|
|
73
|
+
"dependencies" in pkgJson.content && isObject(pkgJson.content.dependencies)
|
|
74
|
+
? pkgJson.content.dependencies
|
|
75
|
+
: {};
|
|
76
|
+
const devDep =
|
|
77
|
+
"devDependencies" in pkgJson.content &&
|
|
78
|
+
isObject(pkgJson.content.devDependencies)
|
|
79
|
+
? pkgJson.content.devDependencies
|
|
80
|
+
: {};
|
|
81
|
+
|
|
82
|
+
const moduleName = source
|
|
83
|
+
.split("/")
|
|
84
|
+
.slice(0, source.startsWith("@") ? 2 : 1)
|
|
85
|
+
.join("/");
|
|
86
|
+
if (["test", "spec"].includes(filename.split(".").at(-2) ?? "")) {
|
|
87
|
+
return !(moduleName in dep || moduleName in devDep);
|
|
88
|
+
}
|
|
89
|
+
return !(moduleName in dep);
|
|
90
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noRelativeParentImports } from "./no-relative-parent-imports.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
"import foo from 'foo'",
|
|
6
|
+
"import 'foo'",
|
|
7
|
+
"require('foo')",
|
|
8
|
+
"import('foo')",
|
|
9
|
+
"export * from 'foo'",
|
|
10
|
+
"export {name} from 'foo'",
|
|
11
|
+
|
|
12
|
+
"import foo from '.foo'",
|
|
13
|
+
"import foo from './foo'",
|
|
14
|
+
"import foo from '../foo'",
|
|
15
|
+
"import foo from '../../foo'",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const invalid = [
|
|
19
|
+
"import foo from '../../../foo'",
|
|
20
|
+
"import '../../../foo'",
|
|
21
|
+
"import('../../../foo')",
|
|
22
|
+
"export * from '../../../foo'",
|
|
23
|
+
"export {name} from '../../../foo'",
|
|
24
|
+
|
|
25
|
+
"import foo from '../../../../foo'",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
test({ valid, invalid, ...noRelativeParentImports });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { create, createRule, getRuleName } from "../common.js";
|
|
2
|
+
|
|
3
|
+
const depth = 3;
|
|
4
|
+
|
|
5
|
+
export const noRelativeParentImports = createRule({
|
|
6
|
+
name: getRuleName(import.meta.url),
|
|
7
|
+
message: "Disallow importing module from a relative parent path too deeply.",
|
|
8
|
+
create: (context) => create(context, checkDepth),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
function checkDepth(_filename: string, source: string) {
|
|
12
|
+
return new RegExp(`^(\\.\\./){${depth},}`).test(source);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noRenameExports } from "./no-rename-exports.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
"let foo=1; export {foo}",
|
|
6
|
+
"export let foo",
|
|
7
|
+
"export const foo = bar",
|
|
8
|
+
"export default foo",
|
|
9
|
+
"export default {}",
|
|
10
|
+
"export {}",
|
|
11
|
+
];
|
|
12
|
+
const invalid = [
|
|
13
|
+
"let foo=1; export {foo as bar}",
|
|
14
|
+
"let foo=1; export {foo as default}",
|
|
15
|
+
"export {foo as bar} from './foo'",
|
|
16
|
+
"export {default as foo} from './foo'",
|
|
17
|
+
// ts
|
|
18
|
+
"export {type Foo as Bar}",
|
|
19
|
+
"export type {Foo as Bar}",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
test({ valid, invalid, ...noRenameExports });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createRule, DEFAULT_MESSAGE_ID, getRuleName } from "../common.js";
|
|
2
|
+
|
|
3
|
+
export const noRenameExports = createRule({
|
|
4
|
+
name: getRuleName(import.meta.url),
|
|
5
|
+
message: "Disallow renaming the named-exports.",
|
|
6
|
+
create: (context) => ({
|
|
7
|
+
ExportSpecifier: (node) => {
|
|
8
|
+
if (node.exported.name !== node.local.name) {
|
|
9
|
+
context.report({ node, messageId: DEFAULT_MESSAGE_ID });
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noRenameImports } from "./no-rename-imports.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
"import Foo from 'foo'",
|
|
6
|
+
"import {foo, bar} from 'foo'",
|
|
7
|
+
// ts
|
|
8
|
+
"import {type foo} from 'foo'",
|
|
9
|
+
"import type {foo} from 'foo'",
|
|
10
|
+
"import type Foo from 'foo'",
|
|
11
|
+
];
|
|
12
|
+
const invalid = [
|
|
13
|
+
"import {foo as bar} from 'foo'",
|
|
14
|
+
"import {default as foo} from 'foo'",
|
|
15
|
+
// ts
|
|
16
|
+
"import type {foo as bar} from 'foo'",
|
|
17
|
+
"import {type foo as bar} from 'foo'",
|
|
18
|
+
"import type {default as foo} from 'foo'",
|
|
19
|
+
"import {type default as foo} from 'foo'",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
test({ valid, invalid, ...noRenameImports });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createRule, DEFAULT_MESSAGE_ID, getRuleName } from "../common.js";
|
|
2
|
+
|
|
3
|
+
export const noRenameImports = createRule({
|
|
4
|
+
name: getRuleName(import.meta.url),
|
|
5
|
+
message: "Disallow renaming the named-imports.",
|
|
6
|
+
create: (context) => ({
|
|
7
|
+
ImportSpecifier: (node) => {
|
|
8
|
+
if (node.imported.name !== node.local.name) {
|
|
9
|
+
context.report({ node, messageId: DEFAULT_MESSAGE_ID });
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noSideEffectImports } from "./no-side-effect-imports.js";
|
|
3
|
+
|
|
4
|
+
const valid = [
|
|
5
|
+
"import 'reflect-metadata'",
|
|
6
|
+
"import {} from 'reflect-metadata'",
|
|
7
|
+
"import 'foo.css'",
|
|
8
|
+
"import './foo.css'",
|
|
9
|
+
"import 'module.css'",
|
|
10
|
+
"import {foo} from 'foo'",
|
|
11
|
+
]
|
|
12
|
+
.map((code) => ({ code, filename: "foo.ts" }))
|
|
13
|
+
.concat({ code: "import 'foo'", filename: "foo.d.ts" });
|
|
14
|
+
|
|
15
|
+
const invalid = [
|
|
16
|
+
"import 'foo'",
|
|
17
|
+
"import './foo'",
|
|
18
|
+
"import {} from 'foo'",
|
|
19
|
+
"import {} from './foo'",
|
|
20
|
+
"import './reflect-metadata'",
|
|
21
|
+
"import './foo.module.css'",
|
|
22
|
+
"import 'foo.module.css'",
|
|
23
|
+
].map((code) => ({ code, filename: "foo.ts" }));
|
|
24
|
+
|
|
25
|
+
test({ valid, invalid, ...noSideEffectImports });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ImportDeclaration } from "estree";
|
|
2
|
+
import { createRule, DEFAULT_MESSAGE_ID, getRuleName } from "../common.js";
|
|
3
|
+
|
|
4
|
+
const ignores = [
|
|
5
|
+
"^reflect-metadata$",
|
|
6
|
+
// https://github.com/vitejs/vite/blob/main/packages/vite/client.d.ts
|
|
7
|
+
"(?<!\\.module)\\.css$",
|
|
8
|
+
"(?<!\\.module)\\.scss$",
|
|
9
|
+
"(?<!\\.module)\\.sass$",
|
|
10
|
+
"(?<!\\.module)\\.less$",
|
|
11
|
+
"(?<!\\.module)\\.styl$",
|
|
12
|
+
"(?<!\\.module)\\.stylus$",
|
|
13
|
+
"(?<!\\.module)\\.pcss$",
|
|
14
|
+
"(?<!\\.module)\\.sss$",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
|
18
|
+
export const noSideEffectImports = createRule({
|
|
19
|
+
name: getRuleName(import.meta.url),
|
|
20
|
+
message:
|
|
21
|
+
"Side effect import is often used for polyfills and css. It's unsafe to use it.",
|
|
22
|
+
create: (context) => {
|
|
23
|
+
if (
|
|
24
|
+
[".d.ts", ".d.cts", ".d.mts", ".d.tsx"].some((ext) =>
|
|
25
|
+
context.filename.endsWith(ext),
|
|
26
|
+
)
|
|
27
|
+
) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const ignoreExps = ignores.map((ignore) => new RegExp(ignore));
|
|
31
|
+
return {
|
|
32
|
+
"ImportDeclaration[specifiers.length=0]": (node: ImportDeclaration) => {
|
|
33
|
+
if (
|
|
34
|
+
ignoreExps.some((exp) =>
|
|
35
|
+
exp.test(node.source.value?.toString() ?? ""),
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
context.report({ node, messageId: DEFAULT_MESSAGE_ID });
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { test } from "../test.spec.js";
|
|
2
|
+
import { noTsFileImports } from "./no-ts-file-imports.js";
|
|
3
|
+
|
|
4
|
+
const codes = [
|
|
5
|
+
"import foo from './foo.ts'",
|
|
6
|
+
"import foo from './foo.cts'",
|
|
7
|
+
"import foo from './foo.mts'",
|
|
8
|
+
"import foo from './foo.tsx'",
|
|
9
|
+
|
|
10
|
+
"import foo from 'foo.d.bar'",
|
|
11
|
+
"import foo from './foo.d.bar'",
|
|
12
|
+
"import foo from './foo/foo.d.bar'",
|
|
13
|
+
|
|
14
|
+
"import foo from './foo.d.ts'",
|
|
15
|
+
"import foo from './foo.d.cts'",
|
|
16
|
+
"import foo from './foo.d.mts'",
|
|
17
|
+
"import foo from './foo.d.tsx'",
|
|
18
|
+
|
|
19
|
+
"import foo from './foo.d.js'",
|
|
20
|
+
"import foo from './foo.d.cjs'",
|
|
21
|
+
"import foo from './foo.d.mjs'",
|
|
22
|
+
"import foo from './foo.d.jsx'",
|
|
23
|
+
|
|
24
|
+
"import foo from '/foo.ts'",
|
|
25
|
+
"import foo from '/foo.d.js'",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const invalid = codes.flatMap((code) => [
|
|
29
|
+
{ code, filename: "bar.js" },
|
|
30
|
+
{ code, filename: "bar" },
|
|
31
|
+
{ code, filename: "bar.ts" },
|
|
32
|
+
{ code, filename: "bar.tsx" },
|
|
33
|
+
]);
|
|
34
|
+
const valid = codes.flatMap((code) => [
|
|
35
|
+
{ code, filename: "bar.d.ts" },
|
|
36
|
+
{ code, filename: "bar.d.tsx" },
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
test({ valid, invalid, ...noTsFileImports });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { create, createRule, getRuleName } from "../common.js";
|
|
2
|
+
|
|
3
|
+
export const noTsFileImports = createRule({
|
|
4
|
+
name: getRuleName(import.meta.url),
|
|
5
|
+
message: "Disallow importing from a declaration style file or a ts file",
|
|
6
|
+
create: (context) => create(context, check),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function check(filename: string, source: string) {
|
|
10
|
+
// disabled this rule in declaration files
|
|
11
|
+
if (
|
|
12
|
+
[".d.ts", ".d.cts", ".d.mts", ".d.tsx"].some((ext) =>
|
|
13
|
+
filename.endsWith(ext),
|
|
14
|
+
)
|
|
15
|
+
) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const file = source.split("/").at(-1);
|
|
19
|
+
if (!file || file.includes(".d.")) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return [".ts", ".cts", ".mts", ".tsx"].some((ext) => file.endsWith(ext));
|
|
23
|
+
}
|