@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
@@ -8,8 +8,9 @@
8
8
 
9
9
  const { setProp, isBetaEnabled } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
- const { getNamespace, copyAnnotations, isBuiltinType, findAnnotationExpression,
11
+ const { getNamespace, copyAnnotations, findAnnotationExpression,
12
12
  forEachDefinition, forEachMember, forEachGeneric, isEdmPropertyRendered } = require('../../model/csnUtils');
13
+ const { isBuiltinType } = require('../../base/builtins');
13
14
  const { CompilerAssertion } = require('../../base/error');
14
15
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
15
16
 
@@ -76,18 +77,21 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
76
77
  if (serviceName && requestedServiceNames.includes(serviceName)) {
77
78
  if (def.kind === 'type' || def.kind === 'entity') {
78
79
  forEachMember(def, (element, elementName, propertyName, path) => {
79
- if (propertyName === 'elements' || propertyName === 'params') {
80
+ if (propertyName === 'elements') {
80
81
  const newTypeName = getNewTypeName(element, elementName, defName, serviceName);
81
- exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, newTypeName, path);
82
+ exposeTypeOf(element, element.key, elementName, defName, serviceName, newTypeName, defName, path);
83
+ } else if (propertyName === 'params') {
84
+ const newTypeName = getNewTypeName(element, elementName, `ep_${defName.replace(/\./g, '_')}`, serviceName);
85
+ exposeTypeOf(element, true, elementName, defName, serviceName, newTypeName, defName, path);
82
86
  }
83
87
  }, path);
84
88
  }
85
89
 
86
90
  if (def.kind === 'action' || def.kind === 'function') {
87
- exposeTypesOfAction(def, defName, defName, serviceName, path);
91
+ exposeTypesOfAction(def, defName, defName, serviceName, path, false);
88
92
  }
89
93
  def.actions && Object.entries(def.actions).forEach(([actionName, action]) => {
90
- exposeTypesOfAction(action, `${defName}_${actionName}`, defName, serviceName, path.concat(['actions', actionName]));
94
+ exposeTypesOfAction(action, `${defName}_${actionName}`, defName, serviceName, path.concat(['actions', actionName]), true);
91
95
  });
92
96
  }
93
97
  });
@@ -105,7 +109,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
105
109
  csn.definitions[defName] = def;
106
110
  const artificialName = `term_${defName.replace(/\./g, '_')}`;//_${paramName}`;
107
111
  const newTypeName = getNewTypeName(undefined, undefined, artificialName, serviceName);
108
- exposeTypeOf(def, false, defName, defName, serviceName, newTypeName, path.concat(['vocabularies', defName]), undefined, true);
112
+ exposeTypeOf(def, false, defName, defName, serviceName, newTypeName, defName, path.concat(['vocabularies', defName]), undefined, true);
109
113
  }
110
114
  }
111
115
  });
@@ -121,17 +125,17 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
121
125
  * @param {String} actionName
122
126
  * @param {String} serviceName
123
127
  */
