@sap/cds-compiler 4.9.4 → 5.0.6

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 (87) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +15 -11
  4. package/bin/cdshi.js +1 -0
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +6 -18
  7. package/lib/api/options.js +3 -11
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +29 -29
  12. package/lib/base/messages.js +22 -26
  13. package/lib/base/model.js +0 -2
  14. package/lib/base/node-helpers.js +0 -1
  15. package/lib/checks/enricher.js +1 -5
  16. package/lib/checks/structuredAnnoExpressions.js +30 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +4 -1
  19. package/lib/compiler/base.js +1 -1
  20. package/lib/compiler/builtins.js +18 -2
  21. package/lib/compiler/checks.js +2 -5
  22. package/lib/compiler/define.js +7 -7
  23. package/lib/compiler/extend.js +68 -33
  24. package/lib/compiler/generate.js +1 -1
  25. package/lib/compiler/index.js +23 -6
  26. package/lib/compiler/lsp-api.js +501 -2
  27. package/lib/compiler/populate.js +2 -2
  28. package/lib/compiler/propagator.js +1 -4
  29. package/lib/compiler/resolve.js +2 -15
  30. package/lib/compiler/shared.js +112 -31
  31. package/lib/compiler/tweak-assocs.js +2 -16
  32. package/lib/compiler/utils.js +2 -1
  33. package/lib/compiler/xsn-model.js +4 -0
  34. package/lib/edm/annotations/genericTranslation.js +95 -42
  35. package/lib/edm/csn2edm.js +16 -4
  36. package/lib/edm/edm.js +2 -3
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  38. package/lib/edm/edmPreprocessor.js +1 -7
  39. package/lib/gen/Dictionary.json +29 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +2 -1
  42. package/lib/gen/languageParser.js +4995 -4817
  43. package/lib/json/csnVersion.js +1 -1
  44. package/lib/json/from-csn.js +4 -7
  45. package/lib/json/to-csn.js +23 -12
  46. package/lib/language/antlrParser.js +2 -2
  47. package/lib/language/errorStrategy.js +0 -1
  48. package/lib/language/genericAntlrParser.js +35 -12
  49. package/lib/language/multiLineStringParser.js +3 -2
  50. package/lib/language/textUtils.js +1 -0
  51. package/lib/main.d.ts +28 -9
  52. package/lib/main.js +7 -4
  53. package/lib/model/csnRefs.js +20 -4
  54. package/lib/model/csnUtils.js +0 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +1 -1
  57. package/lib/optionProcessor.js +28 -9
  58. package/lib/render/manageConstraints.js +1 -1
  59. package/lib/render/toCdl.js +36 -7
  60. package/lib/render/toSql.js +1 -0
  61. package/lib/render/utils/common.js +12 -9
  62. package/lib/render/utils/stringEscapes.js +1 -0
  63. package/lib/transform/db/applyTransformations.js +13 -8
  64. package/lib/transform/db/associations.js +62 -54
  65. package/lib/transform/db/expansion.js +1 -6
  66. package/lib/transform/db/flattening.js +89 -111
  67. package/lib/transform/db/temporal.js +3 -4
  68. package/lib/transform/db/views.js +0 -1
  69. package/lib/transform/draft/odata.js +51 -3
  70. package/lib/transform/effective/annotations.js +3 -2
  71. package/lib/transform/effective/flattening.js +135 -0
  72. package/lib/transform/effective/main.js +6 -6
  73. package/lib/transform/effective/types.js +13 -9
  74. package/lib/transform/forOdata.js +0 -2
  75. package/lib/transform/forRelationalDB.js +0 -19
  76. package/lib/transform/localized.js +7 -8
  77. package/lib/transform/odata/flattening.js +39 -31
  78. package/lib/transform/odata/typesExposure.js +5 -17
  79. package/lib/transform/transformUtils.js +1 -1
  80. package/lib/transform/translateAssocsToJoins.js +21 -3
  81. package/lib/utils/file.js +13 -7
  82. package/lib/utils/moduleResolve.js +59 -8
  83. package/lib/utils/term.js +3 -2
  84. package/package.json +7 -3
  85. package/share/messages/message-explanations.json +2 -0
  86. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  87. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -12,10 +12,11 @@ const {
12
12
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
13
13
  const transformUtils = require('../transformUtils');
14
14
  const { csnRefs } = require('../../model/csnRefs');
15
- const { setProp, isBetaEnabled } = require('../../base/model');
15
+ const { setProp } = require('../../base/model');
16
16
  const { forEach } = require('../../utils/objectUtils');
17
17
  const { transformExpression } = require('./applyTransformations');
18
18
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
19
+ const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
19
20
 
20
21
  /**
21
22
  * Strip off leading $self from refs where applicable.
@@ -206,9 +207,24 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
206
207
  * @param {object} iterateOptions
207
208
  */
208
209
  function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
210
+ const adaptRefs = [];
211
+
212
+ applyTransformations(csn, getStructStepsFlattener(csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs), [], iterateOptions);
213
+
214
+ adaptRefs.forEach(fn => fn());
215
+ }
216
+ /**
217
+ * @param {CSN.Model} csn
218
+ * @param {CSN.Options} options
219
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
220
+ * @param {WeakMap} resolved Cache for resolved refs
221
+ * @param {string} pathDelimiter
222
+ * @param {Function[]} adaptRefs
223
+ * @returns {object} applyTransformations transformer
224
+ */
225
+ function getStructStepsFlattener( csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs ) {
209
226
  const { inspectRef, effectiveType } = csnRefs(csn);
210
227
  const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
211
- const adaptRefs = [];
212
228
 
213
229
  /**
214
230
  * For each step of the links, check if there is a type reference.
@@ -229,41 +245,57 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
229
245
  return resolvedLinkTypes;
230
246
  }
231
247
 
232
- applyTransformations(csn, {
248
+ const transformer = {
233
249
  // @ts-ignore
234
- ref: (parent, prop, ref, path) => {
250
+ ref: (parent, prop, ref, path, _parent, _prop, context) => {
235
251
  const { links, art, scope } = inspectRef(path);
236
252
  const resolvedLinkTypes = resolveLinkTypes(links);
237
253
  setProp(parent, '$path', [ ...path ]);
238
254
  const lastRef = ref[ref.length - 1];
239
- const fn = () => {
240
- const scopedPath = [ ...parent.$path ];
241
- // TODO: If foreign key annotations should be assigned via
242
- // full path into target, uncomment this line and
243
- // comment/remove setProp in expansion.js
244
- // setProp(parent, '$structRef', parent.ref);
245
- [ parent.ref ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
246
- resolved.set(parent, { links, art, scope });
247
- // Explicitly set implicit alias for things that are now flattened - but only in columns
248
- // TODO: Can this be done elegantly during expand phase already?
249
- if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
250
- if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
251
- delete parent.as;
252
- delete parent.$implicitAlias;
253
- }
254
- // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
255
- else if (parent.ref[parent.ref.length - 1] !== lastRef &&
256
- (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
257
- !parent.as) {
258
- parent.as = lastRef;
255
+ const fn = (suspend = false, suspendPos = 0,
256
+ refFilter = _parent => true) => {
257
+ let refChanged = false;
258
+ if (refFilter(parent)) {
259
+ const scopedPath = [ ...parent.$path ];
260
+ // TODO: If foreign key annotations should be assigned via
261
+ // full path into target, uncomment this line and
262
+ // comment/remove setProp in expansion.js
263
+ // setProp(parent, '$structRef', parent.ref);
264
+ [ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam);
265
+ resolved.set(parent, { links, art, scope });
266
+ // Explicitly set implicit alias for things that are now flattened - but only in columns
267
+ // TODO: Can this be done elegantly during expand phase already?
268
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
269
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
270
+ delete parent.as;
271
+ delete parent.$implicitAlias;
272
+ }
273
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
274
+ else if (parent.ref[parent.ref.length - 1] !== lastRef &&
275
+ (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
276
+ !parent.as) {
277
+ parent.as = lastRef;
278
+ }
259
279
  }
280
+
281
+ return refChanged;
260
282
  };
261
- // adapt queries later
262
- adaptRefs.push(fn);
283
+
284
+ if (context?.$annotation) {
285
+ const annotation = context.$annotation.value;
286
+ adaptRefs.push((...args) => {
287
+ const refChanged = fn(...args);
288
+ if (refChanged && annotation['='])
289
+ annotation['='] = true;
290
+ });
291
+ }
292
+ else {
293
+ // adapt queries later
294
+ adaptRefs.push(fn);
295
+ }
263
296
  },
264
- }, [], iterateOptions);
297
+ };
265
298
 
266
- adaptRefs.forEach(fn => fn());
267
299
 
268
300
  /**
269
301
  * Return true if the path points inside columns
@@ -283,6 +315,8 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
283
315
  function insideKeys( path ) {
284
316
  return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
285
317
  }
318
+
319
+ return transformer;
286
320
  }
287
321
 
288
322
  /**
@@ -343,25 +377,28 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
343
377
  if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
344
378
  // unmanaged relations can't be primary key
345
379
  delete flatElement.key;
346
- transformExpression(flatElement, 'on', {
347
- ref: (_parent, _prop, xpr) => {
348
- const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
380
+ if (options.transformation !== 'effective') {
381
+ const process = endIndex => function processRef(_parent, _prop, xpr) {
382
+ const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, endIndex).join(pathDelimiter);
349
383
  const possibleFlatName = prefix + pathDelimiter + xpr[0];
350
384
  /*
351
- when element is defined in the current name resolution scope, like
352
- entity E {
353
- key x: Integer;
354
- s : {
355
- y : Integer;
356
- a3 : association to E on a3.x = y;
357
- }
358
- }
359
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
360
- */
385
+ when element is defined in the current name resolution scope, like
386
+ entity E {
387
+ key x: Integer;
388
+ s : {
389
+ y : Integer;
390
+ a3 : association to E on a3.x = y;
391
+ }
392
+ }
393
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
394
+ */
361
395
  if (flatElems[possibleFlatName])
362
396
  xpr[0] = possibleFlatName;
363
- },
364
- });
397
+ };
398
+ transformExpression(flatElement, 'on', {
399
+ ref: process(-1),
400
+ });
401
+ }
365
402
  }
