@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -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
@@ -607,12 +620,16 @@ function classifyImplicitName( category, ref ) {
607
620
  }
608
621
 
609
622
  function fragileAlias( ast, safe = false ) {
610
- if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id ))
623
+ if (this.getCurrentToken().text === '.')
624
+ return ast;
625
+ if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
611
626
  this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' },
612
627
  'Please add the keyword $(KEYWORD) in front of the alias name' );
613
- else // configurable error
628
+ }
629
+ else { // configurable error
614
630
  this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' },
615
631
  'Please add the keyword $(KEYWORD) in front of the alias name' );
632
+ }
616
633
  return ast;
617
634
  }
618
635
 
@@ -643,6 +660,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
643
660
  }
644
661
  else {
645
662
  this.message( 'syntax-deprecated-ident', token, { delimited: id },
663
+ // eslint-disable-next-line max-len
646
664
  'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
647
665
  }
648
666
  return { id, $delimited: true, location: this.tokenLocation( token ) };
@@ -674,7 +692,7 @@ function setLastAsXpr( args ) {
674
692
 
675
693
  function xprToken() {
676
694
  const token = this._input.LT(-1);
677
- return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
695
+ return { location: this.tokenLocation( token ), val: token.text, literal: 'token' };
678
696
  }
679
697
 
680
698
  function valuePathAst( ref ) {
@@ -687,7 +705,7 @@ function valuePathAst( ref ) {
687
705
  const item = path.find( i => i.args && i.$syntax !== ':' );
688
706
  if (!item)
689
707
  return ref;
690
- this.error( 'syntax-not-supported', item.location,
708
+ this.error( 'syntax-not-supported', item.location, {},
691
709
  'Methods in expressions are not supported yet' );
692
710
  path.broken = true;
693
711
  path.length = 1;
@@ -703,7 +721,9 @@ function valuePathAst( ref ) {
703
721
  implicit.isIdentifier = 'func';
704
722
  const op = { location, val: 'call' };
705
723
  return (args)
706
- ? { op, func: ref, location: ref.location, args }
724
+ ? {
725
+ op, func: ref, location: ref.location, args,
726
+ }
707
727
  : { op, func: ref, location: ref.location };
708
728
  }
709
729
 
@@ -711,15 +731,15 @@ function valuePathAst( ref ) {
711
731
  // otherwise (including for '+'), represent it as extra unary prefix operator.
712
732
  function signedExpression( signToken, expr ) {
713
733
  const sign = this.valueWithTokenLocation( signToken.text, signToken );
714
- const nval =
715
- (signToken.text === '-' &&
734
+ const nval
735
+ = (signToken.text === '-' &&
716
736
  expr && // expr may be null if `-` rule can't be parsed
717
737
  expr.literal === 'number' &&
718
738
  sign.location.endLine === expr.location.line &&
719
739
  sign.location.endCol === expr.location.col &&
720
740
  (typeof expr.val === 'number'
721
- ? expr.val >= 0 && -expr.val
722
- : !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
741
+ ? expr.val >= 0 && -expr.val
742
+ : !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
723
743
  if (nval === false)
724
744
  return { op: sign, args: expr ? [ expr ] : [] };
725
745
  expr.val = nval;
@@ -745,9 +765,11 @@ function numberLiteral( token, sign, text = token.text ) {
745
765
  const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
746
766
  if (!Number.isSafeInteger(num)) {
747
767
  if (sign == null) {
748
- 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' } );
749
770
  }
750
- else if (text !== `${num}`) {
771
+
772
+ else if (text !== `${ num }`) {
751
773
  return { literal: 'number', val: text, location };
752
774
  }
753
775
  }
@@ -766,7 +788,8 @@ function quotedLiteral( token, literal ) {
766
788
  if (token.text.startsWith('`')) {
767
789
  val = this.parseMultiLineStringLiteral(token);
768
790
  literal = 'string';
769
- } else {
791
+ }
792
+ else {
770
793
  pos = token.text.search( '\'' ) + 1; // pos of char after quote
771
794
  val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
772
795
  }
@@ -780,7 +803,7 @@ function quotedLiteral( token, literal ) {
780
803
 
781
804
  if (p.unexpected_char) {
782
805
  const idx = val.search(p.unexpected_char);
783
- if (~idx) {
806
+ if (idx > -1) {
784
807
  this.warning( 'syntax-invalid-literal', {
785
808
  file: location.file,
786
809
  line: location.line,
@@ -932,6 +955,15 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
932
955
  }
933
956
  }
934
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
+
935
967
  // must be in action directly after having parsed '{' or '(`
936
968
  function createDict() {
937
969
  const dict = Object.create(null);
@@ -975,10 +1007,10 @@ function createPrefixOp( token, args ) {
975
1007
 
976
1008
  // Create AST node for binary operator `op` and arguments `args`
977
1009
  function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
978
- const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
1010
+ const op = this.valueWithTokenLocation( opToken.text.toLowerCase(), opToken);
979
1011
  const extra = eToken
980
- ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
981
- : undefined;
1012
+ ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
1013
+ : undefined;
982
1014
  if (!left.$parens &&
983
1015
  (left.op && left.op.val) === (op && op.val) &&
984
1016
  (left[extraProp] && left[extraProp].val) === (extra && extra.val)) {
@@ -986,11 +1018,12 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
986
1018
  return left;
987
1019
  }
988
1020
  else if (extra) {
989
- return { op, [extraProp]: extra, args: [ left, right ], location: left.location };
990
- }
991
- else {
992
- return { op, args: [ left, right ], location: left.location };
1021
+ return {
1022
+ op, [extraProp]: extra, args: [ left, right ], location: left.location,
1023
+ };
993
1024
  }
1025
+
1026
+ return { op, args: [ left, right ], location: left.location };
994
1027
  }
995
1028
 
996
1029
  // Set property `prop` of `target` to value `value`. Issue error if that
@@ -1001,7 +1034,7 @@ function setOnce( target, prop, value, ...tokens ) {
1001
1034
  const prev = target[prop];
1002
1035
  if (prev) {
1003
1036
  this.error( 'syntax-repeated-option', loc, { option: prev.option },
1004
- 'Option $(OPTION) has already been specified' );
1037
+ 'Option $(OPTION) has already been specified' );
1005
1038
  }
1006
1039
  if (typeof value === 'boolean') {
1007
1040
  if (!value)
@@ -1039,57 +1072,71 @@ function associationInSelectItem( art ) {
1039
1072
  if (!art.value) // e.g. `expand` without value (for new structures)
1040
1073
  return;
1041
1074
 
1042
- const isPath = art.value.path && art.value.path.length
1075
+ const isPath = art.value.path && art.value.path.length;
1043
1076
  const isIdentifier = isPath && art.value.path.length === 1;
1044
1077
  if (isIdentifier) {
1045
1078
  if (!art.name) {
1046
1079
  art.name = art.value.path[0];
1047
- } else {
1080
+ }
1081
+ else {
1048
1082
  // Use alias if provided, i.e. ignore art.value.path.
1049
1083
  this.error( 'query-unexpected-alias', art.name.location, {},
1050
- 'Unexpected alias for association' );
1084
+ 'Unexpected alias for association' );
1051
1085
  }
1052
1086
  delete art.value;
1053
- } else {
1087
+ }
1088
+ else {
1054
1089
  const loc = isPath ? art.value.path[1].location : art.value.location;
1055
1090
  // If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
1056
1091
  if (isPath || art.name) {
1057
1092
  this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
1058
- if (isPath) {
1093
+ if (isPath)
1059
1094
  art.name = art.value.path[art.value.path.length - 1];
1060
- }
1095
+
1061
1096
  delete art.value;
1062
1097
  }
1063
1098
  }
1064
1099
  }
1065
1100
 
1066
- function reportExpandInline( clauseName ) {
1067
- let token = this.getCurrentToken();
1068
- // improve error location when using "inline" `.{…}` after ref (arguments and
1069
- // filters not covered, not worth the effort); after an expression where
1070
- // the last token is an identifier, not the `.` is wrong, but the `{`:
1071
- if (token.text === '.' && this._input.LT(-1).type >= this.constructor.Identifier)
1072
- token = this._input.LT(2);
1073
- this.error( 'syntax-unexpected-refclause', token, { prop: clauseName },
1074
- 'Unexpected nested $(PROP), can only be used after a reference' );
1101
+ function reportExpandInline( column, isInline ) {
1102
+ const { name } = column;
1103
+ if (column.value && !column.value.path) {
1104
+ let token = this.getCurrentToken();
1105
+ // improve error location when using "inline" `.{…}` after ref (arguments and
1106
+ // filters not covered, not worth the effort); after an expression where
1107
+ // the last token is an identifier, not the `.` is wrong, but the `{`:
1108
+ if (isInline && !name && this._input.LT(-1).type >= this.constructor.Identifier)
1109
+ token = this._input.LT(2);
1110
+ this.error( 'syntax-unexpected-nested-proj', token,
1111
+ { prop: isInline ? 'inline' : 'expand' },
1112
+ { std: 'Unexpected nested $(PROP), can only be used after a reference' } );
1113
+ // continuation semantics:
1114
+ // - add elements anyway (could lead to duplicate errors as usual)
1115
+ // - no errors for refs inside expand/inline, but for refs in sibling expr
1116
+ // - think about: reference to these (sub) elements from other view
1117
+ }
1118
+ if (isInline && name) {
1119
+ const location = this.tokenLocation( isInline, this._input.LT(-1) );
1120
+ this.error( 'syntax-unexpected-alias', location, { prop: 'inline' },
1121
+ 'Unexpected alias name before nested $(PROP)' );
1122
+ // continuation semantics: ignore AS
1123
+ }
1075
1124
  }
1076
1125
 
1077
1126
  function checkTypeFacet( art, argIdent ) {
1078
- const id = argIdent.id;
1127
+ const { id } = argIdent;
1079
1128
  if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
1080
1129
  if (art[id] !== undefined) {
1081
1130
  this.error( 'syntax-duplicate-argument', argIdent.location,
1082
- { '#': 'duplicate', code: id } );
1131
+ { '#': 'duplicate', code: id } );
1083
1132
  this.error( 'syntax-duplicate-argument', art[id].location,
1084
- { '#': 'duplicate', code: id } );
1133
+ { '#': 'duplicate', code: id } );
1085
1134
  }
1086
1135
  return true;
1087
-
1088
- } else {
1089
- this.error( 'syntax-duplicate-argument', argIdent.location,
1090
- { '#': 'unknown', code: id } );
1091
- return false;
1092
1136
  }
1137
+ this.error( 'syntax-duplicate-argument', argIdent.location,
1138
+ { '#': 'unknown', code: id } );
1139
+ return false;
1093
1140
  }
1094
1141
 
1095
1142
  module.exports = GenericAntlrParser;