@makano/rew 1.1.73 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. package/lib/coffeescript/browser.js +144 -139
  2. package/lib/coffeescript/cake.js +132 -133
  3. package/lib/coffeescript/coffeescript.js +437 -381
  4. package/lib/coffeescript/command.js +806 -724
  5. package/lib/coffeescript/grammar.js +1908 -2474
  6. package/lib/coffeescript/helpers.js +509 -473
  7. package/lib/coffeescript/index.js +228 -215
  8. package/lib/coffeescript/lexer.js +2282 -1909
  9. package/lib/coffeescript/nodes.js +9782 -9202
  10. package/lib/coffeescript/optparse.js +255 -227
  11. package/lib/coffeescript/parser.js +20305 -1265
  12. package/lib/coffeescript/register.js +107 -87
  13. package/lib/coffeescript/repl.js +307 -284
  14. package/lib/coffeescript/rewriter.js +1389 -1079
  15. package/lib/coffeescript/scope.js +176 -172
  16. package/lib/coffeescript/sourcemap.js +242 -227
  17. package/lib/rew/cli/cli.js +288 -186
  18. package/lib/rew/cli/log.js +31 -32
  19. package/lib/rew/cli/run.js +10 -12
  20. package/lib/rew/cli/utils.js +344 -176
  21. package/lib/rew/const/config_path.js +4 -0
  22. package/lib/rew/const/default.js +38 -35
  23. package/lib/rew/const/files.js +9 -9
  24. package/lib/rew/const/opt.js +8 -8
  25. package/lib/rew/css/theme.css +2 -2
  26. package/lib/rew/functions/core.js +55 -57
  27. package/lib/rew/functions/curl.js +23 -0
  28. package/lib/rew/functions/emitter.js +52 -55
  29. package/lib/rew/functions/exec.js +25 -21
  30. package/lib/rew/functions/export.js +18 -20
  31. package/lib/rew/functions/fs.js +55 -54
  32. package/lib/rew/functions/future.js +29 -21
  33. package/lib/rew/functions/id.js +8 -9
  34. package/lib/rew/functions/import.js +107 -93
  35. package/lib/rew/functions/map.js +13 -16
  36. package/lib/rew/functions/match.js +35 -26
  37. package/lib/rew/functions/path.js +8 -8
  38. package/lib/rew/functions/require.js +32 -33
  39. package/lib/rew/functions/sleep.js +2 -2
  40. package/lib/rew/functions/stdout.js +20 -20
  41. package/lib/rew/functions/types.js +96 -95
  42. package/lib/rew/html/ui.html +12 -13
  43. package/lib/rew/html/ui.js +205 -168
  44. package/lib/rew/main.js +14 -14
  45. package/lib/rew/misc/bin.js +37 -0
  46. package/lib/rew/misc/findAppInfo.js +16 -0
  47. package/lib/rew/misc/findAppPath.js +21 -0
  48. package/lib/rew/misc/req.js +7 -0
  49. package/lib/rew/misc/seededid.js +13 -0
  50. package/lib/rew/models/enum.js +12 -12
  51. package/lib/rew/models/struct.js +30 -32
  52. package/lib/rew/modules/compiler.js +237 -177
  53. package/lib/rew/modules/context.js +35 -22
  54. package/lib/rew/modules/fs.js +10 -10
  55. package/lib/rew/modules/runtime.js +17 -21
  56. package/lib/rew/modules/yaml.js +27 -30
  57. package/lib/rew/pkgs/conf.js +82 -75
  58. package/lib/rew/pkgs/data.js +12 -7
  59. package/lib/rew/pkgs/date.js +27 -28
  60. package/lib/rew/pkgs/env.js +6 -8
  61. package/lib/rew/pkgs/modules/data/bintree.js +52 -52
  62. package/lib/rew/pkgs/modules/data/doublylinked.js +85 -85
  63. package/lib/rew/pkgs/modules/data/linkedList.js +73 -73
  64. package/lib/rew/pkgs/modules/data/queue.js +19 -20
  65. package/lib/rew/pkgs/modules/data/stack.js +19 -19
  66. package/lib/rew/pkgs/modules/threads/worker.js +36 -26
  67. package/lib/rew/pkgs/modules/ui/classes.js +182 -178
  68. package/lib/rew/pkgs/pkgs.js +9 -10
  69. package/lib/rew/pkgs/rune.js +400 -0
  70. package/lib/rew/pkgs/threads.js +57 -53
  71. package/lib/rew/pkgs/ui.js +148 -136
  72. package/lib/rew/qrew/compile.js +12 -0
  73. package/package.json +11 -4
@@ -1,1943 +1,2316 @@
1
1
  // Generated by CoffeeScript 2.7.0
