/*eslint no-var:0, object-shorthand:0 */

var coverage = String(process.env.COVERAGE) === 'true',
	minify = String(process.env.MINIFY) === 'true',
	ci = String(process.env.CI).match(/^(1|true)$/gi),
	sauceLabs = ci && String(process.env.RUN_SAUCE_LABS) === 'true',
	performance = !coverage && String(process.env.PERFORMANCE) !== 'false',
	path = require('path'),
	errorstacks = require('errorstacks'),
	kl = require('kolorist');

const babel = require('@babel/core');
const fs = require('fs').promises;

// This strips Karma's annoying `LOG: '...'` string from logs
const orgStdoutWrite = process.stdout.write;
process.stdout.write = msg => {
	let out = '';
	const match = msg.match(
		/(^|.*\s)(LOG|WARN|ERROR):\s'__LOG_CUSTOM:([\S\s]*)'/
	);
	if (match && match.length >= 4) {
		// Sometimes the UA of the browser will be included in the message
		if (match[1].length) {
			out += kl.yellow(kl.italic(match[1]));
			out += match[3]
				.split('\n')
				.map(line => '  ' + line)
				.join('\n');
		} else {
			out += match[3];
		}
		out += '\n';
	} else if (/(^|.*\s)(LOG|WARN|ERROR):\s([\S\s]*)/.test(msg)) {
		// Nothing
	} else {
		out = msg;
	}

	return orgStdoutWrite.call(process.stdout, out);
};

var sauceLabsLaunchers = {
	sl_chrome: {
		base: 'SauceLabs',
		browserName: 'chrome',
		platform: 'Windows 10'
	},
	sl_firefox: {
		base: 'SauceLabs',
		browserName: 'firefox',
		platform: 'Windows 10'
	},
	// TODO: Safari always fails and disconnects before any tests are executed.
	// This seems to be an issue with Saucelabs and they're actively investigating
	// that (see: https://mobile.twitter.com/bromann/status/1136323458328084482).
	// We'll disable Safari for now until that's resolved.
	// sl_safari: {
	// 	base: 'SauceLabs',
	// 	browserName: 'Safari',
	// 	version: '11',
	// 	platform: 'OS X 10.13'
	// },
	sl_edge: {
		base: 'SauceLabs',
		browserName: 'MicrosoftEdge',
		platform: 'Windows 10'
	},
	sl_ie_11: {
		base: 'SauceLabs',
		browserName: 'internet explorer',
		version: '11.0',
		platform: 'Windows 7'
	}
};

var localLaunchers = {
	ChromeNoSandboxHeadless: {
		base: 'Chrome',
		flags: [
			'--no-sandbox',
			'--disable-setuid-sandbox',
			// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
			'--headless',
			'--disable-gpu',
			'--no-gpu',
			// Without a remote debugging port, Google Chrome exits immediately.
			'--remote-debugging-port=9333'
		]
	}
};

const subPkgPath = pkgName => {
	if (!minify) {
		return path.join(__dirname, pkgName, 'src', 'index.js');
	}

	// Resolve from package.exports field
	const stripped = pkgName.replace(/[/\\./]/g, '');
	const pkgJson = path.join(__dirname, 'package.json');
	const pkgExports = require(pkgJson).exports;
	const file = pkgExports[stripped ? `./${stripped}` : '.'].browser;
	return path.join(__dirname, file);
};

// Esbuild plugin for aliasing + babel pass
function createEsbuildPlugin() {
	const pending = new Map();
	const cache = new Map();

	const rename = {};
	const mangle = require('./mangle.json');
	for (let prop in mangle.props.props) {
		let name = prop;
		if (name[0] === '$') {
			name = name.slice(1);
		}

		rename[name] = mangle.props.props[prop];
	}

	const alias = {
		'preact/debug': subPkgPath('./debug/'),
		'preact/devtools': subPkgPath('./devtools/'),
		'preact/compat': subPkgPath('./compat/'),
		'preact/hooks': subPkgPath('./hooks/'),
		'preact/test-utils': subPkgPath('./test-utils/'),
		'preact/jsx-runtime': subPkgPath('./jsx-runtime/'),
		'preact/jsx-dev-runtime': subPkgPath('./jsx-runtime/'),
		preact: subPkgPath('')
	};
	return {
		name: 'custom',
		setup(build) {
			// Aliasing: If "MINIFY" is set to "true" we use the dist/
			// files instead of those from src/
			build.onResolve({ filter: /^preact.*/ }, args => {
				const pkg = alias[args.path];
				return {
					path: pkg,
					namespace: 'preact'
				};
			});

			build.onResolve({ filter: /^(react|react-dom)$/ }, args => {
				const pkg = alias['preact/compat'];
				return {
					path: pkg,
					namespace: 'preact'
				};
			});

			// Apply babel pass whenever we load a .js file
			build.onLoad({ filter: /\.js$/ }, async args => {
				const contents = await fs.readFile(args.path, 'utf-8');

				// Using a cache is crucial as babel is 30x slower than esbuild
				const cached = cache.get(args.path);
				if (cached && cached.input === contents) {
					return {
						contents: cached.result,
						resolveDir: path.dirname(args.path),
						loader: 'js'
					};
				}

				let result = contents;

				// Check if somebody already requested the current file. If they
				// did than we push a listener instead of doing a duplicate
				// transform of the same file. This is crucial for build perf.
				if (!pending.has(args.path)) {
					pending.set(args.path, []);

					const tmp = await babel.transformAsync(result, {
						filename: args.path,
						sourceMaps: 'inline',
						plugins: [
							coverage && [
								'istanbul',
								{
									include: minify ? '**/dist/**/*.js' : '**/src/**/*.js'
								}
							]
						].filter(Boolean)
					});
					result = tmp.code || result;
					cache.set(args.path, { input: contents, result });

					// Fire all pending listeners that are waiting on the same
					// file transformation
					const waited = pending.get(args.path);
					pending.delete(args.path);
					waited.forEach(fn => fn());
				} else {
					// Subscribe to the existing transformation completion call
					await new Promise(r => {
						pending.get(args.path).push(r);
					});
					result = cache.get(args.path).result;
				}

				return {
					contents: result,
					resolveDir: path.dirname(args.path),
					loader: 'js'
				};
			});
		}
	};
}

module.exports = function(config) {
	config.set({
		browsers: sauceLabs
			? Object.keys(sauceLabsLaunchers)
			: Object.keys(localLaunchers),

		frameworks: ['mocha', 'chai-sinon'],

		reporters: ['mocha'].concat(
			coverage ? 'coverage' : [],
			sauceLabs ? 'saucelabs' : []
		),

		formatError(msg) {
			const frames = errorstacks.parseStackTrace(msg);
			if (!frames.length || frames[0].column === -1) return '\n' + msg + '\n';

			const frame = frames[0];
			const filePath = kl.lightCyan(
				frame.fileName.replace(__dirname + '/', '')
			);

			const indentMatch = msg.match(/^(\s*)/);
			const indent = indentMatch ? indentMatch[1] : '  ';
			const location = kl.yellow(`:${frame.line}:${frame.column}`);
			return `${indent}at ${frame.name} (${filePath}${location})\n`;
		},

		coverageReporter: {
			dir: path.join(__dirname, 'coverage'),
			reporters: [
				{ type: 'text-summary' },
				{ type: 'html' },
				{ type: 'lcovonly', subdir: '.', file: 'lcov.info' }
			]
		},

		mochaReporter: {
			showDiff: true
		},

		browserLogOptions: { terminal: true },
		browserConsoleLogOptions: { terminal: true },

		browserNoActivityTimeout: 5 * 60 * 1000,

		// Use only two browsers concurrently, works better with open source Sauce Labs remote testing
		concurrency: 2,

		captureTimeout: 0,

		sauceLabs: {
			build: `CI #${process.env.GITHUB_RUN_NUMBER} (${process.env.GITHUB_RUN_ID})`,
			tunnelIdentifier:
				process.env.GITHUB_RUN_NUMBER ||
				`local${require('./package.json').version}`,
			connectLocationForSERelay: 'localhost',
			connectPortForSERelay: 4445,
			startConnect: !!sauceLabs
		},

		customLaunchers: sauceLabs ? sauceLabsLaunchers : localLaunchers,

		files: [
			{ pattern: 'test/polyfills.js', watched: false },
			{
				pattern:
					config.grep ||
					'{debug,devtools,hooks,compat,test-utils,jsx-runtime,}/test/{browser,shared}/**/*.test.js',
				watched: false,
				type: 'js'
			}
		],

		mime: {
			'text/javascript': ['js', 'jsx']
		},

		preprocessors: {
			'{debug,devtools,hooks,compat,test-utils,jsx-runtime,}/test/**/*': [
				'esbuild',
				'sourcemap'
			]
		},

		esbuild: {
			target: 'es5',
			define: {
				COVERAGE: coverage,
				'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || ''),
				ENABLE_PERFORMANCE: performance
			},
			plugins: [createEsbuildPlugin()]
		}
	});
};


