@sap/cds-compiler 4.7.6 → 4.9.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 (129) hide show
  1. package/CHANGELOG.md +63 -3
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +28 -1
  6. package/bin/cdshi.js +13 -3
  7. package/doc/CHANGELOG_BETA.md +24 -1
  8. package/lib/api/main.js +119 -46
  9. package/lib/api/options.js +51 -0
  10. package/lib/api/validate.js +1 -5
  11. package/lib/base/builtins.js +116 -0
  12. package/lib/base/keywords.js +5 -1
  13. package/lib/base/location.js +91 -14
  14. package/lib/base/message-registry.js +76 -46
  15. package/lib/base/messages.js +121 -35
  16. package/lib/base/model.js +4 -7
  17. package/lib/checks/actionsFunctions.js +3 -3
  18. package/lib/checks/annotationsOData.js +3 -0
  19. package/lib/checks/defaultValues.js +5 -2
  20. package/lib/checks/elements.js +2 -1
  21. package/lib/checks/enricher.js +2 -2
  22. package/lib/checks/queryNoDbArtifacts.js +5 -3
  23. package/lib/checks/utils.js +1 -1
  24. package/lib/checks/validator.js +8 -56
  25. package/lib/compiler/assert-consistency.js +11 -7
  26. package/lib/compiler/builtins.js +0 -74
  27. package/lib/compiler/checks.js +105 -29
  28. package/lib/compiler/define.js +37 -25
  29. package/lib/compiler/extend.js +35 -12
  30. package/lib/compiler/index.js +9 -10
  31. package/lib/compiler/lsp-api.js +5 -0
  32. package/lib/compiler/populate.js +13 -5
  33. package/lib/compiler/propagator.js +24 -18
  34. package/lib/compiler/resolve.js +47 -45
  35. package/lib/compiler/shared.js +61 -21
  36. package/lib/compiler/tweak-assocs.js +15 -90
  37. package/lib/compiler/utils.js +3 -3
  38. package/lib/compiler/xpr-rewrite.js +689 -0
  39. package/lib/compiler/{classes.js → xsn-model.js} +0 -16
  40. package/lib/edm/annotations/edmJson.js +7 -5
  41. package/lib/edm/annotations/genericTranslation.js +149 -71
  42. package/lib/edm/csn2edm.js +25 -9
  43. package/lib/edm/edm.js +7 -7
  44. package/lib/edm/edmInboundChecks.js +57 -5
  45. package/lib/edm/edmPreprocessor.js +54 -25
  46. package/lib/edm/edmUtils.js +3 -16
  47. package/lib/gen/Dictionary.json +138 -14
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +1 -1
  50. package/lib/gen/languageParser.js +2085 -1989
  51. package/lib/json/csnVersion.js +7 -4
  52. package/lib/json/from-csn.js +21 -11
  53. package/lib/json/to-csn.js +8 -4
  54. package/lib/language/antlrParser.js +1 -1
  55. package/lib/language/genericAntlrParser.js +23 -16
  56. package/lib/language/multiLineStringParser.js +2 -2
  57. package/lib/language/textUtils.js +1 -1
  58. package/lib/main.d.ts +90 -14
  59. package/lib/main.js +9 -1
  60. package/lib/model/cloneCsn.js +21 -9
  61. package/lib/model/csnRefs.js +153 -42
  62. package/lib/model/csnUtils.js +14 -11
  63. package/lib/model/enrichCsn.js +4 -2
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/model/sortViews.js +14 -6
  66. package/lib/modelCompare/compare.js +135 -122
  67. package/lib/optionProcessor.js +49 -2
  68. package/lib/render/DuplicateChecker.js +6 -6
  69. package/lib/render/manageConstraints.js +1 -0
  70. package/lib/render/toCdl.js +6 -3
  71. package/lib/render/toHdbcds.js +4 -48
  72. package/lib/render/toSql.js +6 -3
  73. package/lib/transform/addTenantFields.js +58 -35
  74. package/lib/transform/db/applyTransformations.js +34 -1
  75. package/lib/transform/db/constraints.js +1 -1
  76. package/lib/transform/db/expansion.js +11 -3
  77. package/lib/transform/db/flattening.js +71 -46
  78. package/lib/transform/db/groupByOrderBy.js +2 -2
  79. package/lib/transform/db/temporal.js +6 -3
  80. package/lib/transform/db/transformExists.js +2 -2
  81. package/lib/transform/db/views.js +1 -4
  82. package/lib/transform/effective/annotations.js +194 -0
  83. package/lib/transform/effective/main.js +11 -10
  84. package/lib/transform/effective/misc.js +45 -14
  85. package/lib/transform/effective/types.js +4 -3
  86. package/lib/transform/forOdata.js +29 -12
  87. package/lib/transform/forRelationalDB.js +104 -113
  88. package/lib/transform/localized.js +7 -6
  89. package/lib/transform/odata/flattening.js +228 -107
  90. package/lib/transform/odata/toFinalBaseType.js +10 -26
  91. package/lib/transform/odata/typesExposure.js +41 -25
  92. package/lib/transform/parseExpr.js +4 -7
  93. package/lib/transform/transformUtils.js +50 -43
  94. package/lib/transform/translateAssocsToJoins.js +48 -48
  95. package/lib/transform/universalCsn/coreComputed.js +2 -1
  96. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  97. package/package.json +2 -2
  98. package/share/messages/README.md +4 -0
  99. package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
  100. package/share/messages/anno-missing-rewrite.md +45 -0
  101. package/share/messages/check-proper-type-of.md +1 -1
  102. package/share/messages/def-duplicate-autoexposed.md +1 -1
  103. package/share/messages/extend-repeated-intralayer.md +3 -16
  104. package/share/messages/extend-unrelated-layer.md +1 -1
  105. package/share/messages/message-explanations.json +2 -0
  106. package/share/messages/redirected-to-ambiguous.md +1 -1
  107. package/share/messages/redirected-to-complex.md +1 -1
  108. package/share/messages/redirected-to-unrelated.md +1 -1
  109. package/share/messages/rewrite-not-supported.md +1 -1
  110. package/share/messages/syntax-expecting-unsigned-int.md +2 -2
  111. package/share/messages/type-missing-enum-value.md +59 -0
  112. package/share/messages/wildcard-excluding-one.md +1 -1
  113. package/bin/.eslintrc.json +0 -17
  114. package/lib/api/.eslintrc.json +0 -37
  115. package/lib/checks/.eslintrc.json +0 -31
  116. package/lib/compiler/.eslintrc.json +0 -8
  117. package/lib/edm/.eslintrc.json +0 -46
  118. package/lib/inspect/.eslintrc.json +0 -4
  119. package/lib/json/.eslintrc.json +0 -4
  120. package/lib/language/.eslintrc.json +0 -4
  121. package/lib/model/.eslintrc.json +0 -13
  122. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  123. package/lib/render/.eslintrc.json +0 -22
  124. package/lib/transform/.eslintrc.json +0 -13
  125. package/lib/transform/db/.eslintrc.json +0 -41
  126. package/lib/transform/draft/.eslintrc.json +0 -4
  127. package/lib/transform/effective/.eslintrc.json +0 -4
  128. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  129. package/lib/utils/.eslintrc.json +0 -7
