eslint-plugin-esm 0.0.0 → 0.1.1

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +60 -0
  3. package/dist/common.d.ts +24 -0
  4. package/dist/common.d.ts.map +1 -0
  5. package/dist/common.js +65 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +23 -2
  9. package/dist/rules/nearest-relative-path.d.ts +5 -0
  10. package/dist/rules/nearest-relative-path.d.ts.map +1 -0
  11. package/dist/rules/nearest-relative-path.js +25 -0
  12. package/dist/rules/no-directory-imports.d.ts +5 -0
  13. package/dist/rules/no-directory-imports.d.ts.map +1 -0
  14. package/dist/rules/no-directory-imports.js +31 -0
  15. package/dist/rules/no-dynamic-imports.d.ts +5 -0
  16. package/dist/rules/no-dynamic-imports.d.ts.map +1 -0
  17. package/dist/rules/no-dynamic-imports.js +14 -0
  18. package/dist/rules/no-git-ignored-imports.d.ts +5 -0
  19. package/dist/rules/no-git-ignored-imports.d.ts.map +1 -0
  20. package/dist/rules/no-git-ignored-imports.js +46 -0
  21. package/dist/rules/no-phantom-dep-imports.d.ts +5 -0
  22. package/dist/rules/no-phantom-dep-imports.d.ts.map +1 -0
  23. package/dist/rules/no-phantom-dep-imports.js +73 -0
  24. package/dist/rules/no-relative-parent-imports.d.ts +5 -0
  25. package/dist/rules/no-relative-parent-imports.d.ts.map +1 -0
  26. package/dist/rules/no-relative-parent-imports.js +11 -0
  27. package/dist/rules/no-rename-exports.d.ts +5 -0
  28. package/dist/rules/no-rename-exports.d.ts.map +1 -0
  29. package/dist/rules/no-rename-exports.js +15 -0
  30. package/dist/rules/no-rename-imports.d.ts +5 -0
  31. package/dist/rules/no-rename-imports.d.ts.map +1 -0
  32. package/dist/rules/no-rename-imports.js +14 -0
  33. package/dist/rules/no-side-effect-imports.d.ts +5 -0
  34. package/dist/rules/no-side-effect-imports.d.ts.map +1 -0
  35. package/dist/rules/no-side-effect-imports.js +33 -0
  36. package/dist/rules/no-ts-file-imports.d.ts +5 -0
  37. package/dist/rules/no-ts-file-imports.d.ts.map +1 -0
  38. package/dist/rules/no-ts-file-imports.js +18 -0
  39. package/dist/utils.d.ts +5 -0
  40. package/dist/utils.d.ts.map +1 -0
  41. package/dist/utils.js +16 -0
  42. package/doc/rules/nearest-relative-path.md +48 -0
  43. package/doc/rules/no-directory-imports.md +30 -0
  44. package/doc/rules/no-dynamic-imports.md +30 -0
  45. package/doc/rules/no-git-ignored-imports.md +36 -0
  46. package/doc/rules/no-phantom-dep-imports.md +26 -0
  47. package/doc/rules/no-relative-parent-imports.md +33 -0
  48. package/doc/rules/no-rename-exports.md +29 -0
  49. package/doc/rules/no-rename-imports.md +28 -0
  50. package/doc/rules/no-side-effect-imports.md +31 -0
  51. package/doc/rules/no-ts-file-imports.md +119 -0
  52. package/package.json +7 -5
  53. package/src/common.ts +96 -0
  54. package/src/index.ts +23 -0
  55. package/src/rules/nearest-relative-path.spec.ts +44 -0
  56. package/src/rules/nearest-relative-path.ts +28 -0
  57. package/src/rules/no-directory-imports.spec.ts +31 -0
  58. package/src/rules/no-directory-imports.ts +34 -0
  59. package/src/rules/no-dynamic-imports.spec.ts +27 -0
  60. package/src/rules/no-dynamic-imports.ts +15 -0
  61. package/src/rules/no-git-ignored-imports.spec.ts +40 -0
  62. package/src/rules/no-git-ignored-imports.ts +53 -0
  63. package/src/rules/no-phantom-dep-imports.spec.ts +28 -0
  64. package/src/rules/no-phantom-dep-imports.ts +90 -0
  65. package/src/rules/no-relative-parent-imports.spec.ts +28 -0
  66. package/src/rules/no-relative-parent-imports.ts +13 -0
  67. package/src/rules/no-rename-exports.spec.ts +22 -0
  68. package/src/rules/no-rename-exports.ts +17 -0
  69. package/src/rules/no-rename-imports.spec.ts +22 -0
  70. package/src/rules/no-rename-imports.ts +16 -0
  71. package/src/rules/no-side-effect-imports.spec.ts +25 -0
  72. package/src/rules/no-side-effect-imports.ts +44 -0
  73. package/src/rules/no-ts-file-imports.spec.ts +39 -0
  74. package/src/rules/no-ts-file-imports.ts +23 -0
  75. package/src/test.spec.ts +110 -0
  76. 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,44 @@
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
+ 'import foo from "."',
17
+ ].map((code) => ({ code, filename: "/a/b/c/d/e.js" }));
18
+
19
+ const invalid = [
20
+ 'import xxx from ".././../a"',
21
+ 'import ".././../a"',
22
+ 'import(".././../a")',
23
+ 'export * from ".././../a"',
24
+ 'export {a} from ".././../a"',
25
+
26
+ 'import xxx from "./../a"',
27
+ 'import "./../a"',
28
+ 'import("./../a")',
29
+ 'export * from "./../a"',
30
+ 'export {a} from "./../a"',
31
+
32
+ 'import "././foo"',
33
+ 'import "./../.././foo"',
34
+ 'import("./../.././foo")',
35
+ 'export * from "./../.././foo"',
36
+ 'export {a} from "./../.././foo"',
37
+
38
+ 'import "./../foo"',
39
+ 'import("./../foo")',
40
+ 'export * from "./../foo"',
41
+ 'export {a} from "./../foo"',
42
+ ].map((code) => ({ code, filename: "/a/b/c/d/e.js" }));
43
+
44
+ test({ valid, invalid, ...nearestRelativePath });
@@ -0,0 +1,28 @@
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 (
12
+ getSourceType(source) !== "local" ||
13
+ source.startsWith("/") ||
14
+ source === "."
15
+ ) {
16
+ return false;
17
+ }
18
+ const currentPath = path.dirname(filename);
19
+ const absoluteSource = path.resolve(currentPath, source);
20
+ // compatible with windows
21
+ let resultPath = path
22
+ .relative(currentPath, absoluteSource)
23
+ .replaceAll("\\", "/");
24
+ if (!resultPath.startsWith("./") && !resultPath.startsWith("../")) {
25
+ resultPath = `./${resultPath}`;
26
+ }
27
+ return resultPath !== source;
28
+ }
@@ -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,17 @@
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 (
9
+ node.exported.type !== "Identifier" ||
10
+ node.local.type !== "Identifier" ||
11
+ node.exported.name !== node.local.name
12
+ ) {
13
+ context.report({ node, messageId: DEFAULT_MESSAGE_ID });
14
+ }
15
+ },
16
+ }),
17
+ });
@@ -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,16 @@
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 (
9
+ node.imported.type !== "Identifier" ||
10
+ node.imported.name !== node.local.name
11
+ ) {
12
+ context.report({ node, messageId: DEFAULT_MESSAGE_ID });
13
+ }
14
+ },
15
+ }),
16
+ });
@@ -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 });