@sap/cds-compiler 4.9.6 → 5.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -5,7 +5,6 @@
5
5
  const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
- isBetaEnabled,
9
8
  } = require('../base/model');
10
9
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
11
10
 
@@ -39,16 +38,17 @@ function tweakAssocs( model ) {
39
38
  checkOnCondition,
40
39
  effectiveType,
41
40
  getOrigin,
41
+ extendForeignKeys,
42
+ createRemainingAnnotateStatements,
43
+ mergeSpecifiedForeignKeys,
42
44
  } = model.$functions;
43
45
 
44
46
  Object.assign(model.$functions, {
45
47
  firstProjectionForPath,
46
48
  });
47
49
 
48
- const isV5preview = isBetaEnabled( model.options, 'v5preview' );
49
-
50
50
  // Phase 5: rewrite associations
51
- model._entities.forEach( rewriteArtifact );
51
+ model._entities.forEach( rewriteArtifact ); // _entities contains all definitions, sorted.
52
52
  // Think hard whether an on condition rewrite can lead to a new cyclic
53
53
  // dependency. If so, we need other messages anyway. TODO: probably dox
54
54
  // another cyclic check with testMode.js
@@ -59,6 +59,11 @@ function tweakAssocs( model ) {
59
59
  if (art.kind === 'select')
60
60
  forEachQueryExpr( art, checkExpr );
61
61
  } );
62
+
63
+
64
+ // create “super” ANNOTATE statements for annotations on unknown artifacts:
65
+ createRemainingAnnotateStatements();
66
+
62
67
  return;
63
68
 
64
69
 