@@ -39,24 +39,8 @@ class XsnName {
39
39
  location;
40
40
  }
41
41
 
42
- class CsnLocation {
43
- file;
44
- line;
45
- col;
46
- endLine;
47
- endCol;
48
- constructor(file, line, col, endLine, endCol) {
49
- this.file = file;
50
- this.line = line;
51
- this.col = col;
52
- this.endLine = endLine;
53
- this.endCol = endCol;
54
- }
55
- }
56
-
57
42
  module.exports = {
58
43
  XsnSource,
59
44
  XsnArtifact,
60
45
  XsnName,
61
- CsnLocation,
62
46
  };
@@ -7,7 +7,7 @@ const {
7
7
  EdmTypeFacetNames,
8
8
  EdmPrimitiveTypeMap,
9
9
  } = require('../EdmPrimitiveTypeDefinitions.js');
10
- const { isBuiltinType, isAnnotationExpression } = require('../../model/csnUtils');
10
+ const { isBuiltinType, isAnnotationExpression } = require('../../base/builtins');
11
11
  const { transformExpression } = require('../../transform/db/applyTransformations.js');
12
12
 
13
13
  /**
@@ -270,7 +270,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
270
270
  evalArgs({ exact: 2 }, xpr, prop);
271
271
  transformExpression(xpr, undefined, transform);
272
272
  delete parent[prop];
273
- parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
273
+ parentparent[parentprop].$Apply = xpr;
274
+ parentparent[parentprop].$Function = 'odata.concat';
274
275
  };
275
276
  //----------------------------------
276
277
  // ARITHMETICAL AND UNARY
@@ -305,8 +306,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
305
306
  };
306
307
  transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
307
308
  if (xpr.some(ps => ps.args || ps.where)) {
308
- error('odata-anno-xpr-args', location, {
309
- anno, elemref: parent, '#': 'wrongref',
309
+ error('odata-anno-xpr-ref', location, {
310
+ anno, elemref: parent, '#': 'args',
310
311
  });
311
312
  }
312
313
  const [ head, ...tail ] = xpr;
@@ -742,7 +743,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
742
743
  }
743
744
  else {
744
745
  evalArgs(argDef, parent.args, xpr);
745
- parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
746
+ parentparent[parentprop].$Apply = [ ...(parent.args || []) ];
747
+ parentparent[parentprop].$Function = funcName;
746
748
  delete parentparent[parentprop].func;
747
749
  delete parentparent[parentprop].args;
748
750
  transformExpression(parentparent, parentprop, transform);
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const { isEdmPropertyRendered, isBuiltinType, transformExpression } = require('../../model/csnUtils');
3
+ const { isEdmPropertyRendered, transformExpression } = require('../../model/csnUtils');
4
+ const { isBuiltinType } = require('../../base/builtins');
4
5
  const edmUtils = require('../edmUtils.js');
5
6
  const oDataDictionary = require('../../gen/Dictionary.json');
6
7
  const preprocessAnnotations = require('./preprocessAnnotations.js');
7
8
  const { forEachDefinition } = require('../../model/csnUtils');
8
- // const { csnRefs } = require('../../model/csnRefs');
9
9
  const { isBetaEnabled, setProp } = require('../../base/model.js');
10
10
  const { xpr2edmJson, getEdmJsonHandler } = require('./edmJson.js');
11
11
  const { vocabularyDefinitions } = require('./vocabularyDefinitions.js');
@@ -18,7 +18,7 @@ const { EdmPathTypeMap } = require('../EdmPrimitiveTypeDefinitions.js');
18
18
  * v - array with two boolean entries, first is for v2, second is for v4
19
19
  * dictReplacement: for test purposes, replaces the standard oDataDictionary
20
20
  */