366
403
  parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
367
404
  // Still add them - otherwise we might not detect collisions between generated elements.
@@ -427,36 +464,6 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
427
464
  return branches;
428
465
  }
429
466
 
430
- /**
431
- * Link annotate extensions to managed associations as a preparational step
432
- * for later annotation assignment on the final foreignkeys
433
- * This function must be applied on an unmodified, structured CSN in order to
434
- * traverse both the extensions and dictionary trees in corresponding order.
435
- *
436
- * @param {CSN.Model} csn
437
- * @param {object} options
438
- */
439
- function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
440
- if (isBetaEnabled(options, 'annotateForeignKeys')) {
441
- csn.extensions?.forEach(( ext ) => {
442
- const defName = ext.annotate;
443
-
444
- const traverseExtensions = (env, enode) => {
445
- if (env?.target && env?.keys) {
446
- setProp(env, '$fkExtensions', enode);
447
- }
448
- else {
449
- const elements = env?.items?.elements || env?.elements;
450
- if (enode?.elements && elements)
451
- Object.keys(enode.elements).forEach(en => traverseExtensions(elements[en], enode.elements[en]));
452
- }
453
- };
454
- if (ext.annotate)
455
- traverseExtensions(csn.definitions[defName], ext);
456
- });
457
- }
458
- }
459
-
460
467
  /**
461
468
  * @param {CSN.Model} csn
462
469
  * @param {CSN.Options} options
@@ -587,7 +594,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
587
594
  * @returns {object} The clone of base
588
595
  */
