@sap/cds-compiler 3.3.2 → 3.4.2

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 (76) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/cdsc.js +3 -1
  3. package/doc/CHANGELOG_BETA.md +17 -0
  4. package/lib/api/main.js +147 -18
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/keywords.js +104 -0
  8. package/lib/base/message-registry.js +137 -68
  9. package/lib/base/messages.js +59 -48
  10. package/lib/base/model.js +1 -0
  11. package/lib/checks/actionsFunctions.js +1 -1
  12. package/lib/checks/cdsPersistence.js +1 -1
  13. package/lib/checks/checkForTypes.js +13 -8
  14. package/lib/checks/defaultValues.js +3 -1
  15. package/lib/checks/elements.js +1 -1
  16. package/lib/checks/parameters.js +4 -2
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/sql-snippets.js +12 -10
  19. package/lib/checks/validator.js +14 -4
  20. package/lib/compiler/assert-consistency.js +8 -7
  21. package/lib/compiler/checks.js +30 -20
  22. package/lib/compiler/define.js +89 -25
  23. package/lib/compiler/extend.js +33 -28
  24. package/lib/compiler/finalize-parse-cdl.js +14 -9
  25. package/lib/compiler/populate.js +30 -8
  26. package/lib/compiler/propagator.js +23 -28
  27. package/lib/compiler/resolve.js +11 -5
  28. package/lib/compiler/shared.js +66 -48
  29. package/lib/compiler/tweak-assocs.js +2 -3
  30. package/lib/compiler/utils.js +11 -0
  31. package/lib/edm/annotations/genericTranslation.js +7 -4
  32. package/lib/edm/csn2edm.js +1 -1
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +1 -1
  35. package/lib/gen/languageParser.js +3565 -3544
  36. package/lib/json/csnVersion.js +13 -13
  37. package/lib/json/from-csn.js +140 -158
  38. package/lib/json/to-csn.js +23 -5
  39. package/lib/language/.eslintrc.json +4 -0
  40. package/lib/language/antlrParser.js +7 -10
  41. package/lib/language/docCommentParser.js +1 -2
  42. package/lib/language/errorStrategy.js +54 -27
  43. package/lib/language/genericAntlrParser.js +115 -84
  44. package/lib/language/language.g4 +29 -25
  45. package/lib/language/multiLineStringParser.js +75 -63
  46. package/lib/main.js +1 -0
  47. package/lib/model/csnRefs.js +4 -3
  48. package/lib/model/csnUtils.js +39 -7
  49. package/lib/model/sortViews.js +7 -3
  50. package/lib/modelCompare/compare.js +49 -15
  51. package/lib/modelCompare/filter.js +83 -0
  52. package/lib/optionProcessor.js +5 -1
  53. package/lib/render/manageConstraints.js +9 -5
  54. package/lib/render/toCdl.js +120 -62
  55. package/lib/render/toHdbcds.js +1 -1
  56. package/lib/render/toSql.js +6 -2
  57. package/lib/render/utils/common.js +7 -0
  58. package/lib/sql-identifier.js +7 -0
  59. package/lib/transform/db/assertUnique.js +27 -38
  60. package/lib/transform/db/expansion.js +11 -4
  61. package/lib/transform/db/temporal.js +3 -1
  62. package/lib/transform/db/transformExists.js +7 -1
  63. package/lib/transform/db/views.js +42 -13
  64. package/lib/transform/draft/db.js +2 -2
  65. package/lib/transform/forOdataNew.js +7 -3
  66. package/lib/transform/forRelationalDB.js +12 -6
  67. package/lib/transform/localized.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +2 -1
  69. package/lib/transform/parseExpr.js +245 -0
  70. package/lib/transform/transformUtilsNew.js +23 -14
  71. package/lib/transform/translateAssocsToJoins.js +12 -12
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  73. package/lib/utils/term.js +5 -5
  74. package/package.json +2 -2
  75. package/share/messages/message-explanations.json +1 -1
  76. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
