@sap/cds-compiler 5.2.0 → 5.3.2

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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdshi.js +8 -8
  4. package/doc/CHANGELOG_BETA.md +9 -4
  5. package/lib/api/validate.js +5 -0
  6. package/lib/base/message-registry.js +25 -1
  7. package/lib/base/messages.js +1 -1
  8. package/lib/base/model.js +0 -1
  9. package/lib/compiler/assert-consistency.js +2 -2
  10. package/lib/compiler/builtins.js +1 -1
  11. package/lib/compiler/checks.js +25 -6
  12. package/lib/compiler/define.js +24 -28
  13. package/lib/compiler/extend.js +11 -13
  14. package/lib/compiler/generate.js +3 -3
  15. package/lib/compiler/populate.js +13 -7
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +58 -60
  18. package/lib/compiler/shared.js +5 -5
  19. package/lib/compiler/tweak-assocs.js +247 -34
  20. package/lib/compiler/utils.js +40 -32
  21. package/lib/compiler/xpr-rewrite.js +44 -58
  22. package/lib/edm/annotations/genericTranslation.js +4 -4
  23. package/lib/edm/csn2edm.js +2 -2
  24. package/lib/edm/edm.js +46 -21
  25. package/lib/edm/edmInboundChecks.js +0 -1
  26. package/lib/edm/edmPreprocessor.js +40 -27
  27. package/lib/edm/edmUtils.js +1 -1
  28. package/lib/gen/BaseParser.js +180 -122
  29. package/lib/gen/CdlParser.js +2226 -2170
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +3820 -3777
  33. package/lib/inspect/inspectPropagation.js +1 -1
  34. package/lib/json/from-csn.js +5 -3
  35. package/lib/json/to-csn.js +7 -10
  36. package/lib/language/antlrParser.js +38 -4
  37. package/lib/language/errorStrategy.js +1 -1
  38. package/lib/language/genericAntlrParser.js +4 -4
  39. package/lib/language/multiLineStringParser.js +1 -1
  40. package/lib/main.d.ts +23 -0
  41. package/lib/model/cloneCsn.js +22 -13
  42. package/lib/optionProcessor.js +7 -7
  43. package/lib/parsers/AstBuildingParser.js +155 -37
  44. package/lib/parsers/CdlGrammar.g4 +154 -81
  45. package/lib/parsers/Lexer.js +20 -10
  46. package/lib/render/toCdl.js +23 -18
  47. package/lib/transform/addTenantFields.js +4 -4
  48. package/lib/transform/db/rewriteCalculatedElements.js +11 -5
  49. package/lib/transform/db/transformExists.js +43 -18
  50. package/lib/transform/effective/main.js +1 -1
  51. package/lib/transform/forRelationalDB.js +8 -7
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +1 -1
  54. package/share/messages/redirected-to-complex.md +6 -3
@@ -174,7 +174,8 @@ function xprRewriteFns( model ) {
174
174
  resolvePath,
175
175
  navigationEnv,
176
176
  resolvePathRoot,
177
- firstProjectionForPath,
177
+ cachedRedirectionChain,
178
+ findRewriteTarget,
178
179
  } = model.$functions;
179
180
 
