You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							347 lines
						
					
					
						
							9.1 KiB
						
					
					
				
			
		
		
	
	
							347 lines
						
					
					
						
							9.1 KiB
						
					
					
				| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Sergey Melyukov @smelukov
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const { UsageState } = require("../ExportsInfo");
 | |
| 
 | |
| /** @typedef {import("estree").Node} AnyNode */
 | |
| /** @typedef {import("../Dependency")} Dependency */
 | |
| /** @typedef {import("../ModuleGraph")} ModuleGraph */
 | |
| /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
 | |
| /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
 | |
| /** @typedef {import("../Parser").ParserState} ParserState */
 | |
| /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
 | |
| /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
 | |
| 
 | |
| /** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true | undefined>} InnerGraph */
 | |
| /** @typedef {function(boolean | Set<string> | undefined): void} UsageCallback */
 | |
| 
 | |
| /**
 | |
|  * @typedef {Object} StateObject
 | |
|  * @property {InnerGraph} innerGraph
 | |
|  * @property {TopLevelSymbol=} currentTopLevelSymbol
 | |
|  * @property {Map<TopLevelSymbol, Set<UsageCallback>>} usageCallbackMap
 | |
|  */
 | |
| 
 | |
| /** @typedef {false|StateObject} State */
 | |
| 
 | |
| /** @type {WeakMap<ParserState, State>} */
 | |
| const parserStateMap = new WeakMap();
 | |
| const topLevelSymbolTag = Symbol("top level symbol");
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} parserState parser state
 | |
|  * @returns {State | undefined} state
 | |
|  */
 | |
| function getState(parserState) {
 | |
| 	return parserStateMap.get(parserState);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} parserState parser state
 | |
|  * @returns {void}
 | |
|  */
 | |
| exports.bailout = parserState => {
 | |
| 	parserStateMap.set(parserState, false);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} parserState parser state
 | |
|  * @returns {void}
 | |
|  */
 | |
| exports.enable = parserState => {
 | |
| 	const state = parserStateMap.get(parserState);
 | |
| 	if (state === false) {
 | |
| 		return;
 | |
| 	}
 | |
| 	parserStateMap.set(parserState, {
 | |
| 		innerGraph: new Map(),
 | |
| 		currentTopLevelSymbol: undefined,
 | |
| 		usageCallbackMap: new Map()
 | |
| 	});
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} parserState parser state
 | |
|  * @returns {boolean} true, when enabled
 | |
|  */
 | |
| exports.isEnabled = parserState => {
 | |
| 	const state = parserStateMap.get(parserState);
 | |
| 	return !!state;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} state parser state
 | |
|  * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols
 | |
|  * @param {string | TopLevelSymbol | true} usage usage data
 | |
|  * @returns {void}
 | |
|  */
 | |
