All files / src/compiler/phases/1-parse/read script.js

90.81% Statements 89/98
57.89% Branches 11/19
100% Functions 2/2
90.42% Lines 85/94

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 952x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4381x 4381x 137x 137x 6x     6x 6x 6x 6x 1x 1x 5x 5x 5x 5x 5x 137x 73x       73x 73x 73x 137x 137x 137x 137x 137x     137x 137x 4302x 4302x 4302x 2x 2x 2x 2x 2x 2x 2x 2x 4383x 4383x 4383x 1x 1x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4383x     4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x 4381x  
/** @import { Program } from 'estree' */
/** @import { Attribute, SpreadAttribute, Directive, Script } from '#compiler' */
/** @import { Parser } from '../index.js' */
import * as acorn from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
 
const regex_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
 
/**
 * @param {any[]} attributes
 * @returns {string}
 */
function get_context(attributes) {
	for (const attribute of attributes) {
		switch (attribute.name) {
			case 'context': {
				if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') {
					e.script_invalid_context(attribute.start);
				}
 
				const value = attribute.value[0].data;
 
				if (value !== 'module') {
					e.script_invalid_context(attribute.start);
				}
 
				w.script_context_deprecated(attribute);
 
				return value;
			}
			case 'module': {
				if (attribute.value !== true) {
					// Deliberately a generic code to future-proof for potential other attributes
					e.script_invalid_attribute_value(attribute.start, attribute.name);
				}
 
				return 'module';
			}
			case 'server':
			case 'client':
			case 'worker':
			case 'test':
			case 'default': {
				e.script_reserved_attribute(attribute.start, attribute.name);
			}
		}
	}
 
	return 'default';
}
 
/**
 * @param {Parser} parser
 * @param {number} start
 * @param {Array<Attribute | SpreadAttribute | Directive>} attributes
 * @returns {Script}
 */
export function read_script(parser, start, attributes) {
	const script_start = parser.index;
	const data = parser.read_until(regex_closing_script_tag);
	if (parser.index >= parser.template.length) {
		e.element_unclosed(parser.template.length, 'script');
	}
 
	const source =
		parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
	parser.read(regex_starts_with_closing_script_tag);
 
	/** @type {Program} */
	let ast;
 
	try {
		ast = acorn.parse(source, parser.ts);
	} catch (err) {
		parser.acorn_error(err);
	}
 
	// TODO is this necessary?
	ast.start = script_start;
 
	return {
		type: 'Script',
		start,
		end: parser.index,
		context: get_context(attributes),
		content: ast,
		parent: null,
		// @ts-ignore
		attributes: attributes
	};
}