21
- function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
21
+ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
22
22
  Edm, options, messageFunctions, mergedVocDefs = vocabularyDefinitions ) {
23
23
  const gAnnosArray = []; // global variable where we store all the generated annotations
24
24
  const usedExperimentalTerms = {}; // take note of all experimental annos that have been used
@@ -27,7 +27,6 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
27
27
  const { v } = options;
28
28
  const { message, error } = messageFunctions;
29
29
  const { handleEdmJson } = getEdmJsonHandler(Edm, options, messageFunctions, handleTerm);
30
- // const { inspectRef } = csnRefs(reqDefs);
31
30
 
32
31
  const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
33
32
 
@@ -57,8 +56,10 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
57
56
  { name: serviceName, '#': 'service' } );
58
57
  }
59
58
 
60
- assignParameterAnnotations();
61
-
59
+ forEachDefinition(reqDefs, (def, defName) => {
60
+ if (defName.startsWith(`${serviceName}.`))
61
+ assignParameterAnnotations(def);
62
+ });
62
63
  // Crawl over the csn and trigger the annotation translation for all kinds
63
64
  // of annotated things.
64
65
  // Note: only works for single service
@@ -96,15 +97,29 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
96
97
  return v && v[0];
97
98
  }
98
99
 
99
- function assignParameterAnnotations() {
100
+ function assignParameterAnnotations( def ) {
100
101
  // Copy annotations from origin to parameter entity if it's
101
102
  // qualified with #$parameters or if its applicable to an EntitySet or Singleton
102
103
  const scopeCheck = {
103
104
  ref: (elemref, prop, xpr, path) => {
104
- if (scopeCheck.scope === 'param' && (!elemref.param || (xpr[0].id || xpr[0]) === '$self'))
105
- error('odata-anno-xpr-ref', path, { elemref, anno: scopeCheck.anno, '#': 'notaparam' });
106
- if (scopeCheck.scope === 'type' && elemref.param)
107
- error('odata-anno-xpr-ref', path, { elemref, anno: scopeCheck.anno, '#': 'notaneelement' });
105
+ if (scopeCheck.scope === 'param' &&
106
+ (!elemref.param ||
107
+ (xpr[0].id || xpr[0]) === '$self' && !def.elements.$self)) {
108
+ error('odata-anno-xpr-ref', path, { anno: scopeCheck.anno, elemref, '#': 'notaparam' });
109
+ // don't try to resolve those paths later on
110
+ delete elemref[prop];
111
+ }
112
+ if (scopeCheck.scope === 'type' && elemref.param) {
113
+ error('odata-anno-xpr-ref', path, { anno: scopeCheck.anno, elemref, '#': 'notaneelement' });
114
+ delete elemref[prop];
115
+ }
116
+ if (scopeCheck.scope === 'param' && elemref.param) {
117
+ // make sure that path is resolvable as element path later on
118
+ delete elemref.param;
119
+ const head = xpr[0].id || xpr[0];
120
+ if (head[0] === '$')
121
+ xpr.unshift('$self');
122
+ }
108
123
  },
109
124
  };
110
125
  const checkDict = (dict, scope) => {
@@ -119,52 +134,50 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
119
134
  });
120
135
  }
