eslint-config-typed 2.2.0 → 2.3.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.
Files changed (80) hide show
  1. package/dist/configs/plugins.d.mts.map +1 -1
  2. package/dist/configs/plugins.mjs +1 -2
  3. package/dist/configs/plugins.mjs.map +1 -1
  4. package/dist/configs/typescript.d.mts.map +1 -1
  5. package/dist/configs/typescript.mjs +2 -1
  6. package/dist/configs/typescript.mjs.map +1 -1
  7. package/dist/entry-point.mjs +5 -0
  8. package/dist/entry-point.mjs.map +1 -1
  9. package/dist/index.mjs +1 -0
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/plugins/index.d.mts +1 -0
  12. package/dist/plugins/index.d.mts.map +1 -1
  13. package/dist/plugins/index.mjs +1 -0
  14. package/dist/plugins/index.mjs.map +1 -1
  15. package/dist/plugins/strict-dependencies/index.d.mts +2 -0
  16. package/dist/plugins/strict-dependencies/index.d.mts.map +1 -0
  17. package/dist/plugins/strict-dependencies/index.mjs +2 -0
  18. package/dist/plugins/strict-dependencies/index.mjs.map +1 -0
  19. package/dist/plugins/strict-dependencies/plugin.d.mts +3 -0
  20. package/dist/plugins/strict-dependencies/plugin.d.mts.map +1 -0
  21. package/dist/plugins/strict-dependencies/plugin.mjs +9 -0
  22. package/dist/plugins/strict-dependencies/plugin.mjs.map +1 -0
  23. package/dist/plugins/strict-dependencies/rules/index.d.mts +2 -0
  24. package/dist/plugins/strict-dependencies/rules/index.d.mts.map +1 -0
  25. package/dist/plugins/strict-dependencies/rules/index.mjs +2 -0
  26. package/dist/plugins/strict-dependencies/rules/index.mjs.map +1 -0
  27. package/dist/plugins/strict-dependencies/rules/resolve-import-path.d.mts +5 -0
  28. package/dist/plugins/strict-dependencies/rules/resolve-import-path.d.mts.map +1 -0
  29. package/dist/plugins/strict-dependencies/rules/resolve-import-path.mjs +79 -0
  30. package/dist/plugins/strict-dependencies/rules/resolve-import-path.mjs.map +1 -0
  31. package/dist/plugins/strict-dependencies/rules/rules.d.mts +19 -0
  32. package/dist/plugins/strict-dependencies/rules/rules.d.mts.map +1 -0
  33. package/dist/plugins/strict-dependencies/rules/rules.mjs +8 -0
  34. package/dist/plugins/strict-dependencies/rules/rules.mjs.map +1 -0
  35. package/dist/plugins/strict-dependencies/rules/strict-dependencies.d.mts +17 -0
  36. package/dist/plugins/strict-dependencies/rules/strict-dependencies.d.mts.map +1 -0
  37. package/dist/plugins/strict-dependencies/rules/strict-dependencies.mjs +139 -0
  38. package/dist/plugins/strict-dependencies/rules/strict-dependencies.mjs.map +1 -0
  39. package/dist/plugins/total-functions/plugin.mjs +0 -18
  40. package/dist/plugins/total-functions/plugin.mjs.map +1 -1
  41. package/dist/plugins/total-functions/rules/index.d.mts +0 -17
  42. package/dist/plugins/total-functions/rules/index.d.mts.map +1 -1
  43. package/dist/plugins/total-functions/rules/index.mjs +0 -17
  44. package/dist/plugins/total-functions/rules/index.mjs.map +1 -1
  45. package/dist/plugins/tree-shakable/plugin.mjs +0 -1
  46. package/dist/plugins/tree-shakable/plugin.mjs.map +1 -1
  47. package/dist/plugins/tree-shakable/rules/index.d.mts +0 -1
  48. package/dist/plugins/tree-shakable/rules/index.d.mts.map +1 -1
  49. package/dist/plugins/tree-shakable/rules/index.mjs +0 -1
  50. package/dist/plugins/tree-shakable/rules/index.mjs.map +1 -1
  51. package/dist/rules/eslint-import-rules.d.mts +1 -1
  52. package/dist/rules/eslint-import-rules.d.mts.map +1 -1
  53. package/dist/rules/eslint-import-rules.mjs +3 -2
  54. package/dist/rules/eslint-import-rules.mjs.map +1 -1
  55. package/dist/rules/typescript-eslint-rules.d.mts +1 -1
  56. package/dist/rules/typescript-eslint-rules.d.mts.map +1 -1
  57. package/dist/rules/typescript-eslint-rules.mjs +1 -0
  58. package/dist/rules/typescript-eslint-rules.mjs.map +1 -1
  59. package/dist/types/define-config.d.mts +1 -1
  60. package/dist/types/define-config.d.mts.map +1 -1
  61. package/dist/types/rules/eslint-strict-dependencies-rules.d.mts +40 -32
  62. package/dist/types/rules/eslint-strict-dependencies-rules.d.mts.map +1 -1
  63. package/package.json +6 -2
  64. package/src/configs/plugins.mts +1 -4
  65. package/src/configs/typescript.mts +3 -1
  66. package/src/plugins/index.mts +1 -0
  67. package/src/plugins/strict-dependencies/index.mts +1 -0
  68. package/src/plugins/strict-dependencies/plugin.mts +7 -0
  69. package/src/plugins/strict-dependencies/rules/index.mts +1 -0
  70. package/src/plugins/strict-dependencies/rules/resolve-import-path.mts +113 -0
  71. package/src/plugins/strict-dependencies/rules/resolve-import-path.test.mts +258 -0
  72. package/src/plugins/strict-dependencies/rules/rules.mts +6 -0
  73. package/src/plugins/strict-dependencies/rules/strict-dependencies.mts +201 -0
  74. package/src/plugins/strict-dependencies/rules/strict-dependencies.test.mts +113 -0
  75. package/src/plugins/total-functions/rules/index.mts +0 -17
  76. package/src/plugins/tree-shakable/rules/index.mts +0 -1
  77. package/src/rules/eslint-import-rules.mts +3 -2
  78. package/src/rules/typescript-eslint-rules.mts +1 -0
  79. package/src/types/define-config.mts +1 -1
  80. package/src/types/rules/eslint-strict-dependencies-rules.mts +40 -34
