HarmonyImportDependencyParserPlugin.js 6.7 KB
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const { SyncBailHook } = require("tapable");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const ConstDependency = require("./ConstDependency");

module.exports = class HarmonyImportDependencyParserPlugin {
	constructor(moduleOptions) {
		this.strictExportPresence = moduleOptions.strictExportPresence;
		this.strictThisContextOnImports = moduleOptions.strictThisContextOnImports;
	}

	apply(parser) {
		parser.hooks.import.tap(
			"HarmonyImportDependencyParserPlugin",
			(statement, source) => {
				parser.state.lastHarmonyImportOrder =
					(parser.state.lastHarmonyImportOrder || 0) + 1;
				const clearDep = new ConstDependency("", statement.range);
				clearDep.loc = statement.loc;
				parser.state.module.addDependency(clearDep);
				const sideEffectDep = new HarmonyImportSideEffectDependency(
					source,
					parser.state.module,
					parser.state.lastHarmonyImportOrder,
					parser.state.harmonyParserScope
				);
				sideEffectDep.loc = statement.loc;
				parser.state.module.addDependency(sideEffectDep);
				return true;
			}
		);
		parser.hooks.importSpecifier.tap(
			"HarmonyImportDependencyParserPlugin",
			(statement, source, id, name) => {
				parser.scope.definitions.delete(name);
				parser.scope.renames.set(name, "imported var");
				if (!parser.state.harmonySpecifier) {
					parser.state.harmonySpecifier = new Map();
				}
				parser.state.harmonySpecifier.set(name, {
					source,
					id,
					sourceOrder: parser.state.lastHarmonyImportOrder
				});
				return true;
			}
		);
		parser.hooks.expression
			.for("imported var")
			.tap("HarmonyImportDependencyParserPlugin", expr => {
				const name = expr.name;
				const settings = parser.state.harmonySpecifier.get(name);
				const dep = new HarmonyImportSpecifierDependency(
					settings.source,
					parser.state.module,
					settings.sourceOrder,
					parser.state.harmonyParserScope,
					settings.id,
					name,
					expr.range,
					this.strictExportPresence
				);
				dep.shorthand = parser.scope.inShorthand;
				dep.directImport = true;
				dep.loc = expr.loc;
				parser.state.module.addDependency(dep);
				return true;
			});
		parser.hooks.expressionAnyMember
			.for("imported var")
			.tap("HarmonyImportDependencyParserPlugin", expr => {
				const name = expr.object.name;
				const settings = parser.state.harmonySpecifier.get(name);
				if (settings.id !== null) return false;
				const dep = new HarmonyImportSpecifierDependency(
					settings.source,
					parser.state.module,
					settings.sourceOrder,
					parser.state.harmonyParserScope,
					expr.property.name || expr.property.value,
					name,
					expr.range,
					this.strictExportPresence
				);
				dep.shorthand = parser.scope.inShorthand;
				dep.directImport = false;
				dep.loc = expr.loc;
				parser.state.module.addDependency(dep);
				return true;
			});
		if (this.strictThisContextOnImports) {
			// only in case when we strictly follow the spec we need a special case here
			parser.hooks.callAnyMember
				.for("imported var")
				.tap("HarmonyImportDependencyParserPlugin", expr => {
					if (expr.callee.type !== "MemberExpression") return;
					if (expr.callee.object.type !== "Identifier") return;
					const name = expr.callee.object.name;
					const settings = parser.state.harmonySpecifier.get(name);
					if (settings.id !== null) return false;
					const dep = new HarmonyImportSpecifierDependency(
						settings.source,
						parser.state.module,
						settings.sourceOrder,
						parser.state.harmonyParserScope,
						expr.callee.property.name || expr.callee.property.value,
						name,
						expr.callee.range,
						this.strictExportPresence
					);
					dep.shorthand = parser.scope.inShorthand;
					dep.directImport = false;
					dep.namespaceObjectAsContext = true;
					dep.loc = expr.callee.loc;
					parser.state.module.addDependency(dep);
					if (expr.arguments) parser.walkExpressions(expr.arguments);
					return true;
				});
		}
		parser.hooks.call
			.for("imported var")
			.tap("HarmonyImportDependencyParserPlugin", expr => {
				const args = expr.arguments;
				const fullExpr = expr;
				expr = expr.callee;
				if (expr.type !== "Identifier") return;
				const name = expr.name;
				const settings = parser.state.harmonySpecifier.get(name);
				const dep = new HarmonyImportSpecifierDependency(
					settings.source,
					parser.state.module,
					settings.sourceOrder,
					parser.state.harmonyParserScope,
					settings.id,
					name,
					expr.range,
					this.strictExportPresence
				);
				dep.directImport = true;
				dep.callArgs = args;
				dep.call = fullExpr;
				dep.loc = expr.loc;
				parser.state.module.addDependency(dep);
				if (args) parser.walkExpressions(args);
				return true;
			});
		// TODO webpack 5: refactor this, no custom hooks
		if (!parser.hooks.hotAcceptCallback) {
			parser.hooks.hotAcceptCallback = new SyncBailHook([
				"expression",
				"requests"
			]);
		}
		if (!parser.hooks.hotAcceptWithoutCallback) {
			parser.hooks.hotAcceptWithoutCallback = new SyncBailHook([
				"expression",
				"requests"
			]);
		}
		parser.hooks.hotAcceptCallback.tap(
			"HarmonyImportDependencyParserPlugin",
			(expr, requests) => {
				const harmonyParserScope = parser.state.harmonyParserScope;
				if (!harmonyParserScope) {
					// This is not a harmony module, skip it
					return;
				}
				const dependencies = requests.map(request => {
					const dep = new HarmonyAcceptImportDependency(
						request,
						parser.state.module,
						harmonyParserScope
					);
					dep.loc = expr.loc;
					parser.state.module.addDependency(dep);
					return dep;
				});
				if (dependencies.length > 0) {
					const dep = new HarmonyAcceptDependency(
						expr.range,
						dependencies,
						true
					);
					dep.loc = expr.loc;
					parser.state.module.addDependency(dep);
				}
			}
		);
		parser.hooks.hotAcceptWithoutCallback.tap(
			"HarmonyImportDependencyParserPlugin",
			(expr, requests) => {
				const dependencies = requests.map(request => {
					const dep = new HarmonyAcceptImportDependency(
						request,
						parser.state.module,
						parser.state.harmonyParserScope
					);
					dep.loc = expr.loc;
					parser.state.module.addDependency(dep);
					return dep;
				});
				if (dependencies.length > 0) {
					const dep = new HarmonyAcceptDependency(
						expr.range,
						dependencies,
						false
					);
					dep.loc = expr.loc;
					parser.state.module.addDependency(dep);
				}
			}
		);
	}
};