@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -705,7 +705,7 @@ let csnFilename = '';
705
705
  let virtualLine = 1;
706
706
  /** @type {CSN.Location[]} */
707
707
  let dollarLocations = [];
708
- let arrayLvlCnt = 0;
708
+ let arrayLevelCount = 0;
709
709
 
710
710
  /**
711
711
  * @param {Object.<string, SchemaSpec>} specs
@@ -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
  }
@@ -1168,6 +1164,12 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
1168
1164
  xsn.sym = { id, location: location() };
1169
1165
  }
1170
1166
 
1167
+ // returns: false = no "...", true = "..." without UP TO, 'upTo' = "..." with UP TO
1168
+ function isEllipsis( val ) {
1169
+ return val && typeof val === 'object' && '...' in val && Object.keys(val).length === 1 &&
1170
+ (val['...'] === true || 'upTo');
1171
+ }
1172
+
1171
1173
  function annoValue( val, spec ) {
1172
1174
  if (val == null) // TODO: reject undefined
1173
1175
  return { val, literal: 'null', location: location() };
@@ -1175,22 +1177,39 @@ function annoValue( val, spec ) {
1175
1177
  if (lit !== 'object')
1176
1178
  return { val, literal: lit, location: location() };
1177
1179
  if (Array.isArray( val )) {
1178
- const ec = val.reduce((c, v) => ((v && v['...'] && Object.keys(v).length === 1) ? ++c : c), 0);
1179
- if (arrayLvlCnt === 0 && ec > 1) {
1180
- error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
1181
- 'Expected no more than one $(CODE)' );
1180
+ let seenEllipsis = false;
1181
+ if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
1182
+ if (val.some( isEllipsis )) {
1183
+ error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
1184
+ 'Unexpected $(CODE) in nested array' );
1185
+ }
1182
1186
  }
1183
- if (arrayLvlCnt > 0 && ec > 0) {
1184
- error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
1185
- 'Unexpected $(CODE) in nested array' );
1187
+ else {
1188
+ for (const item of val) {
1189
+ if (seenEllipsis !== true) {
1190
+ seenEllipsis = isEllipsis( item ) || seenEllipsis;
1191
+ }
1192
+ else if (isEllipsis( item )) { // with or without UP TO
1193
+ // error position at the beginning of the array, but that is fine
1194
+ error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
1195
+ 'Expected no more than one $(CODE)' );
1196
+ break;
1197
+ }
1198
+ }
1186
1199
  }
1187
- arrayLvlCnt++;
1200
+ arrayLevelCount++;
1188
1201
  const retval = {
1189
1202
  location: location(),
1190
1203
  val: arrayOf( annoValue )( val, spec ),
1191
1204
  literal: 'array',
1192
1205
  };
1193
- arrayLvlCnt--;
1206
+ arrayLevelCount--;
1207
+ if (seenEllipsis === 'upTo') {
1208
+ error( 'syntax-csn-expecting-ellipsis', location(true), // at closing bracket
1209
+ { code: '... up to', newcode: '...' },
1210
+ // TODO: should we be more CSN specific in the message?
1211
+ 'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
1212
+ }
1194
1213
  return retval;
1195
1214
  }
1196
1215
  if (typeof val['#'] === 'string') {
@@ -1209,12 +1228,19 @@ function annoValue( val, spec ) {
1209
1228
  return refSplit( val['='], '=' );
1210
1229
  }
1211
1230
  }
1212
- else if (val['...'] && Object.keys(val).length === 1) {
1213
- return {
1231
+ else if (val['...'] && Object.keys( val ).length === 1) {
1232
+ // TODO: only if not nested - see error above
1233
+ ++virtualLine;
1234
+ const ell = val['...'];
1235
+ const r = {
1214
1236
  val: '...',
1215
1237
  literal: 'token',
1216
1238
  location: location(),
1217
1239
  };
1240
+ if (ell !== true)
1241
+ r.upTo = annoValue( ell, schema['@'] );
1242
+ ++virtualLine;
1243
+ return r;
1218
1244
  }
1219
1245
  const struct = Object.create(null);
1220
1246
  ++virtualLine;
@@ -1271,6 +1297,10 @@ function func( val, spec, xsn ) {
1271
1297
 
1272
1298
  function xpr( exprs, spec, xsn, csn ) {
1273
1299
  if (csn.func) {
1300
+ if (!exprs.length) {
1301
+ message( 'syntax-csn-expected-length', location(true),
1302
+ { prop: 'xpr', otherprop: 'func', '#': 'suffix' });
1303
+ }
1274
1304
  xsn.suffix = exprArgs( exprs, spec );
1275
1305
  }
1276
1306
  else {
@@ -1354,12 +1384,11 @@ function exprArgs( cond, spec, xsn, csn ) {
1354
1384
 
1355
1385
  function condition( cond, spec ) {
1356
1386
  const loc = location();
1357
- const x = {
1387
+ return {
1358
1388
  op: { val: 'xpr', location: loc },
1359
1389
  args: exprArgs( cond, spec ),
1360
1390
  location: loc,
1361
1391
  };
1362
- return x;
1363
1392
  }
1364
1393
 
1365
1394
  function vZeroValue( obj, spec, xsn ) {
@@ -1666,8 +1695,8 @@ function pushLocation( obj ) {
1666
1695
  error( 'syntax-csn-expected-object', location(true), { prop: '$location' } );
1667
1696
  }
1668
1697
  // hidden feature: string $location
1669
- const m = /:(\d+)(?::(\d+)(?:-[0-9-]+)?)?$/.exec( loc ); // extra - at end for .refloc
1670
- if (!m) {
1698
+ const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
1699
+ if (!m) { // without location or with '^'s: do not use
1671
1700
  dollarLocations.push( null );
1672
1701
  }
1673
1702
  else {
@@ -1710,8 +1739,10 @@ function toXsn( csn, filename, options, messageFunctions ) {
1710
1739
  csnFilename = filename;
1711
1740
  virtualLine = 1;
1712
1741
  dollarLocations = [];
1742
+ arrayLevelCount = 0;
1713
1743
  inExtensions = null;
1714
1744
  vocabInDefinitions = null;
1745
+
1715
1746
  const xsn = { $frontend: 'json' };
1716
1747
 
1717
1748
  // eslint-disable-next-line object-curly-newline
@@ -33,12 +33,23 @@ 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
+
40
+ // XSN $inferred values mapped to Universal CSN $generated values:
41
+ const inferredAsGenerated = {
42
+ autoexposed: 'exposed',
43
+ 'localized-entity': 'localized',
44
+ localized: 'localized', // on elements (texts, localized)
45
+ 'composition-entity': 'composed', // ('aspect-composition' on element not in CSN)
46
+ };
47
+
36
48
  // IMPORTANT: the order of these properties determine the order of properties
37
49
  // in the resulting CSN !!! Also check const `csnPropertyNames`.
38
50
  const transformers = {
39
51
  // early and modifiers (without null / not null) ---------------------------
40
52
  kind,
41
- _outer: ( _, csn, node ) => addOrigin( csn, node ),
42
53
  id: n => n, // in path item
43
54
  doc: value,
44
55
  '@': value,
@@ -63,11 +74,11 @@ const transformers = {
63
74
  precision: value,
64
75
  scale: value,
65
76
  srid: value,
66
- cardinality: standard, // also for pathItem: after 'id', before 'where'
77
+ cardinality, // also in pathItem: after 'id', before 'where'
67
78
  targetAspect,
68
79
  target,
69
80
  foreignKeys,
70
- enum: insertOrderDict,
81
+ enum: enumDict,
71
82
  items,
72
83
  includes: arrayOf( artifactRef ), // also entities
73
84
  // late expressions / query properties -------------------------------------
@@ -89,7 +100,7 @@ const transformers = {
89
100
  offset: expression,
90
101
  on: onCondition,
91
102
  // definitions, extensions, members ----------------------------------------
92
- returns: definition, // storing the return type of actions
103
+ returns, // storing the return type of actions
93
104
  notNull: value,
94
105
  default: expression,
95
106
  // targetElement: ignore, // special display of foreign key, renameTo: select
@@ -199,6 +210,15 @@ const operators = {
199
210
  partitionBy: exprs => [
200
211
  'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
212
  ],
213
+ rows: exprs => [
214
+ 'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
215
+ ],
216
+ preceding: postfix( [ 'preceding' ] ),
217
+ unboundedPreceding: [ 'unbounded', 'preceding' ],
218
+ currentRow: [ 'current', 'row' ],
219
+ unboundedFollowing: [ 'unbounded', 'following' ],
220
+ following: postfix( [ 'following' ] ),
221
+ frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
202
222
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
203
223
  };
204
224
 
@@ -536,7 +556,7 @@ function sources( srcDict, csn ) {
536
556
  }
537
557
  }
538
558
 
539
- function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
559
+ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
540
560
  const annoDict = Object.create( dictionaryPrototype );
541
561
  for (const name in dict) {
542
562
  const elem = dict[name];
@@ -554,7 +574,7 @@ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
554
574
  annoDict[name] = sub;
555
575
  }
556
576
  if (Object.keys( annoDict ).length) {
557
- if (returns)
577
+ if (insideReturns)
558
578
  annotate.returns = { elements: annoDict };
559
579
  else
560
580
  annotate[prop] = annoDict;
@@ -597,10 +617,21 @@ function set( prop, csn, node ) {
597
617
  }
598
618
 
599
619
  function targetAspect( val, csn, node ) {
620
+ if (universalCsn) {
621
+ if (val.$inferred)
622
+ return undefined;
623
+ if (node.target) {
624
+ csn.$origin = { type: 'cds.Composition' };
625
+ if (node.cardinality)
626
+ csn.$origin.cardinality = standard( node.cardinality );
627
+ csn.$origin.target = (val.elements) ? standard( val ) : artifactRef( val, true );
628
+ return undefined;
629
+ }
630
+ }
600
631
  const ta = (val.elements)
601
632
  ? addLocation( val.location, standard( val ) )
602
633
  : artifactRef( val, true );
603
- if (!gensrcFlavor || node.target && !node.target.$inferred)
634
+ if (!gensrcFlavor && !universalCsn || node.target && !node.target.$inferred)
604
635
  return ta;
605
636
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
606
637
  csn.target = ta;
@@ -622,7 +653,7 @@ function target( val, _csn, node ) {
622
653
  }
623
654
 
624
655
  function items( obj, csn, node ) {
625
- if (!keepElements( node ))
656
+ if (!keepElements( node, obj ))
626
657
  return undefined;
627
658
  return standard( obj ); // no 'elements' with inferred elements with gensrc
628
659
  }
@@ -637,23 +668,42 @@ function elements( dict, csn, node ) {
637
668
  return insertOrderDict( dict );
638
669
  }
639
670
 
671
+ function enumDict( dict, csn, node ) {
672
+ if (gensrcFlavor && dict[$inferred] ||
673
+ !keepElements( node ))
674
+ // no 'elements' with SELECT or inferred elements with gensrc;
675
+ // hidden or visible 'elements' will be set in query()
676
+ return undefined;
677
+ if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate' &&
678
+ node.type._artifact && !node.type._artifact.builtin)
679
+ // derived type of enum type with individual annotations: also set $origin
680
+ csn.$origin = originRef( node.type._artifact );
681
+ return insertOrderDict( dict );
682
+ }
683
+
640
684
  function enumerableQueryElements( select ) {
641
- if (!universalCsn || select === select._main._leadingQuery)
642
- return false;
643
- if (select.orderBy || select.$orderBy)
644
- return true;
645
- const alias = select._parent;
646
- return alias.query && (alias.query._leadingQuery || alias.query) === select;
685
+ return (universalCsn && select !== select._main._leadingQuery);
647
686
  }
648
687
 
649
688
  // Should we render the elements? (and items?)
650
- function keepElements( node ) {
689
+ function keepElements( node, line ) {
651
690
  if (universalCsn)
652
691
  // $expand = null/undefined: not elements not via expansion
653
692
  // $expand = 'target'/'annotate': with redirections / individual annotations
654
693
  return node.$expand !== 'origin';
655
694
  if (!node.type || node.kind === 'type')
656
695
  return true;
696
+ // keep many SimpleType/Entity
697
+ if (line) {
698
+ if (!node.type)
699
+ return true;
700
+ const array = node.type._artifact; // see function items() in propagator.js
701
+ const ltype = line.type && line.type._artifact;
702
+ if (!array || // reference errors
703
+ array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
704
+ (!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
705
+ return true;
706
+ }
657
707
  // even if expanded elements have no new target or direct annotation,
658
708
  // they might have got one via propagation – any new target/annos during their
659
709
  // way from the original structure type definition to the current usage
@@ -711,7 +761,8 @@ function ignore() { /* no-op: ignore property */ }
711
761
 