124
- function exposeTypesOfAction(action, actionName, defName, serviceName, path) {
128
+ function exposeTypesOfAction(action, actionName, defName, serviceName, path, isBound) {
125
129
  if (action.returns) {
126
130
  const artificialName = `return_${actionName.replace(/\./g, '_')}`;
127
131
  const newTypeName = getNewTypeName(action.returns, undefined, artificialName, serviceName);
128
- exposeTypeOf(action.returns, false, actionName, defName, serviceName, newTypeName, path.concat(['returns']));
132
+ exposeTypeOf(action.returns, false, actionName, defName, serviceName, newTypeName, defName, path.concat(['returns']));
129
133
  }
130
134
 
131
135
  action.params && Object.entries(action.params).forEach(([paramName, param]) => {
132
- const artificialName = `param_${actionName.replace(/\./g, '_')}`;//_${paramName}`;
136
+ const artificialName = `${isBound ? 'bap' : 'ap'}_${actionName.replace(/\./g, '_')}`;//_${paramName}`;
133
137
  const newTypeName = getNewTypeName(param, paramName, artificialName, serviceName);
134
- exposeTypeOf(param, false, actionName, defName, serviceName, newTypeName, path.concat(['params', paramName]));
138
+ exposeTypeOf(param, false, actionName, defName, serviceName, newTypeName, defName, path.concat(['params', paramName]));
135
139
  });
136
140
  }
137
141
 
@@ -144,7 +148,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
144
148
  * @param {String} serviceName
145
149
  * @param {String} newTypeName
146
150
  */
147
- function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName, isTermDef=false, ignoreInAPI=false) {
151
+ function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, lastNonAnonymousFQDefName, path, parentName, isTermDef=false, ignoreInAPI=false) {
148
152
  const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable();
149
153
  if (isExposable) {
150
154
  // this is the name used to register the new type in csn.definitions
@@ -164,6 +168,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
164
168
  // expose the type as a new one not changing the original definition.
165
169
  if(elements && !!node['@open'] !== !!typeDef['@open'])
166
170
  fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
171
+ lastNonAnonymousFQDefName = fullQualifiedNewTypeName;
167
172
  }
168
173
  // check if that type is already defined
169
174
  let newType = csn.definitions[fullQualifiedNewTypeName];
@@ -197,10 +202,12 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
197
202
  newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
198
203
  if (node.elements && node.elements[elemName].$location)
199
204
  setProp(newElem, '$location', node.elements[elemName].$location);
205
+ if (newElem.$path)
206
+ newElem.$path[1] = lastNonAnonymousFQDefName;
200
207
  defName = typeDef.kind === 'type' ? typeName : defName;
201
208
  {
202
209
  const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
203
- getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef, ignoreInAPI);
210
+ getNewTypeName(newElem, elemName, newTypeName, serviceName), lastNonAnonymousFQDefName, path, fullQualifiedNewTypeName, isTermDef, ignoreInAPI);
204
211
  // if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
205
212
  // been caught by expandToFinalBaseType() (forODataNew must not modify external imported services)
206
213
  if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
@@ -222,14 +229,23 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
222
229
  }
223
230
  });
224
231
 
225
- // expression annos and their sub annotations are not propagated to type
226
- let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
227
- if (pn[0] === '@')
228
- acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
229
- return acc;
230
- }, [ [], [] ]);
231
- nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
232
- copyAnnotations(typeDef, newType, false, {}, nxprANames);
232
+ // TODO: remove the this if and the else clause for the V5 release
233
+ if (isBetaEnabled(options, 'v5preview')) {
234
+ // We need this check, as we only need to copy the annotions if the type
235
+ // is user defined structured type outside of the service
236
+ if (!isAnonymous) {
237
+ copyAnnotations(typeDef, newType);
238
+ }
239
+ } else {
240
+ // expression annos and their sub annotations are not propagated to type
241
+ let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
242
+ if (pn[0] === '@')
243
+ acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
244
+ return acc;
245
+ }, [ [], [] ]);
246
+ nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
247
+ copyAnnotations(typeDef, newType, false, {}, nxprANames);
248
+ }
233
249
 
234
250
  // if the origin type had items, add items to exposed type
235
251
  if(typeDef.kind === 'type') {
@@ -241,7 +257,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
241
257
  }
242
258
  else if(isTermDef) {
243
259
  newType = Object.create(null);
244
- for(let n in typeDef) {
260
+ for(const n in typeDef) {
245
261
  newType[n] = typeDef[n];
246
262
  }
247
263
  newType.kind = 'type';
@@ -280,7 +296,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
280
296
  function isTypeExposable() {
281
297
  let typeName = undefined;
282
298
  let typeDef = node;
283
- let elements = (node.items?.elements || node.elements)
299
+ const elements = (node.items?.elements || node.elements)
284
300
  // anonymous structured type
285
301
  if(elements)
286
302
  return { isExposable: true, typeDef, typeName, elements, isAnonymous: true };
@@ -302,7 +318,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
302
318
  typeName = (type.ref && csnUtils.artifactRef(type)) || type;
303
319
  }
304
320
  else {
305
- throw new CompilerAssertion(`Please debug me: ${typeName} not found`);
321
+ throw new CompilerAssertion(`Debug me: ${typeName} not found`);
306
322
  }
307
323
  }
308
324
  }
@@ -359,7 +375,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
359
375
  * @param {string} parentName name of the parent def holding the element
