@sap/cds-compiler 3.4.0 → 3.4.4

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 (60) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/bin/cdsc.js +3 -4
  3. package/bin/cdshi.js +19 -6
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/lib/api/main.js +6 -3
  6. package/lib/base/message-registry.js +61 -38
  7. package/lib/base/messages.js +7 -3
  8. package/lib/checks/.eslintrc.json +2 -0
  9. package/lib/compiler/.eslintrc.json +4 -1
  10. package/lib/compiler/assert-consistency.js +8 -6
  11. package/lib/compiler/builtins.js +13 -13
  12. package/lib/compiler/checks.js +50 -33
  13. package/lib/compiler/define.js +9 -6
  14. package/lib/compiler/extend.js +83 -55
  15. package/lib/compiler/finalize-parse-cdl.js +3 -3
  16. package/lib/compiler/populate.js +16 -5
  17. package/lib/compiler/propagator.js +22 -29
  18. package/lib/compiler/resolve.js +2 -15
  19. package/lib/compiler/shared.js +4 -4
  20. package/lib/compiler/utils.js +14 -0
  21. package/lib/edm/annotations/genericTranslation.js +68 -56
  22. package/lib/edm/csn2edm.js +214 -174
  23. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  24. package/lib/edm/edmInboundChecks.js +2 -2
  25. package/lib/edm/edmPreprocessor.js +1 -1
  26. package/lib/edm/edmUtils.js +3 -3
  27. package/lib/gen/Dictionary.json +176 -8
  28. package/lib/gen/language.checksum +1 -1
  29. package/lib/gen/language.interp +2 -1
  30. package/lib/gen/languageParser.js +4776 -4513
  31. package/lib/json/from-csn.js +21 -16
  32. package/lib/json/to-csn.js +37 -41
  33. package/lib/language/.eslintrc.json +4 -1
  34. package/lib/language/antlrParser.js +5 -2
  35. package/lib/language/docCommentParser.js +6 -6
  36. package/lib/language/errorStrategy.js +43 -23
  37. package/lib/language/genericAntlrParser.js +54 -95
  38. package/lib/language/language.g4 +92 -66
  39. package/lib/language/multiLineStringParser.js +2 -2
  40. package/lib/language/textUtils.js +2 -2
  41. package/lib/model/csnRefs.js +5 -0
  42. package/lib/modelCompare/compare.js +2 -2
  43. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  44. package/lib/modelCompare/utils/filter.js +99 -0
  45. package/lib/render/.eslintrc.json +1 -0
  46. package/lib/render/toCdl.js +96 -127
  47. package/lib/render/toHdbcds.js +38 -35
  48. package/lib/render/toSql.js +75 -161
  49. package/lib/render/utils/common.js +133 -83
  50. package/lib/render/utils/delta.js +227 -0
  51. package/lib/transform/db/.eslintrc.json +2 -0
  52. package/lib/transform/draft/.eslintrc.json +1 -35
  53. package/lib/transform/forOdataNew.js +33 -22
  54. package/lib/transform/forRelationalDB.js +1 -1
  55. package/lib/transform/localized.js +9 -8
  56. package/lib/transform/odata/typesExposure.js +26 -4
  57. package/lib/transform/transformUtilsNew.js +15 -8
  58. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  59. package/package.json +2 -3
  60. package/lib/modelCompare/filter.js +0 -83