589
596
  function cloneAndExtendRef( key, base, ref ) {
590
- const clone = cloneCsnNonDict(base, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
597
+ const clone = cloneCsnNonDict(base, options );
591
598
  if (key.ref) {
592
599
  // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
593
600
  // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
@@ -603,8 +610,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
603
610
  }
604
611
  setProp(clone, '$ref', $ref);
605
612
  clone.ref = clone.ref.concat(key.ref);
606
- if (clone.$structRef && key.$structRef)
607
- clone.$structRef = clone.$structRef.concat(key.$structRef);
608
613
  }
609
614
 
610
615
  if (!clone.as && clone.ref && clone.ref.length > 0) {
@@ -704,34 +709,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
704
709
  'Expected target cardinality $(VALUE) and $(CODE) to match');
705
710
  }
706
711
  }
707
- }
708
- // assign annotations from fkExtension tree to foreign keys
709
- if (isBetaEnabled(options, 'annotateForeignKeys')) {
710
- const extCollector = {};
711
- fks.forEach(([ _fkn, fk ]) => {
712
- let ext = element.$fkExtensions;
713
- let extKey = elementName;
714
- for (const step of fk.$extensionPath) {
715
- extKey += `.${step}`;
716
- ext = ext?.elements?.[step];
717
- if (!ext)
718
- break;
719
- // collect annotations, lowest wins
720
- // eslint-disable-next-line no-loop-func
721
- Object.entries(ext).forEach(([ k, v ]) => {
722
- if (k[0] === '@') {
723
- fk[k] = v;
724
- extCollector[extKey] = ext;
725
- }
726
- });
727
- }
728
- });
729
712
 
730
- // remove consumed annotations after applying the annotation hierarchy to each fk!
731
- Object.values(extCollector).forEach(ext => Object.keys(ext).forEach((k) => {
732
- if (k[0] === '@')
733
- delete ext[k];
734
- }));
713
+ if (options.transformation === 'effective')
714
+ delete element.default;
735
715
  }