712
762
  function location( loc, csn, xsn ) {
713
763
  if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
714
- (!xsn.$inferred || !xsn._main)) { // TODO: also for 'select'
764
+ (!xsn.$inferred || !xsn._main) &&
765
+ xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
715
766
  // Also include $location for elements in queries (if not via '*')
716
767
  addLocation( xsn.name && xsn.name.location || loc, csn );
717
768
  }
@@ -767,8 +818,10 @@ function dictionary( dict, keys, prop ) {
767
818
  }
768
819
 
769
820
  function foreignKeys( dict, csn, node ) {
770
- if (universalCsn && !target( node.target, csn, node ))
771
- return;
821
+ if (universalCsn) {
822
+ if (!target( node.target, csn, node ) || dict[$inferred] === 'keys')
823
+ return;
824
+ }
772
825
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
773
826
  dict = node._origin.foreignKeys;
774
827
  const keys = [];
@@ -782,6 +835,14 @@ function foreignKeys( dict, csn, node ) {
782
835
  csn.keys = keys;
783
836
  }
784
837
 
838
+ function returns( art, csn, _node, prop ) {
839
+ // TODO: currently, the `returns` structure might just have been created by the propagator
840
+ // if that is the case, there should be no reason to store it in universal CSN
841
+ if (universalCsn && art.$inferred === 'proxy')
842
+ return undefined;
843
+ return definition( art, csn, _node, prop );
844
+ }
845
+
785
846
  function definition( art, _csn, _node, prop ) {
786
847
  if (!art || typeof art !== 'object')
787
848
  return undefined; // TODO: complain with strict
@@ -801,50 +862,151 @@ function definition( art, _csn, _node, prop ) {
801
862
  delete c.elements;
802
863
  c.returns = { elements: elems };
803
864
  }
865
+ // precondition already fulfilled: art.kind !== 'key'
866
+ addOrigin( c, art, art._origin );
804
867
  return c;
805
868
  }
806
869
 
807
- function addOrigin( csn, xsn ) {
808
- if (!universalCsn)
809
- return csn;
870
+ // create $origin specification for `includes` of `art`
871
+ function includesOrigin( includes, art ) {
872
+ const $origin = originRef( includes[0]._artifact );
873
+ if (includes.length === 1)
874
+ return $origin;
875
+ const result = { $origin };
876
+ for (const incl of includes.slice(1)) {
877
+ const aspect = incl._artifact;
878
+ for (const prop in aspect) {
879
+ if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
880
+ const anno = aspect[prop];
881
+ if (anno.val !== null)
882
+ // matererialize non-null annos (whether direct or inherited)
883
+ result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
884
+ }
885
+ }
886
+ }
887
+ return (Object.keys( result ).length === 1) ? $origin : result;
888
+ }
889
+
890
+ function addOrigin( csn, xsn, origin ) {
891
+ if (!universalCsn || hasExplicitProp( xsn.type ))
892
+ return;
810
893
  if (xsn._from) {
811
- csn.$origin = originRef( xsn._from[0]._origin );
894
+ const source = xsn._from[0]._origin;
895
+ csn.$origin = originRef( source );
896
+ if (source.params && !xsn.params)
897
+ csn.params = null; // discontinue `params` inheritance
898
+ if (source.actions && !xsn.actions)
899
+ csn.actions = null; // discontinue `actions` inheritance
900
+ return;
812
901
  }
813
- else if (xsn.includes && xsn.includes.length > 1) {
814
- csn.$origin = { $origin: originRef( xsn.includes[0]._artifact ) };
902
+ else if (xsn.includes) {
903
+ csn.$origin = includesOrigin( xsn.includes, xsn );
904
+ return;
815
905
  }
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 );
906
+ else if (!xsn._main || xsn.kind === 'select') {
907
+ return;
908
+ }
909
+ const parent = getParent( xsn );
910
+ const parentOrigin = getOrigin( parent );
911
+ if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
912
+ if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
913
+ csn.$origin = null;
914
+ return;
915
+ }
916
+ // Skip all proxies which do not make it into the CSN, as there are no
917
+ // individual annotations or redirection targets on it:
918
+ while (origin._parent && origin._parent.$expand === 'origin')
919
+ origin = origin._origin || origin.type._artifact;
920
+ // The while loop is not only for the else case below: when setting implicit
921
+ // prototypes, it is important that we do not have to follow the prototypes of
922
+ // other object; we would need to ensure a right order to avoid issues otherwise.
923
+ if (parentOrigin === getParent( origin )) {
924
+ // implicit prototype or shortened reference
925
+ const { id } = origin.name || {};
926
+ if (id && xsn.name && id !== xsn.name.id)
927
+ csn.$origin = id;
928
+ return;
929
+ }
930
+ if (origin.kind === 'mixin') {
931
+ // currently, target and on are always set - nothing to do here, just set type
932
+ csn.type = 'cds.Association';
933
+ return;
934
+ }
935
+ const ref = originRef( origin, xsn );
936
+ if (ref) {
937
+ csn.$origin = ref;
938
+ return;
939
+ }
940
+ // An element of a query with a query in FROM:
941
+ const anon = definition( origin ); // use $origin: {...} if necessary
942
+ // as there are no implicit $origin prototypes on sub query elements (yet),
943
+ // we do not have to care about $origin not being set
944
+ const { $origin } = anon;
945
+ if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
946
+ // repeated anon: flatten
947
+ csn.$origin = Object.assign( $origin, anon );
948
+ }
949
+ else if (Object.keys( anon )
950
+ // (we can use the properties in `csn`, because addOrigin() is called last)
951
+ .every( p => p in csn || p === '$origin' || p === '$location')) {
952
+ // nothing new in $origin: {...}
953
+ addOrigin( csn, xsn, origin._origin );
954
+ }
955
+ else {
956
+ csn.$origin = anon;
821
957
  }
