@sap/cds-compiler 3.8.2 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +27 -18
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +7 -2
  22. package/lib/compiler/base.js +8 -4
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +910 -1711
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +3 -1
  34. package/lib/edm/annotations/genericTranslation.js +6 -6
  35. package/lib/edm/csn2edm.js +1 -1
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3564 -3493
  47. package/lib/json/from-csn.js +26 -4
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +2 -1
  51. package/lib/language/language.g4 +14 -3
  52. package/lib/model/csnRefs.js +10 -5
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/odata/toFinalBaseType.js +6 -6
  72. package/lib/transform/odata/typesExposure.js +12 -3
  73. package/lib/transform/odata/utils.js +3 -0
  74. package/lib/transform/transformUtilsNew.js +11 -26
  75. package/lib/transform/translateAssocsToJoins.js +9 -9
  76. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  77. package/lib/transform/universalCsn/coreComputed.js +1 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  79. package/package.json +1 -1
@@ -244,7 +244,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
244
244
  }
245
245
  });
246
246
 
247
- processCalculatedElementsInEntities(csn, options);
247
+ processCalculatedElementsInEntities(csn);
248
248
 
249
249
  timetrace.start('Transform CSN')
250
250
 
@@ -272,7 +272,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
272
272
  ]);
273
273
 
274
274
  // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
275
- doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
275
+ doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
276
276
 
277
277
  doA2J && forEachDefinition(csn, flattenIndexes);
278
278
  // Managed associations get an on-condition - in views and entities
@@ -613,7 +613,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
613
613
  function doit(dict, subPath) {
614
614
  for (const elemName in dict) {
615
615
  const elem = dict[elemName];
616
- if (isAssocOrComposition(elem.type) && elem.on)
616
+ if (elem.on && isAssocOrComposition(elem))
617
617
  processBacklinkAssoc(elem, elemName, artifact, artifactName, subPath.concat([ elemName, 'on' ]));
618
618
  }
619
619
  }
@@ -636,16 +636,16 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
636
636
  // For HANA: Report an error on
637
637
  // - view with parameters that has an element of type association/composition
638
638
  // - association that points to entity with parameters
639
- if (options.sqlDialect === 'hana' && member.target && isAssocOrComposition(member.type) && !isBetaEnabled(options, 'assocsWithParams')) {
639
+ if (options.sqlDialect === 'hana' && member.target && isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
640
640
  if (artifact.params) {
641
641
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
642
642
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
643
- message('def-unexpected-paramview-assoc', path, { '#': 'view' });
643
+ message('def-unexpected-paramview-assoc', path, { '#': 'source' });
644
644
  }
645
645
  else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
646
646
  // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
647
647
  const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
648
- message('def-unexpected-calcview-assoc', path, { '#': 'entity-persistence', anno });
648
+ message('def-unexpected-calcview-assoc', path, { '#': 'source', anno });
649
649
  }
650
650
  if (csn.definitions[member.target].params) {
651
651
  // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
@@ -660,7 +660,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
660
660
  // CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
661
661
  // CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
662
662
  const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
663
- message('def-unexpected-calcview-assoc', path, { '#': 'target-persistence', anno });
663
+ message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
664
664
  }
665
665
  }
666
666
  }, [ 'definitions', artifactName ]);
@@ -27,7 +27,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
27
27
  /*
28
28
  If the definition('def' variable) is a type definition and the assigned type of this very same definition('def' variable)
29
29
  is structured type, e.g.:
30
-
30
+
31
31
  type Struct1 {
32
32
  a : Integer;
33
33
  b : Integer;
@@ -48,7 +48,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
48
48
  "a": { "type": "cds.Integer" },
49
49
  "b": { "type": "cds.Integer" }
50
50
  } } ...
51
-
51
+
52
52
  "S.Struct2" should looks just like "S.Struct1" => the "type": "S.Struct1" property has to be removed
53
53
  */
