@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -0,0 +1,536 @@
1
+ 'use strict';
2
+
3
+ const whitespaceRegEx = /[\t\u{000B}\u{000C} \u{00A0}\u{FEFF}\p{Zs}]/u;
4
+ const newLineRegEx = /\r\n?|\n|\u2028|\u2029/u;
5
+
6
+ /**
7
+ * Returns true if the given string only contains whitespace characters.
8
+ *
9
+ * @todo Combine with function from docCommentParser
10
+ * @param {string} str
11
+ * @returns {boolean}
12
+ */
13
+ function isWhiteSpaceOnly(str) {
14
+ return /^\s*$/.test(str);
15
+ }
16
+
17
+ /**
18
+ * Check whether the given character is a white-space character as
19
+ * defined by §11.2 of the ECMAScript 2020 specification.
20
+ * See <https://262.ecma-international.org/11.0/#sec-white-space>.
21
+ *
22
+ * | Code Point | Name | Abbreviation |
23
+ * |:--------------------|:-----------------------------------------------|--------------|
24
+ * | U+0009 | CHARACTER TABULATION | `<TAB>` |
25
+ * | U+000B | LINE TABULATION | `<VT>` |
26
+ * | U+000C | FORM FEED (FF) | `<FF>` |
27
+ * | U+0020 | SPACE | `<SP>` |
28
+ * | U+00A0 | NO-BREAK SPACE | `<NBSP>` |
29
+ * | U+FEFF | ZERO WIDTH NO-BREAK SPACE | `<ZWNBSP>` |
30
+ * | Other category “Zs” | Any other Unicode “Space_Separator” code point | `<USP>` |
31
+ *
32
+ * @param char
33
+ * @returns {boolean}
34
+ */
35
+ function isWhitespaceCharacter(char) {
36
+ return whitespaceRegEx.test(char);
37
+ }
38
+
39
+ /**
40
+ * Strips and counts the indentation from the given string.
41
+ *
42
+ * @example
43
+ * | hello
44
+ * | world
45
+ * | foo bar
46
+ * becomes
47
+ * | hello
48
+ * | world
49
+ * | foo bar
50
+ *
51
+ * @param {string} str String prior to newline-normalization and escape parsing.
52
+ * @returns {[string, number]} The indentation-stripped string and the number
53
+ * of whitespace characters removed.
54
+ */
55
+ function stripIndentation(str) {
56
+ if (str === '') {
57
+ return ['', 0];
58
+ }
59
+
60
+ // Note: We have to check all newline characters, as the string is not normalized, yet.
61
+ const lines = str.split(newLineRegEx);
62
+ const n = lines.length;
63
+
64
+ const hasTrailingLineBreak = newLineRegEx.test(str[str.length - 1]);
65
+ if (hasTrailingLineBreak) {
66
+ // Shortcut:
67
+ // If there is a trailing line break, it means that ``` is on newline and
68
+ // therefore the indentation to remove is 0.
69
+ // Remove the last newline, which may be CRLF.
70
+ return [lines.slice(0, -1).join('\n'), 0];
71
+ }
72
+
73
+ const min = lines.reduce((min, line, index) => {
74
+ // Note: Last line is the line containing ```. There, we always count the indentation,
75
+ // even if blank. For all other lines, blank lines are ignored.
76
+ if (isWhiteSpaceOnly(line) && index !== (n-1))
77
+ return min;
78
+
79
+ let count = 0;
80
+ const length = Math.min(min, line.length);
81
+ while (count < length && isWhitespaceCharacter(line[count])) {
82
+ count++;
83
+ }
84
+ return Math.min(min, count);
85
+ }, Number.MAX_SAFE_INTEGER);
86
+
87
+ for (let i = 0; i < n; ++i) {
88
+ // Note: Line may be empty and have fewer characters than `min`.
89
+ // In that case, slice() returns an empty string.
90
+ lines[i] = lines[i].slice(min);
91
+ }
92
+
93
+ // Remove trailing last line, if there was nothing else in that line.
94
+ if (lines[n-1] === '')
95
+ lines.pop();
96
+
97
+ return [lines.join('\n'), min];
98
+ }
99
+
100
+ class MultiLineStringParser {
101
+ constructor(antlrParser, token) {
102
+ this.parser = antlrParser; // for message functions
103
+ this.token = token;
104
+ this.str = token.text; // Copy because .text is a getter
105
+
106
+ if (this.str[0] !== '`' || this.str[this.str.length-1] !== '`') {
107
+ throw new Error('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
108
+ }
109
+
110
+ this.output = [];
111
+ this.isTextBlock = this.str.startsWith('```');
112
+ this._indentation = 0;
113
+
114
+ // For message locations
115
+ this._lineInString = 0;
116
+ this._currentLineBreakIndex = 0;
117
+
118
+ if (this.isTextBlock) {
119
+ this.i = 3;
120
+ this.end = this.str.length - 3;
121
+ } else {
122
+ this.i = 1;
123
+ this.end = this.str.length - 1;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Parse the token's text and return it.
129
+ *
130
+ * @return {string}
131
+ */
132
+ parse() {
133
+ if (this.str.length === 2) {
134
+ return ''; // Nothing to do: ``
135
+ }
136
+
137
+ if (this.isTextBlock) {
138
+ // If there are no line breaks, emit an error as normal single-back-tick
139
+ // strings should be used instead. Because the first line is skipped,
140
+ // there is no text without at least one line break.
141
+ if (!newLineRegEx.test(this.str)) {
142
+ const loc = this._locationForCharacters(this.end, 1);
143
+ this.parser.message('syntax-invalid-text-block', loc);
144
+ return '';
145
+ }
146
+ this._skipOptionalLanguageIdentifierLine();
147
+ // Indentation needs to be stripped _before_ escape sequences are parsed and
148
+ // _after_ the first line is skipped, because otherwise `\n` in the string
149
+ // will interfere with calculating indentation and the language identifier
150
+ // is not part of the actual string.
151
+ // Because of message locations, we still need to keep track of indentation count
152
+ // and need to update the cursor and end position as well as the currentLineBreakIndex.
153
+ const [ str, indent ] = stripIndentation(this.str.slice(this.i, -3));
154
+ this.str = str;
155
+ this._indentation = indent;
156
+ this.i = 0;
157
+ this.end = this.str.length;
158
+ // this._lineInString is > 0, but having this._currentLineBreakIndex = 0 would be incorrect,
159
+ // as the line break isn't the first character in the indentation-stripped string
160
+ this._currentLineBreakIndex = -1;
161
+ }
162
+
163
+ // Note: Index is at first character of string
164
+
165
+ do {
166
+ switch(this._current()) {
167
+ case this._matchLineBreakAtCurrentChar():
168
+ this.output.push('\n');
169
+ break;
170
+ case '\\':
171
+ this._move();
172
+ this._innerEscape();
173
+ break;
174
+ case '$':
175
+ if (this._lookahead() === '{') {
176
+ const loc = this._locationForCharacters(this.i, 2);
177
+ this.parser.message('syntax-missing-escape', loc,
178
+ { '#': 'placeholder', code: '${', newcode: '\\${' });
179
+ }
180
+ this.output.push(this.str[this.i]);
181
+ break;
182
+ default:
183
+ this.output.push(this.str[this.i]);
184
+ break;
185
+ }
186
+ } while(this._move());
187
+
188
+ return this.output.join('');
189
+ }
190
+
191
+ /**
192
+ * Parse the escape sequence after the first '\'.
193
+ *
194
+ * @private
195
+ */
196
+ _innerEscape() {
197
+ switch(this._current()) {
198
+ case this._matchLineBreakAtCurrentChar():
199
+ // Don't add to output -> line break is escaped
200
+ break;
201
+ case 'b': // backspace
202
+ this.output.push(`\b`);
203
+ break;
204
+ case 'f': // form feed
205
+ this.output.push(`\f`);
206
+ break;
207
+ case 'v': // vertical tabulator
208
+ this.output.push(`\v`);
209
+ break;
210
+ case 'r': // carriage return
211
+ this.output.push(`\r`);
212
+ break;
213
+ case 'n': // line feed
214
+ this.output.push(`\n`);
215
+ break;
216
+ case 't': // tab
217
+ this.output.push(`\t`);
218
+ break;
219
+ case '\\':
220
+ case '"':
221
+ case '\'':
222
+ case '`':
223
+ case '$':
224
+ this.output.push(this._current());
225
+ break;
226
+ case 'x':
227
+ this._parseHexEscape('x', 2);
228
+ break;
229
+ case 'u':
230
+ if (this._lookahead() === '{')
231
+ this._parseBracedUnicodeEscape();
232
+ else
233
+ this._parseHexEscape('u', 4);
234
+ break;
235
+ case '0': // null terminator
236
+ if (!/^\d$/.test(this._lookahead())) {
237
+ this.output.push(`\0`);
238
+ break;
239
+ }
240
+ // Let the default case handle octal representation.
241
+ // fallthrough
242
+ default: {
243
+ this.output.push(this._current());
244
+ const loc = this._locationForCharacters(this.i-1, 2);
245
+ if (/\s/.test(this._current())) {
246
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'whitespace' });
247
+ } else if (/\d/.test(this._current())) {
248
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'octal' });
249
+ } else {
250
+ const code = this._makeCode('\\' + this._current());
251
+ this.parser.message('syntax-unknown-escape', loc, { '#': 'std', code });
252
+ }
253
+ break;
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Parse the given hexadecimal string to a unicode code-point.
260
+ *
261
+ * @param {string} codePoint Code-point represented as hexadecimal string, e.g. 'ABCD'.
262
+ * @private
263
+ */
264
+ _parseHexCodePoint(codePoint) {
265
+ // Notes:
266
+ // It isn't possible to get an invalid code point with the \u0000
267
+ // syntax variant as the first invalid code point is \u{110000}
268
+ // and an empty `codePoint` is only possible with the braced variant.
269
+ const reportInvalidCodePoint = () => {
270
+ const code = this._makeCode(`\\u{${codePoint}}`);
271
+ const loc = this._locationForCharacters(this.i-codePoint.length, codePoint.length);
272
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'codepoint', code });
273
+ };
274
+
275
+ const n = Number.parseInt(codePoint, 16);
276
+ if (Number.isNaN(n)) {
277
+ reportInvalidCodePoint();
278
+ return;
279
+ }
280
+
281
+ try {
282
+ this.output.push(String.fromCodePoint(n));
283
+ } catch (e) {
284
+ // RangeError is thrown if number isn't a valid code point
285
+ reportInvalidCodePoint();
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Parse a hex escape-sequence. Useful for unicode escapes and hex escapes.
291
+ * Cursor is at the `x`: `\x00`
292
+ * ^
293
+ * or at the `u`: `\u0000`
294
+ * ^
295
+ * @param {string} mode Either `x` or `u`. Used for error messages.
296
+ * @param {number} count Number of expected hexadecimal numbers
297
+ * @private
298
+ */
299
+ _parseHexEscape(mode, count) {
300
+ let codePoint = '';
301
+
302
+ for (let j = 0; j < count; ++j) {
303
+ if (!this._eos() && /^[0-9A-Fa-f]$/.test(this._lookahead())) {
304
+ this._move();
305
+ codePoint += this._current();
306
+ } else {
307
+ break;
308
+ }
309
+ }
310
+
311
+ if (codePoint.length === count) {
312
+ this._parseHexCodePoint(codePoint);
313
+ } else {
314
+ const loc = this._locationForCharacters(this.i+1, 1);
315
+ const code = this._eos(this.i+1) ? `\\${mode}${codePoint}` : `\\${mode}${codePoint}${this._lookahead()}`;
316
+ this.parser.message('syntax-invalid-escape', loc,
317
+ { '#': 'hex-count', number: count, code: this._makeCode(code) });
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Parse a unicode escape-sequence with braces.
323
+ * Cursor is at the `u`: `\u{0000}`
324
+ * ^
325
+ * @private
326
+ */
327
+ _parseBracedUnicodeEscape() {
328
+ let codePoint = '';
329
+
330
+ this._move(); // 'u'
331
+
332
+ while (!this._eos()) {
333
+ if (/^[0-9A-Fa-f]$/.test(this._lookahead())) {
334
+ this._move();
335
+ codePoint += this._current();
336
+ } else if (this._lookahead() === '}') {
337
+ break;
338
+ } else if (!this._eos(this.i+1)) {
339
+ const loc = this._locationForCharacters(this.i+1, 1); // Point to the exact character
340
+ const code = this._makeCode(`\\u{${codePoint}${this._lookahead()}…}`);
341
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'unicode-hex', code });
342
+ return;
343
+ } else {
344
+ break;
345
+ }
346
+ }
347
+
348
+ if (this._lookahead() === '}') {
349
+ this._move();
350
+ this._parseHexCodePoint(codePoint);
351
+ } else {
352
+ const loc = this._locationForCharacters(this.i, 1);
353
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'unicode-brace' });
354
+ }
355
+ }
356
+
357
+ /**
358
+ * This function skips the language identifier, i.e. until the next line.
359
+ * After this function, the cursor will be at the character _after_ the newline.
360
+ *
361
+ * @private
362
+ */
363
+ _skipOptionalLanguageIdentifierLine() {
364
+ while(!this._eos()) {
365
+ switch (this._current()) {
366
+ case this._matchLineBreakAtCurrentChar():
367
+ this._move();
368
+ return;
369
+ case '\\': {
370
+ // Do not allow an escape in the language identifier. If at the line's end, users
371
+ // may expect the identifier to span more than the first line, which is _not_ the case.
372
+ const loc = this._locationForCharacters(this.i, 1);
373
+ this.parser.message('syntax-invalid-escape', loc, { '#': 'language-identifier' });
374
+ this._move();
375
+ break;
376
+ }
377
+ default:
378
+ this._move();
379
+ break;
380
+ }
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Consume a line-break Character. Because CDS is close to JavaScript, we
386
+ * also support LS and PS. This function also ensures that CRLF (`\r\n`) is
387
+ * recognized as a single character.
388
+ * We increase the line number for LF (`\n`) for correct message locations.
389
+ *
390
+ * This function returns the input character, so that it can be used
391
+ * in a switch-case.
392
+ *
393
+ * @returns {string|null}
394
+ * @private
395
+ */
396
+ _matchLineBreakAtCurrentChar() {
397
+ // Only increase line number for \n, because ANTLR does the same
398
+ switch(this._current()) {
399
+ case '\r':
400
+ if (this._lookahead() === '\n') {
401
+ this._move(); // \r\n is normalized
402
+ this._lineInString++;
403
+ this._currentLineBreakIndex = this.i;
404
+ }
405
+ return '\r';
406
+ case '\n':
407
+ this._lineInString++;
408
+ this._currentLineBreakIndex = this.i;
409
+ // fallthrough
410
+ case '\u2028': // LS
411
+ case '\u2029': // PS
412
+ return this._current();
413
+ }
414
+ return null;
415
+ }
416
+
417
+ /**
418
+ * Move the cursor to the next character _if_ we're not at the end.
419
+ *
420
+ * @private
421
+ * @returns {boolean} `true` if we're not at the end
422
+ */
423
+ _move() {
424
+ if (this.i < this.end) { // Don't move past last char and `
425
+ ++this.i;
426
+ }
427
+ return this.i < this.end;
428
+ }
429
+
430
+ /**
431
+ * Returns `true` if we're at the end of the string
432
+ *
433
+ * @param {Number} [i=this.i] Index to check for EOS
434
+ * @private
435
+ * @returns {boolean}
436
+ */
437
+ _eos(i = this.i) {
438
+ // end-of-string -> char before `
439
+ return i >= this.end;
440
+ }
441
+
442
+ /**
443
+ * Get the next character without increasing the cursor.
444
+ * @note Does not check for `eos()`
445
+ *
446
+ * @private
447
+ * @returns {string}
448
+ */
449
+ _lookahead() {
450
+ return this.str[this.i+1];
451
+ }
452
+
453
+ /**
454
+ * Get the current character without increasing the cursor.
455
+ *
456
+ * @private
457
+ * @returns {string}
458
+ */
459
+ _current() {
460
+ return this.str[this.i];
461
+ }
462
+
463
+ /**
464
+ * Get the previous character without decreasing the cursor.
465
+ *
466
+ * @private
467
+ * @returns {string}
468
+ */
469
+ _previous() {
470
+ return this.str[this.i-1];
471
+ }
472
+
473
+ /**
474
+ * Get message location for the given cursor position inside the string.
475
+ *
476
+ * @param {Number} i Cursor position
477
+ * @param {Number} width Width of the location
478
+ * @private
479
+ * @returns {CSN.Location}
480
+ */
481
+ _locationForCharacters(i, width) {
482
+ return {
483
+ file: this.parser.filename,
484
+ line: this.token.line + this._lineInString,
485
+ endLine: this.token.line + this._lineInString,
486
+ col: this._lineInString > 0 ? i - this._currentLineBreakIndex + this._indentation : this.token.column + i + 1,
487
+ endCol: this._lineInString > 0 ? i - this._currentLineBreakIndex + width + this._indentation : this.token.column + i + width + 1,
488
+ };
489
+ }
490
+
491
+ /**
492
+ * For text messages, escape the given string for $(CODE).
493
+ * Escaping is required to avoid line breaks in compiler messages, e.g.
494
+ * if \u000<LF> is the code, the line-feed must be escaped.
495
+ *
496
+ * @param {string} code
497
+ * @private
498
+ */
499
+ _makeCode(code) {
500
+ // For characters that may be rendered as newline,
501
+ // see <https://www.unicode.org/reports/tr14/tr14-32.html>.
502
+ //
503
+ // Note: Unicode class `General_Category=Line_Separator` does not work for '\n'.
504
+ //
505
+ // U+000A: Line Feed (short: LF)
506
+ // U+000B: Vertical Tab (short: VT)
507
+ // U+000C: Form Feed (short: FF)
508
+ // U+000D: Carriage Return (short: CR)
509
+ // U+0085: Next Line (short: NEL)
510
+ // U+2028: Line Separator (short: LS)
511
+ // U+2029: Paragraph Separator (short: PS)
512
+ //
513
+ // For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
514
+ // U+23CE: ⏎
515
+
516
+ const newLineRegEx = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
517
+ return code.replace(newLineRegEx, '\u{23CE}');
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Parse a back-tick string and return it. This includes escape
523
+ * sequences, newlines, etc.
524
+ *
525
+ * Does _not_ modify the token's text.
526
+ *
527
+ * @param {object} token
528
+ */
529
+ function parseMultiLineStringLiteral(token) {
530
+ const p = new MultiLineStringParser(this, token);
531
+ return p.parse();
532
+ }
533
+
534
+ module.exports = {
535
+ parseMultiLineStringLiteral,
536
+ };