@sap/cds-compiler 3.0.2 → 3.1.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 (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. package/lib/checks/unknownMagic.js +0 -41
@@ -73,14 +73,8 @@ function populate( model ) {
73
73
  /** @type {any} may also be a boolean */
74
74
  let newAutoExposed = [];
75
75
 
76
- // behavior depending on option `deprecated`:
77
- const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
78
- // TODO: we should get rid of noElementsExpansion soon; both
79
- // beta.nestedProjections and beta.universalCsn do not work with it.
80
76
  const scopedRedirections
81
- = enableExpandElements &&
82
- !isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
83
- !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
77
+ = !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
84
78
  !isDeprecatedEnabled( options, '_longAutoexposed' ) &&
85
79
  !isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
86
80
  !isDeprecatedEnabled( options, '_noScopedRedirections' );
@@ -109,6 +103,8 @@ function populate( model ) {
109
103
  function traverseElementEnvironments( art ) {
110
104
  populateView( art );
111
105
  environment( art );
106
+ if (art.elements$)
107
+ mergeSpecifiedElements(art);
112
108
  forEachMember( art, traverseElementEnvironments );
113
109
  }
114
110
 
@@ -165,7 +161,7 @@ function populate( model ) {
165
161
  // console.log(message( null, art.location, art, {}, 'Info','FT').toString())
166
162
  const chain = [];
167
163
  while (art && !('_effectiveType' in art) &&
168
- (art.type || art._origin || art.value && art.value.path) &&
164
+ (art.type || art._origin || art.value?.path || art.value?.type) &&
169
165
  // TODO: really stop at art.enum? See #8942
170
166
  !art.target && !art.enum && !art.elements && !art.items) {
171
167
  chain.push( art );
@@ -195,7 +191,7 @@ function populate( model ) {
195
191
  let eType = art;
196
192
  if (eType._outer)
197
193
  eType = effectiveType( eType._outer );
198
- // collect the "latest" cardinality (calculate lazyly if necessary)
194
+ // collect the "latest" cardinality (calculate lazily if necessary)
199
195
  let cardinality = art.cardinality ||
200
196
  art._effectiveType && (() => getCardinality( art._effectiveType ));
201
197
  let prev = art;
@@ -222,10 +218,12 @@ function populate( model ) {
222
218
  return art._origin;
223
219
  if (art.type)
224
220
  return resolveType( art.type, art );
221
+ if (art.value?.type)
222
+ return resolveType( art.value.type, art );
225
223
  // console.log( 'EXPR-IN', art.kind, refString(art.name) )
226
224
  if (!art._main || !art.value || !art.value.path)
227
225
  return undefined;
228
- if (art._pathHead && art.value) {
226
+ if (art._pathHead && art.value.path) {
229
227
  setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
230
228
  return art._origin;
231
229
  }
@@ -295,7 +293,7 @@ function populate( model ) {
295
293
 
296
294
 
297
295
  function expandItems( art, origin, eType ) {
298
- if (!enableExpandElements || art.items)
296
+ if (art.items)
299
297
  return false;
300
298
  if (isInParents( art, eType )) {
301
299
  art.items = 0; // circular
@@ -312,8 +310,6 @@ function populate( model ) {
312
310
  }
313
311
 
314
312
  function expandElements( art, struct, eType ) {
315
- if (!enableExpandElements)
316
- return false;
317
313
  if (art.elements || art.kind === '$tableAlias' ||
318
314
  // no element expansions for "non-proper" types like
319
315
  // entities (as parameter types) etc:
@@ -347,7 +343,7 @@ function populate( model ) {
347
343
  }
348
344
 
349
345
  function expandEnum( art, origin ) {
350
- if (!enableExpandElements || art.enum)
346
+ if (art.enum)
351
347
  return false;
352
348
  const ref = art.type || art.value || art.name;
353
349
  const location = weakLocation( ref && ref.location || art.location );
@@ -427,8 +423,6 @@ function populate( model ) {
427
423
  setLink( view, '_status', '_query' );
428
424
  // must be run in order “sub query in FROM first”:
429
425
  traverseQueryPost( view.query, null, populateQuery );
430
- if (view.elements$) // specified elements
431
- mergeSpecifiedElements( view );
432
426
  if (!view.$entity) {
433
427
  model._entities.push( view );
434
428
  view.$entity = ++model.$entity;
@@ -437,14 +431,25 @@ function populate( model ) {
437
431
  }
438
432
  }
439
433
 
440
- function mergeSpecifiedElements( view ) {
434
+ /**
435
+ * Merge _specified_ elements with _inferred_ elements in the given view/element,
436
+ * where specified elements can appear through CSN.
437
+ *
438
+ * We only copy annotations, since they are not part of `columns`,
439
+ * but only appear in `elements` in CSN.
440
+ *
441
+ * This is important to ensure re-compilability.
442
+ *
443
+ * @param art
444
+ */
445
+ function mergeSpecifiedElements( art ) {
441
446
  // Later we use specified elements as proxies to inferred of leading query
442
447
  // (No, we probably do not.)
443
- for (const id in view.elements) {
444
- const ielem = view.elements[id]; // inferred element
445
- const selem = view.elements$[id]; // specified element
448
+ for (const id in art.elements) {
449
+ const ielem = art.elements[id]; // inferred element
450
+ const selem = art.elements$[id]; // specified element
446
451
  if (!selem) {
447
- info( 'query-missing-element', [ ielem.name.location, view ], { id },
452
+ info( 'query-missing-element', [ ielem.name.location, art ], { id },
448
453
  'Element $(ID) is missing in specified elements' );
449
454
  }
450
455
  else {
@@ -454,10 +459,14 @@ function populate( model ) {
454
459
  ielem[prop] = selem[prop];
455
460
  }
456
461
  selem.$replacement = true;
462
+ if (selem.elements) {
463
+ setLink(ielem, 'elements$', selem.elements);
464
+ delete selem.elements;
465
+ }
457
466
  }
458
467
  }
459
- for (const id in view.elements$) {
460
- const selem = view.elements$[id]; // specified element
468
+ for (const id in art.elements$) {
469
+ const selem = art.elements$[id]; // specified element
461
470
  if (!selem.$replacement) {
462
471
  error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
463
472
  'Element $(ID) does not result from the query' );
@@ -474,8 +483,6 @@ function populate( model ) {
474
483
  forEachGeneric( query, '$tableAliases', resolveTabRef );
475
484
 
476
485
  initFromColumns( query, query.columns );
477
- // TODO: already in definer: complain about EXCLUDING with no wildcard
478
- // (would have been automatically with a good CDL syntax: `* without (...)`)
479
486
  if (query.excludingDict) {
480
487
  for (const name in query.excludingDict)
481
488
  resolveExcluding( name, query._combined, query.excludingDict, query );
@@ -1039,8 +1046,6 @@ function populate( model ) {
1039
1046
  // which is not a context/service/namespace, or the definition itself.
1040
1047
  // If inside service, it is the direct child of the (most inner) service.
1041
1048
  function definitionScope( art ) {
1042
- if (art._base) // with deprecated.generatedEntityNameWithUnderscore
1043
- return art._base;
1044
1049
  let base = art;
1045
1050
  while (art._parent) {
1046
1051
  if (art._parent.kind === 'service')
@@ -1112,10 +1117,8 @@ function populate( model ) {
1112
1117
  // the name for dependent entities have already been created using `_` then
1113
1118
  return `${ service.name.absolute }.${ name }`;
1114
1119
  }
1115
- if (isDeprecatedEnabled( options, '_longAutoexposed' )) {
1116
- const dedot = isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' );
1117
- return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
1118
- }
1120
+ if (isDeprecatedEnabled( options, '_longAutoexposed' ))
1121
+ return `${ service.name.absolute }.${ absolute }`;
1119
1122
  const base = definitionScope( target );
1120
1123
  if (base === target)
1121
1124
  return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
@@ -1,4 +1,10 @@
1
- //
1
+ // Propagate properties in XSN
2
+
3
+ // See also internalDoc/PropagatedCsn.md.
4
+ // As opposed to that document, the propagator here works on XSN, not CSN.
5
+ // We also do not deep-copy member dictionaries here, but create proxy members
6
+ // which get their properties via propagation: we use function `onlyViaParent`
7
+ // if that property would not be propagated otherwise.
2
8
 
3
9
  'use strict';
4
10
 
@@ -11,6 +17,7 @@ const {
11
17
  const { setLink, linkToOrigin, withAssociation } = require('./utils');
12
18
  // const { refString } = require( '../base/messages')
13
19
 
20
+ // Note that propagation here is also used for deep-copying (function `onlyViaParent`)
14
21
  function propagate( model ) {
15
22
  const props = {
16
23
  '@com.sap.gtt.core.CoreModel.Indexable': never,
@@ -56,7 +63,6 @@ function propagate( model ) {
56
63
  returns,
57
64
  };
58
65
  const { options } = model;
59
- const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
60
66
  // eslint-disable-next-line max-len
61
67
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
62
68
 
@@ -189,7 +195,7 @@ function propagate( model ) {
189
195
  if (!type || type._main)
190
196
  return false;
191
197
  // We do not consider the $expand status, as elements are already expanded
192
- // by the resolve(), and if not due to deprecated._noElementsExpansion
198
+ // by the resolve()
193
199
  run( type );
194
200
  return type[prop];
195
201
  }
@@ -313,11 +319,10 @@ function propagate( model ) {
313
319
 
314
320
  function items( prop, target, source ) {
315
321
  // usually considered expensive, except:
316
- // - array of Entity (smooth upgrade: array of String(3), array of DerivedScalar)
322
+ // - array of Entity
317
323
  const line = availableAtType( prop, target, source );
318
324
  if (!line ||
319
- line.type && line.type._artifact && line.type._artifact.kind === 'entity' ||
320
- !line.elements && !line.enum && !line.items && !enableExpandElements)
325
+ line.type && line.type._artifact && line.type._artifact.kind === 'entity')
321
326
  returns( prop, target, source, true );
322
327
  }
323
328
  }
@@ -87,6 +87,7 @@ function resolve( model ) {
87
87
  } = model.$messageFunctions;
88
88
  const {
89
89
  resolvePath,
90
+ checkAnnotate,
90
91
  defineAnnotations,
91
92
  attachAndEmitValidNames,
92
93
  lateExtensions,
@@ -103,11 +104,6 @@ function resolve( model ) {
103
104
 
104
105
  /** @type {any} may also be a boolean */
105
106
 
106
- // behavior depending on option `deprecated`:
107
- const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
108
- // TODO: we should get rid of noElementsExpansion soon; both
109
- // beta.nestedProjections and beta.universalCsn do not work with it.
110
-
111
107
  return doResolve();
112
108
 
113
109
  function doResolve() {
@@ -314,7 +310,7 @@ function resolve( model ) {
314
310
  if (obj.type) // TODO: && !obj.type.$inferred ?
315
311
  resolveTypeExpr( obj, art );
316
312
  const type = effectiveType( obj ); // make sure implicitly redirected target exists
317
- if (!obj.items && type && type.items && enableExpandElements) {
313
+ if (!obj.items && type && type.items) {
318
314
  // TODO: shouldn't be this part of populate.js ?
319
315
  const items = {
320
316
  location: weakLocation( (obj.type || obj).location ),
@@ -327,8 +323,7 @@ function resolve( model ) {
327
323
  }
328
324
  if (obj.items) { // TODO: make this a while in v2 (also items proxy)
329
325
  obj = obj.items || obj; // the object which has type properties
330
- if (enableExpandElements)
331
- effectiveType(obj);
326
+ effectiveType(obj);
332
327
  }
333
328
  if (obj.type) { // TODO: && !obj.type.$inferred ?
334
329
  if (obj !== (art.returns || art)) // not already checked
@@ -516,6 +511,8 @@ function resolve( model ) {
516
511
  setArtifactLink( ext.name, art );
517
512
 
518
513
  if (art) {
514
+ if (art.kind === 'annotate')
515
+ checkAnnotate( ext, art );
519
516
  defineAnnotations( ext, art, ext._block, ext.kind );
520
517
  // eslint-disable-next-line no-shadow
521
518
  forEachMember( ext, ( elem, name, prop ) => {
@@ -538,7 +535,7 @@ function resolve( model ) {
538
535
  }
539
536
  else if (prop === 'actions') {
540
537
  if (!feature) {
541
- warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
538
+ warning( 'anno-unexpected-actions', [ ext.name.location, art._parent || art ], {},
542
539
  'Actions and functions only exist top-level and for entities' );
543
540
  }
544
541
  else {
@@ -569,7 +566,7 @@ function resolve( model ) {
569
566
  // Currently(?), effectiveType() does not calculate the effective type of
570
567
  // its line item:
571
568
  effectiveType( obj );
572
- if (art._annotate.elements)
569
+ if (art._annotate.elements) // explicit $expand on aor needed
573
570
  setExpandStatusAnnotate( aor, 'annotate' );
574
571
  annotate( obj, 'element', 'elements', 'enum', art );
575
572
  annotate( art, 'action', 'actions' );
@@ -593,6 +590,8 @@ function resolve( model ) {
593
590
  // eslint-disable-next-line no-shadow
594
591
  function annotate( obj, kind, prop, altProp, parent = obj ) {
595
592
  const dict = art._annotate[prop];
593
+ if (dict && art._annotate[prop])
594
+ setExpandStatusAnnotate( art, 'annotate' );
596
595
  const env = obj[prop] || altProp && obj[altProp] || null;
597
596
  for (const n in dict)
598
597
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
@@ -615,7 +614,7 @@ function resolve( model ) {
615
614
 
616
615
  function expandParameters( action ) {
617
616
  // see also expandElements()
618
- if (!enableExpandElements || !effectiveType( action ))
617
+ if (!effectiveType( action ))
619
618
  return;
620
619
  const chain = [];
621
620
  // Should we be able to consider params and returns separately?
@@ -956,7 +955,7 @@ function resolve( model ) {
956
955
  resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
957
956
  if (query.orderBy) { // ORDER BY
958
957
  // search in `query.elements` after having checked table aliases of the current query
959
- resolveBy( query.orderBy, 'expr', query.elements );
958
+ resolveBy( query.orderBy, 'order-by', query.elements );
960
959
  // TODO: disallow resulting element ref if in expression!
961
960
  // Necessary to check it in the compiler as it might work with other semantics on DB!
962
961
  // (we could downgrade it to a warning if name is equal to unique source element name)
@@ -1264,7 +1263,7 @@ function resolve( model ) {
1264
1263
  const typeArt = resolveType( art.type, user );
1265
1264
  if (typeArt) {
1266
1265
  resolveTypeArgumentsUnchecked( art, typeArt, user );
1267
- checkTypeArguments( art );
1266
+ checkTypeArguments( art, typeArt );
1268
1267
  }
1269
1268
  }
1270
1269
 
@@ -1272,13 +1271,13 @@ function resolve( model ) {
1272
1271
  * Check the type arguments on `artWithType`.
1273
1272
  * If the effective type is an array or structured type, an error is emitted.
1274
1273
  */
1275
- function checkTypeArguments( artWithType ) {
1274
+ function checkTypeArguments( artWithType, typeArt ) {
1276
1275
  // Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
1277
1276
  // Also: For enums, it points to the enum type, which is why this trick is needed.
1278
1277
  // TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
1279
1278
  // trick may be removed if effectiveType() does not stop at enums.
1280
1279
  const cyclic = new Set();
1281
- let effectiveTypeArt = effectiveType( artWithType );
1280
+ let effectiveTypeArt = effectiveType( typeArt );
1282
1281
  while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
1283
1282
  cyclic.add(effectiveTypeArt);
1284
1283
  const underlyingEnumType = directType(effectiveTypeArt);
@@ -5,6 +5,7 @@
5
5
 
6
6
  const { searchName } = require('../base/messages');
7
7
  const { dictAddArray } = require('../base/dictionaries');
8
+ const { isDeprecatedEnabled } = require('../base/model');
8
9
 
9
10
  const {
10
11
  setLink,
@@ -144,6 +145,13 @@ function fns( model ) {
144
145
  rewrite: {
145
146
  next: '_$next', dollar: true, escape: 'param', noDep: true, rewrite: true,
146
147
  }, // TODO: assertion that there is no next/escape used
148
+ 'order-by': {
149
+ next: '_$next',
150
+ dollar: true,
151
+ escape: 'param',
152
+ assoc: 'nav',
153
+ deprecatedSourceRefs: true,
154
+ },
147
155
  'order-by-union': {
148
156
  next: '_$next', dollar: true, escape: 'param', noDep: true, noExt: true,
149
157
  },
@@ -160,6 +168,7 @@ function fns( model ) {
160
168
  resolveUncheckedPath,
161
169
  resolveTypeArgumentsUnchecked,
162
170
  resolvePath,
171
+ checkAnnotate,
163
172
  defineAnnotations,
164
173
  attachAndEmitValidNames,
165
174
  } );
@@ -602,6 +611,21 @@ function fns( model ) {
602
611
  else if (r) {
603
612
  return setArtifactLink( head, r );
604
613
  }
614
+ else if (spec.deprecatedSourceRefs && env._combined &&
615
+ isDeprecatedEnabled( options, 'autoCorrectOrderBySourceRefs' )) {
616
+ // User has provided a source element without table alias where a query
617
+ // element is expected. Possible on many DBs (and compiler v1), in CAP
618
+ // only with table alias. Auto-correct it if no duplicate.
619
+ // TODO: we could use that info also in messages when the deprecated flag is not set
620
+ const s = env._combined[head.id];
621
+ if (s && !Array.isArray(s)) {
622
+ path.$prefix = s.name.alias; // pushing it to path directly could be problematic
623
+ warning( null, [ head.location, user ],
624
+ { id: head.id, newcode: `${ s.name.alias }.${ head.id }` },
625
+ 'Replace source element reference $(ID) by $(NEWCODE); auto-corrected' );
626
+ return setArtifactLink( head, s );
627
+ }
628
+ }
605
629
  }
606
630
  if (spec.noMessage || msgArt === true && extDict === model.definitions)
607
631
  return null;
@@ -861,35 +885,38 @@ function fns( model ) {
861
885
  }
862
886
  }
863
887
 
888
+ // Issue messages for annotations on namespaces and builtins
889
+ // (TODO: really here?, probably split main artifacts vs returns)
890
+ // see also lateExtensions() where similar messages are reported
891
+ function checkAnnotate( construct, art ) {
892
+ // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
893
+ // they can still be applied. Namespace annotations are extracted in to-csn.js
894
+ // In parseCdl mode USINGs and other unknown references are generated as
895
+ // namespaces which would lead to false positives.
896
+ // TODO: should this really be different to annotate-unknown?
897
+ if (art.kind === 'namespace') {
898
+ info( 'anno-namespace', [ construct.name.location, construct ], {},
899
+ 'Namespaces can\'t be annotated' );
900
+ }
901
+ // Builtin annotations would also get lost. Same as for namespaces:
902
+ // extracted in to-csn.js
903
+ else if (art.builtin === true) {
904
+ info( 'anno-builtin', [ construct.name.location, construct ], {},
905
+ 'Builtin types should not be annotated. Use custom type instead' );
906
+ }
907
+ else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
908
+ art.kind !== 'function' ) {
909
+ // `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
910
+ // for non-actions. We can't only check for !art.returns, because `action A();` is valid.
911
+ // `art._block` ensures that `art` is a defined def.
912
+ warning('anno-unexpected-returns', [ construct.name.location, construct ],
913
+ { keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
914
+ }
915
+ }
916
+
864
917
  // Set _block links for annotations (necessary for layering).
865
- // Issue messages for annotations on namespaces and builtins (TODO: really here?)
866
918
  // Also copy annotations from `construct` to `art` (TODO: separate that functionality).
867
919
  function defineAnnotations( construct, art, block, priority = false ) {
868
- if (!options.parseCdl && construct.kind === 'annotate') {
869
- // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
870
- // they can still be applied. Namespace annotations are extracted in to-csn.js
871
- // In parseCdl mode USINGs and other unknown references are generated as
872
- // namespaces which would lead to false positives.
873
- // TODO: should this really be different to annotate-unknown?
874
- if (art.kind === 'namespace') {
875
- info( 'anno-namespace', [ construct.name.location, construct ], {},
876
- 'Namespaces can\'t be annotated' );
877
- }
878
- // Builtin annotations would also get lost. Same as for namespaces:
879
- // extracted in to-csn.js
880
- else if (art.builtin === true) {
881
- info( 'anno-builtin', [ construct.name.location, construct ], {},
882
- 'Builtin types should not be annotated. Use custom type instead' );
883
- }
884
- else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
885
- art.kind !== 'function' ) {
886
- // `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
887
- // for non-actions. We can't only check for !art.returns, because `action A();` is valid.
888
- // `art._block` ensures that `art` is a defined def.
889
- warning('anno-unexpected-returns', [ construct.name.location, construct ],
890
- { keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
891
- }
892
- }
893
920
  if (construct.doc)
894
921
  art.doc = construct.doc; // e.g. through `extensions` array in CSN
895
922
 
@@ -3,7 +3,6 @@
3
3
  'use strict';
4
4
 
5
5
  const {
6
- isDeprecatedEnabled,
7
6
  forEachDefinition,
8
7
  forEachGeneric,
9
8
  forEachInOrder,
@@ -26,7 +25,6 @@ const $location = Symbol.for('cds.$location');
26
25
 
27
26
  // Export function of this file.
28
27
  function tweakAssocs( model ) {
29
- const { options } = model;
30
28
  // Get shared functionality and the message function:
31
29
  const {
32
30
  info, warning, error,
@@ -38,11 +36,6 @@ function tweakAssocs( model ) {
38
36
  } = model.$functions;
39
37
  const { environment } = model.$volatileFunctions;
40
38
 
41
- // behavior depending on option `deprecated`:
42
- const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
43
- // TODO: we should get rid of noElementsExpansion soon; both
44
- // beta.nestedProjections and beta.universalCsn do not work with it.
45
-
46
39
  // Phase 5: rewrite associations
47
40
  forEachDefinition( model, rewriteSimple );
48
41
  // TODO: sequence not good enough with derived type of structure with
@@ -113,7 +106,7 @@ function tweakAssocs( model ) {
113
106
 
114
107
  function rewriteAssociationCheck( element ) {
115
108
  const elem = element.items || element; // TODO v2: nested items
116
- if (elem.elements && enableExpandElements)
109
+ if (elem.elements)
117
110
  forEachGeneric( elem, 'elements', rewriteAssociationCheck );
118
111
  if (!elem.target)
119
112
  return;
@@ -207,7 +200,7 @@ function tweakAssocs( model ) {
207
200
 
208
201
  function rewriteAssociation( element ) {
209
202
  let elem = element.items || element; // TODO v2: nested items
210
- if (elem.elements && enableExpandElements)
203
+ if (elem.elements)
211
204
  forEachGeneric( elem, 'elements', rewriteAssociation );
212
205
  if (!originTarget( elem ))
213
206
  return;
@@ -286,7 +279,7 @@ function tweakAssocs( model ) {
286
279
  // same (TODO later: set status whether rewrite changes anything),
287
280
  // especially problematic are refs starting with $self:
288
281
  setExpandStatus( elem, 'target' );
289
- if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
282
+ if (elem._parent && elem._parent.kind === 'element') {
290
283
  // managed association as sub element not supported yet
291
284
  error( null, [ elem.location, elem ], {},
292
285
  // eslint-disable-next-line max-len
@@ -369,7 +362,8 @@ function tweakAssocs( model ) {
369
362
  const item = expr.path[root.kind === '$self' ? 1 : 0];
370
363
  if (!item)
371
364
  return; // just $self
372
- const elem = assoc._main.elements[item.id]; // corresponding elem in including structure
365
+ // corresponding elem in including structure
366
+ const elem = (assoc._main.items || assoc._main).elements[item.id];
373
367
  if (!(Array.isArray(elem) || // no msg for redefs
374
368
  elem === item._artifact || // redirection for explicit def
375
369
  elem._origin === item._artifact)) {
@@ -95,6 +95,14 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
95
95
  return elem;
96
96
  }
97
97
 
98
+ /**
99
+ * Set the member `elem` to have a _parent link to `parent` and a corresponding
100
+ * _main link. Also set the member's name accordingly, where argument `name`
101
+ * is most often the property `elem.name.id`.
102
+ *
103
+ * If argument `prop` is provided, add `elem` to the dictionary of that name,
104
+ * e.g. `elements`.
105
+ */
98
106
  function setMemberParent( elem, name, parent, prop ) {
99
107
  if (prop) { // extension or structure include
100
108
  // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
@@ -107,9 +115,10 @@ function setMemberParent( elem, name, parent, prop ) {
107
115
  parent = parent._outer;
108
116
  setLink( elem, '_parent', parent );
109
117
  setLink( elem, '_main', parent._main || parent );
110
- const parentName = parent.name || parent._outer.name;
111
- elem.name.absolute = parentName.absolute;
112
- if (name == null)
118
+ const parentName = parent.name || parent._outer?.name;
119
+ if (parentName) // may not be available in e.g. cast() - TODO recheck (#9503)
120
+ elem.name.absolute = parentName.absolute;
121
+ if (!parentName || name == null)
113
122
  return;
114
123
  const normalized = kindProperties[elem.kind].normalized || elem.kind;
115
124
  [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
@@ -357,7 +366,7 @@ function traverseQueryExtra( main, callback ) {
357
366
  // that value is only on elements, types, and params -> no other members
358
367
  // when set, only on elem/art with expanded elements
359
368
  // - 'target': all expanded (sub) elements might only have new target/on, but
360
- // no indivual annotations on any (sub) member
369
+ // no individual annotations on any (sub) member
361
370
  // when set, traverse all parents where the value has been 'origin' before
362
371
  // - 'annotate': at least one inferred (sub) member has an individual annotation,
363
372
  // not counting propagated ones; set up to the definition (main artifact)
@@ -58,7 +58,7 @@ function preprocessAnnotations(csn, serviceName, options) {
58
58
  let art = null;
59
59
 
60
60
  forEachDefinition(csn, (artifact, artifactName) => {
61
- if(artifactName == serviceName || artifactName.startsWith(serviceName + '.')) {
61
+ if(artifactName === serviceName || artifactName.startsWith(serviceName + '.')) {
62
62
  art = artifactName;
63
63
  handleAnnotations(artifactName, artifact);
64
64
  artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
@@ -164,7 +164,7 @@ function preprocessAnnotations(csn, serviceName, options) {
164
164
  }
165
165
  else if (aNameWithoutQualifier === '@Common.ValueList.entity') {
166
166
  // if both annotations are present, ignore 'entity' and raise a message
167
- if (annoNames.map(x=>x.split('#')[0]).find(x=>(x=='@Common.ValueList.viaAssociation'))) {
167
+ if (annoNames.map(x=>x.split('#')[0]).find(x=>(x==='@Common.ValueList.viaAssociation'))) {
168
168
  warning(null, null, `in annotation preprocessing/@Common.ValueList: 'entity' is ignored, as 'viaAssociation' is present, ${ctx}`);
169
169
  return false;
170
170
  }
@@ -296,8 +296,12 @@ function preprocessAnnotations(csn, serviceName, options) {
296
296
 
297
297
  //change the scalar anno into a "pseudo-structured" one
298
298
  // TODO should be flattened, but then alphabetical order is destroyed
299
- let newTextAnno = { '$value': textAnno, '@UI.TextArrangement': value };
300
- carrier['@Common.Text'] = newTextAnno;
299
+
300
+ // Do not overwrite existing nested annotation values, instead give existing
301
+ // nested annotation precedence and remove outer annotation (always)
302
+ if(!carrier['@Common.Text.@UI.TextArrangement'] && textAnno) {
303
+ carrier['@Common.Text'] = { '$value': textAnno, '@UI.TextArrangement': value };
304
+ }
301
305
  delete carrier[aName];
302
306
  }
303
307
  }
@@ -7,7 +7,7 @@ const NAVPROP_TRENNER = '_';
7
7
  const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
- const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
10
+ const { initializeModel } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
13
  const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
@@ -326,7 +326,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
326
326
 
327
327
  /* create the entitytypes and sets
328
328
  Do not create an entity set if:
329
- V4 containment: _containerEntity is set and not equal with the artifact name
329
+ V4 containment: $containerNames is set and not equal with the artifact name
330
330
  Entity starts with 'localserviceNameized.' or ends with '_localized'
331
331
  */
332
332
  edmUtils.foreach(schemaCsn.definitions,
@@ -433,7 +433,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
433
433
  // CDXCORE-CDXCORE-173
434
434
  if(options.isV2() && hasStream) {
435
435
  attributes['m:HasStream'] = true;
436
- assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
+ edmUtils.assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
437
437
  }
438
438
 
439
439
  Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
package/lib/edm/edm.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const edmUtils = require('./edmUtils.js');
4
4
  const { isBuiltinType } = require('../model/csnUtils.js');
5
5
  const { forEach } = require("../utils/objectUtils");
6
+ const { isBetaEnabled } = require('../base/model.js');
6
7
 
7
8
  // facet definitions, optional could either be true or array of edm types
8
9
  // remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
@@ -754,7 +755,14 @@ function getEdm(options, messageFunctions) {
754
755
  }
755
756
  }
756
757
 
757
- class ComplexType extends TypeBase { }
758
+ class ComplexType extends TypeBase {
759
+ constructor(v, details, csn) {
760
+ super(v, details, csn);
761
+ if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
762
+ this._edmAttributes['OpenType'] = true;
763
+ }
764
+ }
765
+ }
758
766
  class EntityType extends ComplexType
759
767
  {
760
768
  constructor(v, details, properties, csn)