src/parse/state/tag.js/tag
export default function tag ( parser ) {
	const start = parser.index++;

	let parent = parser.current();

	if ( parser.eat( '!--' ) ) {
		const data = parser.readUntil( /-->/ );
		parser.eat( '-->' );

		parser.current().children.push({
			start,
			end: parser.index,
			type: 'Comment',
			data
		});

		return null;
	}

	const isClosingTag = parser.eat( '/' );

	const name = readTagName( parser );

	if ( name in metaTags ) {
		if ( name in parser.metaTags ) {
			if ( isClosingTag && parser.current().children.length ) {
				parser.error( `<${name}> cannot have children`, parser.current().children[0].start );
			}

			parser.error( `A component can only have one <${name}> tag`, start );
		}

		parser.metaTags[ name ] = true;

		if ( parser.stack.length > 1 ) {
			parser.error( `<${name}> tags cannot be inside elements or blocks`, start );
		}
	}

	parser.allowWhitespace();

	if ( isClosingTag ) {
		if ( isVoidElementName( name ) ) {
			parser.error( `<${name}> is a void element and cannot have children, or a closing tag`, start );
		}

		if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );

		// close any elements that don't have their own closing tags, e.g. <div><p></div>
		while ( parent.name !== name ) {
			if ( parent.type !== 'Element' ) parser.error( `</${name}> attempted to close an element that was not open`, start );

			parent.end = start;
			parser.stack.pop();

			parent = parser.current();
		}

		// strip leading/trailing whitespace as necessary
		stripWhitespace( parent );

		parent.end = parser.index;
		parser.stack.pop();

		return null;
	} else if ( disallowedContents.has( parent.name ) ) {
		// can this be a child of the parent element, or does it implicitly
		// close it, like `<li>one<li>two`?
		if ( disallowedContents.get( parent.name ).has( name ) ) {
			stripWhitespace( parent );

			parent.end = start;
			parser.stack.pop();
		}
	}

	const attributes = [];
	const uniqueNames = new Set();

	let attribute;
	while ( attribute = readAttribute( parser, uniqueNames ) ) {
		attributes.push( attribute );
		parser.allowWhitespace();
	}

	parser.allowWhitespace();

	// special cases – top-level <script> and <style>
	if ( specials.has( name ) && parser.stack.length === 1 ) {
		const special = specials.get( name );

		if ( parser[ special.property ] ) {
			parser.index = start;
			parser.error( `You can only have one top-level <${name}> tag per component` );
		}

		parser.eat( '>', true );
		parser[ special.property ] = special.read( parser, start, attributes );
		return;
	}

	const element = {
		start,
		end: null, // filled in later
		type: 'Element',
		name,
		attributes,
		children: []
	};

	parser.current().children.push( element );

	const selfClosing = parser.eat( '/' ) || isVoidElementName( name );

	parser.eat( '>', true );

	if ( selfClosing ) {
		element.end = parser.index;
	} else {
		// don't push self-closing elements onto the stack
		parser.stack.push( element );
	}

	return null;
}