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

90.81% Statements 89/98
73.33% Branches 11/15
100% Functions 1/1
90.32% Lines 84/93

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 942x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4410x 4410x 4410x 1x 1x 4408x 4408x 4408x 4408x 4408x 4408x 4408x 4408x 4408x 4408x 4410x     4408x 4408x 4408x 4408x 4408x 4408x 4408x 4410x 150x     150x 150x 2x 2x 150x 150x 76x       76x 76x 76x 150x 150x 6x     6x 6x 6x 6x 1x 1x 5x 5x 5x 5x 5x 150x 4407x 4407x 4407x 4407x 4407x 4407x 4407x 4407x 4407x 4407x 4407x 4407x  
/** @import { Program } from 'estree' */
/** @import { AST, Directive } 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';
import { is_text_attribute } from '../../../utils/ast.js';
 
const regex_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
 
const RESERVED_ATTRIBUTES = ['server', 'client', 'worker', 'test', 'default'];
const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
 
/**
 * @param {Parser} parser
 * @param {number} start
 * @param {Array<AST.Attribute | AST.SpreadAttribute | Directive>} attributes
 * @returns {AST.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;
 
	/** @type {'default' | 'module'} */
	let context = 'default';
 
	for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
		if (RESERVED_ATTRIBUTES.includes(attribute.name)) {
			e.script_reserved_attribute(attribute, attribute.name);
		}
 
		if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
			w.script_unknown_attribute(attribute);
		}
 
		if (attribute.name === 'module') {
			if (attribute.value !== true) {
				// Deliberately a generic code to future-proof for potential other attributes
				e.script_invalid_attribute_value(attribute, attribute.name);
			}
 
			context = 'module';
		}
 
		if (attribute.name === 'context') {
			if (attribute.value === true || !is_text_attribute(attribute)) {
				e.script_invalid_context(attribute);
			}
 
			const value = attribute.value[0].data;
 
			if (value !== 'module') {
				e.script_invalid_context(attribute);
			}
 
			w.script_context_deprecated(attribute);
 
			context = 'module';
		}
	}
 
	return {
		type: 'Script',
		start,
		end: parser.index,
		context,
		content: ast,
		parent: null,
		// @ts-ignore
		attributes
	};
}