736
716
  orderedElements.push(...fks);
737
717
  });
@@ -756,11 +736,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
756
736
  * @param {CSN.Model} csn
757
737
  * @param {object} options
758
738
  * @param {string} pathDelimiter
759
- * @param {object} extensionPath
760
739
  * @param {number} lvl
761
740
  * @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
762
741
  */
763
- function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, extensionPath = [], lvl = 0 ) {
742
+ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
764
743
  const special$self = !csn?.definitions?.$self && '$self';
765
744
  const isInspectRefResult = !Array.isArray(path);
766
745
 
@@ -806,7 +785,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
806
785
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
807
786
  const alias = key.as || implicitAs(key.ref);
808
787
  const result = csnUtils.inspectRef(continuePath);
809
- fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, extensionPath.concat(key.$structRef), lvl + 1));
788
+ fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
810
789
  });
811
790
  if (!hasKeys)
812
791
  delete finalElement.keys;
@@ -820,19 +799,18 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
820
799
  // Skip already produced foreign keys
821
800
  if (!elem['@odata.foreignKey4']) {
822
801
  const continuePath = getContinuePath([ 'elements', elemName ]);
823
- fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, extensionPath.concat(elemName), lvl + 1));
802
+ fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
824
803
  }
825
804
  });
826
805
  }
827
806
  // we have reached a leaf element, create a foreign key
828
807
  else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
829
808
  const newFk = Object.create(null);
830
- setProp(newFk, '$extensionPath', extensionPath);
831
- for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
809
+ [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
832
810
  // copy props from original element to preserve derived types!
833
811
  if (element[prop] !== undefined)
834
812
  newFk[prop] = element[prop];
835
- }
813
+ });
836
814
  return [ [ prefix, newFk ] ];
837
815
  }
838
816
 