@@ -961,7 +961,7 @@ function dictionaryOf( elementFct ) {
961
961
  ++virtualLine;
962
962
  for (const name of allNames) {
963
963
  if (!name) {
964
- warning( 'syntax-invalid-name', location(true), // TODO: Error
964
+ message( 'syntax-invalid-name', location(true),
965
965
  { '#': 'csn', parentprop: spec.prop } );
966
966
  }
967
967
  const val = elementFct( dict[name], spec, r, dict, name );
@@ -1148,8 +1148,11 @@ function natnum( val, spec ) {
1148
1148
  if (typeof val === 'number' && val >= 0)
1149
1149
  // XSN TODO: do not require literal
1150
1150
  return { val, literal: 'number', location: location() };
1151
- error( spec.msgId || 'syntax-expecting-natnum', location(true),
1152
- { prop: spec.msgProp } );
1151
+ const loc = location(true);
1152
+ if (spec.msgId)
1153
+ error( spec.msgId, loc, { prop: spec.msgProp } );
1154
+ else
1155
+ error( 'syntax-expecting-natnum', loc, { prop: spec.msgProp } );
1153
1156
  return ignore( val );
1154
1157
  }
1155
1158
 
@@ -1456,8 +1459,7 @@ function excluding( array, spec, xsn ) {
1456
1459
  }
1457
1460
 
1458
1461
  function duplicateExcluding( name, loc ) {
1459
- error( 'syntax-duplicate-excluding', loc, { name, keyword: 'excluding' }, // TODO: also CDL
1460
- 'Duplicate $(NAME) in the $(KEYWORD) clause' );
1462
+ error( 'syntax-duplicate-excluding', loc, { '#': 'csn', name, prop: 'excluding[]' } );
1461
1463
  }
1462
1464
 
1463
1465
  function masked( val, spec ) {
@@ -1541,14 +1543,14 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1541
1543
  const variant = kind && s.inKind
1542
1544
  ? ([ 'extend', 'annotate' ].includes(kind) ? kind : 'kind')
1543
1545
  : (parentSpec.msgProp ? 'prop' : 'top');
1544
- message( 'syntax-unexpected-property', location(true),
1545
- {
1546
- '#': variant,
1547
- prop,
1548
- parentprop:
1549
- parentSpec.msgProp,
1550
- kind,
1551
- } );
1546
+ error( 'syntax-unexpected-property', location(true),
1547
+ {
1548
+ '#': variant,
1549
+ prop,
1550
+ parentprop:
1551
+ parentSpec.msgProp,
1552
+ kind,
1553
+ } );
1552
1554
  }
1553
1555
  else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) {
1554
1556
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
@@ -1622,7 +1624,7 @@ function checkAndSetXorGroup( group, exception, prop, xor ) {
1622
1624
  }
1623
1625
  if (siblingprop === exception)
1624
1626
  return true;
1625
- message( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1627
+ error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1626
1628
  return false;
1627
1629
  }
1628
1630
 
@@ -1652,8 +1654,11 @@ function isArray( array, spec ) {
1652
1654
  function isObject( obj, spec ) {
1653
1655
  if (obj && typeof obj === 'object' && !Array.isArray( obj ))
1654
1656
  return obj;
1655
- error( spec.msgId || 'syntax-expecting-object', location(true),
1656
- { prop: spec.msgProp });
1657
+ const loc = location(true);
1658
+ if (spec.msgId)
1659
+ error( spec.msgId, loc, { prop: spec.msgProp });
1660
+ else
1661
+ error( 'syntax-expecting-object', loc, { prop: spec.msgProp });
1657
1662
  return ignore( obj );
1658
1663
  }
1659
1664
 
@@ -23,6 +23,7 @@ const normalizedKind = {
23
23
  param: 'param',
24
24
  action: 'action',
25
25
  function: 'action',
26
+ enum: 'enum',
26
27
  };
27
28
 
28
29
  /** @type {boolean|string} */
@@ -429,21 +430,10 @@ function extensions( node, csn, model ) {
429
430
  }
430
431
  else if (gensrcFlavor) {
431
432
  // From definitions (without redefinitions) with potential inferred elements:
432
- const annotate = { annotate: name };
433
- if (art.$inferred)
434
- Object.assign( annotate, annotationsAndDocComment( art, true ) );
435
- if (art.$expand === 'annotate') {
436
- if (art.actions)
437
- attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
438
- else if (art.params)
439
- attachAnnotations( annotate, 'params', art.params, art.$inferred );
440
- const obj = art.returns || art;
441
- const elems = (obj.items || obj).elements; // no targetAspect here
442
- if (elems)
443
- attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
444
- }
445
- if (Object.keys( annotate ).length > 1)
446
- exts.push( annotate );
433
+ const result = { annotate: Object.create(null) };
434
+ attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
435
+ if (result.annotate[name])
436
+ exts.push({ annotate: name, ...result.annotate[name] } );
447
437
  }
448
438
  }
449
439
 
@@ -470,7 +460,7 @@ function extensions( node, csn, model ) {
470
460
  const par = art.params[name];
471
461
  if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
472
462
  continue;
473
- const render = annotationsAndDocComment( par, true );
463
+ const render = annotationsAndDocComment( par );
474
464
  const subElems = par.$expand !== 'origin' && (par.items || par).elements;
475
465
  if (subElems) {
476
466
  const sub = inferred( subElems, par.$inferred );
@@ -487,7 +477,7 @@ function extensions( node, csn, model ) {
487
477
  const par = art.returns;
488
478
  if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
489
479
  return;
490
- const render = annotationsAndDocComment( par, true );
480
+ const render = annotationsAndDocComment( par );
491
481
  const subElems = par.$expand !== 'origin' && (par.items || par).elements;
492
482
  if (subElems) {
493
483
  const sub = inferred( subElems, par.$inferred );
@@ -508,7 +498,7 @@ function extensions( node, csn, model ) {
508
498
  const name = art.name.absolute;
509
499
  // 'true' because annotations on namespaces and builtins can only
510
500
  // happen through extensions.
511
- const annos = annotationsAndDocComment( art, true );
501
+ const annos = annotationsAndDocComment( art );
512
502
  const annotate = Object.assign( { annotate: name }, annos );
513
503
  if (Object.keys( annotate ).length > 1) {
514
504
  const loc = locationForAnnotationExtension();
@@ -567,16 +557,21 @@ function sources( srcDict, csn ) {
567
557
  function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
568
558
  const annoDict = Object.create( dictionaryPrototype );
569
559
  for (const name in dict) {
570
- const elem = dict[name];
571
- const inf = inferred || elem.$inferred; // is probably always inferred if parent was
572
- const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
573
- if (elem.$expand === 'annotate') {
574
- if (elem.params)
575
- attachAnnotations( sub, 'params', elem.params, inf );
576
- const obj = elem.returns || elem;
577
- const elems = (obj.items || obj.targetAspect || obj).elements;
560
+ const entry = dict[name];
561
+ const inf = inferred || entry.$inferred; // is probably always inferred if parent was
562
+ const sub = (inf) ? annotationsAndDocComment( entry ) : {};
563
+ if (entry.$expand === 'annotate') {
564
+ if (entry.actions)
565
+ attachAnnotations( sub, 'actions', entry.actions, inf );
566
+ else if (entry.params)
567
+ attachAnnotations( sub, 'params', entry.params, inf );
568
+ const obj = entry.returns || entry;
569
+ const many = obj.items || obj;
570
+ const elems = (many.targetAspect || many).elements;
578
571
  if (elems)
579
- attachAnnotations( sub, 'elements', elems, inf, elem.returns );
572
+ attachAnnotations( sub, 'elements', elems, inf, entry.returns );
573
+ if (many.enum)
574
+ attachAnnotations( sub, 'enum', many.enum, inf );
580
575
  }
581
576
  if (Object.keys( sub ).length)
582
577
  annoDict[name] = sub;
@@ -723,10 +718,14 @@ function keepElements( node, line ) {
723
718
  return false; // no need to render elements
724
719
  }
725
720
 
726
- // for gensrcFlavor and namespace/builtin annotation extraction:
727
- // return annotations from definition (annotated==false)
728
- // or annotations (annotated==true)
729
- function annotationsAndDocComment( node, annotated ) {
721
+ /**
722
+ * For gensrcFlavor and namespace/builtin annotation extraction:
723
+ * return annotations from definition and annotations.
724
+ * The call side should check that node.$inferred is truthy.
725
+ *
726
+ * @param {object} node
727
+ */
728
+ function annotationsAndDocComment( node ) {
730
729
  const csn = {};
731
730
  const transformer = transformers['@'];
732
731
  const keys = Object.keys( node ).filter( a => a.charAt(0) === '@' ).sort();
@@ -734,15 +733,12 @@ function annotationsAndDocComment( node, annotated ) {
734
733
  const val = node[prop];
735
734
  // val.$priority isn't set for computed annotations like @Core.Computed
736
735
  // and @odata.containment.ignore
737
- // TODO: use $inferred instead special $priority value
738
- if (val.$priority !== undefined && (!!val.$priority) === annotated) {
739
- // transformer (= value) takes care to exclude $inferred annotation assignments
740
- const sub = transformer( val );
741
- // As value() just has one value, so we do not provide ( val, csn, node, prop )
742
- // which would be more robust, but makes some JS checks unhappy
743
- if (sub !== undefined)
744
- csn[prop] = sub;
745
- }
736
+ // transformer (= value) takes care to exclude $inferred annotation assignments
737
+ const sub = transformer( val );
738
+ // As value() just has one value, so we do not provide ( val, csn, node, prop )
739
+ // which would be more robust, but makes some JS checks unhappy
740
+ if (sub !== undefined)
741
+ csn[prop] = sub;
746
742
  }
747
743
  if (node.doc)
748
744
  csn.doc = transformers.doc(node.doc);
@@ -1534,7 +1530,7 @@ function addElementAsColumn( elem, cols ) {
1534
1530
  if (elem.$inferred === '*')
1535
1531
  return;
1536
1532
  // only list annotations here which are provided directly with definition
1537
- const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {};
1533
+ const col = (gensrcFlavor) ? annotationsAndDocComment( elem ) : {};
1538
1534
  // with `client` flavor, assignments are available at the element
1539
1535
  const gensrcSaved = gensrcFlavor;
1540
1536
 
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "root": true,
3
- "extends": "../../.eslintrc-ydkjsi.json"
3
+ "extends": "../../.eslintrc-ydkjsi.json",
4
+ "rules": {
5
+ "cds-compiler/space-in-func-decl": "error"
6
+ }
4
7
  }
@@ -164,8 +164,11 @@ function parse( source, filename = '<undefined>.cds',
164
164
  if (options.docComment !== false) {
165
165
  for (const token of tokenStream.tokens) {
166
166
  if (token.type === parser.constructor.DocComment && !token.isUsed) {
167
- messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
168
- 'Ignoring doc comment as it does not belong to any artifact');
167
+ // TODO: think of 'syntax-unexpected-doc-comment'
168
+ messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
169
+ 'Ignoring doc comment as it is not written at a defined position' );
170
+ // this is also for position inside some artifact definition, i.e. previous text
171
+ // "does not belong to any artifact" might be confusing
169
172
  }
170
173
  }
171
174
  }
@@ -16,7 +16,7 @@ const {
16
16
  * @returns {string|null} Parsed contents or if the comment has an invalid format or
17
17
  * does not have any content, null is returned.
18
18
  */
19
- function parseDocComment(comment) {
19
+ function parseDocComment( comment ) {
20
20
  // Also return "null" for empty doc comments so that doc comment propagation
21
21
  // can be stopped.
22
22
  if (comment.length <= 5) // at least "/***/"
@@ -83,7 +83,7 @@ function parseDocComment(comment) {
83
83
  * @param {string[]} lines String split into lines.
84
84
  * @param {boolean} ignoreFirstLine Whether to ignore the first line for indentation counting.
85
85
  */
86
- function stripCommentIndentation(lines, ignoreFirstLine) {
86
+ function stripCommentIndentation( lines, ignoreFirstLine ) {
87
87
  const n = lines.length;
88
88
 
89
89
  const minIndent = lines.reduce((min, line, index) => {
@@ -114,7 +114,7 @@ function stripCommentIndentation(lines, ignoreFirstLine) {
114
114
  * @param {string} line
115
115
  * @returns {string} line without fence
116
116
  */
117
- function removeFence(line) {
117
+ function removeFence( line ) {
118
118
  return line.replace(/^\s*[*]\s?/, '');
119
119
  }
120
120
 
@@ -125,7 +125,7 @@ function removeFence(line) {
125
125
  * @param {string} line
126
126
  * @returns {string} Header without fence.
127
127
  */
128
- function removeHeaderFence(line) {
128
+ function removeHeaderFence( line ) {
129
129
  return line.replace(/^\/[*]{2,}\s?/, '');
130
130
  }
131
131
 
@@ -138,7 +138,7 @@ function removeHeaderFence(line) {
138
138
  * @param {string} line
139
139
  * @returns {string} header without fence
140
140
  */
141
- function removeFooterFence(line) {
141
+ function removeFooterFence( line ) {
142
142
  return line.replace(/\s*[*]+\/$/, '');
143
143
  }
144
144
 
@@ -148,7 +148,7 @@ function removeFooterFence(line) {
148
148
  *
149
149
  * @param {string[]} lines
150
150
  */
151
- function isFencedComment(lines) {
151
+ function isFencedComment( lines ) {
152
152
  const index = lines.findIndex((line, i) => {
153
153
  const exclude = (i === 0 || i === lines.length - 1);
154
154
  return !exclude && !(/^\s*[*]/.test(line));
@@ -55,8 +55,11 @@ function match( ttype ) {
55
55
  const token = this.getCurrentToken();
56
56
  if (token.type === identType || !keywordRegexp.test( token.text ))
57
57
  return antlr4.Parser.prototype.match.call( this, ttype );
58
-
59
- this.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
58
+ // This is very likely to be dead code: we do not use a simple Identifier
59
+ // without alternatives in the grammar. With alternatives, recoverInline() is
60
+ // the place to go. (But this code should work with a changed grammar…)
61
+ this.message( 'syntax-unexpected-reserved-word', token,
62
+ { code: token.text, delimited: token.text } );
60
63
  this._errHandler.reportMatch(this);
61
64
  this.consume();
62
65
  return token;
@@ -72,7 +75,6 @@ class KeywordErrorStrategy extends DefaultErrorStrategy {
72
75
  super( ...args );
73
76
 
74
77
  this._super = {
75
- consumeUntil: super.consumeUntil,
76
78
  recoverInline: super.recoverInline,
77
79
  getExpectedTokens: super.getExpectedTokens,
78
80
  };
@@ -89,6 +91,7 @@ Object.assign( KeywordErrorStrategy.prototype, {
89
91
  reportIgnoredWith,
90
92
  // getErrorRecoverySet,
91
93
  consumeUntil,
94
+ consumeAndMarkUntil,
92
95
  recoverInline,
93
96
  getMissingSymbol,
94
97
  getExpectedTokensForMessage,
@@ -137,16 +140,19 @@ function sync( recognizer ) {
137
140
  }
138
141
 
139
142
  // Expected token is identifier, current is (reserved) KEYWORD:
140
- // TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens
143
+ // TODO: do not use this if "close enough" (1 char diff or prefix)
144
+ // to a keyword in nextTokens
141
145
  //
142
146
  // NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
143
- // which means that we cannot bring the better special syntax-fragile-ident
147
+ // which means that we cannot bring the better special syntax-unexpected-reserved
144
148
  // in all cases. Reason: high performance impact of the alternative,
145
149
  // i.e. calling method Parser#isExpectedToken() = invoking the ATN
146
150
  // interpreter to see behind EPSILON.
147
151
  const identType = recognizer.constructor.Identifier;
148
152
  if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
149
- recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
153
+ recognizer.message( 'syntax-unexpected-reserved-word', token,
154
+ { code: token.text, delimited: token.text } );
155
+ // TODO: attach tokens like for 'syntax-unexpected-token'
150
156
  token.type = identType; // make next ANTLR decision assume identifier
151
157
  return;
152
158
  }
@@ -210,11 +216,11 @@ function reportInputMismatch( recognizer, e, deadEnds ) {
210
216
  const expecting = deadEnds !== true && // true: cannot compute expecting
211
217
  this.getExpectedTokensForMessage( recognizer, e.offendingToken, deadEnds );
212
218
  const offending = this.getTokenDisplay( e.offendingToken, recognizer );
219
+ e.offendingToken.$isSkipped = 'offending';
213
220
  let err;
214
221
  if (expecting && expecting.length) {
215
- err = recognizer.error( 'syntax-mismatched-token', e.offendingToken,
216
- { offending, expecting },
217
- 'Mismatched $(OFFENDING), expecting $(EXPECTING)' );
222
+ err = recognizer.error( 'syntax-unexpected-token', e.offendingToken,
223
+ { offending, expecting } );
218
224
  err.expectedTokens = expecting;
219
225
  }
220
226
  else { // should not really happen anymore... -> no messageId !
@@ -232,11 +238,12 @@ function reportUnwantedToken( recognizer ) {
232
238
  this.beginErrorCondition(recognizer);
233
239
 
234
240
  const token = recognizer.getCurrentToken();
241
+ token.$isSkipped = 'offending';
235
242
  const expecting = this.getExpectedTokensForMessage( recognizer, token );
236
243
  const offending = this.getTokenDisplay( token, recognizer );
237
- const err = recognizer.error( 'syntax-extraneous-token', token,
238
- { offending, expecting },
239
- 'Extraneous $(OFFENDING), expecting $(EXPECTING)' );
244
+ // Just text variant, no other message id! Would depend on ANTLR-internals
245
+ const err = recognizer.error( 'syntax-unexpected-token', token,
246
+ { '#': 'unwanted', offending, expecting } );
240
247
  err.expectedTokens = expecting; // TODO: remove next token?
241
248
  if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
242
249
  recognizer.notifyErrorListeners( err.message, token, err );
@@ -249,9 +256,11 @@ function reportMissingToken( recognizer ) {
249
256
  this.beginErrorCondition(recognizer);
250
257
 
251
258
  const token = recognizer.getCurrentToken();
259
+ token.$isSkipped = 'offending';
252
260
  const expecting = this.getExpectedTokensForMessage( recognizer, token );
253
261
  const offending = this.getTokenDisplay( token, recognizer );
254
262
  // TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending
263
+ // Hopefully not too ANTLR-specific, so extra message id is ok:
255
264
  const err = recognizer.error( 'syntax-missing-token', token,
256
265
  { offending, expecting },
257
266
  'Missing $(EXPECTING) before $(OFFENDING)' );
@@ -264,10 +273,10 @@ function reportIgnoredWith( recognizer, t ) {
264
273
  const next = recognizer._interp.atn.states[recognizer.state].transitions[0].target;
265
274
  recognizer.state = next.stateNumber; // previous match() does not set the state
266
275
  const expecting = this.getExpectedTokensForMessage( recognizer, t );
267
- const m = recognizer.warning( 'syntax-ignored-with', t,
268
- { offending: "';'", expecting },
276
+ const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
277
+ { offending: "';'", expecting, keyword: 'with' },
269
278
  // eslint-disable-next-line max-len
270
- 'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous WITH' );
279
+ 'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
271
280
  m.expectedTokens = expecting;
272
281
  }
273
282
 
@@ -280,10 +289,10 @@ function consumeUntil( recognizer, set ) {
280
289
 
281
290
  // let s=this.getTokenDisplay( recognizer.getCurrentToken(), recognizer );
282
291
  if (SEMI < 1 || RBRACE < 1) {
283
- this._super.consumeUntil.call( this, recognizer, set );
292
+ this.consumeAndMarkUntil( recognizer, set );
284
293
  }
285
294
  else if (set.contains(SEMI)) { // do not check for RBRACE here!
286
- this._super.consumeUntil.call( this, recognizer, set );
295
+ this.consumeAndMarkUntil( recognizer, set );
287
296
  // console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
288
297
  // recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
289
298
  }
@@ -295,9 +304,9 @@ function consumeUntil( recognizer, set ) {
295
304
  stop.addOne( SEMI );
296
305
  // I am not that sure whether to add RBRACE...
297
306
  stop.addOne( RBRACE );
298
- this._super.consumeUntil.call( this, recognizer, stop );
299
- if (recognizer.getTokenStream().LA(1) === SEMI ||
300
- recognizer.getTokenStream().LA(1) === RBRACE && !set.contains(RBRACE)) {
307
+ this.consumeAndMarkUntil( recognizer, stop );
308
+ const ttype = recognizer.getTokenStream().LA(1);
309
+ if (ttype === SEMI || ttype === RBRACE && !set.contains(RBRACE)) {
301
310
  recognizer.consume();
302
311
  this.reportMatch(recognizer); // we know current token is correct
303
312
  }
@@ -312,6 +321,15 @@ function consumeUntil( recognizer, set ) {
312
321
  }
313
322
  }
314
323
 
324
+ function consumeAndMarkUntil( recognizer, set ) {
325
+ let t = recognizer.getTokenStream().LT(1);
326
+ while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) {
327
+ if (!t.$isSkipped)
328
+ t.$isSkipped = true;
329
+ recognizer.consume();
330
+ t = recognizer.getTokenStream().LT(1);
331
+ }
332
+ }
315
333
 
316
334
  // As the `match` function of the parser `recognizer` does not allow to check
317
335
  // against a set of token types, the generated parser code checks against that
@@ -328,7 +346,9 @@ function recoverInline( recognizer ) {
328
346
  if (!keywordRegexp.test( token.text ))
329
347
  return this._super.recoverInline.call( this, recognizer );
330
348
 
331
- recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
349
+ // TODO: attach `Identifier` as valid name to message?
350
+ recognizer.message( 'syntax-unexpected-reserved-word', token,
351
+ { code: token.text, delimited: token.text } );
332
352
  this.reportMatch(recognizer); // we know current token is correct
333
353
  recognizer.consume();
334
354
  return token;
@@ -496,14 +516,14 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
496
516
  // expected.toString(recognizer.literalNames, recognizer.symbolicNames));
497
517
  return intervalSetToArray( recognizer, expected );
498
518
 
499
- function addSet(other) {
519
+ function addSet( other ) {
500
520
  if (!other.contains( hideAltsType ))
501
521
  origAddSet.call( this, other );
502
522
  }
503
523
 
504
524
  // Add an interval `v` to the IntervalSet `this`. If `v` contains the token
505
525
  // type `Identifier`, do not add non-reserved keywords in `v`.
506
- function addInterval(v) {
526
+ function addInterval( v ) {
507
527
  if (v.stop <= identType) {
508
528
  origAddInterval.call(this, v);
509
529
  }