121
136
  };
122
- const checkDef = (def, scope) => {
137
+ const checkObj = (obj, scope) => {
123
138
  scopeCheck.scope = scope;
124
- const knownAnnos = filterKnownAnnotations(def);
139
+ const knownAnnos = filterKnownAnnotations(obj);
125
140
  knownAnnos.forEach((pn) => {
126
141
  scopeCheck.anno = pn;
127
- transformExpression(def, pn, scopeCheck, def.$path);
142
+ transformExpression(obj, pn, scopeCheck, obj.$path);
128
143
  });
129
144
  };
130
- forEachDefinition(reqDefs, (object) => {
131
- if (object.$isParamEntity && object._origin) {
132
- // check for correct paths
133
- if (object._origin.$paramAnnoProxies) {
134
- object.$eltAnnoProxies = object._origin.$paramAnnoProxies;
135
- object._origin.$paramAnnoProxies = null;
136
- checkDict(object.$eltAnnoProxies, 'param');
137
- }
138
- checkDict(object._origin.$eltAnnoProxies, 'type');
139
- checkDict(object.elements, 'param');
140
- checkDict(object._origin.elements, 'type');
141
-
142
- scopeCheck.scope = 'param';
143
- Object.keys(object._origin).forEach((attr) => {
144
- if (attr[0] === '@') {
145
- scopeCheck.anno = attr;
146
- const [ prefix, innerAnnotation ] = attr.split('.@');
147
- const ns = whatsMyTermNamespace(prefix);
148
- if (ns) {
149
- const steps = prefix.replace(`@${ns}.`, '').split('.');
150
- const paramAnnoParts = steps[0].split('#$parameters');
151
- const dictTerm = getDictTerm(`${ns}.${paramAnnoParts[0]}`, options);
152
- if (paramAnnoParts.length > 1 || [ 'Singleton', 'EntitySet' ].some(y => dictTerm?.AppliesTo?.includes(y))) {
153
- steps[0] = `@${ns}.${paramAnnoParts.join('')}`;
154
- let newAnno = steps.join('.');
155
- if (innerAnnotation)
156
- newAnno += `.@${innerAnnotation}`;
157
- edmUtils.assignAnnotation(object, newAnno, object._origin[attr]);
158
- transformExpression(object._origin, attr, scopeCheck, object._origin.$path);
159
- if (paramAnnoParts.length > 1)
160
- delete object._origin[attr];
161
- }
145
+ if (def.$isParamEntity && def._origin) {
146
+ // check for correct paths
147
+ if (def._origin.$paramsAnnoProxies) {
148
+ def.$elementsAnnoProxies = def._origin.$paramsAnnoProxies;
149
+ checkDict(def.$elementsAnnoProxies, 'param');
150
+ }
151
+ checkDict(def._origin.$elementsAnnoProxies, 'type');
152
+ checkDict(def.elements, 'param');
153
+ checkDict(def._origin.elements, 'type');
154
+
155
+ scopeCheck.scope = 'param';
156
+ Object.keys(def._origin).forEach((attr) => {
157
+ if (attr[0] === '@') {
158
+ scopeCheck.anno = attr;
159
+ const [ prefix, innerAnnotation ] = attr.split('.@');
160
+ const ns = whatsMyTermNamespace(prefix);
161
+ if (ns) {
162
+ const steps = prefix.replace(`@${ns}.`, '').split('.');
163
+ const paramAnnoParts = steps[0].split('#$parameters');
164
+ const dictTerm = getDictTerm(`${ns}.${paramAnnoParts[0]}`, options);
165
+ if (paramAnnoParts.length > 1 ||
166
+ [ 'Singleton', 'EntitySet' ].some(y => dictTerm?.AppliesTo?.includes(y))) {
167
+ steps[0] = `@${ns}.${paramAnnoParts.join('')}`;
168
+ let newAnno = steps.join('.');
169
+ if (innerAnnotation)
170
+ newAnno += `.@${innerAnnotation}`;
171
+ edmUtils.assignAnnotation(def, newAnno, def._origin[attr]);
172
+ transformExpression(def._origin, attr, scopeCheck, def._origin.$path);
173
+ if (paramAnnoParts.length > 1)
174
+ delete def._origin[attr];
162
175
  }
163
176
  }
164
- });
165
- checkDef(object._origin, 'type');
166
- }
167
- });
177
+ }
178
+ });
179
+ checkObj(def._origin, 'type');
180
+ }
168
181
  }
169
182
 
170
183
  /*
@@ -179,12 +192,14 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
179
192
  There is one exception (Schema), see below
180
193
 
181
194
  carrier = service
182
- the target is the EntityContainer, unless the annotation has an "AppliesTo" where only Schema is given, but not EntityContainer
183
- then the <Annotation ...> is directly put into <Schema ...> without an enclosing <Annotations ...>
195
+ the target is the EntityContainer, unless the annotation has an "AppliesTo"
196
+ where only Schema is given, but not EntityContainer then the <Annotation ...>
197
+ is directly put into <Schema ...> without an enclosing <Annotations ...>
184
198
 
185
199
  carrier = entity (incl. view/projection)
186
- the target is the corresponding EntityType, unless the annotation has an "AppliesTo" where only EntitySet is given, but not EntityType
187
- then the target is the corresponding EntitySet
200
+ the target is the corresponding EntityType, unless the annotation has an
201
+ "AppliesTo" where only EntitySet is given, but not EntityType then the target
202
+ is the corresponding EntitySet
188
203
 
189
204
  carrier = structured type
190
205
  the target is the corresponding ComplexType
@@ -208,10 +223,11 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
208
223
  // definition bound annotations
209
224
  handleAnnotations(defName, def, location);
210
225
  // definition bound element annotations
211
- if (def.$eltAnnoProxies) {
212
- Object.entries(def.$eltAnnoProxies).forEach(([ elemPath, element ]) => {
226
+ if (def.$elementsAnnoProxies) {
227
+ Object.entries(def.$elementsAnnoProxies).forEach(([ elemPath, element ]) => {
213
228
  const edmTargetName = `${defName}/${elemPath}`;
214
- handleAnnotations(edmTargetName, element, element.$path);
229
+ handleAnnotations(edmTargetName, element, element.$path,
230
+ [ ...location, '$elementsAnnoProxies', elemPath ]);
215
231
  });
216
232
  }
217
233
  // element bound annotations
@@ -269,15 +285,31 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
269
285
  handleAnnotations(actionName, cAction, location);
270
286
 
271
287
  if (cAction.params) {
288
+ if (cAction.$paramsAnnoProxies) {
289
+ Object.entries(cAction.$paramsAnnoProxies).forEach(([ paramPath, param ]) => {
290
+ const edmTargetName = `${actionName}/${paramPath}`;
291
+ handleAnnotations(edmTargetName, param, param.$path,
292
+ [ ...location, '$paramsAnnoProxies', paramPath ]);
293
+ });
294
+ }
272
295
  Object.entries(cAction.params).forEach(([ n, p ]) => {
273
296
  const edmTargetName = `${actionName}/${n}`;
274
- handleAnnotations(edmTargetName, p, [ ...location, 'params', n ]);
297
+ handleAnnotations(edmTargetName, p,
298
+ [ ...location, 'params', n ]);
275
299
  });
276
300
  }
277
301
  if (cAction.returns) {
302
+ if (cAction.$returnsAnnoProxies) {
303
+ Object.entries(cAction.$returnsAnnoProxies).forEach(([ returnsPath, returns ]) => {
304
+ const edmTargetName = `${actionName}/${returnsPath}`;
305
+ handleAnnotations(edmTargetName, returns, returns.$path,
306
+ [ ...location, '$returnsAnnoProxies', returnsPath ]);
307
+ });
308
+ }
278
309
  const edmTargetName = `${actionName}/$ReturnType`;
279
310
  setProp(cAction.returns, '$appliesToReturnType', true);
280
- handleAnnotations(edmTargetName, cAction.returns, [ ...location, 'returns' ]);
311
+ handleAnnotations(edmTargetName, cAction.returns,
312
+ [ ...location, 'returns' ]);
281
313
  delete cAction.returns.$appliesToReturnType;
282
314
  }
283
315
 
@@ -286,12 +318,19 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
286
318
  const params = [];
287
319
  if (entityNameIfBound) {
288
320
  // If this is an action and has an explicit binding parameter add it here
289
- if (cAction.$bindingParam && cAction.kind === 'action')
290
- params.push(cAction.$bindingParam.items ? `Collection(${entityNameIfBound})` : entityNameIfBound);
291
-
321
+ if (cAction.$bindingParam && cAction.kind === 'action') {
322
+ params.push(
323
+ cAction.$bindingParam.items
324
+ ? `Collection(${entityNameIfBound})`
325
+ : entityNameIfBound
326
+ );
327
+ }
292
328
  // If action/function has no explicit binding parameter add it here
293
- else if (!cAction.$bindingParam)
294
- params.push(cAction['@cds.odata.bindingparameter.collection'] ? `Collection(${entityNameIfBound})` : entityNameIfBound);
329
+ else if (!cAction.$bindingParam) {
330
+ params.push(cAction['@cds.odata.bindingparameter.collection']
331
+ ? `Collection(${entityNameIfBound})`
332
+ : entityNameIfBound);
333
+ }
295
334
  }
296
335
  // In case this is a function the explicit binding parameter is part of
297
336
  // the functions params dictionary. Only for functions all parameters must
@@ -324,7 +363,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
324
363
  // edmTargetName : string, name of the target in edm
325
364
  // carrier: object, the annotated cds thing, contains all the annotations
326
365
  // as properties with names starting with @
327
- function handleAnnotations( edmTargetName, carrier, location ) {
366
+ function handleAnnotations( edmTargetName, carrier, location, csnPathResolutionLocation = location ) {
328
367
  // collect the names of the carrier's annotation properties
329
368
  // keep only those annotations that - start with a known vocabulary name
330
369
  // - have a value other than null
@@ -351,12 +390,49 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
351
390
  if (knownAnnos.length === 0)
352
391
  return;
353
392
  }
354
- if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
355
- knownAnnos.forEach((knownAnno) => {
356
- if (knownAnno.search(/\.\$edmJson\./g) < 0)
357
- xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
358
- });
359
- }
393
+
394
+ knownAnnos.forEach((knownAnno) => {
395
+ if (knownAnno.search(/\.\$edmJson\./g) < 0) {
396
+ transformExpression(carrier, knownAnno, {
397
+ ref: (elemref, prop, xpr, csnPath) => {
398
+ if (options.isV2() && elemref.$bparam) {
399
+ error('odata-anno-xpr-ref', location, {
400
+ elemref, anno: knownAnno, version: '2.0', '#': 'bparam_v2',
401
+ });
402
+ return;
403
+ }
404
+ const { links, scope } = reqDefsUtils.inspectRef(csnPath);
405
+ let i = scope === '$self' ? 1 : 0;
406
+ if (scope === '$magic') {
407
+ error('odata-anno-xpr-ref', location, {
408
+ elemref, anno: knownAnno, '#': 'magic',
409
+ });
410
+ return;
411
+ }
412
+ let stop = false;
413
+ for (; i < links.length && !stop; i++) {
414
+ if (!isEdmPropertyRendered(links[i].art, csnPath)) {
415
+ error('odata-anno-xpr-ref', location, {
416
+ count: i + 1, elemref, anno: knownAnno, '#': 'notrendered',
417
+ });
418
+ stop = true;
419
+ }
420
+ if (links[i].art?._target?.$proxy && i < links.length - 1) {
421
+ const proxy = links[i].art?._target;
422
+ const eltName = links[i + 1].art?.name;
423
+ if (!proxy.elements[eltName]) {
424
+ error('odata-anno-xpr-ref', location, {
425
+ count: i + 2, elemref, anno: knownAnno, '#': 'notrendered',
426
+ });
427
+ stop = true;
428
+ }
429
+ }
430
+ }
431
+ },
432
+ }, csnPathResolutionLocation);
433
+ xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
434
+ }
435
+ });
360
436
 
361
437
  const prefixTree = createPrefixTree();
362
438
 
@@ -1358,6 +1434,8 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
1358
1434
  dict.xrefs[myServiceRoot] = { $myServiceRoot: myServiceRoot, used: false };
1359
1435
  const edmType = new Edm.TypeBase(options.v, {}, annoDef);
1360
1436
  dictDef = edmType._edmAttributes;
1437
+ if (dictDef.Type?.startsWith('Edm.Int'))
1438
+ dictDef.Type = 'Edm.Int';
1361
1439
  dictDef.$myServiceRoot = myServiceRoot;
1362
1440
  let val = annoDef['@odata.term.AppliesTo'];
1363
1441
  if (val != null)
@@ -11,8 +11,9 @@ const { initializeModel } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp, isBetaEnabled } = require('../base/model');
13
13
  const {
14
- isEdmPropertyRendered, isBuiltinType, getUtils,
14
+ isEdmPropertyRendered, getUtils, findAnnotationExpression,
15
15
  } = require('../model/csnUtils');
16
+ const { isBuiltinType } = require('../base/builtins');
16
17
  const { checkCSNVersion } = require('../json/csnVersion');
17
18
  const {
18
19
  EdmTypeFacetMap,
@@ -86,7 +87,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
86
87
  return rc;
87
88
  }
88
89
 
89
- const reqDefsUtils = getUtils(reqDefs);
90
+ // refresh csn cache after preprocessor model augmentation with parameters, types, proxies etc
91
+ const csnUtils = getUtils(csn);
90
92
 
91
93
  if (serviceNames === undefined)
92
94
  serviceNames = options.serviceNames;
@@ -744,17 +746,31 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
744
746
  // bpName is eventually used later for EntitySetPath
745
747
  // No explicit binding parameter, check (user defined) annotation value)
746
748
  if (!actionCsn.$bindingParam) {
747
- const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
749
+ const bpnAnnoName = '@cds.odata.bindingparameter.name';
750
+ const bpnAnnoLoc = [ ...location, bpnAnnoName ];
751
+ const bpNameAnno = actionCsn[bpnAnnoName];
748
752
  if (bpNameAnno != null) {
749
753
  if (typeof bpNameAnno === 'string')
750
754
  bpName = bpNameAnno;
751
- if (typeof bpNameAnno === 'object' && bpNameAnno['='])
752
- bpName = bpNameAnno['='];
755
+ if (typeof bpNameAnno === 'object' && bpNameAnno['=']) {
756
+ if (findAnnotationExpression(actionCsn, bpnAnnoName)) {
757
+ if (!bpNameAnno.ref)
758
+ message('odata-anno-xpr', bpnAnnoLoc, { anno: bpnAnnoName, '#': 'unexpected' });
759
+ else if (bpNameAnno.ref.length !== 1)
760
+ error('odata-anno-xpr-ref', bpnAnnoLoc, { anno: bpnAnnoName, elemref: bpNameAnno, '#': 'invalid' });
761
+ else
762
+ bpName = bpNameAnno.ref[0];
763
+ }
764
+ else {
765
+ bpName = bpNameAnno['='];
766
+ }
767
+ }
753
768
  }
769
+
754
770
  if (!edmUtils.isODataSimpleIdentifier(bpName))
755
- message('odata-spec-violation-id', [ ...location, '@cds.odata.bindingparameter.name' ], { id: bpName });
771
+ message('odata-spec-violation-id', bpnAnnoLoc, { id: bpName });
756
772
  if (actionCsn.params && actionCsn.params[bpName])
757
- error('duplicate-definition', [ ...location, '@cds.odata.bindingparameter.name' ], { '#': 'param', name: bpName });
773
+ error('duplicate-definition', bpnAnnoLoc, { '#': 'param', name: bpName });
758
774
  }
759
775
  if (entityCsn) {
760
776
  actionNode.setEdmAttribute('IsBound', true);
@@ -1072,8 +1088,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
1072
1088
 
1073
1089
  // generate the Edm.Annotations tree and append it to the corresponding schema
1074
1090
  function addAnnotations2XServiceRefs( ) {
1075
- options.getFinalTypeInfo = reqDefsUtils.getFinalTypeInfo;
1076
- const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
1091
+ options.getFinalTypeInfo = csnUtils.getFinalTypeInfo;
1092
+ const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csnUtils, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
1077
1093
  // distribute edm:Annotations into the schemas
1078
1094
  // Distribute each anno into Schema
1079
1095
  annos.forEach((anno) => {
package/lib/edm/edm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const edmUtils = require('./edmUtils.js');
4
- const { isBuiltinType } = require('../model/csnUtils.js');
3
+ const edmUtils = require('./edmUtils');
4
+ const { isBuiltinType } = require('../base/builtins');
5
5
  const { forEach } = require('../utils/objectUtils');
6
6
  const {
7
7
  EdmTypeFacetMap,
@@ -19,11 +19,11 @@ function getEdm( options, messageFunctions ) {
19
19
  */
20
20
  constructor(version, attributes = Object.create(null), csn = undefined) {
21
21
  if (!attributes || typeof attributes !== 'object')
22
- error(null, 'Please debug me: attributes must be a dictionary');
22
+ error(null, 'Debug me: attributes must be a dictionary');
23
23
  if (!Array.isArray(version))
24
- error(null, `Please debug me: v is either undefined or not an array: ${version}`);
24
+ error(null, `Debug me: v is either undefined or not an array: ${version}`);
25
25
  if (version.filter(v => v).length !== 1)
26
- error(null, 'Please debug me: exactly one version must be set');
26
+ error(null, 'Debug me: exactly one version must be set');
27
27
 
28
28
  // Common attributes of JSON and XML.
29
29
  // Note: Can't assign attributes directly, due to the input object being modified.
@@ -782,7 +782,7 @@ function getEdm( options, messageFunctions ) {
782
782
  super(version, attributes, csn);
783
783
  const appliesTo = csn['@odata.term.AppliesTo'];
784
784
  if (appliesTo)
785
- this.setEdmAttribute('AppliesTo', Array.isArray(appliesTo) ? appliesTo.map(v => v['='] || v).join(' ') : appliesTo['='] || appliesTo);
785
+ this.setEdmAttribute('AppliesTo', Array.isArray(appliesTo) ? appliesTo.join(' ') : appliesTo);
786
786
  }
787
787
  }
788
788
 
@@ -1108,7 +1108,7 @@ function getEdm( options, messageFunctions ) {
1108
1108
  json.$OnDelete = c._edmAttributes.Action;
1109
1109
  break;
1110
1110
  default:
1111
- error(null, `Please debug me: Unhandled NavProp child: ${c.kind}`);
1111
+ error(null, `Debug me: Unhandled NavProp child: ${c.kind}`);
1112
1112
  }
1113
1113
  });