* @param {import('./internal').VNode["type"]} type The node name or Component

* @param {object | null | undefined} [props] The properties of the virtual node

import { createElement, render, lazy, Suspense } from 'preact/compat';
import 'preact/debug';
import { setupRerender } from 'preact/test-utils';
import {
	setupScratch,
	teardown,
	serializeHtml
} from '../../../test/_util/helpers';

/** @jsx createElement */

describe('debug with suspense', () => {
	/** @type {HTMLDivElement} */
	let scratch;
	let rerender;
	let errors = [];
	let warnings = [];

	beforeEach(() => {
		errors = [];
		warnings = [];
		scratch = setupScratch();
		rerender = setupRerender();
		sinon.stub(console, 'error').callsFake(e => errors.push(e));
		sinon.stub(console, 'warn').callsFake(w => warnings.push(w));
	});

	afterEach(() => {
		console.error.restore();
		console.warn.restore();
		teardown(scratch);
	});

	it('should throw on missing <Suspense>', () => {
		function Foo() {
			throw Promise.resolve();
		}

		expect(() => render(<Foo />, scratch)).to.throw;
	});

	it('should throw an error when using lazy and missing Suspense', () => {
		const Foo = () =>
Foo
;
		const LazyComp = lazy(
			() => new Promise(resolve => resolve({ default: Foo }))
		);
		const fn = () => {
			render(<LazyComp />, scratch);
		};

		expect(fn).to.throw(/Missing Suspense/gi);
	});

	describe('PropTypes', () => {
		it('should validate propTypes inside lazy()', () => {
			function Baz(props) {
				return
{props.unhappy}
;
			}

			Baz.propTypes = {
				unhappy: function alwaysThrows(obj, key) {
					if (obj[key] === 'signal') throw Error('got prop inside lazy()');
				}
			};

			const loader = Promise.resolve({ default: Baz });
			const LazyBaz = lazy(() => loader);

			const suspense = (
				<Suspense fallback={
fallback...
}>
					<LazyBaz unhappy="signal" />
				</Suspense>
			);
			render(suspense, scratch);
			rerender(); // render fallback

			expect(console.error).to.not.be.called;
			expect(serializeHtml(scratch)).to.equal('
fallback...
');

			return loader.then(() => {
				rerender();
				expect(errors.length).to.equal(1);
				expect(errors[0].includes('got prop')).to.equal(true);
				expect(serializeHtml(scratch)).to.equal('
signal
');
			});
		});

		describe('warn for PropTypes on lazy()', () => {
			it('should log the function name', () => {
				const loader = Promise.resolve({
					default: function MyLazyLoaded() {
						return
Hi there
;
					}
				});
				const FakeLazy = lazy(() => loader);
				FakeLazy.propTypes = {};
				const suspense = (
					<Suspense fallback={
fallback...
}>
						<FakeLazy />
					</Suspense>
				);
				render(suspense, scratch);
				rerender(); // Render fallback

				expect(serializeHtml(scratch)).to.equal('
fallback...
');

				return loader.then(() => {
					rerender();
					expect(console.warn).to.be.calledTwice;
					expect(warnings[1].includes('MyLazyLoaded')).to.equal(true);
					expect(serializeHtml(scratch)).to.equal('
Hi there
');
				});
			});

			it('should log the displayName', () => {
				function MyLazyLoadedComponent() {
					return
Hi there
;
				}
				MyLazyLoadedComponent.displayName = 'HelloLazy';
				const loader = Promise.resolve({ default: MyLazyLoadedComponent });
				const FakeLazy = lazy(() => loader);
				FakeLazy.propTypes = {};
				const suspense = (
					<Suspense fallback={
fallback...
}>
						<FakeLazy />
					</Suspense>
				);
				render(suspense, scratch);
				rerender(); // Render fallback

				expect(serializeHtml(scratch)).to.equal('
fallback...
');

				return loader.then(() => {
					rerender();
					expect(console.warn).to.be.calledTwice;
					expect(warnings[1].includes('HelloLazy')).to.equal(true);
					expect(serializeHtml(scratch)).to.equal('
Hi there
');
				});
			});

			it("should not log a component if lazy loader's Promise rejects", () => {
				const loader = Promise.reject(new Error('Hey there'));
				const FakeLazy = lazy(() => loader);
				FakeLazy.propTypes = {};
				render(
					<Suspense fallback={
fallback...
}>
						<FakeLazy />
					</Suspense>,
					scratch
				);
				rerender(); // Render fallback

				expect(serializeHtml(scratch)).to.equal('
fallback...
');

				return loader.catch(() => {
					try {
						rerender();
					} catch (e) {
						// Ignore the loader's bubbling error
					}

					// Called once on initial render, and again when promise rejects
					expect(console.warn).to.be.calledTwice;
				});
			});

			it("should not log a component if lazy's loader throws", () => {
				const FakeLazy = lazy(() => {
					throw new Error('Hello');
				});
				FakeLazy.propTypes = {};
				let error;
				try {
					render(
						<Suspense fallback={
fallback...
}>
							<FakeLazy />
						</Suspense>,
						scratch
					);
				} catch (e) {
					error = e;
				}

				expect(console.warn).to.be.calledOnce;
				expect(error).not.to.be.undefined;
				expect(error.message).to.eql('Hello');
			});
		});
	});
});


_original: original == null ? ++options._vnodeId : original

if (options.vnode != null) options.vnode(vnode);

keep keep keep replace keep keep keep keep keep

_original: original == null ? ++vnodeId : original

import { options, Fragment } from 'preact';

/** @typedef {import('preact').VNode} VNode */

/**
 * @fileoverview
 * This file exports various methods that implement Babel's "automatic" JSX runtime API:
 * - jsx(type, props, key)
 * - jsxs(type, props, key)
 * - jsxDEV(type, props, key, __source, __self)
 *
 * The implementation of createVNode here is optimized for performance.
 * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3
 */

/**
 * JSX.Element factory used by Babel's {runtime:"automatic"} JSX transform
 * @param {VNode['type']} type
 * @param {VNode['props']} props
 * @param {VNode['key']} [key]
 * @param {string} [__source]
 * @param {string} [__self]
 */
function createVNode(type, props, key, __source, __self) {
	// We'll want to preserve `ref` in props to get rid of the need for
	// forwardRef components in the future, but that should happen via
	// a separate PR.
	let normalizedProps = {};
	for (let i in props) {
		if (i != 'ref') {
			normalizedProps[i] = props[i];
		}
	}

	const vnode = {
		type,
		props: normalizedProps,
		key,
		ref: props && props.ref,
		_children: null,
		_parent: null,
		_depth: 0,
		_dom: null,
		_nextDom: undefined,
		_component: null,
		_hydrating: null,
		constructor: undefined,
		_original: ++options._vnodeId,
		__source,
		__self
	};

	// If a Component VNode, check for and apply defaultProps.
	// Note: `type` is often a String, and can be `undefined` in development.
	let defaults, i;
	if (typeof type === 'function' && (defaults = type.defaultProps)) {
		for (i in defaults)
			if (normalizedProps[i] === undefined) {
				normalizedProps[i] = defaults[i];
			}
	}

	if (options.vnode) options.vnode(vnode);
	return vnode;
}

export {
	createVNode as jsx,
	createVNode as jsxs,
	createVNode as jsxDEV,
	Fragment
};


import {
	vnodeSpy,
	rootSpy,
	beforeDiffSpy,
	hookSpy,
	afterDiffSpy,
	catchErrorSpy
} from '../../../test/_util/optionSpies';

import { createElement, render, Component } from 'preact';
import { useState } from 'preact/hooks';
import { setupRerender } from 'preact/test-utils';
import 'preact/debug';
import { setupScratch, teardown } from '../../../test/_util/helpers';

/** @jsx createElement */

describe('debug options', () => {
	/** @type {HTMLDivElement} */
	let scratch;

	/** @type {() => void} */
	let rerender;

	/** @type {(count: number) => void} */
	let setCount;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();

		vnodeSpy.resetHistory();
		rootSpy.resetHistory();
		beforeDiffSpy.resetHistory();
		hookSpy.resetHistory();
		afterDiffSpy.resetHistory();
		catchErrorSpy.resetHistory();
	});

	afterEach(() => {
		teardown(scratch);
	});

	class ClassApp extends Component {
		constructor() {
			super();
			this.state = { count: 0 };
			setCount = count => this.setState({ count });
		}

		render() {
			return
{this.state.count}
;
		}
	}

	it('should call old options on mount', () => {
		render(<ClassApp />, scratch);

		expect(vnodeSpy).to.have.been.called;
		expect(rootSpy).to.have.been.called;
		expect(beforeDiffSpy).to.have.been.called;
		expect(afterDiffSpy).to.have.been.called;
	});

	it('should call old options on update', () => {
		render(<ClassApp />, scratch);

		setCount(1);
		rerender();

		expect(vnodeSpy).to.have.been.called;
		expect(rootSpy).to.have.been.called;
		expect(beforeDiffSpy).to.have.been.called;
		expect(afterDiffSpy).to.have.been.called;
	});

	it('should call old options on unmount', () => {
		render(<ClassApp />, scratch);
		render(null, scratch);

		expect(vnodeSpy).to.have.been.called;
		expect(rootSpy).to.have.been.called;
		expect(beforeDiffSpy).to.have.been.called;
		expect(afterDiffSpy).to.have.been.called;
	});

	it('should call old hook options for hook components', () => {
		function HookApp() {
			const [count, realSetCount] = useState(0);
			setCount = realSetCount;
			return
{count}
;
		}

		render(<HookApp />, scratch);

		expect(hookSpy).to.have.been.called;
	});

	it('should call old options on error', () => {
		const e = new Error('test');
		class ErrorApp extends Component {
			constructor() {
				super();
				this.state = { error: true };
			}
			componentDidCatch() {
				this.setState({ error: false });
			}
			render() {
				return <Throw error={this.state.error} />;
			}
		}

		function Throw({ error }) {
			if (error) {
				throw e;
			} else {
				return
no error
;
			}
		}

		const clock = sinon.useFakeTimers();

		render(<ErrorApp />, scratch);
		rerender();

		expect(catchErrorSpy).to.have.been.called;

		// we expect to throw after setTimeout to trigger a window.onerror
		// this is to ensure react compat (i.e. with next.js' dev overlay)
		expect(() => clock.tick(0)).to.throw(e);

		clock.restore();
	});
});


import 'mocha';
import { expect } from 'chai';
import {
	createElement,
	Component,
	toChildArray,
	FunctionalComponent,
	ComponentConstructor,
	ComponentFactory,
	VNode,
	ComponentChildren,
	cloneElement
} from '../../';

function getDisplayType(vnode: VNode | string | number) {
	if (typeof vnode === 'string' || typeof vnode == 'number') {
		return vnode.toString();
	} else if (typeof vnode.type == 'string') {
		return vnode.type;
	} else {
		return vnode.type.displayName;
	}
}

class SimpleComponent extends Component<{}, {}> {
	render() {
		return
{this.props.children}
;
	}
}

const SimpleFunctionalComponent = () =>
;

const a: ComponentFactory = SimpleComponent;
const b: ComponentFactory = SimpleFunctionalComponent;

describe('VNode TS types', () => {
	it('is returned by h', () => {
		const actual =
;
		expect(actual).to.include.all.keys('type', 'props', 'key');
	});

	it('has a nodeName of string when html element', () => {
		const div =
Hi!
;
		expect(div.type).to.equal('div');
	});

	it('has a nodeName equal to the construction function when SFC', () => {
		const sfc = <SimpleFunctionalComponent />;
		expect(sfc.type).to.be.instanceOf(Function);
		const constructor = sfc.type as FunctionalComponent<any>;
		expect(constructor.name).to.eq('SimpleFunctionalComponent');
	});

	it('has a nodeName equal to the constructor of a component', () => {
		const sfc = <SimpleComponent />;
		expect(sfc.type).to.be.instanceOf(Function);
		const constructor = sfc.type as ComponentConstructor<any>;
		expect(constructor.name).to.eq('SimpleComponent');
	});

	it('has children which is an array of string or other vnodes', () => {
		const comp = (
			<SimpleComponent>
				<SimpleComponent>child1</SimpleComponent>
				child2
			</SimpleComponent>
		);

		expect(comp.props.children).to.be.instanceOf(Array);
		expect(comp.props.children[1]).to.be.a('string');
	});

	it('children type should work with toChildArray', () => {
		const comp: VNode = <SimpleComponent>child1 {1}</SimpleComponent>;

		const children = toChildArray(comp.props.children);
		expect(children).to.have.lengthOf(2);
	});

	it('toChildArray should filter out some types', () => {
		const compChild = <SimpleComponent />;
		const comp: VNode = (
			<SimpleComponent>
				a{null}
				{true}
				{false}
				{2}
				{undefined}
				{['b', 'c']}
				{compChild}
			</SimpleComponent>
		);

		const children = toChildArray(comp.props.children);
		expect(children).to.deep.equal(['a', 2, 'b', 'c', compChild]);
	});

	it('functions like getDisplayType should work', () => {
		function TestComp(props: { children?: ComponentChildren }) {
			return
{props.children}
;
		}
		TestComp.displayName = 'TestComp';

		const compChild = <TestComp />;
		const comp: VNode = (
			<SimpleComponent>
				a{null}
				{true}
				{false}
				{2}
				{undefined}
				{['b', 'c']}
				{compChild}
			</SimpleComponent>
		);

		const types = toChildArray(comp.props.children).map(getDisplayType);
		expect(types).to.deep.equal(['a', '2', 'b', 'c', 'TestComp']);
	});

	it('component should work with cloneElement', () => {
		const comp: VNode = (
			<SimpleComponent>

child 1

			</SimpleComponent>
		);
		const clone: VNode = cloneElement(comp);

		expect(comp.type).to.equal(clone.type);
		expect(comp.props).not.to.equal(clone.props);
		expect(comp.props).to.deep.equal(clone.props);
	});

	it('component should work with cloneElement using generics', () => {
		const comp: VNode<string> = <SimpleComponent></SimpleComponent>;
		const clone: VNode<string> = cloneElement<string>(comp);

		expect(comp.type).to.equal(clone.type);
		expect(comp.props).not.to.equal(clone.props);
		expect(comp.props).to.deep.equal(clone.props);
	});
});

class ComponentWithFunctionChild extends Component<{
	children: (num: number) => string;
}> {
	render() {
		return null;
	}
}

<ComponentWithFunctionChild>
	{num => num.toFixed(2)}
</ComponentWithFunctionChild>;

class ComponentWithStringChild extends Component<{ children: string }> {
	render() {
		return null;
	}
}

<ComponentWithStringChild>child</ComponentWithStringChild>;

class ComponentWithNumberChild extends Component<{ children: number }> {
	render() {
		return null;
	}
}

<ComponentWithNumberChild>{1}</ComponentWithNumberChild>;

class ComponentWithBooleanChild extends Component<{ children: boolean }> {
	render() {
		return null;
	}
}

<ComponentWithBooleanChild>{false}</ComponentWithBooleanChild>;

class ComponentWithNullChild extends Component<{ children: null }> {
	render() {
		return null;
	}
}

<ComponentWithNullChild>{null}</ComponentWithNullChild>;

class ComponentWithNumberChildren extends Component<{ children: number[] }> {
	render() {
		return null;
	}
}

<ComponentWithNumberChildren>
	{1}
	{2}
</ComponentWithNumberChildren>;


/* eslint no-console:0 */

// parent node types that we don't want to remove pointless initializations from (because it breaks hoisting)
const BLOCKED = ['ForStatement', 'WhileStatement']; // 'IfStatement', 'SwitchStatement'

/** Removes var initialization to `void 0`, which Babel adds for TDZ strictness. */
export default (file, api) => {
	let { jscodeshift } = api,
		found = 0;

	let code = jscodeshift(file.source)
		.find(jscodeshift.VariableDeclaration)
		.forEach(handleDeclaration);

	function handleDeclaration(decl) {
		let p = decl,
			remove = true;

		while ((p = p.parentPath)) {
			if (~BLOCKED.indexOf(p.value.type)) {
				remove = false;
				break;
			}
		}

		decl.value.declarations.filter(isPointless).forEach(node => {
			if (remove === false) {
				console.log(
					`> Skipping removal of undefined init for "${node.id.name}": within ${p.value.type}`
				);
			} else {
				removeNodeInitialization(node);
			}
		});
	}

	function removeNodeInitialization(node) {
		node.init = null;
		found++;
	}

	function isPointless(node) {
		let { init } = node;
		if (init) {
			if (
				init.type === 'UnaryExpression' &&
				init.operator === 'void' &&
				init.argument.value == 0
			) {
				return true;
			}
			if (init.type === 'Identifier' && init.name === 'undefined') {
				return true;
			}
		}
		return false;
	}

	return found ? code.toSource({ quote: 'single' }) : null;
};


export interface Options extends preact.Options {

/** Attach a hook that is invoked before render, mainly to check the arguments. */

parent: Element | Document | ShadowRoot | DocumentFragment

keep keep keep replace keep keep keep keep

import options from './options';

/**
 * Create an virtual node (used for JSX)
 * @param {import('./internal').VNode["type"]} type The node name or Component
 * constructor for this virtual node
 * @param {object | null | undefined} [props] The properties of the virtual node
 * @param {Array<import('.').ComponentChildren>} [children] The children of the virtual node
 * @returns {import('./internal').VNode}
 */
export function createElement(type, props, children) {
	let normalizedProps = {},
		key,
		ref,
		i;
	for (i in props) {
		if (i == 'key') key = props[i];
		else if (i == 'ref') ref = props[i];
		else normalizedProps[i] = props[i];
	}

	if (arguments.length > 3) {
		children = [children];
		// https://github.com/preactjs/preact/issues/1916
		for (i = 3; i < arguments.length; i++) {
			children.push(arguments[i]);
		}
	}
	if (children != null) {
		normalizedProps.children = children;
	}

	// If a Component VNode, check for and apply defaultProps
	// Note: type may be undefined in development, must never error here.
	if (typeof type == 'function' && type.defaultProps != null) {
		for (i in type.defaultProps) {
			if (normalizedProps[i] === undefined) {
				normalizedProps[i] = type.defaultProps[i];
			}
		}
	}

	return createVNode(type, normalizedProps, key, ref, null);
}

/**
 * Create a VNode (used internally by Preact)
 * @param {import('./internal').VNode["type"]} type The node name or Component
 * Constructor for this virtual node
 * @param {object | string | number | null} props The properties of this virtual node.
 * If this virtual node represents a text node, this is the text of the node (string or number).
 * @param {string | number | null} key The key for this virtual node, used when
 * diffing it against its children
 * @param {import('./internal').VNode["ref"]} ref The ref property that will
 * receive a reference to its created child
 * @returns {import('./internal').VNode}
 */
export function createVNode(type, props, key, ref, original) {
	// V8 seems to be better at detecting type shapes if the object is allocated from the same call site
	// Do not inline into createElement and coerceToVNode!
	const vnode = {
		type,
		props,
		key,
		ref,
		_children: null,
		_parent: null,
		_depth: 0,
		_dom: null,
		// _nextDom must be initialized to undefined b/c it will eventually
		// be set to dom.nextSibling which can return `null` and it is important
		// to be able to distinguish between an uninitialized _nextDom and
		// a _nextDom that has been set to `null`
		_nextDom: undefined,
		_component: null,
		_hydrating: null,
		constructor: undefined,
		_original: original == null ? ++options._vnodeId : original
	};

	if (options.vnode != null) options.vnode(vnode);

	return vnode;
}

export function createRef() {
	return { current: null };
}

export function Fragment(props) {
	return props.children;
}

/**
 * Check if a the argument is a valid Preact VNode.
 * @param {*} vnode
 * @returns {vnode is import('./internal').VNode}
 */
export const isValidElement = vnode =>
	vnode != null && vnode.constructor === undefined;


dist/
results/
node_modules/
proxy-packages/*/package-lock.json


/* eslint-disable */
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const preact = path.join(__dirname, '..', 'src');
const compat = path.join(__dirname, '..', 'compat', 'src');

module.exports = {
	context: __dirname,
	entry: './index',
	output: {
		publicPath: '/'
	},
	resolve: {
		alias: {
			['preact/debug']: path.join(__dirname, '..', 'debug'),
			['preact/devtools']: path.join(__dirname, '..', 'devtools'),
			['preact/hooks']: path.join(__dirname, '..', 'hooks', 'src'),
			preact: preact,
			react: compat,
			'react-dom': compat
		},
		extensions: ['.tsx', '.ts', '.js']
	},
	module: {
		rules: [
			{
				test: /\.tsx?$/,
				loader: 'babel-loader',
				options: {
					sourceMap: true,
					presets: [
						[require.resolve('@babel/preset-typescript'), { jsxPragma: 'h' }],
						[
							require.resolve('@babel/preset-env'),
							{
								targets: {
									browsers: ['last 2 versions', 'IE >= 9']
								},
								modules: false,
								loose: true
							}
						],
						[require.resolve('@babel/preset-react')]
					],
					plugins: [
						[require.resolve('@babel/plugin-transform-runtime')],
						[require.resolve('@babel/plugin-transform-react-jsx-source')],
						[
							require.resolve('@babel/plugin-transform-react-jsx'),
							{ pragma: 'h', pragmaFrag: 'Fragment' }
						],
						[
							require.resolve('@babel/plugin-proposal-decorators'),
							{ legacy: true }
						],
						[
							require.resolve('@babel/plugin-proposal-class-properties'),
							{ loose: true }
						]
					]
				}
			},
			{
				test: /\.js$/,
				loader: 'babel-loader',
				options: {
					sourceMap: true,
					presets: [
						[
							require.resolve('@babel/preset-env'),
							{
								targets: {
									browsers: ['last 2 versions', 'IE >= 9']
								},
								modules: false,
								loose: true
							}
						],
						[require.resolve('@babel/preset-react')]
					],
					plugins: [
						[require.resolve('@babel/plugin-transform-react-jsx-source')],
						[
							require.resolve('@babel/plugin-transform-react-jsx'),
							{ pragma: 'createElement', pragmaFrag: 'Fragment' }
						],
						[require.resolve('@babel/plugin-proposal-class-properties')],
						[
							require.resolve('@babel/plugin-transform-react-constant-elements')
						],
						[require.resolve('@babel/plugin-syntax-dynamic-import')]
					]
				}
			},
			{
				test: /\.s?css$/,
				use: ['style-loader', 'css-loader', 'sass-loader']
			}
		]
	},
	devtool: 'inline-source-map',
	node: {
		process: 'mock',
		Buffer: false,
		setImmediate: false
	},
	devServer: {
		historyApiFallback: true
	},
	plugins: [new HtmlWebpackPlugin()]
};


{
	"compilerOptions": {
		"experimentalDecorators": true,
		"jsx": "react",
		"jsxFactory": "h",
		"baseUrl": ".",
		"target": "es2018",
		"module": "es2015",
		"moduleResolution": "node",
		"paths": {
			"preact/hooks": ["../hooks/src/index.js"],
			"preact": ["../src/index.js"],
		}
	}
}


keep keep keep replace replace keep keep keep


// This file includes experimental React APIs exported from the "scheduler"
// npm package. Despite being explicitely marked as unstable some libraries
// already make use of them. This file is not a full replacement for the
// scheduler package, but includes the necessary shims to make those libraries
// work with Preact.

export const unstable_ImmediatePriority = 1;
export const unstable_UserBlockingPriority = 2;
export const unstable_NormalPriority = 3;
export const unstable_LowPriority = 4;
export const unstable_IdlePriority = 5;

/**
 * @param {number} priority
 * @param {() => void} callback
 */
export function unstable_runWithPriority(priority, callback) {
	return callback();
}

export const unstable_now =
	typeof performance === 'object' && typeof performance.now === 'function'
		? performance.now.bind(performance)
		: () => Date.now();


import { createElement, render, Component } from 'preact';
import {
	setupScratch,
	teardown,
	serializeHtml,
	sortAttributes
} from '../_util/helpers';

/** @jsx createElement */

describe('replaceNode parameter in render()', () => {
	let scratch;

	/**
	 * @param {HTMLDivElement} container
	 * @returns {HTMLDivElement[]}
	 */
	function setupABCDom(container) {
		return ['a', 'b', 'c'].map(id => {
			const child = document.createElement('div');
			child.id = id;
			container.appendChild(child);

			return child;
		});
	}

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should use replaceNode as render root and not inject into it', () => {
		setupABCDom(scratch);
		const childA = scratch.querySelector('#a');

		render(
contents
, scratch, childA);
		expect(scratch.querySelector('#a')).to.equalNode(childA);
		expect(childA.innerHTML).to.equal('contents');
	});

	it('should not remove siblings of replaceNode', () => {
		setupABCDom(scratch);
		const childA = scratch.querySelector('#a');

		render(
, scratch, childA);
		expect(scratch.innerHTML).to.equal(
			'

'
		);
	});

	it('should notice prop changes on replaceNode', () => {
		setupABCDom(scratch);
		const childA = scratch.querySelector('#a');

		render(
, scratch, childA);
		expect(sortAttributes(String(scratch.innerHTML))).to.equal(
			sortAttributes(
				'

'
			)
		);
	});

	it('should unmount existing components', () => {
		const unmount = sinon.spy();
		const mount = sinon.spy();
		class App extends Component {
			componentDidMount() {
				mount();
			}

			componentWillUnmount() {
				unmount();
			}

			render() {
				return
App
;
			}
		}

		render(

				<App />

,
			scratch
		);
		expect(scratch.innerHTML).to.equal('

App

');
		expect(mount).to.be.calledOnce;

		render(
new
, scratch, scratch.querySelector('#a'));
		expect(scratch.innerHTML).to.equal('
new
');
		expect(unmount).to.be.calledOnce;
	});

	it('should unmount existing components in prerendered HTML', () => {
		const unmount = sinon.spy();
		const mount = sinon.spy();
		class App extends Component {
			componentDidMount() {
				mount();
			}

			componentWillUnmount() {
				unmount();
			}

			render() {
				return App;
			}
		}

		scratch.innerHTML = `<div id="child"></div>`;

		const childContainer = scratch.querySelector('#child');

		render(<App />, childContainer);
		expect(serializeHtml(childContainer)).to.equal('App');
		expect(mount).to.be.calledOnce;

		render(
, scratch, scratch.firstElementChild);
		expect(serializeHtml(scratch)).to.equal('

');
		expect(unmount).to.be.calledOnce;
	});

	it('should render multiple render roots in one parentDom', () => {
		setupABCDom(scratch);
		const childA = scratch.querySelector('#a');
		const childB = scratch.querySelector('#b');
		const childC = scratch.querySelector('#c');

		const expectedA = '
childA
';
		const expectedB = '
childB
';
		const expectedC = '
childC
';

		render(
childA
, scratch, childA);
		render(
childB
, scratch, childB);
		render(
childC
, scratch, childC);

		expect(scratch.innerHTML).to.equal(`${expectedA}${expectedB}${expectedC}`);
	});

	it('should call unmount when working with replaceNode', () => {
		const mountSpy = sinon.spy();
		const unmountSpy = sinon.spy();
		class MyComponent extends Component {
			componentDidMount() {
				mountSpy();
			}
			componentWillUnmount() {
				unmountSpy();
			}
			render() {
				return
My Component
;
			}
		}

		const container = document.createElement('div');
		scratch.appendChild(container);

		render(<MyComponent />, scratch, container);
		expect(mountSpy).to.be.calledOnce;

		render(
Not my component
, document.body, container);
		expect(unmountSpy).to.be.calledOnce;
	});

	it('should double replace', () => {
		const container = document.createElement('div');
		scratch.appendChild(container);

		render(
Hello
, scratch, scratch.firstElementChild);
		expect(scratch.innerHTML).to.equal('
Hello
');

		render(
Hello
, scratch, scratch.firstElementChild);
		expect(scratch.innerHTML).to.equal('
Hello
');
	});

	it('should replaceNode after rendering', () => {
		function App({ i }) {
			return
{i}
;
		}

		render(<App i={2} />, scratch);
		expect(scratch.innerHTML).to.equal('
2
');

		render(<App i={3} />, scratch, scratch.firstChild);
		expect(scratch.innerHTML).to.equal('
3
');
	});

	it("shouldn't remove elements on subsequent renders with replaceNode", () => {
		const placeholder = document.createElement('div');
		scratch.appendChild(placeholder);
		const App = () => (

				New content
				<button>Update</button>

		);

		render(<App />, scratch, placeholder);
		expect(scratch.innerHTML).to.equal(
			'
New content<button>Update</button>
'
		);

		render(<App />, scratch, placeholder);
		expect(scratch.innerHTML).to.equal(
			'
New content<button>Update</button>
'
		);
	});

	it('should remove redundant elements on subsequent renders with replaceNode', () => {
		const placeholder = document.createElement('div');
		scratch.appendChild(placeholder);
		const App = () => (

				New content
				<button>Update</button>

		);

		render(<App />, scratch, placeholder);
		expect(scratch.innerHTML).to.equal(
			'
New content<button>Update</button>
'
		);

		placeholder.appendChild(document.createElement('span'));
		expect(scratch.innerHTML).to.equal(
			'
New content<button>Update</button>
'
		);

		render(<App />, scratch, placeholder);
		expect(scratch.innerHTML).to.equal(
			'
New content<button>Update</button>
'
		);
	});
});


import React, { createElement } from 'preact/compat';
import { setupRerender } from 'preact/test-utils';
import { setupScratch, teardown } from '../../../test/_util/helpers';

describe('PureComponent', () => {
	/** @type {HTMLDivElement} */
	let scratch;

	/** @type {() => void} */
	let rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should be a class', () => {
		expect(React)
			.to.have.property('PureComponent')
			.that.is.a('function');
	});

	it('should pass props in constructor', () => {
		let spy = sinon.spy();
		class Foo extends React.PureComponent {
			constructor(props) {
				super(props);
				spy(this.props, props);
			}
		}

		React.render(<Foo foo="bar" />, scratch);

		let expected = { foo: 'bar' };
		expect(spy).to.be.calledWithMatch(expected, expected);
	});

	it('should ignore the __source variable', () => {
		const pureSpy = sinon.spy();
		const appSpy = sinon.spy();
		let set;
		class Pure extends React.PureComponent {
			render() {
				pureSpy();
				return
Static
;
			}
		}

		const App = () => {
			const [, setState] = React.useState(0);
			appSpy();
			set = setState;
			return <Pure __source={{}} />;
		};

		React.render(<App />, scratch);
		expect(appSpy).to.be.calledOnce;
		expect(pureSpy).to.be.calledOnce;

		set(1);
		rerender();
		expect(appSpy).to.be.calledTwice;
		expect(pureSpy).to.be.calledOnce;
	});

	it('should only re-render when props or state change', () => {
		class C extends React.PureComponent {
			render() {
				return
;
			}
		}
		let spy = sinon.spy(C.prototype, 'render');

		let inst = React.render(<C />, scratch);
		expect(spy).to.have.been.calledOnce;
		spy.resetHistory();

		inst = React.render(<C />, scratch);
		expect(spy).not.to.have.been.called;

		let b = { foo: 'bar' };
		inst = React.render(<C a="a" b={b} />, scratch);
		expect(spy).to.have.been.calledOnce;
		spy.resetHistory();

		inst = React.render(<C a="a" b={b} />, scratch);
		expect(spy).not.to.have.been.called;

		inst.setState({});
		rerender();
		expect(spy).not.to.have.been.called;

		inst.setState({ a: 'a', b });
		rerender();
		expect(spy).to.have.been.calledOnce;
		spy.resetHistory();

		inst.setState({ a: 'a', b });
		rerender();
		expect(spy).not.to.have.been.called;
	});

	it('should update when props are removed', () => {
		let spy = sinon.spy();
		class App extends React.PureComponent {
			render() {
				spy();
				return
foo
;
			}
		}

		React.render(<App a="foo" />, scratch);
		React.render(<App />, scratch);
		expect(spy).to.be.calledTwice;
	});

	it('should have "isPureReactComponent" property', () => {
		let Pure = new React.PureComponent();
		expect(Pure.isReactComponent).to.deep.equal({});
	});
});


var GetColorFromValue = require('../../../display/color/GetColorFromValue');

keep keep keep keep keep replace keep keep keep

/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var BatchChar = require('../BatchChar');
var Utils = require('../../../renderer/webgl/Utils');

/**
 * Renders this Game Object with the WebGL Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.BitmapText#renderWebGL
 * @since 3.0.0
 * @private
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
 * @param {Phaser.GameObjects.BitmapText} src - The Game Object being rendered in this call.
 * @param {number} interpolationPercentage - Reserved for future use and custom pipelines.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var BitmapTextWebGLRenderer = function (renderer, src, interpolationPercentage, camera, parentMatrix)
{
    var text = src._text;
    var textLength = text.length;

    if (textLength === 0)
    {
        return;
    }

    var pipeline = renderer.pipelines.set(this.pipeline, src);

    var camMatrix = pipeline._tempMatrix1;
    var spriteMatrix = pipeline._tempMatrix2;
    var calcMatrix = pipeline._tempMatrix3;

    spriteMatrix.applyITRS(src.x, src.y, src.rotation, src.scaleX, src.scaleY);

    camMatrix.copyFrom(camera.matrix);

    if (parentMatrix)
    {
        //  Multiply the camera by the parent matrix
        camMatrix.multiplyWithOffset(parentMatrix, -camera.scrollX * src.scrollFactorX, -camera.scrollY * src.scrollFactorY);

        //  Undo the camera scroll
        spriteMatrix.e = src.x;
        spriteMatrix.f = src.y;

        //  Multiply by the Sprite matrix, store result in calcMatrix
        camMatrix.multiply(spriteMatrix, calcMatrix);
    }
    else
    {
        spriteMatrix.e -= camera.scrollX * src.scrollFactorX;
        spriteMatrix.f -= camera.scrollY * src.scrollFactorY;

        //  Multiply by the Sprite matrix, store result in calcMatrix
        camMatrix.multiply(spriteMatrix, calcMatrix);
    }

    var roundPixels = camera.roundPixels;

    var cameraAlpha = camera.alpha;

    var charColors = src.charColors;

    var tintEffect = (src._isTinted && src.tintFill);

    var tintTL = Utils.getTintAppendFloatAlpha(src._tintTL, cameraAlpha * src._alphaTL);
    var tintTR = Utils.getTintAppendFloatAlpha(src._tintTR, cameraAlpha * src._alphaTR);
    var tintBL = Utils.getTintAppendFloatAlpha(src._tintBL, cameraAlpha * src._alphaBL);
    var tintBR = Utils.getTintAppendFloatAlpha(src._tintBR, cameraAlpha * src._alphaBR);

    var texture = src.frame.glTexture;
    var textureUnit = pipeline.setGameObject(src);

    //  Update the bounds - skipped internally if not dirty
    var bounds = src.getTextBounds(false);

    var i;
    var char;
    var glyph;

    var characters = bounds.characters;

    var dropShadowX = src.dropShadowX;
    var dropShadowY = src.dropShadowY;

    var dropShadow = (dropShadowX !== 0 || dropShadowY !== 0);

    if (dropShadow)
    {
        var srcShadowColor = src._dropShadowColorGL;
        var srcShadowAlpha = src.dropShadowAlpha;

        var blackTL = Utils.getTintAppendFloatAlpha(srcShadowColor, cameraAlpha * srcShadowAlpha * src._alphaTL);
        var blackTR = Utils.getTintAppendFloatAlpha(srcShadowColor, cameraAlpha * srcShadowAlpha * src._alphaTR);
        var blackBL = Utils.getTintAppendFloatAlpha(srcShadowColor, cameraAlpha * srcShadowAlpha * src._alphaBL);
        var blackBR = Utils.getTintAppendFloatAlpha(srcShadowColor, cameraAlpha * srcShadowAlpha * src._alphaBR);

        for (i = 0; i < characters.length; i++)
        {
            char = characters[i];
            glyph = char.glyph;

            if (char.code === 32 || glyph.width === 0 || glyph.height === 0)
            {
                continue;
            }

            BatchChar(pipeline, src, char, glyph, dropShadowX, dropShadowY, calcMatrix, roundPixels, blackTL, blackTR, blackBL, blackBR, 0, texture, textureUnit);
        }
    }

    for (i = 0; i < characters.length; i++)
    {
        char = characters[i];
        glyph = char.glyph;

        if (char.code === 32 || glyph.width === 0 || glyph.height === 0)
        {
            continue;
        }

        if (charColors[char.i])
        {
            var color = charColors[char.i];

            var ctintEffect = color.tintEffect;
            var ctintTL = Utils.getTintAppendFloatAlpha(color.tintTL, cameraAlpha * src._alphaTL);
            var ctintTR = Utils.getTintAppendFloatAlpha(color.tintTR, cameraAlpha * src._alphaTR);
            var ctintBL = Utils.getTintAppendFloatAlpha(color.tintBL, cameraAlpha * src._alphaBL);
            var ctintBR = Utils.getTintAppendFloatAlpha(color.tintBR, cameraAlpha * src._alphaBR);

            BatchChar(pipeline, src, char, glyph, 0, 0, calcMatrix, roundPixels, ctintTL, ctintTR, ctintBL, ctintBR, ctintEffect, texture, textureUnit);
        }
        else
        {
            BatchChar(pipeline, src, char, glyph, 0, 0, calcMatrix, roundPixels, tintTL, tintTR, tintBL, tintBR, tintEffect, texture, textureUnit);
        }

        //  Debug test if the characters are in the correct place when rendered:
        // pipeline.drawFillRect(tx0, ty0, tx2 - tx0, ty2 - ty0, 0x00ff00, 0.5);
    }
};

module.exports = BitmapTextWebGLRenderer;


#define SHADER_NAME PHASER_BITMAP_MASK_FS

precision mediump float;

uniform vec2 uResolution;
uniform sampler2D uMainSampler;
uniform sampler2D uMaskSampler;
uniform bool uInvertMaskAlpha;

void main()
{
    vec2 uv = gl_FragCoord.xy / uResolution;
    vec4 mainColor = texture2D(uMainSampler, uv);
    vec4 maskColor = texture2D(uMaskSampler, uv);
    float alpha = mainColor.a;

    if (!uInvertMaskAlpha)
    {
        alpha *= (maskColor.a);
    }
    else
    {
        alpha *= (1.0 - maskColor.a);
    }

    gl_FragColor = vec4(mainColor.rgb * alpha, alpha);
}


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var CONST = require('../const');
var FileTypesManager = require('../FileTypesManager');
var JSONFile = require('./JSONFile.js');

/**
 * @classdesc
 * A single JSON Pack File suitable for loading by the Loader.
 *
 * These are created when you use the Phaser.Loader.LoaderPlugin#pack method and are not typically created directly.
 *
 * For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#pack.
 *
 * @class PackFile
 * @extends Phaser.Loader.File
 * @memberof Phaser.Loader.FileTypes
 * @constructor
 * @since 3.7.0
 *
 * @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader that is responsible for this file.
 * @param {(string|Phaser.Types.Loader.FileTypes.PackFileConfig)} key - The key to use for this file, or a file configuration object.
 * @param {string} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
 * @param {string} [dataKey] - When the JSON file loads only this property will be stored in the Cache.
 */
var PackFile = new Class({

    Extends: JSONFile,

    initialize:

    //  url can either be a string, in which case it is treated like a proper url, or an object, in which case it is treated as a ready-made JS Object
    //  dataKey allows you to pluck a specific object out of the JSON and put just that into the cache, rather than the whole thing

    function PackFile (loader, key, url, xhrSettings, dataKey)
    {
        JSONFile.call(this, loader, key, url, xhrSettings, dataKey);

        this.type = 'packfile';
    },

    /**
     * Called automatically by Loader.nextFile.
     * This method controls what extra work this File does with its loaded data.
     *
     * @method Phaser.Loader.FileTypes.PackFile#onProcess
     * @since 3.7.0
     */
    onProcess: function ()
    {
        if (this.state !== CONST.FILE_POPULATED)
        {
            this.state = CONST.FILE_PROCESSING;

            this.data = JSON.parse(this.xhrLoader.responseText);
        }

        //  Let's pass the pack file data over to the Loader ...
        this.loader.addPack(this.data, this.config);

        this.onProcessComplete();
    }

});

/**
 * Adds a JSON File Pack, or array of packs, to the current load queue.
 *
 * You can call this method from within your Scene's `preload`, along with any other files you wish to load:
 *
 * ```javascript
 * function preload ()
 * {
 *     this.load.pack('level1', 'data/Level1Files.json');
 * }
 * ```
 *
 * A File Pack is a JSON file (or object) that contains details about other files that should be added into the Loader.
 * Here is a small example:
 *
 * ```json
 * {
 *    "test1": {
 *        "files": [
 *            {
 *                "type": "image",
 *                "key": "taikodrummaster",
 *                "url": "assets/pics/taikodrummaster.jpg"
 *            },
 *            {
 *                "type": "image",
 *                "key": "sukasuka-chtholly",
 *                "url": "assets/pics/sukasuka-chtholly.png"
 *            }
 *        ]
 *    },
 *    "meta": {
 *        "generated": "1401380327373",
 *        "app": "Phaser 3 Asset Packer",
 *        "url": "https://phaser.io",
 *        "version": "1.0",
 *        "copyright": "Photon Storm Ltd. 2018"
 *    }
 * }
 * ```
 *
 * The pack can be split into sections. In the example above you'll see a section called `test1. You can tell
 * the `load.pack` method to parse only a particular section of a pack. The pack is stored in the JSON Cache,
 * so you can pass it to the Loader to process additional sections as needed in your game, or you can just load
 * them all at once without specifying anything.
 *
 * The pack file can contain an entry for any type of file that Phaser can load. The object structures exactly
 * match that of the file type configs, and all properties available within the file type configs can be used
 * in the pack file too.
 *
 * The file is **not** loaded right away. It is added to a queue ready to be loaded either when the loader starts,
 * or if it's already running, when the next free load slot becomes available. This happens automatically if you
 * are calling this from within the Scene's `preload` method, or a related callback. Because the file is queued
 * it means you cannot use the file immediately after calling this method, but must wait for the file to complete.
 * The typical flow for a Phaser Scene is that you load assets in the Scene's `preload` method and then when the
 * Scene's `create` method is called you are guaranteed that all of those assets are ready for use and have been
 * loaded.
 *
 * If you call this from outside of `preload` then you are responsible for starting the Loader afterwards and monitoring
 * its events to know when it's safe to use the asset. Please see the Phaser.Loader.LoaderPlugin class for more details.
 *
 * The key must be a unique String. It is used to add the file to the global JSON Cache upon a successful load.
 * The key should be unique both in terms of files being loaded and files already present in the JSON Cache.
 * Loading a file using a key that is already taken will result in a warning. If you wish to replace an existing file
 * then remove it from the JSON Cache first, before loading a new one.
 *
 * Instead of passing arguments you can pass a configuration object, such as:
 *
 * ```javascript
 * this.load.pack({
 *     key: 'level1',
 *     url: 'data/Level1Files.json'
 * });
 * ```
 *
 * See the documentation for `Phaser.Types.Loader.FileTypes.PackFileConfig` for more details.
 *
 * If you have specified a prefix in the loader, via `Loader.setPrefix` then this value will be prepended to this files
 * key. For example, if the prefix was `LEVEL1.` and the key was `Waves` the final key will be `LEVEL1.Waves` and
 * this is what you would use to retrieve the text from the JSON Cache.
 *
 * The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
 *
 * If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "data"
 * and no URL is given then the Loader will set the URL to be "data.json". It will always add `.json` as the extension, although
 * this can be overridden if using an object instead of method arguments. If you do not desire this action then provide a URL.
 *
 * You can also optionally provide a `dataKey` to use. This allows you to extract only a part of the JSON and store it in the Cache,
 * rather than the whole file. For example, if your JSON data had a structure like this:
 *
 * ```json
 * {
 *     "level1": {
 *         "baddies": {
 *             "aliens": {},
 *             "boss": {}
 *         }
 *     },
 *     "level2": {},
 *     "level3": {}
 * }
 * ```
 *
 * And you only wanted to store the `boss` data in the Cache, then you could pass `level1.baddies.boss`as the `dataKey`.
 *
 * Note: The ability to load this type of file will only be available if the Pack File type has been built into Phaser.
 * It is available in the default build but can be excluded from custom builds.
 *
 * @method Phaser.Loader.LoaderPlugin#pack
 * @fires Phaser.Loader.LoaderPlugin#ADD
 * @since 3.7.0
 *
 * @param {(string|Phaser.Types.Loader.FileTypes.PackFileConfig|Phaser.Types.Loader.FileTypes.PackFileConfig[])} key - The key to use for this file, or a file configuration object, or array of them.
 * @param {string} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
 * @param {string} [dataKey] - When the JSON file loads only this property will be stored in the Cache.
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - An XHR Settings configuration object. Used in replacement of the Loaders default XHR Settings.
 *
 * @return {this} The Loader instance.
 */
FileTypesManager.register('pack', function (key, url, packKey, xhrSettings)
{
    //  Supports an Object file definition in the key argument
    //  Or an array of objects in the key argument
    //  Or a single entry where all arguments have been defined

    if (Array.isArray(key))
    {
        for (var i = 0; i < key.length; i++)
        {
            this.addFile(new PackFile(this, key[i]));
        }
    }
    else
    {
        this.addFile(new PackFile(this, key, url, xhrSettings, packKey));
    }

    return this;
});

module.exports = PackFile;


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../utils/Class');
var Events = require('./events');

/**
 * @callback DataEachCallback
 *
 * @param {*} parent - The parent object of the DataManager.
 * @param {string} key - The key of the value.
 * @param {*} value - The value.
 * @param {...*} [args] - Additional arguments that will be passed to the callback, after the game object, key, and data.
 */

/**
 * @classdesc
 * The Data Manager Component features a means to store pieces of data specific to a Game Object, System or Plugin.
 * You can then search, query it, and retrieve the data. The parent must either extend EventEmitter,
 * or have a property called `events` that is an instance of it.
 *
 * @class DataManager
 * @memberof Phaser.Data
 * @constructor
 * @since 3.0.0
 *
 * @param {object} parent - The object that this DataManager belongs to.
 * @param {Phaser.Events.EventEmitter} [eventEmitter] - The DataManager's event emitter.
 */
var DataManager = new Class({

    initialize:

    function DataManager (parent, eventEmitter)
    {
        /**
         * The object that this DataManager belongs to.
         *
         * @name Phaser.Data.DataManager#parent
         * @type {*}
         * @since 3.0.0
         */
        this.parent = parent;

        /**
         * The DataManager's event emitter.
         *
         * @name Phaser.Data.DataManager#events
         * @type {Phaser.Events.EventEmitter}
         * @since 3.0.0
         */
        this.events = eventEmitter;

        if (!eventEmitter)
        {
            this.events = (parent.events) ? parent.events : parent;
        }

        /**
         * The data list.
         *
         * @name Phaser.Data.DataManager#list
         * @type {Object.<string, *>}
         * @default {}
         * @since 3.0.0
         */
        this.list = {};

        /**
         * The public values list. You can use this to access anything you have stored
         * in this Data Manager. For example, if you set a value called `gold` you can
         * access it via:
         *
         * ```javascript
         * this.data.values.gold;
         * ```
         *
         * You can also modify it directly:
         *
         * ```javascript
         * this.data.values.gold += 1000;
         * ```
         *
         * Doing so will emit a `setdata` event from the parent of this Data Manager.
         *
         * Do not modify this object directly. Adding properties directly to this object will not
         * emit any events. Always use `DataManager.set` to create new items the first time around.
         *
         * @name Phaser.Data.DataManager#values
         * @type {Object.<string, *>}
         * @default {}
         * @since 3.10.0
         */
        this.values = {};

        /**
         * Whether setting data is frozen for this DataManager.
         *
         * @name Phaser.Data.DataManager#_frozen
         * @type {boolean}
         * @private
         * @default false
         * @since 3.0.0
         */
        this._frozen = false;

        if (!parent.hasOwnProperty('sys') && this.events)
        {
            this.events.once(Events.DESTROY, this.destroy, this);
        }
    },

    /**
     * Retrieves the value for the given key, or undefined if it doesn't exist.
     *
     * You can also access values via the `values` object. For example, if you had a key called `gold` you can do either:
     *
     * ```javascript
     * this.data.get('gold');
     * ```
     *
     * Or access the value directly:
     *
     * ```javascript
     * this.data.values.gold;
     * ```
     *
     * You can also pass in an array of keys, in which case an array of values will be returned:
     *
     * ```javascript
     * this.data.get([ 'gold', 'armor', 'health' ]);
     * ```
     *
     * This approach is useful for destructuring arrays in ES6.
     *
     * @method Phaser.Data.DataManager#get
     * @since 3.0.0
     *
     * @param {(string|string[])} key - The key of the value to retrieve, or an array of keys.
     *
     * @return {*} The value belonging to the given key, or an array of values, the order of which will match the input array.
     */
    get: function (key)
    {
        var list = this.list;

        if (Array.isArray(key))
        {
            var output = [];

            for (var i = 0; i < key.length; i++)
            {
                output.push(list[key[i]]);
            }

            return output;
        }
        else
        {
            return list[key];
        }
    },

    /**
     * Retrieves all data values in a new object.
     *
     * @method Phaser.Data.DataManager#getAll
     * @since 3.0.0
     *
     * @return {Object.<string, *>} All data values.
     */
    getAll: function ()
    {
        var results = {};

        for (var key in this.list)
        {
            if (this.list.hasOwnProperty(key))
            {
                results[key] = this.list[key];
            }
        }

        return results;
    },

    /**
     * Queries the DataManager for the values of keys matching the given regular expression.
     *
     * @method Phaser.Data.DataManager#query
     * @since 3.0.0
     *
     * @param {RegExp} search - A regular expression object. If a non-RegExp object obj is passed, it is implicitly converted to a RegExp by using new RegExp(obj).
     *
     * @return {Object.<string, *>} The values of the keys matching the search string.
     */
    query: function (search)
    {
        var results = {};

        for (var key in this.list)
        {
            if (this.list.hasOwnProperty(key) && key.match(search))
            {
                results[key] = this.list[key];
            }
        }

        return results;
    },

    /**
     * Sets a value for the given key. If the key doesn't already exist in the Data Manager then it is created.
     *
     * ```javascript
     * data.set('name', 'Red Gem Stone');
     * ```
     *
     * You can also pass in an object of key value pairs as the first argument:
     *
     * ```javascript
     * data.set({ name: 'Red Gem Stone', level: 2, owner: 'Link', gold: 50 });
     * ```
     *
     * To get a value back again you can call `get`:
     *
     * ```javascript
     * data.get('gold');
     * ```
     *
     * Or you can access the value directly via the `values` property, where it works like any other variable:
     *
     * ```javascript
     * data.values.gold += 50;
     * ```
     *
     * When the value is first set, a `setdata` event is emitted.
     *
     * If the key already exists, a `changedata` event is emitted instead, along an event named after the key.
     * For example, if you updated an existing key called `PlayerLives` then it would emit the event `changedata-PlayerLives`.
     * These events will be emitted regardless if you use this method to set the value, or the direct `values` setter.
     *
     * Please note that the data keys are case-sensitive and must be valid JavaScript Object property strings.
     * This means the keys `gold` and `Gold` are treated as two unique values within the Data Manager.
     *
     * @method Phaser.Data.DataManager#set
     * @fires Phaser.Data.Events#SET_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA_KEY
     * @since 3.0.0
     *
     * @param {(string|object)} key - The key to set the value for. Or an object or key value pairs. If an object the `data` argument is ignored.
     * @param {*} data - The value to set for the given key. If an object is provided as the key this argument is ignored.
     *
     * @return {this} This DataManager object.
     */
    set: function (key, data)
    {
        if (this._frozen)
        {
            return this;
        }

        if (typeof key === 'string')
        {
            return this.setValue(key, data);
        }
        else
        {
            for (var entry in key)
            {
                this.setValue(entry, key[entry]);
            }
        }

        return this;
    },

    /**
     * Increase a value for the given key. If the key doesn't already exist in the Data Manager then it is increased from 0.
     *
     * When the value is first set, a `setdata` event is emitted.
     *
     * @method Phaser.Data.DataManager#inc
     * @fires Phaser.Data.Events#SET_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA_KEY
     * @since 3.23.0
     *
     * @param {(string|object)} key - The key to increase the value for.
     * @param {*} [data] - The value to increase for the given key.
     *
     * @return {Phaser.Data.DataManager} This DataManager object.
     */
    inc: function (key, data)
    {
        if (this._frozen)
        {
            return this;
        }

        if (data === undefined)
        {
            data = 1;
        }

        var value = this.get(key);
        if (value === undefined)
        {
            value = 0;
        }

        this.set(key, (value + data));

        return this;
    },

    /**
     * Toggle a boolean value for the given key. If the key doesn't already exist in the Data Manager then it is toggled from false.
     *
     * When the value is first set, a `setdata` event is emitted.
     *
     * @method Phaser.Data.DataManager#toggle
     * @fires Phaser.Data.Events#SET_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA_KEY
     * @since 3.23.0
     *
     * @param {(string|object)} key - The key to toggle the value for.
     *
     * @return {Phaser.Data.DataManager} This DataManager object.
     */
    toggle: function (key)
    {
        if (this._frozen)
        {
            return this;
        }

        this.set(key, !this.get(key));

        return this;
    },

    /**
     * Internal value setter, called automatically by the `set` method.
     *
     * @method Phaser.Data.DataManager#setValue
     * @fires Phaser.Data.Events#SET_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA_KEY
     * @private
     * @since 3.10.0
     *
     * @param {string} key - The key to set the value for.
     * @param {*} data - The value to set.
     *
     * @return {this} This DataManager object.
     */
    setValue: function (key, data)
    {
        if (this._frozen)
        {
            return this;
        }

        if (this.has(key))
        {
            //  Hit the key getter, which will in turn emit the events.
            this.values[key] = data;
        }
        else
        {
            var _this = this;
            var list = this.list;
            var events = this.events;
            var parent = this.parent;

            Object.defineProperty(this.values, key, {

                enumerable: true,

                configurable: true,

                get: function ()
                {
                    return list[key];
                },

                set: function (value)
                {
                    if (!_this._frozen)
                    {
                        var previousValue = list[key];
                        list[key] = value;

                        events.emit(Events.CHANGE_DATA, parent, key, value, previousValue);
                        events.emit(Events.CHANGE_DATA_KEY + key, parent, value, previousValue);
                    }
                }

            });

            list[key] = data;

            events.emit(Events.SET_DATA, parent, key, data);
        }

        return this;
    },

    /**
     * Passes all data entries to the given callback.
     *
     * @method Phaser.Data.DataManager#each
     * @since 3.0.0
     *
     * @param {DataEachCallback} callback - The function to call.
     * @param {*} [context] - Value to use as `this` when executing callback.
     * @param {...*} [args] - Additional arguments that will be passed to the callback, after the game object, key, and data.
     *
     * @return {this} This DataManager object.
     */
    each: function (callback, context)
    {
        var args = [ this.parent, null, undefined ];

        for (var i = 1; i < arguments.length; i++)
        {
            args.push(arguments[i]);
        }

        for (var key in this.list)
        {
            args[1] = key;
            args[2] = this.list[key];

            callback.apply(context, args);
        }

        return this;
    },

    /**
     * Merge the given object of key value pairs into this DataManager.
     *
     * Any newly created values will emit a `setdata` event. Any updated values (see the `overwrite` argument)
     * will emit a `changedata` event.
     *
     * @method Phaser.Data.DataManager#merge
     * @fires Phaser.Data.Events#SET_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA
     * @fires Phaser.Data.Events#CHANGE_DATA_KEY
     * @since 3.0.0
     *
     * @param {Object.<string, *>} data - The data to merge.
     * @param {boolean} [overwrite=true] - Whether to overwrite existing data. Defaults to true.
     *
     * @return {this} This DataManager object.
     */
    merge: function (data, overwrite)
    {
        if (overwrite === undefined) { overwrite = true; }

        //  Merge data from another component into this one
        for (var key in data)
        {
            if (data.hasOwnProperty(key) && (overwrite || (!overwrite && !this.has(key))))
            {
                this.setValue(key, data[key]);
            }
        }

        return this;
    },

    /**
     * Remove the value for the given key.
     *
     * If the key is found in this Data Manager it is removed from the internal lists and a
     * `removedata` event is emitted.
     *
     * You can also pass in an array of keys, in which case all keys in the array will be removed:
     *
     * ```javascript
     * this.data.remove([ 'gold', 'armor', 'health' ]);
     * ```
     *
     * @method Phaser.Data.DataManager#remove
     * @fires Phaser.Data.Events#REMOVE_DATA
     * @since 3.0.0
     *
     * @param {(string|string[])} key - The key to remove, or an array of keys to remove.
     *
     * @return {this} This DataManager object.
     */
    remove: function (key)
    {
        if (this._frozen)
        {
            return this;
        }

        if (Array.isArray(key))
        {
            for (var i = 0; i < key.length; i++)
            {
                this.removeValue(key[i]);
            }
        }
        else
        {
            return this.removeValue(key);
        }

        return this;
    },

    /**
     * Internal value remover, called automatically by the `remove` method.
     *
     * @method Phaser.Data.DataManager#removeValue
     * @private
     * @fires Phaser.Data.Events#REMOVE_DATA
     * @since 3.10.0
     *
     * @param {string} key - The key to set the value for.
     *
     * @return {this} This DataManager object.
     */
    removeValue: function (key)
    {
        if (this.has(key))
        {
            var data = this.list[key];

            delete this.list[key];
            delete this.values[key];

            this.events.emit(Events.REMOVE_DATA, this.parent, key, data);
        }

        return this;
    },

    /**
     * Retrieves the data associated with the given 'key', deletes it from this Data Manager, then returns it.
     *
     * @method Phaser.Data.DataManager#pop
     * @fires Phaser.Data.Events#REMOVE_DATA
     * @since 3.0.0
     *
     * @param {string} key - The key of the value to retrieve and delete.
     *
     * @return {*} The value of the given key.
     */
    pop: function (key)
    {
        var data = undefined;

        if (!this._frozen && this.has(key))
        {
            data = this.list[key];

            delete this.list[key];
            delete this.values[key];

            this.events.emit(Events.REMOVE_DATA, this.parent, key, data);
        }

        return data;
    },

    /**
     * Determines whether the given key is set in this Data Manager.
     *
     * Please note that the keys are case-sensitive and must be valid JavaScript Object property strings.
     * This means the keys `gold` and `Gold` are treated as two unique values within the Data Manager.
     *
     * @method Phaser.Data.DataManager#has
     * @since 3.0.0
     *
     * @param {string} key - The key to check.
     *
     * @return {boolean} Returns `true` if the key exists, otherwise `false`.
     */
    has: function (key)
    {
        return this.list.hasOwnProperty(key);
    },

    /**
     * Freeze or unfreeze this Data Manager. A frozen Data Manager will block all attempts
     * to create new values or update existing ones.
     *
     * @method Phaser.Data.DataManager#setFreeze
     * @since 3.0.0
     *
     * @param {boolean} value - Whether to freeze or unfreeze the Data Manager.
     *
     * @return {this} This DataManager object.
     */
    setFreeze: function (value)
    {
        this._frozen = value;

        return this;
    },

    /**
     * Delete all data in this Data Manager and unfreeze it.
     *
     * @method Phaser.Data.DataManager#reset
     * @since 3.0.0
     *
     * @return {this} This DataManager object.
     */
    reset: function ()
    {
        for (var key in this.list)
        {
            delete this.list[key];
            delete this.values[key];
        }

        this._frozen = false;

        return this;
    },

    /**
     * Destroy this data manager.
     *
     * @method Phaser.Data.DataManager#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.reset();

        this.events.off(Events.CHANGE_DATA);
        this.events.off(Events.SET_DATA);
        this.events.off(Events.REMOVE_DATA);

        this.parent = null;
    },

    /**
     * Gets or sets the frozen state of this Data Manager.
     * A frozen Data Manager will block all attempts to create new values or update existing ones.
     *
     * @name Phaser.Data.DataManager#freeze
     * @type {boolean}
     * @since 3.0.0
     */
    freeze: {

        get: function ()
        {
            return this._frozen;
        },

        set: function (value)
        {
            this._frozen = (value) ? true : false;
        }

    },

    /**
     * Return the total number of entries in this Data Manager.
     *
     * @name Phaser.Data.DataManager#count
     * @type {integer}
     * @since 3.0.0
     */
    count: {

        get: function ()
        {
            var i = 0;

            for (var key in this.list)
            {
                if (this.list[key] !== undefined)
                {
                    i++;
                }
            }

            return i;
        }

    }

});

module.exports = DataManager;