@sap/cds-compiler 2.10.4 → 2.11.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 (70) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +4 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +9 -23
  7. package/lib/api/options.js +12 -4
  8. package/lib/api/validate.js +23 -2
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/message-registry.js +10 -2
  12. package/lib/base/messages.js +23 -9
  13. package/lib/base/model.js +5 -4
  14. package/lib/base/optionProcessorHelper.js +56 -22
  15. package/lib/checks/selectItems.js +4 -0
  16. package/lib/checks/unknownMagic.js +6 -3
  17. package/lib/compiler/assert-consistency.js +7 -0
  18. package/lib/compiler/base.js +65 -0
  19. package/lib/compiler/builtins.js +28 -1
  20. package/lib/compiler/checks.js +2 -1
  21. package/lib/compiler/definer.js +58 -91
  22. package/lib/compiler/index.js +16 -4
  23. package/lib/compiler/propagator.js +5 -2
  24. package/lib/compiler/resolver.js +93 -34
  25. package/lib/compiler/shared.js +29 -202
  26. package/lib/compiler/utils.js +173 -0
  27. package/lib/edm/annotations/genericTranslation.js +1 -1
  28. package/lib/edm/csn2edm.js +3 -2
  29. package/lib/edm/edmPreprocessor.js +31 -36
  30. package/lib/edm/edmUtils.js +3 -3
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +17 -1
  33. package/lib/gen/language.tokens +79 -73
  34. package/lib/gen/languageLexer.interp +19 -1
  35. package/lib/gen/languageLexer.js +779 -731
  36. package/lib/gen/languageLexer.tokens +71 -65
  37. package/lib/gen/languageParser.js +4668 -4072
  38. package/lib/json/from-csn.js +10 -10
  39. package/lib/json/to-csn.js +169 -34
  40. package/lib/language/antlrParser.js +11 -0
  41. package/lib/language/genericAntlrParser.js +72 -14
  42. package/lib/language/language.g4 +73 -0
  43. package/lib/main.d.ts +136 -17
  44. package/lib/main.js +3 -1
  45. package/lib/model/api.js +2 -2
  46. package/lib/model/csnRefs.js +108 -31
  47. package/lib/model/csnUtils.js +63 -29
  48. package/lib/model/enrichCsn.js +36 -9
  49. package/lib/model/revealInternalProperties.js +20 -4
  50. package/lib/modelCompare/compare.js +2 -1
  51. package/lib/optionProcessor.js +29 -18
  52. package/lib/render/DuplicateChecker.js +1 -1
  53. package/lib/render/toCdl.js +9 -3
  54. package/lib/render/toHdbcds.js +16 -36
  55. package/lib/render/toSql.js +23 -5
  56. package/lib/transform/db/constraints.js +278 -119
  57. package/lib/transform/db/draft.js +3 -2
  58. package/lib/transform/db/expansion.js +6 -4
  59. package/lib/transform/db/flattening.js +17 -1
  60. package/lib/transform/db/transformExists.js +61 -2
  61. package/lib/transform/db/views.js +438 -0
  62. package/lib/transform/forHanaNew.js +56 -435
  63. package/lib/transform/forOdataNew.js +9 -2
  64. package/lib/transform/localized.js +2 -0
  65. package/lib/transform/transformUtilsNew.js +10 -0
  66. package/lib/transform/translateAssocsToJoins.js +5 -13
  67. package/lib/utils/file.js +5 -3
  68. package/lib/utils/term.js +65 -42
  69. package/lib/utils/timetrace.js +48 -26
  70. package/package.json +1 -1
@@ -773,12 +773,8 @@ function arrayOf( fn, filter = undefined ) {
773
773
  } );
774
774
  const minLength = spec.minLength || 0;