1114
1114
  // TODO Annotations
@@ -2,8 +2,10 @@
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const {
5
- forEachDefinition, forEachMemberRecursively, isBuiltinType, getUtils,
5
+ forEachDefinition, forEachMemberRecursively, getUtils,
6
+ transformExpression, findAnnotationExpression,
6
7
  } = require('../model/csnUtils');
8
+ const { isBuiltinType } = require('../base/builtins');
7
9
  const { assignAnnotation } = require('./edmUtils.js');
8
10
 
9
11
  // eslint-disable-next-line no-unused-vars
@@ -49,6 +51,17 @@ function inboundQualificationChecks( csn, options, messageFunctions,
49
51
  // check items.items
50
52
  checkIfItemsOfItems(def, undefined, undefined, location);
51
53
  forEachMemberRecursively(def, checkIfItemsOfItems, location);
54
+ checkIfItemsOfItems(def.returns, undefined, undefined, location.concat('returns'));
55
+ if (def.actions) {
56
+ Object.entries(def.actions).forEach(([ n, action ]) => {
57
+ const aLoc = location.concat('actions', n);
58
+ checkIfItemsOfItems(action, undefined, undefined, aLoc);
59
+ markBindingParamPaths(action, aLoc);
60
+ forEachMemberRecursively(action, checkIfItemsOfItems, aLoc);
61
+ checkIfItemsOfItems(action.returns, undefined, undefined, aLoc.concat('returns'));
62
+ markBindingParamPaths(action, aLoc);
63
+ });
64
+ }
52
65
 
53
66
  // decorate UUID keys with @Core.ComputedDefaultValue and complain
54
67
  // on named type UUID elements that have no such annotation
@@ -88,14 +101,23 @@ function inboundQualificationChecks( csn, options, messageFunctions,
88
101
  }
89
102
 
90
103
  function checkIfItemsOfItems( member, _memberName, _prop, path ) {
104
+ if (!member)
105
+ return;
91
106
  const memberType = csnUtils.effectiveType(member);
92
- if (memberType.items) {
93
- if (memberType.items.target) {
94
- const isComp = memberType.items.type === 'cds.Composition';
107
+ let { items } = memberType;
108
+ if (items) {
109
+ if (items.target) {
110
+ const isComp = items.type === 'cds.Composition';
95
111
  message('type-invalid-items', path, { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
96
112
  return;
97
113
  }
98
- if (memberType.items.items) {
114
+ let i = 1;
115
+ while (items) {
116
+ items = items.items;
117
+ if (items)
118
+ i++;
119
+ }
120
+ if (i > 1) {
99
121
  message('chained-array-of', path);
100
122
  return;
101
123
  }
@@ -105,6 +127,36 @@ function inboundQualificationChecks( csn, options, messageFunctions,
105
127
  message('chained-array-of', path);
106
128
  }
107
129
  }
130
+
131
+ // we need to know if the first path step is the bindind param
132
+ // for the rejection of V2 paths where the BP is ignored
133
+ function markBindingParamPaths( action, loc ) {
134
+ const special$self = !csn?.definitions?.$self && '$self';
135
+ if (action.params) {
136
+ const params = Object.entries(action.params);
137
+ const firstParam = params[0][1];
138
+ const type = firstParam?.items?.type || firstParam?.type;
139
+ if (type === special$self) {
140
+ const bindingParamName = params[0][0];
141
+ const markBindingParam = {
142
+ ref: (parent, prop, xpr) => {
143
+ if ((xpr[0].id || xpr[0]) === bindingParamName)
144
+ parent.$bparam = true;
145
+ },
146
+ };
147
+ let exprAnnos = Object.keys(action).filter(pn => findAnnotationExpression(action, pn));
148
+ exprAnnos.forEach((pn) => {
149
+ transformExpression(action, pn, markBindingParam, loc);
150
+ });
151
+ forEachMemberRecursively(action, (member, _memberName, _prop, path, _parent) => {
152
+ exprAnnos = Object.keys(member).filter(pn => findAnnotationExpression(member, pn));
153
+ exprAnnos.forEach((pn) => {
154
+ transformExpression(member, pn, markBindingParam, path);
155
+ });
156
+ }, loc);
157
+ }
158
+ }
159
+ }
108
160
  }
109
161
 
110
162
  function checkNestedContextsAndServices() {