File size: 2,819 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// @ts-ignore
// import parse from 'css-tree/parser'; // When css-tree supports container queries uncomment.
import { parse } from './css-tree-cq/css_tree_parse.js'; // Use extended css-tree for container query support.
import { walk } from 'estree-walker';
import parser_errors from '../errors.js';

const regex_closing_style_tag = /<\/style\s*>/;
const regex_starts_with_closing_style_tag = /^<\/style\s*>/;

/**
 *
 * @param {import('../index.js').Parser} parser
 * @param {number} start
 * @param {import('estree').Node[]} attributes
 * @returns {import('../../interfaces.js').Style}
 */
export default function read_style(parser, start, attributes) {
	const content_start = parser.index;

	const styles = parser.read_until(regex_closing_style_tag, parser_errors.unclosed_style);

	if (parser.index >= parser.template.length) {
		parser.error(parser_errors.unclosed_style);
	}

	const content_end = parser.index;

	// discard styles when css is disabled
	if (parser.css_mode === 'none') {
		parser.read(regex_starts_with_closing_style_tag);
		return null;
	}

	let ast;

	try {
		ast = parse(styles, {
			positions: true,
			offset: content_start,
			onParseError(error) {
				throw error;
			}
		});
	} catch (err) {
		if (err.name === 'SyntaxError') {
			parser.error(parser_errors.css_syntax_error(err.message), err.offset);
		} else {
			throw err;
		}
	}

	ast = JSON.parse(JSON.stringify(ast));

	// tidy up AST
	walk(ast, {
		/** @param {any} node */
		enter: (node) => {
			// `any` because this isn't an ESTree node
			// replace `ref:a` nodes
			if (node.type === 'Selector') {
				for (let i = 0; i < node.children.length; i += 1) {
					const a = node.children[i];
					const b = node.children[i + 1];

					if (is_ref_selector(a, b)) {
						parser.error(parser_errors.invalid_ref_selector, a.loc.start.offset);
					}
				}
			}

			if (
				node.type === 'Declaration' &&
				node.value.type === 'Value' &&
				node.value.children.length === 0
			) {
				parser.error(parser_errors.invalid_declaration, node.start);
			}

			if (node.type === 'PseudoClassSelector' && node.name === 'global' && node.children === null) {
				parser.error(parser_errors.empty_global_selector, node.loc.start.offset);
			}

			if (node.loc) {
				node.start = node.loc.start.offset;
				node.end = node.loc.end.offset;
				delete node.loc;
			}
		}
	});

	parser.read(regex_starts_with_closing_style_tag);

	const end = parser.index;

	return {
		type: 'Style',
		start,
		end,
		attributes,
		children: ast.children,
		content: {
			start: content_start,
			end: content_end,
			styles
		}
	};
}

/**
 * @param {any} a
 * @param {any} b
 */
function is_ref_selector(a, b) {
	// TODO add CSS node types
	if (!b) return false;

	return a.type === 'TypeSelector' && a.name === 'ref' && b.type === 'PseudoClassSelector';
}