@sap/cds-compiler 3.9.4 → 4.0.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 (94) hide show
  1. package/CHANGELOG.md +92 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +26 -8
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +12 -4
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +18 -17
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/modelCompare/compare.js +1 -1
  63. package/lib/modelCompare/utils/filter.js +40 -2
  64. package/lib/optionProcessor.js +0 -3
  65. package/lib/render/toCdl.js +247 -214
  66. package/lib/render/toHdbcds.js +197 -181
  67. package/lib/render/toSql.js +325 -289
  68. package/lib/render/utils/common.js +42 -4
  69. package/lib/render/utils/delta.js +1 -1
  70. package/lib/render/utils/sql.js +3 -3
  71. package/lib/transform/braceExpression.js +2 -2
  72. package/lib/transform/db/.eslintrc.json +1 -1
  73. package/lib/transform/db/applyTransformations.js +3 -3
  74. package/lib/transform/db/associations.js +24 -12
  75. package/lib/transform/db/expansion.js +17 -18
  76. package/lib/transform/db/flattening.js +17 -21
  77. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  78. package/lib/transform/db/views.js +3 -4
  79. package/lib/transform/draft/db.js +21 -12
  80. package/lib/transform/draft/odata.js +4 -0
  81. package/lib/transform/forOdataNew.js +11 -10
  82. package/lib/transform/forRelationalDB.js +12 -7
  83. package/lib/transform/localized.js +4 -2
  84. package/lib/transform/odata/toFinalBaseType.js +5 -5
  85. package/lib/transform/odata/typesExposure.js +3 -3
  86. package/lib/transform/parseExpr.js +3 -0
  87. package/lib/transform/transformUtilsNew.js +43 -23
  88. package/lib/transform/translateAssocsToJoins.js +7 -6
  89. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  90. package/lib/transform/universalCsn/coreComputed.js +7 -5
  91. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  92. package/package.json +2 -2
  93. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  94. package/share/messages/message-explanations.json +1 -1
@@ -4,9 +4,10 @@
4
4
 
5
5
  const { searchName, weakLocation } = require('../base/messages');
6
6
  const {
7
- forEachInOrder, forEachDefinition,
7
+ forEachInOrder,
8
+ forEachDefinition,
8
9
  forEachMember,
9
- isBetaEnabled,
10
+ isDeprecatedEnabled,
10
11
  } = require('../base/model');
11
12
  const { dictAdd, pushToDict } = require('../base/dictionaries');
12
13
  const { kindProperties, dictKinds } = require('./base');
@@ -21,6 +22,7 @@ const {
21
22
  annotationHasEllipsis,
22
23
  } = require('./utils');
23
24
  const layers = require('./moduleLayers');
25
+ const { CompilerAssertion } = require('../base/error');
24
26
 
25
27
  const $location = Symbol.for('cds.$location');
26
28
 
@@ -29,7 +31,6 @@ const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
29
31
  // Array.prototype.spread = 42; // prototype-polluted JS classes
30
32
 
