UNPKG

9.22 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Sergey Melyukov @smelukov
4*/
5
6"use strict";
7
8const { UsageState } = require("../ExportsInfo");
9
10/** @typedef {import("estree").Node} AnyNode */
11/** @typedef {import("../Dependency")} Dependency */
12/** @typedef {import("../ModuleGraph")} ModuleGraph */
13/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
14/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
15/** @typedef {import("../Parser").ParserState} ParserState */
16/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
17/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
18
19/** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true>} InnerGraph */
20/** @typedef {function(boolean | Set<string> | undefined): void} UsageCallback */
21
22/**
23 * @typedef {Object} StateObject
24 * @property {InnerGraph} innerGraph
25 * @property {TopLevelSymbol=} currentTopLevelSymbol
26 * @property {Map<TopLevelSymbol, Set<UsageCallback>>} usageCallbackMap
27 */
28
29/** @typedef {false|StateObject} State */
30
31/** @type {WeakMap<ParserState, State>} */
32const parserStateMap = new WeakMap();
33const topLevelSymbolTag = Symbol("top level symbol");
34
35/**
36 * @param {ParserState} parserState parser state
37 * @returns {State} state
38 */
39function getState(parserState) {
40 return parserStateMap.get(parserState);
41}
42
43/**
44 * @param {ParserState} parserState parser state
45 * @returns {void}
46 */
47exports.bailout = parserState => {
48 parserStateMap.set(parserState, false);
49};
50
51/**
52 * @param {ParserState} parserState parser state
53 * @returns {void}
54 */
55exports.enable = parserState => {
56 const state = parserStateMap.get(parserState);
57 if (state === false) {
58 return;
59 }
60 parserStateMap.set(parserState, {
61 innerGraph: new Map(),
62 currentTopLevelSymbol: undefined,
63 usageCallbackMap: new Map()
64 });
65};
66
67/**
68 * @param {ParserState} parserState parser state
69 * @returns {boolean} true, when enabled
70 */
71exports.isEnabled = parserState => {
72 const state = parserStateMap.get(parserState);
73 return !!state;
74};
75
76/**
77 * @param {ParserState} state parser state
78 * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols
79 * @param {string | TopLevelSymbol | true} usage usage data
80 * @returns {void}
81 */
82exports.addUsage = (state, symbol, usage) => {
83 const innerGraphState = getState(state);
84
85 if (innerGraphState) {
86 const { innerGraph } = innerGraphState;
87 const info = innerGraph.get(symbol);
88 if (usage === true) {
89 innerGraph.set(symbol, true);
90 } else if (info === undefined) {
91 innerGraph.set(symbol, new Set([usage]));
92 } else if (info !== true) {
93 info.add(usage);
94 }
95 }
96};
97
98/**
99 * @param {JavascriptParser} parser the parser
100 * @param {string} name name of variable
101 * @param {string | TopLevelSymbol | true} usage usage data
102 * @returns {void}
103 */
104exports.addVariableUsage = (parser, name, usage) => {
105 const symbol =
106 /** @type {TopLevelSymbol} */ (
107 parser.getTagData(name, topLevelSymbolTag)
108 ) || exports.tagTopLevelSymbol(parser, name);
109 if (symbol) {
110 exports.addUsage(parser.state, symbol, usage);
111 }
112};
113
114/**
115 * @param {ParserState} state parser state
116 * @returns {void}
117 */
118exports.inferDependencyUsage = state => {
119 const innerGraphState = getState(state);
120
121 if (!innerGraphState) {
122 return;
123 }
124
125 const { innerGraph, usageCallbackMap } = innerGraphState;
126 const processed = new Map();
127 // flatten graph to terminal nodes (string, undefined or true)
128 const nonTerminal = new Set(innerGraph.keys());
129 while (nonTerminal.size > 0) {
130 for (const key of nonTerminal) {
131 /** @type {Set<string|TopLevelSymbol> | true} */
132 let newSet = new Set();
133 let isTerminal = true;
134 const value = innerGraph.get(key);
135 let alreadyProcessed = processed.get(key);
136 if (alreadyProcessed === undefined) {
137 alreadyProcessed = new Set();
138 processed.set(key, alreadyProcessed);
139 }
140 if (value !== true && value !== undefined) {
141 for (const item of value) {
142 alreadyProcessed.add(item);
143 }
144 for (const item of value) {
145 if (typeof item === "string") {
146 newSet.add(item);
147 } else {
148 const itemValue = innerGraph.get(item);
149 if (itemValue === true) {
150 newSet = true;
151 break;
152 }
153 if (itemValue !== undefined) {
154 for (const i of itemValue) {
155 if (i === key) continue;
156 if (alreadyProcessed.has(i)) continue;
157 newSet.add(i);
158 if (typeof i !== "string") {
159 isTerminal = false;
160 }
161 }
162 }
163 }
164 }
165 if (newSet === true) {
166 innerGraph.set(key, true);
167 } else if (newSet.size === 0) {
168 innerGraph.set(key, undefined);
169 } else {
170 innerGraph.set(key, newSet);
171 }
172 }
173 if (isTerminal) {
174 nonTerminal.delete(key);
175
176 // For the global key, merge with all other keys
177 if (key === null) {
178 const globalValue = innerGraph.get(null);
179 if (globalValue) {
180 for (const [key, value] of innerGraph) {
181 if (key !== null && value !== true) {
182 if (globalValue === true) {
183 innerGraph.set(key, true);
184 } else {
185 const newSet = new Set(value);
186 for (const item of globalValue) {
187 newSet.add(item);
188 }
189 innerGraph.set(key, newSet);
190 }
191 }
192 }
193 }
194 }
195 }
196 }
197 }
198
199 /** @type {Map<Dependency, true | Set<string>>} */
200 for (const [symbol, callbacks] of usageCallbackMap) {
201 const usage = /** @type {true | Set<string> | undefined} */ (
202 innerGraph.get(symbol)
203 );
204 for (const callback of callbacks) {
205 callback(usage === undefined ? false : usage);
206 }
207 }
208};
209
210/**
211 * @param {ParserState} state parser state
212 * @param {UsageCallback} onUsageCallback on usage callback
213 */
214exports.onUsage = (state, onUsageCallback) => {
215 const innerGraphState = getState(state);
216
217 if (innerGraphState) {
218 const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState;
219 if (currentTopLevelSymbol) {
220 let callbacks = usageCallbackMap.get(currentTopLevelSymbol);
221
222 if (callbacks === undefined) {
223 callbacks = new Set();
224 usageCallbackMap.set(currentTopLevelSymbol, callbacks);
225 }
226
227 callbacks.add(onUsageCallback);
228 } else {
229 onUsageCallback(true);
230 }
231 } else {
232 onUsageCallback(undefined);
233 }
234};
235
236/**
237 * @param {ParserState} state parser state
238 * @param {TopLevelSymbol} symbol the symbol
239 */
240exports.setTopLevelSymbol = (state, symbol) => {
241 const innerGraphState = getState(state);
242
243 if (innerGraphState) {
244 innerGraphState.currentTopLevelSymbol = symbol;
245 }
246};
247
248/**
249 * @param {ParserState} state parser state
250 * @returns {TopLevelSymbol|void} usage data
251 */
252exports.getTopLevelSymbol = state => {
253 const innerGraphState = getState(state);
254
255 if (innerGraphState) {
256 return innerGraphState.currentTopLevelSymbol;
257 }
258};
259
260/**
261 * @param {JavascriptParser} parser parser
262 * @param {string} name name of variable
263 * @returns {TopLevelSymbol} symbol
264 */
265exports.tagTopLevelSymbol = (parser, name) => {
266 const innerGraphState = getState(parser.state);
267 if (!innerGraphState) return;
268
269 parser.defineVariable(name);
270
271 const existingTag = /** @type {TopLevelSymbol} */ (
272 parser.getTagData(name, topLevelSymbolTag)
273 );
274 if (existingTag) {
275 return existingTag;
276 }
277
278 const fn = new TopLevelSymbol(name);
279 parser.tagVariable(name, topLevelSymbolTag, fn);
280 return fn;
281};
282
283/**
284 * @param {Dependency} dependency the dependency
285 * @param {Set<string> | boolean} usedByExports usedByExports info
286 * @param {ModuleGraph} moduleGraph moduleGraph
287 * @param {RuntimeSpec} runtime runtime
288 * @returns {boolean} false, when unused. Otherwise true
289 */
290exports.isDependencyUsedByExports = (
291 dependency,
292 usedByExports,
293 moduleGraph,
294 runtime
295) => {
296 if (usedByExports === false) return false;
297 if (usedByExports !== true && usedByExports !== undefined) {
298 const selfModule = moduleGraph.getParentModule(dependency);
299 const exportsInfo = moduleGraph.getExportsInfo(selfModule);
300 let used = false;
301 for (const exportName of usedByExports) {
302 if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
303 used = true;
304 }
305 if (!used) return false;
306 }
307 return true;
308};
309
310/**
311 * @param {Dependency} dependency the dependency
312 * @param {Set<string> | boolean} usedByExports usedByExports info
313 * @param {ModuleGraph} moduleGraph moduleGraph
314 * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active
315 */
316exports.getDependencyUsedByExportsCondition = (
317 dependency,
318 usedByExports,
319 moduleGraph
320) => {
321 if (usedByExports === false) return false;
322 if (usedByExports !== true && usedByExports !== undefined) {
323 const selfModule = moduleGraph.getParentModule(dependency);
324 const exportsInfo = moduleGraph.getExportsInfo(selfModule);
325 return (connections, runtime) => {
326 for (const exportName of usedByExports) {
327 if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
328 return true;
329 }
330 return false;
331 };
332 }
333 return null;
334};
335
336class TopLevelSymbol {
337 /**
338 * @param {string} name name of the variable
339 */
340 constructor(name) {
341 this.name = name;
342 }
343}
344
345exports.TopLevelSymbol = TopLevelSymbol;
346exports.topLevelSymbolTag = topLevelSymbolTag;