@@ -0,0 +1,258 @@
1
+ import * as ts from 'typescript';
2
+ import { resolveImportPath } from './resolve-import-path.mjs';
3
+
4
+ // FIXME: https://github.com/knowledge-work/eslint-plugin-strict-dependencies/tree/v1.3.27 のテストをコピーしてきて
5
+ // Codex でテスト・型チェックが通るようにさせただけなので、意味の無いテストになっていないかチェックして修正する
6
+
7
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
8
+ type TypeScriptModule = typeof import('typescript');
9
+
10
+ const actualFunctions = vi.hoisted<{
11
+ findConfigFile?: TypeScriptModule['findConfigFile'];
12
+ getParsedCommandLineOfConfigFile?: TypeScriptModule['getParsedCommandLineOfConfigFile'];
13
+ }>(() => ({
14
+ findConfigFile: undefined,
15
+ getParsedCommandLineOfConfigFile: undefined,
16
+ }));
17
+
18
+ vi.mock('typescript', async () => {
19
+ const actual = await vi.importActual<TypeScriptModule>('typescript');
20
+ // eslint-disable-next-line functional/immutable-data
21
+ actualFunctions.findConfigFile = actual.findConfigFile;
22
+ // eslint-disable-next-line functional/immutable-data
23
+ actualFunctions.getParsedCommandLineOfConfigFile =
24
+ actual.getParsedCommandLineOfConfigFile;
25
+ return {
26
+ ...actual,
27
+ findConfigFile: vi.fn(actual.findConfigFile),
28
+ getParsedCommandLineOfConfigFile: vi.fn(
29
+ actual.getParsedCommandLineOfConfigFile,
30
+ ),
31
+ };
32
+ });
33
+
34
+ const mockFindConfigFile = vi.mocked(ts.findConfigFile);
35
+ const mockGetParsedCommandLine = vi.mocked(ts.getParsedCommandLineOfConfigFile);
36
+
37
+ const compilerOptionsByFixture = {
38
+ 'no-paths': {},
39
+ 'paths-basic': {
40
+ paths: {
41
+ '@/components/': ['components/'],
42
+ '@/components': ['components'],
43
+ '@/components/*': ['components/*'],
44
+ },
45
+ },
46
+ 'base-url-dot': {
47
+ baseUrl: '.',
48
+ paths: {
49
+ '@/components/': ['components/'],
50
+ },
51
+ },
52
+ 'base-url-dot-slash': {
53
+ baseUrl: './',
54
+ paths: {
55
+ '@/components/': ['components/'],
56
+ },
57
+ },
58
+ 'base-url-parent': {
59
+ baseUrl: '../',
60
+ paths: {
61
+ '@/components/': ['components/'],
62
+ },
63
+ },
64
+ 'base-url-src': {
65
+ baseUrl: 'src',
66
+ paths: {
67
+ '@/components/': ['components/'],
68
+ },
69
+ },
70
+ 'base-url-dot-src': {
71
+ baseUrl: './src',
72
+ paths: {
73
+ '@/components/': ['components/'],
74
+ },
75
+ },
76
+ 'base-url-src-slash': {
77
+ baseUrl: 'src/',
78
+ paths: {
79
+ '@/components/': ['components/'],
80
+ },
81
+ },
82
+ 'base-url-dot-src-slash': {
83
+ baseUrl: './src/',
84
+ paths: {
85
+ '@/components/': ['components/'],
86
+ },
87
+ },
88
+ 'paths-multiple': {
89
+ paths: {
90
+ '@/components/*': ['src/components/*', 'src/alternativeComponents/*'],
91
+ },
92
+ },
93
+ } as const satisfies Readonly<Record<string, ts.CompilerOptions>>;
94
+
95
+ type FixtureName = keyof typeof compilerOptionsByFixture;
96
+
97
+ const restoreActual = (): void => {
98
+ if (
99
+ actualFunctions.findConfigFile !== undefined &&
100
+ actualFunctions.getParsedCommandLineOfConfigFile !== undefined
101
+ ) {
102
+ mockFindConfigFile.mockImplementation(actualFunctions.findConfigFile);
103
+ mockGetParsedCommandLine.mockImplementation(
104
+ actualFunctions.getParsedCommandLineOfConfigFile,
105
+ );
106
+ }
107
+ };
108
+
109
+ const useFixture = (fixtureName: FixtureName | undefined): void => {
110
+ restoreActual();
111
+
112
+ if (fixtureName === undefined) {
113
+ return;
114
+ }
115
+
116
+ const compilerOptions = compilerOptionsByFixture[fixtureName];
117
+ mockFindConfigFile.mockImplementation(() => 'tsconfig.json');
118
+ mockGetParsedCommandLine.mockImplementation(() => ({
119
+ options: { ...compilerOptions },
120
+ fileNames: [],
121
+ errors: [],
122
+ }));
123
+ };
124
+
125
+ const aliasCases = [
126
+ {
127
+ label: '@/components/',
128
+ importPath: '@/components/aaa/bbb',
129
+ expected: 'components/aaa/bbb',
130
+ },
131
+ {
132
+ label: '@/components',
133
+ importPath: '@/components/aaa/bbb',
134
+ expected: 'components/aaa/bbb',
135
+ },
136
+ {
137
+ label: '@/components/*',
138
+ importPath: '@/components/aaa/bbb',
139
+ expected: 'components/aaa/bbb',
140
+ },
141
+ ] as const;
142
+
143
+ const baseUrlCases = [
144
+ {
145
+ fixtureName: 'base-url-dot',
146
+ expected: 'components/aaa/bbb',
147
+ },
148
+ {
149
+ fixtureName: 'base-url-dot-slash',
150
+ expected: 'components/aaa/bbb',
151
+ },
152
+ {
153
+ fixtureName: 'base-url-parent',
154
+ expected: '../components/aaa/bbb',
155
+ },
156
+ {
157
+ fixtureName: 'base-url-src',
158
+ expected: 'src/components/aaa/bbb',
159
+ },
160
+ {
161
+ fixtureName: 'base-url-dot-src',
162
+ expected: 'src/components/aaa/bbb',
163
+ },
164
+ {
165
+ fixtureName: 'base-url-src-slash',
166
+ expected: 'src/components/aaa/bbb',
167
+ },
168
+ {
169
+ fixtureName: 'base-url-dot-src-slash',
170
+ expected: 'src/components/aaa/bbb',
171
+ },
172
+ ] as const satisfies readonly Readonly<{
173
+ fixtureName: FixtureName;
174
+ expected: string;
175
+ }>[];
176
+
177
+ describe('resolveImportPath', () => {
178
+ test('should resolve relative path', () => {
179
+ expect(
180
+ resolveImportPath('../../components/ui/Text', 'src/pages/aaa/bbb.ts', {}),
181
+ ).toBe('src/components/ui/Text');
182
+ });
183
+
184
+ test('should not resolve relative path if relativeFilePath is empty', () => {
185
+ expect(resolveImportPath('../../components/ui/Text', undefined, {})).toBe(
186
+ '../../components/ui/Text',
187
+ );
188
+ });
189
+
190
+ test('should do nothing if tsconfig.json does not exist', () => {
191
+ useFixture(undefined);
192
+ expect(resolveImportPath('components/aaa/bbb', undefined, {})).toBe(
193
+ 'components/aaa/bbb',
194
+ );
195
+ });
196
+
197
+ test('should do nothing if no paths setting', () => {
198
+ useFixture('no-paths');
199
+ expect(resolveImportPath('components/aaa/bbb', undefined, {})).toBe(
200
+ 'components/aaa/bbb',
201
+ );
202
+ useFixture(undefined);
203
+ });
204
+
205
+ test.each(aliasCases)(
206
+ 'alias $label',
207
+ ({ importPath, expected }: Readonly<(typeof aliasCases)[number]>) => {
208
+ useFixture('paths-basic');
209
+ expect(resolveImportPath('components/aaa/bbb', undefined, {})).toBe(
210
+ 'components/aaa/bbb',
211
+ );
212
+ expect(resolveImportPath(importPath, undefined, {})).toBe(expected);
213
+ useFixture(undefined);
214
+ },
215
+ );
216
+
217
+ test.each(baseUrlCases)(
218
+ 'baseUrl $fixtureName',
219
+ ({ fixtureName, expected }: Readonly<(typeof baseUrlCases)[number]>) => {
220
+ useFixture(fixtureName);
221
+ expect(resolveImportPath('components/aaa/bbb', undefined, {})).toBe(
222
+ 'components/aaa/bbb',
223
+ );
224
+ expect(resolveImportPath('@/components/aaa/bbb', undefined, {})).toBe(
225
+ expected,
226
+ );
227
+ useFixture(undefined);
228
+ },
229
+ );
230
+
231
+ test('should resolve path alias with specified index in pathIndexMap', () => {
232
+ useFixture('paths-multiple');
233
+ expect(
234
+ resolveImportPath('@/components/aaa/bbb', undefined, {
235
+ '@/components/*': 1,
236
+ }),
237
+ ).toBe('src/alternativeComponents/aaa/bbb');
238
+ useFixture(undefined);
239
+ });
240
+
241
+ test('should resolve path alias with default index when specified index does not exist', () => {
242
+ useFixture('paths-multiple');
243
+ expect(
244
+ resolveImportPath('@/components/aaa/bbb', undefined, {
245
+ '@/components/*': 5,
246
+ }),
247
+ ).toBe('src/components/aaa/bbb');
248
+ useFixture(undefined);
249
+ });
250
+
251
+ test('should resolve path alias with default index when pathIndexMap is empty', () => {
252
+ useFixture('paths-multiple');
253
+ expect(resolveImportPath('@/components/aaa/bbb', undefined, {})).toBe(
254
+ 'src/components/aaa/bbb',
255
+ );
256
+ useFixture(undefined);
257
+ });
258
+ });
@@ -0,0 +1,6 @@
1
+ import { type ESLintPlugin } from '../../../types/index.mjs';
2
+ import { strictDependenciesRule } from './strict-dependencies.mjs';
3
+
4
+ export const strictDependenciesRules = {
5
+ 'strict-dependencies': strictDependenciesRule,
6
+ } as const satisfies ESLintPlugin['rules'];
@@ -0,0 +1,201 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ import isGlob from 'is-glob';
3
+ import mm from 'micromatch';
4
+ import * as path from 'node:path';
5
+ import { Arr, castDeepMutable, hasKey, isNotUndefined } from 'ts-data-forge';
6
+ import { resolveImportPath } from './resolve-import-path.mjs';
7
+
8
+ // Forked from https://github.com/knowledge-work/eslint-plugin-strict-dependencies/blob/v1.3.27/strict-dependencies/index.js
9
+
10
+ // Fork 時の変更点メモ
11
+ // - normalize は path.normalize に変更(厳密には挙動が異なるが、この eslint ルールの用途的には問題ないはず)
12
+ // - MessageId を使ってレポートするように変更(context.report に 2引数渡していた形式を object を渡すように変更)
13
+
14
+ type RuleOptions = readonly [Dependencies] | readonly [Dependencies, Options];
15
+
16
+ type Dependencies = readonly Readonly<{
17
+ module: string;
18
+ allowReferenceFrom: readonly string[];
19
+ allowSameModule?: boolean;
20
+ targetMembers?: readonly string[];
21
+ excludeTypeImportChecks?: boolean;
22
+ }>[];
23
+
24
+ type Options = Readonly<{
25
+ resolveRelativeImport?: boolean;
26
+ pathIndexMap?: ReadonlyRecord<string, number>;
27
+ }>;
28
+
29
+ type MessageId = 'forbidden-import-specifier' | 'forbidden-import';
30
+
31
+ export const strictDependenciesRule: TSESLint.RuleModule<
32
+ MessageId,
33
+ RuleOptions
34
+ > = {
35
+ meta: {
36
+ type: 'suggestion',
37
+ messages: {
38
+ 'forbidden-import-specifier':
39
+ "import specifier '{{specifiers}}' is not allowed from {{from}}.",
40
+ 'forbidden-import':
41
+ "import '{{importPath}}' is not allowed from {{from}}.",
42
+ },
43
+ schema: [
44
+ {
45
+ type: 'array',
46
+ items: {
47
+ type: 'object',
48
+ additionalProperties: false,
49
+ required: ['module', 'allowReferenceFrom'],
50
+ properties: {
51
+ module: {
52
+ type: 'string',
53
+ },
54
+ allowReferenceFrom: {
55
+ type: 'array',
56
+ items: {
57
+ type: 'string',
58
+ },
59
+ },
60
+ allowSameModule: {
61
+ type: 'boolean',
62
+ },
63
+ targetMembers: {
64
+ type: 'array',
65
+ items: {
66
+ type: 'string',
67
+ },
68
+ },
69
+ excludeTypeImportChecks: {
70
+ type: 'boolean',
71
+ },
72
+ },
73
+ },
74
+ },
75
+ {
76
+ type: 'object',
77
+ additionalProperties: false,
78
+ properties: {
79
+ resolveRelativeImport: {
80
+ type: 'boolean',
81
+ },
82
+ pathIndexMap: {
83
+ type: 'object',
84
+ additionalProperties: {
85
+ type: 'number',
86
+ },
87
+ },
88
+ },
89
+ },
90
+ ],
91
+ },
92
+
93
+ create: (context) => {
94
+ const ruleOptions: RuleOptions = context.options;
95
+
96
+ const dependencies: Dependencies = ruleOptions[0];
97
+
98
+ const options: Options = Arr.isArrayAtLeastLength(ruleOptions, 2)
99
+ ? ruleOptions[1]
100
+ : {};
101
+
102
+ const resolveRelativeImport = options.resolveRelativeImport;
103
+ const pathIndexMap = options.pathIndexMap ?? {};
104
+
105
+ return {
106
+ ImportDeclaration: (node: DeepReadonly<TSESTree.ImportDeclaration>) => {
107
+ const fileFullPath = context.filename;
108
+ const relativeFilePath = path.normalize(
109
+ path.relative(process.cwd(), fileFullPath),
110
+ );
111
+ const importPath = resolveImportPath(
112
+ node.source.value,
113
+ resolveRelativeImport === true ? relativeFilePath : undefined,
114
+ pathIndexMap,
115
+ );
116
+
117
+ const importedModules: readonly string[] = (
118
+ node.specifiers satisfies DeepReadonly<
119
+ (
120
+ | TSESTree.ImportDefaultSpecifier
121
+ | TSESTree.ImportNamespaceSpecifier
122
+ | TSESTree.ImportSpecifier
123
+ )[]
124
+ >
125
+ )
126
+ .filter((spec) => hasKey(spec, 'imported')) // NOTE: ImportSpecifierの場合はimportedが存在する
127
+ .map((spec: DeepReadonly<TSESTree.ImportSpecifier>) =>
128
+ hasKey(spec.imported, 'name') ? spec.imported.name : undefined,
129
+ )
130
+ .filter(isNotUndefined);
131
+
132
+ for (const dependency of dependencies) {
133
+ // そもそもmoduleがimportPathと一致していない場合は必ず報告しない
134
+ if (!isMatch(importPath, dependency.module)) continue;
135
+
136
+ /**
137
+ * 1. 参照元チェックをしてAllowであればそこで処理を終了する
138
+ * 2. targetMembersが指定されていれば、targetMembersと実際にimportしているモジュールを比較して含まれていればエラーレポートして処理を終了する
139
+ * 3. targetMembersが指定されていない場合は、エラー扱いなのでエラーレポートして処理を終了する
140
+ */
141
+ const isAllowedByPath =
142
+ // 参照元が許可されている
143
+ dependency.allowReferenceFrom.some((allowPath) =>
144
+ isMatch(relativeFilePath, allowPath),
145
+ ) || // または同一モジュール間の参照が許可されている場合
146
+ (dependency.allowSameModule === true &&
147
+ isMatch(relativeFilePath, dependency.module)) ||
148
+ // または明示的に対象外としたtype importである場合
149
+ // FIXME: importKind という key は node に存在しないため修正が必要
150
+ (dependency.excludeTypeImportChecks === true &&
151
+ node.importKind === 'type');
152
+
153
+ if (isAllowedByPath) continue;
154
+
155
+ if (dependency.targetMembers !== undefined) {
156
+ const commonImportedList = getCommonElements<string>(
157
+ dependency.targetMembers,
158
+ importedModules,
159
+ );
160
+ if (commonImportedList.length > 0) {
161
+ context.report({
162
+ node: castDeepMutable(node),
163
+ messageId: 'forbidden-import-specifier',
164
+ data: {
165
+ specifiers: commonImportedList.join(', '),
166
+ from: relativeFilePath,
167
+ },
168
+ });
169
+ }
170
+ continue;
171
+ }
172
+
173
+ context.report({
174
+ node: castDeepMutable(node),
175
+ messageId: 'forbidden-import',
176
+ data: {
177
+ importPath,
178
+ from: relativeFilePath,
179
+ },
180
+ });
181
+ }
182
+ },
183
+ };
184
+ },
185
+ defaultOptions: [[]],
186
+ };
187
+
188
+ /**
189
+ * pathのmatcher。
190
+ * eslintrcで設定できる値は以下のケースを扱う
191
+ * - globパターン指定
192
+ * - globパターン以外の場合 => 前方部分一致
193
+ */
194
+ const isMatch = (str: string, pattern: string): boolean =>
195
+ isGlob(pattern) ? mm.isMatch(str, pattern) : str.startsWith(pattern);
196
+
197
+ // importedされた値・型名もLintのターゲットに入っている場合の検出処理
198
+ const getCommonElements = <T,>(
199
+ arrA: readonly T[],
200
+ arrB: readonly T[],
201
+ ): readonly T[] => arrA.filter((element) => arrB.includes(element));
@@ -0,0 +1,113 @@
1
+ import parserModule from '@typescript-eslint/parser';
2
+ import { TSESLint } from '@typescript-eslint/utils';
3
+ import * as path from 'node:path';
4
+ import { describe, expect, test } from 'vitest';
5
+ import { strictDependenciesRule } from './strict-dependencies.mjs';
6
+
7
+ // FIXME: https://github.com/knowledge-work/eslint-plugin-strict-dependencies/tree/v1.3.27 のテストをコピーしてきて
8
+ // Codex でテスト・型チェックが通るようにさせただけなので、意味の無いテストになっていないかチェックして修正する
9
+
10
+ type RuleOptions = Parameters<
11
+ (typeof strictDependenciesRule)['create']
12
+ >[0]['options'];
13
+
14
+ const pluginName = 'test-strict-dependencies';
15
+ const ruleId = 'strict-dependencies';
16
+
17
+ const fromRoot = (relativePath: string): string =>
18
+ path.join(process.cwd(), relativePath);
19
+
20
+ const normalize = (value: string): string => path.normalize(value);
21
+
22
+ const allowPagesOptions: RuleOptions = [
23
+ [
24
+ {
25
+ module: 'src/components/ui',
26
+ allowReferenceFrom: ['src/components/pages'],
27
+ allowSameModule: true,
28
+ },
29
+ ],
30
+ ] as const;
31
+
32
+ const targetMembersOptions: RuleOptions = [
33
+ [
34
+ {
35
+ module: 'src/components/ui',
36
+ allowReferenceFrom: ['src/pages/**'],
37
+ targetMembers: ['Text'],
38
+ },
39
+ ],
40
+ ] as const;
41
+
42
+ const runRule = (
43
+ code: string,
44
+ filename: string,
45
+ options: RuleOptions,
46
+ ): readonly TSESLint.Linter.LintMessage[] => {
47
+ const linter = new TSESLint.Linter();
48
+
49
+ const config: TSESLint.Linter.ConfigType = [
50
+ {
51
+ files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
52
+ languageOptions: {
53
+ parser: parserModule,
54
+ parserOptions: {
55
+ ecmaVersion: 2022,
56
+ sourceType: 'module',
57
+ },
58
+ },
59
+ plugins: {
60
+ [pluginName]: {
61
+ rules: {
62
+ [ruleId]: strictDependenciesRule,
63
+ },
64
+ },
65
+ },
66
+ rules: {
67
+ [`${pluginName}/${ruleId}`]: ['error', ...options],
68
+ },
69
+ },
70
+ ];
71
+
72
+ return linter.verify(code, config, filename);
73
+ };
74
+
75
+ describe('strictDependenciesRule', () => {
76
+ test('allows import from permitted module', () => {
77
+ const messages = runRule(
78
+ "import { Text } from 'src/components/ui/Text';",
79
+ fromRoot('src/components/pages/allowed.ts'),
80
+ allowPagesOptions,
81
+ );
82
+
83
+ expect(messages).toHaveLength(0);
84
+ });
85
+
86
+ test('reports forbidden import when module is not allowed', () => {
87
+ const messages = runRule(
88
+ "import { Text } from 'src/components/ui/Text';",
89
+ fromRoot('src/components/test/aaa.ts'),
90
+ allowPagesOptions,
91
+ );
92
+
93
+ expect(messages).toHaveLength(1);
94
+ expect(messages[0]?.messageId).toBe('forbidden-import');
95
+ expect(messages[0]?.message).toBe(
96
+ `import '${normalize('src/components/ui/Text')}' is not allowed from ${normalize('src/components/test/aaa.ts')}.`,
97
+ );
98
+ });
99
+
100
+ test('reports forbidden specifier when target member is restricted', () => {
101
+ const messages = runRule(
102
+ "import { Text, TextProps } from 'src/components/ui/Text';",
103
+ fromRoot('src/components/button.tsx'),
104
+ targetMembersOptions,
105
+ );
106
+
107
+ expect(messages).toHaveLength(1);
108
+ expect(messages[0]?.messageId).toBe('forbidden-import-specifier');
109
+ expect(messages[0]?.message).toBe(
110
+ `import specifier 'Text' is not allowed from ${normalize('src/components/button.tsx')}.`,
111
+ );
112
+ });
113
+ });
@@ -1,18 +1 @@
1
- export * from './common.mjs';
2
- export * from './fp-ts.mjs';
3
- export * from './no-enums.mjs';
4
- export * from './no-hidden-type-assertions.mjs';
5
- export * from './no-nested-fp-ts-effects.mjs';
6
- export * from './no-partial-array-reduce.mjs';
7
- export * from './no-partial-division.mjs';
8
- export * from './no-partial-string-normalize.mjs';
9
- export * from './no-partial-url-constructor.mjs';
10
- export * from './no-premature-fp-ts-effects.mjs';
11
- export * from './no-unsafe-enum-assignment.mjs';
12
- export * from './no-unsafe-mutable-readonly-assignment.mjs';
13
- export * from './no-unsafe-optional-property-assignment.mjs';
14
- export * from './no-unsafe-readonly-mutable-assignment.mjs';
15
- export * from './no-unsafe-type-assertion.mjs';
16
- export * from './require-strict-mode.mjs';
17
1
  export * from './rules.mjs';
