Skip to content

Commit 7a28ba2

Browse files
soryy708ljharb
authored andcommitted
[Refactor] ExportMap: separate ExportMap instance from its builder logic
1 parent c77c1a6 commit 7a28ba2

14 files changed

+242
-236
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
{
230230
"files": [
231231
"utils/**", // TODO
232-
"src/ExportMap.js", // TODO
232+
"src/exportMapBuilder.js", // TODO
233233
],
234234
"rules": {
235235
"no-use-before-define": "off",

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1313
- [Docs] `no-extraneous-dependencies`: Make glob pattern description more explicit ([#2944], thanks [@mulztob])
1414
- [`no-unused-modules`]: add console message to help debug [#2866]
1515
- [Refactor] `ExportMap`: make procedures static instead of monkeypatching exportmap ([#2982], thanks [@soryy708])
16+
- [Refactor] `ExportMap`: separate ExportMap instance from its builder logic ([#2985], thanks [@soryy708])
1617

1718
## [2.29.1] - 2023-12-14
1819

@@ -1109,6 +1110,7 @@ for info on changes for earlier releases.
11091110

11101111
[`memo-parser`]: ./memo-parser/README.md
11111112

1113+
[#2985]: https://github.com/import-js/eslint-plugin-import/pull/2985
11121114
[#2982]: https://github.com/import-js/eslint-plugin-import/pull/2982
11131115
[#2944]: https://github.com/import-js/eslint-plugin-import/pull/2944
11141116
[#2942]: https://github.com/import-js/eslint-plugin-import/pull/2942

src/exportMap.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
export default class ExportMap {
2+
constructor(path) {
3+
this.path = path;
4+
this.namespace = new Map();
5+
// todo: restructure to key on path, value is resolver + map of names
6+
this.reexports = new Map();
7+
/**
8+
* star-exports
9+
* @type {Set<() => ExportMap>}
10+
*/
11+
this.dependencies = new Set();
12+
/**
13+
* dependencies of this module that are not explicitly re-exported
14+
* @type {Map<string, () => ExportMap>}
15+
*/
16+
this.imports = new Map();
17+
this.errors = [];
18+
/**
19+
* type {'ambiguous' | 'Module' | 'Script'}
20+
*/
21+
this.parseGoal = 'ambiguous';
22+
}
23+
24+
get hasDefault() { return this.get('default') != null; } // stronger than this.has
25+
26+
get size() {
27+
let size = this.namespace.size + this.reexports.size;
28+
this.dependencies.forEach((dep) => {
29+
const d = dep();
30+
// CJS / ignored dependencies won't exist (#717)
31+
if (d == null) { return; }
32+
size += d.size;
33+
});
34+
return size;
35+
}
36+
37+
/**
38+
* Note that this does not check explicitly re-exported names for existence
39+
* in the base namespace, but it will expand all `export * from '...'` exports
40+
* if not found in the explicit namespace.
41+
* @param {string} name
42+
* @return {boolean} true if `name` is exported by this module.
43+
*/
44+
has(name) {
45+
if (this.namespace.has(name)) { return true; }
46+
if (this.reexports.has(name)) { return true; }
47+
48+
// default exports must be explicitly re-exported (#328)
49+
if (name !== 'default') {
50+
for (const dep of this.dependencies) {
51+
const innerMap = dep();
52+
53+
// todo: report as unresolved?
54+
if (!innerMap) { continue; }
55+
56+
if (innerMap.has(name)) { return true; }
57+
}
58+
}
59+
60+
return false;
61+
}
62+
63+
/**
64+
* ensure that imported name fully resolves.
65+
* @param {string} name
66+
* @return {{ found: boolean, path: ExportMap[] }}
67+
*/
68+
hasDeep(name) {
69+
if (this.namespace.has(name)) { return { found: true, path: [this] }; }
70+
71+
if (this.reexports.has(name)) {
72+
const reexports = this.reexports.get(name);
73+
const imported = reexports.getImport();
74+
75+
// if import is ignored, return explicit 'null'
76+
if (imported == null) { return { found: true, path: [this] }; }
77+
78+
// safeguard against cycles, only if name matches
79+
if (imported.path === this.path && reexports.local === name) {
80+
return { found: false, path: [this] };
81+
}
82+
83+
const deep = imported.hasDeep(reexports.local);
84+
deep.path.unshift(this);
85+
86+
return deep;
87+
}
88+
89+
// default exports must be explicitly re-exported (#328)
90+
if (name !== 'default') {
91+
for (const dep of this.dependencies) {
92+
const innerMap = dep();
93+
if (innerMap == null) { return { found: true, path: [this] }; }
94+
// todo: report as unresolved?
95+
if (!innerMap) { continue; }
96+
97+
// safeguard against cycles
98+
if (innerMap.path === this.path) { continue; }
99+
100+
const innerValue = innerMap.hasDeep(name);
101+
if (innerValue.found) {
102+
innerValue.path.unshift(this);
103+
return innerValue;
104+
}
105+
}
106+
}
107+
108+
return { found: false, path: [this] };
109+
}
110+
111+
get(name) {
112+
if (this.namespace.has(name)) { return this.namespace.get(name); }
113+
114+
if (this.reexports.has(name)) {
115+
const reexports = this.reexports.get(name);
116+
const imported = reexports.getImport();
117+
118+
// if import is ignored, return explicit 'null'
119+
if (imported == null) { return null; }
120+
121+
// safeguard against cycles, only if name matches
122+
if (imported.path === this.path && reexports.local === name) { return undefined; }
123+
124+
return imported.get(reexports.local);
125+
}
126+
127+
// default exports must be explicitly re-exported (#328)
128+
if (name !== 'default') {
129+
for (const dep of this.dependencies) {
130+
const innerMap = dep();
131+
// todo: report as unresolved?
132+
if (!innerMap) { continue; }
133+
134+
// safeguard against cycles
135+
if (innerMap.path === this.path) { continue; }
136+
137+
const innerValue = innerMap.get(name);
138+
if (innerValue !== undefined) { return innerValue; }
139+
}
140+
}
141+
142+
return undefined;
143+
}
144+
145+
forEach(callback, thisArg) {
146+
this.namespace.forEach((v, n) => { callback.call(thisArg, v, n, this); });
147+
148+
this.reexports.forEach((reexports, name) => {
149+
const reexported = reexports.getImport();
150+
// can't look up meta for ignored re-exports (#348)
151+
callback.call(thisArg, reexported && reexported.get(reexports.local), name, this);
152+
});
153+
154+
this.dependencies.forEach((dep) => {
155+
const d = dep();
156+
// CJS / ignored dependencies won't exist (#717)
157+
if (d == null) { return; }
158+
159+
d.forEach((v, n) => {
160+
if (n !== 'default') {
161+
callback.call(thisArg, v, n, this);
162+
}
163+
});
164+
});
165+
}
166+
167+
// todo: keys, values, entries?
168+
169+
reportErrors(context, declaration) {
170+
const msg = this.errors
171+
.map((e) => `${e.message} (${e.lineNumber}:${e.column})`)
172+
.join(', ');
173+
context.report({
174+
node: declaration.source,
175+
message: `Parse errors in imported module '${declaration.source.value}': ${msg}`,
176+
});
177+
}
178+
}

0 commit comments

Comments
 (0)