@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,1391 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import _typeof from "@babel/runtime/helpers/typeof";
|
|
3
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
4
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
5
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
6
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
|
+
import { dirname, relative } from 'path';
|
|
8
|
+
import * as ts from 'typescript';
|
|
9
|
+
import { hasReExportsFromOtherFiles, parseBarrelExports } from '../shared/barrel-parsing';
|
|
10
|
+
import { findWorkspaceRoot, isRelativeImport, resolveImportPath } from '../shared/file-system';
|
|
11
|
+
import { extractImportPath, findJestRequireMockCalls, isJestMockCall, isJestRequireActual, resolveNewPathForRequireMock } from '../shared/jest-utils';
|
|
12
|
+
import { findPackageInRegistry } from '../shared/package-registry';
|
|
13
|
+
import { findExportForSourceFile, parsePackageExports } from '../shared/package-resolution';
|
|
14
|
+
import { realFileSystem } from '../shared/types';
|
|
15
|
+
|
|
16
|
+
// Cache per source package name to avoid repeated exports parsing during a single lint run.
|
|
17
|
+
// This is keyed by fs instance to avoid test pollution.
|
|
18
|
+
var sourcePackageExportsMapsByFs = new WeakMap();
|
|
19
|
+
function getSourcePackageExportsMaps(fs) {
|
|
20
|
+
var map = sourcePackageExportsMapsByFs.get(fs);
|
|
21
|
+
if (!map) {
|
|
22
|
+
map = new Map();
|
|
23
|
+
sourcePackageExportsMapsByFs.set(fs, map);
|
|
24
|
+
}
|
|
25
|
+
return map;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Information about a mock factory's preamble (statements before the return)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract identifiers defined by a statement (e.g., variable declarations)
|
|
34
|
+
* Uses TypeScript AST to find declared identifiers.
|
|
35
|
+
*/
|
|
36
|
+
function extractDefinedIdentifiers(statementText) {
|
|
37
|
+
var identifiers = new Set();
|
|
38
|
+
try {
|
|
39
|
+
// Parse the statement as a mini source file
|
|
40
|
+
var sourceFile = ts.createSourceFile('temp.ts', statementText, ts.ScriptTarget.Latest, true);
|
|
41
|
+
var _visit = function visit(node) {
|
|
42
|
+
if (ts.isVariableStatement(node)) {
|
|
43
|
+
var _iterator = _createForOfIteratorHelper(node.declarationList.declarations),
|
|
44
|
+
_step;
|
|
45
|
+
try {
|
|
46
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
47
|
+
var decl = _step.value;
|
|
48
|
+
if (ts.isIdentifier(decl.name)) {
|
|
49
|
+
identifiers.add(decl.name.text);
|
|
50
|
+
} else if (ts.isObjectBindingPattern(decl.name)) {
|
|
51
|
+
// Handle destructuring: const { a, b } = ...
|
|
52
|
+
var _iterator2 = _createForOfIteratorHelper(decl.name.elements),
|
|
53
|
+
_step2;
|
|
54
|
+
try {
|
|
55
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
56
|
+
var element = _step2.value;
|
|
57
|
+
if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) {
|
|
58
|
+
identifiers.add(element.name.text);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
_iterator2.e(err);
|
|
63
|
+
} finally {
|
|
64
|
+
_iterator2.f();
|
|
65
|
+
}
|
|
66
|
+
} else if (ts.isArrayBindingPattern(decl.name)) {
|
|
67
|
+
// Handle array destructuring: const [a, b] = ...
|
|
68
|
+
var _iterator3 = _createForOfIteratorHelper(decl.name.elements),
|
|
69
|
+
_step3;
|
|
70
|
+
try {
|
|
71
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
72
|
+
var _element = _step3.value;
|
|
73
|
+
if (ts.isBindingElement(_element) && ts.isIdentifier(_element.name)) {
|
|
74
|
+
identifiers.add(_element.name.text);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
_iterator3.e(err);
|
|
79
|
+
} finally {
|
|
80
|
+
_iterator3.f();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
_iterator.e(err);
|
|
86
|
+
} finally {
|
|
87
|
+
_iterator.f();
|
|
88
|
+
}
|
|
89
|
+
} else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
90
|
+
identifiers.add(node.name.text);
|
|
91
|
+
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
92
|
+
identifiers.add(node.name.text);
|
|
93
|
+
}
|
|
94
|
+
ts.forEachChild(node, _visit);
|
|
95
|
+
};
|
|
96
|
+
ts.forEachChild(sourceFile, _visit);
|
|
97
|
+
} catch (_unused) {
|
|
98
|
+
// Ignore parsing errors
|
|
99
|
+
}
|
|
100
|
+
return identifiers;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Find all identifiers used in a given text string.
|
|
105
|
+
* Uses a simple regex approach to find potential identifier references.
|
|
106
|
+
*/
|
|
107
|
+
function findUsedIdentifiers(text, potentialIdentifiers) {
|
|
108
|
+
var used = new Set();
|
|
109
|
+
var _iterator4 = _createForOfIteratorHelper(potentialIdentifiers),
|
|
110
|
+
_step4;
|
|
111
|
+
try {
|
|
112
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
113
|
+
var identifier = _step4.value;
|
|
114
|
+
// Use word boundary matching to find identifier usage
|
|
115
|
+
// This matches the identifier as a whole word (not part of another word)
|
|
116
|
+
var regex = new RegExp("\\b".concat(escapeRegExpForIdentifier(identifier), "\\b"));
|
|
117
|
+
if (regex.test(text)) {
|
|
118
|
+
used.add(identifier);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
_iterator4.e(err);
|
|
123
|
+
} finally {
|
|
124
|
+
_iterator4.f();
|
|
125
|
+
}
|
|
126
|
+
return used;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Escape special regex characters for identifier matching
|
|
131
|
+
*/
|
|
132
|
+
function escapeRegExpForIdentifier(str) {
|
|
133
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Filter preamble statements to only include those whose defined identifiers
|
|
138
|
+
* are used in the given property texts.
|
|
139
|
+
*/
|
|
140
|
+
function filterPreambleForProperties(preamble, propertyTexts) {
|
|
141
|
+
if (!preamble.hasPreamble || preamble.statements.length === 0) {
|
|
142
|
+
return preamble;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Collect all identifiers defined in the preamble
|
|
146
|
+
var allDefinedIdentifiers = new Set();
|
|
147
|
+
var _iterator5 = _createForOfIteratorHelper(preamble.statements),
|
|
148
|
+
_step5;
|
|
149
|
+
try {
|
|
150
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
151
|
+
var stmt = _step5.value;
|
|
152
|
+
var _iterator7 = _createForOfIteratorHelper(stmt.definedIdentifiers),
|
|
153
|
+
_step7;
|
|
154
|
+
try {
|
|
155
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
156
|
+
var id = _step7.value;
|
|
157
|
+
allDefinedIdentifiers.add(id);
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
_iterator7.e(err);
|
|
161
|
+
} finally {
|
|
162
|
+
_iterator7.f();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Find which identifiers are used in the property texts
|
|
167
|
+
} catch (err) {
|
|
168
|
+
_iterator5.e(err);
|
|
169
|
+
} finally {
|
|
170
|
+
_iterator5.f();
|
|
171
|
+
}
|
|
172
|
+
var combinedPropertyText = propertyTexts.join('\n');
|
|
173
|
+
var usedIdentifiers = findUsedIdentifiers(combinedPropertyText, allDefinedIdentifiers);
|
|
174
|
+
|
|
175
|
+
// Filter statements to only those that define used identifiers
|
|
176
|
+
var filteredStatements = preamble.statements.filter(function (stmt) {
|
|
177
|
+
// Include statement if any of its defined identifiers are used
|
|
178
|
+
var _iterator6 = _createForOfIteratorHelper(stmt.definedIdentifiers),
|
|
179
|
+
_step6;
|
|
180
|
+
try {
|
|
181
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
182
|
+
var id = _step6.value;
|
|
183
|
+
if (usedIdentifiers.has(id)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
_iterator6.e(err);
|
|
189
|
+
} finally {
|
|
190
|
+
_iterator6.f();
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
});
|
|
194
|
+
if (filteredStatements.length === 0) {
|
|
195
|
+
return {
|
|
196
|
+
text: '',
|
|
197
|
+
hasPreamble: false,
|
|
198
|
+
statements: []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
text: filteredStatements.map(function (s) {
|
|
203
|
+
return s.text;
|
|
204
|
+
}).join('\n\t'),
|
|
205
|
+
hasPreamble: true,
|
|
206
|
+
statements: filteredStatements
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Convert absolute file path to an import path, handling cross-package resolution.
|
|
212
|
+
* If the export comes from a cross-package source, returns the package path (e.g., '@atlassian/package-b/utils').
|
|
213
|
+
* Otherwise, returns a relative path.
|
|
214
|
+
*/
|
|
215
|
+
function getImportPathForSourceFile(_ref) {
|
|
216
|
+
var _exportInfo$crossPack;
|
|
217
|
+
var sourceFilePath = _ref.sourceFilePath,
|
|
218
|
+
basedir = _ref.basedir,
|
|
219
|
+
originalImportPath = _ref.originalImportPath,
|
|
220
|
+
exportInfo = _ref.exportInfo,
|
|
221
|
+
workspaceRoot = _ref.workspaceRoot,
|
|
222
|
+
fs = _ref.fs;
|
|
223
|
+
var crossPackageName = exportInfo === null || exportInfo === void 0 || (_exportInfo$crossPack = exportInfo.crossPackageSource) === null || _exportInfo$crossPack === void 0 ? void 0 : _exportInfo$crossPack.packageName;
|
|
224
|
+
if (crossPackageName) {
|
|
225
|
+
var sourcePackageExportsMaps = getSourcePackageExportsMaps(fs);
|
|
226
|
+
var exportsMap = sourcePackageExportsMaps.get(crossPackageName);
|
|
227
|
+
if (!exportsMap) {
|
|
228
|
+
var pkgDir = findPackageInRegistry({
|
|
229
|
+
packageName: crossPackageName,
|
|
230
|
+
workspaceRoot: workspaceRoot,
|
|
231
|
+
fs: fs
|
|
232
|
+
});
|
|
233
|
+
if (pkgDir) {
|
|
234
|
+
exportsMap = parsePackageExports({
|
|
235
|
+
packageDir: pkgDir,
|
|
236
|
+
fs: fs
|
|
237
|
+
});
|
|
238
|
+
sourcePackageExportsMaps.set(crossPackageName, exportsMap);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
var targetExportPath = exportsMap ? findExportForSourceFile({
|
|
242
|
+
sourceFilePath: sourceFilePath,
|
|
243
|
+
exportsMap: exportsMap
|
|
244
|
+
}) : null;
|
|
245
|
+
return targetExportPath ? crossPackageName + targetExportPath.slice(1) : crossPackageName;
|
|
246
|
+
}
|
|
247
|
+
return getRelativeImportPath({
|
|
248
|
+
basedir: basedir,
|
|
249
|
+
absolutePath: sourceFilePath,
|
|
250
|
+
originalImportPath: originalImportPath
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Convert absolute file path back to relative import path
|
|
256
|
+
*/
|
|
257
|
+
function getRelativeImportPath(_ref2) {
|
|
258
|
+
var basedir = _ref2.basedir,
|
|
259
|
+
absolutePath = _ref2.absolutePath,
|
|
260
|
+
originalImportPath = _ref2.originalImportPath;
|
|
261
|
+
var relativePath = relative(basedir, absolutePath);
|
|
262
|
+
// Normalize to use forward slashes
|
|
263
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
264
|
+
|
|
265
|
+
// Check for extension in original path
|
|
266
|
+
var extMatch = originalImportPath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
|
|
267
|
+
var originalExt = extMatch ? extMatch[0] : '';
|
|
268
|
+
|
|
269
|
+
// Get extension from the resolved absolute path
|
|
270
|
+
var targetExtMatch = absolutePath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
|
|
271
|
+
var targetExt = targetExtMatch ? targetExtMatch[0] : '';
|
|
272
|
+
|
|
273
|
+
// Remove file extension from the target path
|
|
274
|
+
relativePath = relativePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
|
|
275
|
+
|
|
276
|
+
// If original had extension, append it
|
|
277
|
+
if (originalExt) {
|
|
278
|
+
// If original was a TypeScript source extension, use the actual target extension
|
|
279
|
+
if (['.ts', '.tsx'].includes(originalExt) && targetExt) {
|
|
280
|
+
relativePath += targetExt;
|
|
281
|
+
} else {
|
|
282
|
+
relativePath += originalExt;
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
// Remove /index suffix only if no extension was present
|
|
286
|
+
if (relativePath.endsWith('/index')) {
|
|
287
|
+
relativePath = relativePath.slice(0, -6);
|
|
288
|
+
} else if (relativePath === 'index') {
|
|
289
|
+
relativePath = '.';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Ensure it starts with .. or .
|
|
294
|
+
if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
|
|
295
|
+
relativePath = './' + relativePath;
|
|
296
|
+
}
|
|
297
|
+
return relativePath;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if a node is an Object.assign call
|
|
302
|
+
*/
|
|
303
|
+
function isObjectAssignCall(node) {
|
|
304
|
+
if (node.type !== 'CallExpression') {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
var callee = node.callee;
|
|
308
|
+
if (callee.type === 'MemberExpression') {
|
|
309
|
+
return callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'assign';
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract mock object from Object.assign pattern
|
|
316
|
+
* Pattern: Object.assign({}, jest.requireActual(...), { mockProps })
|
|
317
|
+
* Returns the properties object and whether it has requireActual
|
|
318
|
+
*/
|
|
319
|
+
function extractObjectAssignMock(node) {
|
|
320
|
+
var args = node.arguments;
|
|
321
|
+
|
|
322
|
+
// Object.assign typically has at least 2 arguments: target and source(s)
|
|
323
|
+
// Pattern: Object.assign({}, jest.requireActual(...), { mockProps })
|
|
324
|
+
// or: Object.assign({}, jest.requireActual(...), { mockProps1 }, { mockProps2 })
|
|
325
|
+
if (args.length < 2) {
|
|
326
|
+
return {
|
|
327
|
+
propertiesObject: null,
|
|
328
|
+
hasRequireActual: false
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
var hasRequireActual = false;
|
|
332
|
+
var lastObjectExpression = null;
|
|
333
|
+
|
|
334
|
+
// Scan through arguments to find jest.requireActual and the last object literal
|
|
335
|
+
var _iterator8 = _createForOfIteratorHelper(args),
|
|
336
|
+
_step8;
|
|
337
|
+
try {
|
|
338
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
339
|
+
var arg = _step8.value;
|
|
340
|
+
if (isJestRequireActual(arg)) {
|
|
341
|
+
hasRequireActual = true;
|
|
342
|
+
}
|
|
343
|
+
if (arg.type === 'ObjectExpression') {
|
|
344
|
+
// Skip empty objects (the first {} in Object.assign({}, ...))
|
|
345
|
+
if (arg.properties.length > 0) {
|
|
346
|
+
lastObjectExpression = arg;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
_iterator8.e(err);
|
|
352
|
+
} finally {
|
|
353
|
+
_iterator8.f();
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
propertiesObject: lastObjectExpression,
|
|
357
|
+
hasRequireActual: hasRequireActual
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extract mock object properties from jest.mock call
|
|
363
|
+
* Returns a map of property name -> { node, text } and whether there's a jest.requireActual spread
|
|
364
|
+
*/
|
|
365
|
+
function extractMockProperties(_ref3) {
|
|
366
|
+
var sourceCode = _ref3.sourceCode,
|
|
367
|
+
mockObjectNode = _ref3.mockObjectNode;
|
|
368
|
+
var properties = new Map();
|
|
369
|
+
var hasRequireActual = false;
|
|
370
|
+
|
|
371
|
+
// Handle Object.assign pattern: Object.assign({}, jest.requireActual(...), { props })
|
|
372
|
+
if (isObjectAssignCall(mockObjectNode)) {
|
|
373
|
+
var _extractObjectAssignM = extractObjectAssignMock(mockObjectNode),
|
|
374
|
+
propertiesObject = _extractObjectAssignM.propertiesObject,
|
|
375
|
+
objectAssignHasRequireActual = _extractObjectAssignM.hasRequireActual;
|
|
376
|
+
if (propertiesObject) {
|
|
377
|
+
// Recursively extract properties from the properties object
|
|
378
|
+
var result = extractMockProperties({
|
|
379
|
+
sourceCode: sourceCode,
|
|
380
|
+
mockObjectNode: propertiesObject
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
properties: result.properties,
|
|
384
|
+
hasRequireActual: objectAssignHasRequireActual || result.hasRequireActual
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
properties: properties,
|
|
389
|
+
hasRequireActual: objectAssignHasRequireActual
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (mockObjectNode.type === 'ObjectExpression') {
|
|
393
|
+
var _iterator9 = _createForOfIteratorHelper(mockObjectNode.properties),
|
|
394
|
+
_step9;
|
|
395
|
+
try {
|
|
396
|
+
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
|
|
397
|
+
var prop = _step9.value;
|
|
398
|
+
if (prop.type === 'SpreadElement') {
|
|
399
|
+
// Check if this is ...jest.requireActual(...)
|
|
400
|
+
if (isJestRequireActual(prop.argument)) {
|
|
401
|
+
hasRequireActual = true;
|
|
402
|
+
}
|
|
403
|
+
} else if (prop.type === 'Property') {
|
|
404
|
+
var keyName = void 0;
|
|
405
|
+
if (prop.key.type === 'Identifier') {
|
|
406
|
+
keyName = prop.key.name;
|
|
407
|
+
} else if (prop.key.type === 'Literal') {
|
|
408
|
+
keyName = String(prop.key.value);
|
|
409
|
+
} else {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
var propText = sourceCode.getText(prop);
|
|
413
|
+
var valueText = sourceCode.getText(prop.value);
|
|
414
|
+
properties.set(keyName, {
|
|
415
|
+
node: prop,
|
|
416
|
+
text: propText,
|
|
417
|
+
valueText: valueText
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch (err) {
|
|
422
|
+
_iterator9.e(err);
|
|
423
|
+
} finally {
|
|
424
|
+
_iterator9.f();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
properties: properties,
|
|
429
|
+
hasRequireActual: hasRequireActual
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Validate and resolve a barrel file from an import path
|
|
435
|
+
* Returns null if not a valid relative barrel import
|
|
436
|
+
*/
|
|
437
|
+
export function validateAndResolveBarrelFile(_ref4) {
|
|
438
|
+
var importPath = _ref4.importPath,
|
|
439
|
+
basedir = _ref4.basedir,
|
|
440
|
+
workspaceRoot = _ref4.workspaceRoot,
|
|
441
|
+
fs = _ref4.fs;
|
|
442
|
+
if (!isRelativeImport(importPath)) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
var resolvedPath = resolveImportPath({
|
|
446
|
+
basedir: basedir,
|
|
447
|
+
importPath: importPath,
|
|
448
|
+
fs: fs
|
|
449
|
+
});
|
|
450
|
+
if (!resolvedPath) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
var exportMap = parseBarrelExports({
|
|
454
|
+
barrelFilePath: resolvedPath,
|
|
455
|
+
workspaceRoot: workspaceRoot,
|
|
456
|
+
fs: fs
|
|
457
|
+
});
|
|
458
|
+
if (exportMap.size === 0) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// A file is considered a barrel file if it has re-exports from other files.
|
|
463
|
+
// This is the semantic check - we don't care about the filename.
|
|
464
|
+
if (!hasReExportsFromOtherFiles({
|
|
465
|
+
exportMap: exportMap,
|
|
466
|
+
sourceFilePath: resolvedPath
|
|
467
|
+
})) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
resolvedPath: resolvedPath,
|
|
472
|
+
exportMap: exportMap
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Extract the mock implementation object from the jest.mock call
|
|
478
|
+
*/
|
|
479
|
+
function extractMockImplementation(mockImpl) {
|
|
480
|
+
if (mockImpl.type === 'ArrowFunctionExpression') {
|
|
481
|
+
if (mockImpl.body.type === 'ObjectExpression') {
|
|
482
|
+
return mockImpl.body;
|
|
483
|
+
}
|
|
484
|
+
// Handle arrow functions that return a call expression directly (e.g., Object.assign)
|
|
485
|
+
if (mockImpl.body.type === 'CallExpression') {
|
|
486
|
+
return mockImpl.body;
|
|
487
|
+
}
|
|
488
|
+
if (mockImpl.body.type === 'BlockStatement') {
|
|
489
|
+
var returnStmt = mockImpl.body.body.find(function (s) {
|
|
490
|
+
return s.type === 'ReturnStatement';
|
|
491
|
+
});
|
|
492
|
+
if (returnStmt !== null && returnStmt !== void 0 && returnStmt.argument) {
|
|
493
|
+
return returnStmt.argument;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (mockImpl.type === 'FunctionExpression' && mockImpl.body.type === 'BlockStatement') {
|
|
498
|
+
var _returnStmt = mockImpl.body.body.find(function (s) {
|
|
499
|
+
return s.type === 'ReturnStatement';
|
|
500
|
+
});
|
|
501
|
+
if (_returnStmt !== null && _returnStmt !== void 0 && _returnStmt.argument) {
|
|
502
|
+
return _returnStmt.argument;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return mockImpl;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Extract the preamble (statements before the return) from a mock factory function.
|
|
510
|
+
* This captures variable declarations, assignments, etc. that need to be preserved.
|
|
511
|
+
*/
|
|
512
|
+
function extractMockFactoryPreamble(_ref5) {
|
|
513
|
+
var mockImpl = _ref5.mockImpl,
|
|
514
|
+
sourceCode = _ref5.sourceCode;
|
|
515
|
+
var emptyPreamble = {
|
|
516
|
+
text: '',
|
|
517
|
+
hasPreamble: false,
|
|
518
|
+
statements: []
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// Get the block statement body from the mock factory
|
|
522
|
+
var blockBody = null;
|
|
523
|
+
if ((mockImpl.type === 'ArrowFunctionExpression' || mockImpl.type === 'FunctionExpression') && mockImpl.body.type === 'BlockStatement') {
|
|
524
|
+
blockBody = mockImpl.body.body;
|
|
525
|
+
}
|
|
526
|
+
if (!blockBody) {
|
|
527
|
+
return emptyPreamble;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Find the return statement index
|
|
531
|
+
var returnIndex = blockBody.findIndex(function (s) {
|
|
532
|
+
return s.type === 'ReturnStatement';
|
|
533
|
+
});
|
|
534
|
+
if (returnIndex <= 0) {
|
|
535
|
+
// No preamble (return is first statement or not found)
|
|
536
|
+
return emptyPreamble;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Extract all statements before the return
|
|
540
|
+
var preambleStatements = blockBody.slice(0, returnIndex);
|
|
541
|
+
var statementsWithIdentifiers = preambleStatements.map(function (stmt) {
|
|
542
|
+
var text = sourceCode.getText(stmt);
|
|
543
|
+
var definedIdentifiers = extractDefinedIdentifiers(text);
|
|
544
|
+
return {
|
|
545
|
+
text: text,
|
|
546
|
+
definedIdentifiers: definedIdentifiers
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
var preambleTexts = statementsWithIdentifiers.map(function (s) {
|
|
550
|
+
return s.text;
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
text: preambleTexts.join('\n\t'),
|
|
554
|
+
hasPreamble: true,
|
|
555
|
+
statements: statementsWithIdentifiers
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Rewrite jest.requireActual paths in a text string from the original barrel path to a new path.
|
|
561
|
+
*/
|
|
562
|
+
function rewriteRequireActualPaths(_ref6) {
|
|
563
|
+
var text = _ref6.text,
|
|
564
|
+
originalPath = _ref6.originalPath,
|
|
565
|
+
newPath = _ref6.newPath,
|
|
566
|
+
quote = _ref6.quote;
|
|
567
|
+
// Match jest.requireActual('originalPath') or jest.requireActual("originalPath")
|
|
568
|
+
// Also handle the 'as Object' or 'as any' type assertions
|
|
569
|
+
var patterns = [
|
|
570
|
+
// With single quotes
|
|
571
|
+
new RegExp("jest\\.requireActual\\(\\s*'".concat(escapeRegExp(originalPath), "'\\s*\\)(?:\\s+as\\s+\\w+)?"), 'g'),
|
|
572
|
+
// With double quotes
|
|
573
|
+
new RegExp("jest\\.requireActual\\(\\s*\"".concat(escapeRegExp(originalPath), "\"\\s*\\)(?:\\s+as\\s+\\w+)?"), 'g')];
|
|
574
|
+
var result = text;
|
|
575
|
+
for (var _i = 0, _patterns = patterns; _i < _patterns.length; _i++) {
|
|
576
|
+
var pattern = _patterns[_i];
|
|
577
|
+
result = result.replace(pattern, "jest.requireActual(".concat(quote).concat(newPath).concat(quote, ")"));
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Escape special regex characters in a string
|
|
584
|
+
*/
|
|
585
|
+
function escapeRegExp(str) {
|
|
586
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Group mocked properties by their source file
|
|
591
|
+
*/
|
|
592
|
+
function groupPropertiesBySource(_ref7) {
|
|
593
|
+
var mockProperties = _ref7.mockProperties,
|
|
594
|
+
exportMap = _ref7.exportMap;
|
|
595
|
+
var propertiesBySource = new Map();
|
|
596
|
+
var _iterator0 = _createForOfIteratorHelper(mockProperties),
|
|
597
|
+
_step0;
|
|
598
|
+
try {
|
|
599
|
+
for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
|
|
600
|
+
var _step0$value = _slicedToArray(_step0.value, 1),
|
|
601
|
+
propName = _step0$value[0];
|
|
602
|
+
var exportInfo = exportMap.get(propName);
|
|
603
|
+
if (!exportInfo) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (!propertiesBySource.has(exportInfo.path)) {
|
|
607
|
+
propertiesBySource.set(exportInfo.path, []);
|
|
608
|
+
}
|
|
609
|
+
propertiesBySource.get(exportInfo.path).push(propName);
|
|
610
|
+
}
|
|
611
|
+
} catch (err) {
|
|
612
|
+
_iterator0.e(err);
|
|
613
|
+
} finally {
|
|
614
|
+
_iterator0.f();
|
|
615
|
+
}
|
|
616
|
+
return propertiesBySource;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Determine if we should report a barrel mock violation
|
|
621
|
+
*/
|
|
622
|
+
function shouldReportBarrelMock(_ref8) {
|
|
623
|
+
var propertiesBySource = _ref8.propertiesBySource,
|
|
624
|
+
barrelFilePath = _ref8.barrelFilePath;
|
|
625
|
+
// Report if any mocked property is a re-export (comes from a different file than the barrel)
|
|
626
|
+
// This catches both:
|
|
627
|
+
// 1. Properties from multiple source files
|
|
628
|
+
// 2. Properties from a single source file that isn't the barrel itself
|
|
629
|
+
var _iterator1 = _createForOfIteratorHelper(propertiesBySource.keys()),
|
|
630
|
+
_step1;
|
|
631
|
+
try {
|
|
632
|
+
for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
|
|
633
|
+
var sourcePath = _step1.value;
|
|
634
|
+
if (sourcePath !== barrelFilePath) {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} catch (err) {
|
|
639
|
+
_iterator1.e(err);
|
|
640
|
+
} finally {
|
|
641
|
+
_iterator1.f();
|
|
642
|
+
}
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Generate auto-fix for auto-mock case (no mock implementation)
|
|
648
|
+
*/
|
|
649
|
+
function generateAutoMockFix(_ref9) {
|
|
650
|
+
var exportMap = _ref9.exportMap,
|
|
651
|
+
basedir = _ref9.basedir,
|
|
652
|
+
importPath = _ref9.importPath,
|
|
653
|
+
quote = _ref9.quote,
|
|
654
|
+
workspaceRoot = _ref9.workspaceRoot,
|
|
655
|
+
fs = _ref9.fs;
|
|
656
|
+
// Group exports by source file, filtering out type-only source files
|
|
657
|
+
// Also track the ExportInfo for cross-package resolution
|
|
658
|
+
var sourceFilesWithInfo = new Map();
|
|
659
|
+
var _iterator10 = _createForOfIteratorHelper(exportMap),
|
|
660
|
+
_step10;
|
|
661
|
+
try {
|
|
662
|
+
for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
|
|
663
|
+
var _step10$value = _slicedToArray(_step10.value, 2),
|
|
664
|
+
info = _step10$value[1];
|
|
665
|
+
if (!info.isTypeOnly && !sourceFilesWithInfo.has(info.path)) {
|
|
666
|
+
sourceFilesWithInfo.set(info.path, info);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
} catch (err) {
|
|
670
|
+
_iterator10.e(err);
|
|
671
|
+
} finally {
|
|
672
|
+
_iterator10.f();
|
|
673
|
+
}
|
|
674
|
+
var sourceFileArray = Array.from(sourceFilesWithInfo.entries());
|
|
675
|
+
return sourceFileArray.map(function (_ref0) {
|
|
676
|
+
var _ref1 = _slicedToArray(_ref0, 2),
|
|
677
|
+
sourceFile = _ref1[0],
|
|
678
|
+
exportInfo = _ref1[1];
|
|
679
|
+
var mockPath = getImportPathForSourceFile({
|
|
680
|
+
sourceFilePath: sourceFile,
|
|
681
|
+
basedir: basedir,
|
|
682
|
+
originalImportPath: importPath,
|
|
683
|
+
exportInfo: exportInfo,
|
|
684
|
+
workspaceRoot: workspaceRoot,
|
|
685
|
+
fs: fs
|
|
686
|
+
});
|
|
687
|
+
return "jest.mock(".concat(quote).concat(mockPath).concat(quote, ")");
|
|
688
|
+
}).join(';\n');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Normalize a path for comparison (resolve to absolute path)
|
|
693
|
+
*/
|
|
694
|
+
function normalizePathForComparison(_ref10) {
|
|
695
|
+
var basedir = _ref10.basedir,
|
|
696
|
+
importPath = _ref10.importPath,
|
|
697
|
+
fs = _ref10.fs;
|
|
698
|
+
if (!isRelativeImport(importPath)) {
|
|
699
|
+
// For non-relative imports, just return as-is for comparison
|
|
700
|
+
return importPath;
|
|
701
|
+
}
|
|
702
|
+
var resolved = resolveImportPath({
|
|
703
|
+
basedir: basedir,
|
|
704
|
+
importPath: importPath,
|
|
705
|
+
fs: fs
|
|
706
|
+
});
|
|
707
|
+
return resolved || importPath;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Scan the entire file for all existing jest.mock calls
|
|
712
|
+
* Returns a map of normalized path -> { node, properties, hasRequireActual }
|
|
713
|
+
*/
|
|
714
|
+
function findAllJestMocksInFile(_ref11) {
|
|
715
|
+
var context = _ref11.context,
|
|
716
|
+
basedir = _ref11.basedir,
|
|
717
|
+
fs = _ref11.fs;
|
|
718
|
+
var allMocks = new Map();
|
|
719
|
+
var sourceCode = context.getSourceCode();
|
|
720
|
+
var ast = sourceCode.ast;
|
|
721
|
+
|
|
722
|
+
// Use a visited set to prevent infinite recursion
|
|
723
|
+
var visited = new Set();
|
|
724
|
+
|
|
725
|
+
// Properties to skip to avoid circular references
|
|
726
|
+
var skipKeys = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);
|
|
727
|
+
function visitNode(node) {
|
|
728
|
+
// Prevent revisiting nodes
|
|
729
|
+
if (visited.has(node)) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
visited.add(node);
|
|
733
|
+
if (node.type === 'CallExpression' && isJestMockCall(node)) {
|
|
734
|
+
var importPath = extractImportPath(node);
|
|
735
|
+
if (importPath) {
|
|
736
|
+
var normalizedPath = normalizePathForComparison({
|
|
737
|
+
basedir: basedir,
|
|
738
|
+
importPath: importPath,
|
|
739
|
+
fs: fs
|
|
740
|
+
});
|
|
741
|
+
var mockImpl = node.arguments[1];
|
|
742
|
+
if (mockImpl) {
|
|
743
|
+
var mockObjectNode = extractMockImplementation(mockImpl);
|
|
744
|
+
var _extractMockPropertie = extractMockProperties({
|
|
745
|
+
sourceCode: sourceCode,
|
|
746
|
+
mockObjectNode: mockObjectNode
|
|
747
|
+
}),
|
|
748
|
+
properties = _extractMockPropertie.properties,
|
|
749
|
+
hasRequireActual = _extractMockPropertie.hasRequireActual;
|
|
750
|
+
allMocks.set(normalizedPath, {
|
|
751
|
+
node: node,
|
|
752
|
+
importPath: importPath,
|
|
753
|
+
properties: properties,
|
|
754
|
+
hasRequireActual: hasRequireActual
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Recursively visit child nodes
|
|
761
|
+
for (var key in node) {
|
|
762
|
+
if (skipKeys.has(key)) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
var value = node[key];
|
|
766
|
+
if (value && _typeof(value) === 'object') {
|
|
767
|
+
if (Array.isArray(value)) {
|
|
768
|
+
value.forEach(function (child) {
|
|
769
|
+
if (child && _typeof(child) === 'object' && 'type' in child) {
|
|
770
|
+
visitNode(child);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
} else if ('type' in value) {
|
|
774
|
+
visitNode(value);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
visitNode(ast);
|
|
780
|
+
return allMocks;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Merge mock properties from multiple sources for the same file
|
|
785
|
+
*/
|
|
786
|
+
function mergeMockProperties(_ref12) {
|
|
787
|
+
var existingProperties = _ref12.existingProperties,
|
|
788
|
+
newProperties = _ref12.newProperties;
|
|
789
|
+
var merged = new Map(existingProperties);
|
|
790
|
+
var _iterator11 = _createForOfIteratorHelper(newProperties),
|
|
791
|
+
_step11;
|
|
792
|
+
try {
|
|
793
|
+
for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
|
|
794
|
+
var _step11$value = _slicedToArray(_step11.value, 2),
|
|
795
|
+
key = _step11$value[0],
|
|
796
|
+
value = _step11$value[1];
|
|
797
|
+
merged.set(key, value);
|
|
798
|
+
}
|
|
799
|
+
} catch (err) {
|
|
800
|
+
_iterator11.e(err);
|
|
801
|
+
} finally {
|
|
802
|
+
_iterator11.f();
|
|
803
|
+
}
|
|
804
|
+
return merged;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Generate mock call text for a specific file with given properties
|
|
809
|
+
*/
|
|
810
|
+
function generateMockCallText(_ref13) {
|
|
811
|
+
var relativePath = _ref13.relativePath,
|
|
812
|
+
properties = _ref13.properties,
|
|
813
|
+
hasRequireActual = _ref13.hasRequireActual,
|
|
814
|
+
quote = _ref13.quote,
|
|
815
|
+
exportMap = _ref13.exportMap,
|
|
816
|
+
sourceFile = _ref13.sourceFile,
|
|
817
|
+
preamble = _ref13.preamble,
|
|
818
|
+
originalImportPath = _ref13.originalImportPath;
|
|
819
|
+
var propNames = Array.from(properties.keys());
|
|
820
|
+
|
|
821
|
+
// Separate props by whether they're from default exports
|
|
822
|
+
var defaultExportProps = [];
|
|
823
|
+
var namedExportProps = [];
|
|
824
|
+
var _loop = function _loop() {
|
|
825
|
+
var prop = _propNames[_i2];
|
|
826
|
+
var exportInfo = Array.from(exportMap.entries()).find(function (_ref14) {
|
|
827
|
+
var _ref15 = _slicedToArray(_ref14, 2),
|
|
828
|
+
exportName = _ref15[0],
|
|
829
|
+
info = _ref15[1];
|
|
830
|
+
return exportName === prop && info.path === sourceFile;
|
|
831
|
+
});
|
|
832
|
+
if (exportInfo && exportInfo[1].isDefaultExport) {
|
|
833
|
+
defaultExportProps.push(prop);
|
|
834
|
+
} else {
|
|
835
|
+
namedExportProps.push(prop);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
for (var _i2 = 0, _propNames = propNames; _i2 < _propNames.length; _i2++) {
|
|
839
|
+
_loop();
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Collect all property texts for filtering the preamble
|
|
843
|
+
var allPropertyTexts = [];
|
|
844
|
+
for (var _i3 = 0, _namedExportProps = namedExportProps; _i3 < _namedExportProps.length; _i3++) {
|
|
845
|
+
var _properties$get;
|
|
846
|
+
var prop = _namedExportProps[_i3];
|
|
847
|
+
var propText = (_properties$get = properties.get(prop)) === null || _properties$get === void 0 ? void 0 : _properties$get.text;
|
|
848
|
+
if (propText) {
|
|
849
|
+
allPropertyTexts.push(propText);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
for (var _i4 = 0, _defaultExportProps = defaultExportProps; _i4 < _defaultExportProps.length; _i4++) {
|
|
853
|
+
var _properties$get2;
|
|
854
|
+
var _prop = _defaultExportProps[_i4];
|
|
855
|
+
var _propText = (_properties$get2 = properties.get(_prop)) === null || _properties$get2 === void 0 ? void 0 : _properties$get2.valueText;
|
|
856
|
+
if (_propText) {
|
|
857
|
+
allPropertyTexts.push(_propText);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Filter preamble to only include statements used by this mock's properties
|
|
862
|
+
var filteredPreamble = preamble ? filterPreambleForProperties(preamble, allPropertyTexts) : undefined;
|
|
863
|
+
|
|
864
|
+
// If we have a preamble, we need to use block body syntax with return statement
|
|
865
|
+
if (filteredPreamble !== null && filteredPreamble !== void 0 && filteredPreamble.hasPreamble) {
|
|
866
|
+
// Rewrite any jest.requireActual paths in the preamble
|
|
867
|
+
var preambleText = filteredPreamble.text;
|
|
868
|
+
if (originalImportPath) {
|
|
869
|
+
preambleText = rewriteRequireActualPaths({
|
|
870
|
+
text: preambleText,
|
|
871
|
+
originalPath: originalImportPath,
|
|
872
|
+
newPath: relativePath,
|
|
873
|
+
quote: quote
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Rewrite any jest.requireActual paths in property values
|
|
878
|
+
var rewrittenMockObjectProps = namedExportProps.map(function (p) {
|
|
879
|
+
var _properties$get3;
|
|
880
|
+
var propText = (_properties$get3 = properties.get(p)) === null || _properties$get3 === void 0 ? void 0 : _properties$get3.text;
|
|
881
|
+
if (propText && originalImportPath) {
|
|
882
|
+
propText = rewriteRequireActualPaths({
|
|
883
|
+
text: propText,
|
|
884
|
+
originalPath: originalImportPath,
|
|
885
|
+
newPath: relativePath,
|
|
886
|
+
quote: quote
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return propText;
|
|
890
|
+
}).filter(function (p) {
|
|
891
|
+
return !!p;
|
|
892
|
+
});
|
|
893
|
+
var mockContentLines = [];
|
|
894
|
+
if (hasRequireActual) {
|
|
895
|
+
mockContentLines.push("...jest.requireActual(".concat(quote).concat(relativePath).concat(quote, ")"));
|
|
896
|
+
}
|
|
897
|
+
mockContentLines.push.apply(mockContentLines, _toConsumableArray(rewrittenMockObjectProps));
|
|
898
|
+
var formattedContent = mockContentLines.map(function (line) {
|
|
899
|
+
return "\t\t".concat(line, ",");
|
|
900
|
+
}).join('\n');
|
|
901
|
+
return "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => {\n\t").concat(preambleText, "\n\treturn {\n").concat(formattedContent, "\n\t};\n})");
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Generate the mock (original logic for simple cases without preamble)
|
|
905
|
+
var mockCall;
|
|
906
|
+
if (defaultExportProps.length > 0 && namedExportProps.length === 0) {
|
|
907
|
+
// All props are from default export
|
|
908
|
+
if (defaultExportProps.length === 1) {
|
|
909
|
+
var _properties$get4;
|
|
910
|
+
// Single default export - use __esModule pattern
|
|
911
|
+
var mockText = ((_properties$get4 = properties.get(defaultExportProps[0])) === null || _properties$get4 === void 0 ? void 0 : _properties$get4.valueText) || '';
|
|
912
|
+
mockCall = "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => ({\n\t__esModule: true,\n\tdefault: ").concat(mockText, "\n}))");
|
|
913
|
+
} else {
|
|
914
|
+
// Multiple props from same default - shouldn't happen, but handle it
|
|
915
|
+
var mockTexts = defaultExportProps.map(function (p) {
|
|
916
|
+
var _properties$get5;
|
|
917
|
+
return (_properties$get5 = properties.get(p)) === null || _properties$get5 === void 0 ? void 0 : _properties$get5.text;
|
|
918
|
+
}).join(',\n\t');
|
|
919
|
+
mockCall = "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => ({\n\t").concat(mockTexts, "\n}))");
|
|
920
|
+
}
|
|
921
|
+
} else if (defaultExportProps.length === 0 && namedExportProps.length > 0) {
|
|
922
|
+
// All props are named exports
|
|
923
|
+
var mockObjectProps = namedExportProps.map(function (p) {
|
|
924
|
+
var _properties$get6;
|
|
925
|
+
return (_properties$get6 = properties.get(p)) === null || _properties$get6 === void 0 ? void 0 : _properties$get6.text;
|
|
926
|
+
}).filter(function (p) {
|
|
927
|
+
return !!p;
|
|
928
|
+
});
|
|
929
|
+
var _mockContentLines = [];
|
|
930
|
+
if (hasRequireActual) {
|
|
931
|
+
_mockContentLines.push("...jest.requireActual(".concat(quote).concat(relativePath).concat(quote, ")"));
|
|
932
|
+
}
|
|
933
|
+
_mockContentLines.push.apply(_mockContentLines, _toConsumableArray(mockObjectProps));
|
|
934
|
+
if (_mockContentLines.length === 1 && _mockContentLines[0].length < 60) {
|
|
935
|
+
mockCall = "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => ({ ").concat(_mockContentLines[0], " }))");
|
|
936
|
+
} else {
|
|
937
|
+
var _formattedContent = _mockContentLines.map(function (line) {
|
|
938
|
+
return "\t".concat(line, ",");
|
|
939
|
+
}).join('\n');
|
|
940
|
+
mockCall = "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => ({\n").concat(_formattedContent, "\n}))");
|
|
941
|
+
}
|
|
942
|
+
} else {
|
|
943
|
+
// Mixed: has both default and named exports
|
|
944
|
+
var defaultMock = defaultExportProps.map(function (p) {
|
|
945
|
+
var _properties$get7;
|
|
946
|
+
return (_properties$get7 = properties.get(p)) === null || _properties$get7 === void 0 ? void 0 : _properties$get7.valueText;
|
|
947
|
+
}).join(', ');
|
|
948
|
+
var namedMocks = namedExportProps.map(function (p) {
|
|
949
|
+
var _properties$get8;
|
|
950
|
+
return (_properties$get8 = properties.get(p)) === null || _properties$get8 === void 0 ? void 0 : _properties$get8.text;
|
|
951
|
+
}).filter(function (p) {
|
|
952
|
+
return !!p;
|
|
953
|
+
});
|
|
954
|
+
var _mockContentLines2 = ["__esModule: true", "default: ".concat(defaultMock)].concat(_toConsumableArray(namedMocks));
|
|
955
|
+
if (hasRequireActual) {
|
|
956
|
+
_mockContentLines2.unshift("...jest.requireActual(".concat(quote).concat(relativePath).concat(quote, ")"));
|
|
957
|
+
}
|
|
958
|
+
var _formattedContent2 = _mockContentLines2.map(function (line) {
|
|
959
|
+
return "\t".concat(line, ",");
|
|
960
|
+
}).join('\n');
|
|
961
|
+
mockCall = "jest.mock(".concat(quote).concat(relativePath).concat(quote, ", () => ({\n").concat(_formattedContent2, "\n}))");
|
|
962
|
+
}
|
|
963
|
+
return mockCall;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Generate auto-fix for mock with implementation
|
|
968
|
+
*/
|
|
969
|
+
function generateMockImplementationFix(_ref16) {
|
|
970
|
+
var propertiesBySource = _ref16.propertiesBySource,
|
|
971
|
+
mockProperties = _ref16.mockProperties,
|
|
972
|
+
hasRequireActual = _ref16.hasRequireActual,
|
|
973
|
+
basedir = _ref16.basedir,
|
|
974
|
+
importPath = _ref16.importPath,
|
|
975
|
+
quote = _ref16.quote,
|
|
976
|
+
exportMap = _ref16.exportMap,
|
|
977
|
+
context = _ref16.context,
|
|
978
|
+
currentNode = _ref16.currentNode,
|
|
979
|
+
preamble = _ref16.preamble,
|
|
980
|
+
workspaceRoot = _ref16.workspaceRoot,
|
|
981
|
+
fs = _ref16.fs;
|
|
982
|
+
var sourceFilesToMock = Array.from(propertiesBySource.entries());
|
|
983
|
+
|
|
984
|
+
// Find all existing jest.mock calls in the file
|
|
985
|
+
var allExistingMocks = findAllJestMocksInFile({
|
|
986
|
+
context: context,
|
|
987
|
+
basedir: basedir,
|
|
988
|
+
fs: fs
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// Track which nodes we need to remove and what mock calls to generate
|
|
992
|
+
var nodesToRemove = new Set();
|
|
993
|
+
var mergedMocks = new Map();
|
|
994
|
+
|
|
995
|
+
// Always remove the current barrel mock node
|
|
996
|
+
nodesToRemove.add(currentNode);
|
|
997
|
+
|
|
998
|
+
// Process each source file we're creating mocks for
|
|
999
|
+
var _loop2 = function _loop2() {
|
|
1000
|
+
var _sourceFilesToMock$_i = _slicedToArray(_sourceFilesToMock[_i5], 2),
|
|
1001
|
+
sourceFile = _sourceFilesToMock$_i[0],
|
|
1002
|
+
props = _sourceFilesToMock$_i[1];
|
|
1003
|
+
// Find the ExportInfo for this source file to get cross-package info
|
|
1004
|
+
var exportInfoForSource = Array.from(exportMap.values()).find(function (info) {
|
|
1005
|
+
return info.path === sourceFile;
|
|
1006
|
+
});
|
|
1007
|
+
var mockPath = getImportPathForSourceFile({
|
|
1008
|
+
sourceFilePath: sourceFile,
|
|
1009
|
+
basedir: basedir,
|
|
1010
|
+
originalImportPath: importPath,
|
|
1011
|
+
exportInfo: exportInfoForSource !== null && exportInfoForSource !== void 0 ? exportInfoForSource : null,
|
|
1012
|
+
workspaceRoot: workspaceRoot,
|
|
1013
|
+
fs: fs
|
|
1014
|
+
});
|
|
1015
|
+
var normalizedPath = normalizePathForComparison({
|
|
1016
|
+
basedir: basedir,
|
|
1017
|
+
importPath: mockPath,
|
|
1018
|
+
fs: fs
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
// Get properties for this source file from the barrel mock
|
|
1022
|
+
var newProperties = new Map();
|
|
1023
|
+
var _iterator12 = _createForOfIteratorHelper(props),
|
|
1024
|
+
_step12;
|
|
1025
|
+
try {
|
|
1026
|
+
for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
|
|
1027
|
+
var prop = _step12.value;
|
|
1028
|
+
var propInfo = mockProperties.get(prop);
|
|
1029
|
+
if (propInfo) {
|
|
1030
|
+
newProperties.set(prop, propInfo);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Check if there's already a mock for this path
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
_iterator12.e(err);
|
|
1037
|
+
} finally {
|
|
1038
|
+
_iterator12.f();
|
|
1039
|
+
}
|
|
1040
|
+
var existingMock = allExistingMocks.get(normalizedPath);
|
|
1041
|
+
if (existingMock && existingMock.node !== currentNode) {
|
|
1042
|
+
// Merge properties from existing mock with new properties
|
|
1043
|
+
var mergedProperties = mergeMockProperties({
|
|
1044
|
+
existingProperties: existingMock.properties,
|
|
1045
|
+
newProperties: newProperties
|
|
1046
|
+
});
|
|
1047
|
+
mergedMocks.set(normalizedPath, {
|
|
1048
|
+
mockPath: mockPath,
|
|
1049
|
+
properties: mergedProperties,
|
|
1050
|
+
hasRequireActual: existingMock.hasRequireActual || hasRequireActual
|
|
1051
|
+
});
|
|
1052
|
+
// Mark the existing mock node for removal
|
|
1053
|
+
nodesToRemove.add(existingMock.node);
|
|
1054
|
+
} else {
|
|
1055
|
+
// No existing mock, just use the new properties
|
|
1056
|
+
// For newly created mocks from barrel file splits, always include jest.requireActual.
|
|
1057
|
+
// This ensures that any properties not explicitly mocked will still be included from the original module.
|
|
1058
|
+
mergedMocks.set(normalizedPath, {
|
|
1059
|
+
mockPath: mockPath,
|
|
1060
|
+
properties: newProperties,
|
|
1061
|
+
hasRequireActual: true
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
for (var _i5 = 0, _sourceFilesToMock = sourceFilesToMock; _i5 < _sourceFilesToMock.length; _i5++) {
|
|
1066
|
+
_loop2();
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Generate mock calls for all merged mocks
|
|
1070
|
+
var replacementParts = [];
|
|
1071
|
+
var _iterator13 = _createForOfIteratorHelper(mergedMocks),
|
|
1072
|
+
_step13;
|
|
1073
|
+
try {
|
|
1074
|
+
for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
|
|
1075
|
+
var _step13$value = _slicedToArray(_step13.value, 2),
|
|
1076
|
+
mockInfo = _step13$value[1];
|
|
1077
|
+
// Find the source file for this mock path (may be relative or cross-package)
|
|
1078
|
+
// For cross-package paths (starting with @), we don't need to resolve
|
|
1079
|
+
var isCrossPackagePath = mockInfo.mockPath.startsWith('@');
|
|
1080
|
+
var absolutePath = isCrossPackagePath ? null : resolveImportPath({
|
|
1081
|
+
basedir: basedir,
|
|
1082
|
+
importPath: mockInfo.mockPath,
|
|
1083
|
+
fs: fs
|
|
1084
|
+
});
|
|
1085
|
+
if (!isCrossPackagePath && !absolutePath) {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
var mockCall = generateMockCallText({
|
|
1089
|
+
relativePath: mockInfo.mockPath,
|
|
1090
|
+
properties: mockInfo.properties,
|
|
1091
|
+
hasRequireActual: mockInfo.hasRequireActual,
|
|
1092
|
+
quote: quote,
|
|
1093
|
+
exportMap: exportMap,
|
|
1094
|
+
sourceFile: absolutePath !== null && absolutePath !== void 0 ? absolutePath : mockInfo.mockPath,
|
|
1095
|
+
preamble: preamble,
|
|
1096
|
+
originalImportPath: importPath
|
|
1097
|
+
});
|
|
1098
|
+
replacementParts.push(mockCall);
|
|
1099
|
+
}
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
_iterator13.e(err);
|
|
1102
|
+
} finally {
|
|
1103
|
+
_iterator13.f();
|
|
1104
|
+
}
|
|
1105
|
+
var replacementText = replacementParts.join(';\n');
|
|
1106
|
+
|
|
1107
|
+
// Build a map of symbol name -> new mock path for jest.requireMock() rewriting
|
|
1108
|
+
var symbolToNewMockPath = new Map();
|
|
1109
|
+
var _iterator14 = _createForOfIteratorHelper(mergedMocks),
|
|
1110
|
+
_step14;
|
|
1111
|
+
try {
|
|
1112
|
+
for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
|
|
1113
|
+
var _step14$value = _slicedToArray(_step14.value, 2),
|
|
1114
|
+
_mockInfo = _step14$value[1];
|
|
1115
|
+
var _iterator16 = _createForOfIteratorHelper(_mockInfo.properties.keys()),
|
|
1116
|
+
_step16;
|
|
1117
|
+
try {
|
|
1118
|
+
for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
|
|
1119
|
+
var propName = _step16.value;
|
|
1120
|
+
symbolToNewMockPath.set(propName, _mockInfo.mockPath);
|
|
1121
|
+
}
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
_iterator16.e(err);
|
|
1124
|
+
} finally {
|
|
1125
|
+
_iterator16.f();
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Create fixes: remove all nodes except the first, replace the first with merged mocks
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
_iterator14.e(err);
|
|
1132
|
+
} finally {
|
|
1133
|
+
_iterator14.f();
|
|
1134
|
+
}
|
|
1135
|
+
var fixes = [];
|
|
1136
|
+
var sortedNodesToRemove = Array.from(nodesToRemove).sort(function (a, b) {
|
|
1137
|
+
var _a$range$, _a$range, _b$range$, _b$range;
|
|
1138
|
+
return ((_a$range$ = (_a$range = a.range) === null || _a$range === void 0 ? void 0 : _a$range[0]) !== null && _a$range$ !== void 0 ? _a$range$ : 0) - ((_b$range$ = (_b$range = b.range) === null || _b$range === void 0 ? void 0 : _b$range[0]) !== null && _b$range$ !== void 0 ? _b$range$ : 0);
|
|
1139
|
+
});
|
|
1140
|
+
if (sortedNodesToRemove.length > 0) {
|
|
1141
|
+
// Replace the first node with all the merged mocks
|
|
1142
|
+
var firstNode = sortedNodesToRemove[0];
|
|
1143
|
+
fixes.push({
|
|
1144
|
+
range: firstNode.range,
|
|
1145
|
+
text: replacementText
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// Remove all other nodes (subsequent duplicates)
|
|
1149
|
+
for (var i = 1; i < sortedNodesToRemove.length; i++) {
|
|
1150
|
+
var nodeToRemove = sortedNodesToRemove[i];
|
|
1151
|
+
// Find the statement that contains this node to remove the entire line
|
|
1152
|
+
var sourceCode = context.getSourceCode();
|
|
1153
|
+
var tokenAfter = sourceCode.getTokenAfter(nodeToRemove);
|
|
1154
|
+
|
|
1155
|
+
// Try to remove the entire statement including semicolon and newline
|
|
1156
|
+
var startPos = nodeToRemove.range[0];
|
|
1157
|
+
var endPos = nodeToRemove.range[1];
|
|
1158
|
+
|
|
1159
|
+
// Include trailing semicolon if present
|
|
1160
|
+
if (tokenAfter && tokenAfter.type === 'Punctuator' && tokenAfter.value === ';') {
|
|
1161
|
+
endPos = tokenAfter.range[1];
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Include trailing/leading whitespace and newlines
|
|
1165
|
+
var text = sourceCode.getText();
|
|
1166
|
+
while (endPos < text.length && /[\s\n]/.test(text[endPos])) {
|
|
1167
|
+
endPos++;
|
|
1168
|
+
}
|
|
1169
|
+
fixes.push({
|
|
1170
|
+
range: [startPos, endPos],
|
|
1171
|
+
text: ''
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Fix jest.requireMock() calls that reference the old barrel path.
|
|
1177
|
+
// When we split a jest.mock('./barrel') into jest.mock('./specific-file'),
|
|
1178
|
+
// any jest.requireMock('./barrel') calls also need to be updated.
|
|
1179
|
+
var ast = context.getSourceCode().ast;
|
|
1180
|
+
var normalizedTarget = normalizePathForComparison({
|
|
1181
|
+
basedir: basedir,
|
|
1182
|
+
importPath: importPath,
|
|
1183
|
+
fs: fs
|
|
1184
|
+
});
|
|
1185
|
+
var requireMockCalls = findJestRequireMockCalls({
|
|
1186
|
+
ast: ast,
|
|
1187
|
+
matchPath: function matchPath(candidatePath) {
|
|
1188
|
+
return normalizePathForComparison({
|
|
1189
|
+
basedir: basedir,
|
|
1190
|
+
importPath: candidatePath,
|
|
1191
|
+
fs: fs
|
|
1192
|
+
}) === normalizedTarget;
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
var _iterator15 = _createForOfIteratorHelper(requireMockCalls),
|
|
1196
|
+
_step15;
|
|
1197
|
+
try {
|
|
1198
|
+
for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
|
|
1199
|
+
var requireMockNode = _step15.value;
|
|
1200
|
+
var requireMockArg = requireMockNode.arguments[0];
|
|
1201
|
+
if (!requireMockArg) {
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
var newPath = resolveNewPathForRequireMock({
|
|
1205
|
+
requireMockNode: requireMockNode,
|
|
1206
|
+
symbolToNewPath: symbolToNewMockPath
|
|
1207
|
+
});
|
|
1208
|
+
if (newPath) {
|
|
1209
|
+
fixes.push({
|
|
1210
|
+
range: requireMockArg.range,
|
|
1211
|
+
text: "".concat(quote).concat(newPath).concat(quote)
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
_iterator15.e(err);
|
|
1217
|
+
} finally {
|
|
1218
|
+
_iterator15.f();
|
|
1219
|
+
}
|
|
1220
|
+
return fixes;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Metadata for the ESLint rule
|
|
1225
|
+
*/
|
|
1226
|
+
var ruleMeta = {
|
|
1227
|
+
type: 'problem',
|
|
1228
|
+
docs: {
|
|
1229
|
+
description: 'Warn when jest.mock is used on a relative import path from a barrel file, and provide an auto-fix to split mocks by source file.',
|
|
1230
|
+
category: 'Best Practices',
|
|
1231
|
+
recommended: false
|
|
1232
|
+
},
|
|
1233
|
+
fixable: 'code',
|
|
1234
|
+
messages: {
|
|
1235
|
+
barrelMock: "jest.mock('{{path}}') is mocking a barrel file. This should be split into separate mocks for each source file to improve performance. Use auto-fix to resolve."
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Factory function to create the ESLint rule with a given file system.
|
|
1241
|
+
* This enables testing with mock file systems.
|
|
1242
|
+
*/
|
|
1243
|
+
export function createRule(fs) {
|
|
1244
|
+
return {
|
|
1245
|
+
meta: ruleMeta,
|
|
1246
|
+
create: function create(context) {
|
|
1247
|
+
return {
|
|
1248
|
+
CallExpression: function CallExpression(rawNode) {
|
|
1249
|
+
var node = rawNode;
|
|
1250
|
+
|
|
1251
|
+
// Step 1: Validate this is a jest.mock call
|
|
1252
|
+
if (!isJestMockCall(node)) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Step 2: Extract the import path
|
|
1257
|
+
var importPath = extractImportPath(node);
|
|
1258
|
+
if (!importPath) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Step 3: Validate and resolve barrel file
|
|
1263
|
+
var basedir = dirname(context.filename);
|
|
1264
|
+
var workspaceRoot = findWorkspaceRoot({
|
|
1265
|
+
startPath: basedir,
|
|
1266
|
+
fs: fs
|
|
1267
|
+
});
|
|
1268
|
+
var barrelInfo = validateAndResolveBarrelFile({
|
|
1269
|
+
importPath: importPath,
|
|
1270
|
+
basedir: basedir,
|
|
1271
|
+
workspaceRoot: workspaceRoot,
|
|
1272
|
+
fs: fs
|
|
1273
|
+
});
|
|
1274
|
+
if (!barrelInfo) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
var exportMap = barrelInfo.exportMap,
|
|
1278
|
+
barrelFilePath = barrelInfo.resolvedPath;
|
|
1279
|
+
var sourceCode = context.getSourceCode();
|
|
1280
|
+
var firstArg = node.arguments[0];
|
|
1281
|
+
|
|
1282
|
+
// Step 4: Handle auto-mock case (no mock implementation)
|
|
1283
|
+
var mockImpl = node.arguments[1];
|
|
1284
|
+
if (!mockImpl) {
|
|
1285
|
+
// Group exports by source file, filtering out type-only source files
|
|
1286
|
+
var sourceFilesWithNonTypeExports = new Set();
|
|
1287
|
+
var _iterator17 = _createForOfIteratorHelper(exportMap),
|
|
1288
|
+
_step17;
|
|
1289
|
+
try {
|
|
1290
|
+
for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
|
|
1291
|
+
var _step17$value = _slicedToArray(_step17.value, 2),
|
|
1292
|
+
info = _step17$value[1];
|
|
1293
|
+
if (!info.isTypeOnly) {
|
|
1294
|
+
sourceFilesWithNonTypeExports.add(info.path);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
} catch (err) {
|
|
1298
|
+
_iterator17.e(err);
|
|
1299
|
+
} finally {
|
|
1300
|
+
_iterator17.f();
|
|
1301
|
+
}
|
|
1302
|
+
if (sourceFilesWithNonTypeExports.size === 0) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
context.report({
|
|
1306
|
+
node: node,
|
|
1307
|
+
messageId: 'barrelMock',
|
|
1308
|
+
data: {
|
|
1309
|
+
path: importPath
|
|
1310
|
+
},
|
|
1311
|
+
fix: function fix(fixer) {
|
|
1312
|
+
var quote = sourceCode.getText(firstArg)[0];
|
|
1313
|
+
var replacement = generateAutoMockFix({
|
|
1314
|
+
exportMap: exportMap,
|
|
1315
|
+
basedir: basedir,
|
|
1316
|
+
importPath: importPath,
|
|
1317
|
+
quote: quote,
|
|
1318
|
+
workspaceRoot: workspaceRoot,
|
|
1319
|
+
fs: fs
|
|
1320
|
+
});
|
|
1321
|
+
return fixer.replaceText(node, replacement);
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Step 5: Extract mock implementation and properties
|
|
1328
|
+
var mockObjectNode = extractMockImplementation(mockImpl);
|
|
1329
|
+
var _extractMockPropertie2 = extractMockProperties({
|
|
1330
|
+
sourceCode: sourceCode,
|
|
1331
|
+
mockObjectNode: mockObjectNode
|
|
1332
|
+
}),
|
|
1333
|
+
mockProperties = _extractMockPropertie2.properties,
|
|
1334
|
+
hasRequireActual = _extractMockPropertie2.hasRequireActual;
|
|
1335
|
+
|
|
1336
|
+
// Extract preamble (variable declarations, etc.) from the mock factory
|
|
1337
|
+
var preamble = extractMockFactoryPreamble({
|
|
1338
|
+
mockImpl: mockImpl,
|
|
1339
|
+
sourceCode: sourceCode
|
|
1340
|
+
});
|
|
1341
|
+
if (mockProperties.size === 0) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// Step 6: Group properties by their source files
|
|
1346
|
+
var propertiesBySource = groupPropertiesBySource({
|
|
1347
|
+
mockProperties: mockProperties,
|
|
1348
|
+
exportMap: exportMap
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
// Step 7: Determine if we should report
|
|
1352
|
+
if (!shouldReportBarrelMock({
|
|
1353
|
+
propertiesBySource: propertiesBySource,
|
|
1354
|
+
barrelFilePath: barrelFilePath
|
|
1355
|
+
})) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Step 8: Report with auto-fix
|
|
1360
|
+
context.report({
|
|
1361
|
+
node: node,
|
|
1362
|
+
messageId: 'barrelMock',
|
|
1363
|
+
data: {
|
|
1364
|
+
path: importPath
|
|
1365
|
+
},
|
|
1366
|
+
fix: function fix(_fixer) {
|
|
1367
|
+
var quote = sourceCode.getText(firstArg)[0];
|
|
1368
|
+
var fixes = generateMockImplementationFix({
|
|
1369
|
+
propertiesBySource: propertiesBySource,
|
|
1370
|
+
mockProperties: mockProperties,
|
|
1371
|
+
hasRequireActual: hasRequireActual,
|
|
1372
|
+
basedir: basedir,
|
|
1373
|
+
importPath: importPath,
|
|
1374
|
+
quote: quote,
|
|
1375
|
+
exportMap: exportMap,
|
|
1376
|
+
context: context,
|
|
1377
|
+
currentNode: node,
|
|
1378
|
+
preamble: preamble,
|
|
1379
|
+
workspaceRoot: workspaceRoot,
|
|
1380
|
+
fs: fs
|
|
1381
|
+
});
|
|
1382
|
+
return fixes;
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
var rule = createRule(realFileSystem);
|
|
1391
|
+
export default rule;
|