@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
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const { isBuiltinType, isMagicVariable, forEachDefinition,
3
+ const { forEachDefinition,
4
4
  copyAnnotations, forEachMemberRecursively,
5
5
  transformExpression, findAnnotationExpression } = require('../../model/csnUtils');
6
+ const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
6
7
  const transformUtils = require('../transformUtils');
7
- const { setProp, isBetaEnabled } = require('../../base/model');
8
+ const { setProp } = require('../../base/model');
8
9
  const { applyTransformationsOnDictionary,
9
10
  applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
10
11
  const { linkForeignKeyAnnotationExtensionsToAssociation,
@@ -13,46 +14,48 @@ const { cloneCsnNonDict } = require('../../model/cloneCsn');
13
14
  const { forEach } = require('../../utils/objectUtils');
14
15
 
15
16
  function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options) {
16
-
17
- const isExprAnno = (obj, pn) => {
18
- return isBetaEnabled(options, 'odataPathsInAnnotationExpressions') && findAnnotationExpression(obj, pn);
19
- }
20
-
21
17
  forEachDefinition(csn, (def, defName) => {
22
- if(def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
18
+ if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
23
19
  ['elements', 'params'].forEach(dictName => {
24
20
  const dict = def[dictName];
25
- if(dict) {
21
+ if (dict) {
26
22
  const csnPath = ['definitions', defName, dictName];
27
23
  const orderedElementList = [];
28
24
 
29
- forEach(dict, (eltName, elt) => {
30
- const location = [ ...csnPath, eltName ];
31
- const resolvedElt = (elt.type && !elt.elements) ? csnUtils.getFinalTypeInfo(elt.type) : elt;
32
- if(resolvedElt.elements) {
33
- const flattenedSubTree = recurseIntoElement(dictName, elt, resolvedElt,
34
- !!elt.notNull, location, [ defName, eltName ]);
25
+ forEach(dict, (childName, child) => {
26
+ const location = [ ...csnPath, childName ];
27
+ const rootPrefix = [ defName ];
28
+ let resolvedElt = child;
29
+ let typeIdx = 0;
30
+ if (child.type && !child.elements) {
31
+ resolvedElt = csnUtils.getFinalTypeInfo(child.type);
32
+ if (resolvedElt.elements)
33
+ typeIdx = rootPrefix.length + 1;
34
+ }
35
+ if (resolvedElt.elements) {
36
+ const flattenedSubTree = recurseIntoElement(dictName, child, resolvedElt,
37
+ !!child.notNull, location, [ ...rootPrefix, childName ], typeIdx);
35
38
 
36
39
  flattenedSubTree.forEach(([flatEltName, flatElt]) => {
37
40
  if (dict[flatEltName] || orderedElementList.some(elt => elt[0] === flatEltName))
38
41
  error('name-duplicate-element', location,
39
42
  { '#': 'flatten-element-exist', name: flatEltName });
40
- propagateToFlatElem(elt, flatElt);
43
+ propagateToFlatElem(child, flatElt);
41
44
  rewriteOnCondition(flatElt, flattenedSubTree);
42
45
  orderedElementList.push([flatEltName, flatElt]);
43
46
  });
44
47
  }
45
48
  else {
46
- const flatElt = cloneElt(dictName, elt, location, [ defName, eltName ]);
47
- orderedElementList.push([eltName, flatElt]);
49
+ const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
50
+ orderedElementList.push([childName, flatElt]);
48
51
  }
49
52
  });
50
53
 
51
54
  const flatDict = orderedElementList.reduce((elements, [ flatEltName, flatElt ]) => {
52
- if(flatElt.items) {
55
+ if (flatElt.items) {
53
56
  // rewrite annotation paths inside items.elements
54
57
  forEachMemberRecursively(flatElt.items, (elt, _eltName, _prop, path) => {
55
- const exprAnnos = Object.keys(elt).filter(pn => isExprAnno(elt, pn));
58
+ const exprAnnos = Object.keys(elt).filter(pn => findAnnotationExpression(elt, pn));
56
59
  flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
57
60
  }, [ flatEltName ], true, { pathWithoutProp: true } );
58
61
  }
@@ -60,21 +63,77 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
60
63
  elements[flatEltName] = flatElt;
61
64
  return elements;
62
65
  }, Object.create(null));
63
- setProp(def, `$${dictName}`, flatDict);
66
+ setProp(def, `$flat${dictName}`, flatDict);
64
67
  }
65
68
  });
69
+ // entity annotations
66
70
  const flatAnnos = Object.create(null);
67
- const annoNames = copyAnnotations(def, flatAnnos).filter(an => isExprAnno(def, an));
71
+ const annoNames = copyAnnotations(def, flatAnnos).filter(an => findAnnotationExpression(def, an));
68
72
  flattenAndPrefixExprPaths(flatAnnos, annoNames, [ 'definitions', defName ], [ defName ], 0);
69
73
  setProp(def, '$flatAnnotations', flatAnnos);
74
+ // explicit binding parameter of bound action
75
+ if (def.actions) {
76
+ const special$self = !csn?.definitions?.$self && '$self';
77
+ Object.entries(def.actions).forEach(([actionName, action]) => {
78
+ if (action.params) {
79
+ const params = Object.entries(action.params);
80
+ const firstParam = params[0][1];
81
+ const type = firstParam?.items?.type || firstParam?.type;
82
+ if (type === special$self) {
83
+
84
+ const bindingParamName = params[0][0];
85
+ const markBindingParam = {
86
+ ref: (parent, prop, xpr) => {
87
+ if ((xpr[0].id || xpr[0]) === bindingParamName)
88
+ setProp(parent, '$bparam', true)
89
+ },
90
+ };
91
+ const refCheck = {
92
+ ref: (elemref, prop, xpr, path) => {
93
+ const { art, scope } = inspectRef(path);
94
+ if (scope !== '$magic' && art) {
95
+ const ft = csnUtils.getFinalTypeInfo(art.type);
96
+ if (!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
97
+ error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
98
+ }
99
+ }
100
+ }
101
+ };
102
+
103
+ const flatAnnos = Object.create(null);
104
+ const annoNames = copyAnnotations(action, flatAnnos).filter(an => findAnnotationExpression(action, an));
105
+ annoNames.forEach((an) => {
106
+ refCheck.anno = an;
107
+ transformExpression(flatAnnos, an,
108
+ [ markBindingParam, refCheck, refFlattener ],
109
+ [ 'definitions', defName, 'actions', actionName ]);
110
+ adaptRefs.forEach(fn => fn(true, 1, (parent) => parent.$bparam));
111
+ adaptRefs.length = 0;
112
+ });
113
+ setProp(action, '$flatAnnotations', flatAnnos);
114
+
115
+ forEachMemberRecursively(action, (member, memberName, prop, path, _parent) => {
116
+ const exprAnnos = Object.keys(member).filter(pn => findAnnotationExpression(member, pn));
117
+ exprAnnos.forEach((pn) => {
118
+ refCheck.anno = pn;
119
+ transformExpression(member, pn, [ markBindingParam, refCheck, refFlattener ], path);
120
+ adaptRefs.forEach(fn => fn(true, 1, (parent) => parent.$bparam));
121
+ adaptRefs.length = 0;
122
+ });
123
+ },
124
+ [ 'definitions', defName, 'actions', actionName ]);
125
+ }
126
+ }
127
+ });
128
+ }
70
129
  }
71
130
  });
72
131
 
73
132
  function recurseIntoElement(scope, elt, resolvedElt, rootPathIsNotNull, location, rootPrefix = [], typeIdx = 0) {
74
133
  const eltName = rootPrefix[rootPrefix.length-1];
75
- if(!resolvedElt.elements) {
134
+ if (!resolvedElt.elements) {
76
135
  const flatElt = cloneElt(scope, elt, location, rootPrefix, typeIdx);
77
- if(rootPathIsNotNull)
136
+ if (rootPathIsNotNull)
78
137
  flatElt.notNull = true;
79
138
  else
80
139
  delete flatElt.notNull;
@@ -84,14 +143,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
84
143
  let flattenedSubTree = [];
85
144
  forEach(resolvedElt.elements, (childName, child) => {
86
145
  resolvedElt = child;
87
- if(child.type && !child.elements) {
146
+ if (child.type && !child.elements) {
88
147
  resolvedElt = csnUtils.getFinalTypeInfo(child.type);
89
- if(resolvedElt.elements)
148
+ if (resolvedElt.elements)
90
149
  typeIdx = rootPrefix.length + 1;
91
150
  }
92
151
  flattenedSubTree = flattenedSubTree.concat(recurseIntoElement(scope, child, resolvedElt,
93
152
  !!(child.notNull && rootPathIsNotNull),
94
- [... location, 'elements', childName],
153
+ [... location, 'elements', childName],
95
154
  [ ...rootPrefix, childName ], typeIdx));
96
155
  });
97
156
  // 1) rename, 2) filter duplicates and finalize new elements
@@ -99,7 +158,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
99
158
  return flattenedSubTree.map(([flatEltName, flatElt]) => {
100
159
  return [ `${eltName}_${flatEltName}`, flatElt ];
101
160
  }).filter(([name, flatElt]) =>{
102
- if(duplicateDict[name]) {
161
+ if (duplicateDict[name]) {
103
162
  error('name-duplicate-element', location,
104
163
  { '#': 'flatten-element-gen', name });
105
164
  return false;
@@ -135,7 +194,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
135
194
  };
136
195
  // TODO: copy only those expression annotations that have no path into unreachable subtree, starting
137
196
  // of typePathRoot (which could be some completely different path)
138
- const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => isExprAnno(flatElt, pn));
197
+ const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => findAnnotationExpression(flatElt, pn));
139
198
  flattenAndPrefixExprPaths(flatElt, exprAnnoNames, elt.$path, rootPrefix, typeIdx);
140
199
  // Copy selected type properties
141
200
  ['key', 'virtual', 'masked', 'viaAll', 'localized'].forEach(p => {
@@ -157,9 +216,9 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
157
216
  retypeCloneWithFinalBaseType(flatElt, location);
158
217
 
159
218
  const [ nonAnnoProps, exprAnnoProps ] = Object.keys(flatElt).reduce((acc, pn) => {
160
- if(pn[0] !== '@' && pn !== 'value')
219
+ if (pn[0] !== '@' && pn !== 'value')
161
220
  acc[0].push(pn);
162
- if(isExprAnno(flatElt, pn) || pn === 'value')
221
+ if (findAnnotationExpression(flatElt, pn) || pn === 'value')
163
222
  acc[1].push(pn);
164
223
  return acc;
165
224
  }, [[],[]]);
@@ -173,18 +232,18 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
173
232
  return flatElt;
174
233
 
175
234
  function retypeCloneWithFinalBaseType(elt, location) {
176
- if(elt.type &&
235
+ if (elt.type &&
177
236
  !isBuiltinType(elt.type) &&
178
237
  !isODataV4BuiltinFromService(elt.type, location)
179
238
  && !isItemsType(elt.type)) {
180
- let resolvedType = csnUtils.getFinalTypeInfo(elt);
181
-
239
+ const resolvedType = csnUtils.getFinalTypeInfo(elt);
240
+
182
241
  delete resolvedType.kind;
183
- if(resolvedType.items)
242
+ if (resolvedType.items)
184
243
  delete resolvedType.type;
185
-
186
- if(elt.items) {
187
- if(resolvedType.items) {
244
+
245
+ if (elt.items) {
246
+ if (resolvedType.items) {
188
247
  elt.items = resolvedType;
189
248
  delete elt.items.type;
190
249
  }
@@ -192,7 +251,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
192
251
  elt.items.type = resolvedType;
193
252
  }
194
253
  else {
195
- if(resolvedType.items) {
254
+ if (resolvedType.items) {
196
255
  elt.items = resolvedType.items;
197
256
  delete elt.type;
198
257
  }
@@ -200,19 +259,19 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
200
259
  elt.type = resolvedType;
201
260
  }
202
261
  }
203
-
262
+
204
263
  function isItemsType(typeName) {
205
- let typeDef = csn.definitions[typeName];
264
+ const typeDef = csn.definitions[typeName];
206
265
  return !!typeDef?.items;
207
266
  }
208
-
267
+
209
268
  function isODataV4BuiltinFromService( typeName, path ) {
210
269
  if (options.odataVersion === 'v2' || typeof typeName !== 'string')
211
270
  return false;
212
-
271
+
213
272
  const typeServiceName = csnUtils.getServiceName(typeName);
214
273
  let finalBaseType = csnUtils.getFinalTypeInfo(typeName).type;
215
- if(!isBuiltinType(finalBaseType)) {
274
+ if (!isBuiltinType(finalBaseType)) {
216
275
  const typeDef = csn.definitions[finalBaseType];
217
276
  finalBaseType = typeDef?.items?.type || typeDef?.type;
218
277
  }
@@ -224,61 +283,83 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
224
283
  }
225
284
 
226
285
  // The path rewriting must be done with the current CSN path of that exact annotation location
227
- // in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
286
+ // in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
228
287
  // to locate the relative location of the path expression in this element).
229
288
  // At this time both annotations and values must be rewritten.
230
289
  //
231
290
  // Later, the query can/must be rewritten as long as a flat OData CSN is published
232
291
  // but this then operates on the entity/view which has all struct infos available
233
292
  function flattenAndPrefixExprPaths(carrier, propNames, csnPath, rootPrefix, typeIdx, refParentIsItems = false) {
234
- refFlattener.$refParentIsItems = refParentIsItems;
235
293
  const refCheck = {
236
- ref: (parent, prop, xpr, path) => {
237
- const { art } = inspectRef(path);
238
- const ft = csnUtils.getFinalTypeInfo(art?.type);
239
- if(!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
240
- error('odata-anno-xpr-ref', path, { elemref: parent, name: refCheck.eltLocationStr, anno: refCheck.anno, '#': 'flatten_builtin' });
294
+ ref: (elemref, prop, xpr, path) => {
295
+ const { art, scope } = inspectRef(path);
296
+ if (scope !== '$magic' && art) {
297
+ const ft = csnUtils.getFinalTypeInfo(art.type);
298
+ if (!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
299
+ error('odata-anno-xpr-ref', path,
300
+ {
301
+ anno: refCheck.anno,
302
+ elemref,
303
+ name: refCheck.eltLocationStr,
304
+ '#': 'flatten_builtin'
305
+ });
306
+ }
241
307
  }
242
308
  }
243
309
  }
244
310
 
245
311
  refCheck.eltLocationStr = (function() {
246
312
  const [head, ...tail ] = rootPrefix;
247
- return `${head}:${tail.join('.')}`;
313
+ if(tail.length)
314
+ return `${head}:${tail.join('.')}`;
315
+ else
316
+ return `${head}`;
248
317
  })();
249
318
 
250
319
  const absolutifier = {
251
320
  ref : (parent, prop, xpr) => {
252
321
  const head = xpr[0].id || xpr[0];
253
- if(typeIdx > 0 && head === '$self' && !isMagicVariable(head)) {
322
+ if (typeIdx < rootPrefix.length && head === '$self' && !isMagicVariable(head)) {
254
323
  const [xprHead, ...xprTail] = xpr.slice(1, xpr.length);
255
- parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
256
- if(carrier.$scope === 'params')
257
- parent.param = true;
258
- else
259
- parent[prop].unshift('$self');
324
+ if(xprHead) {
325
+ if (xprHead.id) {
326
+ xprHead.id = rootPrefix.slice(1, typeIdx).concat(xprHead.id).join('_');
327
+ parent[prop] = [ xprHead, ...xprTail ];
328
+ }
329
+ else
330
+ parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
331
+ if (carrier.$scope === 'params')
332
+ parent.param = true;
333
+ else
334
+ parent[prop].unshift('$self');
335
+ }
260
336
  }
261
- else if(rootPrefix.length > 2 && head !== '$self' && !parent.param && !isMagicVariable(head)) {
337
+ else if (rootPrefix.length > 2 && head !== '$self' && !parent.param && !isMagicVariable(head)) {
262
338
  const [xprHead, ...xprTail] = xpr;
263
- if(!refParentIsItems)
264
- parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
339
+ if (!refParentIsItems) {
340
+ if (xprHead.id) {
341
+ xprHead.id = rootPrefix.slice(1, -1).concat(xprHead.id).join('_');
342
+ parent[prop] = [ xprHead, ...xprTail ];
343
+ }
344
+ else
345
+ parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
346
+ }
265
347
  else
266
348
  parent[prop] = [ ...rootPrefix.slice(0, rootPrefix.length-1), ...xpr];
267
- if(carrier.$scope === 'params')
349
+ if (carrier.$scope === 'params')
268
350
  parent.param = true;
269
351
  else
270
352
  parent[prop].unshift('$self');
271
353
  }
272
354
  }
273
355
  }
274
-
356
+
275
357
  propNames.forEach(pn => {
276
358
  refCheck.anno = pn;
277
359
  transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
278
360
  });
279
- adaptRefs.forEach(fn => fn());
361
+ adaptRefs.forEach(fn => fn(refParentIsItems));
280
362
  adaptRefs.length = 0;
281
- refFlattener.$refParentIsItems = false;
282
363
  propNames.forEach(pn => {
283
364
  transformExpression(carrier, pn, absolutifier, csnPath)
284
365
  })
@@ -303,35 +384,66 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
303
384
  }
304
385
  }
305
386
 
306
- /**
307
- * Rewrite all structured references in the model such that it matches their flat counterparts
308
- * @param {CSN.Model} csn
309
- * @param {CSN.Options} options
310
- * @param {WeakMap} resolved Cache for resolved refs
311
- * @param {string} pathDelimiter
312
- * @param {object} iterateOptions
313
- */
314
- function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptions = {} ) {
387
+ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType,
388
+ csnUtils, error, options, iterateOptions = {} ) {
315
389
 
316
390
  // All anno path flattening is already done, don't do it on locations where we don't want it!
391
+ const typeNames = [];
317
392
  forEachDefinition(csn, (def, defName) => {
318
- if(def.kind === 'entity') {
393
+ if (def.kind === 'entity') {
319
394
  ['query', 'projection'].forEach(dictName => {
320
- applyTransformationsOnNonDictionary(csn.definitions[defName], dictName, refFlattener, iterateOptions, [ 'definitions', defName ]);
395
+ applyTransformationsOnNonDictionary(csn.definitions[defName],
396
+ dictName, refFlattener, iterateOptions,
397
+ [ 'definitions', defName ]);
321
398
  })
322
- if(csn.definitions[defName].actions)
323
- applyTransformationsOnDictionary(csn.definitions[defName].actions, refFlattener, iterateOptions, [ 'definitions', defName, 'actions' ]);
399
+ if (csn.definitions[defName].actions)
400
+ applyTransformationsOnDictionary(csn.definitions[defName].actions,
401
+ refFlattener, iterateOptions,
402
+ [ 'definitions', defName, 'actions' ]);
324
403
  }
325
- /*
326
- TODO: In real hybrid flat/struct OData transformation, ref rewriting in types must not be done
327
- together with non-rewriting ON condition refs during foreign key creation
328
- */
329
- if(['type'].includes(def.kind)) {
330
- applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, iterateOptions, [ 'definitions' ]);
404
+
405
+ if (['type'].includes(def.kind)) {
406
+ typeNames.push(defName);
407
+ applyTransformationsOnNonDictionary(csn.definitions,
408
+ defName, refFlattener, iterateOptions, [ 'definitions' ]);
331
409
  }
332
410
  });
333
411
  adaptRefs.forEach(fn => fn());
334
412
  adaptRefs.length = 0;
413
+
414
+ const refCheck = {
415
+ ref: (elemref, prop, xpr, path) => {
416
+ const { links, art } = (elemref._links && elemref._art
417
+ ? { links: elemref._links, art: elemref._art }
418
+ : inspectRef(path) );
419
+
420
+ let i = links.length-2;
421
+ const getProp = (propName) =>
422
+ (links[i].art?.[propName] ||
423
+ effectiveType(links[i].art)[propName]);
424
+
425
+ const ft = csnUtils.getFinalTypeInfo(art?.type);
426
+ let target = undefined;
427
+ for(; i >= 0 && !getProp('items') && !target; i--) {
428
+ target = getProp('target');
429
+ }
430
+ if (target && csn.definitions[target].$flatelements
431
+ && !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
432
+ error('odata-anno-xpr-ref', path,
433
+ { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
434
+ }
435
+ }
436
+ }
437
+ typeNames.forEach(tn => {
438
+ forEachMemberRecursively(csn.definitions[tn], (member, memberName, prop, csnPath) => {
439
+ Object.keys(member).filter(pn => pn[0] === '@').forEach(pn => {
440
+ refCheck.anno = pn;
441
+ transformExpression(member, pn, [ refCheck, refFlattener ], csnPath);
442
+ });
443
+ }, [ 'definitions', tn ]);
444
+ })
445
+ adaptRefs.forEach(fn => fn(true, 1));
446
+ adaptRefs.length = 0;
335
447
  }
336
448
 
337
449
  function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, pathDelimiter) {
@@ -355,32 +467,36 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
355
467
  }
356
468
  const adaptRefs = [];
357
469
  const transformer = {
358
- $refParentIsItems: false,
359
470
  ref: (parent, prop, ref, path) => {
360
471
  const { links, art, scope } = inspectRef(path);
361
472
  const resolvedLinkTypes = resolveLinkTypes(links);
362
473
  setProp(parent, '$path', [ ...path ]);
363
474
  const lastRef = ref[ref.length - 1];
364
- const fn = () => {
365
- const scopedPath = [ ...parent.$path ];
366
- // TODO: If foreign key annotations should be assigned via
367
- // full path into target, uncomment this line and
368
- // comment/remove setProp in expansion.js
369
- // setProp(parent, '$structRef', parent.ref);
370
- parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, transformer.$refParentIsItems);
371
- resolved.set(parent, { links, art, scope });
372
- // Explicitly set implicit alias for things that are now flattened - but only in columns
373
- // TODO: Can this be done elegantly during expand phase already?
374
- if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
375
- if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
376
- delete parent.as;
377
- delete parent.$implicitAlias;
378
- }
379
- // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
380
- else if (parent.ref[parent.ref.length - 1] !== lastRef &&
381
- (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
382
- !parent.as) {
383
- parent.as = lastRef;
475
+ const fn = (suspend = false, suspendPos = 0,
476
+ refFilter = (_parent) => true) => {
477
+ if (refFilter(parent)) {
478
+ const scopedPath = [ ...parent.$path ];
479
+ // TODO: If foreign key annotations should be assigned via
480
+ // full path into target, uncomment this line and
481
+ // comment/remove setProp in expansion.js
482
+ // setProp(parent, '$structRef', parent.ref);
483
+ parent.ref = flattenStructStepsInRef(ref,
484
+ scopedPath, links, scope, resolvedLinkTypes,
485
+ suspend, suspendPos, parent.$bparam);
486
+ resolved.set(parent, { links, art, scope });
487
+ // Explicitly set implicit alias for things that are now flattened - but only in columns
488
+ // TODO: Can this be done elegantly during expand phase already?
489
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
490
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
491
+ delete parent.as;
492
+ delete parent.$implicitAlias;
493
+ }
494
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
495
+ else if (parent.ref[parent.ref.length - 1] !== lastRef &&
496
+ (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
497
+ !parent.as) {
498
+ parent.as = lastRef;
499
+ }
384
500
  }
385
501
 
386
502
  /**
@@ -390,7 +506,10 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
390
506
  * @returns {boolean}
391
507
  */
392
508
  function insideColumns( path ) {
393
- return path.length >= 3 && (path[path.length - 3] === 'SELECT' || path[path.length - 3] === 'projection') && path[path.length - 2] === 'columns';
509
+ return path.length >= 3
510
+ && (path[path.length - 3] === 'SELECT'
511
+ || path[path.length - 3] === 'projection')
512
+ && path[path.length - 2] === 'columns';
394
513
  }
395
514
  /**
396
515
  * Return true if the path points inside keys
@@ -399,7 +518,9 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
399
518
  * @returns {boolean}
400
519
  */
401
520
  function insideKeys( path ) {
402
- return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
521
+ return path.length >= 3
522
+ && path[path.length - 2] === 'keys'
523
+ && typeof path[path.length - 1] === 'number';
403
524
  }
404
525
  };
405
526
  // adapt queries later
@@ -6,9 +6,9 @@ const {
6
6
  forEachDefinition,
7
7
  forEachGeneric,
8
8
  forEachMemberRecursively,
9
- isBuiltinType,
10
- findAnnotationExpression,
9
+ findAnnotationExpression,
11
10
  } = require('../../model/csnUtils');
11
+ const { isBuiltinType } = require('../../base/builtins');
12
12
  const { isArtifactInSomeService, isArtifactInService } = require('./utils');
13
13
  const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
14
14
 
@@ -88,7 +88,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
88
88
  // type Foo: array of { qux: Integer };
89
89
  function expandFirstLevelOfArrayed(def) {
90
90
  if (def.items.type && !isBuiltinType(def.items.type)) {
91
- let finalBaseType = csnUtils.getFinalTypeInfo(def.items.type);
91
+ const finalBaseType = csnUtils.getFinalTypeInfo(def.items.type);
92
92
  if (finalBaseType?.elements) {
93
93
  def.items.elements = cloneCsnDict(finalBaseType.elements, options);
94
94
  delete def.items.type;
@@ -98,12 +98,6 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
98
98
 
99
99
  function expandToFinalBaseType(node, defName) {
100
100
  if (!node) return;
101
- // TODO: Clarify how should events be handled?
102
- // They are not treated by the transformUtils::toFinalBaseType function
103
- // in the same manner as named types, because the elements of structured events are not
104
- // propagated as it is with types.
105
- // It is ok to skip the expansion to the final base type for now as events are not rendered in
106
- // EDMX at the moment and the reference in the OData CSN is fulfilled.
107
101
  if (node.kind === 'event') return;
108
102
 
109
103
  if(node.type && !isBuiltinType(node.type)) {
@@ -199,7 +193,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
199
193
  // do the clone only if really needed
200
194
  if((finalBaseType.items && !node.items) ||
201
195
  (finalBaseType.elements && !node.elements)) {
202
- // use the 'real' type, don't clone a clone
196
+ // clone the definition not another clone
203
197
  let _type = node._type;
204
198
  while(_type._type && !_type.items && !_type.elements)
205
199
  _type = _type._type;
@@ -227,15 +221,10 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
227
221
  const typeRefStr = f(node.type.ref);
228
222
  const typeRefRootPath = $path2path(typeRefCsnPath);
229
223
  forEachMemberRecursively(clone, (elt, eltName, prop, location) => {
230
- const [ xprANames /*nxprANames */ ] = Object.keys(elt).reduce((acc, pn) => {
231
- if (pn[0] === '@')
232
- acc[findAnnotationExpression(elt, pn) ? 0 : 1].push(pn);
233
- return acc;
234
- }, [ [], [] ]);
235
224
  const usingPositionStr = f($path2path(location));
236
225
  const eltRootPath = $path2path(elt.$path);
237
- xprANames.forEach(xprAName => {
238
- //let isSubTreeSpan = true;
226
+
227
+ Object.keys(elt).filter(pn => pn[0] === '@' && findAnnotationExpression(elt, pn)).forEach(xprAName => {
239
228
  transformExpression(elt, xprAName, {
240
229
  ref: (parent, prop, xpr, csnPath) => {
241
230
  let prefixMatch = true;
@@ -245,23 +234,18 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
245
234
  prefixMatch = (xpr[i].id || xpr[i]) === typeRefRootPath[i];
246
235
  }
247
236
  if(prefixMatch && xpr.length > typeRefRootPath.length) {
248
- parent[prop] = [ ...xpr.slice(eltRootPath.length-1)];
237
+ if(xpr.length >= eltRootPath.length)
238
+ parent[prop] = [ ...xpr.slice(eltRootPath.length-1)];
239
+ else
240
+ parent[prop] = [ '$self', ...xpr.slice(typeRefRootPath.length)];
249
241
  }
250
242
  else {
251
243
  error('odata-anno-xpr-ref', csnPath,
252
244
  { anno: xprAName, elemref: xpr.join('.'), name: usingPositionStr, code: typeRefStr });
253
- //isSubTreeSpan = false;
254
245
  }
255
246
  }
256
247
  },
257
248
  }, elt.$path);
258
- // if(!isSubTreeSpan) {
259
- // // remove annotation and sub annotations from cloned element
260
- // delete elt[xprAName];
261
- // nxprANames.filter(an => an.startsWith(`${xprAName}.`)).forEach((nxprAName) => {
262
- // delete elt[nxprAName];
263
- // });
264
- // }
265
249
  });
266
250
  setProp(elt, '$path', location);
267
251
  }, node.$path);