822
- return csn;
958
+ }
959
+
960
+ function getParent( art ) {
961
+ const parent = art._parent;
962
+ const main = parent._main;
963
+ return (main && parent === main._leadingQuery) ? main : parent;
964
+ }
965
+
966
+ function getOrigin( art ) {
967
+ if (art._origin)
968
+ return art._origin;
969
+ if (hasExplicitProp( art.type ))
970
+ return art.type._artifact;
971
+ if (art.includes)
972
+ return art.includes[0]._artifact;
973
+ if (art._from)
974
+ return art._from[0]._origin;
975
+ return undefined;
823
976
  }
824
977
 
825
978
  function hasExplicitProp( ref ) {
826
979
  return ref && !ref.$inferred;
827
980
  }
828
981
 
829
- function originRef( art ) {
982
+ function originRef( art, user ) {
830
983
  const r = [];
831
984
  // 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;
838
- }
839
- if (main._main) // well, an element of an query in FROM
840
- return definition( art ); // use $origin: {}
985
+ let parent = art;
986
+ while (parent._main && parent.kind !== 'select') {
987
+ const nkind = normalizedKind[parent.kind];
988
+ if (parent.name.id || !r.length)
989
+ // Return parameter is in XSN - kind: 'param', name.id: ''
990
+ // eslint-disable-next-line no-nested-ternary, max-len
991
+ r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
992
+ parent = parent._parent;
993
+ }
994
+ if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
995
+ // well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
996
+ return null; // probably use $origin: {...}
841
997
  // for sub query in FROM in sub query in FROM, we could condense the info
998
+
999
+ // Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
1000
+ if (r.length === 1 && normalizedKind[art.kind] === 'action')
1001
+ return [ art.name.absolute, art.name.id ];
842
1002
  r.push( art.name.absolute );
843
1003
  r.reverse();
844
1004
  return r;
845
1005
  }
