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

97.43% Statements 152/156
86.11% Branches 31/36
100% Functions 2/2
97.31% Lines 145/149

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 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 1502x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1486x 1486x 1486x 1486x 1486x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 1190x 296x 1317x     296x 296x 1486x 1486x 1486x 7967x 7967x 249x 7967x 545x 545x     545x 296x 296x 296x 545x 7967x 7967x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 296x 1303x 2x 2x 293x 293x 1303x 3x 3x 1486x 2x 2x 2x 2x 2x 2x 1482x 1482x 1482x 1482x 1482x 8x 8x 8x 8x 1482x 1482x 1455x 1455x 1455x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 231x 8x 8x 8x 8x 27x 27x 164x 6x 6x 27x 27x 27x 27x 27x 27x 27x 27x 27x  
// @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart } from 'acorn';
import full_char_code_at from '../utils/full_char_code_at.js';
import {
	is_bracket_open,
	is_bracket_close,
	is_bracket_pair,
	get_bracket_close
} from '../utils/bracket.js';
import { parse_expression_at } from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js';
import { locator } from '../../../state.js';
 
/**
 * @param {import('../index.js').Parser} parser
 * @param {boolean} [optional_allowed]
 * @returns {import('estree').Pattern}
 */
export default function read_pattern(parser, optional_allowed = false) {
	const start = parser.index;
	let i = parser.index;
 
	const code = full_char_code_at(parser.template, i);
	if (isIdentifierStart(code, true)) {
		const name = /** @type {string} */ (parser.read_identifier());
		const annotation = read_type_annotation(parser, optional_allowed);
 
		return {
			type: 'Identifier',
			name,
			start,
			loc: {
				start: /** @type {import('locate-character').Location} */ (locator(start)),
				end: /** @type {import('locate-character').Location} */ (locator(parser.index))
			},
			end: parser.index,
			typeAnnotation: annotation
		};
	}
 
	if (!is_bracket_open(code)) {
		e.expected_pattern(i);
	}
 
	const bracket_stack = [code];
	i += code <= 0xffff ? 1 : 2;
 
	while (i < parser.template.length) {
		const code = full_char_code_at(parser.template, i);
		if (is_bracket_open(code)) {
			bracket_stack.push(code);
		} else if (is_bracket_close(code)) {
			const popped = /** @type {number} */ (bracket_stack.pop());
			if (!is_bracket_pair(popped, code)) {
				e.expected_token(i, String.fromCharCode(/** @type {number} */ (get_bracket_close(popped))));
			}
			if (bracket_stack.length === 0) {
				i += code <= 0xffff ? 1 : 2;
				break;
			}
		}
		i += code <= 0xffff ? 1 : 2;
	}
 
	parser.index = i;
 
	const pattern_string = parser.template.slice(start, i);
	try {
		// the length of the `space_with_newline` has to be start - 1
		// because we added a `(` in front of the pattern_string,
		// which shifted the entire string to right by 1
		// so we offset it by removing 1 character in the `space_with_newline`
		// to achieve that, we remove the 1st space encountered,
		// so it will not affect the `column` of the node
		let space_with_newline = parser.template
			.slice(0, start)
			.replace(regex_not_newline_characters, ' ');
		const first_space = space_with_newline.indexOf(' ');
		space_with_newline =
			space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
 
		const expression = /** @type {any} */ (
			parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1)
		).left;
 
		expression.typeAnnotation = read_type_annotation(parser, optional_allowed);
		if (expression.typeAnnotation) {
			expression.end = expression.typeAnnotation.end;
		}
 
		return expression;
	} catch (error) {
		parser.acorn_error(error);
	}
}
 
/**
 * @param {import('../index.js').Parser} parser
 * @param {boolean} [optional_allowed]
 * @returns {any}
 */
function read_type_annotation(parser, optional_allowed = false) {
	const start = parser.index;
	parser.allow_whitespace();
 
	if (optional_allowed && parser.eat('?')) {
		// Acorn-TS puts the optional info as a property on the surrounding node.
		// We spare the work here because it doesn't matter for us anywhere else.
		parser.allow_whitespace();
	}
 
	if (!parser.eat(':')) {
		parser.index = start;
		return undefined;
	}
 
	// we need to trick Acorn into parsing the type annotation
	const insert = '_ as ';
	let a = parser.index - insert.length;
	const template =
		parser.template.slice(0, a).replace(/[^\n]/g, ' ') +
		insert +
		// If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
		// parameters as part of a sequence expression instead, and will then error on optional
		// parameters (`?:`). Therefore replace that sequence with something that will not error.
		parser.template.slice(parser.index).replace(/\?\s*:/g, ':');
	let expression = parse_expression_at(template, parser.ts, a);
 
	// `foo: bar = baz` gets mangled — fix it
	if (expression.type === 'AssignmentExpression') {
		let b = expression.right.start;
		while (template[b] !== '=') b -= 1;
		expression = parse_expression_at(template.slice(0, b), parser.ts, a);
	}
 
	// `array as item: string, index` becomes `string, index`, which is mistaken as a sequence expression - fix that
	if (expression.type === 'SequenceExpression') {
		expression = expression.expressions[0];
	}
 
	parser.index = /** @type {number} */ (expression.end);
	return {
		type: 'TSTypeAnnotation',
		start,
		end: parser.index,
		typeAnnotation: /** @type {any} */ (expression).typeAnnotation
	};
}