@@ -886,7 +864,7 @@ module.exports = {
886
864
  flattenAllStructStepsInRefs,
887
865
  flattenElements,
888
866
  removeLeadingSelf,
889
- linkForeignKeyAnnotationExtensionsToAssociation,
890
867
  handleManagedAssociationsAndCreateForeignKeys,
891
868
  getBranches,
869
+ getStructStepsFlattener,
892
870
  };
@@ -70,11 +70,10 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
70
70
  ],
71
71
  };
72
72
 
73
- // Clarify: What about $valid?
74
- const atFrom = { ref: [ '$at', 'from' ] };
75
- const atTo = { ref: [ '$at', 'to' ] };
73
+ const validFrom = { ref: [ '$valid', 'from' ] };
74
+ const validTo = { ref: [ '$valid', 'to' ] };
76
75
 
77
- const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
76
+ const cond = [ '(', fromPath, '<', validTo, 'and', toPath, '>', validFrom, ')' ];
78
77
 
79
78
  if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
80
79
  normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
@@ -324,7 +324,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
324
324
  * @param {string} artName
325
325
  * @param {CSN.Path} path
326
326
  */
327
- // eslint-disable-next-line complexity
328
327
  function transformViewOrEntity( query, artifact, artName, path ) {
329
328
  const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
330
329
  csnUtils.initDefinition(artifact);
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { forEachDefinition, getServiceNames, applyAnnotationsFromExtensions } = require('../../model/csnUtils');
3
+ const { forEachDefinition, forEachMemberRecursively,
4
+ getServiceNames, applyAnnotationsFromExtensions,
5
+ transformExpression } = require('../../model/csnUtils');
4
6
  const { forEach } = require('../../utils/objectUtils');
5
7
  const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
6
8
  const { getTransformers } = require('../transformUtils');
@@ -58,12 +60,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
58
60
  // Generate artificial draft fields for entities/views if requested, ignore if not part of a service
59
61
  if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
60
62
  generateDraftForOdata(def, defName, def);
61
-
62
- visitedArtifacts[defName] = true;
63
63
  }, { skipArtifact: isExternalServiceMember });
64
64
 
65
65
  applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
66
+ rewriteDollarDraft();
67
+
66
68
  return csn;
69
+
67
70
  /**
68
71
  * Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
69
72
  * into its transitively reachable composition targets, and into the model.
@@ -84,6 +87,9 @@ function generateDrafts( csn, options, services, messageFunctions ) {
84
87
  artifact.actions && artifact.actions.draftPrepare)
85
88
  return;
86
89
 
90
+ if(!visitedArtifacts[artifactName])
91
+ visitedArtifacts[artifactName] = artifact;
92
+
87
93
  const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
88
94
  assignAction(draftPrepare, artifact);
89
95
  // Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
@@ -211,6 +217,48 @@ function generateDrafts( csn, options, services, messageFunctions ) {
211
217
  }
212
218
  }
213
219
  }
220
+
221
+ /*
222
+ * After draft decoration, all visited artifacts are supposed to have the draft state elements
223
+ * Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
224
+ * in the core compiler builtins) needs to be translated into $self.<postfix>.
225
+ *
226
+ * It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
227
+ * some $draft path expressions.
228
+ */
229
+ function rewriteDollarDraft() {
230
+
231
+ function $draft2$self(member) {
232
+ Object.keys(member).forEach(pn => {
233
+ if(pn[0] === '@')
234
+ transformExpression(member, pn,{
235
+ ref: (_parent, _prop, xpr, _path) => {
236
+ if(xpr[0] === '$draft') {
237
+ xpr[0] = '$self';
238
+ }
239
+ }
240
+ });
241
+ });
242
+ }
243
+
244
+ // entity parameters are not substituted as the EDM param entity is not draft enabled
245
+ Object.entries(visitedArtifacts).forEach(([artName, art]) => {
246
+ $draft2$self(art);
247
+ forEachMemberRecursively(art, $draft2$self,
248
+ [ 'definitions', artName ],
249
+ true, { elementsOnly: true }
250
+ );
251
+ if(art.actions) {
252
+ Object.entries(art.actions).forEach(([actionName, action]) => {
253
+ $draft2$self(action);
254
+ forEachMemberRecursively(action, $draft2$self,
255
+ [ 'definitions', artName, 'actions', actionName ]);
256
+ if(action.returns)
257
+ $draft2$self(action.returns);
258
+ })
259
+ }
260
+ })
261
+ }
214
262
  }