18
- export * from './unsafe-assignment-rule.mjs';
@@ -1,2 +1 @@
1
- export * from './import-star.mjs';
2
1
  export * from './rules.mjs';
@@ -14,6 +14,9 @@ export const eslintImportsRules = {
14
14
  'error',
15
15
  {
16
16
  allow: [
17
+ '*/index.js',
18
+ '*/index.mjs',
19
+ '*/index.cjs',
17
20
  'rxjs/operators',
18
21
  'solid-js/web',
19
22
  '@testing-library/jest-dom/**',
@@ -28,8 +31,6 @@ export const eslintImportsRules = {
28
31
  '@fontsource/**',
29
32
  'resize-observer/lib/ResizeObserverEntry',
30
33
  'vitest/config',
31
- '*/index.mjs',
32
- '*/index.js',
33
34
  'zx/globals',
34
35
  ],
35
36
  },
@@ -249,6 +249,7 @@ export const typescriptEslintRules = {
249
249
  {
250
250
  from: 'lib',
251
251
  name: [
252
+ 'RegExp',
252
253
  'AnimationEvent',
253
254
  'ClipboardEvent',
254
255
  'CompositionEvent',
@@ -1,4 +1,4 @@
1
- import { type FlatConfig } from './index.mjs';
1
+ import { type FlatConfig } from './flat-config.mjs';
2
2
 
3
3
  export const defineConfig = <const TConfig extends readonly FlatConfig[]>(
4
4
  config: TConfig,