@atlaskit/eslint-plugin-platform 2.7.1 → 2.8.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 +14 -0
- package/dist/cjs/index.js +17 -9
- package/dist/cjs/rules/constants.js +1 -1
- package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +5 -5
- package/dist/cjs/rules/ensure-no-private-dependencies/index.js +48 -66
- package/dist/cjs/rules/feature-gating/inline-usage/index.js +14 -3
- package/dist/cjs/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/cjs/rules/feature-gating/no-module-level-eval/index.js +1 -1
- package/dist/cjs/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
- package/dist/cjs/rules/feature-gating/no-preconditioning/index.js +4 -1
- package/dist/cjs/rules/feature-gating/prefer-fg/index.js +1 -1
- package/dist/cjs/rules/feature-gating/static-feature-flags/index.js +2 -2
- package/dist/cjs/rules/feature-gating/use-recommended-utils/index.js +1 -1
- package/dist/cjs/rules/feature-gating/valid-gate-name/index.js +60 -0
- package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +871 -0
- package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +1384 -0
- package/dist/cjs/rules/import/no-conversation-assistant-barrel-imports/index.js +43 -0
- package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +1401 -0
- package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +777 -0
- package/dist/cjs/rules/import/shared/barrel-parsing.js +511 -0
- package/dist/cjs/rules/import/shared/file-system.js +186 -0
- package/dist/cjs/rules/import/shared/jest-utils.js +191 -0
- package/dist/cjs/rules/import/shared/package-registry.js +263 -0
- package/dist/cjs/rules/import/shared/package-resolution.js +185 -0
- package/dist/cjs/rules/import/shared/perf.js +89 -0
- package/dist/cjs/rules/import/shared/types.js +67 -0
- package/dist/cjs/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
- package/dist/cjs/rules/no-sparse-checkout/index.js +1 -1
- package/dist/cjs/rules/prefer-crypto-random-uuid/index.js +87 -0
- package/dist/cjs/rules/use-entrypoints-in-examples/index.js +1 -1
- package/dist/es2019/index.js +17 -9
- package/dist/es2019/rules/constants.js +1 -1
- package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +5 -5
- package/dist/es2019/rules/ensure-no-private-dependencies/index.js +10 -9
- package/dist/es2019/rules/feature-gating/inline-usage/index.js +14 -3
- package/dist/es2019/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/es2019/rules/feature-gating/no-module-level-eval/index.js +1 -1
- package/dist/es2019/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
- package/dist/es2019/rules/feature-gating/no-preconditioning/index.js +4 -1
- package/dist/es2019/rules/feature-gating/prefer-fg/index.js +1 -1
- package/dist/es2019/rules/feature-gating/static-feature-flags/index.js +2 -2
- package/dist/es2019/rules/feature-gating/use-recommended-utils/index.js +1 -1
- package/dist/es2019/rules/feature-gating/valid-gate-name/index.js +52 -0
- package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +801 -0
- package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +1113 -0
- package/dist/es2019/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
- package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +1179 -0
- package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +738 -0
- package/dist/es2019/rules/import/shared/barrel-parsing.js +433 -0
- package/dist/es2019/rules/import/shared/file-system.js +174 -0
- package/dist/es2019/rules/import/shared/jest-utils.js +159 -0
- package/dist/es2019/rules/import/shared/package-registry.js +240 -0
- package/dist/es2019/rules/import/shared/package-resolution.js +161 -0
- package/dist/es2019/rules/import/shared/perf.js +83 -0
- package/dist/es2019/rules/import/shared/types.js +57 -0
- package/dist/es2019/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
- package/dist/es2019/rules/no-sparse-checkout/index.js +1 -1
- package/dist/es2019/rules/prefer-crypto-random-uuid/index.js +81 -0
- package/dist/es2019/rules/use-entrypoints-in-examples/index.js +1 -1
- package/dist/esm/index.js +17 -9
- package/dist/esm/rules/constants.js +1 -1
- package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +5 -5
- package/dist/esm/rules/ensure-no-private-dependencies/index.js +48 -65
- package/dist/esm/rules/feature-gating/inline-usage/index.js +14 -3
- package/dist/esm/rules/feature-gating/no-alias/index.js +1 -1
- package/dist/esm/rules/feature-gating/no-module-level-eval/index.js +1 -1
- package/dist/esm/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
- package/dist/esm/rules/feature-gating/no-preconditioning/index.js +4 -1
- package/dist/esm/rules/feature-gating/prefer-fg/index.js +1 -1
- package/dist/esm/rules/feature-gating/static-feature-flags/index.js +2 -2
- package/dist/esm/rules/feature-gating/use-recommended-utils/index.js +1 -1
- package/dist/esm/rules/feature-gating/valid-gate-name/index.js +53 -0
- package/dist/esm/rules/import/no-barrel-entry-imports/index.js +864 -0
- package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +1375 -0
- package/dist/esm/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
- package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +1391 -0
- package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +770 -0
- package/dist/esm/rules/import/shared/barrel-parsing.js +500 -0
- package/dist/esm/rules/import/shared/file-system.js +176 -0
- package/dist/esm/rules/import/shared/jest-utils.js +179 -0
- package/dist/esm/rules/import/shared/package-registry.js +256 -0
- package/dist/esm/rules/import/shared/package-resolution.js +175 -0
- package/dist/esm/rules/import/shared/perf.js +80 -0
- package/dist/esm/rules/import/shared/types.js +61 -0
- package/dist/esm/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
- package/dist/esm/rules/no-sparse-checkout/index.js +1 -1
- package/dist/esm/rules/prefer-crypto-random-uuid/index.js +81 -0
- package/dist/esm/rules/use-entrypoints-in-examples/index.js +1 -1
- package/dist/types/index.d.ts +18 -16
- package/dist/types/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
- package/dist/types/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
- package/dist/types/rules/import/no-conversation-assistant-barrel-imports/index.d.ts +3 -0
- package/dist/types/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
- package/dist/types/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
- package/dist/types/rules/import/shared/barrel-parsing.d.ts +30 -0
- package/dist/types/rules/import/shared/file-system.d.ts +38 -0
- package/dist/types/rules/import/shared/jest-utils.d.ts +47 -0
- package/dist/types/rules/import/shared/package-registry.d.ts +26 -0
- package/dist/types/rules/import/shared/package-resolution.d.ts +38 -0
- package/dist/types/rules/import/shared/perf.d.ts +13 -0
- package/dist/types/rules/import/shared/types.d.ts +131 -0
- package/dist/types/rules/prefer-crypto-random-uuid/index.d.ts +3 -0
- package/dist/types-ts4.5/index.d.ts +18 -28
- package/dist/types-ts4.5/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
- package/dist/types-ts4.5/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
- package/dist/types-ts4.5/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
- package/dist/types-ts4.5/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
- package/dist/types-ts4.5/rules/import/shared/barrel-parsing.d.ts +30 -0
- package/dist/types-ts4.5/rules/import/shared/file-system.d.ts +38 -0
- package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +47 -0
- package/dist/types-ts4.5/rules/import/shared/package-registry.d.ts +26 -0
- package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +38 -0
- package/dist/types-ts4.5/rules/import/shared/perf.d.ts +13 -0
- package/dist/types-ts4.5/rules/import/shared/types.d.ts +131 -0
- package/package.json +4 -5
- package/dist/cjs/rules/ensure-feature-flag-prefix/index.js +0 -75
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -158
- package/dist/es2019/rules/ensure-feature-flag-prefix/index.js +0 -65
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -146
- package/dist/esm/rules/ensure-feature-flag-prefix/index.js +0 -69
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -151
- /package/dist/types/rules/{ensure-native-and-af-exports-synced → feature-gating/valid-gate-name}/index.d.ts +0 -0
- /package/dist/types-ts4.5/rules/{ensure-feature-flag-prefix → feature-gating/valid-gate-name}/index.d.ts +0 -0
- /package/dist/types-ts4.5/rules/{ensure-native-and-af-exports-synced → import/no-conversation-assistant-barrel-imports}/index.d.ts +0 -0
- /package/dist/{types/rules/ensure-feature-flag-prefix → types-ts4.5/rules/prefer-crypto-random-uuid}/index.d.ts +0 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { dirname, relative } from 'path';
|
|
2
|
+
import { hasReExportsFromOtherFiles, parseBarrelExports as parseBarrelExportsFromShared } from '../shared/barrel-parsing';
|
|
3
|
+
import { findWorkspaceRoot, isRelativeImport, resolveImportPath } from '../shared/file-system';
|
|
4
|
+
import { findPackageInRegistry } from '../shared/package-registry';
|
|
5
|
+
import { findExportForSourceFile, parsePackageExports } from '../shared/package-resolution';
|
|
6
|
+
import { realFileSystem } from '../shared/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Specifier with additional metadata for tracking during barrel file resolution.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Context for barrel file resolution, returned by resolveBarrelFileContext.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Result of collecting specifiers by their source file.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Cache per source package name to avoid repeated exports parsing during a single lint run.
|
|
21
|
+
// This is keyed by fs instance to avoid test pollution.
|
|
22
|
+
const sourcePackageExportsMapsByFs = new WeakMap();
|
|
23
|
+
function getSourcePackageExportsMaps(fs) {
|
|
24
|
+
let map = sourcePackageExportsMapsByFs.get(fs);
|
|
25
|
+
if (!map) {
|
|
26
|
+
map = new Map();
|
|
27
|
+
sourcePackageExportsMapsByFs.set(fs, map);
|
|
28
|
+
}
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
31
|
+
function getImportedName(spec) {
|
|
32
|
+
const imported = spec.imported;
|
|
33
|
+
return imported.type === 'Identifier' ? imported.name : String(imported.value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the exported name from an ExportSpecifier, handling both Identifier and Literal
|
|
38
|
+
*/
|
|
39
|
+
function getExportedName(node) {
|
|
40
|
+
return node.type === 'Identifier' ? node.name : String(node.value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert absolute file path back to relative import path
|
|
45
|
+
*/
|
|
46
|
+
function getImportPathForSourceFile({
|
|
47
|
+
sourceFilePath,
|
|
48
|
+
basedir,
|
|
49
|
+
originalImportPath,
|
|
50
|
+
exportInfo,
|
|
51
|
+
workspaceRoot,
|
|
52
|
+
fs
|
|
53
|
+
}) {
|
|
54
|
+
var _exportInfo$crossPack;
|
|
55
|
+
const crossPackageName = exportInfo === null || exportInfo === void 0 ? void 0 : (_exportInfo$crossPack = exportInfo.crossPackageSource) === null || _exportInfo$crossPack === void 0 ? void 0 : _exportInfo$crossPack.packageName;
|
|
56
|
+
if (crossPackageName) {
|
|
57
|
+
const sourcePackageExportsMaps = getSourcePackageExportsMaps(fs);
|
|
58
|
+
let exportsMap = sourcePackageExportsMaps.get(crossPackageName);
|
|
59
|
+
if (!exportsMap) {
|
|
60
|
+
const pkgDir = findPackageInRegistry({
|
|
61
|
+
packageName: crossPackageName,
|
|
62
|
+
workspaceRoot,
|
|
63
|
+
fs
|
|
64
|
+
});
|
|
65
|
+
if (pkgDir) {
|
|
66
|
+
exportsMap = parsePackageExports({
|
|
67
|
+
packageDir: pkgDir,
|
|
68
|
+
fs
|
|
69
|
+
});
|
|
70
|
+
sourcePackageExportsMaps.set(crossPackageName, exportsMap);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const targetExportPath = exportsMap ? findExportForSourceFile({
|
|
74
|
+
sourceFilePath,
|
|
75
|
+
exportsMap
|
|
76
|
+
}) : null;
|
|
77
|
+
return targetExportPath ? crossPackageName + targetExportPath.slice(1) : crossPackageName;
|
|
78
|
+
}
|
|
79
|
+
return getRelativeImportPath({
|
|
80
|
+
basedir,
|
|
81
|
+
absolutePath: sourceFilePath,
|
|
82
|
+
originalImportPath
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function getRelativeImportPath({
|
|
86
|
+
basedir,
|
|
87
|
+
absolutePath,
|
|
88
|
+
originalImportPath
|
|
89
|
+
}) {
|
|
90
|
+
if (!absolutePath.startsWith('/') && !absolutePath.match(/^[a-zA-Z]:/)) {
|
|
91
|
+
return absolutePath;
|
|
92
|
+
}
|
|
93
|
+
let relativePath = relative(basedir, absolutePath);
|
|
94
|
+
// Normalize to use forward slashes
|
|
95
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
96
|
+
|
|
97
|
+
// Check for extension in original path
|
|
98
|
+
const extMatch = originalImportPath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
|
|
99
|
+
const originalExt = extMatch ? extMatch[0] : '';
|
|
100
|
+
|
|
101
|
+
// Get extension from the resolved absolute path
|
|
102
|
+
const targetExtMatch = absolutePath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
|
|
103
|
+
const targetExt = targetExtMatch ? targetExtMatch[0] : '';
|
|
104
|
+
|
|
105
|
+
// Remove file extension from the target path
|
|
106
|
+
relativePath = relativePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
|
|
107
|
+
|
|
108
|
+
// If original had extension, append it
|
|
109
|
+
if (originalExt) {
|
|
110
|
+
// If original was a TypeScript source extension, use the actual target extension
|
|
111
|
+
if (['.ts', '.tsx'].includes(originalExt) && targetExt) {
|
|
112
|
+
relativePath += targetExt;
|
|
113
|
+
} else {
|
|
114
|
+
relativePath += originalExt;
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Remove /index suffix only if no extension was present
|
|
118
|
+
if (relativePath.endsWith('/index')) {
|
|
119
|
+
relativePath = relativePath.slice(0, -6);
|
|
120
|
+
} else if (relativePath === 'index') {
|
|
121
|
+
relativePath = '.';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Ensure it starts with .. or .
|
|
126
|
+
if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
|
|
127
|
+
relativePath = './' + relativePath;
|
|
128
|
+
}
|
|
129
|
+
return relativePath;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build an import statement for a set of specifiers
|
|
134
|
+
*/
|
|
135
|
+
function buildImportStatement({
|
|
136
|
+
specs,
|
|
137
|
+
path,
|
|
138
|
+
quoteChar,
|
|
139
|
+
isTypeImport = false
|
|
140
|
+
}) {
|
|
141
|
+
const importNames = specs.map(spec => {
|
|
142
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
143
|
+
return spec.local.name;
|
|
144
|
+
} else if (spec.type === 'ImportSpecifier') {
|
|
145
|
+
const imported = getImportedName(spec);
|
|
146
|
+
const local = spec.local.name;
|
|
147
|
+
const isInlineType = spec.importKind === 'type' && !isTypeImport;
|
|
148
|
+
const prefix = isInlineType ? 'type ' : '';
|
|
149
|
+
return imported === local ? `${prefix}${imported}` : `${prefix}${imported} as ${local}`;
|
|
150
|
+
}
|
|
151
|
+
return '';
|
|
152
|
+
}).filter(name => name.length > 0);
|
|
153
|
+
if (importNames.length === 0) {
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
const typeKeyword = isTypeImport ? 'type ' : '';
|
|
157
|
+
const hasDefault = specs.some(spec => spec.type === 'ImportDefaultSpecifier');
|
|
158
|
+
const hasNamed = specs.some(spec => spec.type === 'ImportSpecifier');
|
|
159
|
+
if (hasDefault && hasNamed) {
|
|
160
|
+
var _specs$find;
|
|
161
|
+
const defaultName = (_specs$find = specs.find(spec => spec.type === 'ImportDefaultSpecifier')) === null || _specs$find === void 0 ? void 0 : _specs$find.local.name;
|
|
162
|
+
const namedImports = specs.filter(spec => spec.type === 'ImportSpecifier').map(spec => {
|
|
163
|
+
const imported = getImportedName(spec);
|
|
164
|
+
const local = spec.local.name;
|
|
165
|
+
const isInlineType = spec.importKind === 'type' && !isTypeImport;
|
|
166
|
+
const prefix = isInlineType ? 'type ' : '';
|
|
167
|
+
return imported === local ? `${prefix}${imported}` : `${prefix}${imported} as ${local}`;
|
|
168
|
+
}).join(', ');
|
|
169
|
+
return `import ${typeKeyword}${defaultName}, { ${namedImports} } from ${quoteChar}${path}${quoteChar};`;
|
|
170
|
+
} else if (hasDefault) {
|
|
171
|
+
var _specs$find2;
|
|
172
|
+
const defaultName = (_specs$find2 = specs.find(spec => spec.type === 'ImportDefaultSpecifier')) === null || _specs$find2 === void 0 ? void 0 : _specs$find2.local.name;
|
|
173
|
+
return `import ${typeKeyword}${defaultName} from ${quoteChar}${path}${quoteChar};`;
|
|
174
|
+
} else {
|
|
175
|
+
return `import ${typeKeyword}{ ${importNames.join(', ')} } from ${quoteChar}${path}${quoteChar};`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Build an export statement for a set of specifiers
|
|
181
|
+
*/
|
|
182
|
+
function buildExportStatement({
|
|
183
|
+
specs,
|
|
184
|
+
path,
|
|
185
|
+
quoteChar,
|
|
186
|
+
isTypeExport = false
|
|
187
|
+
}) {
|
|
188
|
+
const exportNames = specs.map(spec => {
|
|
189
|
+
const isInlineType = spec.kind === 'type' && !isTypeExport;
|
|
190
|
+
const prefix = isInlineType ? 'type ' : '';
|
|
191
|
+
return spec.nameInSource === spec.nameInLocal ? `${prefix}${spec.nameInSource}` : `${prefix}${spec.nameInSource} as ${spec.nameInLocal}`;
|
|
192
|
+
}).filter(name => name.length > 0);
|
|
193
|
+
if (exportNames.length === 0) {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
const typeKeyword = isTypeExport ? 'type ' : '';
|
|
197
|
+
return `export ${typeKeyword}{ ${exportNames.join(', ')} } from ${quoteChar}${path}${quoteChar};`;
|
|
198
|
+
}
|
|
199
|
+
const ruleMeta = {
|
|
200
|
+
type: 'problem',
|
|
201
|
+
docs: {
|
|
202
|
+
description: 'Warn when imports are from a relative barrel file and provide an auto-fix to split them into specific imports.',
|
|
203
|
+
category: 'Best Practices',
|
|
204
|
+
recommended: false
|
|
205
|
+
},
|
|
206
|
+
fixable: 'code',
|
|
207
|
+
messages: {
|
|
208
|
+
barrelImport: "Import from barrel file '{{path}}' should be split into more specific imports. Use auto-fix to resolve."
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Resolve barrel file context from an import/export node.
|
|
214
|
+
* Returns null if the node should not be checked (not relative, not resolvable, not a barrel).
|
|
215
|
+
*/
|
|
216
|
+
function resolveBarrelFileContext({
|
|
217
|
+
node,
|
|
218
|
+
context,
|
|
219
|
+
fs
|
|
220
|
+
}) {
|
|
221
|
+
if (!node.source) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const importPath = node.source.value;
|
|
225
|
+
if (!isRelativeImport(importPath)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const basedir = dirname(context.filename);
|
|
229
|
+
const resolvedPath = resolveImportPath({
|
|
230
|
+
basedir,
|
|
231
|
+
importPath,
|
|
232
|
+
fs
|
|
233
|
+
});
|
|
234
|
+
if (!resolvedPath) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const workspaceRoot = findWorkspaceRoot({
|
|
238
|
+
startPath: basedir,
|
|
239
|
+
fs
|
|
240
|
+
});
|
|
241
|
+
const exportMap = parseBarrelExportsFromShared({
|
|
242
|
+
barrelFilePath: resolvedPath,
|
|
243
|
+
workspaceRoot,
|
|
244
|
+
fs
|
|
245
|
+
});
|
|
246
|
+
if (exportMap.size === 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
if (!hasReExportsFromOtherFiles({
|
|
250
|
+
exportMap,
|
|
251
|
+
sourceFilePath: resolvedPath
|
|
252
|
+
})) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
importPath,
|
|
257
|
+
basedir,
|
|
258
|
+
resolvedPath,
|
|
259
|
+
workspaceRoot,
|
|
260
|
+
exportMap
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Collect specifiers grouped by their source file.
|
|
266
|
+
*/
|
|
267
|
+
function collectSpecifiersBySource({
|
|
268
|
+
specifiers,
|
|
269
|
+
node,
|
|
270
|
+
exportMap
|
|
271
|
+
}) {
|
|
272
|
+
const importsBySource = new Map();
|
|
273
|
+
const unmappedSpecifiers = [];
|
|
274
|
+
let hasNamespaceImport = false;
|
|
275
|
+
for (const spec of specifiers) {
|
|
276
|
+
let nameInSource;
|
|
277
|
+
let nameInLocal;
|
|
278
|
+
let kind = 'value';
|
|
279
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
280
|
+
hasNamespaceImport = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
284
|
+
nameInSource = 'default';
|
|
285
|
+
nameInLocal = spec.local.name;
|
|
286
|
+
} else if (spec.type === 'ImportSpecifier') {
|
|
287
|
+
nameInSource = getImportedName(spec);
|
|
288
|
+
nameInLocal = spec.local.name;
|
|
289
|
+
const parentImportKind = node.type === 'ImportDeclaration' ? node.importKind : undefined;
|
|
290
|
+
kind = parentImportKind === 'type' || spec.importKind === 'type' ? 'type' : 'value';
|
|
291
|
+
} else if (spec.type === 'ExportSpecifier') {
|
|
292
|
+
nameInSource = spec.local.name;
|
|
293
|
+
nameInLocal = spec.exported.name;
|
|
294
|
+
const parentExportKind = node.type === 'ExportNamedDeclaration' ? node.exportKind : undefined;
|
|
295
|
+
kind = parentExportKind === 'type' || spec.exportKind === 'type' ? 'type' : 'value';
|
|
296
|
+
} else {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const exportInfo = exportMap.get(nameInSource);
|
|
300
|
+
if (exportInfo) {
|
|
301
|
+
if (!importsBySource.has(exportInfo.path)) {
|
|
302
|
+
importsBySource.set(exportInfo.path, []);
|
|
303
|
+
}
|
|
304
|
+
const effectiveKind = kind === 'type' || exportInfo.isTypeOnly ? 'type' : 'value';
|
|
305
|
+
importsBySource.get(exportInfo.path).push({
|
|
306
|
+
spec,
|
|
307
|
+
originalName: exportInfo.originalName,
|
|
308
|
+
exportInfo,
|
|
309
|
+
nameInSource,
|
|
310
|
+
nameInLocal,
|
|
311
|
+
kind: effectiveKind
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
unmappedSpecifiers.push(spec);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
importsBySource,
|
|
319
|
+
unmappedSpecifiers,
|
|
320
|
+
hasNamespaceImport
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Find an existing import statement that imports from the same source file.
|
|
326
|
+
*/
|
|
327
|
+
function findExistingImportForSourceFile({
|
|
328
|
+
sourceFile,
|
|
329
|
+
allImports,
|
|
330
|
+
basedir,
|
|
331
|
+
fs
|
|
332
|
+
}) {
|
|
333
|
+
return allImports.find(n => {
|
|
334
|
+
const isSourceAbsolute = sourceFile.startsWith('/') || sourceFile.match(/^[a-zA-Z]:/);
|
|
335
|
+
if (!isRelativeImport(sourceFile) && !isSourceAbsolute) {
|
|
336
|
+
return n.source.value === sourceFile;
|
|
337
|
+
}
|
|
338
|
+
if (!isRelativeImport(n.source.value)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const existingPath = resolveImportPath({
|
|
342
|
+
basedir,
|
|
343
|
+
importPath: n.source.value,
|
|
344
|
+
fs
|
|
345
|
+
});
|
|
346
|
+
return existingPath === sourceFile;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Find an existing export statement that exports from the same source file.
|
|
352
|
+
*/
|
|
353
|
+
function findExistingExportForSourceFile({
|
|
354
|
+
sourceFile,
|
|
355
|
+
allExports,
|
|
356
|
+
basedir,
|
|
357
|
+
fs
|
|
358
|
+
}) {
|
|
359
|
+
return allExports.find(n => {
|
|
360
|
+
if (!n.source) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const isSourceAbsolute = sourceFile.startsWith('/') || sourceFile.match(/^[a-zA-Z]:/);
|
|
364
|
+
if (!isRelativeImport(sourceFile) && !isSourceAbsolute) {
|
|
365
|
+
return n.source.value === sourceFile;
|
|
366
|
+
}
|
|
367
|
+
if (!isRelativeImport(n.source.value)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const existingPath = resolveImportPath({
|
|
371
|
+
basedir,
|
|
372
|
+
importPath: n.source.value,
|
|
373
|
+
fs
|
|
374
|
+
});
|
|
375
|
+
return existingPath === sourceFile;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Transform import specifiers to handle aliasing when the original name differs.
|
|
381
|
+
*/
|
|
382
|
+
function transformImportSpecifiers({
|
|
383
|
+
specsWithOriginal
|
|
384
|
+
}) {
|
|
385
|
+
return specsWithOriginal.map(({
|
|
386
|
+
spec,
|
|
387
|
+
originalName,
|
|
388
|
+
kind
|
|
389
|
+
}) => {
|
|
390
|
+
if (!originalName) {
|
|
391
|
+
return spec;
|
|
392
|
+
}
|
|
393
|
+
if (originalName === 'default') {
|
|
394
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
395
|
+
return spec;
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
type: 'ImportDefaultSpecifier',
|
|
399
|
+
local: spec.local,
|
|
400
|
+
range: spec.range,
|
|
401
|
+
loc: spec.loc,
|
|
402
|
+
parent: spec.parent
|
|
403
|
+
};
|
|
404
|
+
} else {
|
|
405
|
+
return {
|
|
406
|
+
type: 'ImportSpecifier',
|
|
407
|
+
local: spec.local,
|
|
408
|
+
imported: {
|
|
409
|
+
type: 'Identifier',
|
|
410
|
+
name: originalName,
|
|
411
|
+
range: [0, 0],
|
|
412
|
+
loc: {
|
|
413
|
+
start: {
|
|
414
|
+
line: 0,
|
|
415
|
+
column: 0
|
|
416
|
+
},
|
|
417
|
+
end: {
|
|
418
|
+
line: 0,
|
|
419
|
+
column: 0
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
importKind: kind,
|
|
424
|
+
range: spec.range,
|
|
425
|
+
loc: spec.loc,
|
|
426
|
+
parent: spec.parent
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Transform export specifiers to use original names from source.
|
|
434
|
+
*/
|
|
435
|
+
function transformExportSpecifiers({
|
|
436
|
+
specsWithOriginal
|
|
437
|
+
}) {
|
|
438
|
+
return specsWithOriginal.map(({
|
|
439
|
+
originalName,
|
|
440
|
+
nameInLocal,
|
|
441
|
+
kind
|
|
442
|
+
}) => ({
|
|
443
|
+
nameInSource: originalName || 'default',
|
|
444
|
+
nameInLocal,
|
|
445
|
+
kind
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
export function createRule(fs) {
|
|
449
|
+
return {
|
|
450
|
+
meta: ruleMeta,
|
|
451
|
+
create(context) {
|
|
452
|
+
const checkNode = rawNode => {
|
|
453
|
+
const node = rawNode;
|
|
454
|
+
|
|
455
|
+
// Resolve barrel file context (handles early exits for non-barrel files)
|
|
456
|
+
const barrelContext = resolveBarrelFileContext({
|
|
457
|
+
node,
|
|
458
|
+
context,
|
|
459
|
+
fs
|
|
460
|
+
});
|
|
461
|
+
if (!barrelContext) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const {
|
|
465
|
+
importPath,
|
|
466
|
+
resolvedPath,
|
|
467
|
+
exportMap
|
|
468
|
+
} = barrelContext;
|
|
469
|
+
|
|
470
|
+
// Check if we have any imports from this barrel
|
|
471
|
+
const specifiers = node.specifiers;
|
|
472
|
+
if (specifiers.length === 0) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Collect imports by source file
|
|
477
|
+
const {
|
|
478
|
+
importsBySource,
|
|
479
|
+
unmappedSpecifiers,
|
|
480
|
+
hasNamespaceImport
|
|
481
|
+
} = collectSpecifiersBySource({
|
|
482
|
+
specifiers,
|
|
483
|
+
node,
|
|
484
|
+
exportMap
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// If we have import * as, warn but don't auto-fix (too complex)
|
|
488
|
+
if (hasNamespaceImport) {
|
|
489
|
+
context.report({
|
|
490
|
+
node: node,
|
|
491
|
+
messageId: 'barrelImport',
|
|
492
|
+
data: {
|
|
493
|
+
path: importPath
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Check if we actually found any imports that map to the barrel exports
|
|
500
|
+
// If we found 0 mapped imports (all are unknown or missing), don't warn
|
|
501
|
+
if (importsBySource.size === 0) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// If all imports map back to the resolved path itself, don't warn.
|
|
506
|
+
// This happens when the file (e.g. index.ts) exports the symbols directly (not re-exports),
|
|
507
|
+
// so it is not acting as a barrel file for these imports.
|
|
508
|
+
if (importsBySource.size === 1 && importsBySource.has(resolvedPath)) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Build the report with optional auto-fix
|
|
513
|
+
const reportObj = {
|
|
514
|
+
node: node,
|
|
515
|
+
messageId: 'barrelImport',
|
|
516
|
+
data: {
|
|
517
|
+
path: importPath
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// Only provide auto-fix if all imports are mapped
|
|
522
|
+
// If there are unmapped specifiers, don't auto-fix to avoid issues
|
|
523
|
+
const nodeSource = node.source;
|
|
524
|
+
if (unmappedSpecifiers.length === 0 && nodeSource) {
|
|
525
|
+
reportObj.fix = function (fixer) {
|
|
526
|
+
const sourceCode = context.getSourceCode();
|
|
527
|
+
const quote = sourceCode.getText(nodeSource)[0]; // Get quote character
|
|
528
|
+
const basedirForFix = dirname(context.filename);
|
|
529
|
+
const fixes = [];
|
|
530
|
+
const newStatementsForBarrelReplacement = [];
|
|
531
|
+
|
|
532
|
+
// Generate new import statements for each source file
|
|
533
|
+
const sourceFileArray = Array.from(importsBySource.entries());
|
|
534
|
+
for (let i = 0; i < sourceFileArray.length; i++) {
|
|
535
|
+
const [sourceFile, specsWithOriginal] = sourceFileArray[i];
|
|
536
|
+
if (node.type === 'ImportDeclaration') {
|
|
537
|
+
// Transform specifiers if needed (handle aliasing)
|
|
538
|
+
const specs = transformImportSpecifiers({
|
|
539
|
+
specsWithOriginal
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Get all existing imports to check for merging
|
|
543
|
+
const allImports = sourceCode.ast.body.filter(n => n.type === 'ImportDeclaration' && n !== node);
|
|
544
|
+
|
|
545
|
+
// Check for existing import
|
|
546
|
+
const existingImport = findExistingImportForSourceFile({
|
|
547
|
+
sourceFile,
|
|
548
|
+
allImports,
|
|
549
|
+
basedir: basedirForFix,
|
|
550
|
+
fs
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Skip merging if existing is namespace import
|
|
554
|
+
const isNamespace = existingImport === null || existingImport === void 0 ? void 0 : existingImport.specifiers.some(s => s.type === 'ImportNamespaceSpecifier');
|
|
555
|
+
if (existingImport && !isNamespace) {
|
|
556
|
+
var _specsWithOriginal$0$, _specsWithOriginal$;
|
|
557
|
+
// Merge!
|
|
558
|
+
const existingSpecs = existingImport.specifiers.map(s => {
|
|
559
|
+
// Normalize importKind
|
|
560
|
+
if (existingImport.importKind === 'type') {
|
|
561
|
+
// If the parent declaration is 'type', treat all specifiers as 'type'
|
|
562
|
+
// We cast to AugmentedSpecifier to allow attaching importKind to DefaultSpecifier
|
|
563
|
+
return {
|
|
564
|
+
...s,
|
|
565
|
+
importKind: 'type'
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return s;
|
|
569
|
+
});
|
|
570
|
+
const newSpecs = specs.map(s => {
|
|
571
|
+
if (node.importKind === 'type') {
|
|
572
|
+
return {
|
|
573
|
+
...s,
|
|
574
|
+
importKind: 'type'
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
return s;
|
|
578
|
+
});
|
|
579
|
+
const mergedSpecs = [...existingSpecs, ...newSpecs];
|
|
580
|
+
|
|
581
|
+
// Determine if we should use 'import type'
|
|
582
|
+
const allType = mergedSpecs.every(s => s.importKind === 'type');
|
|
583
|
+
const relativePath = getImportPathForSourceFile({
|
|
584
|
+
sourceFilePath: sourceFile,
|
|
585
|
+
basedir: basedirForFix,
|
|
586
|
+
originalImportPath: importPath,
|
|
587
|
+
exportInfo: (_specsWithOriginal$0$ = (_specsWithOriginal$ = specsWithOriginal[0]) === null || _specsWithOriginal$ === void 0 ? void 0 : _specsWithOriginal$.exportInfo) !== null && _specsWithOriginal$0$ !== void 0 ? _specsWithOriginal$0$ : null,
|
|
588
|
+
workspaceRoot: findWorkspaceRoot({
|
|
589
|
+
startPath: basedirForFix,
|
|
590
|
+
fs: fs
|
|
591
|
+
}),
|
|
592
|
+
fs: fs
|
|
593
|
+
});
|
|
594
|
+
const newImportStatement = buildImportStatement({
|
|
595
|
+
specs: mergedSpecs,
|
|
596
|
+
path: relativePath,
|
|
597
|
+
quoteChar: quote,
|
|
598
|
+
isTypeImport: allType // Use type import if all are types
|
|
599
|
+
});
|
|
600
|
+
if (newImportStatement.length > 0) {
|
|
601
|
+
fixes.push(fixer.replaceText(existingImport, newImportStatement));
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
var _specsWithOriginal$0$2, _specsWithOriginal$2;
|
|
605
|
+
// Create new import
|
|
606
|
+
const relativePath = getImportPathForSourceFile({
|
|
607
|
+
sourceFilePath: sourceFile,
|
|
608
|
+
basedir: basedirForFix,
|
|
609
|
+
originalImportPath: importPath,
|
|
610
|
+
exportInfo: (_specsWithOriginal$0$2 = (_specsWithOriginal$2 = specsWithOriginal[0]) === null || _specsWithOriginal$2 === void 0 ? void 0 : _specsWithOriginal$2.exportInfo) !== null && _specsWithOriginal$0$2 !== void 0 ? _specsWithOriginal$0$2 : null,
|
|
611
|
+
workspaceRoot: findWorkspaceRoot({
|
|
612
|
+
startPath: basedirForFix,
|
|
613
|
+
fs: fs
|
|
614
|
+
}),
|
|
615
|
+
fs: fs
|
|
616
|
+
});
|
|
617
|
+
const isTypeImport = node.importKind === 'type';
|
|
618
|
+
const importStatement = buildImportStatement({
|
|
619
|
+
specs,
|
|
620
|
+
path: relativePath,
|
|
621
|
+
quoteChar: quote,
|
|
622
|
+
isTypeImport
|
|
623
|
+
});
|
|
624
|
+
if (importStatement.length > 0) {
|
|
625
|
+
newStatementsForBarrelReplacement.push(importStatement);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
} else if (node.type === 'ExportNamedDeclaration') {
|
|
629
|
+
// Handle ExportNamedDeclaration
|
|
630
|
+
const specs = transformExportSpecifiers({
|
|
631
|
+
specsWithOriginal
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Get all existing exports to check for merging
|
|
635
|
+
const allExports = sourceCode.ast.body.filter(n => n.type === 'ExportNamedDeclaration' && n !== node && !!n.source);
|
|
636
|
+
|
|
637
|
+
// Check for existing export
|
|
638
|
+
const existingExport = findExistingExportForSourceFile({
|
|
639
|
+
sourceFile,
|
|
640
|
+
allExports,
|
|
641
|
+
basedir: basedirForFix,
|
|
642
|
+
fs
|
|
643
|
+
});
|
|
644
|
+
if (existingExport) {
|
|
645
|
+
var _specsWithOriginal$0$3, _specsWithOriginal$3;
|
|
646
|
+
// Merge!
|
|
647
|
+
const existingSpecs = existingExport.specifiers.map(s => {
|
|
648
|
+
// For `export type { A, B }`, the parent's exportKind is 'type'
|
|
649
|
+
// Individual specifiers have exportKind: 'type' only for inline type (export { type A })
|
|
650
|
+
// If parent is type-only, all specifiers are types
|
|
651
|
+
// Otherwise, check individual specifier's exportKind
|
|
652
|
+
const effectiveKind = existingExport.exportKind === 'type' || s.exportKind === 'type' ? 'type' : 'value';
|
|
653
|
+
return {
|
|
654
|
+
nameInSource: getExportedName(s.local),
|
|
655
|
+
nameInLocal: getExportedName(s.exported),
|
|
656
|
+
kind: effectiveKind
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
const newSpecs = specs.map(s => {
|
|
660
|
+
if (node.exportKind === 'type') {
|
|
661
|
+
return {
|
|
662
|
+
...s,
|
|
663
|
+
kind: 'type'
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
return s;
|
|
667
|
+
});
|
|
668
|
+
const mergedSpecs = [...existingSpecs, ...newSpecs];
|
|
669
|
+
|
|
670
|
+
// Determine if we should use 'export type'
|
|
671
|
+
const allType = mergedSpecs.every(s => s.kind === 'type');
|
|
672
|
+
const relativePath = getImportPathForSourceFile({
|
|
673
|
+
sourceFilePath: sourceFile,
|
|
674
|
+
basedir: basedirForFix,
|
|
675
|
+
originalImportPath: importPath,
|
|
676
|
+
exportInfo: (_specsWithOriginal$0$3 = (_specsWithOriginal$3 = specsWithOriginal[0]) === null || _specsWithOriginal$3 === void 0 ? void 0 : _specsWithOriginal$3.exportInfo) !== null && _specsWithOriginal$0$3 !== void 0 ? _specsWithOriginal$0$3 : null,
|
|
677
|
+
workspaceRoot: findWorkspaceRoot({
|
|
678
|
+
startPath: basedirForFix,
|
|
679
|
+
fs: fs
|
|
680
|
+
}),
|
|
681
|
+
fs: fs
|
|
682
|
+
});
|
|
683
|
+
const newExportStatement = buildExportStatement({
|
|
684
|
+
specs: mergedSpecs,
|
|
685
|
+
path: relativePath,
|
|
686
|
+
quoteChar: quote,
|
|
687
|
+
isTypeExport: allType
|
|
688
|
+
});
|
|
689
|
+
if (newExportStatement.length > 0) {
|
|
690
|
+
fixes.push(fixer.replaceText(existingExport, newExportStatement));
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
var _specsWithOriginal$0$4, _specsWithOriginal$4;
|
|
694
|
+
// Create new export
|
|
695
|
+
const relativePath = getImportPathForSourceFile({
|
|
696
|
+
sourceFilePath: sourceFile,
|
|
697
|
+
basedir: basedirForFix,
|
|
698
|
+
originalImportPath: importPath,
|
|
699
|
+
exportInfo: (_specsWithOriginal$0$4 = (_specsWithOriginal$4 = specsWithOriginal[0]) === null || _specsWithOriginal$4 === void 0 ? void 0 : _specsWithOriginal$4.exportInfo) !== null && _specsWithOriginal$0$4 !== void 0 ? _specsWithOriginal$0$4 : null,
|
|
700
|
+
workspaceRoot: findWorkspaceRoot({
|
|
701
|
+
startPath: basedirForFix,
|
|
702
|
+
fs: fs
|
|
703
|
+
}),
|
|
704
|
+
fs: fs
|
|
705
|
+
});
|
|
706
|
+
const isTypeExport = node.exportKind === 'type';
|
|
707
|
+
const exportStatement = buildExportStatement({
|
|
708
|
+
specs,
|
|
709
|
+
path: relativePath,
|
|
710
|
+
quoteChar: quote,
|
|
711
|
+
isTypeExport
|
|
712
|
+
});
|
|
713
|
+
if (exportStatement.length > 0) {
|
|
714
|
+
newStatementsForBarrelReplacement.push(exportStatement);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (newStatementsForBarrelReplacement.length > 0) {
|
|
720
|
+
fixes.push(fixer.replaceText(node, newStatementsForBarrelReplacement.join('\n')));
|
|
721
|
+
} else {
|
|
722
|
+
// If all were merged, remove the node
|
|
723
|
+
fixes.push(fixer.remove(node));
|
|
724
|
+
}
|
|
725
|
+
return fixes;
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
context.report(reportObj);
|
|
729
|
+
};
|
|
730
|
+
return {
|
|
731
|
+
ImportDeclaration: checkNode,
|
|
732
|
+
ExportNamedDeclaration: checkNode
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const rule = createRule(realFileSystem);
|
|
738
|
+
export default rule;
|