<commit_before>import MagicString from 'magic-string';
import { parseExpressionAt } from 'acorn';
import annotateWithScopes from '../../utils/annotateWithScopes';
import isReference from '../../utils/isReference';
import { walk } from 'estree-walker';
import deindent from '../../utils/deindent';
import { stringify } from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
import visit from './visit';
import shared from './shared';
import Generator from '../Generator';
import Stylesheet from '../../css/Stylesheet';
import preprocess from './preprocess';
import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';

export class DomGenerator extends Generator {
	blocks: (Block|string)[];
	readonly: Set<string>;
	metaBindings: string[];

	hydratable: boolean;
	legacy: boolean;

	hasIntroTransitions: boolean;
	hasOutroTransitions: boolean;
	hasComplexBindings: boolean;

	needsEncapsulateHelper: boolean;

	constructor(
		parsed: Parsed,
		source: string,
		name: string,
		stylesheet: Stylesheet,
		options: CompileOptions
	) {
		super(parsed, source, name, stylesheet, options);
		this.blocks = [];

		this.readonly = new Set();

		this.hydratable = options.hydratable;
		this.legacy = options.legacy;
		this.needsEncapsulateHelper = false;

		// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
		this.metaBindings = [];
	}
}

export default function dom(
	parsed: Parsed,
	source: string,
	stylesheet: Stylesheet,
	options: CompileOptions
) {
	const format = options.format || 'es';

	const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options);

	const {
		computations,
		hasJs,
		name,
		templateProperties,
		namespace,
	} = generator;

	const { block, state } = preprocess(generator, namespace, parsed.html);

	generator.stylesheet.warnOnUnusedSelectors(options.onwarn);

	parsed.html.children.forEach((node: Node) => {
		visit(generator, block, state, node, []);
	});

	const builder = new CodeBuilder();
	const computationBuilder = new CodeBuilder();

	if (computations.length) {
		computations.forEach(({ key, deps }) => {
			if (generator.readonly.has(key)) {
				// <:Window> bindings
				throw new Error(
					`Cannot have a computed value '${key}' that clashes with a read-only property`
				);
			}

			generator.readonly.add(key);

			const condition = `isInitial || ${deps.map(dep => `changed.${dep}`).join(' || ')}`;

			const statement = `if ( @differs( ( state.${key} = @template.computed.${key}( ${deps
				.map(dep => `state.${dep}`)
				.join(', ')} ) ), oldState.${key} ) ) changed.${key} = true;`;

			computationBuilder.addConditional(condition, statement);
		});
	}

	if (hasJs) {
		builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`);
	}

	if (generator.needsEncapsulateHelper) {
		builder.addBlock(deindent`
			function @encapsulateStyles ( node ) {
				@setAttribute( node, '${generator.stylesheet.id}', '' );
			}
		`);
	}

	if (generator.stylesheet.hasStyles && options.css !== false) {
		const { css, cssMap } = generator.stylesheet.render(options.filename);

		const textContent = stringify(options.dev ?
			`${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` :
			css, { onlyEscapeAtSymbol: true });

		builder.addBlock(deindent`
			function @add_css () {
				var style = @createElement( 'style' );
				style.id = '${generator.stylesheet.id}-style';
				style.textContent = ${textContent};
				@appendNode( style, document.head );
			}
		`);
	}

	generator.blocks.forEach(block => {
		builder.addBlock(block.toString());
	});

	const sharedPath = options.shared === true
		? 'svelte/shared.js'
		: options.shared;

	const prototypeBase =
		`${name}.prototype` +
		(templateProperties.methods ? `, @template.methods` : '');
	const proto = sharedPath
		? `@proto `
		: deindent`
		{
			${['destroy', 'get', 'fire', 'observe', 'on', 'set', '_set', 'teardown']
				.map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`)
				.join(',\n')}
		}`;

	// TODO deprecate component.teardown()
	builder.addBlock(deindent`
		function ${name} ( options ) {
			options = options || {};
			${options.dev &&
				`if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
			${generator.usesRefs && `this.refs = {};`}
			this._state = ${templateProperties.data
				? `@assign( @template.data(), options.data )`
				: `options.data || {}`};
			${generator.metaBindings}
			${computations.length && `this._recompute( {}, this._state, {}, true );`}
			${options.dev &&
				Array.from(generator.expectedProperties).map(
					prop =>
						`if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`
				)}
			${generator.bindingGroups.length &&
				`this._bindingGroups = [ ${Array(generator.bindingGroups.length)
					.fill('[]')
					.join(', ')} ];`}

			this._observers = {
				pre: Object.create( null ),
				post: Object.create( null )
			};

			this._handlers = Object.create( null );
			${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`}

			this._root = options._root || this;
			this._yield = options._yield;
			this._bind = options._bind;

			${generator.stylesheet.hasStyles &&
				options.css !== false &&
				`if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`}

			${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`}

			${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
				if ( !options._root ) {
					this._oncreate = [${templateProperties.oncreate && `oncreate`}];
					${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
					${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
				} ${templateProperties.oncreate && deindent`
					else {
						this._root._oncreate.push(oncreate);
					}
				`}
			`}

			this._fragment = @create_main_fragment( this._state, this );

			if ( options.target ) {
				${generator.hydratable
					? deindent`
						var nodes = @children( options.target );
						options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create();
						nodes.forEach( @detachNode );
					` :
					deindent`
						${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`}
						this._fragment.create();
					`}
				this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( options.target, null );
			}

			${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent`
				if ( !options._root ) {
					${generator.hasComponents && `this._lock = true;`}
					${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
					${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`}
					${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
					${generator.hasComponents && `this._lock = false;`}
				}
			`}
		}

		@assign( ${prototypeBase}, ${proto});

		${options.dev && deindent`
			${name}.prototype._checkReadOnly = function _checkReadOnly ( newState ) {
				${Array.from(generator.readonly).map(
					prop =>
						`if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );`
				)}
			};
		`}

		${computations.length ? deindent`<commit_msg>allow an anchor element to be passed for initial component bootstrap<commit_after>import MagicString from 'magic-string';
import { parseExpressionAt } from 'acorn';
import annotateWithScopes from '../../utils/annotateWithScopes';
import isReference from '../../utils/isReference';
import { walk } from 'estree-walker';
import deindent from '../../utils/deindent';
import { stringify } from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
import visit from './visit';
import shared from './shared';
import Generator from '../Generator';
import Stylesheet from '../../css/Stylesheet';
import preprocess from './preprocess';
import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';

export class DomGenerator extends Generator {
	blocks: (Block|string)[];
	readonly: Set<string>;
	metaBindings: string[];

	hydratable: boolean;
	legacy: boolean;

	hasIntroTransitions: boolean;
	hasOutroTransitions: boolean;
	hasComplexBindings: boolean;

	needsEncapsulateHelper: boolean;

	constructor(
		parsed: Parsed,
		source: string,
		name: string,
		stylesheet: Stylesheet,
		options: CompileOptions
	) {
		super(parsed, source, name, stylesheet, options);
		this.blocks = [];

		this.readonly = new Set();

		this.hydratable = options.hydratable;
		this.legacy = options.legacy;
		this.needsEncapsulateHelper = false;

		// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
		this.metaBindings = [];
	}
}

export default function dom(
	parsed: Parsed,
	source: string,
	stylesheet: Stylesheet,
	options: CompileOptions
) {
	const format = options.format || 'es';

	const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options);

	const {
		computations,
		hasJs,
		name,
		templateProperties,
		namespace,
	} = generator;

	const { block, state } = preprocess(generator, namespace, parsed.html);

	generator.stylesheet.warnOnUnusedSelectors(options.onwarn);

	parsed.html.children.forEach((node: Node) => {
		visit(generator, block, state, node, []);
	});

	const builder = new CodeBuilder();
	const computationBuilder = new CodeBuilder();

	if (computations.length) {
		computations.forEach(({ key, deps }) => {
			if (generator.readonly.has(key)) {
				// <:Window> bindings
				throw new Error(
					`Cannot have a computed value '${key}' that clashes with a read-only property`
				);
			}

			generator.readonly.add(key);

			const condition = `isInitial || ${deps.map(dep => `changed.${dep}`).join(' || ')}`;

			const statement = `if ( @differs( ( state.${key} = @template.computed.${key}( ${deps
				.map(dep => `state.${dep}`)
				.join(', ')} ) ), oldState.${key} ) ) changed.${key} = true;`;

			computationBuilder.addConditional(condition, statement);
		});
	}

	if (hasJs) {
		builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`);
	}

	if (generator.needsEncapsulateHelper) {
		builder.addBlock(deindent`
			function @encapsulateStyles ( node ) {
				@setAttribute( node, '${generator.stylesheet.id}', '' );
			}
		`);
	}

	if (generator.stylesheet.hasStyles && options.css !== false) {
		const { css, cssMap } = generator.stylesheet.render(options.filename);

		const textContent = stringify(options.dev ?
			`${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` :
			css, { onlyEscapeAtSymbol: true });

		builder.addBlock(deindent`
			function @add_css () {
				var style = @createElement( 'style' );
				style.id = '${generator.stylesheet.id}-style';
				style.textContent = ${textContent};
				@appendNode( style, document.head );
			}
		`);
	}

	generator.blocks.forEach(block => {
		builder.addBlock(block.toString());
	});

	const sharedPath = options.shared === true
		? 'svelte/shared.js'
		: options.shared;

	const prototypeBase =
		`${name}.prototype` +
		(templateProperties.methods ? `, @template.methods` : '');
	const proto = sharedPath
		? `@proto `
		: deindent`
		{
			${['destroy', 'get', 'fire', 'observe', 'on', 'set', '_set', 'teardown']
				.map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`)
				.join(',\n')}
		}`;

	// TODO deprecate component.teardown()
	builder.addBlock(deindent`
		function ${name} ( options ) {
			options = options || {};
			${options.dev &&
				`if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
			${generator.usesRefs && `this.refs = {};`}
			this._state = ${templateProperties.data
				? `@assign( @template.data(), options.data )`
				: `options.data || {}`};
			${generator.metaBindings}
			${computations.length && `this._recompute( {}, this._state, {}, true );`}
			${options.dev &&
				Array.from(generator.expectedProperties).map(
					prop =>
						`if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`
				)}
			${generator.bindingGroups.length &&
				`this._bindingGroups = [ ${Array(generator.bindingGroups.length)
					.fill('[]')
					.join(', ')} ];`}

			this._observers = {
				pre: Object.create( null ),
				post: Object.create( null )
			};

			this._handlers = Object.create( null );
			${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`}

			this._root = options._root || this;
			this._yield = options._yield;
			this._bind = options._bind;

			${generator.stylesheet.hasStyles &&
				options.css !== false &&
				`if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`}

			${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`}

			${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
				if ( !options._root ) {
					this._oncreate = [${templateProperties.oncreate && `oncreate`}];
					${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
					${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
				} ${templateProperties.oncreate && deindent`
					else {
						this._root._oncreate.push(oncreate);
					}
				`}
			`}

			this._fragment = @create_main_fragment( this._state, this );

			if ( options.target ) {
				${generator.hydratable
					? deindent`
						var nodes = @children( options.target );
						options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create();
						nodes.forEach( @detachNode );
					` :
					deindent`
						${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`}
						this._fragment.create();
					`}
				this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( options.target, options.anchor || null );
			}

			${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent`
				if ( !options._root ) {
					${generator.hasComponents && `this._lock = true;`}
					${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
					${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`}
					${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
					${generator.hasComponents && `this._lock = false;`}
				}
			`}
		}

		@assign( ${prototypeBase}, ${proto});

		${options.dev && deindent`
			${name}.prototype._checkReadOnly = function _checkReadOnly ( newState ) {
				${Array.from(generator.readonly).map(
					prop =>
						`if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );`
				)}
			};
		`}

		${computations.length ? deindent`
{'fraction_non_alphanumeric': 0.1716674227204977, 'fraction_numerical': 0.0, 'mean_word_length': 3.86813880126183, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '940593', 'n_tokens_mistral': 5534, 'n_tokens_neox': 4798, 'n_words': 1004}