2
- (function() {
3
- // The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
4
- // matches against the beginning of the source code. When a match is found,
5
- // a token is produced, we consume the match, and start again. Tokens are in the
6
- // form:
7
-
8
- // [tag, value, locationData]
9
-
10
- // where locationData is {first_line, first_column, last_line, last_column, last_line_exclusive, last_column_exclusive}, which is a
11
- // format that can be fed directly into [Jison](https://github.com/zaach/jison). These
12
- // are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
13
- var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_COMMENT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_JSX, INVERSES, JSTOKEN, JSX_ATTRIBUTE, JSX_FRAGMENT_IDENTIFIER, JSX_IDENTIFIER, JSX_IDENTIFIER_PART, JSX_INTERPOLATION, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_SINGLE, STRING_START, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, VALID_FLAGS, WHITESPACE, addTokenData, attachCommentsToNode, compact, count, flatten, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, parseNumber, repeat, replaceUnicodeCodePointEscapes, starts, throwSyntaxError,
14
- indexOf = [].indexOf,
15
- slice = [].slice;
16
-
17
- ({Rewriter, INVERSES, UNFINISHED} = require('./rewriter'));
18
-
19
- // Import the helpers we need.
20
- ({count, starts, compact, repeat, invertLiterate, merge, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, flatten, parseNumber} = require('./helpers'));
21
-
22
- // The Lexer Class
23
- // ---------------
24
-
25
- // The Lexer class reads a stream of CoffeeScript and divvies it up into tagged
26
- // tokens. Some potential ambiguity in the grammar has been avoided by
27
- // pushing some extra smarts into the Lexer.
28
- exports.Lexer = Lexer = class Lexer {
29
- constructor() {
30
- // Throws an error at either a given offset from the current chunk or at the
31
- // location of a token (`token[2]`).
32
- this.error = this.error.bind(this);
33
- }
34
-
35
- // **tokenize** is the Lexer's main method. Scan by attempting to match tokens
36
- // one at a time, using a regular expression anchored at the start of the
37
- // remaining code, or a custom recursive token-matching method
38
- // (for interpolations). When the next token has been recorded, we move forward
39
- // within the code past the token, and begin again.
40
-
41
- // Each tokenizing method is responsible for returning the number of characters
42
- // it has consumed.
43
-
44
- // Before returning the token stream, run it through the [Rewriter](rewriter.html).
45
- tokenize(code, opts = {}) {
46
- var consumed, end, i, ref;
47
- this.literate = opts.literate; // Are we lexing literate CoffeeScript?
48
- this.indent = 0; // The current indentation level.
49
- this.baseIndent = 0; // The overall minimum indentation level.
50
- this.continuationLineAdditionalIndent = 0; // The over-indentation at the current level.
51
- this.outdebt = 0; // The under-outdentation at the current level.
52
- this.indents = []; // The stack of all current indentation levels.
53
- this.indentLiteral = ''; // The indentation.
54
- this.ends = []; // The stack for pairing up tokens.
55
- this.tokens = []; // Stream of parsed tokens in the form `['TYPE', value, location data]`.
56
- this.seenFor = false; // Used to recognize `FORIN`, `FOROF` and `FORFROM` tokens.
57
- this.seenImport = false; // Used to recognize `IMPORT FROM? AS?` tokens.
58
- this.seenExport = false; // Used to recognize `EXPORT FROM? AS?` tokens.
59
- this.importSpecifierList = false; // Used to identify when in an `IMPORT {...} FROM? ...`.
60
- this.exportSpecifierList = false; // Used to identify when in an `EXPORT {...} FROM? ...`.
61
- this.jsxDepth = 0; // Used to optimize JSX checks, how deep in JSX we are.
62
- this.jsxObjAttribute = {}; // Used to detect if JSX attributes is wrapped in {} (<div {props...} />).
63
- this.chunkLine = opts.line || 0; // The start line for the current @chunk.
64
- this.chunkColumn = opts.column || 0; // The start column of the current @chunk.
65
- this.chunkOffset = opts.offset || 0; // The start offset for the current @chunk.
66
- this.locationDataCompensations = opts.locationDataCompensations || {};
67
- code = this.clean(code); // The stripped, cleaned original source code.
68
-
69
- // At every position, run through this list of attempted matches,
70
- // short-circuiting if any of them succeed. Their order determines precedence:
71
- // `@literalToken` is the fallback catch-all.
72
- i = 0;
73
- while (this.chunk = code.slice(i)) {
74
- consumed = this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.stringToken() || this.numberToken() || this.jsxToken() || this.regexToken() || this.jsToken() || this.literalToken();
75
- // Update position.
76
- [this.chunkLine, this.chunkColumn, this.chunkOffset] = this.getLineAndColumnFromChunk(consumed);
77
- i += consumed;
78
- if (opts.untilBalanced && this.ends.length === 0) {
79
- return {
80
- tokens: this.tokens,
81
- index: i
82
- };
83
- }
84
- }
85
- this.closeIndentation();
86
- if (end = this.ends.pop()) {
87
- this.error(`missing ${end.tag}`, ((ref = end.origin) != null ? ref : end)[2]);
88
- }
89
- if (opts.rewrite === false) {
90
- return this.tokens;
91
- }
92
- return (new Rewriter()).rewrite(this.tokens);
93
- }
94
-
95
- // Preprocess the code to remove leading and trailing whitespace, carriage
96
- // returns, etc. If we’re lexing literate CoffeeScript, strip external Markdown
97
- // by removing all lines that aren’t indented by at least four spaces or a tab.
98
- clean(code) {
99
- var base, thusFar;
100
- thusFar = 0;
101
- if (code.charCodeAt(0) === BOM) {
102
- code = code.slice(1);
103
- this.locationDataCompensations[0] = 1;
104
- thusFar += 1;
105
- }
106
- if (WHITESPACE.test(code)) {
107
- code = `\n${code}`;
108
- this.chunkLine--;
109
- if ((base = this.locationDataCompensations)[0] == null) {
110
- base[0] = 0;
111
- }
112
- this.locationDataCompensations[0] -= 1;
113
- }
114
- code = code.replace(/\r/g, (match, offset) => {
115
- this.locationDataCompensations[thusFar + offset] = 1;
116
- return '';
117
- }).replace(TRAILING_SPACES, '');
118
- if (this.literate) {
119
- code = invertLiterate(code);
120
- }
121
- return code;
122
- }
123
-
124
- // Tokenizers
125
- // ----------
126
-
127
- // Matches identifying literals: variables, keywords, method names, etc.
128
- // Check to ensure that JavaScript reserved words aren’t being used as
129
- // identifiers. Because CoffeeScript reserves a handful of keywords that are
130
- // allowed in JavaScript, we’re careful not to tag them as keywords when
131
- // referenced as property names here, so you can still do `jQuery.is()` even
132
- // though `is` means `===` otherwise.
133
- identifierToken() {
134
- var alias, colon, colonOffset, colonToken, id, idLength, inJSXTag, input, match, poppedToken, prev, prevprev, ref, ref1, ref10, ref11, ref12, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, regExSuper, regex, sup, tag, tagToken, tokenData;
135
- inJSXTag = this.atJSXTag();
136
- regex = inJSXTag ? JSX_ATTRIBUTE : IDENTIFIER;
137
- if (!(match = regex.exec(this.chunk))) {
138
- return 0;
139
- }
140
- [input, id, colon] = match;
141
- // Preserve length of id for location data
142
- idLength = id.length;
143
- poppedToken = void 0;
144
- if (id === 'own' && this.tag() === 'FOR') {
145
- this.token('OWN', id);
146
- return id.length;
147
- }
148
- if (id === 'from' && this.tag() === 'YIELD') {
149
- this.token('FROM', id);
150
- return id.length;
151
- }
152
- if (id === 'as' && this.seenImport) {
153
- if (this.value() === '*') {
154
- this.tokens[this.tokens.length - 1][0] = 'IMPORT_ALL';
155
- } else if (ref = this.value(true), indexOf.call(COFFEE_KEYWORDS, ref) >= 0) {
156
- prev = this.prev();
157
- [prev[0], prev[1]] = ['IDENTIFIER', this.value(true)];
158
- }
159
- if ((ref1 = this.tag()) === 'DEFAULT' || ref1 === 'IMPORT_ALL' || ref1 === 'IDENTIFIER') {
160
- this.token('AS', id);
161
- return id.length;
162
- }
163
- }
164
- if (id === 'as' && this.seenExport) {
165
- if ((ref2 = this.tag()) === 'IDENTIFIER' || ref2 === 'DEFAULT') {
166
- this.token('AS', id);
167
- return id.length;
168
- }
169
- if (ref3 = this.value(true), indexOf.call(COFFEE_KEYWORDS, ref3) >= 0) {
170
- prev = this.prev();
171
- [prev[0], prev[1]] = ['IDENTIFIER', this.value(true)];
172
- this.token('AS', id);
173
- return id.length;
174
- }
175
- }
176
- if (id === 'default' && this.seenExport && ((ref4 = this.tag()) === 'EXPORT' || ref4 === 'AS')) {
177
- this.token('DEFAULT', id);
178
- return id.length;
179
- }
180
- if (id === 'assert' && (this.seenImport || this.seenExport) && this.tag() === 'STRING') {
181
- this.token('ASSERT', id);
182
- return id.length;
183
- }
184
- if (id === 'do' && (regExSuper = /^(\s*super)(?!\(\))/.exec(this.chunk.slice(3)))) {
185
- this.token('SUPER', 'super');
186
- this.token('CALL_START', '(');
187
- this.token('CALL_END', ')');
188
- [input, sup] = regExSuper;
189
- return sup.length + 3;
190
- }
191
- prev = this.prev();
192
- tag = colon || (prev != null) && (((ref5 = prev[0]) === '.' || ref5 === '?.' || ref5 === '::' || ref5 === '?::') || !prev.spaced && prev[0] === '@') ? 'PROPERTY' : 'IDENTIFIER';
193
- tokenData = {};
194
- if (tag === 'IDENTIFIER' && (indexOf.call(JS_KEYWORDS, id) >= 0 || indexOf.call(COFFEE_KEYWORDS, id) >= 0) && !(this.exportSpecifierList && indexOf.call(COFFEE_KEYWORDS, id) >= 0)) {
195
- tag = id.toUpperCase();
196
- if (tag === 'WHEN' && (ref6 = this.tag(), indexOf.call(LINE_BREAK, ref6) >= 0)) {
197
- tag = 'LEADING_WHEN';
198
- } else if (tag === 'FOR') {
199
- this.seenFor = {
200
- endsLength: this.ends.length
201
- };
202
- } else if (tag === 'UNLESS') {
203
- tag = 'IF';
204
- } else if (tag === 'IMPORT') {
205
- this.seenImport = true;
206
- } else if (tag === 'EXPORT') {
207
- this.seenExport = true;
208
- } else if (indexOf.call(UNARY, tag) >= 0) {
209
- tag = 'UNARY';
210
- } else if (indexOf.call(RELATION, tag) >= 0) {
211
- if (tag !== 'INSTANCEOF' && this.seenFor) {
212
- tag = 'FOR' + tag;
213
- this.seenFor = false;
214
- } else {
215
- tag = 'RELATION';
216
- if (this.value() === '!') {
217
- poppedToken = this.tokens.pop();
218
- tokenData.invert = (ref7 = (ref8 = poppedToken.data) != null ? ref8.original : void 0) != null ? ref7 : poppedToken[1];
219
- }
220
- }
221
- }
222
- } else if (tag === 'IDENTIFIER' && this.seenFor && id === 'from' && isForFrom(prev)) {
223
- tag = 'FORFROM';
224
- this.seenFor = false;
225
- // Throw an error on attempts to use `get` or `set` as keywords, or
226
- // what CoffeeScript would normally interpret as calls to functions named
227
- // `get` or `set`, i.e. `get({foo: function () {}})`.
228
- } else if (tag === 'PROPERTY' && prev) {
229
- if (prev.spaced && (ref9 = prev[0], indexOf.call(CALLABLE, ref9) >= 0) && /^[gs]et$/.test(prev[1]) && this.tokens.length > 1 && ((ref10 = this.tokens[this.tokens.length - 2][0]) !== '.' && ref10 !== '?.' && ref10 !== '@')) {
230
- this.error(`'${prev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prev[2]);
231
- } else if (prev[0] === '.' && this.tokens.length > 1 && (prevprev = this.tokens[this.tokens.length - 2])[0] === 'UNARY' && prevprev[1] === 'new') {
232
- prevprev[0] = 'NEW_TARGET';
233
- } else if (prev[0] === '.' && this.tokens.length > 1 && (prevprev = this.tokens[this.tokens.length - 2])[0] === 'IMPORT' && prevprev[1] === 'import') {
234
- this.seenImport = false;
235
- prevprev[0] = 'IMPORT_META';
236
- } else if (this.tokens.length > 2) {
237
- prevprev = this.tokens[this.tokens.length - 2];
238
- if (((ref11 = prev[0]) === '@' || ref11 === 'THIS') && prevprev && prevprev.spaced && /^[gs]et$/.test(prevprev[1]) && ((ref12 = this.tokens[this.tokens.length - 3][0]) !== '.' && ref12 !== '?.' && ref12 !== '@')) {
239
- this.error(`'${prevprev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prevprev[2]);
240
- }
241
- }
242
- }
243
- if (tag === 'IDENTIFIER' && indexOf.call(RESERVED, id) >= 0 && !inJSXTag) {
244
- this.error(`reserved word '${id}'`, {
245
- length: id.length
246
- });
247
- }
248
- if (!(tag === 'PROPERTY' || this.exportSpecifierList || this.importSpecifierList)) {
249
- if (indexOf.call(COFFEE_ALIASES, id) >= 0) {
250
- alias = id;
251
- id = COFFEE_ALIAS_MAP[id];
252
- tokenData.original = alias;
253
- }
254
- tag = (function() {
255
- switch (id) {
256
- case '!':
257
- return 'UNARY';
258
- case '==':
259
- case '!=':
260
- return 'COMPARE';
261
- case 'true':
262
- case 'false':
263
- return 'BOOL';
264
- case 'break':
265
- case 'continue':
266
- case 'debugger':
267
- return 'STATEMENT';
268
- case '&&':
269
- case '||':
270
- return id;
271
- default:
272
- return tag;
273
- }
274
- })();
275
- }
276
- tagToken = this.token(tag, id, {
277
- length: idLength,
278
- data: tokenData
279
- });
280
- if (alias) {
281
- tagToken.origin = [tag, alias, tagToken[2]];
282
- }
283
- if (poppedToken) {
284
- [tagToken[2].first_line, tagToken[2].first_column, tagToken[2].range[0]] = [poppedToken[2].first_line, poppedToken[2].first_column, poppedToken[2].range[0]];
285
- }
286
- if (colon) {
287
- colonOffset = input.lastIndexOf(inJSXTag ? '=' : ':');
288
- colonToken = this.token(':', ':', {
289
- offset: colonOffset
290
- });
291
- if (inJSXTag) { // used by rewriter
292
- colonToken.jsxColon = true;
293
- }
294
- }
295
- if (inJSXTag && tag === 'IDENTIFIER' && prev[0] !== ':') {
296
- this.token(',', ',', {
297
- length: 0,
298
- origin: tagToken,
299
- generated: true
300
- });
301
- }
302
- return input.length;
303
- }
304
-
305
- // Matches numbers, including decimals, hex, and exponential notation.
306
- // Be careful not to interfere with ranges in progress.
307
- numberToken() {
308
- var lexedLength, match, number, parsedValue, tag, tokenData;
309
- if (!(match = NUMBER.exec(this.chunk))) {
310
- return 0;
311
- }
312
- number = match[0];
313
- lexedLength = number.length;
314
- switch (false) {
315
- case !/^0[BOX]/.test(number):
316
- this.error(`radix prefix in '${number}' must be lowercase`, {
317
- offset: 1
318
- });
319
- break;
320
- case !/^0\d*[89]/.test(number):
321
- this.error(`decimal literal '${number}' must not be prefixed with '0'`, {
322
- length: lexedLength
323
- });
324
- break;
325
- case !/^0\d+/.test(number):
326
- this.error(`octal literal '${number}' must be prefixed with '0o'`, {
327
- length: lexedLength
328
- });
329
- }
330
- parsedValue = parseNumber(number);
331
- tokenData = {parsedValue};
332
- tag = parsedValue === 2e308 ? 'INFINITY' : 'NUMBER';
333
- if (tag === 'INFINITY') {
334
- tokenData.original = number;
335
- }
336
- this.token(tag, number, {
337
- length: lexedLength,
338
- data: tokenData
339
- });
340
- return lexedLength;
341
- }
342
-
343
- // Matches strings, including multiline strings, as well as heredocs, with or without
344
- // interpolation.
345
- stringToken() {
346
- var attempt, delimiter, doc, end, heredoc, i, indent, match, prev, quote, ref, regex, token, tokens;
347
- [quote] = STRING_START.exec(this.chunk) || [];
348
- if (!quote) {
349
- return 0;
350
- }
351
- // If the preceding token is `from` and this is an import or export statement,
352
- // properly tag the `from`.
353
- prev = this.prev();
354
- if (prev && this.value() === 'from' && (this.seenImport || this.seenExport)) {
355
- prev[0] = 'FROM';
356
- }
357
- regex = (function() {
358
- switch (quote) {
359
- case "'":
360
- return STRING_SINGLE;
361
- case '"':
362
- return STRING_DOUBLE;
363
- case "'''":
364
- return HEREDOC_SINGLE;
365
- case '"""':
366
- return HEREDOC_DOUBLE;
367
- }
368
- })();
369
- ({
370
- tokens,
371
- index: end
372
- } = this.matchWithInterpolations(regex, quote));
373
- heredoc = quote.length === 3;
374
- if (heredoc) {
375
- // Find the smallest indentation. It will be removed from all lines later.
376
- indent = null;
377
- doc = ((function() {
378
- var j, len, results;
379
- results = [];
380
- for (i = j = 0, len = tokens.length; j < len; i = ++j) {
381
- token = tokens[i];
382
- if (token[0] === 'NEOSTRING') {
383
- results.push(token[1]);
384
- }
385
- }
386
- return results;
387
- })()).join('#{}');
388
- while (match = HEREDOC_INDENT.exec(doc)) {
389
- attempt = match[1];
390
- if (indent === null || (0 < (ref = attempt.length) && ref < indent.length)) {
391
- indent = attempt;
392
- }
393
- }
394
- }
395
- delimiter = quote.charAt(0);
396
- this.mergeInterpolationTokens(tokens, {
397
- quote,
398
- indent,
399
- endOffset: end
400
- }, (value) => {
401
- return this.validateUnicodeCodePointEscapes(value, {
402
- delimiter: quote
403
- });
404
- });
405
- if (this.atJSXTag()) {
406
- this.token(',', ',', {
407
- length: 0,
408
- origin: this.prev,
409
- generated: true
410
- });
411
- }
412
- return end;
413
- }
414
-
415
- // Matches and consumes comments. The comments are taken out of the token
416
- // stream and saved for later, to be reinserted into the output after
417
- // everything has been parsed and the JavaScript code generated.
418
- commentToken(chunk = this.chunk, {heregex, returnCommentTokens = false, offsetInChunk = 0} = {}) {
419
- var commentAttachment, commentAttachments, commentWithSurroundingWhitespace, content, contents, getIndentSize, hasSeenFirstCommentLine, hereComment, hereLeadingWhitespace, hereTrailingWhitespace, i, indentSize, leadingNewline, leadingNewlineOffset, leadingNewlines, leadingWhitespace, length, lineComment, match, matchIllegal, noIndent, nonInitial, placeholderToken, precededByBlankLine, precedingNonCommentLines, prev;
420
- if (!(match = chunk.match(COMMENT))) {
421
- return 0;
422
- }
423
- [commentWithSurroundingWhitespace, hereLeadingWhitespace, hereComment, hereTrailingWhitespace, lineComment] = match;
424
- contents = null;
425
- // Does this comment follow code on the same line?
426
- leadingNewline = /^\s*\n+\s*#/.test(commentWithSurroundingWhitespace);
427
- if (hereComment) {
428
- matchIllegal = HERECOMMENT_ILLEGAL.exec(hereComment);
429
- if (matchIllegal) {
430
- this.error(`block comments cannot contain ${matchIllegal[0]}`, {
431
- offset: '###'.length + matchIllegal.index,
432
- length: matchIllegal[0].length
433
- });
434
- }
435
- // Parse indentation or outdentation as if this block comment didn’t exist.
436
- chunk = chunk.replace(`###${hereComment}###`, '');
437
- // Remove leading newlines, like `Rewriter::removeLeadingNewlines`, to
438
- // avoid the creation of unwanted `TERMINATOR` tokens.
439
- chunk = chunk.replace(/^\n+/, '');
440
- this.lineToken({chunk});
441
- // Pull out the ###-style comment’s content, and format it.
442
- content = hereComment;
443
- contents = [
444
- {
445
- content,
446
- length: commentWithSurroundingWhitespace.length - hereLeadingWhitespace.length - hereTrailingWhitespace.length,
447
- leadingWhitespace: hereLeadingWhitespace
448
- }
449
- ];
450
- } else {
451
- // The `COMMENT` regex captures successive line comments as one token.
452
- // Remove any leading newlines before the first comment, but preserve
453
- // blank lines between line comments.
454
- leadingNewlines = '';
455
- content = lineComment.replace(/^(\n*)/, function(leading) {
456
- leadingNewlines = leading;
457
- return '';
458
- });
459
- precedingNonCommentLines = '';
460
- hasSeenFirstCommentLine = false;
461
- contents = content.split('\n').map(function(line, index) {
462
- var comment, leadingWhitespace;
463
- if (!(line.indexOf('#') > -1)) {
464
- precedingNonCommentLines += `\n${line}`;
465
- return;
466
- }
467
- leadingWhitespace = '';
468
- content = line.replace(/^([ |\t]*)#/, function(_, whitespace) {
469
- leadingWhitespace = whitespace;
470
- return '';
471
- });
472
- comment = {
473
- content,
474
- length: '#'.length + content.length,
475
- leadingWhitespace: `${!hasSeenFirstCommentLine ? leadingNewlines : ''}${precedingNonCommentLines}${leadingWhitespace}`,
476
- precededByBlankLine: !!precedingNonCommentLines
477
- };
478
- hasSeenFirstCommentLine = true;
479
- precedingNonCommentLines = '';
480
- return comment;
481
- }).filter(function(comment) {
482
- return comment;
483
- });
484
- }
485
- getIndentSize = function({leadingWhitespace, nonInitial}) {
486
- var lastNewlineIndex;
487
- lastNewlineIndex = leadingWhitespace.lastIndexOf('\n');
488
- if ((hereComment != null) || !nonInitial) {
489
- if (!(lastNewlineIndex > -1)) {
490
- return null;
491
- }
492
- } else {
493
- if (lastNewlineIndex == null) {
494
- lastNewlineIndex = -1;
495
- }
496
- }
497
- return leadingWhitespace.length - 1 - lastNewlineIndex;
498
- };
499
- commentAttachments = (function() {
500
- var j, len, results;
501
- results = [];
502
- for (i = j = 0, len = contents.length; j < len; i = ++j) {
503
- ({content, length, leadingWhitespace, precededByBlankLine} = contents[i]);
504
- nonInitial = i !== 0;
505
- leadingNewlineOffset = nonInitial ? 1 : 0;
506
- offsetInChunk += leadingNewlineOffset + leadingWhitespace.length;
507
- indentSize = getIndentSize({leadingWhitespace, nonInitial});
508
- noIndent = (indentSize == null) || indentSize === -1;
509
- commentAttachment = {
510
- content,
511
- here: hereComment != null,
512
- newLine: leadingNewline || nonInitial, // Line comments after the first one start new lines, by definition.
513
- locationData: this.makeLocationData({offsetInChunk, length}),
514
- precededByBlankLine,
515
- indentSize,
516
- indented: !noIndent && indentSize > this.indent,
517
- outdented: !noIndent && indentSize < this.indent
518
- };
519
- if (heregex) {
520
- commentAttachment.heregex = true;
521
- }
522
- offsetInChunk += length;
523
- results.push(commentAttachment);
524
- }
525
- return results;
526
- }).call(this);
527
- prev = this.prev();
528
- if (!prev) {
529
- // If there’s no previous token, create a placeholder token to attach
530
- // this comment to; and follow with a newline.
531
- commentAttachments[0].newLine = true;
532
- this.lineToken({
533
- chunk: this.chunk.slice(commentWithSurroundingWhitespace.length),
534
- offset: commentWithSurroundingWhitespace.length // Set the indent.
535
- });
536
- placeholderToken = this.makeToken('JS', '', {
537
- offset: commentWithSurroundingWhitespace.length,
538
- generated: true
539
- });
540
- placeholderToken.comments = commentAttachments;
541
- this.tokens.push(placeholderToken);
542
- this.newlineToken(commentWithSurroundingWhitespace.length);
543
- } else {
544
- attachCommentsToNode(commentAttachments, prev);
545
- }
546
- if (returnCommentTokens) {
547
- return commentAttachments;
548
- }
549
- return commentWithSurroundingWhitespace.length;
550
- }
551
-
552
- // Matches JavaScript interpolated directly into the source via backticks.
553
- jsToken() {
554
- var length, match, matchedHere, script;
555
- if (!(this.chunk.charAt(0) === '`' && (match = (matchedHere = HERE_JSTOKEN.exec(this.chunk)) || JSTOKEN.exec(this.chunk)))) {
556
- return 0;
557
- }
558
- // Convert escaped backticks to backticks, and escaped backslashes
559
- // just before escaped backticks to backslashes
560
- script = match[1];
561
- ({length} = match[0]);
562
- this.token('JS', script, {
563
- length,
564
- data: {
565
- here: !!matchedHere
566
- }
567
- });
568
- return length;
569
- }
570
-
571
- // Matches regular expression literals, as well as multiline extended ones.
572
- // Lexing regular expressions is difficult to distinguish from division, so we
573
- // borrow some basic heuristics from JavaScript and Ruby.
574
- regexToken() {
575
- var body, closed, comment, commentIndex, commentOpts, commentTokens, comments, delimiter, end, flags, fullMatch, index, leadingWhitespace, match, matchedComment, origin, prev, ref, ref1, regex, tokens;
576
- switch (false) {
577
- case !(match = REGEX_ILLEGAL.exec(this.chunk)):
578
- this.error(`regular expressions cannot begin with ${match[2]}`, {
579
- offset: match.index + match[1].length
580
- });
581
- break;
582
- case !(match = this.matchWithInterpolations(HEREGEX, '///')):
583
- ({tokens, index} = match);
584
- comments = [];
585
- while (matchedComment = HEREGEX_COMMENT.exec(this.chunk.slice(0, index))) {
586
- ({
587
- index: commentIndex
588
- } = matchedComment);
589
- [fullMatch, leadingWhitespace, comment] = matchedComment;
590
- comments.push({
591
- comment,
592
- offsetInChunk: commentIndex + leadingWhitespace.length
593
- });
594
- }
595
- commentTokens = flatten((function() {
596
- var j, len, results;
597
- results = [];
598
- for (j = 0, len = comments.length; j < len; j++) {
599
- commentOpts = comments[j];
600
- results.push(this.commentToken(commentOpts.comment, Object.assign(commentOpts, {
601
- heregex: true,
602
- returnCommentTokens: true
603
- })));
604
- }
605
- return results;
606
- }).call(this));
607
- break;
608
- case !(match = REGEX.exec(this.chunk)):
609
- [regex, body, closed] = match;
610
- this.validateEscapes(body, {
611
- isRegex: true,
612
- offsetInChunk: 1
613
- });
614
- index = regex.length;
615
- prev = this.prev();
616
- if (prev) {
617
- if (prev.spaced && (ref = prev[0], indexOf.call(CALLABLE, ref) >= 0)) {
618
- if (!closed || POSSIBLY_DIVISION.test(regex)) {
619
- return 0;
620
- }
621
- } else if (ref1 = prev[0], indexOf.call(NOT_REGEX, ref1) >= 0) {
622
- return 0;
623
- }
624
- }
625
- if (!closed) {
626
- this.error('missing / (unclosed regex)');
627
- }
628
- break;
629
- default:
630
- return 0;
631
- }
632
- [flags] = REGEX_FLAGS.exec(this.chunk.slice(index));
633
- end = index + flags.length;
634
- origin = this.makeToken('REGEX', null, {
635
- length: end
636
- });
637
- switch (false) {
638
- case !!VALID_FLAGS.test(flags):
639
- this.error(`invalid regular expression flags ${flags}`, {
640
- offset: index,
641
- length: flags.length
642
- });
643
- break;
644
- case !(regex || tokens.length === 1):
645
- delimiter = body ? '/' : '///';
646
- if (body == null) {
647
- body = tokens[0][1];
648
- }
649
- this.validateUnicodeCodePointEscapes(body, {delimiter});
650
- this.token('REGEX', `/${body}/${flags}`, {
651
- length: end,
652
- origin,
653
- data: {delimiter}
654
- });
655
- break;
656
- default:
657
- this.token('REGEX_START', '(', {
658
- length: 0,
659
- origin,
660
- generated: true
661
- });
662
- this.token('IDENTIFIER', 'RegExp', {
663
- length: 0,
664
- generated: true
665
- });
666
- this.token('CALL_START', '(', {
667
- length: 0,
668
- generated: true
669
- });
670
- this.mergeInterpolationTokens(tokens, {
671
- double: true,
672
- heregex: {flags},
673
- endOffset: end - flags.length,
674
- quote: '///'
675
- }, (str) => {
676
- return this.validateUnicodeCodePointEscapes(str, {delimiter});
677
- });
678
- if (flags) {
679
- this.token(',', ',', {
680
- offset: index - 1,
681
- length: 0,
682
- generated: true
683
- });
684
- this.token('STRING', '"' + flags + '"', {
685
- offset: index,
686
- length: flags.length
687
- });
688
- }
689
- this.token(')', ')', {
690
- offset: end,
691
- length: 0,
692
- generated: true
693
- });
694
- this.token('REGEX_END', ')', {
695
- offset: end,
696
- length: 0,
697
- generated: true
698
- });
699
- }
700
- // Explicitly attach any heregex comments to the REGEX/REGEX_END token.
701
- if (commentTokens != null ? commentTokens.length : void 0) {
702
- addTokenData(this.tokens[this.tokens.length - 1], {
703
- heregexCommentTokens: commentTokens
704
- });
705
- }
706
- return end;
707
- }
708
-
709
- // Matches newlines, indents, and outdents, and determines which is which.
710
- // If we can detect that the current line is continued onto the next line,
711
- // then the newline is suppressed:
712
-
713
- // elements
714
- // .each( ... )
715
- // .map( ... )
716
-
717
- // Keeps track of the level of indentation, because a single outdent token
718
- // can close multiple indents, so we need to know how far in we happen to be.
719
- lineToken({chunk = this.chunk, offset = 0} = {}) {
720
- var backslash, diff, endsContinuationLineIndentation, indent, match, minLiteralLength, newIndentLiteral, noNewlines, prev, ref, size;
721
- if (!(match = MULTI_DENT.exec(chunk))) {
722
- return 0;
723
- }
724
- indent = match[0];
725
- prev = this.prev();
726
- backslash = (prev != null ? prev[0] : void 0) === '\\';
727
- if (!((backslash || ((ref = this.seenFor) != null ? ref.endsLength : void 0) < this.ends.length) && this.seenFor)) {
728
- this.seenFor = false;
729
- }
730
- if (!((backslash && this.seenImport) || this.importSpecifierList)) {
731
- this.seenImport = false;
732
- }
733
- if (!((backslash && this.seenExport) || this.exportSpecifierList)) {
734
- this.seenExport = false;
735
- }
736
- size = indent.length - 1 - indent.lastIndexOf('\n');
737
- noNewlines = this.unfinished();
738
- newIndentLiteral = size > 0 ? indent.slice(-size) : '';
739
- if (!/^(.?)\1*$/.exec(newIndentLiteral)) {
740
- this.error('mixed indentation', {
741
- offset: indent.length
742
- });
743
- return indent.length;
744
- }
745
- minLiteralLength = Math.min(newIndentLiteral.length, this.indentLiteral.length);
746
- if (newIndentLiteral.slice(0, minLiteralLength) !== this.indentLiteral.slice(0, minLiteralLength)) {
747
- this.error('indentation mismatch', {
748
- offset: indent.length
749
- });
750
- return indent.length;
751
- }
752
- if (size - this.continuationLineAdditionalIndent === this.indent) {
753
- if (noNewlines) {
754
- this.suppressNewlines();
755
- } else {
756
- this.newlineToken(offset);
757
- }
758
- return indent.length;
759
- }
760
- if (size > this.indent) {
761
- if (noNewlines) {
762
- if (!backslash) {
763
- this.continuationLineAdditionalIndent = size - this.indent;
764
- }
765
- if (this.continuationLineAdditionalIndent) {
766
- prev.continuationLineIndent = this.indent + this.continuationLineAdditionalIndent;
767
- }
768
- this.suppressNewlines();
769
- return indent.length;
770
- }
771
- if (!this.tokens.length) {
772
- this.baseIndent = this.indent = size;
773
- this.indentLiteral = newIndentLiteral;
774
- return indent.length;
775
- }
776
- diff = size - this.indent + this.outdebt;
777
- this.token('INDENT', diff, {
778
- offset: offset + indent.length - size,
779
- length: size
780
- });
781
- this.indents.push(diff);
782
- this.ends.push({
783
- tag: 'OUTDENT'
784
- });
785
- this.outdebt = this.continuationLineAdditionalIndent = 0;
786
- this.indent = size;
787
- this.indentLiteral = newIndentLiteral;
788
- } else if (size < this.baseIndent) {
789
- this.error('missing indentation', {
790
- offset: offset + indent.length
791
- });
792
- } else {
793
- endsContinuationLineIndentation = this.continuationLineAdditionalIndent > 0;
794
- this.continuationLineAdditionalIndent = 0;
795
- this.outdentToken({
796
- moveOut: this.indent - size,
797
- noNewlines,
798
- outdentLength: indent.length,
799
- offset,
800
- indentSize: size,
801
- endsContinuationLineIndentation
802
- });
803
- }
804
- return indent.length;
805
- }
806
-
807
- // Record an outdent token or multiple tokens, if we happen to be moving back
808
- // inwards past several recorded indents. Sets new @indent value.
809
- outdentToken({moveOut, noNewlines, outdentLength = 0, offset = 0, indentSize, endsContinuationLineIndentation}) {
810
- var decreasedIndent, dent, lastIndent, ref, terminatorToken;
811
- decreasedIndent = this.indent - moveOut;
812
- while (moveOut > 0) {
813
- lastIndent = this.indents[this.indents.length - 1];
814
- if (!lastIndent) {
815
- this.outdebt = moveOut = 0;
816
- } else if (this.outdebt && moveOut <= this.outdebt) {
817
- this.outdebt -= moveOut;
818
- moveOut = 0;
819
- } else {
820
- dent = this.indents.pop() + this.outdebt;
821
- if (outdentLength && (ref = this.chunk[outdentLength], indexOf.call(INDENTABLE_CLOSERS, ref) >= 0)) {
822
- decreasedIndent -= dent - moveOut;
823
- moveOut = dent;
824
- }
825
- this.outdebt = 0;
826
- // pair might call outdentToken, so preserve decreasedIndent
827
- this.pair('OUTDENT');
828
- this.token('OUTDENT', moveOut, {
829
- length: outdentLength,
830
- indentSize: indentSize + moveOut - dent
831
- });
832
- moveOut -= dent;
833
- }
834
- }
835
- if (dent) {
836
- this.outdebt -= moveOut;
837
- }
838
- this.suppressSemicolons();
839
- if (!(this.tag() === 'TERMINATOR' || noNewlines)) {
840
- terminatorToken = this.token('TERMINATOR', '\n', {
841
- offset: offset + outdentLength,
842
- length: 0
843
- });
844
- if (endsContinuationLineIndentation) {
845
- terminatorToken.endsContinuationLineIndentation = {
846
- preContinuationLineIndent: this.indent
847
- };
848
- }
849
- }
850
- this.indent = decreasedIndent;
851
- this.indentLiteral = this.indentLiteral.slice(0, decreasedIndent);
852
- return this;
853
- }
854
-
855
- // Matches and consumes non-meaningful whitespace. Tag the previous token
856
- // as being “spaced”, because there are some cases where it makes a difference.
857
- whitespaceToken() {
858
- var match, nline, prev;
859
- if (!((match = WHITESPACE.exec(this.chunk)) || (nline = this.chunk.charAt(0) === '\n'))) {
860
- return 0;
861
- }
862
- prev = this.prev();
863
- if (prev) {
864
- prev[match ? 'spaced' : 'newLine'] = true;
865
- }
866
- if (match) {
867
- return match[0].length;
868
- } else {
869
- return 0;
870
- }
871
- }
872
-
873
- // Generate a newline token. Consecutive newlines get merged together.
874
- newlineToken(offset) {
875
- this.suppressSemicolons();
876
- if (this.tag() !== 'TERMINATOR') {
877
- this.token('TERMINATOR', '\n', {
878
- offset,
879
- length: 0
880
- });
881
- }
882
- return this;
883
- }
884
-
885
- // Use a `\` at a line-ending to suppress the newline.
886
- // The slash is removed here once its job is done.
887
- suppressNewlines() {
888
- var prev;
889
- prev = this.prev();
890
- if (prev[1] === '\\') {
891
- if (prev.comments && this.tokens.length > 1) {
892
- // `@tokens.length` should be at least 2 (some code, then `\`).
893
- // If something puts a `\` after nothing, they deserve to lose any
894
- // comments that trail it.
895
- attachCommentsToNode(prev.comments, this.tokens[this.tokens.length - 2]);
896
- }
897
- this.tokens.pop();
898
- }
899
- return this;
900
- }
901
-
902
- jsxToken() {
903
- var afterTag, end, endToken, firstChar, fullId, fullTagName, id, input, j, jsxTag, len, match, offset, openingTagToken, prev, prevChar, properties, property, ref, tagToken, token, tokens;
904
- firstChar = this.chunk[0];
905
- // Check the previous token to detect if attribute is spread.
906
- prevChar = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1][0] : '';
907
- if (firstChar === '<') {
908
- match = JSX_IDENTIFIER.exec(this.chunk.slice(1)) || JSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(1));
909
- // Not the right hand side of an unspaced comparison (i.e. `a<b`).
910
- if (!(match && (this.jsxDepth > 0 || !(prev = this.prev()) || prev.spaced || (ref = prev[0], indexOf.call(COMPARABLE_LEFT_SIDE, ref) < 0)))) {
911
- return 0;
912
- }
913
- [input, id] = match;
914
- fullId = id;
915
- if (indexOf.call(id, '.') >= 0) {
916
- [id, ...properties] = id.split('.');
917
- } else {
918
- properties = [];
919
- }
920
- tagToken = this.token('JSX_TAG', id, {
921
- length: id.length + 1,
922
- data: {
923
- openingBracketToken: this.makeToken('<', '<'),
924
- tagNameToken: this.makeToken('IDENTIFIER', id, {
925
- offset: 1
926
- })
927
- }
928
- });
929
- offset = id.length + 1;
930
- for (j = 0, len = properties.length; j < len; j++) {
931
- property = properties[j];
932
- this.token('.', '.', {offset});
933
- offset += 1;
934
- this.token('PROPERTY', property, {offset});
935
- offset += property.length;
936
- }
937
- this.token('CALL_START', '(', {
938
- generated: true
939
- });
940
- this.token('[', '[', {
941
- generated: true
942
- });
943
- this.ends.push({
944
- tag: '/>',
945
- origin: tagToken,
946
- name: id,
947
- properties
948
- });
949
- this.jsxDepth++;
950
- return fullId.length + 1;
951
- } else if (jsxTag = this.atJSXTag()) {
952
- if (this.chunk.slice(0, 2) === '/>') { // Self-closing tag.
953
- this.pair('/>');
954
- this.token(']', ']', {
955
- length: 2,
956
- generated: true
957
- });
958
- this.token('CALL_END', ')', {
959
- length: 2,
960
- generated: true,
961
- data: {
962
- selfClosingSlashToken: this.makeToken('/', '/'),
963
- closingBracketToken: this.makeToken('>', '>', {
964
- offset: 1
965
- })
966
- }
967
- });
968
- this.jsxDepth--;
969
- return 2;
970
- } else if (firstChar === '{') {
971
- if (prevChar === ':') {
972
- // This token represents the start of a JSX attribute value
973
- // that’s an expression (e.g. the `{b}` in `<div a={b} />`).
974
- // Our grammar represents the beginnings of expressions as `(`
975
- // tokens, so make this into a `(` token that displays as `{`.
976
- token = this.token('(', '{');
977
- this.jsxObjAttribute[this.jsxDepth] = false;
978
- // tag attribute name as JSX
979
- addTokenData(this.tokens[this.tokens.length - 3], {
980
- jsx: true
981
- });
982
- } else {
983
- token = this.token('{', '{');
984
- this.jsxObjAttribute[this.jsxDepth] = true;
985
- }
986
- this.ends.push({
987
- tag: '}',
988
- origin: token
989
- });
990
- return 1;
991
- } else if (firstChar === '>') { // end of opening tag
992
- ({
993
- // Ignore terminators inside a tag.
994
- origin: openingTagToken
995
- } = this.pair('/>')); // As if the current tag was self-closing.
996
- this.token(']', ']', {
997
- generated: true,
998
- data: {
999
- closingBracketToken: this.makeToken('>', '>')
1000
- }
1001
- });
1002
- this.token(',', 'JSX_COMMA', {
1003
- generated: true
1004
- });
1005
- ({
1006
- tokens,
1007
- index: end
1008
- } = this.matchWithInterpolations(INSIDE_JSX, '>', '</', JSX_INTERPOLATION));
1009
- this.mergeInterpolationTokens(tokens, {
1010
- endOffset: end,
1011
- jsx: true
1012
- }, (value) => {
1013
- return this.validateUnicodeCodePointEscapes(value, {
1014
- delimiter: '>'
1015
- });
1016
- });
1017
- match = JSX_IDENTIFIER.exec(this.chunk.slice(end)) || JSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(end));
1018
- if (!match || match[1] !== `${jsxTag.name}${((function() {
1019
- var k, len1, ref1, results;
1020
- ref1 = jsxTag.properties;
1021
- results = [];
1022
- for (k = 0, len1 = ref1.length; k < len1; k++) {
1023
- property = ref1[k];
1024
- results.push(`.${property}`);
1025
- }
1026
- return results;
1027
- })()).join('')}`) {
1028
- this.error(`expected corresponding JSX closing tag for ${jsxTag.name}`, jsxTag.origin.data.tagNameToken[2]);
1029
- }
1030
- [, fullTagName] = match;
1031
- afterTag = end + fullTagName.length;
1032
- if (this.chunk[afterTag] !== '>') {
1033
- this.error("missing closing > after tag name", {
1034
- offset: afterTag,
1035
- length: 1
1036
- });
1037
- }
1038
- // -2/+2 for the opening `</` and +1 for the closing `>`.
1039
- endToken = this.token('CALL_END', ')', {
1040
- offset: end - 2,
1041
- length: fullTagName.length + 3,
1042
- generated: true,
1043
- data: {
1044
- closingTagOpeningBracketToken: this.makeToken('<', '<', {
1045
- offset: end - 2
1046
- }),
1047
- closingTagSlashToken: this.makeToken('/', '/', {
1048
- offset: end - 1
1049
- }),
1050
- // TODO: individual tokens for complex tag name? eg < / A . B >
1051
- closingTagNameToken: this.makeToken('IDENTIFIER', fullTagName, {
1052
- offset: end
1053
- }),
1054
- closingTagClosingBracketToken: this.makeToken('>', '>', {
1055
- offset: end + fullTagName.length
1056
- })
1057
- }
1058
- });
1059
- // make the closing tag location data more easily accessible to the grammar
1060
- addTokenData(openingTagToken, endToken.data);
1061
- this.jsxDepth--;
1062
- return afterTag + 1;
1063
- } else {
1064
- return 0;
1065
- }
1066
- } else if (this.atJSXTag(1)) {
1067
- if (firstChar === '}') {
1068
- this.pair(firstChar);
1069
- if (this.jsxObjAttribute[this.jsxDepth]) {
1070
- this.token('}', '}');
1071
- this.jsxObjAttribute[this.jsxDepth] = false;
1072
- } else {
1073
- this.token(')', '}');
1074
- }
1075
- this.token(',', ',', {
1076
- generated: true
1077
- });
1078
- return 1;
1079
- } else {
1080
- return 0;
1081
- }
1082
- } else {
1083
- return 0;
1084
- }
1085
- }
1086
-
1087
- atJSXTag(depth = 0) {
1088
- var i, last, ref;
1089
- if (this.jsxDepth === 0) {
1090
- return false;
1091
- }
1092
- i = this.ends.length - 1;
1093
- while (((ref = this.ends[i]) != null ? ref.tag : void 0) === 'OUTDENT' || depth-- > 0) { // Ignore indents.
1094
- i--;
1095
- }
1096
- last = this.ends[i];
1097
- return (last != null ? last.tag : void 0) === '/>' && last;
1098
- }
1099
-
1100
- // We treat all other single characters as a token. E.g.: `( ) , . !`
1101
- // Multi-character operators are also literal tokens, so that Jison can assign
1102
- // the proper order of operations. There are some symbols that we tag specially
1103
- // here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
1104
- // parentheses that indicate a method call from regular parentheses, and so on.
1105
- literalToken() {
1106
- var match, message, origin, prev, ref, ref1, ref2, ref3, ref4, ref5, skipToken, tag, token, value;
1107
- if (match = OPERATOR.exec(this.chunk)) {
1108
- [value] = match;
1109
- if (CODE.test(value)) {
1110
- this.tagParameters();
1111
- }
1112
- } else {
1113
- value = this.chunk.charAt(0);
1114
- }
1115
- tag = value;
1116
- prev = this.prev();
1117
- if (prev && indexOf.call(['=', ...COMPOUND_ASSIGN], value) >= 0) {
1118
- skipToken = false;
1119
- if (value === '=' && ((ref = prev[1]) === '||' || ref === '&&') && !prev.spaced) {
1120
- prev[0] = 'COMPOUND_ASSIGN';
1121
- prev[1] += '=';
1122
- if ((ref1 = prev.data) != null ? ref1.original : void 0) {
1123
- prev.data.original += '=';
1124
- }
1125
- prev[2].range = [prev[2].range[0], prev[2].range[1] + 1];
1126
- prev[2].last_column += 1;
1127
- prev[2].last_column_exclusive += 1;
1128
- prev = this.tokens[this.tokens.length - 2];
1129
- skipToken = true;
1130
- }
1131
- if (prev && prev[0] !== 'PROPERTY') {
1132
- origin = (ref2 = prev.origin) != null ? ref2 : prev;
1133
- message = isUnassignable(prev[1], origin[1]);
1134
- if (message) {
1135
- this.error(message, origin[2]);
1136
- }
1137
- }
1138
- if (skipToken) {
1139
- return value.length;
1140
- }
1141
- }
1142
- if (value === '(' && (prev != null ? prev[0] : void 0) === 'IMPORT') {
1143
- prev[0] = 'DYNAMIC_IMPORT';
1144
- }
1145
- if (value === '{' && this.seenImport) {
1146
- this.importSpecifierList = true;
1147
- } else if (this.importSpecifierList && value === '}') {
1148
- this.importSpecifierList = false;
1149
- } else if (value === '{' && (prev != null ? prev[0] : void 0) === 'EXPORT') {
1150
- this.exportSpecifierList = true;
1151
- } else if (this.exportSpecifierList && value === '}') {
1152
- this.exportSpecifierList = false;
1153
- }
1154
- if (value === ';') {
1155
- if (ref3 = prev != null ? prev[0] : void 0, indexOf.call(['=', ...UNFINISHED], ref3) >= 0) {
1156
- this.error('unexpected ;');
1157
- }
1158
- this.seenFor = this.seenImport = this.seenExport = false;
1159
- tag = 'TERMINATOR';
1160
- } else if (value === '*' && (prev != null ? prev[0] : void 0) === 'EXPORT') {
1161
- tag = 'EXPORT_ALL';
1162
- } else if (indexOf.call(MATH, value) >= 0) {
1163
- tag = 'MATH';
1164
- } else if (indexOf.call(COMPARE, value) >= 0) {
1165
- tag = 'COMPARE';
1166
- } else if (indexOf.call(COMPOUND_ASSIGN, value) >= 0) {
1167
- tag = 'COMPOUND_ASSIGN';
1168
- } else if (indexOf.call(UNARY, value) >= 0) {
1169
- tag = 'UNARY';
1170
- } else if (indexOf.call(UNARY_MATH, value) >= 0) {
1171
- tag = 'UNARY_MATH';
1172
- } else if (indexOf.call(SHIFT, value) >= 0) {
1173
- tag = 'SHIFT';
1174
- } else if (value === '?' && (prev != null ? prev.spaced : void 0)) {
1175
- tag = 'BIN?';
1176
- } else if (prev) {
1177
- if (value === '(' && !prev.spaced && (ref4 = prev[0], indexOf.call(CALLABLE, ref4) >= 0)) {
1178
- if (prev[0] === '?') {
1179
- prev[0] = 'FUNC_EXIST';
1180
- }
1181
- tag = 'CALL_START';
1182
- } else if (value === '[' && (((ref5 = prev[0], indexOf.call(INDEXABLE, ref5) >= 0) && !prev.spaced) || (prev[0] === '::'))) { // `.prototype` can’t be a method you can call.
1183
- tag = 'INDEX_START';
1184
- switch (prev[0]) {
1185
- case '?':
1186
- prev[0] = 'INDEX_SOAK';
1187
- }
1188
- }
1189
- }
1190
- token = this.makeToken(tag, value);
1191
- switch (value) {
1192
- case '(':
1193
- case '{':
1194
- case '[':
1195
- this.ends.push({
1196
- tag: INVERSES[value],
1197
- origin: token
1198
- });
1199
- break;
1200
- case ')':
1201
- case '}':
1202
- case ']':
1203
- this.pair(value);
1204
- }
1205
- this.tokens.push(this.makeToken(tag, value));
1206
- return value.length;
1207
- }
1208
-
1209
- // Token Manipulators
1210
- // ------------------
1211
-
1212
- // A source of ambiguity in our grammar used to be parameter lists in function
1213
- // definitions versus argument lists in function calls. Walk backwards, tagging
1214
- // parameters specially in order to make things easier for the parser.
1215
- tagParameters() {
1216
- var i, paramEndToken, stack, tok, tokens;
1217
- if (this.tag() !== ')') {
1218
- return this.tagDoIife();
1219
- }
1220
- stack = [];
1221
- ({tokens} = this);
1222
- i = tokens.length;
1223
- paramEndToken = tokens[--i];
1224
- paramEndToken[0] = 'PARAM_END';
1225
- while (tok = tokens[--i]) {
1226
- switch (tok[0]) {
1227
- case ')':
1228
- stack.push(tok);
1229
- break;
1230
- case '(':
1231
- case 'CALL_START':
1232
- if (stack.length) {
1233
- stack.pop();
1234
- } else if (tok[0] === '(') {
1235
- tok[0] = 'PARAM_START';
1236
- return this.tagDoIife(i - 1);
1237
- } else {
1238
- paramEndToken[0] = 'CALL_END';
1239
- return this;
1240
- }
1241
- }
1242
- }
1243
- return this;
1244
- }
1245
-
1246
- // Tag `do` followed by a function differently than `do` followed by eg an
1247
- // identifier to allow for different grammar precedence
1248
- tagDoIife(tokenIndex) {
1249
- var tok;
1250
- tok = this.tokens[tokenIndex != null ? tokenIndex : this.tokens.length - 1];
1251
- if ((tok != null ? tok[0] : void 0) !== 'DO') {
1252
- return this;
1253
- }
1254
- tok[0] = 'DO_IIFE';
1255
- return this;
1256
- }
1257
-
1258
- // Close up all remaining open blocks at the end of the file.
1259
- closeIndentation() {
1260
- return this.outdentToken({
1261
- moveOut: this.indent,
1262
- indentSize: 0
1263
- });
1264
- }
1265
-
1266
- // Match the contents of a delimited token and expand variables and expressions
1267
- // inside it using Ruby-like notation for substitution of arbitrary
1268
- // expressions.
1269
-
1270
- // "Hello #{name.capitalize()}."
1271
-
1272
- // If it encounters an interpolation, this method will recursively create a new
1273
- // Lexer and tokenize until the `{` of `#{` is balanced with a `}`.
1274
-
1275
- // - `regex` matches the contents of a token (but not `delimiter`, and not
1276
- // `#{` if interpolations are desired).
1277
- // - `delimiter` is the delimiter of the token. Examples are `'`, `"`, `'''`,
1278
- // `"""` and `///`.
1279
- // - `closingDelimiter` is different from `delimiter` only in JSX
1280
- // - `interpolators` matches the start of an interpolation, for JSX it's both
1281
- // `{` and `<` (i.e. nested JSX tag)
1282
-
1283
- // This method allows us to have strings within interpolations within strings,
1284
- // ad infinitum.
1285
- matchWithInterpolations(regex, delimiter, closingDelimiter = delimiter, interpolators = /^#\{/) {
1286
- var braceInterpolator, close, column, index, interpolationOffset, interpolator, line, match, nested, offset, offsetInChunk, open, ref, ref1, rest, str, strPart, tokens;
1287
- tokens = [];
1288
- offsetInChunk = delimiter.length;
1289
- if (this.chunk.slice(0, offsetInChunk) !== delimiter) {
1290
- return null;
1291
- }
1292
- str = this.chunk.slice(offsetInChunk);
1293
- while (true) {
1294
- [strPart] = regex.exec(str);
1295
- this.validateEscapes(strPart, {
1296
- isRegex: delimiter.charAt(0) === '/',
1297
- offsetInChunk
1298
- });
1299
- // Push a fake `'NEOSTRING'` token, which will get turned into a real string later.
1300
- tokens.push(this.makeToken('NEOSTRING', strPart, {
1301
- offset: offsetInChunk
1302
- }));
1303
- str = str.slice(strPart.length);
1304
- offsetInChunk += strPart.length;
1305
- if (!(match = interpolators.exec(str))) {
1306
- break;
1307
- }
1308
- [interpolator] = match;
1309
- // To remove the `#` in `#{`.
1310
- interpolationOffset = interpolator.length - 1;
1311
- [line, column, offset] = this.getLineAndColumnFromChunk(offsetInChunk + interpolationOffset);
1312
- rest = str.slice(interpolationOffset);
1313
- ({
1314
- tokens: nested,
1315
- index
1316
- } = new Lexer().tokenize(rest, {
1317
- line,
1318
- column,
1319
- offset,
1320
- untilBalanced: true,
1321
- locationDataCompensations: this.locationDataCompensations
1322
- }));
1323
- // Account for the `#` in `#{`.
1324
- index += interpolationOffset;
1325
- braceInterpolator = str[index - 1] === '}';
1326
- if (braceInterpolator) {
1327
- // Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
1328
- // parentheses will be removed later.
1329
- [open] = nested, [close] = slice.call(nested, -1);
1330
- open[0] = 'INTERPOLATION_START';
1331
- open[1] = '(';
1332
- open[2].first_column -= interpolationOffset;
1333
- open[2].range = [open[2].range[0] - interpolationOffset, open[2].range[1]];
1334
- close[0] = 'INTERPOLATION_END';
1335
- close[1] = ')';
1336
- close.origin = ['', 'end of interpolation', close[2]];
1337
- }
1338
- if (((ref = nested[1]) != null ? ref[0] : void 0) === 'TERMINATOR') {
1339
- // Remove leading `'TERMINATOR'` (if any).
1340
- nested.splice(1, 1);
1341
- }
1342
- if (((ref1 = nested[nested.length - 3]) != null ? ref1[0] : void 0) === 'INDENT' && nested[nested.length - 2][0] === 'OUTDENT') {
1343
- // Remove trailing `'INDENT'/'OUTDENT'` pair (if any).
1344
- nested.splice(-3, 2);
1345
- }
1346
- if (!braceInterpolator) {
1347
- // We are not using `{` and `}`, so wrap the interpolated tokens instead.
1348
- open = this.makeToken('INTERPOLATION_START', '(', {
1349
- offset: offsetInChunk,
1350
- length: 0,
1351
- generated: true
1352
- });
1353
- close = this.makeToken('INTERPOLATION_END', ')', {
1354
- offset: offsetInChunk + index,
1355
- length: 0,
1356
- generated: true
1357
- });
1358
- nested = [open, ...nested, close];
1359
- }
1360
- // Push a fake `'TOKENS'` token, which will get turned into real tokens later.
1361
- tokens.push(['TOKENS', nested]);
1362
- str = str.slice(index);
1363
- offsetInChunk += index;
1364
- }
1365
- if (str.slice(0, closingDelimiter.length) !== closingDelimiter) {
1366
- this.error(`missing ${closingDelimiter}`, {
1367
- length: delimiter.length
1368
- });
1369
- }
1370
- return {
1371
- tokens,
1372
- index: offsetInChunk + closingDelimiter.length
1373
- };
1374
- }
1375
-
1376
- // Merge the array `tokens` of the fake token types `'TOKENS'` and `'NEOSTRING'`
1377
- // (as returned by `matchWithInterpolations`) into the token stream. The value
1378
- // of `'NEOSTRING'`s are converted using `fn` and turned into strings using
1379
- // `options` first.
1380
- mergeInterpolationTokens(tokens, options, fn) {
1381
- var $, converted, double, endOffset, firstIndex, heregex, i, indent, j, jsx, k, lastToken, len, len1, locationToken, lparen, placeholderToken, quote, ref, ref1, rparen, tag, token, tokensToPush, val, value;
1382
- ({quote, indent, double, heregex, endOffset, jsx} = options);
1383
- if (tokens.length > 1) {
1384
- lparen = this.token('STRING_START', '(', {
1385
- length: (ref = quote != null ? quote.length : void 0) != null ? ref : 0,
1386
- data: {quote},
1387
- generated: !(quote != null ? quote.length : void 0)
1388
- });
1389
- }
1390
- firstIndex = this.tokens.length;
1391
- $ = tokens.length - 1;
1392
- for (i = j = 0, len = tokens.length; j < len; i = ++j) {
1393
- token = tokens[i];
1394
- [tag, value] = token;
1395
- switch (tag) {
1396
- case 'TOKENS':
1397
- // There are comments (and nothing else) in this interpolation.
1398
- if (value.length === 2 && (value[0].comments || value[1].comments)) {
1399
- placeholderToken = this.makeToken('JS', '', {
1400
- generated: true
1401
- });
1402
- // Use the same location data as the first parenthesis.
1403
- placeholderToken[2] = value[0][2];
1404
- for (k = 0, len1 = value.length; k < len1; k++) {
1405
- val = value[k];
1406
- if (!val.comments) {
1407
- continue;
1408
- }
1409
- if (placeholderToken.comments == null) {
1410
- placeholderToken.comments = [];
1411
- }
1412
- placeholderToken.comments.push(...val.comments);
1413
- }
1414
- value.splice(1, 0, placeholderToken);
1415
- }
1416
- // Push all the tokens in the fake `'TOKENS'` token. These already have
1417
- // sane location data.
1418
- locationToken = value[0];
1419
- tokensToPush = value;
1420
- break;
1421
- case 'NEOSTRING':
1422
- // Convert `'NEOSTRING'` into `'STRING'`.
1423
- converted = fn.call(this, token[1], i);
1424
- if (i === 0) {
1425
- addTokenData(token, {
1426
- initialChunk: true
1427
- });
1428
- }
1429
- if (i === $) {
1430
- addTokenData(token, {
1431
- finalChunk: true
1432
- });
1433
- }
1434
- addTokenData(token, {indent, quote, double});
1435
- if (heregex) {
1436
- addTokenData(token, {heregex});
1437
- }
1438
- if (jsx) {
1439
- addTokenData(token, {jsx});
1440
- }
1441
- token[0] = 'STRING';
1442
- token[1] = '"' + converted + '"';
1443
- if (tokens.length === 1 && (quote != null)) {
1444
- token[2].first_column -= quote.length;
1445
- if (token[1].substr(-2, 1) === '\n') {
1446
- token[2].last_line += 1;
1447
- token[2].last_column = quote.length - 1;
1448
- } else {
1449
- token[2].last_column += quote.length;
1450
- if (token[1].length === 2) {
1451
- token[2].last_column -= 1;
1452
- }
1453
- }
1454
- token[2].last_column_exclusive += quote.length;
1455
- token[2].range = [token[2].range[0] - quote.length, token[2].range[1] + quote.length];
1456
- }
1457
- locationToken = token;
1458
- tokensToPush = [token];
1459
- }
1460
- this.tokens.push(...tokensToPush);
1461
- }
1462
- if (lparen) {
1463
- [lastToken] = slice.call(tokens, -1);
1464
- lparen.origin = [
1465
- 'STRING',
1466
- null,
1467
- {
1468
- first_line: lparen[2].first_line,
1469
- first_column: lparen[2].first_column,
1470
- last_line: lastToken[2].last_line,
1471
- last_column: lastToken[2].last_column,
1472
- last_line_exclusive: lastToken[2].last_line_exclusive,
1473
- last_column_exclusive: lastToken[2].last_column_exclusive,
1474
- range: [lparen[2].range[0],
1475
- lastToken[2].range[1]]
1476
- }
1477
- ];
1478
- if (!(quote != null ? quote.length : void 0)) {
1479
- lparen[2] = lparen.origin[2];
1480
- }
1481
- return rparen = this.token('STRING_END', ')', {
1482
- offset: endOffset - (quote != null ? quote : '').length,
1483
- length: (ref1 = quote != null ? quote.length : void 0) != null ? ref1 : 0,
1484
- generated: !(quote != null ? quote.length : void 0)
1485
- });
1486
- }
1487
- }
1488
-
1489
- // Pairs up a closing token, ensuring that all listed pairs of tokens are
1490
- // correctly balanced throughout the course of the token stream.
1491
- pair(tag) {
1492
- var lastIndent, prev, ref, ref1, wanted;
1493
- ref = this.ends, [prev] = slice.call(ref, -1);
1494
- if (tag !== (wanted = prev != null ? prev.tag : void 0)) {
1495
- if ('OUTDENT' !== wanted) {
1496
- this.error(`unmatched ${tag}`);
1497
- }
1498
- // Auto-close `INDENT` to support syntax like this:
1499
-
1500
- // el.click((event) ->
1501
- // el.hide())
1502
-
1503
- ref1 = this.indents, [lastIndent] = slice.call(ref1, -1);
1504
- this.outdentToken({
1505
- moveOut: lastIndent,
1506
- noNewlines: true
1507
- });
1508
- return this.pair(tag);
1509
- }
1510
- return this.ends.pop();
1511
- }
1512
-
1513
- // Helpers
1514
- // -------
1515
-
1516
- // Compensate for the things we strip out initially (e.g. carriage returns)
1517
- // so that location data stays accurate with respect to the original source file.
1518
- getLocationDataCompensation(start, end) {
1519
- var compensation, current, initialEnd, totalCompensation;
1520
- totalCompensation = 0;
1521
- initialEnd = end;
1522
- current = start;
1523
- while (current <= end) {
1524
- if (current === end && start !== initialEnd) {
1525
- break;
1526
- }
1527
- compensation = this.locationDataCompensations[current];
1528
- if (compensation != null) {
1529
- totalCompensation += compensation;
1530
- end += compensation;
1531
- }
1532
- current++;
1533
- }
1534
- return totalCompensation;
1535
- }
1536
-
1537
- // Returns the line and column number from an offset into the current chunk.
1538
-
1539
- // `offset` is a number of characters into `@chunk`.
1540
- getLineAndColumnFromChunk(offset) {
1541
- var column, columnCompensation, compensation, lastLine, lineCount, previousLinesCompensation, ref, string;
1542
- compensation = this.getLocationDataCompensation(this.chunkOffset, this.chunkOffset + offset);
1543
- if (offset === 0) {
1544
- return [this.chunkLine, this.chunkColumn + compensation, this.chunkOffset + compensation];
1545
- }
1546
- if (offset >= this.chunk.length) {
1547
- string = this.chunk;
1548
- } else {
1549
- string = this.chunk.slice(0, +(offset - 1) + 1 || 9e9);
1550
- }
1551
- lineCount = count(string, '\n');
1552
- column = this.chunkColumn;
1553
- if (lineCount > 0) {
1554
- ref = string.split('\n'), [lastLine] = slice.call(ref, -1);
1555
- column = lastLine.length;
1556
- previousLinesCompensation = this.getLocationDataCompensation(this.chunkOffset, this.chunkOffset + offset - column);
1557
- if (previousLinesCompensation < 0) {
1558
- // Don't recompensate for initially inserted newline.
1559
- previousLinesCompensation = 0;
1560
- }
1561
- columnCompensation = this.getLocationDataCompensation(this.chunkOffset + offset + previousLinesCompensation - column, this.chunkOffset + offset + previousLinesCompensation);
1562
- } else {
1563
- column += string.length;
1564
- columnCompensation = compensation;
1565
- }
1566
- return [this.chunkLine + lineCount, column + columnCompensation, this.chunkOffset + offset + compensation];
1567
- }
1568
-
1569
- makeLocationData({offsetInChunk, length}) {
1570
- var endOffset, lastCharacter, locationData;
1571
- locationData = {
1572
- range: []
1573
- };
1574
- [locationData.first_line, locationData.first_column, locationData.range[0]] = this.getLineAndColumnFromChunk(offsetInChunk);
1575
- // Use length - 1 for the final offset - we’re supplying the last_line and the last_column,
1576
- // so if last_column == first_column, then we’re looking at a character of length 1.
1577
- lastCharacter = length > 0 ? length - 1 : 0;
1578
- [locationData.last_line, locationData.last_column, endOffset] = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter);
1579
- [locationData.last_line_exclusive, locationData.last_column_exclusive] = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter + (length > 0 ? 1 : 0));
1580
- locationData.range[1] = length > 0 ? endOffset + 1 : endOffset;
1581
- return locationData;
1582
- }
1583
-
1584
- // Same as `token`, except this just returns the token without adding it
1585
- // to the results.
1586
- makeToken(tag, value, {
1587
- offset: offsetInChunk = 0,
1588
- length = value.length,
1589
- origin,
1590
- generated,
1591
- indentSize
1592
- } = {}) {
1593
- var token;
1594
- token = [tag, value, this.makeLocationData({offsetInChunk, length})];
1595
- if (origin) {
1596
- token.origin = origin;
1597
- }
1598
- if (generated) {
1599
- token.generated = true;
1600
- }
1601
- if (indentSize != null) {
1602
- token.indentSize = indentSize;
1603
- }
1604
- return token;
1605
- }
1606
-
1607
- // Add a token to the results.
1608
- // `offset` is the offset into the current `@chunk` where the token starts.
1609
- // `length` is the length of the token in the `@chunk`, after the offset. If
1610
- // not specified, the length of `value` will be used.
1611
-
1612
- // Returns the new token.
1613
- token(tag, value, {offset, length, origin, data, generated, indentSize} = {}) {
1614
- var token;
1615
- token = this.makeToken(tag, value, {offset, length, origin, generated, indentSize});
1616
- if (data) {
1617
- addTokenData(token, data);
1618
- }
1619
- this.tokens.push(token);
1620
- return token;
1621
- }
1622
-
1623
- // Peek at the last tag in the token stream.
1624
- tag() {
1625
- var ref, token;
1626
- ref = this.tokens, [token] = slice.call(ref, -1);
1627
- return token != null ? token[0] : void 0;
1628
- }
1629
-
1630
- // Peek at the last value in the token stream.
1631
- value(useOrigin = false) {
1632
- var ref, token;
1633
- ref = this.tokens, [token] = slice.call(ref, -1);
1634
- if (useOrigin && ((token != null ? token.origin : void 0) != null)) {
1635
- return token.origin[1];
1636
- } else {
1637
- return token != null ? token[1] : void 0;
1638
- }
1639
- }
1640
-
1641
- // Get the previous token in the token stream.
1642
- prev() {
1643
- return this.tokens[this.tokens.length - 1];
1644
- }
1645
-
1646
- // Are we in the midst of an unfinished expression?
1647
- unfinished() {
1648
- var ref;
1649
- return LINE_CONTINUER.test(this.chunk) || (ref = this.tag(), indexOf.call(UNFINISHED, ref) >= 0);
1650
- }
1651
-
1652
- validateUnicodeCodePointEscapes(str, options) {
1653
- return replaceUnicodeCodePointEscapes(str, merge(options, {error: this.error}));
1654
- }
1655
-
1656
- // Validates escapes in strings and regexes.
1657
- validateEscapes(str, options = {}) {
1658
- var before, hex, invalidEscape, invalidEscapeRegex, match, message, octal, ref, unicode, unicodeCodePoint;
1659
- invalidEscapeRegex = options.isRegex ? REGEX_INVALID_ESCAPE : STRING_INVALID_ESCAPE;
1660
- match = invalidEscapeRegex.exec(str);
1661
- if (!match) {
1662
- return;
1663
- }
1664
- match[0], before = match[1], octal = match[2], hex = match[3], unicodeCodePoint = match[4], unicode = match[5];
1665
- message = octal ? "octal escape sequences are not allowed" : "invalid escape sequence";
1666
- invalidEscape = `\\${octal || hex || unicodeCodePoint || unicode}`;
1667
- return this.error(`${message} ${invalidEscape}`, {
1668
- offset: ((ref = options.offsetInChunk) != null ? ref : 0) + match.index + before.length,
1669
- length: invalidEscape.length
1670
- });
1671
- }
1672
-
1673
- suppressSemicolons() {
1674
- var ref, ref1, results;
1675
- results = [];
1676
- while (this.value() === ';') {
1677
- this.tokens.pop();
1678
- if (ref = (ref1 = this.prev()) != null ? ref1[0] : void 0, indexOf.call(['=', ...UNFINISHED], ref) >= 0) {
1679
- results.push(this.error('unexpected ;'));
1680
- } else {
1681
- results.push(void 0);
1682
- }
1683
- }
1684
- return results;
1685
- }
1686
-
1687
- error(message, options = {}) {
1688
- var first_column, first_line, location, ref, ref1;
1689
- location = 'first_line' in options ? options : ([first_line, first_column] = this.getLineAndColumnFromChunk((ref = options.offset) != null ? ref : 0), {
1690
- first_line,
1691
- first_column,
1692
- last_column: first_column + ((ref1 = options.length) != null ? ref1 : 1) - 1
1693
- });
1694
- return throwSyntaxError(message, location);
1695
- }
1696
-
1697
- };
1698
-
1699
- // Helper functions
1700
- // ----------------
1701
- isUnassignable = function(name, displayName = name) {
1702
- switch (false) {
1703
- case indexOf.call([...JS_KEYWORDS, ...COFFEE_KEYWORDS], name) < 0:
1704
- return `keyword '${displayName}' can't be assigned`;
1705
- case indexOf.call(STRICT_PROSCRIBED, name) < 0:
1706
- return `'${displayName}' can't be assigned`;
1707
- case indexOf.call(RESERVED, name) < 0:
1708
- return `reserved word '${displayName}' can't be assigned`;
1709
- default:
1710
- return false;
1711
- }
1712
- };
1713
-
1714
- exports.isUnassignable = isUnassignable;
1715
-
1716
- // `from` isn’t a CoffeeScript keyword, but it behaves like one in `import` and
1717
- // `export` statements (handled above) and in the declaration line of a `for`
1718
- // loop. Try to detect when `from` is a variable identifier and when it is this
1719
- // “sometimes” keyword.
1720
- isForFrom = function(prev) {
1721
- var ref;
1722
- // `for i from iterable`
1723
- if (prev[0] === 'IDENTIFIER') {
1724
- return true;
1725
- // `for from…`
1726
- } else if (prev[0] === 'FOR') {
1727
- return false;
1728
- // `for {from}…`, `for [from]…`, `for {a, from}…`, `for {a: from}…`
1729
- } else if ((ref = prev[1]) === '{' || ref === '[' || ref === ',' || ref === ':') {
1730
- return false;
1731
- } else {
1732
- return true;
1733
- }
1734
- };
1735
-
1736
- addTokenData = function(token, data) {
1737
- return Object.assign((token.data != null ? token.data : token.data = {}), data);
1738
- };
1739
-
1740
- // Constants
1741
- // ---------
1742
-
1743
- // Keywords that CoffeeScript shares in common with JavaScript.
1744
- JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'await', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default'];
1745
-
1746
- // CoffeeScript-only keywords.
1747
- COFFEE_KEYWORDS = ['undefined', 'Infinity', 'NaN', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
1748
-
1749
- COFFEE_ALIAS_MAP = {
1750
- and: '&&',
1751
- or: '||',
1752
- is: '==',
1753
- isnt: '!=',
1754
- not: '!',
1755
- yes: 'true',
1756
- no: 'false',
1757
- on: 'true',
1758
- off: 'false'
1759
- };
1760
-
1761
- COFFEE_ALIASES = (function() {
1762
- var results;
1763
- results = [];
1764
- for (key in COFFEE_ALIAS_MAP) {
1765
- results.push(key);
1766
- }
1767
- return results;
1768
- })();
1769
-
1770
- COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat(COFFEE_ALIASES);
1771
-
1772
- // The list of keywords that are reserved by JavaScript, but not used, or are
1773
- // used by CoffeeScript internally. We throw an error when these are encountered,
1774
- // to avoid having a JavaScript error at runtime.
1775
- RESERVED = ['case', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'native', 'implements', 'interface', 'package', 'private', 'protected', 'public', 'static'];
1776
-
1777
- STRICT_PROSCRIBED = ['arguments', 'eval'];
1778
-
1779
- // The superset of both JavaScript keywords and reserved words, none of which may
1780
- // be used as identifiers or properties.
1781
- exports.JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
1782
-
1783
- // The character code of the nasty Microsoft madness otherwise known as the BOM.
1784
- BOM = 65279;
1785
-
1786
- // Token matching regexes.
1787
- IDENTIFIER = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)([^\n\S]*:(?!:))?/; // Is this a property name?
1788
-
1789
- // Like `IDENTIFIER`, but includes `-`s
1790
- JSX_IDENTIFIER_PART = /(?:(?!\s)[\-$\w\x7f-\uffff])+/.source;
1791
-
1792
- // In https://facebook.github.io/jsx/ spec, JSXElementName can be
1793
- // JSXIdentifier, JSXNamespacedName (JSXIdentifier : JSXIdentifier), or
1794
- // JSXMemberExpression (two or more JSXIdentifier connected by `.`s).
1795
- JSX_IDENTIFIER = RegExp(`^(?![\\d<])(${JSX_IDENTIFIER_PART // Must not start with `<`.
1796
- // JSXNamespacedName
1797
- // JSXMemberExpression
1798
- }(?:\\s*:\\s*${JSX_IDENTIFIER_PART}|(?:\\s*\\.\\s*${JSX_IDENTIFIER_PART})+)?)`);
1799
-
1800
- // Fragment: <></>
1801
- JSX_FRAGMENT_IDENTIFIER = /^()>/; // Ends immediately with `>`.
1802
-
1803
- // In https://facebook.github.io/jsx/ spec, JSXAttributeName can be either
1804
- // JSXIdentifier or JSXNamespacedName which is JSXIdentifier : JSXIdentifier
1805
- JSX_ATTRIBUTE = RegExp(`^(?!\\d)(${JSX_IDENTIFIER_PART // JSXNamespacedName
1806
- // Is this an attribute with a value?
1807
- }(?:\\s*:\\s*${JSX_IDENTIFIER_PART})?)([^\\S]*=(?!=))?`);
2
+ (function () {
3
+ // The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
4
+ // matches against the beginning of the source code. When a match is found,
5
+ // a token is produced, we consume the match, and start again. Tokens are in the
6
+ // form:
7
+
8
+ // [tag, value, locationData]
9
+
10
+ // where locationData is {first_line, first_column, last_line, last_column, last_line_exclusive, last_column_exclusive}, which is a
11
+ // format that can be fed directly into [Jison](https://github.com/zaach/jison). These
12
+ // are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
13
+ var BOM,
14
+ BOOL,
15
+ CALLABLE,
16
+ CODE,
17
+ COFFEE_ALIASES,
18
+ COFFEE_ALIAS_MAP,
19
+ COFFEE_KEYWORDS,
20
+ COMMENT,
21
+ COMPARABLE_LEFT_SIDE,
22
+ COMPARE,
23
+ COMPOUND_ASSIGN,
24
+ HERECOMMENT_ILLEGAL,
25
+ HEREDOC_DOUBLE,
26
+ HEREDOC_INDENT,
27
+ HEREDOC_SINGLE,
28
+ HEREGEX,
29
+ HEREGEX_COMMENT,
30
+ HERE_JSTOKEN,
31
+ IDENTIFIER,
32
+ INDENTABLE_CLOSERS,
33
+ INDEXABLE,
34
+ INSIDE_JSX,
35
+ INVERSES,
36
+ JSTOKEN,
37
+ JSX_ATTRIBUTE,
38
+ JSX_FRAGMENT_IDENTIFIER,
39
+ JSX_IDENTIFIER,
40
+ JSX_IDENTIFIER_PART,
41
+ JSX_INTERPOLATION,
42
+ JS_KEYWORDS,
43
+ LINE_BREAK,
44
+ LINE_CONTINUER,
45
+ Lexer,
46
+ MATH,
47
+ MULTI_DENT,
48
+ NOT_REGEX,
49
+ NUMBER,
50
+ OPERATOR,
51
+ POSSIBLY_DIVISION,
52
+ REGEX,
53
+ REGEX_FLAGS,
54
+ REGEX_ILLEGAL,
55
+ REGEX_INVALID_ESCAPE,
56
+ RELATION,
57
+ RESERVED,
58
+ Rewriter,
59
+ SHIFT,
60
+ STRICT_PROSCRIBED,
61
+ STRING_DOUBLE,
62
+ STRING_INVALID_ESCAPE,
63
+ STRING_SINGLE,
64
+ STRING_START,
65
+ TRAILING_SPACES,
66
+ UNARY,
67
+ UNARY_MATH,
68
+ UNFINISHED,
69
+ VALID_FLAGS,
70
+ WHITESPACE,
71
+ addTokenData,
72
+ attachCommentsToNode,
73
+ compact,
74
+ count,
75
+ flatten,
76
+ invertLiterate,
77
+ isForFrom,
78
+ isUnassignable,
79
+ key,
80
+ locationDataToString,
81
+ merge,
82
+ parseNumber,
83
+ repeat,
84
+ replaceUnicodeCodePointEscapes,
85
+ starts,
86
+ throwSyntaxError,
87
+ indexOf = [].indexOf,
88
+ slice = [].slice;
89
+
90
+ ({ Rewriter, INVERSES, UNFINISHED } = require('./rewriter'));
91
+
92
+ // Import the helpers we need.
93
+ ({
94
+ count,
95
+ starts,
96
+ compact,
97
+ repeat,
98
+ invertLiterate,
99
+ merge,
100
+ attachCommentsToNode,
101
+ locationDataToString,
102
+ throwSyntaxError,
103
+ replaceUnicodeCodePointEscapes,
104
+ flatten,
105
+ parseNumber,
106
+ } = require('./helpers'));
107
+
108
+ // The Lexer Class
109
+ // ---------------
110
+
111
+ // The Lexer class reads a stream of CoffeeScript and divvies it up into tagged
112
+ // tokens. Some potential ambiguity in the grammar has been avoided by
113
+ // pushing some extra smarts into the Lexer.
114
+ exports.Lexer = Lexer = class Lexer {
115
+ constructor() {
116
+ // Throws an error at either a given offset from the current chunk or at the
117
+ // location of a token (`token[2]`).
118
+ this.error = this.error.bind(this);
119
+ }
120
+
121
+ // **tokenize** is the Lexer's main method. Scan by attempting to match tokens
122
+ // one at a time, using a regular expression anchored at the start of the
123
+ // remaining code, or a custom recursive token-matching method
124
+ // (for interpolations). When the next token has been recorded, we move forward
125
+ // within the code past the token, and begin again.
126
+
127
+ // Each tokenizing method is responsible for returning the number of characters
128
+ // it has consumed.
129
+
130
+ // Before returning the token stream, run it through the [Rewriter](rewriter.html).
131
+ tokenize(code, opts = {}) {
132
+ var consumed, end, i, ref;
133
+ this.literate = opts.literate; // Are we lexing literate CoffeeScript?
134
+ this.indent = 0; // The current indentation level.
135
+ this.baseIndent = 0; // The overall minimum indentation level.
136
+ this.continuationLineAdditionalIndent = 0; // The over-indentation at the current level.
137
+ this.outdebt = 0; // The under-outdentation at the current level.
138
+ this.indents = []; // The stack of all current indentation levels.
139
+ this.indentLiteral = ''; // The indentation.
140
+ this.ends = []; // The stack for pairing up tokens.
141
+ this.tokens = []; // Stream of parsed tokens in the form `['TYPE', value, location data]`.
142
+ this.seenFor = false; // Used to recognize `FORIN`, `FOROF` and `FORFROM` tokens.
143
+ this.seenImport = false; // Used to recognize `IMPORT FROM? AS?` tokens.
144
+ this.seenExport = false; // Used to recognize `EXPORT FROM? AS?` tokens.
145
+ this.importSpecifierList = false; // Used to identify when in an `IMPORT {...} FROM? ...`.
146
+ this.exportSpecifierList = false; // Used to identify when in an `EXPORT {...} FROM? ...`.
147
+ this.jsxDepth = 0; // Used to optimize JSX checks, how deep in JSX we are.
148
+ this.jsxObjAttribute = {}; // Used to detect if JSX attributes is wrapped in {} (<div {props...} />).
149
+ this.chunkLine = opts.line || 0; // The start line for the current @chunk.
150
+ this.chunkColumn = opts.column || 0; // The start column of the current @chunk.
151
+ this.chunkOffset = opts.offset || 0; // The start offset for the current @chunk.
152
+ this.locationDataCompensations = opts.locationDataCompensations || {};
153
+ code = this.clean(code); // The stripped, cleaned original source code.
154
+
155
+ // At every position, run through this list of attempted matches,
156
+ // short-circuiting if any of them succeed. Their order determines precedence:
157
+ // `@literalToken` is the fallback catch-all.
158
+ i = 0;
159
+ while ((this.chunk = code.slice(i))) {
160
+ consumed =
161
+ this.identifierToken() ||
162
+ this.commentToken() ||
163
+ this.whitespaceToken() ||
164
+ this.lineToken() ||
165
+ this.stringToken() ||
166
+ this.numberToken() ||
167
+ this.jsxToken() ||
168
+ this.regexToken() ||
169
+ this.jsToken() ||
170
+ this.literalToken();
171
+ // Update position.
172
+ [this.chunkLine, this.chunkColumn, this.chunkOffset] = this.getLineAndColumnFromChunk(consumed);
173
+ i += consumed;
174
+ if (opts.untilBalanced && this.ends.length === 0) {
175
+ return {
176
+ tokens: this.tokens,
177
+ index: i,
178
+ };
179
+ }
180
+ }
181
+ this.closeIndentation();
182
+ if ((end = this.ends.pop())) {
183
+ this.error(`missing ${end.tag}`, ((ref = end.origin) != null ? ref : end)[2]);
184
+ }
185
+ if (opts.rewrite === false) {
186
+ return this.tokens;
187
+ }
188
+ return new Rewriter().rewrite(this.tokens);
189
+ }
190
+
191
+ // Preprocess the code to remove leading and trailing whitespace, carriage
192
+ // returns, etc. If we’re lexing literate CoffeeScript, strip external Markdown
193
+ // by removing all lines that aren’t indented by at least four spaces or a tab.
194
+ clean(code) {
195
+ var base, thusFar;
196
+ thusFar = 0;
197
+ if (code.charCodeAt(0) === BOM) {
198
+ code = code.slice(1);
199
+ this.locationDataCompensations[0] = 1;
200
+ thusFar += 1;
201
+ }
202
+ if (WHITESPACE.test(code)) {
203
+ code = `\n${code}`;
204
+ this.chunkLine--;
205
+ if ((base = this.locationDataCompensations)[0] == null) {
206
+ base[0] = 0;
207
+ }
208
+ this.locationDataCompensations[0] -= 1;
209
+ }
210
+ code = code
211
+ .replace(/\r/g, (match, offset) => {
212
+ this.locationDataCompensations[thusFar + offset] = 1;
213
+ return '';
214
+ })
215
+ .replace(TRAILING_SPACES, '');
216
+ if (this.literate) {
217
+ code = invertLiterate(code);
218
+ }
219
+ return code;
220
+ }
221
+
222
+ // Tokenizers
223
+ // ----------
224
+
225
+ // Matches identifying literals: variables, keywords, method names, etc.
226
+ // Check to ensure that JavaScript reserved words aren’t being used as
227
+ // identifiers. Because CoffeeScript reserves a handful of keywords that are
228
+ // allowed in JavaScript, we’re careful not to tag them as keywords when
229
+ // referenced as property names here, so you can still do `jQuery.is()` even
230
+ // though `is` means `===` otherwise.
231
+ identifierToken() {
232
+ var alias,
233
+ colon,
234
+ colonOffset,
235
+ colonToken,
236
+ id,
237
+ idLength,
238
+ inJSXTag,
239
+ input,
240
+ match,
241
+ poppedToken,
242
+ prev,
243
+ prevprev,
244
+ ref,
245
+ ref1,
246
+ ref10,
247
+ ref11,
248
+ ref12,
249
+ ref2,
250
+ ref3,
251
+ ref4,
252
+ ref5,
253
+ ref6,
254
+ ref7,
255
+ ref8,
256
+ ref9,
257
+ regExSuper,
258
+ regex,
259
+ sup,
260
+ tag,
261
+ tagToken,
262
+ tokenData;
263
+ inJSXTag = this.atJSXTag();
264
+ regex = inJSXTag ? JSX_ATTRIBUTE : IDENTIFIER;
265
+ if (!(match = regex.exec(this.chunk))) {
266
+ return 0;
267
+ }
268
+ [input, id, colon] = match;
269
+ // Preserve length of id for location data
270
+ idLength = id.length;
271
+ poppedToken = void 0;
272
+ if (id === 'own' && this.tag() === 'FOR') {
273
+ this.token('OWN', id);
274
+ return id.length;
275
+ }
276
+ if (id === 'from' && this.tag() === 'YIELD') {
277
+ this.token('FROM', id);
278
+ return id.length;
279
+ }
280
+ if (id === 'as' && this.seenImport) {
281
+ if (this.value() === '*') {
282
+ this.tokens[this.tokens.length - 1][0] = 'IMPORT_ALL';
283
+ } else if (((ref = this.value(true)), indexOf.call(COFFEE_KEYWORDS, ref) >= 0)) {
284
+ prev = this.prev();
285
+ [prev[0], prev[1]] = ['IDENTIFIER', this.value(true)];
286
+ }
287
+ if ((ref1 = this.tag()) === 'DEFAULT' || ref1 === 'IMPORT_ALL' || ref1 === 'IDENTIFIER') {
288
+ this.token('AS', id);
289
+ return id.length;
290
+ }
291
+ }
292
+ if (id === 'as' && this.seenExport) {
293
+ if ((ref2 = this.tag()) === 'IDENTIFIER' || ref2 === 'DEFAULT') {
294
+ this.token('AS', id);
295
+ return id.length;
296
+ }
297
+ if (((ref3 = this.value(true)), indexOf.call(COFFEE_KEYWORDS, ref3) >= 0)) {
298
+ prev = this.prev();
299
+ [prev[0], prev[1]] = ['IDENTIFIER', this.value(true)];
300
+ this.token('AS', id);
301
+ return id.length;
302
+ }
303
+ }
304
+ if (id === 'default' && this.seenExport && ((ref4 = this.tag()) === 'EXPORT' || ref4 === 'AS')) {
305
+ this.token('DEFAULT', id);
306
+ return id.length;
307
+ }
308
+ if (id === 'assert' && (this.seenImport || this.seenExport) && this.tag() === 'STRING') {
309
+ this.token('ASSERT', id);
310
+ return id.length;
311
+ }
312
+ if (id === 'do' && (regExSuper = /^(\s*super)(?!\(\))/.exec(this.chunk.slice(3)))) {
313
+ this.token('SUPER', 'super');
314
+ this.token('CALL_START', '(');
315
+ this.token('CALL_END', ')');
316
+ [input, sup] = regExSuper;
317
+ return sup.length + 3;
318
+ }
319
+ prev = this.prev();
320
+ tag =
321
+ colon ||
322
+ (prev != null &&
323
+ ((ref5 = prev[0]) === '.' || ref5 === '?.' || ref5 === '::' || ref5 === '?::' || (!prev.spaced && prev[0] === '@')))
324
+ ? 'PROPERTY'
325
+ : 'IDENTIFIER';
326
+ tokenData = {};
327
+ if (
328
+ tag === 'IDENTIFIER' &&
329
+ (indexOf.call(JS_KEYWORDS, id) >= 0 || indexOf.call(COFFEE_KEYWORDS, id) >= 0) &&
330
+ !(this.exportSpecifierList && indexOf.call(COFFEE_KEYWORDS, id) >= 0)
331
+ ) {
332
+ tag = id.toUpperCase();
333
+ if (tag === 'WHEN' && ((ref6 = this.tag()), indexOf.call(LINE_BREAK, ref6) >= 0)) {
334
+ tag = 'LEADING_WHEN';
335
+ } else if (tag === 'FOR') {
336
+ this.seenFor = {
337
+ endsLength: this.ends.length,
338
+ };
339
+ } else if (tag === 'UNLESS') {
340
+ tag = 'IF';
341
+ } else if (tag === 'IMPORT') {
342
+ this.seenImport = true;
343
+ } else if (tag === 'EXPORT') {
344
+ this.seenExport = true;
345
+ } else if (indexOf.call(UNARY, tag) >= 0) {
346
+ tag = 'UNARY';
347
+ } else if (indexOf.call(RELATION, tag) >= 0) {
348
+ if (tag !== 'INSTANCEOF' && this.seenFor) {
349
+ tag = 'FOR' + tag;
350
+ this.seenFor = false;
351
+ } else {
352
+ tag = 'RELATION';
353
+ if (this.value() === '!') {
354
+ poppedToken = this.tokens.pop();
355
+ tokenData.invert = (ref7 = (ref8 = poppedToken.data) != null ? ref8.original : void 0) != null ? ref7 : poppedToken[1];
356
+ }
357
+ }
358
+ }
359
+ } else if (tag === 'IDENTIFIER' && this.seenFor && id === 'from' && isForFrom(prev)) {
360
+ tag = 'FORFROM';
361
+ this.seenFor = false;
362
+ // Throw an error on attempts to use `get` or `set` as keywords, or
363
+ // what CoffeeScript would normally interpret as calls to functions named
364
+ // `get` or `set`, i.e. `get({foo: function () {}})`.
365
+ } else if (tag === 'PROPERTY' && prev) {
366
+ if (
367
+ prev.spaced &&
368
+ ((ref9 = prev[0]), indexOf.call(CALLABLE, ref9) >= 0) &&
369
+ /^[gs]et$/.test(prev[1]) &&
370
+ this.tokens.length > 1 &&
371
+ (ref10 = this.tokens[this.tokens.length - 2][0]) !== '.' &&
372
+ ref10 !== '?.' &&
373
+ ref10 !== '@'
374
+ ) {
375
+ this.error(`'${prev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prev[2]);
376
+ } else if (
377
+ prev[0] === '.' &&
378
+ this.tokens.length > 1 &&
379
+ (prevprev = this.tokens[this.tokens.length - 2])[0] === 'UNARY' &&
380
+ prevprev[1] === 'new'
381
+ ) {
382
+ prevprev[0] = 'NEW_TARGET';
383
+ } else if (
384
+ prev[0] === '.' &&
385
+ this.tokens.length > 1 &&
386
+ (prevprev = this.tokens[this.tokens.length - 2])[0] === 'IMPORT' &&
387
+ prevprev[1] === 'import'
388
+ ) {
389
+ this.seenImport = false;
390
+ prevprev[0] = 'IMPORT_META';
391
+ } else if (this.tokens.length > 2) {
392
+ prevprev = this.tokens[this.tokens.length - 2];
393
+ if (
394
+ ((ref11 = prev[0]) === '@' || ref11 === 'THIS') &&
395
+ prevprev &&
396
+ prevprev.spaced &&
397
+ /^[gs]et$/.test(prevprev[1]) &&
398
+ (ref12 = this.tokens[this.tokens.length - 3][0]) !== '.' &&
399
+ ref12 !== '?.' &&
400
+ ref12 !== '@'
401
+ ) {
402
+ this.error(`'${prevprev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prevprev[2]);
403
+ }
404
+ }
405
+ }
406
+ if (tag === 'IDENTIFIER' && indexOf.call(RESERVED, id) >= 0 && !inJSXTag) {
407
+ this.error(`reserved word '${id}'`, {
408
+ length: id.length,
409
+ });
410
+ }
411
+ if (!(tag === 'PROPERTY' || this.exportSpecifierList || this.importSpecifierList)) {
412
+ if (indexOf.call(COFFEE_ALIASES, id) >= 0) {
413
+ alias = id;
414
+ id = COFFEE_ALIAS_MAP[id];
415
+ tokenData.original = alias;
416
+ }
417
+ tag = (function () {
418
+ switch (id) {
419
+ case '!':
420
+ return 'UNARY';
421
+ case '==':
422
+ case '!=':
423
+ return 'COMPARE';
424
+ case 'true':
425
+ case 'false':
426
+ return 'BOOL';
427
+ case 'break':
428
+ case 'continue':
429
+ case 'debugger':
430
+ return 'STATEMENT';
431
+ case '&&':
432
+ case '||':
433
+ return id;
434
+ default:
435
+ return tag;
436
+ }
437
+ })();
438
+ }
439
+ tagToken = this.token(tag, id, {
440
+ length: idLength,
441
+ data: tokenData,
442
+ });
443
+ if (alias) {
444
+ tagToken.origin = [tag, alias, tagToken[2]];
445
+ }
446
+ if (poppedToken) {
447
+ [tagToken[2].first_line, tagToken[2].first_column, tagToken[2].range[0]] = [
448
+ poppedToken[2].first_line,
449
+ poppedToken[2].first_column,
450
+ poppedToken[2].range[0],
451
+ ];
452
+ }
453
+ if (colon) {
454
+ colonOffset = input.lastIndexOf(inJSXTag ? '=' : ':');
455
+ colonToken = this.token(':', ':', {
456
+ offset: colonOffset,
457
+ });
458
+ if (inJSXTag) {
459
+ // used by rewriter
460
+ colonToken.jsxColon = true;
461
+ }
462
+ }
463
+ if (inJSXTag && tag === 'IDENTIFIER' && prev[0] !== ':') {
464
+ this.token(',', ',', {
465
+ length: 0,
466
+ origin: tagToken,
467
+ generated: true,
468
+ });
469
+ }
470
+ return input.length;
471
+ }
472
+
473
+ // Matches numbers, including decimals, hex, and exponential notation.
474
+ // Be careful not to interfere with ranges in progress.
475
+ numberToken() {
476
+ var lexedLength, match, number, parsedValue, tag, tokenData;
477
+ if (!(match = NUMBER.exec(this.chunk))) {
478
+ return 0;
479
+ }
480
+ number = match[0];
481
+ lexedLength = number.length;
482
+ switch (false) {
483
+ case !/^0[BOX]/.test(number):
484
+ this.error(`radix prefix in '${number}' must be lowercase`, {
485
+ offset: 1,
486
+ });
487
+ break;
488
+ case !/^0\d*[89]/.test(number):
489
+ this.error(`decimal literal '${number}' must not be prefixed with '0'`, {
490
+ length: lexedLength,
491
+ });
492
+ break;
493
+ case !/^0\d+/.test(number):
494
+ this.error(`octal literal '${number}' must be prefixed with '0o'`, {
495
+ length: lexedLength,
496
+ });
497
+ }
498
+ parsedValue = parseNumber(number);
499
+ tokenData = { parsedValue };
500
+ tag = parsedValue === 2e308 ? 'INFINITY' : 'NUMBER';
501
+ if (tag === 'INFINITY') {
502
+ tokenData.original = number;
503
+ }
504
+ this.token(tag, number, {
505
+ length: lexedLength,
506
+ data: tokenData,
507
+ });
508
+ return lexedLength;
509
+ }
510
+
511
+ // Matches strings, including multiline strings, as well as heredocs, with or without
512
+ // interpolation.
513
+ stringToken() {
514
+ var attempt, delimiter, doc, end, heredoc, i, indent, match, prev, quote, ref, regex, token, tokens;
515
+ [quote] = STRING_START.exec(this.chunk) || [];
516
+ if (!quote) {
517
+ return 0;
518
+ }
519
+ // If the preceding token is `from` and this is an import or export statement,
520
+ // properly tag the `from`.
521
+ prev = this.prev();
522
+ if (prev && this.value() === 'from' && (this.seenImport || this.seenExport)) {
523
+ prev[0] = 'FROM';
524
+ }
525
+ regex = (function () {
526
+ switch (quote) {
527
+ case "'":
528
+ return STRING_SINGLE;
529
+ case '"':
530
+ return STRING_DOUBLE;
531
+ case "'''":
532
+ return HEREDOC_SINGLE;
533
+ case '"""':
534
+ return HEREDOC_DOUBLE;
535
+ }
536
+ })();
537
+ ({ tokens, index: end } = this.matchWithInterpolations(regex, quote));
538
+ heredoc = quote.length === 3;
539
+ if (heredoc) {
540
+ // Find the smallest indentation. It will be removed from all lines later.
541
+ indent = null;
542
+ doc = (function () {
543
+ var j, len, results;
544
+ results = [];
545
+ for (i = j = 0, len = tokens.length; j < len; i = ++j) {
546
+ token = tokens[i];
547
+ if (token[0] === 'NEOSTRING') {
548
+ results.push(token[1]);
549
+ }
550
+ }
551
+ return results;
552
+ })().join('#{}');
553
+ while ((match = HEREDOC_INDENT.exec(doc))) {
554
+ attempt = match[1];
555
+ if (indent === null || (0 < (ref = attempt.length) && ref < indent.length)) {
556
+ indent = attempt;
557
+ }
558
+ }
559
+ }
560
+ delimiter = quote.charAt(0);
561
+ this.mergeInterpolationTokens(
562
+ tokens,
563
+ {
564
+ quote,
565
+ indent,
566
+ endOffset: end,
567
+ },
568
+ (value) => {
569
+ return this.validateUnicodeCodePointEscapes(value, {
570
+ delimiter: quote,
571
+ });
572
+ },
573
+ );
574
+ if (this.atJSXTag()) {
575
+ this.token(',', ',', {
576
+ length: 0,
577
+ origin: this.prev,
578
+ generated: true,
579
+ });
580
+ }
581
+ return end;
582
+ }
583
+
584
+ // Matches and consumes comments. The comments are taken out of the token
585
+ // stream and saved for later, to be reinserted into the output after
586
+ // everything has been parsed and the JavaScript code generated.
587
+ commentToken(chunk = this.chunk, { heregex, returnCommentTokens = false, offsetInChunk = 0 } = {}) {
588
+ var commentAttachment,
589
+ commentAttachments,
590
+ commentWithSurroundingWhitespace,
591
+ content,
592
+ contents,
593
+ getIndentSize,
594
+ hasSeenFirstCommentLine,
595
+ hereComment,
596
+ hereLeadingWhitespace,
597
+ hereTrailingWhitespace,
598
+ i,
599
+ indentSize,
600
+ leadingNewline,
601
+ leadingNewlineOffset,
602
+ leadingNewlines,
603
+ leadingWhitespace,
604
+ length,
605
+ lineComment,
606
+ match,
607
+ matchIllegal,
608
+ noIndent,
609
+ nonInitial,
610
+ placeholderToken,
611
+ precededByBlankLine,
612
+ precedingNonCommentLines,
613
+ prev;
614
+ if (!(match = chunk.match(COMMENT))) {
615
+ return 0;
616
+ }
617
+ [commentWithSurroundingWhitespace, hereLeadingWhitespace, hereComment, hereTrailingWhitespace, lineComment] = match;
618
+ contents = null;
619
+ // Does this comment follow code on the same line?
620
+ leadingNewline = /^\s*\n+\s*#/.test(commentWithSurroundingWhitespace);
621
+ if (hereComment) {
622
+ matchIllegal = HERECOMMENT_ILLEGAL.exec(hereComment);
623
+ if (matchIllegal) {
624
+ this.error(`block comments cannot contain ${matchIllegal[0]}`, {
625
+ offset: '###'.length + matchIllegal.index,
626
+ length: matchIllegal[0].length,
627
+ });
628
+ }
629
+ // Parse indentation or outdentation as if this block comment didn’t exist.
630
+ chunk = chunk.replace(`###${hereComment}###`, '');
631
+ // Remove leading newlines, like `Rewriter::removeLeadingNewlines`, to
632
+ // avoid the creation of unwanted `TERMINATOR` tokens.
633
+ chunk = chunk.replace(/^\n+/, '');
634
+ this.lineToken({ chunk });
635
+ // Pull out the ###-style comment’s content, and format it.
636
+ content = hereComment;
637
+ contents = [
638
+ {
639
+ content,
640
+ length: commentWithSurroundingWhitespace.length - hereLeadingWhitespace.length - hereTrailingWhitespace.length,
641
+ leadingWhitespace: hereLeadingWhitespace,
642
+ },
643
+ ];
644
+ } else {
645
+ // The `COMMENT` regex captures successive line comments as one token.
646
+ // Remove any leading newlines before the first comment, but preserve
647
+ // blank lines between line comments.
648
+ leadingNewlines = '';
649
+ content = lineComment.replace(/^(\n*)/, function (leading) {
650
+ leadingNewlines = leading;
651
+ return '';
652
+ });
653
+ precedingNonCommentLines = '';
654
+ hasSeenFirstCommentLine = false;
655
+ contents = content
656
+ .split('\n')
657
+ .map(function (line, index) {
658
+ var comment, leadingWhitespace;
659
+ if (!(line.indexOf('#') > -1)) {
660
+ precedingNonCommentLines += `\n${line}`;
661
+ return;
662
+ }
663
+ leadingWhitespace = '';
664
+ content = line.replace(/^([ |\t]*)#/, function (_, whitespace) {
665
+ leadingWhitespace = whitespace;
666
+ return '';
667
+ });
668
+ comment = {
669
+ content,
670
+ length: '#'.length + content.length,
671
+ leadingWhitespace: `${!hasSeenFirstCommentLine ? leadingNewlines : ''}${precedingNonCommentLines}${leadingWhitespace}`,
672
+ precededByBlankLine: !!precedingNonCommentLines,
673
+ };
674
+ hasSeenFirstCommentLine = true;
675
+ precedingNonCommentLines = '';
676
+ return comment;
677
+ })
678
+ .filter(function (comment) {
679
+ return comment;
680
+ });
681
+ }
682
+ getIndentSize = function ({ leadingWhitespace, nonInitial }) {
683
+ var lastNewlineIndex;
684
+ lastNewlineIndex = leadingWhitespace.lastIndexOf('\n');
685
+ if (hereComment != null || !nonInitial) {
686
+ if (!(lastNewlineIndex > -1)) {
687
+ return null;
688
+ }
689
+ } else {
690
+ if (lastNewlineIndex == null) {
691
+ lastNewlineIndex = -1;
692
+ }
693
+ }
694
+ return leadingWhitespace.length - 1 - lastNewlineIndex;
695
+ };
696
+ commentAttachments = function () {
697
+ var j, len, results;
698
+ results = [];
699
+ for (i = j = 0, len = contents.length; j < len; i = ++j) {
700
+ ({ content, length, leadingWhitespace, precededByBlankLine } = contents[i]);
701
+ nonInitial = i !== 0;
702
+ leadingNewlineOffset = nonInitial ? 1 : 0;
703
+ offsetInChunk += leadingNewlineOffset + leadingWhitespace.length;
704
+ indentSize = getIndentSize({ leadingWhitespace, nonInitial });
705
+ noIndent = indentSize == null || indentSize === -1;
706
+ commentAttachment = {
707
+ content,
708
+ here: hereComment != null,
709
+ newLine: leadingNewline || nonInitial, // Line comments after the first one start new lines, by definition.
710
+ locationData: this.makeLocationData({ offsetInChunk, length }),
711
+ precededByBlankLine,
712
+ indentSize,
713
+ indented: !noIndent && indentSize > this.indent,
714
+ outdented: !noIndent && indentSize < this.indent,
715
+ };
716
+ if (heregex) {
717
+ commentAttachment.heregex = true;
718
+ }
719
+ offsetInChunk += length;
720
+ results.push(commentAttachment);
721
+ }
722
+ return results;
723
+ }.call(this);
724
+ prev = this.prev();
725
+ if (!prev) {
726
+ // If there’s no previous token, create a placeholder token to attach
727
+ // this comment to; and follow with a newline.
728
+ commentAttachments[0].newLine = true;
729
+ this.lineToken({
730
+ chunk: this.chunk.slice(commentWithSurroundingWhitespace.length),
731
+ offset: commentWithSurroundingWhitespace.length, // Set the indent.
732
+ });
733
+ placeholderToken = this.makeToken('JS', '', {
734
+ offset: commentWithSurroundingWhitespace.length,
735
+ generated: true,
736
+ });
737
+ placeholderToken.comments = commentAttachments;
738
+ this.tokens.push(placeholderToken);
739
+ this.newlineToken(commentWithSurroundingWhitespace.length);
740
+ } else {
741
+ attachCommentsToNode(commentAttachments, prev);
742
+ }
743
+ if (returnCommentTokens) {
744
+ return commentAttachments;
745
+ }
746
+ return commentWithSurroundingWhitespace.length;
747
+ }
748
+
749
+ // Matches JavaScript interpolated directly into the source via backticks.
750
+ jsToken() {
751
+ var length, match, matchedHere, script;
752
+ if (!(this.chunk.charAt(0) === '`' && (match = (matchedHere = HERE_JSTOKEN.exec(this.chunk)) || JSTOKEN.exec(this.chunk)))) {
753
+ return 0;
754
+ }
755
+ // Convert escaped backticks to backticks, and escaped backslashes
756
+ // just before escaped backticks to backslashes
757
+ script = match[1];
758
+ ({ length } = match[0]);
759
+ this.token('JS', script, {
760
+ length,
761
+ data: {
762
+ here: !!matchedHere,
763
+ },
764
+ });
765
+ return length;
766
+ }
767
+
768
+ // Matches regular expression literals, as well as multiline extended ones.
769
+ // Lexing regular expressions is difficult to distinguish from division, so we
770
+ // borrow some basic heuristics from JavaScript and Ruby.
771
+ regexToken() {
772
+ var body,
773
+ closed,
774
+ comment,
775
+ commentIndex,
776
+ commentOpts,
777
+ commentTokens,
778
+ comments,
779
+ delimiter,
780
+ end,
781
+ flags,
782
+ fullMatch,
783
+ index,
784
+ leadingWhitespace,
785
+ match,
786
+ matchedComment,
787
+ origin,
788
+ prev,
789
+ ref,
790
+ ref1,
791
+ regex,
792
+ tokens;
793
+ switch (false) {
794
+ case !(match = REGEX_ILLEGAL.exec(this.chunk)):
795
+ this.error(`regular expressions cannot begin with ${match[2]}`, {
796
+ offset: match.index + match[1].length,
797
+ });
798
+ break;
799
+ case !(match = this.matchWithInterpolations(HEREGEX, '///')):
800
+ ({ tokens, index } = match);
801
+ comments = [];
802
+ while ((matchedComment = HEREGEX_COMMENT.exec(this.chunk.slice(0, index)))) {
803
+ ({ index: commentIndex } = matchedComment);
804
+ [fullMatch, leadingWhitespace, comment] = matchedComment;
805
+ comments.push({
806
+ comment,
807
+ offsetInChunk: commentIndex + leadingWhitespace.length,
808
+ });
809
+ }
810
+ commentTokens = flatten(
811
+ function () {
812
+ var j, len, results;
813
+ results = [];
814
+ for (j = 0, len = comments.length; j < len; j++) {
815
+ commentOpts = comments[j];
816
+ results.push(
817
+ this.commentToken(
818
+ commentOpts.comment,
819
+ Object.assign(commentOpts, {
820
+ heregex: true,
821
+ returnCommentTokens: true,
822
+ }),
823
+ ),
824
+ );
825
+ }
826
+ return results;
827
+ }.call(this),
828
+ );
829
+ break;
830
+ case !(match = REGEX.exec(this.chunk)):
831
+ [regex, body, closed] = match;
832
+ this.validateEscapes(body, {
833
+ isRegex: true,
834
+ offsetInChunk: 1,
835
+ });
836
+ index = regex.length;
837
+ prev = this.prev();
838
+ if (prev) {
839
+ if (prev.spaced && ((ref = prev[0]), indexOf.call(CALLABLE, ref) >= 0)) {
840
+ if (!closed || POSSIBLY_DIVISION.test(regex)) {
841
+ return 0;
842
+ }
843
+ } else if (((ref1 = prev[0]), indexOf.call(NOT_REGEX, ref1) >= 0)) {
844
+ return 0;
845
+ }
846
+ }
847
+ if (!closed) {
848
+ this.error('missing / (unclosed regex)');
849
+ }
850
+ break;
851
+ default:
852
+ return 0;
853
+ }
854
+ [flags] = REGEX_FLAGS.exec(this.chunk.slice(index));
855
+ end = index + flags.length;
856
+ origin = this.makeToken('REGEX', null, {
857
+ length: end,
858
+ });
859
+ switch (false) {
860
+ case !!VALID_FLAGS.test(flags):
861
+ this.error(`invalid regular expression flags ${flags}`, {
862
+ offset: index,
863
+ length: flags.length,
864
+ });
865
+ break;
866
+ case !(regex || tokens.length === 1):
867
+ delimiter = body ? '/' : '///';
868
+ if (body == null) {
869
+ body = tokens[0][1];
870
+ }
871
+ this.validateUnicodeCodePointEscapes(body, { delimiter });
872
+ this.token('REGEX', `/${body}/${flags}`, {
873
+ length: end,
874
+ origin,
875
+ data: { delimiter },
876
+ });
877
+ break;
878
+ default:
879
+ this.token('REGEX_START', '(', {
880
+ length: 0,
881
+ origin,
882
+ generated: true,
883
+ });
884
+ this.token('IDENTIFIER', 'RegExp', {
885
+ length: 0,
886
+ generated: true,
887
+ });
888
+ this.token('CALL_START', '(', {
889
+ length: 0,
890
+ generated: true,
891
+ });
892
+ this.mergeInterpolationTokens(
893
+ tokens,
894
+ {
895
+ double: true,
896
+ heregex: { flags },
897
+ endOffset: end - flags.length,
898
+ quote: '///',
899
+ },
900
+ (str) => {
901
+ return this.validateUnicodeCodePointEscapes(str, { delimiter });
902
+ },
903
+ );
904
+ if (flags) {
905
+ this.token(',', ',', {
906
+ offset: index - 1,
907
+ length: 0,
908
+ generated: true,
909
+ });
910
+ this.token('STRING', '"' + flags + '"', {
911
+ offset: index,
912
+ length: flags.length,
913
+ });
914
+ }
915
+ this.token(')', ')', {
916
+ offset: end,
917
+ length: 0,
918
+ generated: true,
919
+ });
920
+ this.token('REGEX_END', ')', {
921
+ offset: end,
922
+ length: 0,
923
+ generated: true,
924
+ });
925
+ }
926
+ // Explicitly attach any heregex comments to the REGEX/REGEX_END token.
927
+ if (commentTokens != null ? commentTokens.length : void 0) {
928
+ addTokenData(this.tokens[this.tokens.length - 1], {
929
+ heregexCommentTokens: commentTokens,
930
+ });
931
+ }
932
+ return end;
933
+ }
934
+
935
+ // Matches newlines, indents, and outdents, and determines which is which.
936
+ // If we can detect that the current line is continued onto the next line,
937
+ // then the newline is suppressed:
938
+
939
+ // elements
940
+ // .each( ... )
941
+ // .map( ... )
942
+
943
+ // Keeps track of the level of indentation, because a single outdent token
944
+ // can close multiple indents, so we need to know how far in we happen to be.
945
+ lineToken({ chunk = this.chunk, offset = 0 } = {}) {
946
+ var backslash, diff, endsContinuationLineIndentation, indent, match, minLiteralLength, newIndentLiteral, noNewlines, prev, ref, size;
947
+ if (!(match = MULTI_DENT.exec(chunk))) {
948
+ return 0;
949
+ }
950
+ indent = match[0];
951
+ prev = this.prev();
952
+ backslash = (prev != null ? prev[0] : void 0) === '\\';
953
+ if (!((backslash || ((ref = this.seenFor) != null ? ref.endsLength : void 0) < this.ends.length) && this.seenFor)) {
954
+ this.seenFor = false;
955
+ }
956
+ if (!((backslash && this.seenImport) || this.importSpecifierList)) {
957
+ this.seenImport = false;
958
+ }
959
+ if (!((backslash && this.seenExport) || this.exportSpecifierList)) {
960
+ this.seenExport = false;
961
+ }
962
+ size = indent.length - 1 - indent.lastIndexOf('\n');
963
+ noNewlines = this.unfinished();
964
+ newIndentLiteral = size > 0 ? indent.slice(-size) : '';
965
+ if (!/^(.?)\1*$/.exec(newIndentLiteral)) {
966
+ this.error('mixed indentation', {
967
+ offset: indent.length,
968
+ });
969
+ return indent.length;
970
+ }
971
+ minLiteralLength = Math.min(newIndentLiteral.length, this.indentLiteral.length);
972
+ if (newIndentLiteral.slice(0, minLiteralLength) !== this.indentLiteral.slice(0, minLiteralLength)) {
973
+ this.error('indentation mismatch', {
974
+ offset: indent.length,
975
+ });
976
+ return indent.length;
977
+ }
978
+ if (size - this.continuationLineAdditionalIndent === this.indent) {
979
+ if (noNewlines) {
980
+ this.suppressNewlines();
981
+ } else {
982
+ this.newlineToken(offset);
983
+ }
984
+ return indent.length;
985
+ }
986
+ if (size > this.indent) {
987
+ if (noNewlines) {
988
+ if (!backslash) {
989
+ this.continuationLineAdditionalIndent = size - this.indent;
990
+ }
991
+ if (this.continuationLineAdditionalIndent) {
992
+ prev.continuationLineIndent = this.indent + this.continuationLineAdditionalIndent;
993
+ }
994
+ this.suppressNewlines();
995
+ return indent.length;
996
+ }
997
+ if (!this.tokens.length) {
998
+ this.baseIndent = this.indent = size;
999
+ this.indentLiteral = newIndentLiteral;
1000
+ return indent.length;
1001
+ }
1002
+ diff = size - this.indent + this.outdebt;
1003
+ this.token('INDENT', diff, {
1004
+ offset: offset + indent.length - size,
1005
+ length: size,
1006
+ });
1007
+ this.indents.push(diff);
1008
+ this.ends.push({
1009
+ tag: 'OUTDENT',
1010
+ });
1011
+ this.outdebt = this.continuationLineAdditionalIndent = 0;
1012
+ this.indent = size;
1013
+ this.indentLiteral = newIndentLiteral;
1014
+ } else if (size < this.baseIndent) {
1015
+ this.error('missing indentation', {
1016
+ offset: offset + indent.length,
1017
+ });
1018
+ } else {
1019
+ endsContinuationLineIndentation = this.continuationLineAdditionalIndent > 0;
1020
+ this.continuationLineAdditionalIndent = 0;
1021
+ this.outdentToken({
1022
+ moveOut: this.indent - size,
1023
+ noNewlines,
1024
+ outdentLength: indent.length,
1025
+ offset,
1026
+ indentSize: size,
1027
+ endsContinuationLineIndentation,
1028
+ });
1029
+ }
1030
+ return indent.length;
1031
+ }
1032
+
1033
+ // Record an outdent token or multiple tokens, if we happen to be moving back
1034
+ // inwards past several recorded indents. Sets new @indent value.
1035
+ outdentToken({ moveOut, noNewlines, outdentLength = 0, offset = 0, indentSize, endsContinuationLineIndentation }) {
1036
+ var decreasedIndent, dent, lastIndent, ref, terminatorToken;
1037
+ decreasedIndent = this.indent - moveOut;
1038
+ while (moveOut > 0) {
1039
+ lastIndent = this.indents[this.indents.length - 1];
1040
+ if (!lastIndent) {
1041
+ this.outdebt = moveOut = 0;
1042
+ } else if (this.outdebt && moveOut <= this.outdebt) {
1043
+ this.outdebt -= moveOut;
1044
+ moveOut = 0;
1045
+ } else {
1046
+ dent = this.indents.pop() + this.outdebt;
1047
+ if (outdentLength && ((ref = this.chunk[outdentLength]), indexOf.call(INDENTABLE_CLOSERS, ref) >= 0)) {
1048
+ decreasedIndent -= dent - moveOut;
1049
+ moveOut = dent;
1050
+ }
1051
+ this.outdebt = 0;
1052
+ // pair might call outdentToken, so preserve decreasedIndent
1053
+ this.pair('OUTDENT');
1054
+ this.token('OUTDENT', moveOut, {
1055
+ length: outdentLength,
1056
+ indentSize: indentSize + moveOut - dent,
1057
+ });
1058
+ moveOut -= dent;
1059
+ }
1060
+ }
1061
+ if (dent) {
1062
+ this.outdebt -= moveOut;
1063
+ }
1064
+ this.suppressSemicolons();
1065
+ if (!(this.tag() === 'TERMINATOR' || noNewlines)) {
1066
+ terminatorToken = this.token('TERMINATOR', '\n', {
1067
+ offset: offset + outdentLength,
1068
+ length: 0,
1069
+ });
1070
+ if (endsContinuationLineIndentation) {
1071
+ terminatorToken.endsContinuationLineIndentation = {
1072
+ preContinuationLineIndent: this.indent,
1073
+ };
1074
+ }
1075
+ }
1076
+ this.indent = decreasedIndent;
1077
+ this.indentLiteral = this.indentLiteral.slice(0, decreasedIndent);
1078
+ return this;
1079
+ }
1080
+
1081
+ // Matches and consumes non-meaningful whitespace. Tag the previous token
1082
+ // as being “spaced”, because there are some cases where it makes a difference.
1083
+ whitespaceToken() {
1084
+ var match, nline, prev;
1085
+ if (!((match = WHITESPACE.exec(this.chunk)) || (nline = this.chunk.charAt(0) === '\n'))) {
1086
+ return 0;
1087
+ }
1088
+ prev = this.prev();
1089
+ if (prev) {
1090
+ prev[match ? 'spaced' : 'newLine'] = true;
1091
+ }
1092
+ if (match) {
1093
+ return match[0].length;
1094
+ } else {
1095
+ return 0;
1096
+ }
1097
+ }
1098
+
1099
+ // Generate a newline token. Consecutive newlines get merged together.
1100
+ newlineToken(offset) {
1101
+ this.suppressSemicolons();
1102
+ if (this.tag() !== 'TERMINATOR') {
1103
+ this.token('TERMINATOR', '\n', {
1104
+ offset,
1105
+ length: 0,
1106
+ });
1107
+ }
1108
+ return this;
1109
+ }
1110
+
1111
+ // Use a `\` at a line-ending to suppress the newline.
1112
+ // The slash is removed here once its job is done.
1113
+ suppressNewlines() {
1114
+ var prev;
1115
+ prev = this.prev();
1116
+ if (prev[1] === '\\') {
1117
+ if (prev.comments && this.tokens.length > 1) {
1118
+ // `@tokens.length` should be at least 2 (some code, then `\`).
1119
+ // If something puts a `\` after nothing, they deserve to lose any
1120
+ // comments that trail it.
1121
+ attachCommentsToNode(prev.comments, this.tokens[this.tokens.length - 2]);
1122
+ }
1123
+ this.tokens.pop();
1124
+ }
1125
+ return this;
1126
+ }
1127
+
1128
+ jsxToken() {
1129
+ var afterTag,
1130
+ end,
1131
+ endToken,
1132
+ firstChar,
1133
+ fullId,
1134
+ fullTagName,
1135
+ id,
1136
+ input,
1137
+ j,
1138
+ jsxTag,
1139
+ len,
1140
+ match,
1141
+ offset,
1142
+ openingTagToken,
1143
+ prev,
1144
+ prevChar,
1145
+ properties,
1146
+ property,
1147
+ ref,
1148
+ tagToken,
1149
+ token,
1150
+ tokens;
1151
+ firstChar = this.chunk[0];
1152
+ // Check the previous token to detect if attribute is spread.
1153
+ prevChar = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1][0] : '';
1154
+ if (firstChar === '<') {
1155
+ match = JSX_IDENTIFIER.exec(this.chunk.slice(1)) || JSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(1));
1156
+ // Not the right hand side of an unspaced comparison (i.e. `a<b`).
1157
+ if (
1158
+ !(
1159
+ match &&
1160
+ (this.jsxDepth > 0 || !(prev = this.prev()) || prev.spaced || ((ref = prev[0]), indexOf.call(COMPARABLE_LEFT_SIDE, ref) < 0))
1161
+ )
1162
+ ) {
1163
+ return 0;
1164
+ }
1165
+ [input, id] = match;
1166
+ fullId = id;
1167
+ if (indexOf.call(id, '.') >= 0) {
1168
+ [id, ...properties] = id.split('.');
1169
+ } else {
1170
+ properties = [];
1171
+ }
1172
+ tagToken = this.token('JSX_TAG', id, {
1173
+ length: id.length + 1,
1174
+ data: {
1175
+ openingBracketToken: this.makeToken('<', '<'),
1176
+ tagNameToken: this.makeToken('IDENTIFIER', id, {
1177
+ offset: 1,
1178
+ }),
1179
+ },
1180
+ });
1181
+ offset = id.length + 1;
1182
+ for (j = 0, len = properties.length; j < len; j++) {
1183
+ property = properties[j];
1184
+ this.token('.', '.', { offset });
1185
+ offset += 1;
1186
+ this.token('PROPERTY', property, { offset });
1187
+ offset += property.length;
1188
+ }
1189
+ this.token('CALL_START', '(', {
1190
+ generated: true,
1191
+ });
1192
+ this.token('[', '[', {
1193
+ generated: true,
1194
+ });
1195
+ this.ends.push({
1196
+ tag: '/>',
1197
+ origin: tagToken,
1198
+ name: id,
1199
+ properties,
1200
+ });
1201
+ this.jsxDepth++;
1202
+ return fullId.length + 1;
1203
+ } else if ((jsxTag = this.atJSXTag())) {
1204
+ if (this.chunk.slice(0, 2) === '/>') {
1205
+ // Self-closing tag.
1206
+ this.pair('/>');
1207
+ this.token(']', ']', {
1208
+ length: 2,
1209
+ generated: true,
1210
+ });
1211
+ this.token('CALL_END', ')', {
1212
+ length: 2,
1213
+ generated: true,
1214
+ data: {
1215
+ selfClosingSlashToken: this.makeToken('/', '/'),
1216
+ closingBracketToken: this.makeToken('>', '>', {
1217
+ offset: 1,
1218
+ }),
1219
+ },
1220
+ });
1221
+ this.jsxDepth--;
1222
+ return 2;
1223
+ } else if (firstChar === '{') {
1224
+ if (prevChar === ':') {
1225
+ // This token represents the start of a JSX attribute value
1226
+ // that’s an expression (e.g. the `{b}` in `<div a={b} />`).
1227
+ // Our grammar represents the beginnings of expressions as `(`
1228
+ // tokens, so make this into a `(` token that displays as `{`.
1229
+ token = this.token('(', '{');
1230
+ this.jsxObjAttribute[this.jsxDepth] = false;
1231
+ // tag attribute name as JSX
1232
+ addTokenData(this.tokens[this.tokens.length - 3], {
1233
+ jsx: true,
1234
+ });
1235
+ } else {
1236
+ token = this.token('{', '{');
1237
+ this.jsxObjAttribute[this.jsxDepth] = true;
1238
+ }
1239
+ this.ends.push({
1240
+ tag: '}',
1241
+ origin: token,
1242
+ });
1243
+ return 1;
1244
+ } else if (firstChar === '>') {
1245
+ // end of opening tag
1246
+ ({
1247
+ // Ignore terminators inside a tag.
1248
+ origin: openingTagToken,
1249
+ } = this.pair('/>')); // As if the current tag was self-closing.
1250
+ this.token(']', ']', {
1251
+ generated: true,
1252
+ data: {
1253
+ closingBracketToken: this.makeToken('>', '>'),
1254
+ },
1255
+ });
1256
+ this.token(',', 'JSX_COMMA', {
1257
+ generated: true,
1258
+ });
1259
+ ({ tokens, index: end } = this.matchWithInterpolations(INSIDE_JSX, '>', '</', JSX_INTERPOLATION));
1260
+ this.mergeInterpolationTokens(
1261
+ tokens,
1262
+ {
1263
+ endOffset: end,
1264
+ jsx: true,
1265
+ },
1266
+ (value) => {
1267
+ return this.validateUnicodeCodePointEscapes(value, {
1268
+ delimiter: '>',
1269
+ });
1270
+ },
1271
+ );
1272
+ match = JSX_IDENTIFIER.exec(this.chunk.slice(end)) || JSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(end));
1273
+ if (
1274
+ !match ||
1275
+ match[1] !==
1276
+ `${jsxTag.name}${(function () {
1277
+ var k, len1, ref1, results;
1278
+ ref1 = jsxTag.properties;
1279
+ results = [];
1280
+ for (k = 0, len1 = ref1.length; k < len1; k++) {
1281
+ property = ref1[k];
1282
+ results.push(`.${property}`);
1283
+ }
1284
+ return results;
1285
+ })().join('')}`
1286
+ ) {
1287
+ this.error(`expected corresponding JSX closing tag for ${jsxTag.name}`, jsxTag.origin.data.tagNameToken[2]);
1288
+ }
1289
+ [, fullTagName] = match;
1290
+ afterTag = end + fullTagName.length;
1291
+ if (this.chunk[afterTag] !== '>') {
1292
+ this.error('missing closing > after tag name', {
1293
+ offset: afterTag,
1294
+ length: 1,
1295
+ });
1296
+ }
1297
+ // -2/+2 for the opening `</` and +1 for the closing `>`.
1298
+ endToken = this.token('CALL_END', ')', {
1299
+ offset: end - 2,
1300
+ length: fullTagName.length + 3,
1301
+ generated: true,
1302
+ data: {
1303
+ closingTagOpeningBracketToken: this.makeToken('<', '<', {
1304
+ offset: end - 2,
1305
+ }),
1306
+ closingTagSlashToken: this.makeToken('/', '/', {
1307
+ offset: end - 1,
1308
+ }),
1309
+ // TODO: individual tokens for complex tag name? eg < / A . B >
1310
+ closingTagNameToken: this.makeToken('IDENTIFIER', fullTagName, {
1311
+ offset: end,
1312
+ }),
1313
+ closingTagClosingBracketToken: this.makeToken('>', '>', {
1314
+ offset: end + fullTagName.length,
1315
+ }),
1316
+ },
1317
+ });
1318
+ // make the closing tag location data more easily accessible to the grammar
1319
+ addTokenData(openingTagToken, endToken.data);
1320
+ this.jsxDepth--;
1321
+ return afterTag + 1;
1322
+ } else {
1323
+ return 0;
1324
+ }
1325
+ } else if (this.atJSXTag(1)) {
1326
+ if (firstChar === '}') {
1327
+ this.pair(firstChar);
1328
+ if (this.jsxObjAttribute[this.jsxDepth]) {
1329
+ this.token('}', '}');
1330
+ this.jsxObjAttribute[this.jsxDepth] = false;
1331
+ } else {
1332
+ this.token(')', '}');
1333
+ }
1334
+ this.token(',', ',', {
1335
+ generated: true,
1336
+ });
1337
+ return 1;
1338
+ } else {
1339
+ return 0;
1340
+ }
1341
+ } else {
1342
+ return 0;
1343
+ }
1344
+ }
1345
+
1346
+ atJSXTag(depth = 0) {
1347
+ var i, last, ref;
1348
+ if (this.jsxDepth === 0) {
1349
+ return false;
1350
+ }
1351
+ i = this.ends.length - 1;
1352
+ while (((ref = this.ends[i]) != null ? ref.tag : void 0) === 'OUTDENT' || depth-- > 0) {
1353
+ // Ignore indents.
1354
+ i--;
1355
+ }
1356
+ last = this.ends[i];
1357
+ return (last != null ? last.tag : void 0) === '/>' && last;
1358
+ }
1359
+
1360
+ // We treat all other single characters as a token. E.g.: `( ) , . !`
1361
+ // Multi-character operators are also literal tokens, so that Jison can assign
1362
+ // the proper order of operations. There are some symbols that we tag specially
1363
+ // here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
1364
+ // parentheses that indicate a method call from regular parentheses, and so on.
1365
+ literalToken() {
1366
+ var match, message, origin, prev, ref, ref1, ref2, ref3, ref4, ref5, skipToken, tag, token, value;
1367
+ if ((match = OPERATOR.exec(this.chunk))) {
1368
+ [value] = match;
1369
+ if (CODE.test(value)) {
1370
+ this.tagParameters();
1371
+ }
1372
+ } else {
1373
+ value = this.chunk.charAt(0);
1374
+ }
1375
+ tag = value;
1376
+ prev = this.prev();
1377
+ if (prev && indexOf.call(['=', ...COMPOUND_ASSIGN], value) >= 0) {
1378
+ skipToken = false;
1379
+ if (value === '=' && ((ref = prev[1]) === '||' || ref === '&&') && !prev.spaced) {
1380
+ prev[0] = 'COMPOUND_ASSIGN';
1381
+ prev[1] += '=';
1382
+ if ((ref1 = prev.data) != null ? ref1.original : void 0) {
1383
+ prev.data.original += '=';
1384
+ }
1385
+ prev[2].range = [prev[2].range[0], prev[2].range[1] + 1];
1386
+ prev[2].last_column += 1;
1387
+ prev[2].last_column_exclusive += 1;
1388
+ prev = this.tokens[this.tokens.length - 2];
1389
+ skipToken = true;
1390
+ }
1391
+ if (prev && prev[0] !== 'PROPERTY') {
1392
+ origin = (ref2 = prev.origin) != null ? ref2 : prev;
1393
+ message = isUnassignable(prev[1], origin[1]);
1394
+ if (message) {
1395
+ this.error(message, origin[2]);
1396
+ }
1397
+ }
1398
+ if (skipToken) {
1399
+ return value.length;
1400
+ }
1401
+ }
1402
+ if (value === '(' && (prev != null ? prev[0] : void 0) === 'IMPORT') {
1403
+ prev[0] = 'DYNAMIC_IMPORT';
1404
+ }
1405
+ if (value === '{' && this.seenImport) {
1406
+ this.importSpecifierList = true;
1407
+ } else if (this.importSpecifierList && value === '}') {
1408
+ this.importSpecifierList = false;
1409
+ } else if (value === '{' && (prev != null ? prev[0] : void 0) === 'EXPORT') {
1410
+ this.exportSpecifierList = true;
1411
+ } else if (this.exportSpecifierList && value === '}') {
1412
+ this.exportSpecifierList = false;
1413
+ }
1414
+ if (value === ';') {
1415
+ if (((ref3 = prev != null ? prev[0] : void 0), indexOf.call(['=', ...UNFINISHED], ref3) >= 0)) {
1416
+ this.error('unexpected ;');
1417
+ }
1418
+ this.seenFor = this.seenImport = this.seenExport = false;
1419
+ tag = 'TERMINATOR';
1420
+ } else if (value === '*' && (prev != null ? prev[0] : void 0) === 'EXPORT') {
1421
+ tag = 'EXPORT_ALL';
1422
+ } else if (indexOf.call(MATH, value) >= 0) {
1423
+ tag = 'MATH';
1424
+ } else if (indexOf.call(COMPARE, value) >= 0) {
1425
+ tag = 'COMPARE';
1426
+ } else if (indexOf.call(COMPOUND_ASSIGN, value) >= 0) {
1427
+ tag = 'COMPOUND_ASSIGN';
1428
+ } else if (indexOf.call(UNARY, value) >= 0) {
1429
+ tag = 'UNARY';
1430
+ } else if (indexOf.call(UNARY_MATH, value) >= 0) {
1431
+ tag = 'UNARY_MATH';
1432
+ } else if (indexOf.call(SHIFT, value) >= 0) {
1433
+ tag = 'SHIFT';
1434
+ } else if (value === '?' && (prev != null ? prev.spaced : void 0)) {
1435
+ tag = 'BIN?';
1436
+ } else if (prev) {
1437
+ if (value === '(' && !prev.spaced && ((ref4 = prev[0]), indexOf.call(CALLABLE, ref4) >= 0)) {
1438
+ if (prev[0] === '?') {
1439
+ prev[0] = 'FUNC_EXIST';
1440
+ }
1441
+ tag = 'CALL_START';
1442
+ } else if (value === '[' && ((((ref5 = prev[0]), indexOf.call(INDEXABLE, ref5) >= 0) && !prev.spaced) || prev[0] === '::')) {
1443
+ // `.prototype` can’t be a method you can call.
1444
+ tag = 'INDEX_START';
1445
+ switch (prev[0]) {
1446
+ case '?':
1447
+ prev[0] = 'INDEX_SOAK';
1448
+ }
1449
+ }
1450
+ }
1451
+ token = this.makeToken(tag, value);
1452
+ switch (value) {
1453
+ case '(':
1454
+ case '{':
1455
+ case '[':
1456
+ this.ends.push({
1457
+ tag: INVERSES[value],
1458
+ origin: token,
1459
+ });
1460
+ break;
1461
+ case ')':
1462
+ case '}':
1463
+ case ']':
1464
+ this.pair(value);
1465
+ }
1466
+ this.tokens.push(this.makeToken(tag, value));
1467
+ return value.length;
1468
+ }
1469
+
1470
+ // Token Manipulators
1471
+ // ------------------
1472
+
1473
+ // A source of ambiguity in our grammar used to be parameter lists in function
1474
+ // definitions versus argument lists in function calls. Walk backwards, tagging
1475
+ // parameters specially in order to make things easier for the parser.
1476
+ tagParameters() {
1477
+ var i, paramEndToken, stack, tok, tokens;
1478
+ if (this.tag() !== ')') {
1479
+ return this.tagDoIife();
1480
+ }
1481
+ stack = [];
1482
+ ({ tokens } = this);
1483
+ i = tokens.length;
1484
+ paramEndToken = tokens[--i];
1485
+ paramEndToken[0] = 'PARAM_END';
1486
+ while ((tok = tokens[--i])) {
1487
+ switch (tok[0]) {
1488
+ case ')':
1489
+ stack.push(tok);
1490
+ break;
1491
+ case '(':
1492
+ case 'CALL_START':
1493
+ if (stack.length) {
1494
+ stack.pop();
1495
+ } else if (tok[0] === '(') {
1496
+ tok[0] = 'PARAM_START';
1497
+ return this.tagDoIife(i - 1);
1498
+ } else {
1499
+ paramEndToken[0] = 'CALL_END';
1500
+ return this;
1501
+ }
1502
+ }
1503
+ }
1504
+ return this;
1505
+ }
1506
+
1507
+ // Tag `do` followed by a function differently than `do` followed by eg an
1508
+ // identifier to allow for different grammar precedence
1509
+ tagDoIife(tokenIndex) {
1510
+ var tok;
1511
+ tok = this.tokens[tokenIndex != null ? tokenIndex : this.tokens.length - 1];
1512
+ if ((tok != null ? tok[0] : void 0) !== 'DO') {
1513
+ return this;
1514
+ }
1515
+ tok[0] = 'DO_IIFE';
1516
+ return this;
1517
+ }
1518
+
1519
+ // Close up all remaining open blocks at the end of the file.
1520
+ closeIndentation() {
1521
+ return this.outdentToken({
1522
+ moveOut: this.indent,
1523
+ indentSize: 0,
1524
+ });
1525
+ }
1526
+
1527
+ // Match the contents of a delimited token and expand variables and expressions
1528
+ // inside it using Ruby-like notation for substitution of arbitrary
1529
+ // expressions.
1530
+
1531
+ // "Hello #{name.capitalize()}."
1532
+
1533
+ // If it encounters an interpolation, this method will recursively create a new
1534
+ // Lexer and tokenize until the `{` of `#{` is balanced with a `}`.
1535
+
1536
+ // - `regex` matches the contents of a token (but not `delimiter`, and not
1537
+ // `#{` if interpolations are desired).
1538
+ // - `delimiter` is the delimiter of the token. Examples are `'`, `"`, `'''`,
1539
+ // `"""` and `///`.
1540
+ // - `closingDelimiter` is different from `delimiter` only in JSX
1541
+ // - `interpolators` matches the start of an interpolation, for JSX it's both
1542
+ // `{` and `<` (i.e. nested JSX tag)
1543
+
1544
+ // This method allows us to have strings within interpolations within strings,
1545
+ // ad infinitum.
1546
+ matchWithInterpolations(regex, delimiter, closingDelimiter = delimiter, interpolators = /^#\{/) {
1547
+ var braceInterpolator,
1548
+ close,
1549
+ column,
1550
+ index,
1551
+ interpolationOffset,
1552
+ interpolator,
1553
+ line,
1554
+ match,
1555
+ nested,
1556
+ offset,
1557
+ offsetInChunk,
1558
+ open,
1559
+ ref,
1560
+ ref1,
1561
+ rest,
1562
+ str,
1563
+ strPart,
1564
+ tokens;
1565
+ tokens = [];
1566
+ offsetInChunk = delimiter.length;
1567
+ if (this.chunk.slice(0, offsetInChunk) !== delimiter) {
1568
+ return null;
1569
+ }
1570
+ str = this.chunk.slice(offsetInChunk);
1571
+ while (true) {
1572
+ [strPart] = regex.exec(str);
1573
+ this.validateEscapes(strPart, {
1574
+ isRegex: delimiter.charAt(0) === '/',
1575
+ offsetInChunk,
1576
+ });
1577
+ // Push a fake `'NEOSTRING'` token, which will get turned into a real string later.
1578
+ tokens.push(
1579
+ this.makeToken('NEOSTRING', strPart, {
1580
+ offset: offsetInChunk,
1581
+ }),
1582
+ );
1583
+ str = str.slice(strPart.length);
1584
+ offsetInChunk += strPart.length;
1585
+ if (!(match = interpolators.exec(str))) {
1586
+ break;
1587
+ }
1588
+ [interpolator] = match;
1589
+ // To remove the `#` in `#{`.
1590
+ interpolationOffset = interpolator.length - 1;
1591
+ [line, column, offset] = this.getLineAndColumnFromChunk(offsetInChunk + interpolationOffset);
1592
+ rest = str.slice(interpolationOffset);
1593
+ ({ tokens: nested, index } = new Lexer().tokenize(rest, {
1594
+ line,
1595
+ column,
1596
+ offset,
1597
+ untilBalanced: true,
1598
+ locationDataCompensations: this.locationDataCompensations,
1599
+ }));
1600
+ // Account for the `#` in `#{`.
1601
+ index += interpolationOffset;
1602
+ braceInterpolator = str[index - 1] === '}';
1603
+ if (braceInterpolator) {
1604
+ // Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
1605
+ // parentheses will be removed later.
1606
+ ([open] = nested), ([close] = slice.call(nested, -1));
1607
+ open[0] = 'INTERPOLATION_START';
1608
+ open[1] = '(';
1609
+ open[2].first_column -= interpolationOffset;
1610
+ open[2].range = [open[2].range[0] - interpolationOffset, open[2].range[1]];
1611
+ close[0] = 'INTERPOLATION_END';
1612
+ close[1] = ')';
1613
+ close.origin = ['', 'end of interpolation', close[2]];
1614
+ }
1615
+ if (((ref = nested[1]) != null ? ref[0] : void 0) === 'TERMINATOR') {
1616
+ // Remove leading `'TERMINATOR'` (if any).
1617
+ nested.splice(1, 1);
1618
+ }
1619
+ if (((ref1 = nested[nested.length - 3]) != null ? ref1[0] : void 0) === 'INDENT' && nested[nested.length - 2][0] === 'OUTDENT') {
1620
+ // Remove trailing `'INDENT'/'OUTDENT'` pair (if any).
1621
+ nested.splice(-3, 2);
1622
+ }
1623
+ if (!braceInterpolator) {
1624
+ // We are not using `{` and `}`, so wrap the interpolated tokens instead.
1625
+ open = this.makeToken('INTERPOLATION_START', '(', {
1626
+ offset: offsetInChunk,
1627
+ length: 0,
1628
+ generated: true,
1629
+ });
1630
+ close = this.makeToken('INTERPOLATION_END', ')', {
1631
+ offset: offsetInChunk + index,
1632
+ length: 0,
1633
+ generated: true,
1634
+ });
1635
+ nested = [open, ...nested, close];
1636
+ }
1637
+ // Push a fake `'TOKENS'` token, which will get turned into real tokens later.
1638
+ tokens.push(['TOKENS', nested]);
1639
+ str = str.slice(index);
1640
+ offsetInChunk += index;
1641
+ }
1642
+ if (str.slice(0, closingDelimiter.length) !== closingDelimiter) {
1643
+ this.error(`missing ${closingDelimiter}`, {
1644
+ length: delimiter.length,
1645
+ });
1646
+ }
1647
+ return {
1648
+ tokens,
1649
+ index: offsetInChunk + closingDelimiter.length,
1650
+ };
1651
+ }
1652
+
1653
+ // Merge the array `tokens` of the fake token types `'TOKENS'` and `'NEOSTRING'`
1654
+ // (as returned by `matchWithInterpolations`) into the token stream. The value
1655
+ // of `'NEOSTRING'`s are converted using `fn` and turned into strings using
1656
+ // `options` first.
1657
+ mergeInterpolationTokens(tokens, options, fn) {
1658
+ var $,
1659
+ converted,
1660
+ double,
1661
+ endOffset,
1662
+ firstIndex,
1663
+ heregex,
1664
+ i,
1665
+ indent,
1666
+ j,
1667
+ jsx,
1668
+ k,
1669
+ lastToken,
1670
+ len,
1671
+ len1,
1672
+ locationToken,
1673
+ lparen,
1674
+ placeholderToken,
1675
+ quote,
1676
+ ref,
1677
+ ref1,
1678
+ rparen,
1679
+ tag,
1680
+ token,
1681
+ tokensToPush,
1682
+ val,
1683
+ value;
1684
+ ({ quote, indent, double, heregex, endOffset, jsx } = options);
1685
+ if (tokens.length > 1) {
1686
+ lparen = this.token('STRING_START', '(', {
1687
+ length: (ref = quote != null ? quote.length : void 0) != null ? ref : 0,
1688
+ data: { quote },
1689
+ generated: !(quote != null ? quote.length : void 0),
1690
+ });
1691
+ }
1692
+ firstIndex = this.tokens.length;
1693
+ $ = tokens.length - 1;
1694
+ for (i = j = 0, len = tokens.length; j < len; i = ++j) {
1695
+ token = tokens[i];
1696
+ [tag, value] = token;
1697
+ switch (tag) {
1698
+ case 'TOKENS':
1699
+ // There are comments (and nothing else) in this interpolation.
1700
+ if (value.length === 2 && (value[0].comments || value[1].comments)) {
1701
+ placeholderToken = this.makeToken('JS', '', {
1702
+ generated: true,
1703
+ });
1704
+ // Use the same location data as the first parenthesis.
1705
+ placeholderToken[2] = value[0][2];
1706
+ for (k = 0, len1 = value.length; k < len1; k++) {
1707
+ val = value[k];
1708
+ if (!val.comments) {
1709
+ continue;
1710
+ }
1711
+ if (placeholderToken.comments == null) {
1712
+ placeholderToken.comments = [];
1713
+ }
1714
+ placeholderToken.comments.push(...val.comments);
1715
+ }
1716
+ value.splice(1, 0, placeholderToken);
1717
+ }
1718
+ // Push all the tokens in the fake `'TOKENS'` token. These already have
1719
+ // sane location data.
1720
+ locationToken = value[0];
1721
+ tokensToPush = value;
1722
+ break;
1723
+ case 'NEOSTRING':
1724
+ // Convert `'NEOSTRING'` into `'STRING'`.
1725
+ converted = fn.call(this, token[1], i);
1726
+ if (i === 0) {
1727
+ addTokenData(token, {
1728
+ initialChunk: true,
1729
+ });
1730
+ }
1731
+ if (i === $) {
1732
+ addTokenData(token, {
1733
+ finalChunk: true,
1734
+ });
1735
+ }
1736
+ addTokenData(token, { indent, quote, double });
1737
+ if (heregex) {
1738
+ addTokenData(token, { heregex });
1739
+ }
1740
+ if (jsx) {
1741
+ addTokenData(token, { jsx });
1742
+ }
1743
+ token[0] = 'STRING';
1744
+ token[1] = '"' + converted + '"';
1745
+ if (tokens.length === 1 && quote != null) {
1746
+ token[2].first_column -= quote.length;
1747
+ if (token[1].substr(-2, 1) === '\n') {
1748
+ token[2].last_line += 1;
1749
+ token[2].last_column = quote.length - 1;
1750
+ } else {
1751
+ token[2].last_column += quote.length;
1752
+ if (token[1].length === 2) {
1753
+ token[2].last_column -= 1;
1754
+ }
1755
+ }
1756
+ token[2].last_column_exclusive += quote.length;
1757
+ token[2].range = [token[2].range[0] - quote.length, token[2].range[1] + quote.length];
1758
+ }
1759
+ locationToken = token;
1760
+ tokensToPush = [token];
1761
+ }
1762
+ this.tokens.push(...tokensToPush);
1763
+ }
1764
+ if (lparen) {
1765
+ [lastToken] = slice.call(tokens, -1);
1766
+ lparen.origin = [
1767
+ 'STRING',
1768
+ null,
1769
+ {
1770
+ first_line: lparen[2].first_line,
1771
+ first_column: lparen[2].first_column,
1772
+ last_line: lastToken[2].last_line,
1773
+ last_column: lastToken[2].last_column,
1774
+ last_line_exclusive: lastToken[2].last_line_exclusive,
1775
+ last_column_exclusive: lastToken[2].last_column_exclusive,
1776
+ range: [lparen[2].range[0], lastToken[2].range[1]],
1777
+ },
1778
+ ];
1779
+ if (!(quote != null ? quote.length : void 0)) {
1780
+ lparen[2] = lparen.origin[2];
1781
+ }
1782
+ return (rparen = this.token('STRING_END', ')', {
1783
+ offset: endOffset - (quote != null ? quote : '').length,
1784
+ length: (ref1 = quote != null ? quote.length : void 0) != null ? ref1 : 0,
1785
+ generated: !(quote != null ? quote.length : void 0),
1786
+ }));
1787
+ }
1788
+ }
1789
+
1790
+ // Pairs up a closing token, ensuring that all listed pairs of tokens are
1791
+ // correctly balanced throughout the course of the token stream.
1792
+ pair(tag) {
1793
+ var lastIndent, prev, ref, ref1, wanted;
1794
+ (ref = this.ends), ([prev] = slice.call(ref, -1));
1795
+ if (tag !== (wanted = prev != null ? prev.tag : void 0)) {
1796
+ if ('OUTDENT' !== wanted) {
1797
+ this.error(`unmatched ${tag}`);
1798
+ }
1799
+ // Auto-close `INDENT` to support syntax like this:
1800
+
1801
+ // el.click((event) ->
1802
+ // el.hide())
1803
+
1804
+ (ref1 = this.indents), ([lastIndent] = slice.call(ref1, -1));
1805
+ this.outdentToken({
1806
+ moveOut: lastIndent,
1807
+ noNewlines: true,
1808
+ });
1809
+ return this.pair(tag);
1810
+ }
1811
+ return this.ends.pop();
1812
+ }
1813
+
1814
+ // Helpers
1815
+ // -------
1816
+
1817
+ // Compensate for the things we strip out initially (e.g. carriage returns)
1818
+ // so that location data stays accurate with respect to the original source file.
1819
+ getLocationDataCompensation(start, end) {
1820
+ var compensation, current, initialEnd, totalCompensation;
1821
+ totalCompensation = 0;
1822
+ initialEnd = end;
1823
+ current = start;
1824
+ while (current <= end) {
1825
+ if (current === end && start !== initialEnd) {
1826
+ break;
1827
+ }
1828
+ compensation = this.locationDataCompensations[current];
1829
+ if (compensation != null) {
1830
+ totalCompensation += compensation;
1831
+ end += compensation;
1832
+ }
1833
+ current++;
1834
+ }
1835
+ return totalCompensation;
1836
+ }
1837
+
1838
+ // Returns the line and column number from an offset into the current chunk.
1839
+
1840
+ // `offset` is a number of characters into `@chunk`.
1841
+ getLineAndColumnFromChunk(offset) {
1842
+ var column, columnCompensation, compensation, lastLine, lineCount, previousLinesCompensation, ref, string;
1843
+ compensation = this.getLocationDataCompensation(this.chunkOffset, this.chunkOffset + offset);
1844
+ if (offset === 0) {
1845
+ return [this.chunkLine, this.chunkColumn + compensation, this.chunkOffset + compensation];
1846
+ }
1847
+ if (offset >= this.chunk.length) {
1848
+ string = this.chunk;
1849
+ } else {
1850
+ string = this.chunk.slice(0, +(offset - 1) + 1 || 9e9);
1851
+ }
1852
+ lineCount = count(string, '\n');
1853
+ column = this.chunkColumn;
1854
+ if (lineCount > 0) {
1855
+ (ref = string.split('\n')), ([lastLine] = slice.call(ref, -1));
1856
+ column = lastLine.length;
1857
+ previousLinesCompensation = this.getLocationDataCompensation(this.chunkOffset, this.chunkOffset + offset - column);
1858
+ if (previousLinesCompensation < 0) {
1859
+ // Don't recompensate for initially inserted newline.
1860
+ previousLinesCompensation = 0;
1861
+ }
1862
+ columnCompensation = this.getLocationDataCompensation(
1863
+ this.chunkOffset + offset + previousLinesCompensation - column,
1864
+ this.chunkOffset + offset + previousLinesCompensation,
1865
+ );
1866
+ } else {
1867
+ column += string.length;
1868
+ columnCompensation = compensation;
1869
+ }
1870
+ return [this.chunkLine + lineCount, column + columnCompensation, this.chunkOffset + offset + compensation];
1871
+ }
1872
+
1873
+ makeLocationData({ offsetInChunk, length }) {
1874
+ var endOffset, lastCharacter, locationData;
1875
+ locationData = {
1876
+ range: [],
1877
+ };
1878
+ [locationData.first_line, locationData.first_column, locationData.range[0]] = this.getLineAndColumnFromChunk(offsetInChunk);
1879
+ // Use length - 1 for the final offset - we’re supplying the last_line and the last_column,
1880
+ // so if last_column == first_column, then we’re looking at a character of length 1.
1881
+ lastCharacter = length > 0 ? length - 1 : 0;
1882
+ [locationData.last_line, locationData.last_column, endOffset] = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter);
1883
+ [locationData.last_line_exclusive, locationData.last_column_exclusive] = this.getLineAndColumnFromChunk(
1884
+ offsetInChunk + lastCharacter + (length > 0 ? 1 : 0),
1885
+ );
1886
+ locationData.range[1] = length > 0 ? endOffset + 1 : endOffset;
1887
+ return locationData;
1888
+ }
1889
+
1890
+ // Same as `token`, except this just returns the token without adding it
1891
+ // to the results.
1892
+ makeToken(tag, value, { offset: offsetInChunk = 0, length = value.length, origin, generated, indentSize } = {}) {
1893
+ var token;
1894
+ token = [tag, value, this.makeLocationData({ offsetInChunk, length })];
1895
+ if (origin) {
1896
+ token.origin = origin;
1897
+ }
1898
+ if (generated) {
1899
+ token.generated = true;
1900
+ }
1901
+ if (indentSize != null) {
1902
+ token.indentSize = indentSize;
1903
+ }
1904
+ return token;
1905
+ }
1906
+
1907
+ // Add a token to the results.
1908
+ // `offset` is the offset into the current `@chunk` where the token starts.
1909
+ // `length` is the length of the token in the `@chunk`, after the offset. If
1910
+ // not specified, the length of `value` will be used.
1911
+
1912
+ // Returns the new token.
1913
+ token(tag, value, { offset, length, origin, data, generated, indentSize } = {}) {
1914
+ var token;
1915
+ token = this.makeToken(tag, value, { offset, length, origin, generated, indentSize });
1916
+ if (data) {
1917
+ addTokenData(token, data);
1918
+ }
1919
+ this.tokens.push(token);
1920
+ return token;
1921
+ }
1922
+
1923
+ // Peek at the last tag in the token stream.
1924
+ tag() {
1925
+ var ref, token;
1926
+ (ref = this.tokens), ([token] = slice.call(ref, -1));
1927
+ return token != null ? token[0] : void 0;
1928
+ }
1929
+
1930
+ // Peek at the last value in the token stream.
1931
+ value(useOrigin = false) {
1932
+ var ref, token;
1933
+ (ref = this.tokens), ([token] = slice.call(ref, -1));
1934
+ if (useOrigin && (token != null ? token.origin : void 0) != null) {
1935
+ return token.origin[1];
1936
+ } else {
1937
+ return token != null ? token[1] : void 0;
1938
+ }
1939
+ }
1940
+
1941
+ // Get the previous token in the token stream.
1942
+ prev() {
1943
+ return this.tokens[this.tokens.length - 1];
1944
+ }
1945
+
1946
+ // Are we in the midst of an unfinished expression?
1947
+ unfinished() {
1948
+ var ref;
1949
+ return LINE_CONTINUER.test(this.chunk) || ((ref = this.tag()), indexOf.call(UNFINISHED, ref) >= 0);
1950
+ }
1951
+
1952
+ validateUnicodeCodePointEscapes(str, options) {
1953
+ return replaceUnicodeCodePointEscapes(str, merge(options, { error: this.error }));
1954
+ }
1955
+
1956
+ // Validates escapes in strings and regexes.
1957
+ validateEscapes(str, options = {}) {
1958
+ var before, hex, invalidEscape, invalidEscapeRegex, match, message, octal, ref, unicode, unicodeCodePoint;
1959
+ invalidEscapeRegex = options.isRegex ? REGEX_INVALID_ESCAPE : STRING_INVALID_ESCAPE;
1960
+ match = invalidEscapeRegex.exec(str);
1961
+ if (!match) {
1962
+ return;
1963
+ }
1964
+ match[0], (before = match[1]), (octal = match[2]), (hex = match[3]), (unicodeCodePoint = match[4]), (unicode = match[5]);
1965
+ message = octal ? 'octal escape sequences are not allowed' : 'invalid escape sequence';
1966
+ invalidEscape = `\\${octal || hex || unicodeCodePoint || unicode}`;
1967
+ return this.error(`${message} ${invalidEscape}`, {
1968
+ offset: ((ref = options.offsetInChunk) != null ? ref : 0) + match.index + before.length,
1969
+ length: invalidEscape.length,
1970
+ });
1971
+ }
1972
+
1973
+ suppressSemicolons() {
1974
+ var ref, ref1, results;
1975
+ results = [];
1976
+ while (this.value() === ';') {
1977
+ this.tokens.pop();
1978
+ if (((ref = (ref1 = this.prev()) != null ? ref1[0] : void 0), indexOf.call(['=', ...UNFINISHED], ref) >= 0)) {
1979
+ results.push(this.error('unexpected ;'));
1980
+ } else {
1981
+ results.push(void 0);
1982
+ }
1983
+ }
1984
+ return results;
1985
+ }
1986
+
1987
+ error(message, options = {}) {
1988
+ var first_column, first_line, location, ref, ref1;
1989
+ location =
1990
+ 'first_line' in options
1991
+ ? options
1992
+ : (([first_line, first_column] = this.getLineAndColumnFromChunk((ref = options.offset) != null ? ref : 0)),
1993
+ {
1994
+ first_line,
1995
+ first_column,
1996
+ last_column: first_column + ((ref1 = options.length) != null ? ref1 : 1) - 1,
1997
+ });
1998
+ return throwSyntaxError(message, location);
1999
+ }
2000
+ };
2001
+
2002
+ // Helper functions
2003
+ // ----------------
2004
+ isUnassignable = function (name, displayName = name) {
2005
+ switch (false) {
2006
+ case indexOf.call([...JS_KEYWORDS, ...COFFEE_KEYWORDS], name) < 0:
2007
+ return `keyword '${displayName}' can't be assigned`;
2008
+ case indexOf.call(STRICT_PROSCRIBED, name) < 0:
2009
+ return `'${displayName}' can't be assigned`;
2010
+ case indexOf.call(RESERVED, name) < 0:
2011
+ return `reserved word '${displayName}' can't be assigned`;
2012
+ default:
2013
+ return false;
2014
+ }
2015
+ };
2016
+
2017
+ exports.isUnassignable = isUnassignable;
2018
+
2019
+ // `from` isn’t a CoffeeScript keyword, but it behaves like one in `import` and
2020
+ // `export` statements (handled above) and in the declaration line of a `for`
2021
+ // loop. Try to detect when `from` is a variable identifier and when it is this
2022
+ // “sometimes” keyword.
2023
+ isForFrom = function (prev) {
2024
+ var ref;
2025
+ // `for i from iterable`
2026
+ if (prev[0] === 'IDENTIFIER') {
2027
+ return true;
2028
+ // `for from…`
2029
+ } else if (prev[0] === 'FOR') {
2030
+ return false;
2031
+ // `for {from}…`, `for [from]…`, `for {a, from}…`, `for {a: from}…`
2032
+ } else if ((ref = prev[1]) === '{' || ref === '[' || ref === ',' || ref === ':') {
2033
+ return false;
2034
+ } else {
2035
+ return true;
2036
+ }
2037
+ };
2038
+
2039
+ addTokenData = function (token, data) {
2040
+ return Object.assign(token.data != null ? token.data : (token.data = {}), data);
2041
+ };
2042
+
2043
+ // Constants
2044
+ // ---------
2045
+
2046
+ // Keywords that CoffeeScript shares in common with JavaScript.
2047
+ JS_KEYWORDS = [
2048
+ 'true',
2049
+ 'false',
2050
+ 'null',
2051
+ 'this',
2052
+ 'new',
2053
+ 'delete',
2054
+ 'typeof',
2055
+ 'in',
2056
+ 'instanceof',
2057
+ 'return',
2058
+ 'throw',
2059
+ 'break',
2060
+ 'continue',
2061
+ 'debugger',
2062
+ 'yield',
2063
+ 'await',
2064
+ 'if',
2065
+ 'else',
2066
+ 'switch',
2067
+ 'for',
2068
+ 'while',
2069
+ 'do',
2070
+ 'try',
2071
+ 'catch',
2072
+ 'finally',
2073
+ 'class',
2074
+ 'extends',
2075
+ 'super',
2076
+ 'import',
2077
+ 'export',
2078
+ 'default',
2079
+ ];
2080
+
2081
+ // CoffeeScript-only keywords.
2082
+ COFFEE_KEYWORDS = ['undefined', 'Infinity', 'NaN', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
2083
+
2084
+ COFFEE_ALIAS_MAP = {
2085
+ and: '&&',
2086
+ or: '||',
2087
+ is: '==',
2088
+ isnt: '!=',
2089
+ not: '!',
2090
+ yes: 'true',
2091
+ no: 'false',
2092
+ on: 'true',
2093
+ off: 'false',
2094
+ };
2095
+
2096
+ COFFEE_ALIASES = (function () {
2097
+ var results;
2098
+ results = [];
2099
+ for (key in COFFEE_ALIAS_MAP) {
2100
+ results.push(key);
2101
+ }
2102
+ return results;
2103
+ })();
2104
+
2105
+ COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat(COFFEE_ALIASES);
2106
+
2107
+ // The list of keywords that are reserved by JavaScript, but not used, or are
2108
+ // used by CoffeeScript internally. We throw an error when these are encountered,
2109
+ // to avoid having a JavaScript error at runtime.
2110
+ RESERVED = [
2111
+ 'case',
2112
+ 'function',
2113
+ 'var',
2114
+ 'void',
2115
+ 'with',
2116
+ 'const',
2117
+ 'let',
2118
+ 'enum',
2119
+ 'native',
2120
+ 'implements',
2121
+ 'interface',
2122
+ 'package',
2123
+ 'private',
2124
+ 'protected',
2125
+ 'public',
2126
+ 'static',
2127
+ ];
2128
+
2129
+ STRICT_PROSCRIBED = ['arguments', 'eval'];
2130
+
2131
+ // The superset of both JavaScript keywords and reserved words, none of which may
2132
+ // be used as identifiers or properties.
2133
+ exports.JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
2134
+
2135
+ // The character code of the nasty Microsoft madness otherwise known as the BOM.
2136
+ BOM = 65279;
2137
+
2138
+ // Token matching regexes.
2139
+ IDENTIFIER = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)([^\n\S]*:(?!:))?/; // Is this a property name?
2140
+
2141
+ // Like `IDENTIFIER`, but includes `-`s
2142
+ JSX_IDENTIFIER_PART = /(?:(?!\s)[\-$\w\x7f-\uffff])+/.source;
2143
+
2144
+ // In https://facebook.github.io/jsx/ spec, JSXElementName can be
2145
+ // JSXIdentifier, JSXNamespacedName (JSXIdentifier : JSXIdentifier), or
2146
+ // JSXMemberExpression (two or more JSXIdentifier connected by `.`s).
2147
+ JSX_IDENTIFIER = RegExp(
2148
+ `^(?![\\d<])(${
2149
+ JSX_IDENTIFIER_PART // Must not start with `<`.
2150
+ // JSXNamespacedName
2151
+ // JSXMemberExpression
2152
+ }(?:\\s*:\\s*${JSX_IDENTIFIER_PART}|(?:\\s*\\.\\s*${JSX_IDENTIFIER_PART})+)?)`,
2153
+ );
2154
+
2155
+ // Fragment: <></>
2156
+ JSX_FRAGMENT_IDENTIFIER = /^()>/; // Ends immediately with `>`.
2157
+
2158
+ // In https://facebook.github.io/jsx/ spec, JSXAttributeName can be either
2159
+ // JSXIdentifier or JSXNamespacedName which is JSXIdentifier : JSXIdentifier
2160
+ JSX_ATTRIBUTE = RegExp(
2161
+ `^(?!\\d)(${
2162
+ JSX_IDENTIFIER_PART // JSXNamespacedName
2163
+ // Is this an attribute with a value?
2164
+ }(?:\\s*:\\s*${JSX_IDENTIFIER_PART})?)([^\\S]*=(?!=))?`,
2165
+ );
2166
+
2167
+ NUMBER =
2168
+ /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+(?:_\d+)*n|^(?:\d+(?:_\d+)*)?\.?\d+(?:_\d+)*(?:e[+-]?\d+(?:_\d+)*)?/i; // binary
2169
+ // octal
2170
+ // hex
2171
+ // decimal bigint
2172
+ // decimal
2173
+ // decimal without support for numeric literal separators for reference:
2174
+ // \d*\.?\d+ (?:e[+-]?\d+)?
2175
+
2176
+ OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
2177
+ // compound assign / compare
2178
+ // zero-fill right shift
2179
+ // doubles
2180
+ // logic / shift / power / floor division / modulo
2181
+ // soak access
2182
+ // range or splat
2183
+
2184
+ WHITESPACE = /^[^\n\S]+/;
2185
+
2186
+ COMMENT = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
2187
+
2188
+ CODE = /^[-=]>/;
2189
+
2190
+ MULTI_DENT = /^(?:\n[^\n\S]*)+/;
2191
+
2192
+ JSTOKEN = /^`(?!``)((?:[^`\\]|\\[\s\S])*)`/;
2193
+
2194
+ HERE_JSTOKEN = /^```((?:[^`\\]|\\[\s\S]|`(?!``))*)```/;
2195
+
2196
+ // String-matching-regexes.
2197
+ STRING_START = /^(?:'''|"""|'|")/;
2198
+
2199
+ STRING_SINGLE = /^(?:[^\\']|\\[\s\S])*/;
2200
+
2201
+ STRING_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|\#(?!\{))*/;
1808
2202
 
1809
- NUMBER = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+(?:_\d+)*n|^(?:\d+(?:_\d+)*)?\.?\d+(?:_\d+)*(?:e[+-]?\d+(?:_\d+)*)?/i; // binary
1810
- // octal
1811
- // hex
1812
- // decimal bigint
1813
- // decimal
1814
- // decimal without support for numeric literal separators for reference:
1815
- // \d*\.?\d+ (?:e[+-]?\d+)?
2203
+ HEREDOC_SINGLE = /^(?:[^\\']|\\[\s\S]|'(?!''))*/;
1816
2204
 
1817
- OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
1818
- // compound assign / compare
1819
- // zero-fill right shift
1820
- // doubles
1821
- // logic / shift / power / floor division / modulo
1822
- // soak access
1823
- // range or splat
2205
+ HEREDOC_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|"(?!"")|\#(?!\{))*/;
1824
2206
 
1825
- WHITESPACE = /^[^\n\S]+/;
2207
+ INSIDE_JSX = /^(?:[^\{<])*/; // Start of CoffeeScript interpolation. // Similar to `HEREDOC_DOUBLE` but there is no escaping.
2208
+ // Maybe JSX tag (`<` not allowed even if bare).
1826
2209
 
1827
- COMMENT = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
2210
+ JSX_INTERPOLATION = /^(?:\{|<(?!\/))/; // CoffeeScript interpolation.
2211
+ // JSX opening tag.
1828
2212
 
1829
- CODE = /^[-=]>/;
2213
+ HEREDOC_INDENT = /\n+([^\n\S]*)(?=\S)/g;
1830
2214
 
1831
- MULTI_DENT = /^(?:\n[^\n\S]*)+/;
2215
+ // Regex-matching-regexes.
2216
+ REGEX = /^\/(?!\/)((?:[^[\/\n\\]|\\[^\n]|\[(?:\\[^\n]|[^\]\n\\])*\])*)(\/)?/; // Every other thing.
2217
+ // Anything but newlines escaped.
2218
+ // Character class.
1832
2219
 
1833
- JSTOKEN = /^`(?!``)((?:[^`\\]|\\[\s\S])*)`/;
2220
+ REGEX_FLAGS = /^\w*/;
1834
2221
 
1835
- HERE_JSTOKEN = /^```((?:[^`\\]|\\[\s\S]|`(?!``))*)```/;
2222
+ VALID_FLAGS = /^(?!.*(.).*\1)[gimsuy]*$/;
1836
2223
 
1837
- // String-matching-regexes.
1838
- STRING_START = /^(?:'''|"""|'|")/;
2224
+ HEREGEX = /^(?:[^\\\/#\s]|\\[\s\S]|\/(?!\/\/)|\#(?!\{)|\s+(?:#(?!\{).*)?)*/; // Match any character, except those that need special handling below.
2225
+ // Match `\` followed by any character.
2226
+ // Match any `/` except `///`.
2227
+ // Match `#` which is not part of interpolation, e.g. `#{}`.
2228
+ // Comments consume everything until the end of the line, including `///`.
1839
2229
 
1840
- STRING_SINGLE = /^(?:[^\\']|\\[\s\S])*/;
2230
+ HEREGEX_COMMENT = /(\s+)(#(?!{).*)/gm;
1841
2231
 
1842
- STRING_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|\#(?!\{))*/;
2232
+ REGEX_ILLEGAL = /^(\/|\/{3}\s*)(\*)/;
1843
2233
 
1844
- HEREDOC_SINGLE = /^(?:[^\\']|\\[\s\S]|'(?!''))*/;
2234
+ POSSIBLY_DIVISION = /^\/=?\s/;
1845
2235
 
1846
- HEREDOC_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|"(?!"")|\#(?!\{))*/;
2236
+ // Other regexes.
2237
+ HERECOMMENT_ILLEGAL = /\*\//;
1847
2238
 
1848
- INSIDE_JSX = /^(?:[^\{<])*/; // Start of CoffeeScript interpolation. // Similar to `HEREDOC_DOUBLE` but there is no escaping.
1849
- // Maybe JSX tag (`<` not allowed even if bare).
2239
+ LINE_CONTINUER = /^\s*(?:,|\??\.(?![.\d])|\??::)/;
1850
2240
 
1851
- JSX_INTERPOLATION = /^(?:\{|<(?!\/))/; // CoffeeScript interpolation.
1852
- // JSX opening tag.
2241
+ STRING_INVALID_ESCAPE =
2242
+ /((?:^|[^\\])(?:\\\\)*)\\(?:(0\d|[1-7])|(x(?![\da-fA-F]{2}).{0,2})|(u\{(?![\da-fA-F]{1,}\})[^}]*\}?)|(u(?!\{|[\da-fA-F]{4}).{0,4}))/; // Make sure the escape isn’t escaped.
2243
+ // octal escape
2244
+ // hex escape
2245
+ // unicode code point escape
2246
+ // unicode escape
1853
2247
 
1854
- HEREDOC_INDENT = /\n+([^\n\S]*)(?=\S)/g;
2248
+ REGEX_INVALID_ESCAPE =
2249
+ /((?:^|[^\\])(?:\\\\)*)\\(?:(0\d)|(x(?![\da-fA-F]{2}).{0,2})|(u\{(?![\da-fA-F]{1,}\})[^}]*\}?)|(u(?!\{|[\da-fA-F]{4}).{0,4}))/; // Make sure the escape isn’t escaped.
2250
+ // octal escape
2251
+ // hex escape
2252
+ // unicode code point escape
2253
+ // unicode escape
1855
2254
 
1856
- // Regex-matching-regexes.
1857
- REGEX = /^\/(?!\/)((?:[^[\/\n\\]|\\[^\n]|\[(?:\\[^\n]|[^\]\n\\])*\])*)(\/)?/; // Every other thing.
1858
- // Anything but newlines escaped.
1859
- // Character class.
2255
+ TRAILING_SPACES = /\s+$/;
1860
2256
 
1861
- REGEX_FLAGS = /^\w*/;
2257
+ // Compound assignment tokens.
2258
+ COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '**=', '//=', '%%='];
1862
2259
 
1863
- VALID_FLAGS = /^(?!.*(.).*\1)[gimsuy]*$/;
2260
+ // Unary tokens.
2261
+ UNARY = ['NEW', 'TYPEOF', 'DELETE'];
1864
2262
 
1865
- HEREGEX = /^(?:[^\\\/#\s]|\\[\s\S]|\/(?!\/\/)|\#(?!\{)|\s+(?:#(?!\{).*)?)*/; // Match any character, except those that need special handling below.
1866
- // Match `\` followed by any character.
1867
- // Match any `/` except `///`.
1868
- // Match `#` which is not part of interpolation, e.g. `#{}`.
1869
- // Comments consume everything until the end of the line, including `///`.
2263
+ UNARY_MATH = ['!', '~'];
1870
2264
 
1871
- HEREGEX_COMMENT = /(\s+)(#(?!{).*)/gm;
2265
+ // Bit-shifting tokens.
2266
+ SHIFT = ['<<', '>>', '>>>'];
1872
2267
 
1873
- REGEX_ILLEGAL = /^(\/|\/{3}\s*)(\*)/;
2268
+ // Comparison tokens.
2269
+ COMPARE = ['==', '!=', '<', '>', '<=', '>='];
1874
2270
 
1875
- POSSIBLY_DIVISION = /^\/=?\s/;
2271
+ // Mathematical tokens.
2272
+ MATH = ['*', '/', '%', '//', '%%'];
1876
2273
 
1877
- // Other regexes.
1878
- HERECOMMENT_ILLEGAL = /\*\//;
2274
+ // Relational tokens that are negatable with `not` prefix.
2275
+ RELATION = ['IN', 'OF', 'INSTANCEOF'];
1879
2276
 
1880
- LINE_CONTINUER = /^\s*(?:,|\??\.(?![.\d])|\??::)/;
2277
+ // Boolean tokens.
2278
+ BOOL = ['TRUE', 'FALSE'];
1881
2279
 
1882
- STRING_INVALID_ESCAPE = /((?:^|[^\\])(?:\\\\)*)\\(?:(0\d|[1-7])|(x(?![\da-fA-F]{2}).{0,2})|(u\{(?![\da-fA-F]{1,}\})[^}]*\}?)|(u(?!\{|[\da-fA-F]{4}).{0,4}))/; // Make sure the escape isn’t escaped.
1883
- // octal escape
1884
- // hex escape
1885
- // unicode code point escape
1886
- // unicode escape
2280
+ // Tokens which could legitimately be invoked or indexed. An opening
2281
+ // parentheses or bracket following these tokens will be recorded as the start
2282
+ // of a function invocation or indexing operation.
2283
+ CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER', 'DYNAMIC_IMPORT'];
1887
2284
 
1888
- REGEX_INVALID_ESCAPE = /((?:^|[^\\])(?:\\\\)*)\\(?:(0\d)|(x(?![\da-fA-F]{2}).{0,2})|(u\{(?![\da-fA-F]{1,}\})[^}]*\}?)|(u(?!\{|[\da-fA-F]{4}).{0,4}))/; // Make sure the escape isn’t escaped.
1889
- // octal escape
1890
- // hex escape
1891
- // unicode code point escape
1892
- // unicode escape
2285
+ INDEXABLE = CALLABLE.concat([
2286
+ 'NUMBER',
2287
+ 'INFINITY',
2288
+ 'NAN',
2289
+ 'STRING',
2290
+ 'STRING_END',
2291
+ 'REGEX',
2292
+ 'REGEX_END',
2293
+ 'BOOL',
2294
+ 'NULL',
2295
+ 'UNDEFINED',
2296
+ '}',
2297
+ '::',
2298
+ ]);
1893
2299
 
1894
- TRAILING_SPACES = /\s+$/;
2300
+ // Tokens which can be the left-hand side of a less-than comparison, i.e. `a<b`.
2301
+ COMPARABLE_LEFT_SIDE = ['IDENTIFIER', ')', ']', 'NUMBER'];
1895
2302
 
1896
- // Compound assignment tokens.
1897
- COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '**=', '//=', '%%='];
2303
+ // Tokens which a regular expression will never immediately follow (except spaced
2304
+ // CALLABLEs in some cases), but which a division operator can.
1898
2305
 
1899
- // Unary tokens.
1900
- UNARY = ['NEW', 'TYPEOF', 'DELETE'];
2306
+ // See: http://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
2307
+ NOT_REGEX = INDEXABLE.concat(['++', '--']);
1901
2308
 
1902
- UNARY_MATH = ['!', '~'];
1903
-
1904
- // Bit-shifting tokens.
1905
- SHIFT = ['<<', '>>', '>>>'];
1906
-
1907
- // Comparison tokens.
1908
- COMPARE = ['==', '!=', '<', '>', '<=', '>='];
1909
-
1910
- // Mathematical tokens.
1911
- MATH = ['*', '/', '%', '//', '%%'];
1912
-
1913
- // Relational tokens that are negatable with `not` prefix.
1914
- RELATION = ['IN', 'OF', 'INSTANCEOF'];
1915
-
1916
- // Boolean tokens.
1917
- BOOL = ['TRUE', 'FALSE'];
1918
-
1919
- // Tokens which could legitimately be invoked or indexed. An opening
1920
- // parentheses or bracket following these tokens will be recorded as the start
1921
- // of a function invocation or indexing operation.
1922
- CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER', 'DYNAMIC_IMPORT'];
1923
-
1924
- INDEXABLE = CALLABLE.concat(['NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
1925
-
1926
- // Tokens which can be the left-hand side of a less-than comparison, i.e. `a<b`.
1927
- COMPARABLE_LEFT_SIDE = ['IDENTIFIER', ')', ']', 'NUMBER'];
1928
-
1929
- // Tokens which a regular expression will never immediately follow (except spaced
1930
- // CALLABLEs in some cases), but which a division operator can.
1931
-
1932
- // See: http://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
1933
- NOT_REGEX = INDEXABLE.concat(['++', '--']);
1934
-
1935
- // Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
1936
- // occurs at the start of a line. We disambiguate these from trailing whens to
1937
- // avoid an ambiguity in the grammar.
1938
- LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
1939
-
1940
- // Additional indent in front of these is ignored.
1941
- INDENTABLE_CLOSERS = [')', '}', ']'];
2309
+ // Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
2310
+ // occurs at the start of a line. We disambiguate these from trailing whens to
2311
+ // avoid an ambiguity in the grammar.
2312
+ LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
1942
2313
 
2314
+ // Additional indent in front of these is ignored.
2315
+ INDENTABLE_CLOSERS = [')', '}', ']'];
1943
2316
  }).call(this);