@@ -12,17 +12,21 @@ const { dictAdd, dictAddArray } = require('../base/dictionaries');
12
12
  const locUtils = require('../base/location');
13
13
  const { parseDocComment } = require('./docCommentParser');
14
14
  const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
15
- const { functionsWithoutParens, specialFunctions, quotedLiteralPatterns } = require('../compiler/builtins');
16
- const { pathName } = require("../compiler/utils");
15
+ const {
16
+ functionsWithoutParens,
17
+ specialFunctions,
18
+ quotedLiteralPatterns,
19
+ } = require('../compiler/builtins');
20
+ const { pathName } = require('../compiler/utils');
21
+ const { isBetaEnabled } = require('../base/model');
17
22
 
18
23
  const $location = Symbol.for('cds.$location');
19
24
 
20
25
  // Push message `msg` with location `loc` to array of errors:
21
26
  function _message( parser, severity, id, loc, ...args ) {
22
27
  const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
23
- if (loc instanceof antlr4.CommonToken) {
28
+ if (loc instanceof antlr4.CommonToken)
24
29
  loc = parser.tokenLocation(loc);
25
- }
26
30
  return msg( id, loc, ...args );
27
31
  }
28
32
 
@@ -57,10 +61,18 @@ class GenericAntlrParser extends antlr4.Parser {
57
61
 
58
62
  // TODO: Use actual methods.
59
63
  Object.assign(GenericAntlrParser.prototype, {
60
- message: function(...args) { return _message( this, 'message', ...args ); },
61
- error: function(...args) { return _message( this, 'error', ...args ); },
62
- warning: function(...args) { return _message( this, 'warning', ...args ); },
63
- info: function(...args) { return _message( this, 'info', ...args ); },
64
+ message(...args) {
65
+ return _message( this, 'message', ...args );
66
+ },
67
+ error(...args) {
68
+ return _message( this, 'error', ...args );
69
+ },
70
+ warning(...args) {
71
+ return _message( this, 'warning', ...args );
72
+ },
73
+ info(...args) {
74
+ return _message( this, 'info', ...args );
75
+ },
64
76
  attachLocation,
65
77
  assignAnnotation,
66
78
  addAnnotation,
@@ -91,6 +103,7 @@ Object.assign(GenericAntlrParser.prototype, {
91
103
  addDef,
92
104
  addItem,
93
105
  addExtension,
106
+ aspectWithoutElements,
94
107
  createSource,
95
108
  createDict,
96
109
  createArray,
@@ -103,7 +116,6 @@ Object.assign(GenericAntlrParser.prototype, {
103
116
  associationInSelectItem,
104
117
  reportExpandInline,
105
118
  checkTypeFacet,
106
- notSupportedYet,
107
119
  csnParseOnly,
108
120
  disallowElementExtension,
109
121
  noAssignmentInSameLine,
@@ -126,30 +138,19 @@ Object.assign(GenericAntlrParser.prototype, {
126
138
  // language construct can come from a CSN as source.
127
139
  // TODO: this is not completely done this way
128
140
 
129
- function notSupportedYet( text, ...tokens ) {
130
- if (!text)
131
- return;
132
- if (typeof text !== 'string') {
133
- tokens = [ text, ...tokens ];
134
- text = `${ tokens.map( t => t.text.toUpperCase() ).join(' ') } is not supported`;
135
- }
136
- this.error( null, this.tokenLocation( tokens[0], tokens[tokens.length - 1] ), {}, text );
137
- }
138
-
139
141
  // Use the following function for language constructs which we (currently) do
140
142
  // not really compile, just use to produce a CSN for functions parse.cql() and
141
143
  // parse.expr().
142
- function csnParseOnly( text, ...tokens ) {
143
- if (!text || this.options.parseOnly)
144
+ // This function has a similar interface to our message functions on purpose!
145
+ // (tokens ~= location)
146
+ function csnParseOnly( msgId, tokens, textArgs ) {
147
+ if (!msgId || this.options.parseOnly)
144
148
  return;
145
- if (typeof text !== 'string') {
146
- tokens = [ text, ...tokens ];
147
- text = `${ tokens.map( t => t.text.toUpperCase() ).join(' ') } is not supported`;
148
- }
149
- this.error( null, this.tokenLocation( tokens[0], tokens[tokens.length - 1] ), {}, text );
149
+ const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] );
150
+ this.error( msgId, loc, textArgs );
150
151
  }
151
152
 
152
- /** @this {object} */
153
+ /** @this {object} */
153
154
  function noSemicolonHere() {
154
155
  const handler = this._errHandler;
155
156
  const t = this.getCurrentToken();
@@ -193,7 +194,7 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
193
194
  }
194
195
 
195
196
  function setLocalTokenForId( tokenNameMap ) {
196
- const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
197
+ const tokenName = tokenNameMap[this._input.LT(2).text || ''];
197
198
  const ll1 = this.getCurrentToken();
198
199
  if (tokenName &&
199
200
  (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
@@ -227,8 +228,10 @@ function setLocalTokenForId( tokenNameMap ) {
227
228
  */
228
229
  function disallowElementExtension(elemName, outer, extensionVariant) {
229
230
  if (elemName.path) {
230
- this.message( 'syntax-invalid-extend', this.tokenLocation(this.getCurrentToken()), { 'kind': extensionVariant } );
231
- outer.extensions.length = outer.extensions.length - 1; // remove last, i.e. new extension
231
+ const loc = this.tokenLocation(this.getCurrentToken());
232
+ this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } );
233
+ // remove last, i.e. new extension
234
+ outer.extensions.pop();
232
235
  }
233
236
  }
234
237
 
@@ -266,7 +269,7 @@ const genericTokenTypes = {
266
269
 
267
270
  function prepareGenericKeywords( pathItem, expected = null) {
268
271
  const length = pathItem?.args?.length || 0;
269
- const argPos = (expected ? length -1 : length);
272
+ const argPos = (expected ? length - 1 : length);
270
273
  const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
271
274
  const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
272
275
  this.$genericKeywords = spec;
@@ -298,7 +301,7 @@ function prepareGenericKeywords( pathItem, expected = null) {
298
301
  // To be called before having matched ( HideAlternatives | … )
299
302
  function reportErrorForGenericKeyword() {
300
303
  this._errHandler.reportUnwantedToken( this );
301
- //this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
304
+ // this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
302
305
  }
303
306
 
304
307
  // Attach location matched by current rule to node `art`. If a location is
@@ -317,11 +320,12 @@ function attachLocation( art ) {
317
320
  // case we can't rely on `this._ctx.stop.line`.
318
321
  if (this.isMultiLineToken(this._ctx.stop)) {
319
322
  this.fixMultiLineTokenEndLocation(this._ctx.stop, art.location);
320
-
321
- } else {
323
+ }
324
+ else {
322
325
  const { stop } = this._ctx;
323
326
  art.location.endLine = stop.line;
324
- art.location.endCol = stop.stop - stop.start + stop.column + 2; // after the last char (special for EOF?)
327
+ // after the last char (special for EOF?)
328
+ art.location.endCol = stop.stop - stop.start + stop.column + 2;
325
329
  }
326
330
 
327
331
  return art;
@@ -355,7 +359,7 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
355
359
  }
356
360
  else {
357
361
  name.absolute = absolute;
358
- this.addAnnotation( art, '@' + absolute, anno );
362
+ this.addAnnotation( art, `@${ absolute }`, anno );
359
363
  }
360
364
  if (!prefix) { // set deprecated $annotations for cds-lsp
361
365
  if (!art.$annotations)
@@ -366,6 +370,7 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
366
370
  }
367
371
 
368
372
  function addAnnotation( art, prop, anno ) {
373
+ // TODO: just overwrite after having reported the error
369
374
  dictAddArray( art, prop, anno, (n, location, a) => {
370
375
  // if we would make it a warning, we would still need to keep it an error
371
376
  // with '...'; otherwise parse.cdl would have to split annotate statements
@@ -376,7 +381,9 @@ function addAnnotation( art, prop, anno ) {
376
381
  } );
377
382
  }
378
383
 
379
- const extensionDicts = { elements: true, enum: true, params: true, returns: true };
384
+ const extensionDicts = {
385
+ elements: true, enum: true, params: true, returns: true,
386
+ };
380
387
 
381
388
  function checkExtensionDict( dict ) {
382
389
  for (const name in dict) {
@@ -385,8 +392,8 @@ function checkExtensionDict( dict ) {
385
392
  continue;
386
393
 
387
394
  if (def.kind !== 'annotate') {
388
- const numDefines =
389
- def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
395
+ const numDefines
396
+ = def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
390
397
  this.handleDuplicateExtension( def, name, numDefines );
391
398
  for (const dup of def.$duplicates)
392
399
  this.handleDuplicateExtension( dup, name, numDefines );
@@ -396,12 +403,13 @@ function checkExtensionDict( dict ) {
396
403
  for (const dup of def.$duplicates) {
397
404
  for (const prop of Object.keys( dup )) {
398
405
  if (prop.charAt(0) === '@') {
399
- this.addAnnotation( def, prop, dup[prop] )
406
+ this.addAnnotation( def, prop, dup[prop] );
400
407
  }
401
408
  else if (prop === 'doc') {
402
- if (def.doc)
409
+ if (def.doc) {
403
410
  this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
404
411
  'Doc comment is overwritten by another one below' );
412
+ }
405
413
  def.doc = dup.doc;
406
414
  }
407
415
  else if (extensionDicts[prop]) {
@@ -427,11 +435,13 @@ function addOneForDefinition( count, ext ) {
427
435
  * @param {number} numDefines
428
436
  */
429
437
  function handleDuplicateExtension( ext, name, numDefines ) {
430
- if (ext.kind === 'extend')
438
+ if (ext.kind === 'extend') {
431
439
  this.error( 'syntax-duplicate-extend', [ ext.name.location ],
432
440
  { name, '#': (numDefines ? 'define' : 'extend') } );
433
- else if (numDefines === 1)
434
- ext.$errorReported = 'syntax-duplicate-extend'; // a definition, but not duplicate
441
+ }
442
+ else if (numDefines === 1) {
443
+ ext.$errorReported = 'syntax-duplicate-extend';
444
+ } // a definition, but not duplicate
435
445
  }
436
446
 
437
447
 
@@ -470,8 +480,9 @@ function tokenLocation( token, endToken = null ) {
470
480
  col: token.column + 1,
471
481
  // Default for single line tokens
472
482
  endLine: endToken.line,
473
- endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
474
- }
483
+ // after the last char (special for EOF?)
484
+ endCol: endToken.stop - endToken.start + endToken.column + 2,
485
+ };
475
486
 
476
487
  // This check is done for performance reason. No need to access a token's
477
488
  // data if we know that it spans only one single line.
@@ -514,9 +525,11 @@ function fixMultiLineTokenEndLocation( token, location ) {
514
525
  if (newLineCount > 0) {
515
526
  location.endLine = token.line + newLineCount;
516
527
  location.endCol = token.stop - lastNewlineIndex + 1;
517
- } else {
528
+ }
529
+ else {
518
530
  location.endLine = token.line;
519
- location.endCol = token.stop - token.start + token.column + 2; // after the last char (special for EOF?)
531
+ // after the last char (special for EOF?)
532
+ location.endCol = token.stop - token.start + token.column + 2;
520
533
  }
521
534
  }
522
535
 
@@ -568,7 +581,7 @@ function unaryOpForParens( query, val ) {
568
581
  if (!parens)
569
582
  return query;
570
583
  const location = parens[parens.length - 1];
571
- return { op: { val, location }, location, args: [query] };
584
+ return { op: { val, location }, location, args: [ query ] };
572
585
  }
573
586
 
574
587
  // If the token before the current one is a doc comment (ignoring other tokens
@@ -609,12 +622,14 @@ function classifyImplicitName( category, ref ) {
609
622
  function fragileAlias( ast, safe = false ) {
610
623
  if (this.getCurrentToken().text === '.')
611
624
  return ast;
612
- if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id ))
625
+ if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
613
626
  this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' },
614
627
  'Please add the keyword $(KEYWORD) in front of the alias name' );
615
- else // configurable error
628
+ }
629
+ else { // configurable error
616
630
  this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' },
617
631
  'Please add the keyword $(KEYWORD) in front of the alias name' );
632
+ }
618
633
  return ast;
619
634
  }
620
635
 
@@ -645,6 +660,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
645
660
  }
646
661
  else {
647
662
  this.message( 'syntax-deprecated-ident', token, { delimited: id },
663
+ // eslint-disable-next-line max-len
648
664
  'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
649
665
  }
650
666
  return { id, $delimited: true, location: this.tokenLocation( token ) };
@@ -676,7 +692,7 @@ function setLastAsXpr( args ) {
676
692
 
677
693
  function xprToken() {
678
694
  const token = this._input.LT(-1);
679
- return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
695
+ return { location: this.tokenLocation( token ), val: token.text, literal: 'token' };
680
696
  }
681
697
 
682
698
  function valuePathAst( ref ) {
@@ -690,7 +706,7 @@ function valuePathAst( ref ) {
690
706
  if (!item)
691
707
  return ref;
692
708
  this.error( 'syntax-not-supported', item.location, {},
693
- 'Methods in expressions are not supported yet' );
709
+ 'Methods in expressions are not supported yet' );
694
710
  path.broken = true;
695
711
  path.length = 1;
696
712
  }
@@ -705,7 +721,9 @@ function valuePathAst( ref ) {
705
721
  implicit.isIdentifier = 'func';
706
722
  const op = { location, val: 'call' };
707
723
  return (args)
708
- ? { op, func: ref, location: ref.location, args }
724
+ ? {
725
+ op, func: ref, location: ref.location, args,
726
+ }
709
727
  : { op, func: ref, location: ref.location };
710
728
  }
711
729
 
@@ -713,15 +731,15 @@ function valuePathAst( ref ) {
713
731
  // otherwise (including for '+'), represent it as extra unary prefix operator.
714
732
  function signedExpression( signToken, expr ) {
715
733
  const sign = this.valueWithTokenLocation( signToken.text, signToken );
716
- const nval =
717
- (signToken.text === '-' &&
734
+ const nval
735
+ = (signToken.text === '-' &&
718
736
  expr && // expr may be null if `-` rule can't be parsed
719
737
  expr.literal === 'number' &&
720
738
  sign.location.endLine === expr.location.line &&
721
739
  sign.location.endCol === expr.location.col &&
722
740
  (typeof expr.val === 'number'
723
- ? expr.val >= 0 && -expr.val
724
- : !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
741
+ ? expr.val >= 0 && -expr.val
742
+ : !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
725
743
  if (nval === false)
726
744
  return { op: sign, args: expr ? [ expr ] : [] };
727
745
  expr.val = nval;
@@ -747,9 +765,11 @@ function numberLiteral( token, sign, text = token.text ) {
747
765
  const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
748
766
  if (!Number.isSafeInteger(num)) {
749
767
  if (sign == null) {
750
- this.error( 'syntax-expected-integer', token, { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe'} );
768
+ this.error( 'syntax-expecting-integer', token,
769
+ { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe' } );
751
770
  }
752
- else if (text !== `${num}`) {
771
+
772
+ else if (text !== `${ num }`) {
753
773
  return { literal: 'number', val: text, location };
754
774
  }
755
775
  }
@@ -768,7 +788,8 @@ function quotedLiteral( token, literal ) {
768
788
  if (token.text.startsWith('`')) {
769
789
  val = this.parseMultiLineStringLiteral(token);
770
790
  literal = 'string';
771
- } else {
791
+ }
792
+ else {
772
793
  pos = token.text.search( '\'' ) + 1; // pos of char after quote
773
794
  val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
774
795
  }
@@ -782,7 +803,7 @@ function quotedLiteral( token, literal ) {
782
803
 
783
804
  if (p.unexpected_char) {
784
805
  const idx = val.search(p.unexpected_char);
785
- if (~idx) {
806
+ if (idx > -1) {
786
807
  this.warning( 'syntax-invalid-literal', {
787
808
  file: location.file,
788
809
  line: location.line,
@@ -934,6 +955,15 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
934
955
  }
935
956
  }
936
957
 
958
+ function aspectWithoutElements( art ) {
959
+ // Empty dictionary to allow element extensions.
960
+ art.elements = this.createDict();
961
+ if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
962
+ this.error( null, [ art.name.location ], {},
963
+ 'Aspects without elements are not supported, yet' );
964
+ }
965
+ }
966
+
937
967
  // must be in action directly after having parsed '{' or '(`
938
968
  function createDict() {
939
969
  const dict = Object.create(null);
@@ -977,10 +1007,10 @@ function createPrefixOp( token, args ) {
977
1007
 
978
1008
  // Create AST node for binary operator `op` and arguments `args`
979
1009
  function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
980
- const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
1010
+ const op = this.valueWithTokenLocation( opToken.text.toLowerCase(), opToken);
981
1011
  const extra = eToken
982
- ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
983
- : undefined;
1012
+ ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
1013
+ : undefined;
984
1014
  if (!left.$parens &&
985
1015
  (left.op && left.op.val) === (op && op.val) &&
986
1016
  (left[extraProp] && left[extraProp].val) === (extra && extra.val)) {
@@ -988,11 +1018,12 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
988
1018
  return left;
989
1019
  }
990
1020
  else if (extra) {
991
- return { op, [extraProp]: extra, args: [ left, right ], location: left.location };
992
- }
993
- else {
994
- return { op, args: [ left, right ], location: left.location };
1021
+ return {
1022
+ op, [extraProp]: extra, args: [ left, right ], location: left.location,
1023
+ };
995
1024
  }
1025
+
1026
+ return { op, args: [ left, right ], location: left.location };
996
1027
  }
997
1028
 
998
1029
  // Set property `prop` of `target` to value `value`. Issue error if that
@@ -1003,7 +1034,7 @@ function setOnce( target, prop, value, ...tokens ) {
1003
1034
  const prev = target[prop];
1004
1035
  if (prev) {
1005
1036
  this.error( 'syntax-repeated-option', loc, { option: prev.option },
1006
- 'Option $(OPTION) has already been specified' );
1037
+ 'Option $(OPTION) has already been specified' );
1007
1038
  }
1008
1039
  if (typeof value === 'boolean') {
1009
1040
  if (!value)
@@ -1041,25 +1072,27 @@ function associationInSelectItem( art ) {
1041
1072
  if (!art.value) // e.g. `expand` without value (for new structures)
1042
1073
  return;
1043
1074
 
1044
- const isPath = art.value.path && art.value.path.length
1075
+ const isPath = art.value.path && art.value.path.length;
1045
1076
  const isIdentifier = isPath && art.value.path.length === 1;
1046
1077
  if (isIdentifier) {
1047
1078
  if (!art.name) {
1048
1079
  art.name = art.value.path[0];
1049
- } else {
1080
+ }
1081
+ else {
1050
1082
  // Use alias if provided, i.e. ignore art.value.path.
1051
1083
  this.error( 'query-unexpected-alias', art.name.location, {},
1052
- 'Unexpected alias for association' );
1084
+ 'Unexpected alias for association' );
1053
1085
  }
1054
1086
  delete art.value;
1055
- } else {
1087
+ }
1088
+ else {
1056
1089
  const loc = isPath ? art.value.path[1].location : art.value.location;
1057
1090
  // If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
1058
1091
  if (isPath || art.name) {
1059
1092
  this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
1060
- if (isPath) {
1093
+ if (isPath)
1061
1094
  art.name = art.value.path[art.value.path.length - 1];
1062
- }
1095
+
1063
1096
  delete art.value;
1064
1097
  }
1065
1098
  }
@@ -1091,21 +1124,19 @@ function reportExpandInline( column, isInline ) {
1091
1124
  }
1092
1125
 
1093
1126
  function checkTypeFacet( art, argIdent ) {
1094
- const id = argIdent.id;
1127
+ const { id } = argIdent;
1095
1128
  if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
1096
1129
  if (art[id] !== undefined) {
1097
1130
  this.error( 'syntax-duplicate-argument', argIdent.location,
1098
- { '#': 'duplicate', code: id } );
1131
+ { '#': 'duplicate', code: id } );
1099
1132
  this.error( 'syntax-duplicate-argument', art[id].location,
1100
- { '#': 'duplicate', code: id } );
1133
+ { '#': 'duplicate', code: id } );
1101
1134
  }
1102
1135
  return true;
1103
-
1104
- } else {
1105
- this.error( 'syntax-duplicate-argument', argIdent.location,
1106
- { '#': 'unknown', code: id } );
1107
- return false;
1108
1136
  }
1137
+ this.error( 'syntax-duplicate-argument', argIdent.location,
1138
+ { '#': 'unknown', code: id } );
1139
+ return false;
1109
1140
  }
1110
1141
 
1111
1142
  module.exports = GenericAntlrParser;
@@ -721,16 +721,21 @@ aspectDef[ art, outer ] locals[ name = {} ]
721
721
  )*
722
722
  )?
723
723
  )?
724
- '{' { $art.elements = this.createDict(); }
725
- ( elementDef[ $art ]* )
726
- '}' { this.finalizeDictOrArray( $art.elements ); }
727
- // TODO: action definitions in a specific section?
728
- (
729
- ACTIONS '{' { $art.actions = this.createDict(); }
730
- actionFunctionDef[ $art ]*
731
- '}' { this.finalizeDictOrArray( $art.actions ); }
732
- )?
733
- optionalSemi
724
+ ( // `aspect MyAspect {};`
725
+ '{' { $art.elements = this.createDict(); }
726
+ ( elementDef[ $art ]* )
727
+ '}' { this.finalizeDictOrArray( $art.elements ); }
728
+ // TODO: action definitions in a specific section?
729
+ (
730
+ ACTIONS '{' { $art.actions = this.createDict(); }
731
+ actionFunctionDef[ $art ]*
732
+ '}' { this.finalizeDictOrArray( $art.actions ); }
733
+ )?
734
+ optionalSemi
735
+ | // `aspect MyAspect;`, e.g. for annotation aspects.
736
+ { this.aspectWithoutElements( $art ); }
737
+ requiredSemi
738
+ )
734
739
  ;
735
740
 
736
741
  typeDef[ art, outer ] locals[ name = {} ]
@@ -1075,12 +1080,12 @@ mixinElementDef[ outer ] locals[ art = {} ]
1075
1080
  |
1076
1081
  typeRefOptArgs[ $art ]
1077
1082
  ( as='=' expression
1078
- { this.notSupportedYet( 'Calculated fields are not supported yet', $as ); }
1083
+ { this.error( 'syntax-unsupported-field', $as ); }
1079
1084
  )?
1080
1085
  )
1081
1086
  |
1082
1087
  as='=' expression
1083
- { this.notSupportedYet( 'Calculated fields are not supported yet', $as ); }
1088
+ { this.error( 'syntax-unsupported-field', $as ); }
1084
1089
  )
1085
1090
  requiredSemi
1086
1091
  ;
@@ -1099,8 +1104,7 @@ elementDefInner[ art, outer, allowEq ]
1099
1104
  ( masked=MASKED
1100
1105
  {
1101
1106
  $art.masked = this.valueWithTokenLocation( true, $masked ) ;
1102
- this.message( 'syntax-invalid-masked', $masked, { keyword: 'masked' },
1103
- 'Keyword $(KEYWORD) not supported' );
1107
+ this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
1104
1108
  }
1105
1109
  )?
1106
1110
  // TODO: order?
@@ -1212,7 +1216,7 @@ elementDefInner[ art, outer, allowEq ]
1212
1216
  eq='=' e=expression // never introduce AS as syntax variant of '='
1213
1217
  {
1214
1218
  if (!$allowEq || $e.expr && !$e.expr.literal )
1215
- this.notSupportedYet( 'Calculated fields are not supported yet', $eq );
1219
+ this.error( 'syntax-unsupported-field', $eq );
1216
1220
  else if ($e.expr)
1217
1221
  $art.value = $e.expr;
1218
1222
  }
@@ -1394,7 +1398,7 @@ elementProperties[ elem ]
1394
1398
  nullability[ $elem ]?
1395
1399
  |
1396
1400
  eq='='
1397
- { this.notSupportedYet( 'Calculated fields are not supported yet', $eq ); }
1401
+ { this.error( 'syntax-unsupported-field', $eq ); }
1398
1402
  expression
1399
1403
  ;
1400
1404
 
@@ -2226,7 +2230,7 @@ conditionTerm returns [ cond ]
2226
2230
  { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
2227
2231
  { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
2228
2232
  ] };
2229
- this.csnParseOnly( 'Dynamic parameter "?" is not supported', $qm );
2233
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2230
2234
  }
2231
2235
  |
2232
2236
  ep=valuePath[ 'ref' ]
@@ -2350,7 +2354,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2350
2354
  ne=NEW nqp=valuePath[ 'ref', null] // token rewrite for NEW
2351
2355
  // please note: there will be no compiler-supported code completion after NEW
2352
2356
  { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2353
- this.notSupportedYet( $ne ); }
2357
+ this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
2354
2358
  |
2355
2359
  vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
2356
2360
  { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
@@ -2363,7 +2367,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2363
2367
  { $expr = $vp.qp;; $expr.scope = 'param'; }
2364
2368
  | pp=Number
2365
2369
  { $expr = { param: this.numberLiteral( $pp ), scope: 'param' };
2366
- this.csnParseOnly( 'Positional parameter ":' + $pp.text + '" is not supported', $pp );
2370
+ this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', name: ':' + $pp.text } );
2367
2371
  }
2368
2372
  )
2369
2373
  |
@@ -2371,7 +2375,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2371
2375
  // if we have an HideAlternatives here, we would block it to use it in
2372
2376
  // parallel to an expression (would produce adaptivePredict() otherwise)
2373
2377
  { $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
2374
- this.csnParseOnly( 'Dynamic parameter "?" is not supported', $qm );
2378
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2375
2379
  }
2376
2380
  |
2377
2381
  open='('
@@ -2661,8 +2665,9 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2661
2665
  const item = { literal: 'token', val: '...', location: this.tokenLocation($e) };
2662
2666
  if ($ctx.upTo) item.upTo = $upTo.val;
2663
2667
  $assignment.val.push( item );
2664
- if ($seenEllipsis === true) // TODO: adapt msg to UP TO
2665
- this.error( 'syntax-unexpected-ellipsis', $e, { '#': 'std', code: '...' } );
2668
+ if ($seenEllipsis === true)
2669
+ this.error( 'syntax-unexpected-ellipsis', $e,
2670
+ { '#': 'duplicate', code: '...', keyword: 'up to' } );
2666
2671
  else
2667
2672
  $seenEllipsis = !$ctx.upTo || 'upTo';
2668
2673
  }}
@@ -2672,9 +2677,8 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2672
2677
  cb=']'
2673
2678
  {
2674
2679
  if ($seenEllipsis === 'upTo')
2675
- this.error( 'syntax-expecting-ellipsis', $cb, // at closing bracket
2676
- { code: '... up to', newcode: '...' },
2677
- 'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
2680
+ this.error( 'syntax-missing-ellipsis', $cb, // at closing bracket
2681
+ { code: '... up to', newcode: '...' } );
2678
2682
  }
2679
2683
  |
2680
2684
  v1=literalValue { Object.assign( $assignment, $v1.val ); }