846
1006
 
847
1007
  function kind( k, csn, node ) {
1008
+ if (node.$inferred === 'REDIRECTED')
1009
+ return;
848
1010
  if (k === 'annotate' || k === 'extend') {
849
1011
  // We just use `name.absolute` because it is very likely a "constructed"
850
1012
  // extensions. The CSN parser must produce name.path like for other refs.
@@ -853,22 +1015,47 @@ function kind( k, csn, node ) {
853
1015
  else if (k === 'extend')
854
1016
  csn.kind = k;
855
1017
  }
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 );
1018
+ else if (k === 'action' && node._main && universalCsn && node.$inferred) {
1019
+ // Universal CSN: do not mention kind: 'action' on expanded action
863
1020
  }
1021
+ else if (![
1022
+ 'element', 'key', 'param', 'enum', 'select', '$join',
1023
+ '$tableAlias', 'annotation', 'mixin',
1024
+ ].includes(k)) {
1025
+ csn.kind = k;
1026
+ }
1027
+ const generated = universalCsn && inferredAsGenerated[node.$inferred];
1028
+ if (typeof generated === 'string')
1029
+ csn.$generated = generated;
864
1030
  }
865
1031
 
866
1032
  function type( node, csn, xsn ) {
867
- if (universalCsn && node.$inferred && xsn._origin)
1033
+ if (!universalCsn)
1034
+ return artifactRef( node, !node.$extra );
1035
+ if (node.$inferred)
1036
+ return undefined;
1037
+ if (xsn._origin) {
1038
+ if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
1039
+ csn.$origin = definition( xsn._origin );
1040
+ return undefined;
1041
+ }
1042
+ }
1043
+ else if ( xsn.targetAspect && xsn.target ) {
1044
+ // type moved to $origin: { type: … }
868
1045
  return undefined;
1046
+ }
869
1047
  return artifactRef( node, !node.$extra );
