Skip to content

Commit 55ef2d4

Browse files
committed
reuse bracket matching logic
1 parent 0e9c9d1 commit 55ef2d4

File tree

3 files changed

+96
-95
lines changed

3 files changed

+96
-95
lines changed

packages/svelte/src/compiler/phases/1-parse/read/context.js

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/** @import { Location } from 'locate-character' */
22
/** @import { Pattern } from 'estree' */
33
/** @import { Parser } from '../index.js' */
4-
import { is_bracket_open, is_bracket_close, get_bracket_close } from '../utils/bracket.js';
4+
import {
5+
is_bracket_open,
6+
is_bracket_close,
7+
get_bracket_close,
8+
match_bracket
9+
} from '../utils/bracket.js';
510
import { parse_expression_at } from '../acorn.js';
611
import { regex_not_newline_characters } from '../../patterns.js';
712
import * as e from '../../../errors.js';
@@ -71,75 +76,6 @@ export default function read_pattern(parser) {
7176
}
7277
}
7378

74-
/**
75-
* @param {Parser} parser
76-
* @param {number} start
77-
*/
78-
function match_bracket(parser, start) {
79-
const bracket_stack = [];
80-
81-
let i = start;
82-
83-
while (i < parser.template.length) {
84-
let char = parser.template[i++];
85-
86-
if (char === "'" || char === '"' || char === '`') {
87-
i = match_quote(parser, i, char);
88-
continue;
89-
}
90-
91-
if (is_bracket_open(char)) {
92-
bracket_stack.push(char);
93-
} else if (is_bracket_close(char)) {
94-
const popped = /** @type {string} */ (bracket_stack.pop());
95-
const expected = /** @type {string} */ (get_bracket_close(popped));
96-
97-
if (char !== expected) {
98-
e.expected_token(i - 1, expected);
99-
}
100-
101-
if (bracket_stack.length === 0) {
102-
return i;
103-
}
104-
}
105-
}
106-
107-
e.unexpected_eof(parser.template.length);
108-
}
109-
110-
/**
111-
* @param {Parser} parser
112-
* @param {number} start
113-
* @param {string} quote
114-
*/
115-
function match_quote(parser, start, quote) {
116-
let is_escaped = false;
117-
let i = start;
118-
119-
while (i < parser.template.length) {
120-
const char = parser.template[i++];
121-
122-
if (is_escaped) {
123-
is_escaped = false;
124-
continue;
125-
}
126-
127-
if (char === quote) {
128-
return i;
129-
}
130-
131-
if (char === '\\') {
132-
is_escaped = true;
133-
}
134-
135-
if (quote === '`' && char === '$' && parser.template[i] === '{') {
136-
i = match_bracket(parser, i);
137-
}
138-
}
139-
140-
e.unterminated_string_constant(start);
141-
}
142-
14379
/**
14480
* @param {Parser} parser
14581
* @returns {any}

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import { parse_expression_at } from '../acorn.js';
88
import read_pattern from '../read/context.js';
99
import read_expression, { get_loose_identifier } from '../read/expression.js';
1010
import { create_fragment } from '../utils/create.js';
11+
import { match_bracket } from '../utils/bracket.js';
1112

1213
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
1314

15+
const pointy_bois = { '<': '>' };
16+
1417
/** @param {Parser} parser */
1518
export default function tag(parser) {
1619
const start = parser.index;
@@ -352,35 +355,17 @@ function open(parser) {
352355
const params_start = parser.index;
353356

354357
// snippets could have a generic signature, e.g. `#snippet foo<T>(...)`
358+
/** @type {string | undefined} */
355359
let generic;
356360

357361
// if we match a generic opening
358362
if (parser.ts && parser.match('<')) {
359-
parser.index += 1;
360-
// keep track of the amount of open brackets
361-
let open_brackets = 1;
362-
// except when we are in quotes eg. <T extends "<">
363-
let in_quotes;
364-
365-
while (parser.index < parser.template.length) {
366-
const char = parser.template[parser.index];
367-
if (!in_quotes && char === '<') open_brackets++;
368-
if (!in_quotes && char === '>') open_brackets--;
369-
if (!in_quotes && (char === '"' || char === "'" || char === '`')) {
370-
in_quotes = char;
371-
} else if (char === in_quotes && parser.template[parser.index - 1] !== '\\') {
372-
// don't close the quotes if we are escaping
373-
in_quotes = undefined;
374-
}
375-
parser.index += 1;
376-
if (open_brackets === 0) {
377-
break;
378-
}
379-
if (generic == null) {
380-
generic = '';
381-
}
382-
generic += char;
383-
}
363+
const start = parser.index;
364+
const end = match_bracket(parser, start, pointy_bois);
365+
366+
generic = parser.template.slice(start + 1, end - 1);
367+
368+
parser.index = end;
384369
}
385370

386371
parser.allow_whitespace();

packages/svelte/src/compiler/phases/1-parse/utils/bracket.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/** @import { Parser } from '../index.js' */
2+
import * as e from '../../../errors.js';
3+
14
const SQUARE_BRACKET_OPEN = '[';
25
const SQUARE_BRACKET_CLOSE = ']';
36
const CURLY_BRACKET_OPEN = '{';
@@ -162,3 +165,80 @@ export function find_matching_bracket(template, index, open) {
162165
}
163166
return undefined;
164167
}
168+
169+
const default_brackets = {
170+
'{': '}',
171+
'(': ')',
172+
'[': ']'
173+
};
174+
175+
/**
176+
* @param {Parser} parser
177+
* @param {number} start
178+
* @param {Record<string, string>} brackets
179+
*/
180+
export function match_bracket(parser, start, brackets = default_brackets) {
181+
const close = Object.values(brackets);
182+
const bracket_stack = [];
183+
184+
let i = start;
185+
186+
while (i < parser.template.length) {
187+
let char = parser.template[i++];
188+
189+
if (char === "'" || char === '"' || char === '`') {
190+
i = match_quote(parser, i, char);
191+
continue;
192+
}
193+
194+
if (char in brackets) {
195+
bracket_stack.push(char);
196+
} else if (close.includes(char)) {
197+
const popped = /** @type {string} */ (bracket_stack.pop());
198+
const expected = /** @type {string} */ (brackets[popped]);
199+
200+
if (char !== expected) {
201+
e.expected_token(i - 1, expected);
202+
}
203+
204+
if (bracket_stack.length === 0) {
205+
return i;
206+
}
207+
}
208+
}
209+
210+
e.unexpected_eof(parser.template.length);
211+
}
212+
213+
/**
214+
* @param {Parser} parser
215+
* @param {number} start
216+
* @param {string} quote
217+
*/
218+
function match_quote(parser, start, quote) {
219+
let is_escaped = false;
220+
let i = start;
221+
222+
while (i < parser.template.length) {
223+
const char = parser.template[i++];
224+
225+
if (is_escaped) {
226+
is_escaped = false;
227+
continue;
228+
}
229+
230+
if (char === quote) {
231+
return i;
232+
}
233+
234+
if (char === '\\') {
235+
is_escaped = true;
236+
}
237+
238+
if (quote === '`' && char === '$' && parser.template[i] === '{') {
239+
i = match_bracket(parser, i);
240+
}
241+
}
242+
243+
e.unterminated_string_constant(start);
244+
}

0 commit comments

Comments
 (0)