215
263
 
216
264
  module.exports = generateDrafts;
@@ -179,9 +179,10 @@ function remapODataAnnotations( csn ) {
179
179
  }
180
180
 
181
181
  return {
182
- elements: (parent, prop, elements, path) => {
182
+ elements: (parent, prop, elements, path, _parentParent, _dummy, context) => {
183
183
  const artifact = csn.definitions[path[1]];
184
- if (artifact?.kind === 'entity') {
184
+ // Don't process bound actions, as they are still structured
185
+ if (artifact?.kind === 'entity' && !context.$in_actions) {
185
186
  for (const elementName in elements)
186
187
  remapAnnotationsOnElement(artifact, elements[elementName]);
187
188
  }
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachDefinition, forEachMemberRecursively, findAnnotationExpression, applyTransformationsOnNonDictionary, transformExpression,
5
+ } = require('../../model/csnUtils');
6
+ const { getStructStepsFlattener } = require('../db/flattening');
7
+ const { setProp } = require('../../base/model');
8
+ const { forEach } = require('../../utils/objectUtils');
9
+
10
+
11
+ /**
12
+ *
13
+ * @param csn
14
+ * @param options
15
+ * @param csnUtils
16
+ * @param messageFunctions
17
+ */
18
+ function flattenRefs( csn, options, csnUtils, messageFunctions ) {
19
+ const cleanup = [];
20
+ forEachDefinition(csn, (artifact) => {
21
+ if (artifact.elements) {
22
+ const stack = [ { prefix: [], elements: artifact.elements } ];
23
+ while (stack.length > 0) {
24
+ const { prefix, elements } = stack.pop();
25
+ forEach(elements, (elementName, element) => {
26
+ if (element.elements)
27
+ stack.push({ prefix: prefix.concat(elementName), elements: element.elements });
28
+
29
+ const absolutifier = absolutifyPaths(prefix, cleanup);
30
+ // Absolutify paths in on-conditions
31
+ if (element.on) {
32
+ transformExpression(element, 'on', {
33
+ ref: absolutifier,
34
+ }, []);
35
+ }
36
+
37
+ // Absolutify paths in annotation expressions
38
+ Object.keys(element)
39
+ .filter(pn => findAnnotationExpression(element, pn))
40
+ .forEach((anno) => {
41
+ applyTransformationsOnNonDictionary(element, anno, {
42
+ xpr: (parent, prop) => {
43
+ transformExpression(parent, prop, {
44
+ ref: absolutifier,
45
+ }, []);
46
+ },
47
+ }, {}, []);
48
+ if (element[anno].ref)
49
+ absolutifier(element[anno], 'ref', element[anno].ref);
50
+ });
51
+ });
52
+ }
53
+ }
54
+ });
55
+
56
+
57
+ const adaptRefs = [];
58
+ const resolved = new WeakMap();
59
+ const refFlattener = getStructStepsFlattener(csn, options, messageFunctions, resolved, '_', adaptRefs);
60
+
61
+ forEachDefinition(csn, (def, defName) => {
62
+ if (def.kind === 'entity') {
63
+ applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: true, skipDict: { actions: 1 } }, [ 'definitions' ]);
64
+
65
+ adaptRefs.forEach(fn => fn());
66
+ adaptRefs.length = 0;
67
+
68
+ // explicit binding parameter of bound action
69
+ if (def.actions) {
70
+ const special$self = !csn?.definitions?.$self && '$self';
71
+ Object.entries(def.actions).forEach(([ an, a ]) => {
72
+ if (a.params) {
73
+ const params = Object.entries(a.params);
74
+ const firstParam = params[0][1];
75
+ const type = firstParam?.items?.type || firstParam?.type;
76
+ if (type === special$self) {
77
+ const bindingParamName = params[0][0];
78
+ const markBindingParam = {
79
+ ref: (parent, prop, xpr) => {
80
+ if ((xpr[0].id || xpr[0]) === bindingParamName)
81
+ setProp(parent, '$bparam', true);
82
+ },
83
+ };
84
+
85
+ Object.keys(a)
86
+ .filter(pn => findAnnotationExpression(a, pn))
87
+ .forEach((pn) => {
88
+ transformExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
89
+ adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
90
+ adaptRefs.length = 0;
91
+ });
92
+
93
+
94
+ forEachMemberRecursively(a, (member, memberName, prop, path) => {
95
+ Object.keys(member).filter(pn => findAnnotationExpression(member, pn)).forEach((pn) => {
96
+ transformExpression(member, pn, [ markBindingParam, refFlattener ], path);
97
+ adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
98
+ adaptRefs.length = 0;
99
+ });
100
+ }, [ 'definitions', defName, 'actions', an ]);
101
+ }
102
+ }
103
+ });
104
+ }
105
+ }
106
+ else {
107
+ applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: false }, [ 'definitions' ]);
108
+ adaptRefs.forEach(fn => fn());
109
+ adaptRefs.length = 0;
110
+ }
111
+ });
112
+
113
+ cleanup.forEach((obj) => {
114
+ if (obj.ref && obj.ref[0] === '$self')
115
+ obj.ref.shift();
116
+ });
117
+ }
118
+
119
+ function absolutifyPaths(prefix, cleanup) {
120
+ return function absolutify(_parent, _prop, ref) {
121
+ if (ref[0].id || ref[0] !== '$self' && ref[0] !== '$projection' && !ref[0].startsWith('$') && !_parent.param) {
122
+ _parent.ref = [ '$self', ...prefix, ...ref ];
123
+ cleanup.push(_parent);
124
+
125
+ return true;
126
+ }
127
+
128
+ return false;
129
+ };
130
+ }
131
+
132
+
133
+ module.exports = {
134
+ flattenRefs,
135
+ };
@@ -4,6 +4,7 @@ const {
4
4
  getUtils, isAspect, mergeTransformers, applyTransformations,
5
5
  } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