@@ -71,7 +76,6 @@ function tweakAssocs( model ) {
71
76
  // return;
72
77
  if (!art.query) {
73
78
  rewriteAssociation( art );
74
- forEachGeneric( art, 'elements', rewriteAssociation );
75
79
  }
76
80
  else {
77
81
  traverseQueryExtra( art, ( query ) => {
@@ -81,9 +85,11 @@ function tweakAssocs( model ) {
81
85
  if (art._service)
82
86
  forEachGeneric( art, 'elements', complainAboutTargetOutsideService );
83
87
 
84
- traverseQueryPost( art.query, false, ( query ) => {
85
- forEachGeneric( query, 'elements', rewriteAssociationCheck );
86
- } );
88
+ if (art.query) {
89
+ traverseQueryPost(art.query, false, (query) => {
90
+ forEachGeneric( query, 'elements', rewriteAssociationCheck );
91
+ });
92
+ }
87
93
  }
88
94
 
89
95
  // function rewriteView( view ) {
@@ -111,9 +117,7 @@ function tweakAssocs( model ) {
111
117
  info( 'assoc-target-not-in-service', loc,
112
118
  { target, '#': (elem._main.query ? 'select' : 'define') }, {
113
119
  std: 'Target $(TARGET) of association is outside any service', // not used
114
- // eslint-disable-next-line max-len
115
120
  define: 'Target $(TARGET) of explicitly defined association is outside any service',
116
- // eslint-disable-next-line max-len
117
121
  select: 'Target $(TARGET) of explicitly selected association is outside any service',
118
122
  } );
119
123
  }
@@ -129,7 +133,7 @@ function tweakAssocs( model ) {
129
133
  }
130
134
 
131
135
  function rewriteAssociationCheck( element ) {
132
- const elem = element.items || element; // TODO v5: nested items
136
+ const elem = element.items || element; // TODO v6: nested items
133
137
  if (elem.elements)
134
138
  forEachGeneric( elem, 'elements', rewriteAssociationCheck );
135
139
  if (!elem.target)
@@ -236,13 +240,24 @@ function tweakAssocs( model ) {
236
240
  }
237
241
 
238
242
  function rewriteAssociation( element ) {
239
- let elem = element.items || element; // TODO v5: nested items
243
+ doRewriteAssociation( element );
244
+ if (element.target) {
245
+ extendForeignKeys( element );
246
+ if (element.foreignKeys$)
247
+ mergeSpecifiedForeignKeys( element );
248
+ }
249
+ }
250
+
251
+ // only to be used by rewriteAssociation()
252
+ function doRewriteAssociation( element ) {
253
+ let elem = element.items || element; // TODO v6: nested items
240
254
  if (elem.elements)
241
255
  forEachGeneric( elem, 'elements', rewriteAssociation );
242
256
  if (elem.targetAspect?.elements)
243
257
  forEachGeneric( elem.targetAspect, 'elements', rewriteAssociation );
244
258
  if (!originTarget( elem ))
245
259
  return;
260
+
246
261
  // console.log(message( null, elem.location, elem,
247
262
  // {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
248
263
 
@@ -300,6 +315,7 @@ function tweakAssocs( model ) {
300
315
  }
301
316
  }
302
317
 
318
+ /** Returns the element's origin's target artifact. */
303
319
  function originTarget( elem ) {
304
320
  const assoc = !elem.expand && getOrigin( elem );
305
321
  const ftype = assoc && effectiveType( assoc );
@@ -378,6 +394,9 @@ function tweakAssocs( model ) {
378
394
  traverseExpr( elem.on, 'rewrite-on', elem,
379
395
  expr => rewriteExpr( expr, elem, nav.tableAlias ) );
380
396
  }
397
+ else if (elem._pathHead) {
398
+ error( 'rewrite-not-supported', [ elem.target.location, elem ] );
399
+ }
381
400
  else {
382
401
  // TODO: support that, now that the ON condition is rewritten in the right order
383
402
  error( null, [ elem.value.location, elem ], {},
@@ -456,15 +475,6 @@ function tweakAssocs( model ) {
456
475
  };
457
476
  setArtifactLink( elem.type, assocType._artifact );
458
477
 
459
- if (!isV5preview) { // TODO(v5): Remove, only use $enclosed
460
- elem.$filtered = {
461
- val: true,
462
- literal: 'boolean',
463
- location,
464
- $inferred: '$generated',
465
- };
466
- }
467
-
468
478
  const isComp = (getUnderlyingBuiltinType( assoc )?.name?.id === 'cds.Composition');
469
479
  if (isComp) {
470
480
  elem.$enclosed = {
@@ -125,7 +125,8 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
125
125
  // TODO throughout the compiler: do not set art.‹prop›.$inferred if art.$inferred
126
126
  if (kind)
127
127
  member.kind = kind;
128
- else if (origin.key && !tmpDeprecated) // TODO(v5/v6): remove tmpDeprecated
128
+ else if (origin.key && !tmpDeprecated)
129
+ // TODO(v6): remove tmpDeprecated once `noKeyPropagationWithExpansions` is removed
129
130
  member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
130
131
  if (kind && origin.masked) // TODO: remove!
131
132
  member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
@@ -27,6 +27,10 @@ class XsnSource {
27
27
  artifacts = Object.create( null );
28
28
  vocabularies = Object.create( null );
29
29
  extensions = [];
30
+ $frontend;
31
+ constructor( frontend ) {
32
+ this.$frontend = frontend;
33
+ }
30
34
  }
31
35
 
32
36
  class XsnArtifact {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { isEdmPropertyRendered, transformExpression } = require('../../model/csnUtils');
4
- const { isBuiltinType } = require('../../base/builtins');
4
+ const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
5
5
  const edmUtils = require('../edmUtils.js');
6
6
  const oDataDictionary = require('../../gen/Dictionary.json');
7
7
  const preprocessAnnotations = require('./preprocessAnnotations.js');
@@ -60,6 +60,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
60
60
  if (defName.startsWith(`${serviceName}.`))
61
61
  assignParameterAnnotations(def);
62
62
  });
63
+
63
64
  // Crawl over the csn and trigger the annotation translation for all kinds
64
65
  // of annotated things.
65
66
  // Note: only works for single service
@@ -103,6 +104,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
103
104
  const scopeCheck = {
104
105
  ref: (elemref, prop, xpr, path) => {
105
106
  if (scopeCheck.scope === 'param' &&
107
+ !isMagicVariable(xpr[0]) &&
106
108
  (!elemref.param ||
107
109
  (xpr[0].id || xpr[0]) === '$self' && !def.elements.$self)) {
108
110
  error('odata-anno-xpr-ref', path, { anno: scopeCheck.anno, elemref, '#': 'notaparam' });
@@ -221,13 +223,16 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
221
223
 
222
224
  function handleDefinition( defName, def, location ) {
223
225
  // definition bound annotations
224
- handleAnnotations(defName, def, location);
226
+ handleAnnotations(defName, def, { location });
225
227
  // definition bound element annotations
226
228
  if (def.$elementsAnnoProxies) {
227
229
  Object.entries(def.$elementsAnnoProxies).forEach(([ elemPath, element ]) => {
228
230
  const edmTargetName = `${defName}/${elemPath}`;
229
- handleAnnotations(edmTargetName, element, element.$path,
230
- [ ...location, '$elementsAnnoProxies', elemPath ]);
231
+ handleAnnotations(edmTargetName, element,
232
+ {
233
+ location: element.$path,
234
+ csnPath: [ ...location, '$elementsAnnoProxies', elemPath ],
235
+ });
231
236
  });
232
237
  }
233
238
  // element bound annotations
@@ -235,7 +240,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
235
240
  Object.entries(def.elements).forEach(([ elemName, element ]) => {
236
241
  const edmTargetName = `${defName}/${elemName}`;
237
242
  const eLocation = [ ...location, 'elements', elemName ];
238
- handleAnnotations(edmTargetName, element, eLocation);
243
+ handleAnnotations(edmTargetName, element, { location: eLocation });
239
244
  });
240
245
  }
241
246
  // bound actions
@@ -274,6 +279,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
274
279
  // entityNameIfBound : qualified name of entity if bound action/function
275
280
  function handleAction( cActionName, cAction, entityNameIfBound, location ) {
276
281
  let actionName = cActionName;
282
+
277
283
  if (isV2()) { // Replace up to last dot with <serviceName>.EntityContainer
278
284
  const lastDotIndex = actionName.lastIndexOf('.');
279
285
  if (lastDotIndex > -1)
@@ -283,55 +289,70 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
283
289
  actionName += relParList();
284
290
  }
285
291
 
286
- handleAnnotations(actionName, cAction, location);
292
+ handleAnnotations(actionName, cAction, { location, cAction });
287
293
 
288
294
  if (cAction.params) {
289
295
  if (cAction.$paramsAnnoProxies) {
290
296
  Object.entries(cAction.$paramsAnnoProxies).forEach(([ paramPath, param ]) => {
291
- const edmTargetName = `${actionName}/${paramPath}`;
292
- handleAnnotations(edmTargetName, param, param.$path,
293
- [ ...location, '$paramsAnnoProxies', paramPath ]);
297
+ // skip explicit binding parameter in V2
298
+ if (!(options.isV2() && param.type === '$self' && paramPath === cAction.$bindingParam?.name)) {
299
+ const edmTargetName = `${actionName}/${paramPath}`;
300
+ handleAnnotations(edmTargetName, param,
301
+ {
302
+ location: param.$path,
303
+ csnPath: [ ...location, '$paramsAnnoProxies', paramPath ],
304
+ cAction,
305
+ });
306
+ }
294
307
  });
295
308
  }
309
+ // explicit binding parameter is removed from params in V2 during
310
+ // createActionV2(), no need to check ;)
296
311
  Object.entries(cAction.params).forEach(([ n, p ]) => {
297
312
  const edmTargetName = `${actionName}/${n}`;
298
313
  handleAnnotations(edmTargetName, p,
299
- [ ...location, 'params', n ]);
314
+ {
315
+ action: true,
316
+ location: [ ...location, 'params', n ],
317
+ cAction,
318
+ });
300
319
  });
301
320
  }
302
321
  if (cAction.returns) {
303
322
  if (cAction.$returnsAnnoProxies) {
304
323
  Object.entries(cAction.$returnsAnnoProxies).forEach(([ returnsPath, returns ]) => {
305
324
  const edmTargetName = `${actionName}/${returnsPath}`;
306
- handleAnnotations(edmTargetName, returns, returns.$path,
307
- [ ...location, '$returnsAnnoProxies', returnsPath ]);
325
+ handleAnnotations(edmTargetName, returns,
326
+ {
327
+ location: returns.$path,
328
+ csnPath: [ ...location, '$returnsAnnoProxies', returnsPath ],
329
+ cAction,
330
+ });
308
331
  });
309
332
  }
310
333
  const edmTargetName = `${actionName}/$ReturnType`;
311
334
  setProp(cAction.returns, '$appliesToReturnType', true);
312
335
  handleAnnotations(edmTargetName, cAction.returns,
313
- [ ...location, 'returns' ]);
336
+ {
337
+ location: [ ...location, 'returns' ],
338
+ cAction,
339
+ });
314
340
  delete cAction.returns.$appliesToReturnType;
315
341
  }
316
342
 
317
343
  function relParList() {
318
- // we rely on the order of params in the csn being the correct one
344
+ // we rely on the order of params in the csn being the correct one
319
345
  const params = [];
320
346
  if (entityNameIfBound) {
321
347
  // If this is an action and has an explicit binding parameter add it here
322
- if (cAction.$bindingParam && cAction.kind === 'action') {
348
+ if (cAction.$bindingParam && cAction.kind === 'action' ||
349
+ cAction.$bindingParam?.viaAnno) {
323
350
  params.push(
324
351
  cAction.$bindingParam.items
325
352
  ? `Collection(${entityNameIfBound})`
326
353
  : entityNameIfBound
327
354
  );
328
355
  }
329
- // If action/function has no explicit binding parameter add it here
330
- else if (!cAction.$bindingParam) {
331
- params.push(cAction['@cds.odata.bindingparameter.collection']
332
- ? `Collection(${entityNameIfBound})`
333
- : entityNameIfBound);
334
- }
335
356
  }
336
357
  // In case this is a function the explicit binding parameter is part of
337
358
  // the functions params dictionary. Only for functions all parameters must
@@ -349,12 +370,12 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
349
370
  return edmUtils.mapCdsToEdmType(p, messageFunctions, false /* is only called for v4 */);
350
371
  }
351
372
  else if (options.whatsMySchemaName) {
352
- const schemaName = options.whatsMySchemaName(p.type);
373
+ const schemaName = options.whatsMySchemaName(p._edmType || p.type);
353
374
  // strip the service namespace of from a parameter type
354
375
  if (schemaName && schemaName !== options.serviceName)
355
- return p.type.replace(`${options.serviceName}.`, '');
376
+ return (p._edmType || p.type).replace(`${options.serviceName}.`, '');
356
377
  }
357
- return p.type;
378
+ return p._edmType || p.type;
358
379
  }
359
380
  }
360
381
  }
@@ -364,7 +385,9 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
364
385
  // edmTargetName : string, name of the target in edm
365
386
  // carrier: object, the annotated cds thing, contains all the annotations
366
387
  // as properties with names starting with @
367
- function handleAnnotations( edmTargetName, carrier, location, csnPathResolutionLocation = location ) {
388
+ // ctx: locations and other information that is required to write the
389
+ // annotations
390
+ function handleAnnotations( edmTargetName, carrier, ctx ) {
368
391
  // collect the names of the carrier's annotation properties
369
392
  // keep only those annotations that - start with a known vocabulary name
370
393
  // - have a value other than null
@@ -378,6 +401,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
378
401
  (isV2() && (edmUtils.isDerivedType(carrier))))
379
402
  return;
380
403
 
404
+ if (ctx.location == null)
405
+ throw Error('location required');
381
406
 
382
407
  // Filter unknown toplevel annotations
383
408
  // Final filtering of all annotations is done in handleTerm
@@ -397,15 +422,15 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
397
422
  transformExpression(carrier, knownAnno, {
398
423
  ref: (elemref, prop, xpr, csnPath) => {
399
424
  if (options.isV2() && elemref.$bparam) {
400
- error('odata-anno-xpr-ref', location, {
401
- elemref, anno: knownAnno, version: '2.0', '#': 'bparam_v2',
425
+ error('odata-anno-xpr-ref', ctx.location, {
426
+ elemref, anno: knownAnno, version: '2.0', '#': 'bparam_v2_expl',
402
427
  });
403
428
  return;
404
429
  }
405
430
  const { links, scope } = reqDefsUtils.inspectRef(csnPath);
406
431
  let i = scope === '$self' ? 1 : 0;
407
432
  if (scope === '$magic') {
408
- error('odata-anno-xpr-ref', location, {
433
+ error('odata-anno-xpr-ref', ctx.location, {
409
434
  elemref, anno: knownAnno, '#': 'magic',
410
435
  });
411
436
  return;
@@ -413,7 +438,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
413
438
  let stop = false;
414
439
  for (; i < links.length && !stop; i++) {
415
440
  if (!isEdmPropertyRendered(links[i].art, csnPath)) {
416
- error('odata-anno-xpr-ref', location, {
441
+ error('odata-anno-xpr-ref', ctx.location, {
417
442
  count: i + 1, elemref, anno: knownAnno, '#': 'notrendered',
418
443
  });
419
444
  stop = true;
@@ -422,16 +447,27 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
422
447
  const proxy = links[i].art?._target;
423
448
  const eltName = links[i + 1].art?.name;
424
449
  if (!proxy.elements[eltName]) {
425
- error('odata-anno-xpr-ref', location, {
450
+ error('odata-anno-xpr-ref', ctx.location, {
426
451
  count: i + 2, elemref, anno: knownAnno, '#': 'notrendered',
427
452
  });
428
453
  stop = true;
429
454
  }
430
455
  }
431
456
  }
457
+ if (!stop && ctx.cAction?.$isBound && scope === '$self') {
458
+ if (options.isV2()) {
459
+ error('odata-anno-xpr-ref', ctx.location, {
460
+ elemref, anno: knownAnno, version: '2.0', '#': 'bparam_v2_impl',
461
+ });
462
+ }
463
+ else {
464
+ xpr[0] = ctx.cAction.$bindingParam.name;
465
+ elemref.param = true;
466
+ }
467
+ }
432
468
  },
433
- }, csnPathResolutionLocation);
434
- xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
469
+ }, ctx.csnPath || ctx.location);
470
+ xpr2edmJson(carrier, knownAnno, ctx.location, options, messageFunctions);
435
471
  }
436
472
  });
437
473
 
@@ -458,7 +494,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
458
494
  const alternativeAnnotations = [];
459
495
 
460
496
  // now create annotation objects for all the annotations of carrier
461
- handleAnno2(addAnnotation, prefixTree, location);
497
+ handleAnno2(addAnnotation, prefixTree, ctx.location);
462
498
 
463
499
  // Produce Edm.Annotations and attach collected Edm.Annotation(s) to the
464
500
  // envelope (or directly to the Schema)
@@ -507,7 +543,6 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
507
543
  }
508
544
 
509
545
  function initCarrierControlVars() {
510
- // eslint-disable-next-line no-unused-vars
511
546
  let testToStandardEdmTargetP = () => true; // if true, assign to standard Edm Target
512
547
  let stdEdmTargetNameP = edmTargetName;
513
548
  let alternativeEdmTargetNameP = null;
@@ -783,7 +818,6 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
783
818
  // addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
784
819
  const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
785
820
  const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
786
- // eslint-disable-next-line sonarjs/no-collapsible-if
787
821
  if (!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
788
822
  if (dictTerm && dictTerm.AppliesTo) {
789
823
  message('odata-anno-def', location,
@@ -732,11 +732,13 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
732
732
  if (type === special$self) {
733
733
  bpName = entries[0][0];
734
734
  setProp(actionCsn, '$bindingParam', firstParam);
735
+ // preserve the original type (as it is the key to reqDefs.defintions)
736
+ // for annotation path resolution (eg. for $draft.IsActiveEntity)
735
737
  if (bpType) {
736
738
  if (firstParam.items?.type)
737
- firstParam.items.type = bpType;
739
+ setProp(firstParam.items, '_edmType', bpType);
738
740
  if (firstParam.type)
739
- firstParam.type = bpType;
741
+ setProp(firstParam, '_edmType', bpType);
740
742
  }
741
743
  if (!edmUtils.isODataSimpleIdentifier(bpName))
742
744
  message('odata-spec-violation-id', [ ...location, 'params', bpName ], { id: bpName });
@@ -775,11 +777,20 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
775
777
  if (entityCsn) {
776
778
  actionNode.setEdmAttribute('IsBound', true);
777
779
  if (!actionCsn.$bindingParam) {
780
+ const bpDef = {
781
+ name: bpName,
782
+ viaAnno: true,
783
+ };
778
784
  // Binding Parameter: 'in' at first position in sequence, this is decisive!
779
- if (actionCsn['@cds.odata.bindingparameter.collection'])
785
+ if (actionCsn['@cds.odata.bindingparameter.collection']) {
780
786
  actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection: true/* , Nullable: false */ } ));
781
- else
787
+ bpDef.items = { type: bpType };
788
+ }
789
+ else {
782
790
  actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType } ));
791
+ bpDef.type = bpType;
792
+ }
793
+ setProp(actionCsn, '$bindingParam', bpDef);
783
794
  }
784
795
  }
785
796
  else if (EntityContainer) { // unbound => produce Action/FunctionImport
@@ -891,6 +902,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
891
902
  if (i === 0 && type === special$self) {
892
903
  // skip and remove the first parameter if it is a $self binding parameter to
893
904
  // omit annotation rendering later on
905
+ setProp(actionCsn, '$bindingParam', parameterCsn);
894
906
  delete actionCsn.params[parameterName];
895
907
  }
896
908
  else {
package/lib/edm/edm.js CHANGED
@@ -625,7 +625,8 @@ function getEdm( options, messageFunctions ) {
625
625
  edmUtils.addTypeFacets(this, this._scalarType);
626
626
  }
627
627
  else {
628
- this._edmAttributes[typeName] = typecsn.type;
628
+ // it's either _edmType or type (_edmType only used for explicit binding param)
629
+ this._edmAttributes[typeName] = typecsn._edmType || typecsn.type;
629
630
  }
630
631
  }
631
632
  // CDXCORE-245:
@@ -882,12 +883,20 @@ function getEdm( options, messageFunctions ) {
882
883
  class ReturnType extends PropertyBase {
883
884
  constructor(version, csn) {
884
885
  super(version, {}, csn);
886
+ // CSDL 12.8: If the return type is a collection of entity types,
887
+ // the Nullable attribute has no meaning and MUST NOT be specified.
888
+ if (csn.$NoNullableProperty)
889
+ delete this._edmAttributes.Nullable;
885
890
  }
886
891
 
887
892
  // we need Name but NO $kind, can't use standard to JSON()
888
893
  toJSON() {
889
894
  const json = Object.create(null);
890
895
  this.toJSONattributes(json);
896
+ // CSDL 12.8: If the return type is a collection of entity types,
897
+ // the Nullable attribute has no meaning and MUST NOT be specified.
898
+ if (this._csn.$NoNullableProperty)
899
+ delete json.$Nullable;
891
900
  return json;
892
901
  }
893
902
  }
@@ -897,7 +906,6 @@ function getEdm( options, messageFunctions ) {
897
906
  super(version, attributes, csn);
898
907
  // TIPHANACDS-4180
899
908
  if (this.v2) {
900
- // eslint-disable-next-line sonarjs/no-redundant-boolean
901
909
  if (csn['@odata.etag'] || csn['@cds.etag'])
902
910
  this._edmAttributes.ConcurrencyMode = 'Fixed';
903
911
 
@@ -1037,7 +1045,6 @@ function getEdm( options, messageFunctions ) {
1037
1045
  See csn2edm.createEntityTypeAndSet() for details
1038
1046
  2) ContainsTarget stems from the @odata.contained annotation
1039
1047
  */
1040
- // eslint-disable-next-line sonarjs/no-redundant-boolean
1041
1048
  if (csn['@odata.contained'] || csn.containsTarget)
1042
1049
  this._edmAttributes.ContainsTarget = true;
1043
1050
 
@@ -59,7 +59,6 @@ function applyAppSpecificLateCsnTransformationOnElement( options, element, struc
59
59
  // runtime cannot set that okra flag (alternatively the runtime has to search
60
60
  // for @[odata|cds].etag annotations...
61
61
  if (options.isV4() && (element['@odata.etag'] || element['@cds.etag'])) {
62
- // eslint-disable-next-line sonarjs/no-redundant-boolean
63
62
  // don't put element name into collection as per advice from Ralf Handl, as
64
63
  // no runtime is interested in the property itself, it is sufficient to mark
65
64
  // the entity set.
@@ -243,7 +242,7 @@ function setSAPSpecificV2AnnotationsToEntitySet( options, carrier ) {
243
242
  '@sap.updatable.path': addToSetAttr,
244
243
  '@sap.deletable.path': addToSetAttr,
245
244
  '@sap.searchable': addToSetAttr,
246
- '@sap.pagable': addToSetAttr,
245
+ '@sap.pageable': addToSetAttr,
247
246
  '@sap.topable': addToSetAttr,
248
247
  '@sap.countable': addToSetAttr,
249
248
  '@sap.addressable': addToSetAttr,
@@ -100,7 +100,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
100
100
  as a step in the OData transformer with the goal to have a protocol agnostic OData CSN.
101
101
  */
102
102
  if (csn.meta && csn.meta.options && csn.meta.options.odataVersion === 'v4' && options.isV2()) {
103
- // eslint-disable-next-line global-require
104
103
  const { toFinalBaseType } = require('../transform/transformUtils').getTransformers(csn, options, messageFunctions);
105
104
  expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
106
105
  }
@@ -704,11 +703,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
704
703
  validFrom.push(element);
705
704
 
706
705
  // forward annotations from managed association element to its foreign keys
707
- // TODO: Try to eliminate this code by rearranging
708
- // forOdata::addCommonValueListviaAssociation(member, memberName)
709
- // such that the VL annotations are distributed to the associations *before*
710
- // FK creation.
711
- // The FK creation already propagates the annotations from the association
712
706
  const elements = construct.items?.elements || construct.elements;
713
707
  const assoc = elements[element['@odata.foreignKey4']];
714
708
  if (assoc) {
@@ -1892,7 +1886,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1892
1886
  mapCdsToEdmProp(def);
1893
1887
  annotateAllowedValues(def, defLocation);
1894
1888
  if (def.returns) {
1895
- markCollection(def.returns);
1889
+ markCollection(def.returns, true);
1896
1890
  mapCdsToEdmProp(def.returns);
1897
1891
  annotateAllowedValues(def.returns, [ ...defLocation, 'returns' ]);
1898
1892
  }
@@ -1905,17 +1899,21 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1905
1899
  rewriteAnnotationExpressions(member);
1906
1900
  if (member.returns) {
1907
1901
  edmUtils.assignAnnotation(member.returns, '@Core.Description', member.returns.doc);
1908
- markCollection(member.returns);
1902
+ markCollection(member.returns, true);
1909
1903
  mapCdsToEdmProp(member.returns);
1910
1904
  annotateAllowedValues(member.returns, [ ...location, 'returns' ]);
1911
1905
  rewriteAnnotationExpressions(member.returns);
1912
1906
  }
1913
1907
  }, defLocation);
1914
1908
  // mark members that need to be rendered as collections
1915
- function markCollection( obj ) {
1909
+ function markCollection( obj, isReturns ) {
1916
1910
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
1917
1911
  if (items) {
1918
- edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
1912
+ edmUtils.assignProp(obj, '$NoNullableProperty',
1913
+ isReturns && items.type &&
1914
+ !isBuiltinType(items.type) &&
1915
+ csn.definitions[items.type]?.kind === 'entity');
1916
+ edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : false);
1919
1917
  edmUtils.assignProp(obj, '$isCollection', true);
1920
1918
  }
1921
1919
  }