@sap/cds-compiler 2.15.4 → 3.0.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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -5,6 +5,8 @@ const { applyTransformations, applyTransformationsOnNonDictionary } = require('.
5
5
  const { isBuiltinType } = require('../compiler/builtins.js')
6
6
  const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
7
7
  const { ModelError } = require("../base/error");
8
+ const { typeParameters } = require("../compiler/builtins");
9
+ const { forEach } = require('../utils/objectUtils');
8
10
  const version = require('../../package.json').version;
9
11
 
10
12
  // Low-level utility functions to work with compact CSN.
@@ -34,11 +36,14 @@ const version = require('../../package.json').version;
34
36
  */
35
37
 
36
38
  /**
37
- * Get utility functions for a given CSN.
39
+ * Get utility functions for a given CSN. Re-exports functions of `csnRefs()`.
38
40
  * @param {CSN.Model} model (Compact) CSN model
39
41
  */
40
42
  function getUtils(model, universalReady) {
41
- const { artifactRef, inspectRef, effectiveType, getOrigin, targetAspect, getColumn, getElement, initDefinition } = csnRefs(model, universalReady);
43
+ const _csnRefs = csnRefs(model, universalReady);
44
+ const { artifactRef } = _csnRefs;
45
+ /** Cache for getFinalBaseTypeWithProps(). Specific to the current model. */
46
+ const finalBaseTypeCache = Object.create(null);
42
47
 
43
48
  return {
44
49
  getCsnDef,
@@ -56,17 +61,10 @@ function getUtils(model, universalReady) {
56
61
  getServiceName,
57
62
  hasAnnotationValue,
58
63
  cloneWithTransformations,
59
- getFinalBaseType,
60
- inspectRef,
61
- artifactRef,
62
- effectiveType,
64
+ getFinalBaseTypeWithProps,
63
65
  get$combined,
64
- getOrigin,
65
66
  getQueryPrimarySource,
66
- targetAspect,
67
- getColumn,
68
- getElement,
69
- initDefinition
67
+ ..._csnRefs,
70
68
  };
71
69
 
72
70
  /**
@@ -261,7 +259,7 @@ function getUtils(model, universalReady) {
261
259
  */
262
260
  function isStructured(obj) {
263
261
  return obj.elements ||
264
- (obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseType(obj.type).elements)));
262
+ (obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseTypeWithProps(obj.type)?.elements)));
265
263
  }
266
264
 
267
265
  /**
@@ -486,97 +484,122 @@ function getUtils(model, universalReady) {
486
484
  }
487
485
  }
488
486
 
487
+ function _normalizeTypeRef(type) {
488
+ if (type && typeof type === 'object' && type.ref?.length === 1)
489
+ type = type.ref[0]; // simplify type: no element -> simple string can be used
490
+ return type;
491
+ }
489
492
 
490
493
  /**
491
- * Resolve to the final type of a type, that means follow type chains, references to other types or
492
- * elements a.s.o
493
- * Works for all kinds of types, strings as well as type objects. Strings need to be absolute type names.
494
- * Returns the final type as string (if it has a name, which is not always the case, think of embedded structures),
495
- * else the type object itself is returned. If a type is structured, you can navigate into it by providing a path,
496
- * e.g. given the following model
497
- * type bar: S.foo;
498
- * type s1 {
499
- * s: s2;
500
- * };
501
- * type s2 {
502
- * u: type of S.e:t;
503
- * }
504
- * service S {
505
- * type foo: type of S.e:i.j1;
506
- * entity e {
507
- * key i: { j1: Integer };
508
- * t: bar;
509
- * v: s1;
510
- * x: blutz.s.u;
511
- * };
512
- * type blutz: S.e.v;
513
- * view V as select from e {
514
- * 1+1 as i: bar,
515
- * };
516
- * type tt: type of V:i;
517
- * }
518
- * the following calls will all return 'cds.Integer'
519
- * getFinalBaseType('S.tt')
520
- * getFinalBaseType('S.e',['i','j1'])
521
- * getFinalBaseType('S.e',['t'])
522
- * getFinalBaseType('S.e',['x'])
523
- * getFinalBaseType('S.blutz',['s', 'u'])
524
- * Types are always resolved as far as possible. A type name which has no further definition is simply returned.
525
- * Composed types (structures, entities, views, ...) are returned as type objects, if not drilled down into
526
- * the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
527
- * no type (e.g. expr in a view without explicit type) returns 'null'
494
+ * Resolve to the final type of a type, that means follow type chains, references, etc.
495
+ * Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
496
+ *
497
+ * Returns `null` if the type can't be resolved or if the referenced element has no type,
498
+ * e.g. `typeof V:calculated`.
499
+ * Otherwise, if scalar, returns an object that has a `type` property and all collected type
500
+ * properties, or the type object with `elements` or `items` property if structured/arrayed.
501
+ *
502
+ * Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
503
+ * and use the newly returned `getFinalBaseTypeWithProps()`.
528
504
  *
529
- * @param {string|object} type Type - either string or ref
530
- * @param {CSN.Path} path
531
- * @param {WeakMap} [resolved=new WeakMap()] WeakMap containing already resolved refs - if a ref is not cached, it will be resolved JIT
532
- * @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
533
- * @returns
505
+ * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
506
+ * @returns {object|null}
534
507
  */