| exports.addUsage = (state, symbol, usage) => {
 | |
| 	const innerGraphState = getState(state);
 | |
| 
 | |
| 	if (innerGraphState) {
 | |
| 		const { innerGraph } = innerGraphState;
 | |
| 		const info = innerGraph.get(symbol);
 | |
| 		if (usage === true) {
 | |
| 			innerGraph.set(symbol, true);
 | |
| 		} else if (info === undefined) {
 | |
| 			innerGraph.set(symbol, new Set([usage]));
 | |
| 		} else if (info !== true) {
 | |
| 			info.add(usage);
 | |
| 		}
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {JavascriptParser} parser the parser
 | |
|  * @param {string} name name of variable
 | |
|  * @param {string | TopLevelSymbol | true} usage usage data
 | |
|  * @returns {void}
 | |
|  */
 | |
| exports.addVariableUsage = (parser, name, usage) => {
 | |
| 	const symbol =
 | |
| 		/** @type {TopLevelSymbol} */ (
 | |
| 			parser.getTagData(name, topLevelSymbolTag)
 | |
| 		) || exports.tagTopLevelSymbol(parser, name);
 | |
| 	if (symbol) {
 | |
| 		exports.addUsage(parser.state, symbol, usage);
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} state parser state
 | |
|  * @returns {void}
 | |
|  */
 | |
| exports.inferDependencyUsage = state => {
 | |
| 	const innerGraphState = getState(state);
 | |
| 
 | |
| 	if (!innerGraphState) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const { innerGraph, usageCallbackMap } = innerGraphState;
 | |
| 	const processed = new Map();
 | |
| 	// flatten graph to terminal nodes (string, undefined or true)
 | |
| 	const nonTerminal = new Set(innerGraph.keys());
 | |
| 	while (nonTerminal.size > 0) {
 | |
| 		for (const key of nonTerminal) {
 | |
| 			/** @type {Set<string|TopLevelSymbol> | true} */
 | |
| 			let newSet = new Set();
 | |
| 			let isTerminal = true;
 | |
| 			const value = innerGraph.get(key);
 | |
| 			let alreadyProcessed = processed.get(key);
 | |
| 			if (alreadyProcessed === undefined) {
 | |
| 				alreadyProcessed = new Set();
 | |
| 				processed.set(key, alreadyProcessed);
 | |
| 			}
 | |
| 			if (value !== true && value !== undefined) {
 | |
| 				for (const item of value) {
 | |
| 					alreadyProcessed.add(item);
 | |
| 				}
 | |
| 				for (const item of value) {
 | |
| 					if (typeof item === "string") {
 | |
| 						newSet.add(item);
 | |
| 					} else {
 | |
| 						const itemValue = innerGraph.get(item);
 | |
| 						if (itemValue === true) {
 | |
| 							newSet = true;
 | |
| 							break;
 | |
| 						}
 | |
| 						if (itemValue !== undefined) {
 | |
| 							for (const i of itemValue) {
 | |
| 								if (i === key) continue;
 | |
| 								if (alreadyProcessed.has(i)) continue;
 | |
| 								newSet.add(i);
 | |
| 								if (typeof i !== "string") {
 | |
| 									isTerminal = false;
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				if (newSet === true) {
 | |
| 					innerGraph.set(key, true);
 | |
| 				} else if (newSet.size === 0) {
 | |
| 					innerGraph.set(key, undefined);
 | |
| 				} else {
 | |
| 					innerGraph.set(key, newSet);
 | |
| 				}
 | |
| 			}
 | |
| 			if (isTerminal) {
 | |
| 				nonTerminal.delete(key);
 | |
| 
 | |
| 				// For the global key, merge with all other keys
 | |
| 				if (key === null) {
 | |
| 					const globalValue = innerGraph.get(null);
 | |
| 					if (globalValue) {
 | |
| 						for (const [key, value] of innerGraph) {
 | |
| 							if (key !== null && value !== true) {
 | |
| 								if (globalValue === true) {
 | |
| 									innerGraph.set(key, true);
 | |
| 								} else {
 | |
| 									const newSet = new Set(value);
 | |
| 									for (const item of globalValue) {
 | |
| 										newSet.add(item);
 | |
| 									}
 | |
| 									innerGraph.set(key, newSet);
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/** @type {Map<Dependency, true | Set<string>>} */
 | |
| 	for (const [symbol, callbacks] of usageCallbackMap) {
 | |
| 		const usage = /** @type {true | Set<string> | undefined} */ (
 | |
| 			innerGraph.get(symbol)
 | |
| 		);
 | |
| 		for (const callback of callbacks) {
 | |
| 			callback(usage === undefined ? false : usage);
 | |
| 		}
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} state parser state
 | |
|  * @param {UsageCallback} onUsageCallback on usage callback
 | |
|  */
 | |
| exports.onUsage = (state, onUsageCallback) => {
 | |
| 	const innerGraphState = getState(state);
 | |
| 
 | |
| 	if (innerGraphState) {
 | |
| 		const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState;
 | |
| 		if (currentTopLevelSymbol) {
 | |
| 			let callbacks = usageCallbackMap.get(currentTopLevelSymbol);
 | |
| 
 | |
| 			if (callbacks === undefined) {
 | |
| 				callbacks = new Set();
 | |
| 				usageCallbackMap.set(currentTopLevelSymbol, callbacks);
 | |
| 			}
 | |
| 
 | |
| 			callbacks.add(onUsageCallback);
 | |
| 		} else {
 | |
| 			onUsageCallback(true);
 | |
| 		}
 | |
| 	} else {
 | |
| 		onUsageCallback(undefined);
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} state parser state
 | |
|  * @param {TopLevelSymbol | undefined} symbol the symbol
 | |
|  */
 | |
| exports.setTopLevelSymbol = (state, symbol) => {
 | |
| 	const innerGraphState = getState(state);
 | |
| 
 | |
| 	if (innerGraphState) {
 | |
| 		innerGraphState.currentTopLevelSymbol = symbol;
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {ParserState} state parser state
 | |
|  * @returns {TopLevelSymbol|void} usage data
 | |
|  */
 | |
| exports.getTopLevelSymbol = state => {
 | |
| 	const innerGraphState = getState(state);
 | |
| 
 | |
| 	if (innerGraphState) {
 | |
| 		return innerGraphState.currentTopLevelSymbol;
 | |
| 	}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {JavascriptParser} parser parser
 | |
|  * @param {string} name name of variable
 | |
|  * @returns {TopLevelSymbol | undefined} symbol
 | |
|  */
 | |
| exports.tagTopLevelSymbol = (parser, name) => {
 | |
| 	const innerGraphState = getState(parser.state);
 | |
| 	if (!innerGraphState) return;
 | |
| 
 | |
| 	parser.defineVariable(name);
 | |
| 
 | |
| 	const existingTag = /** @type {TopLevelSymbol} */ (
 | |
| 		parser.getTagData(name, topLevelSymbolTag)
 | |
| 	);
 | |
| 	if (existingTag) {
 | |
| 		return existingTag;
 | |
| 	}
 | |
| 
 | |
| 	const fn = new TopLevelSymbol(name);
 | |
| 	parser.tagVariable(name, topLevelSymbolTag, fn);
 | |
| 	return fn;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {Dependency} dependency the dependency
 | |
|  * @param {Set<string> | boolean} usedByExports usedByExports info
 | |
|  * @param {ModuleGraph} moduleGraph moduleGraph
 | |
|  * @param {RuntimeSpec} runtime runtime
 | |
|  * @returns {boolean} false, when unused. Otherwise true
 | |
|  */
 | |
| exports.isDependencyUsedByExports = (
 | |
| 	dependency,
 | |
| 	usedByExports,
 | |
| 	moduleGraph,
 | |
| 	runtime
 | |
| ) => {
 | |
| 	if (usedByExports === false) return false;
 | |
| 	if (usedByExports !== true && usedByExports !== undefined) {
 | |
| 		const selfModule = moduleGraph.getParentModule(dependency);
 | |
| 		const exportsInfo = moduleGraph.getExportsInfo(selfModule);
 | |
| 		let used = false;
 | |
| 		for (const exportName of usedByExports) {
 | |
| 			if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
 | |
| 				used = true;
 | |
| 		}
 | |
| 		if (!used) return false;
 | |
| 	}
 | |
| 	return true;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @param {Dependency} dependency the dependency
 | |
|  * @param {Set<string> | boolean | undefined} usedByExports usedByExports info
 | |
|  * @param {ModuleGraph} moduleGraph moduleGraph
 | |
|  * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active
 | |
|  */
 | |
| exports.getDependencyUsedByExportsCondition = (
 | |
| 	dependency,
 | |
| 	usedByExports,
 | |
| 	moduleGraph
 | |
| ) => {
 | |
| 	if (usedByExports === false) return false;
 | |
| 	if (usedByExports !== true && usedByExports !== undefined) {
 | |
| 		const selfModule = moduleGraph.getParentModule(dependency);
 | |
| 		const exportsInfo = moduleGraph.getExportsInfo(selfModule);
 | |
| 		return (connections, runtime) => {
 | |
| 			for (const exportName of usedByExports) {
 | |
| 				if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
 | |
| 					return true;
 | |
| 			}
 | |
| 			return false;
 | |
| 		};
 | |
| 	}
 | |
| 	return null;
 | |
| };
 | |
| 
 | |
| class TopLevelSymbol {
 | |
| 	/**
 | |
| 	 * @param {string} name name of the variable
 | |
| 	 */
 | |
| 	constructor(name) {
 | |
| 		this.name = name;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| exports.TopLevelSymbol = TopLevelSymbol;
 | |
| exports.topLevelSymbolTag = topLevelSymbolTag;
 |