54
54
  if (def.kind === 'type' && def.type && !isBuiltinType(def.type) && !def.type.ref) {
@@ -83,7 +83,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
83
83
  // type Foo: array of { qux: Integer };
84
84
  function expandFirstLevelOfArrayed(def) {
85
85
  if (def.items.type && !isBuiltinType(def.items.type)) {
86
- let finalBaseType = csnUtils.getFinalBaseTypeWithProps(def.items.type);
86
+ let finalBaseType = csnUtils.getFinalTypeInfo(def.items.type);
87
87
  if (finalBaseType?.elements) {
88
88
  def.items.elements = cloneCsnDictionary(finalBaseType.elements, options);
89
89
  delete def.items.type;
@@ -102,7 +102,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
102
102
  if (node.kind === 'event') return;
103
103
 
104
104
  if(node.type && !isBuiltinType(node.type)) {
105
- const finalBaseType = csnUtils.getFinalBaseTypeWithProps(node.type);
105
+ const finalBaseType = csnUtils.getFinalTypeInfo(node.type);
106
106
  if(!finalBaseType) {
107
107
  /*
108
108
  type could not be resolved, set it to null
@@ -179,8 +179,8 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
179
179
  // handle array of defined via a named type
180
180
  // example in actions: 'action act() return Primitive; type Primitive: array of String;'
181
181
  const currService = csnUtils.getServiceName(defName);
182
- const isArrayOfBuiltin = finalBaseType.items &&
183
- isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalBaseType.items.type)?.type)
182
+ const isArrayOfBuiltin = finalBaseType.items &&
183
+ isBuiltinType(csnUtils.getFinalTypeInfo(finalBaseType.items.type)?.type)
184
184
  if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
185
185
  node.items = finalBaseType.items;
186
186
  delete node.type;
@@ -162,7 +162,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
162
162
  isKey = false;
163
163
  // in case this was a named type and if the openess does not match the type definition
164
164
  // expose the type as a new one not changing the original definition.
165
- if(elements && (!!node['@open'] !== !!typeDef['@open']) && isBetaEnabled(options, 'odataOpenType'))
165
+ if(elements && !!node['@open'] !== !!typeDef['@open'])
166
166
  fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
167
167
  }
168
168
  // check if that type is already defined
@@ -381,8 +381,17 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
381
381
  Object.entries(elements).forEach(([elemName, element]) => {
382
382
  let cloned = cloneCsnNonDict(element, options);
383
383
  // if this was an anonymous sub element of a key, mark it as not nullable
384
- if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
385
- cloned.notNull = true;
384
+ if(isAnonymous && isKey && !cloned.key) {
385
+ if(cloned.target) {
386
+ if(cloned.cardinality === undefined)
387
+ cloned.cardinality = {};
388
+ cloned.cardinality.min = 1;
389
+ }
390
+ // if odata-spec-violation-key-null is checking on min>1, this can
391
+ // be an else if
392
+ if(cloned.notNull === undefined)
393
+ cloned.notNull = true;
394
+ }
386
395
  type.elements[elemName] = cloned;
387
396
  });
388
397
  return type;
@@ -13,6 +13,9 @@ function defNameWithoutServiceOrContextName(name, srvOrCtx) {
13
13
  * By the given name of an artifact 'artName 'and an array 'services'
14
14
  * containing all the service names part of the model, return the service
15
15
  * name where the given artifact resides
16
+ *
17
+ * @todo: This function does not take nested services into account => longest match
18
+ *
16
19
  * @param {string} artName
17
20
  * @param {string[]} services
18
21
  */
@@ -4,7 +4,7 @@
4
4
  // different backends.
5
5
  // The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
6
6
 
7
- const { hasErrors, makeMessageFunction } = require('../base/messages');
7
+ const { makeMessageFunction } = require('../base/messages');
8
8
  const { setProp } = require('../base/model');
9
9
 
10
10
  const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
@@ -25,7 +25,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
25
25
  const csnUtils = getUtils(model);
26
26
  const {
27
27
  getCsnDef,
28
- getFinalBaseTypeWithProps,
28
+ getFinalTypeInfo,
29
29
  hasAnnotationValue,
30
30
  inspectRef,
31
31
  isStructured,
@@ -142,12 +142,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
142
142
  // FIXME: Duplicate code
143
143
  // Assemble foreign key element name from assoc name, '_' and foreign key name/alias
144
144
  const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
145
-
146
-
147
- // In case of compiler errors the foreign key might be missing
148
- if (!fkArtifact && hasErrors(options.messages)) {
149
- return null;
150
- }
151
145
  return [ foreignKeyElementName, createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName) ];
152
146
  }
153
147
 
@@ -168,21 +162,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
168
162
 
169
163
 
170
164
  let fkArtifact = inspectRef(path).art;
171
-
172
- // In case of compiler errors the foreign key might be missing
173
- if (!fkArtifact && hasErrors(options.messages)) {
174
- return null;
175
- }
176
-
177
- newForeignKey(fkArtifact,foreignKeyElementName);
165
+ newForeignKey(fkArtifact, foreignKeyElementName);
178
166
 
179
167
  function processAssociationOrComposition(fkArtifact,foreignKeyElementName) {
180
168
  fkArtifact.keys.forEach(iKey => {
181
- let iKeyArtifact = inspectRef(iKey.$path).art;
182
-
183
- if (!iKeyArtifact && hasErrors(options.messages)) {
184
- return;
185
- }
169
+ const iKeyArtifact = inspectRef(iKey.$path).art;
186
170
  if(iKey.ref.length>1)
187
171
  throw new CompilerAssertion(`createForeignKeyElement(${artifactName},${assocName},${iKey.$path.join('/')}) unexpected reference: `+ iKey.ref)
188
172
  newForeignKey(iKeyArtifact,foreignKeyElementName+'_'+iKey.ref[0])
@@ -242,7 +226,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
242
226
  // for type of 'x' -> elem.type is an object, not a string -> use directly
243
227
  let elemType;
244
228
  if (!elem.elements) // structures do not have final base type
245
- elemType = getFinalBaseTypeWithProps(elem.type);
229
+ elemType = getFinalTypeInfo(elem.type);
246
230
 
247
231
  const struct = elemType ? elemType.elements : elem.elements;
248
232
 
@@ -419,11 +403,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
419
403
  typeRef = resolved.get(type)?.art
420
404
  // The cached entry may not be resolved, yet.
421
405
  if (typeRef.type && !isBuiltinType(typeRef.type))
422
- typeRef = getFinalBaseTypeWithProps(typeRef.type);
406
+ typeRef = getFinalTypeInfo(typeRef.type);
423
407
  } else {
424
- typeRef = getFinalBaseTypeWithProps(type);
408
+ typeRef = getFinalTypeInfo(type);
425
409
  }
426
-
410
+ if(!typeRef)
411
+ return;
427
412
  if (typeRef.elements || typeRef.items) {
428
413
  // Copy elements/items and we're finished. No need to look up actual base type,
429
414
  // since it must also be structured and must contain at least as many elements,
@@ -1188,9 +1173,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
1188
1173
  '$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
1189
1174
  }
1190
1175
  else {
1191
- error(null, location,
1176
+ error(null, location,
1192
1177
  {
1193
- prefix: prefix(lhs, op, rhs),
1178
+ prefix: prefix(lhs, op, rhs),
1194
1179
  name: (x.lhs ? lhs : rhs).ref.join('.'),
1195
1180
  alias: (x.lhs ? rhs : lhs).ref.join('.')
1196
1181
  },
@@ -1303,8 +1303,8 @@ function translateAssocsToJoins(model, inputOptions = {})
1303
1303
  path = tail;
1304
1304
  }
1305
1305
  // assocStack eventually undefined => head is not Assoc
1306
- // assoc prefix can be something strucutred or just the id, the core
1307
- // compiler decides if it want's to add 'element' or only 'id' to the XSN
1306
+ // assoc prefix can be something structured or just the id, the core
1307
+ // compiler decides if it wants to add 'element' or only 'id' to the XSN
1308
1308
  // we need to unambiguously identify the target side with the full assoc prefix.
1309
1309
  // If the path is on the target side, strip the prefix of and treat src/tgt
1310
1310
  // paths uniformly.
@@ -1313,19 +1313,19 @@ function translateAssocsToJoins(model, inputOptions = {})
1313
1313
  path.forEach((ps) => {
1314
1314
  /* checks for all path steps */
1315
1315
  if(ps.args) {
1316
- error(null, pathDict.location,
1316
+ error(null, [pathDict.location, lead],
1317
1317
  { name: lead.name.absolute, id: ps.id },
1318
1318
  '$(NAME): $(ID) must not have parameters');
1319
1319
  pathDict.$check = false;
1320
1320
  }
1321
1321
  if(ps.where) {
1322
- error(null, pathDict.location,
1322
+ error(null, [pathDict.location, lead],
1323
1323
  { name: lead.name.absolute, id: ps.id },
1324
1324
  '$(NAME): $(ID) must not have a filter');
1325
1325
  pathDict.$check = false;
1326
1326
  }
1327
1327
  if(ps._artifact.virtual) {
1328
- error(null, pathDict.location,
1328
+ error(null, [pathDict.location, lead],
1329
1329
  { name: lead.name.absolute, id: ps.id },
1330
1330
  '$(NAME): $(ID) must not be virtual');
1331
1331
  pathDict.$check = false;
@@ -1338,7 +1338,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1338
1338
  if(la1) {
1339
1339
  if(ps._artifact.on)
1340
1340
  {
1341
- error(null, pathDict.location,
1341
+ error(null, [pathDict.location, lead],
1342
1342
  { name: lead.name.absolute, id: ps.id, alias: pathAsStr(pathDict.path) },
1343
1343
  '$(NAME): $(ID) in path $(ALIAS)" must not be an unmanaged association');
1344
1344
  pathDict.$check = false;
@@ -1346,7 +1346,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1346
1346
  else if(ps._artifact.$fkPathPrefixTree)// must be managed
1347
1347
  {
1348
1348
  if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
1349
- error(null, pathDict.location,
1349
+ error(null, [pathDict.location, lead],
1350
1350
  { art: lead.name.absolute, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1351
1351
  '$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
1352
1352
  pathDict.$check = false;
@@ -1355,7 +1355,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1355
1355
  }
1356
1356
  else {
1357
1357
  // it is the last path step => no association
1358
- error(null, pathDict.location,
1358
+ error(null, [pathDict.location, lead],
1359
1359
  { art: lead.name.absolute, id: ps.id, alias: pathAsStr(pathDict.path) },
1360
1360
  '$(ART): $(ID) in path $(ALIAS) must not be an association');
1361
1361
  pathDict.$check = false;
@@ -1366,7 +1366,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1366
1366
  const lastSegment = path[path.length - 1];
1367
1367
  const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
1368
1368
  if (artifact && artifact.elements) {
1369
- error(null, pathDict.location,
1369
+ error(null, [pathDict.location, lead],
1370
1370
  { art: lead.name.absolute, id: lastSegment.id },
1371
1371
  '$(ART): $(ID) must have scalar type');
1372
1372
  pathDict.$check = false;
@@ -17,15 +17,16 @@
17
17
  "sonarjs/cognitive-complexity": "off",
18
18
  // Does not recognize TS types
19
19
  "jsdoc/no-undefined-types": "off",
20
+ "jsdoc/tag-lines": "off",
20
21
  // Just annoying as hell
21
22
  "sonarjs/no-duplicate-string": "off"
22
23
  },
23
24
  "parserOptions": {
24
- "ecmaVersion": 2020,
25
+ "ecmaVersion": 2022,
25
26
  "sourceType": "script"
26
27
  },
27
28
  "env": {
28
- "es2020": true,
29
+ "es2022": true,
29
30
  "node": true
30
31
  },
31
32
  "settings": {
@@ -155,7 +155,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
155
155
  (
156
156
  column.xpr || column.list || column.func || column.val !== undefined || column.param ||
157
157
  column.SELECT || column.SET ||
158
- column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
158
+ column.ref && [ '$at', '$valid', '$now', '$user', '$session' ].includes(column.ref[0])
159
159
  );
160
160
  }
161
161
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { setProp } = require('../../base/model');
3
+ const { setProp, isBetaEnabled } = require('../../base/model');
4
4
  const shuffleGen = require('../../base/shuffle');
5
5
  const { setAnnotationIfNotDefined, makeClientCompatible } = require('./utils');
6
6
  const {
@@ -23,6 +23,7 @@ const { setCoreComputedOnViewsAndCalculatedElements } = require('./coreComputed'
23
23
  * @param {CSN.Options} options
24
24
  */
25
25
  module.exports = (csn, options) => {
26
+ const propagateToReturns = isBetaEnabled( options, 'v4preview' );
26
27
  const csnUtils = getUtils(csn, 'init-all');
27
28
  const {
28
29
  initDefinition, getOrigin, getQueryPrimarySource, artifactRef, getColumn,
@@ -174,7 +175,8 @@ module.exports = (csn, options) => {
174
175
  setTargetAspectIfRequired(parent);
175
176
  },
176
177
  type: ( parent, prop, type, path, grandParent, parentProp ) => {
177
- if (parentProp === 'returns') // annos are not propagated to `returns` and `items`
178
+ // annos are not propagated to `returns` (<=v3) and `items`
179
+ if (parentProp === 'returns' && !propagateToReturns)
178
180
  return;
179
181
  const annotationsForBuiltinType = extensions[type];
180
182
  Object.assign( parent, annotationsForBuiltinType );
@@ -292,7 +294,7 @@ module.exports = (csn, options) => {
292
294
  });
293
295
  },
294
296
  returns: (parent, prop, returns) => {
295
- propagateMemberPropsFromOrigin(returns, { items: true, '@': true, elements: true });
297
+ propagateMemberPropsFromOrigin(returns, { items: true, '@': !propagateToReturns, elements: true });
296
298
  if (returns.target)
297
299
  calculateForeignKeys(returns);
298
300
  },
@@ -618,7 +620,7 @@ function copyProperties( from, to, getCustomRule, except = null, force = null )
618
620
  const keys = Object.keys(from);
619
621
  // Copy over properties from the origin element to the target.
620
622
  for (const key of keys) {
621
- if (except && !(force && force[key]) && (key.charAt(0) in except || key in except))
623
+ if (except && !(force && force[key]) && (except[key] || except[key.charAt(0)]))
622
624
  continue;
623
625
  if (!(key in to)) {
624
626
  const func = force && force[key] ? force[key] : getCustomRule(key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "3.8.2",
3
+ "version": "3.9.2",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",