360
376
  */
361
377
  function getAnonymousTypeNameInMultiSchema(typeName, parentName) {
362
- let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
378
+ const currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
363
379
  const newSchemaName = currPrefix || fallBackSchemaName;
364
380
  // new type name without any prefixes
365
381
  const typePlainName = defNameWithoutServiceOrContextName(typeName, newSchemaName);
@@ -390,7 +406,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
390
406
 
391
407
  // Duplicate elements
392
408
  Object.entries(elements).forEach(([elemName, element]) => {
393
- let cloned = cloneCsnNonDict(element, options);
409
+ const cloned = cloneCsnNonDict(element, options);
394
410
  // if this was an anonymous sub element of a key, mark it as not nullable
395
411
  if(isAnonymous && isKey && !cloned.key) {
396
412
  if(cloned.target) {
@@ -2,9 +2,6 @@
2
2
 
3
3
  'use strict'
4
4
 
5
- // const funkyfuncs = Object.keys(require('../compiler/builtins.js').
6
- // specialFunctions).filter(n => n.length).map(n=>n.toLowerCase());
7
-
8
5
  /**
9
6
  * parseExpr() accepts any JSON object and tries to convert a token stream expression
10
7
  * array into an AST like expression with CDL operator precedence.
@@ -61,7 +58,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
61
58
  return { 'cast': [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
62
59
  }
63
60
  else {
64
- for(let n in xpr) {
61
+ for(const n in xpr) {
65
62
  // xpr could be an array with polluted prototype
66
63
  if (Object.hasOwnProperty.call(xpr, n))
67
64
  xpr[n] = Cast(xpr[n], state)
@@ -217,7 +214,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
217
214
  while(i < e && xpr[i] !== 'and') i++;
218
215
  const a = i < e ? i : -1;
219
216
  if(b >= 0) {
220
- let token = [ 'between' ];
217
+ const token = [ 'between' ];
221
218
  not = (xpr[b-1] === 'not');
222
219
  if(not)
223
220
  token.splice(0,0, 'not');
@@ -315,7 +312,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
315
312
  if (typeof xpr === 'object') {
316
313
  // if(xpr?.func && funkyfuncs.includes(xpr?.func))
317
314
  // return xpr;
318
- for(let n in xpr) {
315
+ for(const n in xpr) {
319
316
  // xpr could be an array with polluted prototype
320
317
  if (!Object.hasOwnProperty.call(xpr, n))
321
318
  continue;
@@ -352,7 +349,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
352
349
  s = p+tl;
353
350
  [tl, p] = findToken(s, e);
354
351
  while(p >= 0) {
355
- let rhs = next(xpr, s, p, state);
352
+ const rhs = next(xpr, s, p, state);
356
353
  naryExpr.push(...op, rhs);
357
354
  if(state.array)
358
355
  lhs = [ lhs, ...op, rhs ];
@@ -8,10 +8,12 @@ const { setProp } = require('../base/model');
8
8
 
9
9
  const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
10
10
  const { getUtils } = require('../model/csnUtils');
11
- const { typeParameters, isBuiltinType } = require('../compiler/builtins');
11
+ const { typeParameters } = require('../compiler/builtins');
12
+ const { isBuiltinType } = require('../base/builtins');
12
13
  const { ModelError, CompilerAssertion} = require('../base/error');
13
14
  const { forEach } = require('../utils/objectUtils');
14
15
  const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
16
+ const { addTenantFieldToArt } = require('./addTenantFields');
15
17
 
16
18
  const RestrictedOperators = ['<', '>', '>=', '<='];
17
19
  const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
@@ -160,14 +162,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
160
162
  // Note that this must happen after struct flattening(flattenStructuredElement) - both fot elements and foreign keys.
161
163
  // Return the newly generated foreign key element.
162
164
  function createForeignKeyElement(assoc, assocName, foreignKey, artifact, artifactName, path) {
163
- let result = {};
165
+ const result = {};
164
166
 
165
167
  // FIXME: Duplicate code (postfix is added herein, can this be optimized?)
166
168
  // Assemble foreign key element name from assoc name, '_' and foreign key name/alias
167
- let foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
169
+ const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
168
170
 
169
171
 
170
- let fkArtifact = inspectRef(path).art;
172
+ const fkArtifact = inspectRef(path).art;
171
173
  newForeignKey(fkArtifact, foreignKeyElementName);
172
174
 
173
175
  function processAssociationOrComposition(fkArtifact,foreignKeyElementName) {
@@ -186,7 +188,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
186
188
  return;
187
189
  }
188
190
 
189
- let foreignKeyElement = createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName);
191
+ const foreignKeyElement = createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName);
190
192
 
191
193
  // FIXME: must this code go into createRealFK?
192
194
  // Not present in getForeignKeyArtifact
@@ -227,7 +229,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
227
229
  // length: 42 },
228
230
  // }
229
231
  function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[]) {
230
- let elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
232
+ const elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
231
233
  // in case the element is of user defined type => take the definition of the type
232
234
  // for type of 'x' -> elem.type is an object, not a string -> use directly
233
235
  let elemType;
@@ -239,7 +241,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
239
241
  // Collect all child elements (recursively) into 'result'
240
242
  // TODO: Do not report collisions in the generated elements here, but instead
241
243
  // leave that work to the receiver of this result
242
- let result = Object.create(null);
244
+ const result = Object.create(null);
243
245
  const addGeneratedFlattenedElement = (e, eName) => {
244
246
  if (result[eName])
245
247
  error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName })
@@ -249,10 +251,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
249
251
  forEach(struct, (childName, childElem) => {
250
252
  if (isStructured(childElem)) {
251
253
  // Descend recursively into structured children
252
- let grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
253
- for (let grandChildName in grandChildElems) {
254
- let flatElemName = elemName + pathDelimiter + grandChildName;
255
- let flatElem = grandChildElems[grandChildName];
254
+ const grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
255
+ for (const grandChildName in grandChildElems) {
256
+ const flatElemName = elemName + pathDelimiter + grandChildName;
257
+ const flatElem = grandChildElems[grandChildName];
256
258
  addGeneratedFlattenedElement(flatElem, flatElemName);
257
259
  // TODO: check with values. In CSN such elements have only "@Core.Computed": true
258
260
  // If the original element had a value, construct one for the flattened element
@@ -263,8 +265,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
263
265
  }
264
266
  } else {
265
267
  // Primitive child - clone it and restore its cross references
266
- let flatElemName = elemName + pathDelimiter + childName;
267
- let flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
268
+ const flatElemName = elemName + pathDelimiter + childName;
269
+ const flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
268
270
  // Don't take over notNull from leaf elements
269
271
  delete flatElem.notNull;
270
272
  setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
@@ -300,7 +302,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
300
302
  // 'localized' is needed for OData
301
303
  if(options.toOdata)
302
304
  props.push('localized');
303
- for (let p of props) {
305
+ for (const p of props) {
304
306
  if (elem[p]) {
305
307
  flatElem[p] = elem[p];
306
308
  }
@@ -321,10 +323,12 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
321
323
  * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
322
324
  * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
323
325
  * @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
324
- * @param {bool} [refParentIsItems] relative ref has an items root (suspend flattening by caller)
326
+ * @param {bool} [suspend] suspend flattening by caller until association path step
327
+ * @param {int} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
328
+ * @param {bool} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
325
329
  * @returns {string[]}
326
330
  */
327
- function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), refParentIsItems=false) {
331
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false) {
328
332
  // Refs of length 1 cannot contain steps - no need to check
329
333
  if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
330
334
  return ref;
@@ -350,10 +354,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
350
354
  (resolvedLinkTypes.get(links[i])||{})[propName]);
351
355
 
352
356
  let flattenStep = false;
353
- let nextIsItems = !!art('items') || (refParentIsItems && i === 0);
357
+ suspend = !!art('items') || (suspend && i <= suspendPos);
354
358
  for(; i < links.length; i++) {
355
359
 
356
- if (flattenStep && !nextIsItems) {
360
+ if (flattenStep && !suspend) {
357
361
  result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
358
362
  // if we had a filter or args, we had an assoc so this step is done
359
363
  // we then keep along the filter/args by updating the id of the current ref
@@ -362,14 +366,15 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
362
366
  result[result.length-1] = ref[i];
363
367
  }
364
368
  // suspend flattening if the next path step has some 'items'
365
- nextIsItems = !!art('items');
369
+ suspend = !!art('items');
366
370
  }
367
371
  else {
368
372
  result.push(ref[i]);
373
+ suspend ||= !!art('items');
369
374
  }
370
375
  // revoke items suspension for next assoc step
371
- if(nextIsItems && art('target'))
372
- nextIsItems = false;
376
+ if(suspend && art('target') || (revokeAtSuspendPos && i === suspendPos))
377
+ suspend = false;
373
378
 
374
379
  flattenStep = !links[i].art?.kind &&
375
380
  !links[i].art?.SELECT &&
@@ -469,11 +474,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
469
474
  // Add the created projection to the model and complain if artifact already exists.
470
475
  // Used by Draft generation
471
476
  function createExposingProjection(art, artName, projectionId, service) {
472
- let projectionAbsoluteName = `${service}.${projectionId}`;
477
+ const projectionAbsoluteName = `${service}.${projectionId}`;
473
478
  // Create elements matching the artifact's elements
474
- let elements = Object.create(null);
479
+ const elements = Object.create(null);
475
480
  art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
476
- let elem = Object.assign({}, artElem);
481
+ const elem = Object.assign({}, artElem);
477
482
  // Transfer xrefs, that are redirected to the projection
478
483
  // TODO: shall we remove the transferred elements from the original?
479
484
  // if (artElem._xref) {
@@ -483,7 +488,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
483
488
  elements[elemName] = elem;
484
489
  });
485
490
 
486
- let query = {
491
+ const query = {
487
492
  'SELECT': {
488
493
  'from': {
489
494
  'ref': [
@@ -493,13 +498,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
493
498
  }
494
499
  };
495
500
  // Assemble the projection itself and add it into the model
496
- let projection = {
501
+ const projection = {
497
502
  'kind': 'entity',
498
503
  projection: query.SELECT, // it is important that projection and query refer to the same object!
499
504
  elements
500
505
  };
501
506
  // copy annotations from art to projection
502
- for (let a of Object.keys(art).filter(x => x.startsWith('@'))) {
507
+ for (const a of Object.keys(art).filter(x => x.startsWith('@'))) {
503
508
  projection[a] = art[a];
504
509
  }
505
510
  model.definitions[projectionAbsoluteName] = projection;
@@ -522,6 +527,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
522
527
  if (!draftAdminDataEntity) {
523
528
  draftAdminDataEntity = createAndAddDraftAdminDataEntity();
524
529
  model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
530
+ if(options.tenantDiscriminator && options.transformation !== 'odata')
531
+ addTenantFieldToArt(model.definitions['DRAFT.DraftAdministrativeData'], options);
525
532
  }
526
533
 
527
534
  // Create a projection within this service
@@ -602,7 +609,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
602
609
  if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
603
610
  throw new ModelError('Expecting valid type name: ' + typeName);
604
611
  }
605
- let result = {
612
+ const result = {
606
613
  [elemName]: {
607
614
  type: typeName
608
615
  }
@@ -630,16 +637,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
630
637
  // keys: [{ ref: ['id'] }]
631
638
  // } }
632
639
  function createAssociationElement(elemName, target, isManaged = false) {
633
- let elem = createScalarElement(elemName, 'cds.Association', false, undefined);
634
- let assoc = elem[elemName];
640
+ const elem = createScalarElement(elemName, 'cds.Association', false, undefined);
641
+ const assoc = elem[elemName];
635
642
  assoc.target = target;
636
643
 
637
644
  if (isManaged) {
638
645
  assoc.keys = [];
639
- let targetArt = getCsnDef(target);
646
+ const targetArt = getCsnDef(target);
640
647
  targetArt.elements && Object.entries(targetArt.elements).forEach(([keyElemName, keyElem]) => {
641
648
  if (keyElem.key) {
642
- let foreignKey = createForeignKey(keyElemName, keyElem);
649
+ const foreignKey = createForeignKey(keyElemName, keyElem);
643
650
  addForeignKey(foreignKey, assoc);
644
651
  }
645
652
  });
@@ -696,7 +703,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
696
703
  if (!artifact.elements) {
697
704
  throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
698
705
  }
699
- let elemName = Object.keys(elem)[0];
706
+ const elemName = Object.keys(elem)[0];
700
707
  // Element must not exist
701
708
  if (artifact.elements[elemName]) {
702
709
  let path = null;
@@ -733,7 +740,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
733
740
  error(null, path, { name: elementName }, 'Generated element $(NAME) conflicts with existing element');
734
741
  }
735
742
 
736
- let result = Object.create(null);
743
+ const result = Object.create(null);
737
744
  result[elementName] = {};
738
745
  elem && Object.entries(elem).forEach(([prop, value]) => {
739
746
  result[elementName][prop] = value;
@@ -746,13 +753,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
746
753
  // of type name 'paramTypeName'
747
754
  function createAction(actionName, returnTypeName = undefined, paramName = undefined, paramTypeName = undefined) {
748
755
  // Assemble the action
749
- let result = {
756
+ const result = {
750
757
  [actionName]: {
751
758
  kind: 'action'
752
759
  }
753
760
  };
754
761
 
755
- let action = result[actionName];
762
+ const action = result[actionName];
756
763
 
757
764
  if (returnTypeName) {
758
765
  if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
@@ -787,7 +794,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
787
794
  artifact.actions = Object.create(null);
788
795
  }
789
796
 
790
- let actionName = Object.keys(action)[0];
797
+ const actionName = Object.keys(action)[0];
791
798
  // Element must not exist
792
799
  if (!artifact.actions[actionName]) {
793
800
  // Add the action
@@ -804,7 +811,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
804
811
  * second field if it has @cds.valid.to. Default value is [] for each field.
805
812
  */
806
813
  function extractValidFromToKeyElement(element, path) {
807
- let validFroms = [], validTos = [], validKeys = [];
814
+ const validFroms = [], validTos = [], validKeys = [];
808
815
  if (hasAnnotationValue(element, '@cds.valid.from')) {
809
816
  validFroms.push({ element, path: [...path] });
810
817
  }
@@ -874,7 +881,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
874
881
  */
875
882
  function recurseElements(artifact, path, callback) {
876
883
  callback(artifact, path);
877
- let elements = artifact.elements;
884
+ const elements = artifact.elements;
878
885
  if (elements) {
879
886
  path.push('elements', null);
880
887
  forEach(elements, (name, obj) => {
@@ -888,7 +895,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
888
895
 
889
896
  // Rename annotation 'fromName' in 'node' to 'toName' (both names including '@')
890
897
  function renameAnnotation(node, fromName, toName) {
891
- let annotation = node && node[fromName];
898
+ const annotation = node && node[fromName];
892
899
  // Sanity checks
893
900
  if (!fromName.startsWith('@')) {
894
901
  throw new CompilerAssertion('Annotation name should start with "@": ' + fromName);
@@ -1035,7 +1042,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
1035
1042
  if(art) {
1036
1043
  if(art && !((art.items && art.items.elements) || art.elements)) {
1037
1044
  if(followMgdAssoc && art.target && art.keys) {
1038
- let rc = [];
1045
+ const rc = [];
1039
1046
  for(const k of art.keys) {
1040
1047
  const nps = { ref: k.ref.map(p => fullRef ? { id: p } : p ) };
1041
1048
  setProp(nps, '_art', k._art);
@@ -1053,7 +1060,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
1053
1060
  }
1054
1061
  const elements = art.items && art.items.elements || art.elements;
1055
1062
  if(elements) {
1056
- let rc = []
1063
+ const rc = []
1057
1064
  Object.entries(elements).forEach(([en, elt]) => {
1058
1065
  const nps = { ref: [ (fullRef ? { id: en, _art: elt } : en )] };
1059
1066
  setProp(nps, '_art', elt);
@@ -1102,7 +1109,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
1102
1109
  Flattening stops on all non-structured types.
1103
1110
  */
1104
1111
  function expand(expr, location) {
1105
- let rc = [];
1112
+ const rc = [];
1106
1113
  for(let i = 0; i < expr.length; i++)
1107
1114
  {
1108
1115
  if(Array.isArray(expr[i]))