870
1048
  }
871
1049
 
1050
+ function cardinality( node, csn, xsn ) {
1051
+ if (!universalCsn)
1052
+ return standard( node );
1053
+ // cardinality might be moved to $origin: { cardinality: … }
1054
+ if (node.$inferred || xsn.targetAspect && !xsn.targetAspect.$inferred && xsn.target)
1055
+ return undefined;
1056
+ return standard( node );
1057
+ }
1058
+
872
1059
  function artifactRef( node, terse ) {
873
1060
  // When called as transformer function, a CSN node is provided as argument
874
1061
  // for `terse`, i.e. it is usually truthy, except for FROM
@@ -985,7 +1172,7 @@ function value( node ) {
985
1172
  if (node.literal === 'array')
986
1173
  return node.val.map( value );
987
1174
  if (node.literal === 'token' && node.val === '...')
988
- return extra( { '...': true } );
1175
+ return extra( { '...': !node.upTo || value( node.upTo ) } );
989
1176
  if (node.literal !== 'struct')
990
1177
  // no val (undefined) as true only for annotation values (and struct elem values)
991
1178
  return node.name && !('val' in node) || node.val;
@@ -997,7 +1184,10 @@ function value( node ) {
997
1184
 
998
1185
  function enumValue( v, csn, node ) {
999
1186
  // Enums can have values but if enums are extended, their kind is 'element',
1000
- // so we check whether the node is inside an extension.
1187
+ // so we check whether the node is inside an extension. (TODO: still?)
1188
+ if (universalCsn && v.$inferred)
1189
+ return;
1190
+ // (with gensrc, the symbol itself would not make it into the CSN)
1001
1191
  if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
1002
1192
  Object.assign( csn, expression( v, true ) );
1003
1193
  }
@@ -1185,7 +1375,7 @@ function columns( xsnColumns, csn, xsn ) {
1185
1375
  addElementAsColumn( col, csnColumns );
1186
1376
  }
1187
1377
  }
1188
- else { // null = use elements
1378
+ else { // null = use elements - TODO: still used by A2J? -> remove
1189
1379
  for (const name in xsn.elements)
1190
1380
  addElementAsColumn( xsn.elements[name], csnColumns );
1191
1381
  }
@@ -1335,6 +1525,9 @@ function compactExpr( e ) { // TODO: options
1335
1525
  return e && expression( e, true );
1336
1526
  }
1337
1527
 
1528
+ /**
1529
+ * @param {CSN.Options} options
1530
+ */
1338
1531
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1339
1532
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1340
1533
  options.toCsn && options.toCsn.flavor === 'gensrc';
@@ -112,7 +112,7 @@ const rules = {
112
112
  expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
113
113
  };
114
114
 
115
- function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions, rule = 'cdl' ) {
115
+ function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions = null, rule = 'cdl' ) {
116
116
  const lexer = new Lexer( new antlr4.InputStream(source) );
117
117
  const tokenStream = new RewriteTypeTokenStream(lexer);
118
118
  /** @type {object} */
@@ -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.type === parser.constructor.DocComment && !token.isUsed) {
170
+ messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(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',
@@ -69,7 +69,7 @@ function parseDocComment(comment) {
69
69
  * @param {string} content
70
70
  */
71
71
  function isWhiteSpaceOnly(content) {
72
- return content.trim().length === 0;
72
+ return /^\s*$/.test(content);
73
73
  }
74
74
 
75
75
  /**