@lithent/lithent-template-parser 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/codegen/createElement.d.cts +10 -0
- package/dist/codegen/createElement.d.ts +1 -1
- package/dist/codegen/index.d.cts +1 -0
- package/dist/codegen/index.d.ts +1 -1
- package/dist/compiler.d.cts +26 -0
- package/dist/compiler.d.ts +8 -8
- package/dist/docPipe/index.d.cts +40 -0
- package/dist/docPipe/index.d.ts +2 -2
- package/dist/docPipe/scan.d.cts +8 -0
- package/dist/docPipe/scan.d.ts +2 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +955 -1192
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.cjs +12 -12
- package/dist/index.umd.cjs.map +1 -1
- package/dist/parser/ast.d.cts +207 -0
- package/dist/parser/ast.d.ts +1 -1
- package/dist/parser/lexer.d.cts +109 -0
- package/dist/parser/lexer.d.ts +1 -1
- package/dist/parser/parser.d.cts +80 -0
- package/dist/parser/parser.d.ts +5 -4
- package/dist/parser/tokens.d.cts +51 -0
- package/dist/transform/directives.d.cts +30 -0
- package/dist/transform/directives.d.ts +1 -1
- package/dist/transform/index.d.cts +14 -0
- package/dist/transform/index.d.ts +5 -5
- package/dist/transform/optimize.d.cts +21 -0
- package/dist/transform/optimize.d.ts +1 -1
- package/package.json +2 -2
- package/src/codegen/createElement.ts +48 -332
- package/src/compiler.ts +1 -1
- package/src/docPipe/scan.ts +209 -246
- package/src/parser/lexer.ts +41 -91
- package/src/parser/parser.ts +45 -39
- package/src/tests/compiler.test.ts +2 -2
- package/src/tests/parser.test.ts +2 -2
- package/src/tests/regressions.test.ts +200 -0
- package/src/transform/directives.ts +25 -2
- package/src/transform/optimize.ts +37 -25
package/src/docPipe/scan.ts
CHANGED
|
@@ -4,201 +4,214 @@ export interface TemplateMatch {
|
|
|
4
4
|
snippet: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const identifierStart = (char: string): boolean => /[A-Za-z_$]/.test(char);
|
|
8
|
+
const identifierPart = (char: string): boolean => /[\w$]/.test(char);
|
|
9
|
+
const expressionKeywords = new Set([
|
|
10
|
+
'return',
|
|
11
|
+
'throw',
|
|
12
|
+
'yield',
|
|
13
|
+
'await',
|
|
14
|
+
'case',
|
|
15
|
+
'delete',
|
|
16
|
+
'void',
|
|
17
|
+
'typeof',
|
|
18
|
+
'new',
|
|
19
|
+
'in',
|
|
20
|
+
'instanceof',
|
|
21
|
+
'else',
|
|
22
|
+
'do',
|
|
23
|
+
]);
|
|
8
24
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
for (let code = 97; code <= 122; code++) {
|
|
16
|
-
DEFAULT_ALPHA_NUMERIC.add(code);
|
|
17
|
-
}
|
|
18
|
-
DEFAULT_ALPHA_NUMERIC.add(95); // _
|
|
19
|
-
DEFAULT_ALPHA_NUMERIC.add(36); // $
|
|
20
|
-
|
|
21
|
-
const isAlphaNumeric = (code: number): boolean => {
|
|
22
|
-
return DEFAULT_ALPHA_NUMERIC.has(code);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const shouldAttemptTemplate = (source: string, index: number): boolean => {
|
|
26
|
-
if (source[index] !== '<') {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const next = source.charCodeAt(index + 1);
|
|
31
|
-
if (Number.isNaN(next)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Allow fragments <>, closing </, comments <!-, normal tags <a
|
|
36
|
-
const isValidNext =
|
|
37
|
-
next === 47 || // /
|
|
38
|
-
next === 62 || // >
|
|
39
|
-
next === 33 || // ! (e.g. <!-- -->)
|
|
40
|
-
isAlphaNumeric(next);
|
|
41
|
-
if (!isValidNext) {
|
|
42
|
-
return false;
|
|
25
|
+
const skipQuoted = (source: string, index: number, quote: string): number => {
|
|
26
|
+
let i = index + 1;
|
|
27
|
+
while (i < source.length) {
|
|
28
|
+
if (source[i] === '\\') i += 2;
|
|
29
|
+
else if (source[i++] === quote) return i;
|
|
43
30
|
}
|
|
31
|
+
return source.length;
|
|
32
|
+
};
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
let i = index
|
|
47
|
-
while (i
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
i--;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (isAlphaNumeric(code)) {
|
|
54
|
-
// Identifier characters => likely relational operator (e.g. a<b)
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
34
|
+
const skipLineComment = (source: string, index: number): number => {
|
|
35
|
+
let i = index;
|
|
36
|
+
while (i < source.length && !/[\r\n]/.test(source[i])) i++;
|
|
37
|
+
return i;
|
|
38
|
+
};
|
|
59
39
|
|
|
60
|
-
|
|
40
|
+
const skipBlockComment = (source: string, index: number): number => {
|
|
41
|
+
const end = source.indexOf('*/', index + 2);
|
|
42
|
+
return end === -1 ? source.length : end + 2;
|
|
61
43
|
};
|
|
62
44
|
|
|
63
|
-
const
|
|
45
|
+
const skipRegex = (source: string, index: number): number => {
|
|
64
46
|
let i = index + 1;
|
|
47
|
+
let inClass = false;
|
|
65
48
|
while (i < source.length) {
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return i
|
|
73
|
-
}
|
|
74
|
-
i++;
|
|
49
|
+
const char = source[i++];
|
|
50
|
+
if (char === '\\') i++;
|
|
51
|
+
else if (char === '[') inClass = true;
|
|
52
|
+
else if (char === ']') inClass = false;
|
|
53
|
+
else if (char === '/' && !inClass) {
|
|
54
|
+
while (i < source.length && /[a-z]/i.test(source[i])) i++;
|
|
55
|
+
return i;
|
|
56
|
+
} else if (char === '\n' || char === '\r') return i;
|
|
75
57
|
}
|
|
76
|
-
return
|
|
58
|
+
return i;
|
|
77
59
|
};
|
|
78
60
|
|
|
79
61
|
const skipTemplateLiteral = (source: string, index: number): number => {
|
|
80
62
|
let i = index + 1;
|
|
81
63
|
while (i < source.length) {
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
if (ch === '$' && source[i + 1] === '{') {
|
|
91
|
-
const result = skipJsExpression(source, i + 2);
|
|
92
|
-
if (result == null) {
|
|
93
|
-
return source.length;
|
|
94
|
-
}
|
|
95
|
-
i = result;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
i++;
|
|
99
|
-
}
|
|
100
|
-
return source.length;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const skipLineComment = (source: string, index: number): number => {
|
|
104
|
-
let i = index;
|
|
105
|
-
while (i < source.length && source[i] !== '\n') {
|
|
106
|
-
i++;
|
|
64
|
+
if (source[i] === '\\') i += 2;
|
|
65
|
+
else if (source[i] === '`') return i + 1;
|
|
66
|
+
else if (source[i] === '$' && source[i + 1] === '{') {
|
|
67
|
+
const end = skipJsExpression(source, i + 2);
|
|
68
|
+
if (end === null) return source.length;
|
|
69
|
+
i = end;
|
|
70
|
+
} else i++;
|
|
107
71
|
}
|
|
108
72
|
return i;
|
|
109
73
|
};
|
|
110
74
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
75
|
+
interface JsContext {
|
|
76
|
+
controlParentheses: boolean[];
|
|
77
|
+
controlKeyword: boolean;
|
|
78
|
+
blocks: boolean[];
|
|
79
|
+
previous: string;
|
|
80
|
+
statementStart: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const createContext = (statementStart: boolean): JsContext => ({
|
|
84
|
+
controlParentheses: [],
|
|
85
|
+
controlKeyword: false,
|
|
86
|
+
blocks: [],
|
|
87
|
+
previous: '',
|
|
88
|
+
statementStart,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
interface JsToken {
|
|
92
|
+
end: number;
|
|
93
|
+
expressionAllowed: boolean;
|
|
94
|
+
}
|
|
115
95
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
/** Advance one JS token without interpreting strings, comments, or regexes as markup. */
|
|
97
|
+
const nextJsToken = (
|
|
98
|
+
source: string,
|
|
99
|
+
index: number,
|
|
100
|
+
expressionAllowed: boolean,
|
|
101
|
+
context: JsContext
|
|
102
|
+
): JsToken => {
|
|
103
|
+
const char = source[index];
|
|
104
|
+
if (/\s/.test(char)) return { end: index + 1, expressionAllowed };
|
|
105
|
+
if (char === '/' && source[index + 1] === '/') {
|
|
106
|
+
return { end: skipLineComment(source, index + 2), expressionAllowed };
|
|
107
|
+
}
|
|
108
|
+
if (char === '/' && source[index + 1] === '*') {
|
|
109
|
+
return { end: skipBlockComment(source, index), expressionAllowed };
|
|
110
|
+
}
|
|
111
|
+
if (char === '"' || char === "'") {
|
|
112
|
+
context.previous = 'literal';
|
|
113
|
+
context.statementStart = false;
|
|
114
|
+
return { end: skipQuoted(source, index, char), expressionAllowed: false };
|
|
115
|
+
}
|
|
116
|
+
if (char === '`')
|
|
117
|
+
return {
|
|
118
|
+
end: skipTemplateLiteral(source, index),
|
|
119
|
+
expressionAllowed: false,
|
|
120
|
+
};
|
|
121
|
+
if (char === '/' && expressionAllowed)
|
|
122
|
+
return { end: skipRegex(source, index), expressionAllowed: false };
|
|
123
|
+
if (identifierStart(char)) {
|
|
124
|
+
let end = index + 1;
|
|
125
|
+
while (end < source.length && identifierPart(source[end])) end++;
|
|
126
|
+
const word = source.slice(index, end);
|
|
127
|
+
context.controlKeyword = /^(if|while|for|with|switch|catch)$/.test(word);
|
|
128
|
+
context.previous = word;
|
|
129
|
+
context.statementStart = /^(else|do|try|finally)$/.test(word);
|
|
130
|
+
return { end, expressionAllowed: expressionKeywords.has(word) };
|
|
131
|
+
}
|
|
132
|
+
if (/[0-9]/.test(char)) {
|
|
133
|
+
let end = index + 1;
|
|
134
|
+
while (end < source.length && /[\w.]/.test(source[end])) end++;
|
|
135
|
+
return { end, expressionAllowed: false };
|
|
136
|
+
}
|
|
137
|
+
if (char === '(') {
|
|
138
|
+
context.controlParentheses.push(context.controlKeyword);
|
|
139
|
+
context.controlKeyword = false;
|
|
140
|
+
context.previous = char;
|
|
141
|
+
context.statementStart = false;
|
|
142
|
+
return { end: index + 1, expressionAllowed: true };
|
|
143
|
+
}
|
|
144
|
+
if (char === ')') {
|
|
145
|
+
const control = context.controlParentheses.pop() ?? false;
|
|
146
|
+
context.previous = char;
|
|
147
|
+
context.statementStart = control;
|
|
148
|
+
return { end: index + 1, expressionAllowed: control };
|
|
149
|
+
}
|
|
150
|
+
if (char === '{') {
|
|
151
|
+
const block =
|
|
152
|
+
context.statementStart ||
|
|
153
|
+
context.previous === ')' ||
|
|
154
|
+
context.previous === '>';
|
|
155
|
+
context.blocks.push(block);
|
|
156
|
+
context.previous = char;
|
|
157
|
+
context.statementStart = block;
|
|
158
|
+
return { end: index + 1, expressionAllowed: true };
|
|
159
|
+
}
|
|
160
|
+
if (char === '}') {
|
|
161
|
+
const block = context.blocks.pop() ?? false;
|
|
162
|
+
context.previous = char;
|
|
163
|
+
context.statementStart = block;
|
|
164
|
+
return { end: index + 1, expressionAllowed: block };
|
|
165
|
+
}
|
|
166
|
+
context.previous = char;
|
|
167
|
+
context.statementStart = char === ';';
|
|
168
|
+
if ((char === '+' || char === '-') && source[index + 1] === char) {
|
|
169
|
+
return { end: index + 2, expressionAllowed };
|
|
170
|
+
}
|
|
171
|
+
return { end: index + 1, expressionAllowed: !/[)\]}.]/.test(char) };
|
|
119
172
|
};
|
|
120
173
|
|
|
121
|
-
|
|
174
|
+
/** Return the offset after a matching }, starting immediately after the opening {. */
|
|
175
|
+
export const skipJsExpression = (
|
|
176
|
+
source: string,
|
|
177
|
+
index: number
|
|
178
|
+
): number | null => {
|
|
122
179
|
let depth = 1;
|
|
123
180
|
let i = index;
|
|
181
|
+
let expressionAllowed = true;
|
|
182
|
+
const context = createContext(false);
|
|
124
183
|
while (i < source.length) {
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (ch === '`') {
|
|
135
|
-
i = skipTemplateLiteral(source, i);
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (ch === '/' && source[i + 1] === '*') {
|
|
139
|
-
i = skipBlockComment(source, i + 2);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (ch === '/' && source[i + 1] === '/') {
|
|
143
|
-
i = skipLineComment(source, i + 2);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (ch === '{') {
|
|
147
|
-
depth++;
|
|
148
|
-
i++;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (ch === '}') {
|
|
152
|
-
depth--;
|
|
153
|
-
i++;
|
|
154
|
-
if (depth === 0) {
|
|
155
|
-
return i;
|
|
184
|
+
const char = source[i];
|
|
185
|
+
if (char === '<' && expressionAllowed) {
|
|
186
|
+
const match = extractTemplate(source, i);
|
|
187
|
+
if (match) {
|
|
188
|
+
i = match.end;
|
|
189
|
+
expressionAllowed = false;
|
|
190
|
+
context.previous = 'template';
|
|
191
|
+
context.statementStart = false;
|
|
192
|
+
continue;
|
|
156
193
|
}
|
|
157
|
-
continue;
|
|
158
194
|
}
|
|
159
|
-
|
|
195
|
+
if (char === '{') depth++;
|
|
196
|
+
else if (char === '}' && --depth === 0) return i + 1;
|
|
197
|
+
const token = nextJsToken(source, i, expressionAllowed, context);
|
|
198
|
+
i = token.end;
|
|
199
|
+
expressionAllowed = token.expressionAllowed;
|
|
160
200
|
}
|
|
161
201
|
return null;
|
|
162
202
|
};
|
|
163
203
|
|
|
164
|
-
|
|
165
|
-
position: number;
|
|
166
|
-
selfClosing: boolean;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const skipTag = (
|
|
170
|
-
source: string,
|
|
171
|
-
index: number,
|
|
172
|
-
closing: boolean
|
|
173
|
-
): SkipTagResult | null => {
|
|
204
|
+
const skipTag = (source: string, index: number): number | null => {
|
|
174
205
|
let i = index;
|
|
175
206
|
while (i < source.length) {
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
i = result;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (ch === '/' && source[i + 1] === '*') {
|
|
190
|
-
i = skipBlockComment(source, i + 2);
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (ch === '/' && source[i + 1] === '/') {
|
|
194
|
-
i = skipLineComment(source, i + 2);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (ch === '>') {
|
|
198
|
-
const selfClosing = !closing && source[i - 1] === '/';
|
|
199
|
-
return { position: i + 1, selfClosing };
|
|
200
|
-
}
|
|
201
|
-
i++;
|
|
207
|
+
const char = source[i];
|
|
208
|
+
if (char === '"' || char === "'") i = skipQuoted(source, i, char);
|
|
209
|
+
else if (char === '{') {
|
|
210
|
+
const end = skipJsExpression(source, i + 1);
|
|
211
|
+
if (end === null) return null;
|
|
212
|
+
i = end;
|
|
213
|
+
} else if (char === '>') return i + 1;
|
|
214
|
+
else i++;
|
|
202
215
|
}
|
|
203
216
|
return null;
|
|
204
217
|
};
|
|
@@ -207,112 +220,62 @@ const extractTemplate = (
|
|
|
207
220
|
source: string,
|
|
208
221
|
start: number
|
|
209
222
|
): TemplateMatch | null => {
|
|
210
|
-
|
|
223
|
+
if (source[start] !== '<' || !/[A-Za-z_$>!]/.test(source[start + 1] ?? ''))
|
|
224
|
+
return null;
|
|
211
225
|
let pos = start;
|
|
212
226
|
let depth = 0;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
pos = skipBlockComment(source, pos + 2);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
if (ch === '/' && source[pos + 1] === '/') {
|
|
222
|
-
pos = skipLineComment(source, pos + 2);
|
|
227
|
+
while (pos < source.length) {
|
|
228
|
+
if (source.startsWith('<!--', pos)) {
|
|
229
|
+
const end = source.indexOf('-->', pos + 4);
|
|
230
|
+
if (end === -1) return null;
|
|
231
|
+
pos = end + 3;
|
|
223
232
|
continue;
|
|
224
233
|
}
|
|
225
|
-
if (
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
pos =
|
|
234
|
+
if (source[pos] === '{') {
|
|
235
|
+
const end = skipJsExpression(source, pos + 1);
|
|
236
|
+
if (end !== null) {
|
|
237
|
+
pos = end;
|
|
229
238
|
continue;
|
|
230
239
|
}
|
|
240
|
+
// Still extract a malformed template when possible so compile can diagnose it.
|
|
231
241
|
}
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
pos = res.position;
|
|
245
|
-
if (depth === 0) {
|
|
246
|
-
return {
|
|
247
|
-
start,
|
|
248
|
-
end: pos,
|
|
249
|
-
snippet: source.slice(start, pos),
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
continue;
|
|
253
|
-
} else {
|
|
254
|
-
depth++;
|
|
255
|
-
const res = skipTag(source, pos + 1, false);
|
|
256
|
-
if (!res) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
pos = res.position;
|
|
260
|
-
if (res.selfClosing) {
|
|
261
|
-
depth--;
|
|
262
|
-
if (depth === 0) {
|
|
263
|
-
return {
|
|
264
|
-
start,
|
|
265
|
-
end: pos,
|
|
266
|
-
snippet: source.slice(start, pos),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
242
|
+
if (source[pos] === '<') {
|
|
243
|
+
const closing = source[pos + 1] === '/';
|
|
244
|
+
const end = skipTag(source, pos + (closing ? 2 : 1));
|
|
245
|
+
if (end === null) return null;
|
|
246
|
+
depth += closing ? -1 : 1;
|
|
247
|
+
if (!closing && source[end - 2] === '/') depth--;
|
|
248
|
+
pos = end;
|
|
249
|
+
if (depth === 0)
|
|
250
|
+
return { start, end: pos, snippet: source.slice(start, pos) };
|
|
251
|
+
continue;
|
|
272
252
|
}
|
|
273
|
-
|
|
253
|
+
// Template text is not JavaScript: quotes and // or /* have no special meaning here.
|
|
274
254
|
pos++;
|
|
275
255
|
}
|
|
276
|
-
|
|
277
256
|
return null;
|
|
278
257
|
};
|
|
279
258
|
|
|
280
259
|
export const scanTemplates = (code: string): TemplateMatch[] => {
|
|
281
260
|
const matches: TemplateMatch[] = [];
|
|
282
|
-
const len = code.length;
|
|
283
261
|
let index = 0;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (ch === "'" || ch === '"') {
|
|
289
|
-
index = skipQuoted(code, index, ch);
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
if (ch === '`') {
|
|
293
|
-
index = skipTemplateLiteral(code, index);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (ch === '/' && code[index + 1] === '*') {
|
|
297
|
-
index = skipBlockComment(code, index + 2);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
if (ch === '/' && code[index + 1] === '/') {
|
|
301
|
-
index = skipLineComment(code, index + 2);
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (ch === '<' && shouldAttemptTemplate(code, index)) {
|
|
262
|
+
let expressionAllowed = true;
|
|
263
|
+
const context = createContext(true);
|
|
264
|
+
while (index < code.length) {
|
|
265
|
+
if (code[index] === '<' && expressionAllowed) {
|
|
306
266
|
const match = extractTemplate(code, index);
|
|
307
267
|
if (match) {
|
|
308
268
|
matches.push(match);
|
|
309
269
|
index = match.end;
|
|
270
|
+
expressionAllowed = false;
|
|
271
|
+
context.previous = 'template';
|
|
272
|
+
context.statementStart = false;
|
|
310
273
|
continue;
|
|
311
274
|
}
|
|
312
275
|
}
|
|
313
|
-
|
|
314
|
-
index
|
|
276
|
+
const token = nextJsToken(code, index, expressionAllowed, context);
|
|
277
|
+
index = token.end;
|
|
278
|
+
expressionAllowed = token.expressionAllowed;
|
|
315
279
|
}
|
|
316
|
-
|
|
317
280
|
return matches;
|
|
318
281
|
};
|