180
181
  return {
@@ -393,21 +394,21 @@ function xprRewriteFns( model ) {
393
394
  */
394
395
  function rewriteGenericAnnoPath( expr, config, refCtx ) {
395
396
  const isAbsolute = isAnnoPathAbsolute( expr );
396
- const rootIndex = isAbsolute ? 1 : 0;
397
+ const startIndex = isAbsolute ? 1 : 0;
397
398
 
398
399
  // We get the root environment now, even though below we resolve the root item
399
400
  // again if it was absolute (e.g. $self). We do so, because for queries, we
400
401
  // want to respect the select item's corresponding table alias.
401
402
  const rootEnv = getRootEnv( expr, config );
402
403
 
403
- // reset artifact link; we'll set it again
404
+ // reset artifact link; we'll set it again if there are no errors
404
405
  setArtifactLink( expr, null );
405
406
 
406
- // Adapt root path, as it isn't rewritten in rewriteItem
407
- const rootItem = expr.path[0];
408
407
  if (isAbsolute) {
409
- delete rootItem._artifact;
410
- delete rootItem._navigation;
408
+ // Adapt absolute root path, as it isn't rewritten in rewriteItem
409
+ // The path-prefix was already adapted in rewriteAnnoExpr().
410
+ delete expr.path[0]._artifact;
411
+ delete expr.path[0]._navigation;
411
412
  // TODO: What about `up_`? Shouldn't we set `_navigation` as well?
412
413
  // TODO: Can we handle `$self` of anonymous-composition-of-aspect?
413
414
  const root = resolvePathRoot( expr, refCtx, config.target );
@@ -415,23 +416,46 @@ function xprRewriteFns( model ) {
415
416
  return reportAnnoRewriteError( expr, config );
416
417
  }
417
418
 
419
+ // Store the original artifact, so that we can use it to
420
+ // calculate a redirection chain later on.
421
+ expr.path.forEach((item) => {
422
+ if (item._artifact)
423
+ setLink( item, '_originalArtifact', item._artifact );
424
+ });
425
+
418
426
  let env = rootEnv;
419
- let art = rootItem._artifact;
420
- for (let i = rootIndex; i < expr.path.length; ++i) {
427
+ let art = expr.path[0]._artifact;
428
+
429
+ for (let i = startIndex; i < expr.path.length; ++i) {
430
+ if (i > startIndex && art.target) {
431
+ // if the current artifact is an association, we need to respect the redirection
432
+ // chain from original target to new one.
433
+ // FIXME: Won't work with associations in projected structures.
434
+ const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
435
+ const chain = cachedRedirectionChain( art, origTarget );
436
+ if (!chain)
437
+ return reportAnnoRewriteError( expr, config );
438
+ for (const alias of chain) {
439
+ art = rewriteItem( expr, config, alias, i );
440
+ if (!art)
441
+ return reportAnnoRewriteError( expr, config );
442
+ }
443
+ }
421
444
  art = rewriteItem( expr, config, env, i );
422
445
  if (!art)
423
446
  return reportAnnoRewriteError( expr, config );
447
+ // target, items, …
424
448
  env = navigationEnv( art, null, null, 'nav' );
425
449
  }
426
450
  setArtifactLink( expr, art );
427
451
 
428
- if (rootIndex === 0 && rootItem.id.startsWith('$')) {
452
+ if (startIndex === 0 && expr.path[0].id.startsWith('$')) {
429
453
  if (config.isInFilter) {
430
454
  // In filters, we must not prepend `$self`, as that would change its meaning.
431
455
  // We must reject it. See #11775
432
456
  return reportAnnoRewriteError( expr, config );
433
457
  }
434
- // After rewriting, an element starts with `$` -> add root prefix
458
+ // After rewriting, if an element starts with `$` -> add root prefix
435
459
  prependRootPath( config.origin, config.targetRoot, expr );
436
460
  }
437
461
 
@@ -640,63 +664,25 @@ function xprRewriteFns( model ) {
640
664
  * @returns {*|null}
641
665
  */
642
666
  function rewriteItem( expr, config, env, index ) {
643
- const item = expr.path[index];
644
667
  const rewriteTarget = findRewriteTarget( expr, index, env, config.target );
645
- const found = setArtifactLink( item, rewriteTarget[0] );
668
+ const found = setArtifactLink( expr.path[index], rewriteTarget[0] );
646
669
  if (!found)
647
670
  return null;
648
671
 
649
- if (item.id !== found.name.id) {
650
- // Path was rewritten; original token text string is no longer accurate
651
- config.tokenExpr.$tokenTexts = true;
652
- item.id = found.name.id;
653
- }
654
-
655
- if (rewriteTarget[1] > index)
672
+ if (rewriteTarget[1] > index) {
673
+ // we keep the last segment, in case it has non-enumerable properties
674
+ expr.path[index] = expr.path[rewriteTarget[1]];
656
675
  expr.path.splice(index + 1, rewriteTarget[1] - index);
657
-
658
- return rewriteTarget[0];
659
- }
660
-
661
- function findRewriteTarget( expr, index, env, target ) {
662
- if (env.kind === '$navElement' || env.kind === '$tableAlias') {
663
- const r = firstProjectionForPath( expr.path, index, env, target );
664
- return [ r.elem, r.index ];
665
676
  }
666
677
 
667
678
  const item = expr.path[index];
668
- // If the artifact is already in the same definition, we must not check the query.
669
- // Or if it is not a query -> no $navElement -> use `elements`
670
- if (item._artifact._main === env || !env.query && env.kind !== 'select') {
671
- if (env.elements?.[item.id])
672
- return [ env.elements[item.id], index ];
673
- return [ null, expr.path.length ];
674
- }
675
- const items = (env._leadingQuery || env)._combined?.[item.id];
676
- const allNavs = !items || Array.isArray(items) ? items : [ items ];
677
-
678
- // If the annotation target itself has a table alias, require projections of that
679
- // table alias. Of course, that only works if we're talking about the same query.
680
- const tableAlias = (target._main?._origin === item._artifact._main &&
681
- target.value?.path[0]?._navigation?.kind === '$tableAlias')
682
- ? target.value.path[0]._navigation : null;
683
-
684
- // Look at all table aliase that could project `item` and only select
685
- // those that have actual projections.
686
- const navs = allNavs?.filter(p => p._origin === item._artifact &&
687
- (!tableAlias || tableAlias === p._parent));
688
- if (!navs || navs.length === 0)
689
- return [ null, expr.path.length ];
690
-
691
- // If there are multiple navigations for the element, just use the first that matches.
692
- // In case of table aliases, it's just one.
693
- for (const nav of navs) {
694
- const r = firstProjectionForPath( expr.path, index, nav._parent, target );
695
- if (r.elem)
696
- return [ r.elem, r.index ];
679
+ if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
680
+ // Path was rewritten; original token text string is no longer accurate
681
+ config.tokenExpr.$tokenTexts = true;
682
+ item.id = found.name.id;
697
683
  }
698
684
 
699
- return [ null, expr.path.length ];
685
+ return setArtifactLink( expr.path[index], found );
700
686
  }
701
687
  }
702
688
 
@@ -932,7 +932,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
932
932
  anno: msg.anno(),
933
933
  type: dTypeName,
934
934
  value: `"#${enumSymbol}"`,
935
- rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `"#${m}"`),
935
+ rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `#${m}`),
936
936
  '#': 'enum',
937
937
  });
938
938
  }
@@ -1024,7 +1024,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1024
1024
  anno: msg.anno(),
1025
1025
  type: dTypeName,
1026
1026
  value: `"#${value}"`,
1027
- rawvalues: expectedType.Members.map(m => `"#${m}"`),
1027
+ rawvalues: expectedType.Members.map(m => `#${m}`),
1028
1028
  '#': 'enum',
1029
1029
  });