+ const effectiveFlattening = require('./flattening');
7
8
  const flattening = require('../db/flattening');
8
9
  const types = require('./types');
9
10
  // const { addLocalizationViews } = require('../../transform/localized');
@@ -11,7 +12,6 @@ const validate = require('../../checks/validator');
11
12
  const expansion = require('../db/expansion');
12
13
  const queries = require('./queries');
13
14
  const associations = require('./associations');
14
- const generateDrafts = require('../draft/db');
15
15
  const handleExists = require('../db/transformExists');
16
16
  const misc = require('./misc');
17
17
  const annotations = require('./annotations');
@@ -66,8 +66,9 @@ function effectiveCsn( model, options, messageFunctions ) {
66
66
  // Remove properties attached by validator - they do not "grow" as the model grows.
67
67
  cleanup();
68
68
 
69
- flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, new WeakMap(), '_');
70
- flattening.flattenElements(csn, options, messageFunctions, '_');
69
+
70
+ effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
71
+ flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
71
72
 
72
73
  resolveTypesInActionsAfterFlattening();
73
74
 
@@ -77,9 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
77
78
  processCalculatedElementsInEntities(csn);
78
79
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
79
80
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
80
- generateDrafts(csn, options, '_', messageFunctions);
81
- const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
82
- applyTransformations(csn, transformers, [], { skipIgnore: false });
81
+ const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
82
+ applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
83
83
 
84
84
  if (!options.resolveProjections)
85
85
  redoProjections.forEach(fn => fn());