31
33
  function extend( model ) {
32
- const { options } = model;
33
34
  // Get simplified "resolve" functionality and the message function:
34
35
  const {
35
36
  message, error, warning, info,
@@ -49,6 +50,8 @@ function extend( model ) {
49
50
  applyIncludes, // TODO: re-check
50
51
  } );
51
52
 
53
+ const includesNonShadowedFirst = isDeprecatedEnabled(model.options, 'includesNonShadowedFirst');
54
+
52
55
  sortModelSources();
53
56
  const extensionsDict = Object.create(null); // TODO TMP
54
57
  forEachDefinition( model, tagIncludes ); // TODO TMP
@@ -78,8 +81,8 @@ function extend( model ) {
78
81
  function extendArtifactBefore( art ) {
79
82
  // for main artifacts, move extensions from `$collectedExtensions` model dictionary:
80
83
  if (!art._main && !art._outer && art._extensions === undefined &&
84
+ art.name && // TODO: probably just a workaround, check with TODO in getOriginRaw()
81
85
  art.kind !== 'namespace') {
82
- // if (!art.name) console.log(art)
83
86
  const { absolute } = art.name;
84
87
  setLink( art, '_extensions', model.$collectedExtensions[absolute]?._extensions || null );
85
88
  if (art._extensions && !art.builtin) { // keep extensions for builtin in $collectedExtensions
@@ -123,16 +126,16 @@ function extend( model ) {
123
126
  const scaleDiff = applyTypeExtensions( art, exts.scale, 'scale' );
124
127
  applyTypeExtensions( art, exts.precision, 'precision', scaleDiff );
125
128
  applyTypeExtensions( art, exts.srid, 'srid' );
129
+ checkPrecisionScaleExtension( art, exts );
130
+
126
131
  delete art.$typeExts;
127
132
  }
128
- // TODO tmp: no proper XSN representation yet for annotate … with returns:
129
- if (art.kind === 'annotate' && !art.returns &&
130
- (extensionsMap.elements?.some( e => e.$syntax === 'returns' ) ||
131
- extensionsMap.enum?.some( e => e.$syntax === 'returns' )))
133
+
134
+ if (art.kind === 'annotate' && !art.returns && extensionsMap.returns)
132
135
  annotateCreate( art, '', art, 'returns' );
133
136
 
134
137
  moveDictExtensions( art, extensionsMap, 'params' );
135
- // moveReturnsExtensions( art, extensionsMap );
138
+ moveReturnsExtensions( art, extensionsMap );
136
139
  const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;
137
140
  if (sub) {
138
141
  if (art.returns) { // after having applied params!
@@ -140,8 +143,8 @@ function extend( model ) {
140
143
  extendHandleReturns( extensionsMap.enum, art );
141
144
  }
142
145
  // care about 'ext-unexpected-returns' in a later change if we have XSN returns
143
- pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
144
- pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
146
+ pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'elements'));
147
+ pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'enum') );
145
148
  }
146
149
  else {
147
150
  moveDictExtensions( art, extensionsMap,
@@ -151,6 +154,25 @@ function extend( model ) {
151
154
  moveDictExtensions( art, extensionsMap, 'actions' );
152
155
  }
153
156
 
157
+ /**
158
+ * FIXME: Remove this workaround. This workaround avoids endless recursion for annotate statements
159
+ * that have both "returns" and "elements". The endless recursion happens due to the
160
+ * pushDict on `sub`. The `elements` extensions will point to the same extension on which
161
+ * `returns` exist.
162
+ *
163
+ * @param extensionsMap
164
+ * @param {string} prop
165
+ * @return {XSN.Extension[]}
166
+ */
167
+ function avoidRecursiveReturns( extensionsMap, prop ) {
168
+ if (!extensionsMap[prop])
169
+ return [];
170
+ const exts = [];
171
+ for (const ext of extensionsMap[prop])
172
+ exts.push({ ...ext, returns: undefined });
173
+ return exts;
174
+ }
175
+
154
176
  /**
155
177
  * Create super annotate statements for remaining extensions
156
178
  */
@@ -165,14 +187,16 @@ function extend( model ) {
165
187
  Object.values( model.definitions ).forEach( setArtifactLinkForExtensions );
166
188
  }
167
189
 
168
- // TODO: delete again
190
+ // TODO: delete again - if not, what about extensions in contexts/services?
169
191
  function setArtifactLinkForExtensions( source ) {
170
192
  if (!source.extensions)
171
193
  return;
172
194
  for (const ext of source.extensions ) {
173
195
  const { name } = ext;
174
- if (name?.absolute && name._artifact === undefined) // no link set yet
175
- resolvePath( name, ext.kind, ext ); // for LSP
196
+ if (name?.absolute && name._artifact === undefined) {
197
+ const refCtx = (name.absolute.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
198
+ resolvePath( name, refCtx, ext ); // for LSP
199
+ }
176
200
  }
177
201
  }
178
202
 
@@ -210,7 +234,7 @@ function extend( model ) {
210
234
  const dict = Object.create(null);
211
235
  for (const ext of art._extensions) {
212
236
  for (const prop in ext) {
213
- if (ext[prop] === undefined) // deleted propery
237
+ if (ext[prop] === undefined) // deleted property
214
238
  continue;
215
239
  // TODO: do this check nicer (after complete move to new extensions mechanism)
216
240
  if (prop.charAt(0) === '@' || prop === 'doc' ||
@@ -357,6 +381,8 @@ function extend( model ) {
357
381
  }
358
382
  else if (prop === 'columns') {
359
383
  const { query } = art;
384
+ for (const col of ext.columns)
385
+ col.$extended = true;
360
386
  if (!query?.from?.path)
361
387
  error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
362
388
  else if (!query.columns)
@@ -532,6 +558,24 @@ function extend( model ) {
532
558
  return 0;
533
559
  }
534
560
 
561
+ /**
562
+ * If the target artifact has both precision and scale set, then extensions on it must also
563
+ * provide both to avoid user errors for subsequent `extend` statements.
564
+ *
565
+ * @param {XSN.Artifact} art
566
+ * @param {object} exts
567
+ */
568
+ function checkPrecisionScaleExtension( art, exts ) {
569
+ if (art.precision && art.scale) {
570
+ if ((exts.precision || exts.scale) && !(exts.precision && exts.scale)) {
571
+ const missing = exts.precision ? 'scale' : 'precision';
572
+ const prop = exts.precision ? 'precision' : 'scale';
573
+ error( 'ext-missing-type-property', [ exts[prop].location, exts[prop] ],
574
+ { art, prop, otherprop: missing } );
575
+ }
576
+ }
577
+ }
578
+
535
579
  function allowsTypeArgument( art, prop ) {
536
580
  const { parameters } = art._effectiveType;
537
581
  if (!parameters)
@@ -548,8 +592,8 @@ function extend( model ) {
548
592
 
549
593
  for (const ext of extensions) {
550
594
  const extDict = ext[extProp];
595
+ let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
551
596
  for (const name in extDict) {
552
- let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
553
597
  const elemExt = extDict[name];
554
598
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
555
599
  continue; // definitions inside extend, already handled
@@ -561,13 +605,28 @@ function extend( model ) {
561
605
  }
562
606
  }
563
607
 
564
- // function moveReturnsExtensions( art, extensionsMap ) {
565
- // const artReturns = art.returns;
566
- // const extensions = extensionsMap.returns;
567
- // // TODO: artItem is null
568
- // for (const ext of extensions)
569
- // pushToDict( artReturns, '_extensions', ext.returns );
570
- // }
608
+ function moveReturnsExtensions( art, extensionsMap ) {
609
+ if (!extensionsMap.returns)
610
+ return;
611
+
612
+ for (const ext of extensionsMap.returns) {
613
+ if (!art.returns && art.kind !== 'annotate') { // no check in super annotate statement
614
+ const variant = art.kind === 'action' || art.kind === 'function' ? art.kind : 'std';
615
+ warning( 'ext-unexpected-returns', [ ext.returns.location, ext ], {
616
+ '#': variant, keyword: 'returns',
617
+ }, {
618
+ std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
619
+ action: 'Unexpected $(KEYWORD) for action without return parameter',
620
+ // function without `returns` can happen via CSN input!
621
+ function: 'Unexpected $(KEYWORD) for function without return parameter',
622
+ });
623
+ }
624
+ // If `!art.returns`, then it could be CSN from SQL, where actions are replaced by dummies.
625
+ const elem = art.returns || annotateFor( art, 'params', '' );
626
+ setLink( ext.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
627
+ pushToDict(elem, '_extensions', ext.returns);
628
+ }
629
+ }
571
630
 
572
631
  function annotateFor( art, prop, name ) {
573
632
  const base = annotateBase( art );
@@ -580,7 +639,7 @@ function extend( model ) {
580
639
  }
581
640
 
582
641
  function annotateBase( art ) {
583
- while (art._outer) // TOOD: think about anonymous target aspect
642
+ while (art._outer) // TODO: think about anonymous target aspect
584
643
  art = art._outer;
585
644
  if (art.kind === 'annotate')
586
645
  return art;
@@ -616,19 +675,13 @@ function extend( model ) {
616
675
 
617
676
  function extendHandleReturns( extensions, art ) {
618
677
  for (const ext of extensions || []) {
619
- if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
620
- ext.$syntax = '$inside-returns';
621
- delete ext.params;
622
- }
623
- else {
624
- warning( 'ext-expected-returns', [ ext.name.location, ext ], {
625
- '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
626
- }, {
627
- std: 'Expected $(CODE)', // unused variant
628
- action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
629
- function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
630
- } );
631
- }
678
+ warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
679
+ '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
680
+ }, {
681
+ std: 'Expected $(CODE)', // unused variant
682
+ action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
683
+ function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
684
+ } );
632
685
  }
633
686
  }
634
687
 
@@ -666,12 +719,9 @@ function extend( model ) {
666
719
  warning( 'anno-unexpected-params', [ location, ext._parent ], {},
667
720
  'Parameters only exist for actions or functions' );
668
721
  break;
669
- case 'actions':
670
- warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
671
- 'Actions and functions only exist top-level and for entities' );
672
- break;
673
722
  default:
674
- // assert
723
+ if (model.options.testMode)
724
+ throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
675
725
  }
676
726
  return false;
677
727
  }
@@ -767,17 +817,12 @@ function extend( model ) {
767
817
 
768
818
  // --> without art._block, art not found
769
819
  if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
770
- if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
771
- // `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
772
- // for non-actions. We can't only check for !art.returns, because `action A();` is valid.
773
- // `art._block` ensures that `art` is a defined def.
774
- return;
775
- // warning('ext-unexpected-returns', [ construct.name.location, construct ],
776
- // { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
820
+ if (construct.returns && art.kind !== 'action' && art.kind !== 'function' ) {
821
+ // See moveReturnsExtensions()
777
822
  }
778
- else if (construct.$syntax !== 'returns' &&
823
+ else if (!construct.returns &&
779
824
  (art.kind === 'action' || art.kind === 'function') && construct.elements) {
780
- warning('ext-expected-returns', [ construct.name.location, construct ], {
825
+ warning('ext-expecting-returns', [ construct.name.location, construct ], {
781
826
  '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
782
827
  }, {
783
828
  std: 'Expected $(CODE)', // unused variant
@@ -1084,13 +1129,21 @@ function extend( model ) {
1084
1129
  const parent = ext === art && art;
1085
1130
  const members = ext[prop];
1086
1131
  ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
1132
+ let hasNewElement = false;
1133
+
1087
1134
  for (const ref of ext.includes) {
1088
1135
  const template = ref._artifact; // already resolved
1089
1136
  if (template) { // be robust
1137
+ // eslint-disable-next-line no-loop-func
1090
1138
  forEachInOrder( template, prop, ( origin, name ) => {
1091
- if (members && name in members)
1092
- return; // warning for overwritten element in checks.js
1139
+ if (members && members[name]) {
1140
+ if (!includesNonShadowedFirst && !ext[prop][name])
1141
+ dictAdd( ext[prop], name, members[name] ); // to keep order
1142
+ return;
1143
+ }
1144
+ hasNewElement = true;
1093
1145
  const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
1146
+ setLink( elem, '_block', origin._block );
1094
1147
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1095
1148
  dictAdd( ext[prop], name, elem );
1096
1149
  elem.$inferred = 'include';
@@ -1101,6 +1154,7 @@ function extend( model ) {
1101
1154
  if (origin.value && origin.$syntax === 'calc') {
1102
1155
  // TODO: If paths become invalid in the new artifact, should we mark
1103
1156
  // all usages in the expressions? Possibly just the first one?
1157
+ // TODO: Unify with coding in extend.js
1104
1158
  elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
1105
1159
  elem.$syntax = 'calc';
1106
1160
  setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
@@ -1109,11 +1163,19 @@ function extend( model ) {
1109
1163
  });
1110
1164
  }
1111
1165
  }
1166
+
1112
1167
  checkRedefinitionThroughIncludes( parent, prop );
1113
- // TODO: expand elements having direct elements (if needed)
1114
- if (members) {
1168
+
1169
+ if (!hasNewElement && members) {
1170
+ ext[prop] = members;
1171
+ }
1172
+ else if (members) {
1173
+ // TODO: expand elements having direct elements (if needed)
1115
1174
  forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
1116
- dictAdd( ext[prop], name, elem );
1175
+ // The element could have been added in the previous loop (includes) to keep
1176
+ // the element order.
1177
+ if (ext[prop][name] !== elem )
1178
+ dictAdd( ext[prop], name, elem );
1117
1179
  });
1118
1180
  }
1119
1181
  }
@@ -1130,15 +1192,8 @@ function extend( model ) {
1130
1192
  forEachInOrder(parent, prop, ( member, name ) => {
1131
1193
  if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
1132
1194
  const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
1133
- if (isBetaEnabled(options, 'v4preview')) {
1134
- error( 'duplicate-definition', [ parent.name.location, member ],
1135
- { '#': `include-${ prop }`, name, sorted_arts: includes } );
1136
- }
1137
- else {
1138
- // Error accidentally removed in v2/v3, therefore only a warning.
1139
- warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
1140
- { '#': prop, name, sorted_arts: includes } );
1141
- }
1195
+ error( 'duplicate-definition', [ parent.name.location, member ],
1196
+ { '#': `include-${ prop }`, name, sorted_arts: includes } );
1142
1197
  }
1143
1198
  });
1144
1199
  }
@@ -13,8 +13,8 @@
13
13
 
14
14
  'use strict';
15
15
 
16
+ const { CompilerAssertion } = require('../base/error');
16
17
  const { forEachGeneric } = require('../base/model');
17
- const { setLink, setArtifactLink } = require('./utils');
18
18
 
19
19
 
20
20
  // Used for resolving types in parseCdl mode.
@@ -24,7 +24,6 @@ const parseCdlIgnoredXsnProps = [ 'location', 'query', '$tableAliases' ];
24
24
 
25
25
  function finalizeParseCdl( model ) {
26
26
  // Get simplified "resolve" functionality and the message function:
27
- const { message, error } = model.$messageFunctions;
28
27
  const {
29
28
  resolveUncheckedPath,
30
29
  resolveTypeArgumentsUnchecked,
@@ -41,7 +40,7 @@ function finalizeParseCdl( model ) {
41
40
  // TODO: why not just use the extensions as they are from the first source?
42
41
  for (const name in late) {
43
42
  for (const ext of late[name]._extensions) {
44
- ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
43
+ ext.name.absolute = resolveUncheckedPath( ext.name, '_extensions', ext );
45
44
  // Initialize members and define annotations in sub-elements.
46
45
  initMembers( ext, ext, ext._block, true );
47
46
  extensions.push( ext );
@@ -53,7 +52,7 @@ function finalizeParseCdl( model ) {
53
52
 
54
53
  if (extensions.length > 0) {
55
54
  // TODO: do a sort based on the location in case there were two extensions
56
- // on the same definition?
55
+ // on the same definition? Yes: anno first outside, then inside service def
57
56
  model.extensions = extensions;
58
57
  model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
59
58
  }
@@ -149,51 +148,21 @@ function finalizeParseCdl( model ) {
149
148
  * `artWithType` has the `type` property, i.e. it could be an `items` object.
150
149
  * `user` is the actual artifact, e.g. entity or element.
151
150
  *
151
+ * TODO: this should basically be covered by a function of shared.js
152
+ *
152
153
  * @param {object} artWithType
153
154
  * @param {XSN.Artifact} user
154
155
  */
155
156
  function resolveTypeUnchecked( artWithType, user ) {
156
- const root = artWithType.type.path && artWithType.type.path[0];
157
- if (!root) // parse error
158
- return;
159
- // `scope` is only `typeOf` for `type of element` and not
160
- // `type of Entity:element`. For the latter we can resolve the path
161
- // without special treatment.
162
- if (artWithType.type.scope !== 'typeOf') {
163
- // elem: Type or elem: type of Artifact:elem
164
- const name = resolveUncheckedPath( artWithType.type, 'type', user );
165
- const type = name && model.definitions[name] || { name: { absolute: name } };
157
+ const name = resolveUncheckedPath( artWithType.type, 'type', user );
158
+ if (name) { // correct ref to main artifact
159
+ const type = model.definitions[name];
166
160
  resolveTypeArgumentsUnchecked( artWithType, type, user );
167
161
  }
168
- else if (!user._main) {
169
- error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
170
- 'Current artifact has no element to refer to as type' );
171
- }
172
- else if (root.id === '$self' || root.id === '$projection') {
173
- setArtifactLink( root, user._main );
174
- }
175
- else {
176
- // For better error messages, check for invalid TYPE OFs similarly
177
- // to how `resolveType()` does.
178
- let struct = artWithType;
179
- // `items` have no kind, but need to be skipped as well
180
- while (struct.kind === 'element' || struct._outer?.items) {
181
- if (struct._outer?.items)
182
- struct = struct._outer;
183
- else
184
- struct = struct._parent;
185
- }
186
- if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
187
- message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
188
- { keyword: 'type of', '#': struct.kind } );
189
- return;
190
- }
191
-
192
- const fake = { name: { absolute: user.name.absolute } };
193
- // to-csn just needs a fake element whose absolute name and _parent/_main links are correct
194
- setLink( fake, '_parent', user._parent );
195
- setLink( fake, '_main', user._main ); // value does not matter...
196
- setArtifactLink( root, fake );
162
+ else if (name === '' && artWithType.$typeArgs && model.options.testMode) {
163
+ // Ensure: parser does not allow type args with Main:elem/`type of`,
164
+ // extend with type only with named type arguments
165
+ throw new CompilerAssertion( 'Unexpected type arguments for TYPE OF' );
197
166
  }
198
167
  }
199
168
  }
@@ -16,6 +16,7 @@ const {
16
16
  augmentPath,
17
17
  splitIntoPath,
18
18
  isDirectComposition,
19
+ copyExpr,
19
20
  } = require('./utils');
20
21
 
21
22
  function generate( model ) {
@@ -250,7 +251,8 @@ function generate( model ) {
250
251
  }
251
252
  else if (!art._block.$withLocalized && !options.$recompile) {
252
253
  art._block.$withLocalized = true;
253
- info( 'recalculated-text-entities', [ art.name.location, null ], {},
254
+ // no semantic loc: message only emitted once
255
+ info( 'def-unexpected-texts-entities', [ art.name.location, null ], {},
254
256
  'Input CSN contains expansions for localized data' );
255
257
  return textElems; // make compilation idempotent
256
258
  }
@@ -548,6 +550,7 @@ function generate( model ) {
548
550
  }
549
551
  else if (art.type && art._block && art.type.scope !== 'typeOf') {
550
552
  // TODO: also do something special for TYPE OF inside `art`s own elements
553
+ // TODO: check for own - add test case with Type:elem (not TYPE OF elem)
551
554
  name = resolveUncheckedPath( art.type, 'type', art );
552
555
  art = name && model.definitions[name];
553
556
  }
@@ -593,7 +596,7 @@ function generate( model ) {
593
596
  origin = origin._origin;
594
597
  let target = origin.targetAspect;
595
598
  if (target && target.path)
596
- target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
599
+ target = resolvePath( origin.targetAspect, 'targetAspect', origin );
597
600
  if (!target || !target.elements)
598
601
  return;
599
602
  const entityName = `${ base.name.absolute }.${ elem.name.id }`;
@@ -692,12 +695,11 @@ function generate( model ) {
692
695
  $inferred: 'aspect-composition',
693
696
  };
694
697
 
695
- const elements = Object.create(null);
696
698
  const art = {
697
699
  kind: 'entity',
698
700
  name: { path: splitIntoPath( location, entityName ), absolute: entityName, location },
699
701
  location,
700
- elements,
702
+ elements: Object.create(null),
701
703
  $inferred: 'composition-entity',
702
704
  };
703
705
  if (target.name) { // named target aspect
@@ -752,16 +754,25 @@ function generate( model ) {
752
754
  }
753
755
 
754
756
  function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
755
- // TODO: also use for includeMembers()?
757
+ // TODO: also use for includeMembers()? Both are similar. Combine?
756
758
  for (const name in elements) {
757
759
  const pname = `${ prefix }${ name }`;
758
760
  const origin = elements[name];
759
761
  const proxy = linkToOrigin( origin, pname, null, null, location || origin.location );
762
+ setLink( proxy, '_block', origin._block );
760
763
  proxy.$inferred = inferred;
761
764
  if (origin.masked)
762
765
  proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
763
766
  if (origin.key)
764
767
  proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
768
+ if (origin.value && origin.$syntax === 'calc') {
769
+ // TODO: If paths become invalid in the new artifact, should we mark
770
+ // all usages in the expressions? Possibly just the first one?
771
+ // TODO: Unify with coding in extend.js
772
+ proxy.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
773
+ proxy.$syntax = 'calc';
774
+ setLink( proxy, '_calcOrigin', origin._calcOrigin || origin );
775
+ }
765
776
  if (anno)
766
777
  setAnnotation( proxy, anno );
767
778
  dictAdd( proxyDict.elements, pname, proxy );
@@ -9,8 +9,6 @@
9
9
  // - The XSN is the only context which context-dependent functions can depend on.
10
10
  // - Sharing such a function is by adding it to `‹xsn›.$functions`,
11
11
  // e.g. `resolvePath` and similar will be attached to the XSN.
12
- // - Functions which might be overwritten in a next sub module
13
- // are added to `‹xsn›.$volatileFunctions`, currently just `environment`.
14
12
 
15
13
  'use strict';
16
14
 
@@ -55,6 +53,7 @@ const extensionParsers = {
55
53
  class InvocationError extends Error {
56
54
  constructor( errs, ...args ) {
57
55
  super(...args);
56
+ this.code = 'ERR_CDS_COMPILER_INVOCATION';
58
57
  this.errors = errs;
59
58
  this.hasBeenReported = false;
60
59
  }
@@ -65,6 +64,7 @@ class InvocationError extends Error {
65
64
  class ArgumentError extends Error {
66
65
  constructor( arg, ...args ) {
67
66
  super(...args);
67
+ this.code = 'ERR_CDS_COMPILER_ARGUMENT';
68
68
  this.argument = arg;
69
69
  }
70
70
  }
@@ -410,8 +410,12 @@ function compileSourcesX( sourcesDict, options = {} ) {
410
410
  * TODO: probably issue message api-recompiled-csn there.
411
411
  */
412
412
  function recompileX( csn, options ) {
413
- // Explicitly set parseCdl to false because backends cannot handle it
414
- options = { ...options, parseCdl: false, $recompile: true };
413
+ options = {
414
+ ...options,
415
+ parseCdl: false, // Explicitly set parseCdl to false because backends cannot handle it
416
+ docComment: null, // Input is CSN: leave doc comments alone
417
+ $recompile: true,
418
+ };
415
419
  // Reset csnFlavor: Use client style (default)
416
420
  delete options.csnFlavor;
417
421
  delete options.toCsn;
@@ -453,7 +457,6 @@ function compileDoX( model ) {
453
457
  return model;
454
458
  }
455
459
  model.$functions = {};
456
- model.$volatileFunctions = {};
457
460
  fns( model ); // attach (mostly) paths functions
458
461
  define( model );
459
462
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
@@ -77,7 +77,7 @@ function kickStart( model ) {
77
77
  (preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) )) {
78
78
  chain.push( art );
79
79
  setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
80
- const name = resolveUncheckedPath( art.query.from, 'include', art ); // TODO: 'include'?
80
+ const name = resolveUncheckedPath( art.query.from, 'from', art );
81
81
  art = name && model.definitions[name];
82
82
  if (autoexposed)
83
83
  break; // only direct projection for auto-exposed
@@ -146,9 +146,10 @@ function kickStart( model ) {
146
146
  if (art && art.$duplicates)
147
147
  continue;
148
148
  const ref = def.extern;
149
- const from = (topLevel ? def : src).fileDep;
149
+ const user = (topLevel ? def : src);
150
+ const from = user.fileDep;
150
151
  if (art || !from || from.realname) // no error for non-existing ref with non-existing module
151
- resolvePath( ref, 'global', def ); // TODO: consider FROM for validNames
152
+ resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
152
153
  }
153
154
  }
154
155
  }