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

90.62% Statements 87/96
73.33% Branches 11/15
100% Functions 1/1
90.1% Lines 82/91

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 922x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4576x 4576x 4576x 1x 1x 4574x 4574x 4574x 4574x 4574x 4574x 4574x 4574x 4574x 4574x 4576x     4574x 4574x 4574x 4574x 4574x 4574x 4574x 4576x 166x     166x 166x 2x 2x 166x 166x 78x       78x 78x 78x 166x 166x 7x     7x 7x 7x 7x 1x 1x 6x 6x 6x 166x 4573x 4573x 4573x 4573x 4573x 4573x 4573x 4573x 4573x 4573x 4573x 4573x  
/** @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);
			}
 
			context = 'module';
		}
	}
 
	return {
		type: 'Script',
		start,
		end: parser.index,
		context,
		content: ast,
		parent: null,
		// @ts-ignore
		attributes
	};
}