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

const util = require("util");

const TOMBSTONE = {};
const UNDEFINED_MARKER = {};

class StackedSetMap {
	constructor(parentStack) {
		this.stack = parentStack === undefined ? [] : parentStack.slice();
		this.map = new Map();
		this.stack.push(this.map);
	}

	add(item) {
		this.map.set(item, true);
	}

	set(item, value) {
		this.map.set(item, value === undefined ? UNDEFINED_MARKER : value);
	}

	delete(item) {
		if (this.stack.length > 1) {
			this.map.set(item, TOMBSTONE);
		} else {
			this.map.delete(item);
		}
	}

	has(item) {
		const topValue = this.map.get(item);
		if (topValue !== undefined) return topValue !== TOMBSTONE;
		if (this.stack.length > 1) {
			for (var i = this.stack.length - 2; i >= 0; i--) {
				const value = this.stack[i].get(item);
				if (value !== undefined) {
					this.map.set(item, value);
					return value !== TOMBSTONE;
				}
			}
			this.map.set(item, TOMBSTONE);
		}
		return false;
	}

	get(item) {
		const topValue = this.map.get(item);
		if (topValue !== undefined) {
			return topValue === TOMBSTONE || topValue === UNDEFINED_MARKER
				? undefined
				: topValue;
		}
		if (this.stack.length > 1) {
			for (var i = this.stack.length - 2; i >= 0; i--) {
				const value = this.stack[i].get(item);
				if (value !== undefined) {
					this.map.set(item, value);
					return value === TOMBSTONE || value === UNDEFINED_MARKER
						? undefined
						: value;
				}
			}
			this.map.set(item, TOMBSTONE);
		}
		return undefined;
	}

	_compress() {
		if (this.stack.length === 1) return;
		this.map = new Map();
		for (const data of this.stack) {
			for (const pair of data) {
				if (pair[1] === TOMBSTONE) {
					this.map.delete(pair[0]);
				} else {
					this.map.set(pair[0], pair[1]);
				}
			}
		}
		this.stack = [this.map];
	}

	asArray() {
		this._compress();
		return Array.from(this.map.entries(), pair => pair[0]);
	}

	asSet() {
		return new Set(this.asArray());
	}

	asPairArray() {
		this._compress();
		return Array.from(this.map.entries(), pair =>
			/** @type {[TODO, TODO]} */ (pair[1] === UNDEFINED_MARKER
				? [pair[0], undefined]
				: pair)
		);
	}

	asMap() {
		return new Map(this.asPairArray());
	}

	get size() {
		this._compress();
		return this.map.size;
	}

	createChild() {
		return new StackedSetMap(this.stack);
	}

	get length() {
		throw new Error("This is no longer an Array");
	}

	set length(value) {
		throw new Error("This is no longer an Array");
	}
}

// TODO remove in webpack 5
StackedSetMap.prototype.push = util.deprecate(
	/**
	 * @deprecated
	 * @this {StackedSetMap}
	 * @param {any} item Item to add
	 * @returns {void}
	 */
	function(item) {
		this.add(item);
	},
	"This is no longer an Array: Use add instead."
);

module.exports = StackedSetMap;