535
- function getFinalBaseType(type, path = [], resolved = new WeakMap(), cycleCheck = undefined) {
508
+ function getFinalBaseTypeWithProps(type) {
509
+ type = _normalizeTypeRef(type);
536
510
  if (!type)
537
- return type;
538
- if (typeof(type) === 'string') {
539
- if (isBuiltinType(type)) // built-in type
540
- return type;
541
- if (cycleCheck) {
542
- let visited = path.length? type + ':' + path.join('.') : type;
543
- if (cycleCheck[visited])
544
- throw new ModelError('Circular type chain on type ' + type);
545
- else
546
- cycleCheck[visited] = true;
511
+ return null;
512
+
513
+ // Nothing to copy from builtin type name.
514
+ if (typeof type === 'string' && isBuiltinType( type )) {
515
+ return { type };
516
+ }
517
+
518
+ // We differentiate between ref and type to avoid collisions due to dict key.
519
+ // Delimiter chosen arbitrarily; just one that is rarely used.
520
+ const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
521
+
522
+ if (finalBaseTypeCache[resolvedKey]) {
523
+ if (finalBaseTypeCache[resolvedKey] === true)
524
+ throw new ModelError(`Detected circular type reference; can't resolve: ${resolvedKey}`);
525
+ return finalBaseTypeCache[resolvedKey];
526
+ }
527
+
528
+ let typeRef = artifactRef(type); // throws if not found
529
+ const isNonScalar = _cacheNonScalar(typeRef);
530
+ if (isNonScalar)
531
+ return finalBaseTypeCache[resolvedKey];
532
+
533
+ const props = {};
534
+ _copyTypeProps(props, typeRef);
535
+
536
+ // If the resolved type is a builtin, stop and use its type arguments.
537
+ type = _normalizeTypeRef(typeRef.type);
538
+ if (typeof type === 'string' && isBuiltinType(type))
539
+ return _cacheResolved(props);
540
+
541
+ // Set to true (before the recursive call) to avoid cyclic issues.
542
+ finalBaseTypeCache[resolvedKey] = true;
543
+
544
+ // Continue the search
545
+ const finalBase = getFinalBaseTypeWithProps(type);
546
+ if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
547
+ return _cacheResolved(null);
548
+ const nonScalar = _cacheNonScalar(finalBase);
549
+ if (nonScalar)
550
+ return finalBaseTypeCache[resolvedKey];
551
+
552
+ // If not a non-scalar, must be resolved type.
553
+ _copyTypeProps(props, finalBase);
554
+ _cacheResolved(props);
555
+ return props;
556
+
557
+ /**
558
+ * Cache/Store the type props under the current `resolvedKey` in the `resolved` cache.
559
+ *
560
+ * @param {object} typeProps
561
+ */
562
+ function _cacheResolved(typeProps) {
563
+ finalBaseTypeCache[resolvedKey] = typeProps;
564
+ return typeProps;
565
+ }
566
+
567
+ /**
568
+ * Structured or arrayed types are not followed further, so cache them.
569
+ *
570
+ * @param obj
571
+ * @returns {boolean} True, if structured/arrayed/invalid, false if scalar.
572
+ */
573
+ function _cacheNonScalar(obj) {
574
+ if (!obj) { // Reference has no proper type, e.g. due to `type of View:calculated`.
575
+ _cacheResolved(null);
576
+ return true;
547
577
  }
548
- else {
549
- cycleCheck = Object.create(null);
578
+ if (obj.elements || obj.items) {
579
+ _cacheResolved(obj);
580
+ return true;
550
581
  }
551
- let definedType = model.definitions[type];
552
- if (definedType && definedType.type)
553
- return getFinalBaseType(definedType.type, path, resolved, cycleCheck);
554
- else
555
- return getFinalBaseType(definedType, path, resolved, cycleCheck);
582
+ return false;
556
583
  }
557
- else if (typeof(type) === 'object') {
558
- if (type.ref) {
559
- // assert type.ref instanceof Array && type.ref.length >= 1
560
- const ref = resolved.has(type) ? resolved.get(type).art : artifactRef(type);
561
- return getFinalBaseType(ref, path, resolved, cycleCheck);
562
- }
563
- else if (type.elements) {
564
- if (path.length) {
565
- let [e, ...p] = path;
566
- return getFinalBaseType(type.elements[e], p, resolved, cycleCheck);
584
+
585
+ /**
586
+ * Copy type properties from source to target. Also copies `type`, `enum`,
587
+ * and `localized` (if keepLocalized is true). Only copies from source,
588
+ * if target does not have them.
589
+ *
590
+ * @param {object} target
591
+ * @param {object} source
592
+ */
593
+ function _copyTypeProps(target, source) {
594
+ target.type = source.type;
595
+ const typeProps = [ ...typeParameters.list, 'enum', 'default', 'localized' ];
596
+ for (const param of typeProps) {
597
+ if (target[param] === undefined && source[param] !== undefined) {
598
+ target[param] = source[param];
567
599
  }
568
600
  }
569
- else if (type.type)
570
- return (getFinalBaseType(type.type, path, resolved, cycleCheck));
571
- else if (type.items)
572
- return type;
573
- else
574
- // TODO: this happens if we don't have a type, e.g. an expression in a select list
575
- // in a view without explicit type. Instead of returning null we might want to return
576
- // the object instead?
577
- return null;
601
+ return target;
578
602
  }
579
- return type;
580
603
  }
581
604
  }
582
605
 
@@ -770,25 +793,6 @@ function forEachGeneric( construct, prop, callback, path = [], iterateOptions =
770
793
  }
771
794
  }
772
795
 
773
- // Like Object.assign() but copies also non enumerable properties
774
- function assignAll(target, ...sources) {
775
- sources.forEach(source => {
776
- const descriptors = Object.getOwnPropertyNames(source).reduce((propertyDescriptors, current) => {
777
- propertyDescriptors[current] = Object.getOwnPropertyDescriptor(source, current);
778
- return propertyDescriptors;
779
- }, {});
780
- // by default, Object.assign copies enumerable Symbols too
781
- Object.getOwnPropertySymbols(source).forEach(sym => {
782
- const descriptor = Object.getOwnPropertyDescriptor(source, sym);
783
- if (descriptor.enumerable) {
784
- descriptors[sym] = descriptor;
785
- }
786
- });
787
- Object.defineProperties(target, descriptors);
788
- });
789
- return target;
790
- }
791
-
792
796
  /**
793
797
  * @param {CSN.Query} mainQuery
794
798
  * @param {queryCallback|queryCallback[]} queryCallback
@@ -896,12 +900,10 @@ function hasAnnotationValue(artifact, annotationName, expected = true, caseInsen
896
900
  * @param {ODataOptions} options EDM specific options
897
901
  */
898
902
  function isEdmPropertyRendered(elementCsn, options) {
899
- if(options.toOdata)
900
- options = options.toOdata;
901
903
  // FKs are rendered in
902
904
  // V2/V4 flat: always on
903
905
  // V4 struct: on/off
904
- const renderForeignKey = (options.version === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
906
+ const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
905
907
  const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
906
908
  const isNavigable = elementCsn.target ?
907
909
  (elementCsn['@odata.navigable'] === undefined ||
@@ -924,16 +926,19 @@ function isEdmPropertyRendered(elementCsn, options) {
924
926
  * - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
925
927
  * - For the 'quoted' naming mode, this means correctly replacing some '.' with '_'.
926
928
  *
927
- * If the old function signature is used - with a namespace as the third argument - the result might be wrong,
928
- * since the '.' -> '_' conversion for quoted/hdbcds is missing.
929
+ * The above rules might differ for different SQL dialects.
930
+ * Exceptions will be listed below.
929
931
  *
930
932
  * @param {string} artifactName The fully qualified name of the artifact
931
- * @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
932
- * @param {CSN.Model|string|undefined} csn
933
+ * @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
934
+ * @param {CSN.Model} csn
935
+ * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
933
936
  * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
934
937
  */
935
- function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
936
- if(csn && typeof csn === 'object' && csn.definitions)
938
+ // eslint-disable-next-line no-unused-vars
939
+ function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn, sqlDialect='plain') {
940
+ if(csn && typeof csn === 'object' && csn.definitions) {
941
+ isValidMappingDialectCombi(sqlDialect, sqlMapping)
937
942
  if (sqlMapping === 'quoted' || sqlMapping === 'hdbcds') {
938
943
  return getResultingName(csn, sqlMapping, artifactName);
939
944
  }
@@ -942,24 +947,8 @@ function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
942
947
  } else {
943
948
  throw new Error('Unknown naming mode: ' + sqlMapping);
944
949
  }
945
- else {
946
- console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
947
- if (sqlMapping === 'hdbcds') {
948
- if (csn) {
949
- const namespace = String(csn);
950
- return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
951
- }
952
- return artifactName;
953
- }
954
- else if (sqlMapping === 'plain') {
955
- return artifactName.replace(/\./g, '_').toUpperCase();
956
- }
957
- else if (sqlMapping === 'quoted') {
958
- return artifactName;
959
- }
960
- else {
961
- throw new Error('Unknown naming mode: ' + sqlMapping);
962
- }
950
+ } else {
951
+ throw new Error('A valid CSN model is required to correctly calculate the database name of an artifact.');
963
952
  }
964
953
  }
965
954
 
@@ -1042,6 +1031,12 @@ function getUnderscoredName(startIndex, parts, csn) {
1042
1031
  return null;
1043
1032
  }
1044
1033
 
1034
+ function isValidMappingDialectCombi(sqlDialect, sqlMapping) {
1035
+ if(sqlMapping === 'hdbcds' && sqlDialect !== 'hana')
1036
+ throw new Error(`sqlMapping "hdbcds" must only be used with sqlDialect "hana" - found: ${sqlDialect}`);
1037
+ return true;
1038
+ }
1039
+
1045
1040
 
1046
1041
  /**
1047
1042
  * Return the resulting database element name for 'elemName', depending on the current
@@ -1051,11 +1046,17 @@ function getUnderscoredName(startIndex, parts, csn) {
1051
1046
  * - For the 'quoted' naming mode, it means converting all '.' to '_'.
1052
1047
  * No other naming modes are accepted!
1053
1048
  *
1049
+ * The above rules might differ for different SQL dialects.
1050
+ * Exceptions will be listed below.
1051
+ *
1054
1052
  * @param {string} elemName The name of the element
1055
- * @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
1053
+ * @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
1054
+ * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
1056
1055
  * @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
1057
1056
  */
1058
- function getElementDatabaseNameOf(elemName, sqlMapping) {
1057
+ // eslint-disable-next-line no-unused-vars
1058
+ function getElementDatabaseNameOf(elemName, sqlMapping, sqlDialect='plain') {
1059
+ isValidMappingDialectCombi(sqlMapping, sqlDialect)
1059
1060
  if (sqlMapping === 'hdbcds') {
1060
1061
  return elemName;
1061
1062
  }
@@ -1170,85 +1171,6 @@ function getNormalizedQuery(art) {
1170
1171
  return art;
1171
1172
  }
1172
1173
 
1173
- /**
1174
- * Merge multiple 'options' objects (from right to left, i.e. rightmost wins). Structured option values are
1175
- * merged deeply. Structured option value from the right may override corresponding bool options on the left,
1176
- * but no other combination of struct/scalar values is allowed. Array options are not merged, i.e. their
1177
- * content is treated like scalars.
1178
- * Returns a new options object.
1179
- *
1180
- * @param {...CSN.Options} optionsObjects
1181
- * @return {CSN.Options}
1182
- */
1183
- function mergeOptions(...optionsObjects) {
1184
- let result = {};
1185
- for (const options of optionsObjects) {
1186
- if (options)
1187
- result = mergeTwo(result, options, 'options');
1188
- }
1189
-
1190
- // Reverse the array to ensure that the rightmost option has priority
1191
- const reversedOptions = [...optionsObjects].reverse(); // de-structure and create a new array, so reverse doesn't impact optionsObject
1192
- const msgOptions = reversedOptions.find(opt => opt && Array.isArray(opt.messages));
1193
- if (msgOptions) {
1194
- result.messages = msgOptions.messages;
1195
- }
1196
-
1197
- return result;
1198
-
1199
- // Recursively used for scalars, too
1200
- function mergeTwo(left, right, name) {
1201
- let intermediateResult;
1202
- // Copy left as far as required
1203
- if (Array.isArray(left)) {
1204
- // Shallow-copy left array
1205
- intermediateResult = left.slice();
1206
- } else if (isObject(left)) {
1207
- // Deep-copy left object (unless empty)
1208
- intermediateResult = Object.keys(left).length ? mergeTwo({}, left, name) : {};
1209
- } else {
1210
- // Just use left scalar
1211
- intermediateResult = left;
1212
- }
1213
- // Check against improper overwriting
1214
- if (isObject(left) && !Array.isArray(left) && (Array.isArray(right) || isScalar(right))) {
1215
- throw new ModelError(`Cannot overwrite structured option "${name}" with array or scalar value`);
1216
- }
1217
- if ((isScalar(left) && typeof left !== 'boolean' || Array.isArray(left)) && isObject(right) && !Array.isArray(right)) {
1218
- throw new ModelError(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
1219
- }
1220
-
1221
- // Copy or overwrite properties from right to left
1222
- if (Array.isArray(right)) {
1223
- // Shallow-copy right array
1224
- intermediateResult = right.slice();
1225
- } else if (isObject(right)) {
1226
- // Object overwrites undefined, scalars and arrays
1227
- if (intermediateResult === undefined || isScalar(intermediateResult) || Array.isArray(intermediateResult)) {
1228
- intermediateResult = {};
1229
- }
1230
- // Deep-copy right object into result
1231
- for (let key of Object.keys(right)) {
1232
- intermediateResult[key] = mergeTwo(intermediateResult[key], right[key], `${name}.${key}`);
1233
- }
1234
- } else {
1235
- // Right scalar wins (unless undefined)
1236
- intermediateResult = (right !== undefined) ? right : intermediateResult;
1237
- }
1238
- return intermediateResult;
1239
- }
1240
-
1241
- // Return true if 'o' is a non-null object or array
1242
- function isObject(o) {
1243
- return typeof o === 'object' && o !== null
1244
- }
1245
-
1246
- // Return true if 'o' is a non-undefined scalar
1247
- function isScalar(o) {
1248
- return o !== undefined && !isObject(o);
1249
- }
1250
- }
1251
-
1252
1174
  /**
1253
1175
  * If the artifact with the name given is part of a context (or multiple), return the top-most context.
1254
1176
  * Else, return the artifact itself. Namespaces are not of concern here.
@@ -1300,29 +1222,6 @@ function getLastPartOfRef(ref) {
1300
1222
  return getLastPartOf(lastPathStep.id || lastPathStep);
1301
1223
  }
1302
1224
 
1303
- // Return the name of the parent artifact of the artifact 'name' or
1304
- // '' if there is no parent.
1305
- function getParentNameOf(name) {
1306
- return name.substring(0, name.lastIndexOf('.'));
1307
- }
1308
-
1309
- // Return an array of parent names of 'name' (recursing into grand-parents)
1310
- // Examples:
1311
- // 'foo.bar.wiz' => [ 'foo.bar', 'foo' ]
1312
- // 'foo' => []
1313
- // 'foo::bar.wiz' => 'foo::bar'
1314
- // 'foo::bar' => []
1315
- function getParentNamesOf(name) {
1316
- let remainder = name.slice(0, -getLastPartOf(name).length);
1317
- if (remainder.endsWith('.')) {
1318
- let parentName = remainder.slice(0, -1);
1319
- return [parentName, ...getParentNamesOf(parentName)];
1320
- } else {
1321
- return [];
1322
- }
1323
- }
1324
-
1325
-
1326
1225
  /**
1327
1226
  * Copy all annotations from 'fromNode' to 'toNode'.
1328
1227
  *
@@ -1373,49 +1272,53 @@ function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
1373
1272
  }
1374
1273
 
1375
1274
  /**
1376
- * Applies annotations from `csn.extensions` to definitions, i.e. top-level artifacts.
1377
- * Does _not_ apply element/param/action/... annotations.
1275
+ * Applies annotations from `csn.extensions` to definitions and their elements.
1276
+ *
1378
1277
  * `config.filter` can be used to only copy annotations for those definitions,
1379
1278
  * for which the filter returns true.
1380
1279
  *
1280
+ * @todo Does _not_ apply param/action/... annotations.
1281
+ *
1381
1282
  * @param {CSN.Model} csn
1382
1283
  * @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
1383
1284
  */
1384
- function applyDefinitionAnnotationsFromExtensions(csn, config) {
1285
+ function applyAnnotationsFromExtensions(csn, config) {
1385
1286
  if (!csn.extensions)
1386
1287
  return;
1387
1288
 
1388
1289
  const filter = config.filter || ((_name) => true);
1389
1290
  for (const ext of csn.extensions) {
1390
1291
  const name = ext.annotate || ext.extend;
1391
- if (name && csn.definitions[name] && filter(name)) {
1392
- copyAnnotationsAndDoc(ext, csn.definitions[name], config.overwrite);
1292
+ const def = csn.definitions[name];
1293
+ if (name && def && filter(name)) {
1294
+ copyAnnotationsAndDoc(ext, def, config.overwrite);
1295
+ applyAnnotationsToElements(ext, def);
1393
1296
  }
1394
1297
  }
1395
- }
1396
1298
 
1397
- function isAspect(node) {
1398
- return node && node.kind === 'aspect';
1399
- }
1299
+ function applyAnnotationsToElements(ext, def) {
1300
+ // Only the definition is arrayed but the extension is not since
1301
+ // `items` is not expected in `extensions` by the CSN frontend and not
1302
+ // generated by the CDL parser for `annotate E:arrayed.elem`.
1303
+ if (def.items)
1304
+ def = def.items;
1400
1305
 
1401
- // For each property named 'path' in 'node' (recursively), call callback(path, node)
1402
- function forEachPath(node, callback) {
1403
- if (node === null || typeof node !== 'object') {
1404
- // Primitive node
1405
- return;
1406
- }
1407
- for (let name in node) {
1408
- if (!Object.hasOwnProperty.call( node, name ))
1409
- continue;
1410
- // If path found within a non-dictionary, call callback
1411
- if (name === 'path' && Object.getPrototypeOf(node)) {
1412
- callback(node.path, node);
1413
- }
1414
- // Descend recursively
1415
- forEachPath(node[name], callback);
1306
+ if (!ext.elements || !def.elements)
1307
+ return;
1308
+
1309
+ forEach(ext.elements, (key, sourceElem) => {
1310
+ const targetElem = def.elements[key];
1311
+ if (targetElem) {
1312
+ copyAnnotationsAndDoc(sourceElem, targetElem, config.overwrite);
1313
+ applyAnnotationsToElements(sourceElem, targetElem);
1314
+ }
1315
+ });
1416
1316
  }
1417
1317
  }
1418
1318
 
1319
+ function isAspect(node) {
1320
+ return node && node.kind === 'aspect';
1321
+ }
1419
1322
 
1420
1323
  /**
1421
1324
  * Return true if the artifact has a valid, truthy persistence.exists/skip annotation
@@ -1492,16 +1395,6 @@ function getServiceNames(csn) {
1492
1395
  return result;
1493
1396
  }
1494
1397
 
1495
- /**
1496
- * Check whether the artifact is @cds.persistence.skip
1497
- *
1498
- * @param {CSN.Artifact} artifact
1499
- * @returns {Boolean}
1500
- */
1501
- function isSkipped(artifact) {
1502
- return hasAnnotationValue(artifact, '@cds.persistence.skip', true)
1503
- }
1504
-
1505
1398
  /**
1506
1399
  * Walk path in the CSN and return the result.
1507
1400
  *
@@ -1589,8 +1482,7 @@ module.exports = {
1589
1482
  cloneCsnNonDict,
1590
1483
  cloneCsnDictionary,
1591
1484
  isBuiltinType,
1592
- assignAll,
1593
- applyDefinitionAnnotationsFromExtensions,
1485
+ applyAnnotationsFromExtensions,
1594
1486
  forEachGeneric,
1595
1487
  forEachDefinition,
1596
1488
  forEachMember,
@@ -1610,21 +1502,16 @@ module.exports = {
1610
1502
  isPersistedOnDatabase,
1611
1503
  generatedByCompilerVersion,
1612
1504
  getNormalizedQuery,
1613
- mergeOptions,
1614
1505
  getRootArtifactName,
1615
1506
  getLastPartOfRef,
1616
- getParentNamesOf,
1617
- getParentNameOf,
1618
1507
  getLastPartOf,
1619
1508
  copyAnnotations,
1620
1509
  copyAnnotationsAndDoc,
1621
1510
  isAspect,
1622
- forEachPath,
1623
1511
  hasValidSkipOrExists,
1624
1512
  getNamespace,
1625
1513
  sortCsnDefinitionsForTests,
1626
1514
  getServiceNames,
1627
- isSkipped,
1628
1515
  walkCsnPath,
1629
1516
  getVariableReplacement,
1630
1517
  implicitAs,