1030
1030
  }
@@ -1060,7 +1060,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1060
1060
  anno: msg.anno(),
1061
1061
  type: dTypeName,
1062
1062
  value: value['='] || value,
1063
- rawvalues: type.Members.map(m => `"#${m}"`),
1063
+ rawvalues: type.Members.map(m => `#${m}`),
1064
1064
  '#': 'enum',
1065
1065
  });
1066
1066
  }
@@ -1120,7 +1120,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1120
1120
 
1121
1121
  if (isEnumType(resolvedType)) {
1122
1122
  const type = getDictType(resolvedType);
1123
- const expected = type.Members.map(m => `"#${m}"`);
1123
+ const expected = type.Members.map(m => `#${m}`);
1124
1124
  message('odata-anno-value', msg.location,
1125
1125
  {
1126
1126
  anno: msg.anno(),
@@ -736,8 +736,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
736
736
  attributes.IsComposable = false;
737
737
 
738
738
  /** @type {object} */
739
- const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
740
- : new Edm.FunctionDefinition(v, attributes);
739
+ const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes, actionCsn)
740
+ : new Edm.FunctionDefinition(v, attributes, actionCsn);
741
741
 
742
742
  const bpType = entityCsn ? fullQualified(entityCsn.name) : undefined;
743
743
  /*
package/lib/edm/edm.js CHANGED
@@ -31,6 +31,7 @@ function getEdm( options, messageFunctions ) {
31
31
  this._edmAttributes = Object.assign(Object.create(null), attributes);
32
32
  this._xmlOnlyAttributes = Object.create(null);
33
33
  this._jsonOnlyAttributes = Object.create(null);
34
+ this._openApiHints = Object.create(null);
34
35
 
35
36
  this._children = [];
36
37
  this._ignoreChildren = false;
@@ -38,6 +39,8 @@ function getEdm( options, messageFunctions ) {
38
39
 
39
40
  if (this.v2)
40
41
  this.setSapVocabularyAsAttributes(csn);
42
+
43
+ this.setOpenApiHints(csn);
41
44
  }
42
45
 
43
46
  get v2() {
@@ -103,6 +106,17 @@ function getEdm( options, messageFunctions ) {
103
106
  return this;
104
107
  }
105
108
 
109
+ setOpenApiHints(csn) {
110
+ if (csn && options.odataOpenapiHints) {
111
+ const jsonAttr = Object.create(null);
112
+ Object.entries(csn).filter(([ k, _v ] ) => k.startsWith('@OpenAPI.')).forEach(([ k, v ]) => {
113
+ jsonAttr[k] = v;
114
+ });
115
+ Object.assign(this._openApiHints, jsonAttr);
116
+ }
117
+ return this._openApiHints;
118
+ }
119
+
106
120
  // virtual
107
121
  toJSON() {
108
122
  const json = Object.create(null);
@@ -110,16 +124,24 @@ function getEdm( options, messageFunctions ) {
110
124
  if (!(this.kind in Node.noJsonKinds))
111
125
  json.$Kind = this.kind;
112
126
 
113
- this.toJSONattributes(json);
114
- return this.toJSONchildren(json);
127
+ return this.toJSONchildren(this.toJSONattributes(json));
115
128
  }
116
129
 
117
130
  // virtual
118
- toJSONattributes(json) {
131
+ toJSONattributes(json, withHints = true) {
119
132
  forEach(this._edmAttributes, (p, v) => {
120
133
  if (p !== 'Name')
121
134
  json[p[0] === '@' ? p : `$${p}`] = v;
122
135
  });
136
+ return (withHints ? this.toOpenApiHints(json) : json);
137
+ }
138
+
139
+ toOpenApiHints(json) {
140
+ if (options.odataOpenapiHints && this._openApiHints) {
141
+ Object.entries(this._openApiHints).forEach(([ p, v ]) => {
142
+ json[p[0] === '@' ? p : `$${p}`] = v;
143
+ });
144
+ }
123
145
  return json;
124
146
  }
125
147
 
@@ -243,6 +265,10 @@ function getEdm( options, messageFunctions ) {
243
265
  super.setSapVocabularyAsAttributes(csn, true);
244
266
  }
245
267
 
268
+ toJSONattributes(json) {
269
+ return super.toJSONattributes(json, false);
270
+ }
271
+
246
272
  register(entry) {
247
273
  if (!this._registry[entry._edmAttributes.Name])
248
274
  this._registry[entry._edmAttributes.Name] = [ entry ];
@@ -258,6 +284,7 @@ function getEdm( options, messageFunctions ) {
258
284
  if (alias !== undefined)
259
285
  props.Alias = alias;
260
286
  super(version, props);
287
+ this.setOpenApiHints(serviceCsn);
261
288
  this._annotations = annotations;
262
289
  this._actions = Object.create(null);
263
290
  this.setXml( { xmlns: (this.v2) ? 'http://schemas.microsoft.com/ado/2008/09/edm' : 'http://docs.oasis-open.org/odata/ns/edm' } );
@@ -321,6 +348,7 @@ function getEdm( options, messageFunctions ) {
321
348
  json[p[0] === '@' ? p : `$${p}`] = v;
322
349
  });
323
350
  }
351
+ return this.toOpenApiHints(json);
324
352
  }
325
353
 
326
354
  toJSONchildren(json) {
@@ -550,8 +578,8 @@ function getEdm( options, messageFunctions ) {
550
578
  */
551
579
 
552
580
  class ActionFunctionBase extends Node {
553
- constructor(version, details) {
554
- super(version, details);
581
+ constructor(version, details, csn) {
582
+ super(version, details, csn);
555
583
  this._returnType = undefined;
556
584
  }
557
585
 
@@ -702,7 +730,7 @@ function getEdm( options, messageFunctions ) {
702
730
  if (this.$isCollection)
703
731
  json.$Collection = this.$isCollection;
704
732
 
705
- return json;
733
+ return this.toOpenApiHints(json);
706
734
  }
707
735
  }
708
736
 
@@ -753,11 +781,11 @@ function getEdm( options, messageFunctions ) {
753
781
  else
754
782
  this._keys = undefined;
755
783
 
756
- if (options.odataOpenapiHints) {
784
+ if (this._openApiHints) {
757
785
  if (csn['@cds.autoexpose'])
758
- this.setJSON({ '@cds.autoexpose': true });
786
+ this._openApiHints['@cds.autoexpose'] = true;
759
787
  if (csn['@cds.autoexposed'])
760
- this.setJSON({ '@cds.autoexposed': true });
788
+ this._openApiHints['@cds.autoexposed'] = true;
761
789
  }
762
790
  }
763
791
 
@@ -806,7 +834,7 @@ function getEdm( options, messageFunctions ) {
806
834
  class Member extends Node {
807
835
  toJSONattributes(json) {
808
836
  json[this._edmAttributes.Name] = this._edmAttributes.Value;
809
- return json;
837
+ return super.toOpenApiHints(json);
810
838
  }
811
839
  }
812
840
 
@@ -823,11 +851,6 @@ function getEdm( options, messageFunctions ) {
823
851
  }
824
852
  }
825
853
 
826
- toJSONattributes(json) {
827
- super.toJSONattributes(json);
828
- return json;
829
- }
830
-
831
854
  toJSONchildren(json) {
832
855
  this._children.forEach(c => c.toJSONattributes(json));
833
856
  return json;
@@ -1161,6 +1184,10 @@ function getEdm( options, messageFunctions ) {
1161
1184
  return this.toJSONchildren(json);
1162
1185
  }
1163
1186
 
1187
+ toJSONattributes(json) {
1188
+ return super.toJSONattributes(json, false);
1189
+ }
1190
+
1164
1191
  getConstantExpressionValue() {
1165
1192
  // short form: key: value
1166
1193
  const inlineConstExpr
@@ -1290,6 +1317,7 @@ function getEdm( options, messageFunctions ) {
1290
1317
  const keys = Object.keys(this._edmAttributes).filter(k => k !== 'Type');
1291
1318
  for (const key of keys)
1292
1319
  json[`$${key}`] = this._edmAttributes[key];
1320
+ return json;
1293
1321
  }
1294
1322
 
1295
1323
  toJSONchildren(json) {
@@ -1397,8 +1425,7 @@ function getEdm( options, messageFunctions ) {
1397
1425
  toJSON() {
1398
1426
  const json = this.mergeJSONAnnotations();
1399
1427
  json[`$${this.kind}`] = this._children.filter(c => c.kind !== 'Annotation').map(c => c.toJSON());
1400
- this.toJSONattributes(json);
1401
- return json;
1428
+ return this.toJSONattributes(json);
1402
1429
  }
1403
1430
  }
1404
1431
  class Cast extends AnnotationBase {
@@ -1417,8 +1444,7 @@ function getEdm( options, messageFunctions ) {
1417
1444
  // first expression only, if any
1418
1445
  const children = this._children.filter(child => child.kind !== 'Annotation');
1419
1446
  json[`$${this.kind}`] = children.length ? children[0].toJSON() : {};
1420
- this.toJSONattributes(json);
1421
- return json;
1447
+ return this.toJSONattributes(json);
1422
1448
  }
1423
1449
  toJSONattributes(json) {
1424
1450
  super.toJSONattributes(json);
@@ -1445,8 +1471,7 @@ function getEdm( options, messageFunctions ) {
1445
1471
  // first expression only, if any
1446
1472
  const children = this._children.filter(child => child.kind !== 'Annotation');
1447
1473
  json[`$${this.kind}`] = children.length ? children[0].toJSON() : '';
1448
- this.toJSONattributes(json);
1449
- return json;
1474
+ return this.toJSONattributes(json);
1450
1475
  }
1451
1476
 
1452
1477
  toJSONattributes(json) { // including Name
@@ -59,7 +59,6 @@ function inboundQualificationChecks( csn, options, messageFunctions,
59
59
  markBindingParamPaths(action, aLoc);
60
60
  forEachMemberRecursively(action, checkIfItemsOfItems, aLoc);
61
61
  checkIfItemsOfItems(action.returns, undefined, undefined, aLoc.concat('returns'));
62
- markBindingParamPaths(action, aLoc);
63
62
  });
64
63
  }
65
64
 
@@ -160,6 +160,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
160
160
  forEachDefinition(reqDefs, [
161
161
  exposeTargetsAsProxiesOrSchemaRefs,
162
162
  determineEntitySet,
163
+ annotateOptionalActFuncParams,
163
164
  ]);
164
165
  // finalize proxy creation
165
166
  mergeProxiesIntoModel();
@@ -170,7 +171,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
170
171
  forEachDefinition(reqDefs, [
171
172
  initEdmNavPropBindingTargets,
172
173
  pullupCapabilitiesAnnotations,
173
- annotateOptionalActFuncParams,
174
174
  ]);
175
175
  }
176
176
 
@@ -685,8 +685,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
685
685
  return;
686
686
 
687
687
  let keys = Object.create(null);
688
- const validFrom = []; const
689
- validKey = [];
688
+ const validFrom = [];
689
+ const validKey = [];
690
690
 
691
691
  // Iterate all struct elements
692
692
  forEachMemberRecursively(def.items || def, (element, elementName, prop, _path, construct) => {
@@ -796,7 +796,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
796
796
  Do not render (ignore) elements as properties
797
797
  In V4:
798
798
  1) If this is a foreign key of an association to a container which *is* used
799
- to establish the containment via composition and $self comparison.
799
+ to establish the relation via composition and $self comparison.
800
800
  The $self comparison can only be evaluated after the ON conditions have been
801
801
  parsed in prepareConstraints().
802
802
  2) For all other foreign keys let isEdmPropertyRendered() decide.
@@ -2095,45 +2095,58 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2095
2095
 
2096
2096
  function iterateParams( action, location ) {
2097
2097
  let optPns = [];
2098
+ const isBP = p => (p.items?.type || p.type) === special$self;
2099
+
2098
2100
  if (action.params) {
2099
2101
  Object.entries(action.params).forEach(([ pn, p ]) => {
2100
- // user assigned annotation, don't touch it
2102
+ // user assigned annotation, don't touch it
2101
2103
  const defT = reqDefs.definitions[p.items?.type || p.type];
2102
2104
  const isStructType = !!(defT?.items?.elements || defT?.elements);
2103
2105
  const isItems = !!(p.items || defT?.items);
2104
2106
 
2105
- if (Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a] !== null)) {
2106
- optPns.push(p);
2107
+ if (Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a] != null)) {
2108
+ // expand short cut annotation for unspecified default value
2109
+ if (typeof p['@Core.OptionalParameter'] === 'boolean') {
2110
+ if (p['@Core.OptionalParameter'] && !isBP(p) && !options.isV2()) {
2111
+ if (p.default?.val !== undefined) {
2112
+ if (p.default.val !== null && (isStructType || isItems))
2113
+ warning('odata-ignoring-param-default', location.concat(pn), { '#': 'colitem' });
2114
+ else
2115
+ p['@Core.OptionalParameter'] = { DefaultValue: p.default.val };
2116
+ }
2117
+ else {
2118
+ p['@Core.OptionalParameter'] = { $Type: '' };
2119
+ }
2120
+ optPns.push(p);
2121
+ }
2122
+ else { // reset falsy annotation, param is NOT optional
2123
+ p['@Core.OptionalParameter'] = null;
2124
+ }
2125
+ }
2126
+ else {
2127
+ optPns.push(p);
2128
+ }
2107
2129
  }
2108
- // default value automatically makes param optional
2109
- else if (isBetaEnabled(options, 'optionalActionFunctionParameters')) {
2130
+ else if (!isBP(p) && !options.isV2()) {
2131
+ // default value automatically makes param optional
2110
2132
  if (p.default?.val !== undefined) {
2111
- if (p.default.val !== null && (isStructType || isItems)) {
2133
+ if (p.default.val !== null && (isStructType || isItems))
2112
2134
  warning('odata-ignoring-param-default', location.concat(pn), { '#': 'colitem' });
2113
- }
2114
- else {
2135
+ else
2115
2136
  edmUtils.assignAnnotation(p, '@Core.OptionalParameter.DefaultValue', p.default.val);
2116
- optPns.push(p);
2117
- }
2137
+ optPns.push(p);
2118
2138
  }
2119
- // if no default is available, nullable makes param optional
2120
- else if (!p.notNull) {
2121
- edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
2139
+ // nullable action params are optional (implicit default null)
2140
+ else if (!p.notNull && action.kind === 'action') {
2122
2141
  optPns.push(p);
2123
2142
  }
2124
- else {
2125
- // this is a mandatory parameter, warn about all previously collected optional parameters
2126
- optPns.forEach((op) => {
2127
- const type = op.items?.type || op.type;
2128
- if (type !== special$self)
2129
- error('odata-parameter-order', location.concat(op.name));
2130
- });
2143
+ else if (action.kind === 'function') {
2144
+ // this is a mandatory parameter, warn about all previously collected optional parameters
2145
+ if (optPns.filter(op => (op.items?.type || op.type) !== special$self).length)
2146
+ error('odata-parameter-order', location.concat(pn));
2131
2147
  optPns = [];
2132
2148
  }
2133
2149
  }
2134
- else if (p.default) {
2135
- warning('odata-ignoring-param-default', location.concat(pn));
2136
- }
2137
2150
  });
2138
2151
  }
2139
2152
  }
@@ -386,7 +386,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
386
386
  // If this association points to a redirected Parameter EntityType, do not calculate any constraints,
387
387
  // continue with multiplicity
388
388
  if (assocCsn._target.$isParamEntity)
389
- assocCsn._constraints.constraints = Object.create(null);
389
+ assocCsn._constraints.constraints = {};
390
390
 
391
391
  return assocCsn._constraints;
392
392