775
775
  if (minLength > val.length) {
776
- error( 'syntax-csn-expected-length', location(true),
777
- { prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' },
778
- {
779
- std: 'Expected array in $(PROP) to have at least $(N) items',
780
- one: 'Expected array in $(PROP) to have at least one item',
781
- } );
776
+ message( 'syntax-csn-expected-length', location(true),
777
+ { prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
782
778
  }
783
779
  if (val.length)
784
780
  ++virtualLine; // [] in one JSON line
@@ -913,7 +909,7 @@ function definition( def, spec, xsn, csn, name ) {
913
909
  ++virtualLine;
914
910
  }
915
911
  }
916
- if (!r.name && name) {
912
+ if (!r.name && name != null) {
917
913
  r.name = { id: name, location: r.location };
918
914
  if (prop === 'columns' || prop === 'keys' || prop === 'foreignKeys')
919
915
  r.name.$inferred = 'as';
@@ -1002,13 +998,13 @@ function selectItem( def, spec, xsn, csn ) {
1002
998
  return definition( def, spec, xsn, csn, null ); // definer sets name
1003
999
  }
1004
1000
 
1005
- function returnsDefinition( def, spec, xsn, csn, name ) {
1001
+ function returnsDefinition( def, spec, xsn, csn ) {
1006
1002
  // TODO: be stricter in what is allowed inside returns
1007
1003
  if (!inExtensions)
1008
- return definition( def, spec, xsn, csn, name );
1004
+ return definition( def, spec, xsn, csn, '' );
1009
1005
  // for the moment, flatten elements in returns in an annotate
1010
1006
  // TODO: bigger Core Compiler changes would have to be done otherwise
1011
- xsn.elements = definition( def, spec, xsn, csn, name ).elements;
1007
+ xsn.elements = definition( def, spec, xsn, csn, '' ).elements;
1012
1008
  xsn.$syntax = 'returns';
1013
1009
  return undefined;
1014
1010
  }
@@ -1271,6 +1267,10 @@ function func( val, spec, xsn ) {
1271
1267
 
1272
1268
  function xpr( exprs, spec, xsn, csn ) {
1273
1269
  if (csn.func) {
1270
+ if (!exprs.length) {
1271
+ message( 'syntax-csn-expected-length', location(true),
1272
+ { prop: 'xpr', otherprop: 'func', '#': 'suffix' });
1273
+ }
1274
1274
  xsn.suffix = exprArgs( exprs, spec );
1275
1275
  }
1276
1276
  else {
@@ -33,12 +33,15 @@ let projectionAsQuery = false;
33
33
  let withLocations = false;
34
34
  let dictionaryPrototype = null;
35
35
 
36
+ // Properties for dictionaries, set in compileX() and TODO: parseX(), must be
37
+ // stored with symbols as keys, as we do not want to disallow any key name:
38
+ const $inferred = Symbol.for('cds.$inferred');
39
+
36
40
  // IMPORTANT: the order of these properties determine the order of properties
37
41
  // in the resulting CSN !!! Also check const `csnPropertyNames`.
38
42
  const transformers = {
39
43
  // early and modifiers (without null / not null) ---------------------------
40
44
  kind,
41
- _outer: ( _, csn, node ) => addOrigin( csn, node ),
42
45
  id: n => n, // in path item
43
46
  doc: value,
44
47
  '@': value,
@@ -67,7 +70,7 @@ const transformers = {
67
70
  targetAspect,
68
71
  target,
69
72
  foreignKeys,
70
- enum: insertOrderDict,
73
+ enum: enumDict,
71
74
  items,
72
75
  includes: arrayOf( artifactRef ), // also entities
73
76
  // late expressions / query properties -------------------------------------
@@ -199,6 +202,15 @@ const operators = {
199
202
  partitionBy: exprs => [
200
203
  'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
204
  ],
205
+ rows: exprs => [
206
+ 'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
207
+ ],
208
+ preceding: postfix( [ 'preceding' ] ),
209
+ unboundedPreceding: [ 'unbounded', 'preceding' ],
210
+ currentRow: [ 'current', 'row' ],
211
+ unboundedFollowing: [ 'unbounded', 'following' ],
212
+ following: postfix( [ 'following' ] ),
213
+ frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
202
214
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
203
215
  };
204
216
 
@@ -622,7 +634,7 @@ function target( val, _csn, node ) {
622
634
  }
623
635
 
624
636
  function items( obj, csn, node ) {
625
- if (!keepElements( node ))
637
+ if (!keepElements( node, obj ))
626
638
  return undefined;
627
639
  return standard( obj ); // no 'elements' with inferred elements with gensrc
628
640
  }
@@ -637,6 +649,18 @@ function elements( dict, csn, node ) {
637
649
  return insertOrderDict( dict );
638
650
  }
639
651
 
652
+ function enumDict( dict, csn, node ) {
653
+ if (gensrcFlavor && dict[$inferred] ||
654
+ !keepElements( node ))
655
+ // no 'elements' with SELECT or inferred elements with gensrc;
656
+ // hidden or visible 'elements' will be set in query()
657
+ return undefined;
658
+ if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate')
659
+ // derived type of enum type with individual annotations: also set $origin
660
+ csn.$origin = originRef( node.type._artifact );
661
+ return insertOrderDict( dict );
662
+ }
663
+
640
664
  function enumerableQueryElements( select ) {
641
665
  if (!universalCsn || select === select._main._leadingQuery)
642
666
  return false;
@@ -647,13 +671,24 @@ function enumerableQueryElements( select ) {
647
671
  }
648
672
 
649
673
  // Should we render the elements? (and items?)
650
- function keepElements( node ) {
674
+ function keepElements( node, line ) {
651
675
  if (universalCsn)
652
676
  // $expand = null/undefined: not elements not via expansion
653
677
  // $expand = 'target'/'annotate': with redirections / individual annotations
654
678
  return node.$expand !== 'origin';
655
679
  if (!node.type || node.kind === 'type')
656
680
  return true;
681
+ // keep many SimpleType/Entity
682
+ if (line) {
683
+ if (!node.type)
684
+ return true;
685
+ const array = node.type._artifact; // see function items() in propagator.js
686
+ const ltype = line.type && line.type._artifact;
687
+ if (!array || // reference errors
688
+ array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
689
+ (!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
690
+ return true;
691
+ }
657
692
  // even if expanded elements have no new target or direct annotation,
658
693
  // they might have got one via propagation – any new target/annos during their
659
694
  // way from the original structure type definition to the current usage
@@ -801,44 +836,143 @@ function definition( art, _csn, _node, prop ) {
801
836
  delete c.elements;
802
837
  c.returns = { elements: elems };
803
838
  }
839
+ if (kind && kind !== 'key')
840
+ addOrigin( c, art, art._origin );
804
841
  return c;
805
842
  }
806
843
 
807
- function addOrigin( csn, xsn ) {
808
- if (!universalCsn)
809
- return csn;
844
+ // create $origin specification for `includes` of `art`
845
+ function includesOrigin( includes, art ) {
846
+ const $origin = originRef( includes[0]._artifact );
847
+ if (includes.length === 1)
848
+ return $origin;
849
+ const result = { $origin };
850
+ for (const incl of includes.slice(1)) {
851
+ const aspect = incl._artifact;
852
+ for (const prop in aspect) {
853
+ if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
854
+ const anno = aspect[prop];
855
+ if (anno.val !== null)
856
+ // matererialize non-null annos (whether direct or inherited)
857
+ result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
858
+ }
859
+ }
860
+ }
861
+ return (Object.keys( result ).length === 1) ? $origin : result;
862
+ }
863
+
864
+ function addOrigin( csn, xsn, origin ) {
865
+ if (!universalCsn || hasExplicitProp( xsn.type ))
866
+ return;
810
867
  if (xsn._from) {
811
- csn.$origin = originRef( xsn._from[0]._origin );
868
+ const source = xsn._from[0]._origin;
869
+ csn.$origin = originRef( source );
870
+ if (source.params && !xsn.params)
871
+ csn.params = null; // discontinue `params` inheritance
872
+ if (source.actions && !xsn.actions)
873
+ csn.actions = null; // discontinue `actions` inheritance
874
+ return;
875
+ }
876
+ else if (xsn.includes) {
877
+ csn.$origin = includesOrigin( xsn.includes, xsn );
878
+ return;
812
879
  }
813
- else if (xsn.includes && xsn.includes.length > 1) {
814
- csn.$origin = { $origin: originRef( xsn.includes[0]._artifact ) };
880
+ else if (!xsn._main || xsn.kind === 'select') {
881
+ return;
815
882
  }
816
- else if (xsn._origin && !hasExplicitProp( xsn.type ) && xsn._origin.kind !== 'builtin') {
817
- let origin = xsn._origin;
818
- while (origin._parent && origin._parent.$expand === 'origin')
819
- origin = origin._origin || origin.type._artifact;
820
- csn.$origin = originRef( origin );
883
+ const parent = getParent( xsn );
884
+ const parentOrigin = getOrigin( parent );
885
+ if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
886
+ if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
887
+ csn.$origin = null;
888
+ return;
821
889
  }
822
- return csn;
890
+ // Skip all proxies which do not make it into the CSN, as there are no
891
+ // individual annotations or redirection targets on it:
892
+ while (origin._parent && origin._parent.$expand === 'origin')
893
+ origin = origin._origin || origin.type._artifact;
894
+ // The while loop is not only for the else case below: when setting implicit
895
+ // prototypes, it is important that we do not have to follow the prototypes of
896
+ // other object; we would need to ensure a right order to avoid issues otherwise.
897
+ if (parentOrigin === getParent( origin )) {
898
+ // implicit prototype or shortened reference
899
+ const { id } = origin.name || {};
900
+ if (id && xsn.name && id !== xsn.name.id)
901
+ csn.$origin = id;
902
+ return;
903
+ }
904
+ if (origin.kind === 'mixin') {
905
+ // currently, target and on are always set - nothing to do here, just set type
906
+ csn.type = 'cds.Association';
907
+ return;
908
+ }
909
+ const ref = originRef( origin, xsn );
910
+ if (ref) {
911
+ csn.$origin = ref;
912
+ return;
913
+ }
914
+ // An element of a query with a query in FROM:
915
+ const anon = definition( origin ); // use $origin: {...} if necessary
916
+ // as there are no implicit $origin prototypes on sub query elements (yet),
917
+ // we do not have to care about $origin not being set
918
+ const { $origin } = anon;
919
+ if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
920
+ // repeated anon: flatten
921
+ csn.$origin = Object.assign( $origin, anon );
922
+ }
923
+ else if (Object.keys( anon )
924
+ // (we can use the properties in `csn`, because addOrigin() is called last)
925
+ .every( p => p in csn || p === '$origin' || p === '$location')) {
926
+ // nothing new in $origin: {...}
927
+ addOrigin( csn, xsn, origin._origin );
928
+ }
929
+ else {
930
+ csn.$origin = anon;
931
+ }
932
+ }
933
+
934
+ function getParent( art ) {
935
+ const parent = art._parent;
936
+ const main = parent._main;
937
+ return (main && parent === main._leadingQuery) ? main : parent;
938
+ }
939
+
940
+ function getOrigin( art ) {
941
+ if (art._origin)
942
+ return art._origin;
943
+ if (hasExplicitProp( art.type ))
944
+ return art.type._artifact;
945
+ if (art.includes)
946
+ return art.includes[0]._artifact;
947
+ if (art._from)
948
+ return art._from[0]._origin;
949
+ return undefined;
823
950
  }
824
951
 
825
952
  function hasExplicitProp( ref ) {
826
953
  return ref && !ref.$inferred;
827
954
  }
828
955
 
829
- function originRef( art ) {
956
+ function originRef( art, user ) {
830
957
  const r = [];
831
958
  // do not use name.element, as we allow `.`s in name
832
- let main = art;
833
- while (main._main && main.kind !== 'select') {
834
- const nkind = normalizedKind[main.kind];
835
- if (main.name.id || !r.length) // { param: "" } only for return, not elements inside
836
- r.push( nkind ? { [nkind]: main.name.id } : main.name.id );
837
- main = main._parent;
959
+ let parent = art;
960
+ while (parent._main && parent.kind !== 'select') {
961
+ const nkind = normalizedKind[parent.kind];
962
+ if (parent.name.id || !r.length)
963
+ // Return parameter is in XSN - kind: 'param', name.id: ''
964
+ // eslint-disable-next-line no-nested-ternary, max-len
965
+ r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
966
+ parent = parent._parent;
838
967
  }
839
- if (main._main) // well, an element of an query in FROM
840
- return definition( art ); // use $origin: {}
968
+ if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
969
+ // well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
970
+ return null; // probably use $origin: {...}
841
971
  // for sub query in FROM in sub query in FROM, we could condense the info
972
+
973
+ // Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
974
+ if (r.length === 1 && normalizedKind[art.kind] === 'action')
975
+ return [ art.name.absolute, art.name.id ];
842
976
  r.push( art.name.absolute );
843
977
  r.reverse();
844
978
  return r;
@@ -853,13 +987,11 @@ function kind( k, csn, node ) {
853
987
  else if (k === 'extend')
854
988
  csn.kind = k;
855
989
  }
856
- else {
857
- if (![
858
- 'element', 'key', 'param', 'enum', 'select', '$join',
859
- '$tableAlias', 'annotation', 'mixin',
860
- ].includes(k))
861
- csn.kind = k;
862
- addOrigin( csn, node );
990
+ else if (![
991
+ 'element', 'key', 'param', 'enum', 'select', '$join',
992
+ '$tableAlias', 'annotation', 'mixin',
993
+ ].includes(k)) {
994
+ csn.kind = k;
863
995
  }
864
996
  }
865
997
 
@@ -997,7 +1129,10 @@ function value( node ) {
997
1129
 
998
1130
  function enumValue( v, csn, node ) {
999
1131
  // Enums can have values but if enums are extended, their kind is 'element',
1000
- // so we check whether the node is inside an extension.
1132
+ // so we check whether the node is inside an extension. (TODO: still?)
1133
+ if (universalCsn && v.$inferred)
1134
+ return;
1135
+ // (with gensrc, the symbol itself would not make it into the CSN)
1001
1136
  if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
1002
1137
  Object.assign( csn, expression( v, true ) );
1003
1138
  }
@@ -1185,7 +1320,7 @@ function columns( xsnColumns, csn, xsn ) {
1185
1320
  addElementAsColumn( col, csnColumns );
1186
1321
  }
1187
1322
  }
1188
- else { // null = use elements
1323
+ else { // null = use elements - TODO: still used by A2J? -> remove
1189
1324
  for (const name in xsn.elements)
1190
1325
  addElementAsColumn( xsn.elements[name], csnColumns );
1191
1326
  }
@@ -162,6 +162,17 @@ function parse( source, filename = '<undefined>.cds', options = {}, messageFunct
162
162
  if (rulespec.$frontend)
163
163
  ast.$frontend = rulespec.$frontend;
164
164
 
165
+ // Warn about unused doc-comments.
166
+ // Do not warn if docComments are explicitly disabled.
167
+ if (options.docComment !== false) {
168
+ for (const token of tokenStream.tokens) {
169
+ if (token.channel === antlr4.Token.HIDDEN_CHANNEL && token.type === parser.constructor.DocComment && !token.isUsed) {
170
+ messageFunctions.info('syntax-ignoring-doc-comment', parser.multiLineTokenLocation(token), {},
171
+ "Ignoring doc-comment as it does not belong to any artifact");
172
+ }
173
+ }
174
+ }
175
+
165
176
  // TODO: clarify with LSP colleagues: still necessary?
166
177
  if (parser.messages) {
167
178
  Object.defineProperty( ast, 'messages',
@@ -49,6 +49,8 @@ GenericAntlrParser.prototype = Object.assign(
49
49
  attachLocation,
50
50
  startLocation,
51
51
  tokenLocation,
52
+ multiLineTokenLocation,
53
+ previousTokenAtLocation,
52
54
  combinedLocation,
53
55
  surroundByParens,
54
56
  unaryOpForParens,
@@ -245,9 +247,12 @@ function prepareGenericKeywords( pathItem ) {
245
247
  // TODO: If not just at the beginning, we need a stack for $genericKeywords,
246
248
  // as we can have nested special functions
247
249
  this.$genericKeywords.argFull = Object.keys( spec );
250
+ // @ts-ignore
248
251
  const token = this.getCurrentToken() || { text: '' };
249
- if (spec[token.text.toUpperCase()] === 'argFull')
252
+ if (spec[token.text.toUpperCase()] === 'argFull') {
253
+ // @ts-ignore
250
254
  token.type = this.constructor.GenericArgFull;
255
+ }
251
256
  }
252
257
 
253
258
  // Attach location matched by current rule to node `art`. If a location is
@@ -297,7 +302,7 @@ function tokenLocation( token, endToken, val ) {
297
302
  file: this.filename,
298
303
  line: token.line,
299
304
  col: token.column + 1,
300
- // we only have single-line tokens
305
+ // we only have single-line tokens, except for docComments
301
306
  endLine: endToken.line,
302
307
  endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
303
308
  };
@@ -306,6 +311,51 @@ function tokenLocation( token, endToken, val ) {
306
311
  return r;
307
312
  }
308
313
 
314
+ /**
315
+ * Return location of `token`. In contrast to `tokenLocation()`, this function
316
+ * can handle multiline tokens.
317
+ */
318
+ function multiLineTokenLocation(token, val) {
319
+ if (!token)
320
+ return undefined;
321
+
322
+ // Count the number of newlines in the token.
323
+ // TODO: I want to avoid a substring, that's why I don't use RegEx here
324
+ const source = token.source[1].data;
325
+ let newLineCount = 0;
326
+ let lastNewlineIndex = token.start;
327
+ for (let i = token.start; i < token.stop; i++) {
328
+ if (source[i] === 10) { // ASCII code for '\n'
329
+ newLineCount++;
330
+ lastNewlineIndex = i;
331
+ }
332
+ }
333
+ if (newLineCount === 0)
334
+ // endCol calculation below requires at least one newLine.
335
+ return this.tokenLocation(token, token, val);
336
+
337
+ /** @type {CSN.Location} */
338
+ const r = {
339
+ file: this.filename,
340
+ line: token.line,
341
+ col: token.column + 1,
342
+ endLine: token.line + newLineCount,
343
+ endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
344
+ };
345
+ if (val !== undefined)
346
+ return { location: r, val };
347
+ return r;
348
+ }
349
+
350
+ function previousTokenAtLocation( location ) {
351
+ let k = -1;
352
+ let token = this._input.LT(k);
353
+ while (token.line > location.line ||
354
+ token.line === location.line && token.column >= location.col)
355
+ token = this._input.LT(--k);
356
+ return (token.line === location.line && token.column + 1 === location.col) && token;
357
+ }
358
+
309
359
  // Create a location with location properties `filename` and `start` from
310
360
  // argument `start`, and location property `end` from argument `end`.
311
361
  function combinedLocation( start, end ) {
@@ -342,16 +392,20 @@ function unaryOpForParens( query, val ) {
342
392
  // - would influence the prediction, probably even induce adaptivePredict() calls,
343
393
  // - is only slightly "more declarative" in the grammar.
344
394
  function docComment( node ) {
345
- if (!this.options.docComment)
346
- return;
347
395
  const token = this._input.getHiddenTokenToLeft( this.constructor.DocComment );
348
396
  if (!token)
349
397
  return;
398
+
399
+ // This token is actually used by / assigned to an artifact.
400
+ token.isUsed = true;
401
+
402
+ if (!this.options.docComment)
403
+ return;
350
404
  if (node.doc) {
351
405
  this.warning( 'syntax-duplicate-doc-comment', token, {},
352
406
  'Repeated doc comment - previous doc is replaced' );
353
407
  }
354
- node.doc = this.tokenLocation( token, token, parseDocComment( token.text ) );
408
+ node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
355
409
  }
356
410
 
357
411
  // Classify token (identifier category) for implicit names,
@@ -435,14 +489,18 @@ function valuePathAst( ref ) {
435
489
  path.length = 1;
436
490
  }
437
491
  const { args, id, location } = path[0];
438
- if (args) {
439
- if (path[0].$syntax !== ':')
440
- return { op: { location, val: 'call' }, func: ref, location: ref.location, args };
441
- }
442
- else if (!path[0].$delimited && functionsWithoutParens.includes( id.toUpperCase() )) {
443
- return { op: { location, val: 'call' }, func: ref, location: ref.location };
444
- }
445
- return ref;
492
+ if (args
493
+ ? path[0].$syntax === ':'
494
+ : path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
495
+ return ref;
496
+
497
+ const implicit = this.previousTokenAtLocation( location );
498
+ if (implicit && implicit.isIdentifier)
499
+ implicit.isIdentifier = 'func';
500
+ const op = { location, val: 'call' };
501
+ return (args)
502
+ ? { op, func: ref, location: ref.location, args }
503
+ : { op, func: ref, location: ref.location };
446
504
  }
447
505
 
448
506
  // If a '-' is directly before an unsigned number, consider it part of the number;
@@ -601,7 +659,7 @@ function addDef( parent, env, kind, name, annos, props, location ) {
601
659
  // which could be tested in name search (then no undefined-ref error)
602
660
  return art;
603
661
  }
604
- else if (env === 'artifacts') {
662
+ else if (env === 'artifacts' || env === 'vocabularies') {
605
663
  dictAddArray( parent[env], art.name.id, art );
606
664
  }
607
665
  else if (kind || this.options.parseOnly) {
@@ -498,6 +498,8 @@ projectionSpec returns[ query ] locals[ src ]
498
498
  fromPath[ $src, 'ref']
499
499
  )?
500
500
  ( AS aliasName=ident['FromAlias'] { $src.name = $aliasName.id } )?
501
+ // ANTLR errors are better if we use ( A )? instead of ( A | ):
502
+ { if (!$src.name) this.classifyImplicitName( $src.scope ? 'FromAlias' : 'Without' ); }
501
503
  bracedSelectItemListDef[ $query ]?
502
504
  excludingClause[ $query ]?
503
505
  ;
@@ -1776,6 +1778,62 @@ partitionByClause returns [ expr ]
1776
1778
  ( ',' en=expression { $expr.args.push( $en.expr ); } )*
1777
1779
  ;
1778
1780
 
1781
+ windowFrameClause returns [ wf ]
1782
+ :
1783
+ r=ROWS { $wf = { op: this.tokenLocation($r, null, 'rows' ) , args: [] }}
1784
+ wfe=windowFrameExtentSpec { $wf.args.push( $wfe.wfe ); }
1785
+ ;
1786
+
1787
+ windowFrameExtentSpec returns[ wfe ]
1788
+ :
1789
+ { $wfe = {} }
1790
+ windowFrameStartSpec [ $wfe ]
1791
+ |
1792
+ b=BETWEEN
1793
+ { $wfe = { op: this.tokenLocation( $b, null, 'frameBetween' ), args: [] } }
1794
+ wfb1=windowFrameBoundSpec { $wfe.args.push( $wfb1.wfb ); }
1795
+ AND
1796
+ wfb2=windowFrameBoundSpec { $wfe.args.push( $wfb2.wfb ); }
1797
+ ;
1798
+
1799
+ windowFrameBoundSpec returns [ wfb ]
1800
+ @after{ /* #ATN 1 */ }
1801
+ :
1802
+ // #ATN: Not ll1 because `UNBOUNDED` could also be part of the windowFrameStartSpec
1803
+ // `UNBOUNDED` would then be immediately followed by `PRECEDING`
1804
+ u=UNBOUNDED f=FOLLOWING
1805
+ { $wfb = { op: this.tokenLocation($u, $f, 'unboundedFollowing' ), args: []} }
1806
+ |
1807
+ // #ATN: Not ll1 because `Number` could also be part of the windowFrameStartSpec
1808
+ // `Number` would then be immediately followed by `PRECEDING`
1809
+ n=Number f=FOLLOWING
1810
+ { $wfb = { op: this.tokenLocation($n, $f, 'following' ), args: [ this.numberLiteral( $n ) ]} }
1811
+ |
1812
+ { $wfb = {} }
1813
+ windowFrameStartSpec [ $wfb ]
1814
+ ;
1815
+
1816
+ windowFrameStartSpec [ wf ]
1817
+ :
1818
+ u=UNBOUNDED p=PRECEDING
1819
+ {
1820
+ $wf.op = this.tokenLocation($u, $p, 'unboundedPreceding' );
1821
+ $wf.args = [];
1822
+ }
1823
+ |
1824
+ n=Number p=PRECEDING
1825
+ {
1826
+ $wf.op = this.tokenLocation($p, null, 'preceding' );
1827
+ $wf.args = [ this.numberLiteral( $n ) ];
1828
+ }
1829
+ |
1830
+ c=CURRENT r=ROW
1831
+ {
1832
+ $wf.op = this.tokenLocation($c, $r, 'currentRow' );
1833
+ $wf.args = [];
1834
+ }
1835
+ ;
1836
+
1779
1837
  overClause returns [ over ]
1780
1838
  @after { this.attachLocation($over); }
1781
1839
  :
@@ -1783,6 +1841,7 @@ overClause returns [ over ]
1783
1841
  '('
1784
1842
  ( pb=partitionByClause { $over.args.push( $pb.expr ); } )?
1785
1843
  ( ob=overOrderByClause { $over.args.push( $ob.expr ); } )?
1844
+ ( wf=windowFrameClause { $over.args.push( $wf.wf ); } )?
1786
1845
  ')'
1787
1846
  ;
1788
1847
 
@@ -1948,6 +2007,8 @@ tableTerm returns [ table ]
1948
2007
  // if we would use rule `ident`, we would either had to make all JOIN
1949
2008
  // kinds reserved or introduce ATN
1950
2009
  )?
2010
+ // ANTLR errors are better if we use ( A | B )? instead of ( A | B | ):
2011
+ { if (!$table.name) this.classifyImplicitName( $table.scope ? 'FromAlias' : 'Without' ); }
1951
2012
  |
1952
2013
  open='('
1953
2014
  // #ATN: The following alternative is not LL1, because both can start with
@@ -2602,6 +2663,7 @@ ident[ category ] returns[ id ]
2602
2663
  | COMPOSITION
2603
2664
  | CONTEXT
2604
2665
  | CROSS
2666
+ | CURRENT
2605
2667
  | DAY
2606
2668
  | DEFAULT
2607
2669
  | DEFINE
@@ -2619,6 +2681,7 @@ ident[ category ] returns[ id ]
2619
2681
  | EXTEND
2620
2682
  | FIRST
2621
2683
  | FLOATING
2684
+ | FOLLOWING
2622
2685
  | FULL
2623
2686
  | FUNCTION
2624
2687
  | GROUP
@@ -2650,10 +2713,13 @@ ident[ category ] returns[ id ]
2650
2713
  | OUTER
2651
2714
  | PARAMETERS
2652
2715
  | PARTITION
2716
+ | PRECEDING
2653
2717
  | PROJECTION
2654
2718
  | REDIRECTED
2655
2719
  | RETURNS
2656
2720
  | RIGHT
2721
+ | ROW
2722
+ | ROWS
2657
2723
  | SECOND
2658
2724
  | SERVICE
2659
2725
  | THEN
@@ -2662,6 +2728,7 @@ ident[ category ] returns[ id ]
2662
2728
  | TO
2663
2729
  | TYPE
2664
2730
  | USING
2731
+ | UNBOUNDED
2665
2732
  | VARIABLE
2666
2733
  | VIEW
2667
2734
  | YEAR
@@ -2763,6 +2830,7 @@ BOTH : [bB][oO][tT][hH] ;
2763
2830
  COMPOSITION : [cC][oO][mM][pP][oO][sS][iI][tT][iI][oO][nN] ;
2764
2831
  CONTEXT : [cC][oO][nN][tT][eE][xX][tT] ;
2765
2832
  CROSS : [cC][rR][oO][sS][sS] ;
2833
+ CURRENT : [cC][uU][rR][rR][eE][nN][tT] ;
2766
2834
  DAY : [dD][aA][yY] ;
2767
2835
  DEFAULT : [dD][eE][fF][aA][uU][lL][tT] ;
2768
2836
  DEFINE : [dD][eE][fF][iI][nN][eE] ;
@@ -2780,6 +2848,7 @@ EXCLUDING : [eE][xX][cC][lL][uU][dD][iI][nN][gG] ;
2780
2848
  EXTEND : [eE][xX][tT][eE][nN][dD] ;
2781
2849
  FIRST : [fF][iI][rR][sS][tT] ;
2782
2850
  FLOATING : [fF][lL][oO][aA][tT][iI][nN][gG] ;
2851
+ FOLLOWING : [fF][oO][lL][lL][oO][wW][iI][nN][gG] ;
2783
2852
  FULL : [fF][uU][lL][lL] ;
2784
2853
  FUNCTION : [fF][uU][nN][cC][tT][iI][oO][nN] ;
2785
2854
  GROUP : [gG][rR][oO][uU][pP] ;
@@ -2812,10 +2881,13 @@ OUTER : [oO][uU][tT][eE][rR] ;
2812
2881
  // OVER : [oO][vV][eE][rR] ;
2813
2882
  PARAMETERS : [pP][aA][rR][aA][mM][eE][tT][eE][rR][sS] ;
2814
2883
  PARTITION: [pP][aA][rR][tT][iI][tT][iI][oO][nN] ;
2884
+ PRECEDING: [pP][rR][eE][cC][eE][dD][iI][nN][gG] ;
2815
2885
  PROJECTION : [pP][rR][oO][jJ][eE][cC][tT][iI][oO][nN] ;
2816
2886
  REDIRECTED : [rR][eE][dD][iI][rR][eE][cC][tT][eE][dD] ;
2817
2887
  RETURNS : [rR][eE][tT][uU][rR][nN][sS] ;
2818
2888
  RIGHT : [rR][iI][gG][hH][tT] ;
2889
+ ROW : [rR][oO][wW] ;
2890
+ ROWS : [rR][oO][wW][sS] ;
2819
2891
  SECOND : [sS][eE][cC][oO][nN][dD] ;
2820
2892
  SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
2821
2893
  THEN : [tT][hH][eE][nN] ;
@@ -2823,6 +2895,7 @@ TRAILING : [tT][rR][aA][iI][lL][iI][nN][gG] ;
2823
2895
  TO : [tT][oO] ; // or make reserved? (is in SQL-92)
2824
2896
  TYPE : [tT][yY][pP][eE] ;
2825
2897
  UNION : [uU][nN][iI][oO][nN] ;
2898
+ UNBOUNDED : [uU][nN][bB][oO][uU][nN][dD][eE][dD] ;
2826
2899
  USING : [uU][sS][iI][nN][gG] ;
2827
2900
  VARIABLE : [vV][aA][rR][iI][aA][bB][lL][eE] ;
2828
2901
  VIEW : [vV][iI][eE][wW] ;