@sap/cds-compiler 3.4.2 → 3.4.4

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 (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/cdsc.js +3 -4
  3. package/bin/cdshi.js +19 -6
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/lib/api/main.js +6 -3
  6. package/lib/base/message-registry.js +60 -37
  7. package/lib/base/messages.js +7 -3
  8. package/lib/checks/.eslintrc.json +2 -0
  9. package/lib/compiler/.eslintrc.json +4 -1
  10. package/lib/compiler/assert-consistency.js +8 -6
  11. package/lib/compiler/builtins.js +13 -13
  12. package/lib/compiler/checks.js +50 -33
  13. package/lib/compiler/define.js +9 -6
  14. package/lib/compiler/extend.js +71 -45
  15. package/lib/compiler/finalize-parse-cdl.js +3 -3
  16. package/lib/compiler/populate.js +16 -5
  17. package/lib/compiler/resolve.js +2 -15
  18. package/lib/compiler/shared.js +4 -4
  19. package/lib/compiler/utils.js +14 -0
  20. package/lib/edm/annotations/genericTranslation.js +68 -56
  21. package/lib/edm/csn2edm.js +214 -174
  22. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  23. package/lib/edm/edmInboundChecks.js +2 -2
  24. package/lib/edm/edmPreprocessor.js +1 -1
  25. package/lib/edm/edmUtils.js +3 -3
  26. package/lib/gen/Dictionary.json +176 -8
  27. package/lib/gen/language.checksum +1 -1
  28. package/lib/gen/language.interp +2 -1
  29. package/lib/gen/languageParser.js +4776 -4513
  30. package/lib/json/from-csn.js +21 -16
  31. package/lib/json/to-csn.js +37 -41
  32. package/lib/language/.eslintrc.json +4 -1
  33. package/lib/language/antlrParser.js +5 -2
  34. package/lib/language/docCommentParser.js +6 -6
  35. package/lib/language/errorStrategy.js +43 -23
  36. package/lib/language/genericAntlrParser.js +54 -95
  37. package/lib/language/language.g4 +92 -66
  38. package/lib/language/multiLineStringParser.js +2 -2
  39. package/lib/language/textUtils.js +2 -2
  40. package/lib/model/csnRefs.js +5 -0
  41. package/lib/modelCompare/compare.js +2 -2
  42. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  43. package/lib/modelCompare/utils/filter.js +99 -0
  44. package/lib/render/.eslintrc.json +1 -0
  45. package/lib/render/toCdl.js +96 -127
  46. package/lib/render/toHdbcds.js +38 -35
  47. package/lib/render/toSql.js +75 -161
  48. package/lib/render/utils/common.js +133 -83
  49. package/lib/render/utils/delta.js +227 -0
  50. package/lib/transform/db/.eslintrc.json +2 -0
  51. package/lib/transform/draft/.eslintrc.json +1 -35
  52. package/lib/transform/forOdataNew.js +26 -19
  53. package/lib/transform/localized.js +9 -8
  54. package/lib/transform/odata/typesExposure.js +26 -4
  55. package/lib/transform/transformUtilsNew.js +15 -8
  56. package/package.json +2 -3
  57. package/lib/modelCompare/filter.js +0 -83
@@ -23,9 +23,10 @@ const { pathName } = require('./utils');
23
23
 
24
24
  function check( model ) { // = XSN
25
25
  const {
26
- error, warning, message,
26
+ error, warning, message, info,
27
27
  } = model.$messageFunctions;
28
28
  forEachDefinition( model, checkArtifact );
29
+ forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
29
30
  checkSapCommonLocale( model, model.$messageFunctions );
30
31
  return;
31
32
 
@@ -36,6 +37,11 @@ function check( model ) { // = XSN
36
37
  art.$queries.forEach( checkQuery );
37
38
  }
38
39
 
40
+ function checkAnnotationDefinition( art ) {
41
+ checkEnumType( art );
42
+ // TODO: Should we check elements similar to definition-elements as well?
43
+ }
44
+
39
45
  function checkGenericConstruct( art ) {
40
46
  checkName( art );
41
47
  if (model.vocabularies) {
@@ -80,14 +86,15 @@ function check( model ) { // = XSN
80
86
  }
81
87
  }
82
88
 
83
- function checkLocalizedElement(elem) {
89
+ function checkLocalizedElement( elem ) {
84
90
  // if it is directly a localized element
85
91
  if (elem.localized && elem.localized.val) {
86
92
  const type = elem._effectiveType;
87
93
  // See discussion issue #6520: should we allow all scalar types?
88
94
  if (!type || !type.builtin || type.category !== 'string') {
89
- warning(null, [ elem.type?.location, elem ], { keyword: 'localized' },
90
- 'Keyword $(KEYWORD) should only be used in combination with string types');
95
+ info('ref-expecting-localized-string', [ elem.type?.location, elem ],
96
+ { keyword: 'localized' },
97
+ 'Expecting a string type in combination with keyword $(KEYWORD)');
91
98
  }
92
99
  }
93
100
  // "key" keyword at localized element in SELECT list.
@@ -151,7 +158,7 @@ function check( model ) { // = XSN
151
158
  *
152
159
  * @param {XSN.Artifact} enumNode
153
160
  */
154
- function checkEnum(enumNode) {
161
+ function checkEnum( enumNode ) {
155
162
  if (!enumNode.value)
156
163
  return;
157
164
 
@@ -167,7 +174,7 @@ function check( model ) { // = XSN
167
174
  }
168
175
  }
169
176
 
170
- function checkEnumType(enumNode) {
177
+ function checkEnumType( enumNode ) {
171
178
  // Either the type is an enum or an arrayed enum. We are only interested in
172
179
  // the enum and don't care whether the enum is arrayed.
173
180
  enumNode = enumNode.enum ? enumNode : enumNode.items;
@@ -222,7 +229,7 @@ function check( model ) { // = XSN
222
229
  *
223
230
  * @param {XSN.Definition} enumNode
224
231
  */
225
- function checkEnumValue(enumNode) {
232
+ function checkEnumValue( enumNode ) {
226
233
  const type = enumNode.type && enumNode.type._artifact &&
227
234
  enumNode.type._artifact._effectiveType;
228
235
  if (!enumNode.enum || !type || !type.builtin)
@@ -293,7 +300,7 @@ function check( model ) { // = XSN
293
300
  *
294
301
  * @param {XSN.Artifact} element
295
302
  */
296
- function checkLocalizedSubElement(element) {
303
+ function checkLocalizedSubElement( element ) {
297
304
  if (element._parent.kind !== 'element')
298
305
  return;
299
306
 
@@ -310,7 +317,7 @@ function check( model ) { // = XSN
310
317
  return;
311
318
 
312
319
  // TODO: Recursive check
313
- function isTypeLocalized(type) {
320
+ function isTypeLocalized( type ) {
314
321
  return (type && type.localized && type.localized.val);
315
322
  }
316
323
  }
@@ -321,7 +328,7 @@ function check( model ) { // = XSN
321
328
  *
322
329
  * @param {any} element Element to check recursively
323
330
  */
324
- function checkForUnmanagedAssociations(element, keyObj) {
331
+ function checkForUnmanagedAssociations( element, keyObj ) {
325
332
  if (element.targetAspect) {
326
333
  // TODO: bad location / message
327
334
  message('composition-as-key', [ keyObj.location, element ], {},
@@ -342,7 +349,7 @@ function check( model ) { // = XSN
342
349
 
343
350
  // Check that min and max cardinalities of 'elem' in 'art' have legal values
344
351
  // TODO: move to define.js or parsers
345
- function checkCardinality(elem) {
352
+ function checkCardinality( elem ) {
346
353
  if (!elem.cardinality)
347
354
  return;
348
355
 
@@ -444,7 +451,7 @@ function check( model ) { // = XSN
444
451
 
445
452
  // Traverses 'node' recursively and applies 'checkExpression' to all expressions
446
453
  // found within paths (e.g. filters, parameters, ...)
447
- function checkExpressionsInPaths(node) {
454
+ function checkExpressionsInPaths( node ) {
448
455
  foreachPath(node, (path) => {
449
456
  for (const pathStep of path) {
450
457
  if (pathStep.where)
@@ -458,7 +465,7 @@ function check( model ) { // = XSN
458
465
  });
459
466
  }
460
467
 
461
- function checkAssociation(elem) {
468
+ function checkAssociation( elem ) {
462
469
  // TODO: yes, a check similar to this could make it into the compiler)
463
470
  // when virtual element is part of association
464
471
  if (elem.foreignKeys) {
@@ -474,19 +481,19 @@ function check( model ) { // = XSN
474
481
  checkAssociationCondition(elem, elem.on);
475
482
  }
476
483
 
477
- function checkAssociationCondition(elem, onCond) {
484
+ function checkAssociationCondition( elem, onCond ) {
478
485
  if (Array.isArray(onCond)) // condition in brackets results an array
479
486
  onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
480
487
  else
481
488
  checkAssociationConditionArgs(elem, onCond.args, onCond.op);
482
489
  }
483
490
 
484
- function checkAssociationConditionArgs(elem, args, op) {
491
+ function checkAssociationConditionArgs( elem, args, op ) {
485
492
  if (args)
486
493
  args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
487
494
  }
488
495
 
489
- function checkAssociationOnCondArg(elem, arg, op) {
496
+ function checkAssociationOnCondArg( elem, arg, op ) {
490
497
  if (Array.isArray(arg)) {
491
498
  arg.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
492
499
  }
@@ -503,7 +510,7 @@ function check( model ) { // = XSN
503
510
  // integration into name resolution - did the first step.
504
511
  // It is also incomplete, as associations in structures are not checked.
505
512
  // Additionally, `$self.assoc` references are also not found.
506
- function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
513
+ function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
507
514
  if (!arg.path)
508
515
  return;
509
516
  const path0 = arg.path[0];
@@ -545,7 +552,7 @@ function check( model ) { // = XSN
545
552
  *
546
553
  * @param {XSN.Artifact} artifact
547
554
  */
548
- function checkTypeStructure(artifact) {
555
+ function checkTypeStructure( artifact ) {
549
556
  // Just a basic check. We do not check that the inner structure of `items`
550
557
  // is the same as the type but only that all are arrayed or structured.
551
558
  if (artifact.type && artifact.type._artifact) {
@@ -575,7 +582,7 @@ function check( model ) { // = XSN
575
582
  * @param {Boolean} allowAssocTail
576
583
  * @returns {void}
577
584
  */
578
- function checkExpression(xpr, allowAssocTail = false) {
585
+ function checkExpression( xpr, allowAssocTail = false ) {
579
586
  // Since the checks for tree-like and token-stream expressions differ,
580
587
  // check here what kind of expression we are looking at
581
588
  if (xpr.op && xpr.op.val === 'xpr')
@@ -590,7 +597,7 @@ function check( model ) { // = XSN
590
597
  * @param {any} arg Argument to check (part of an expression)
591
598
  * @returns {Boolean}
592
599
  */
593
- function isVirtualElement(arg) {
600
+ function isVirtualElement( arg ) {
594
601
  return arg.path &&
595
602
  arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true &&
596
603
  arg._artifact.kind && arg._artifact.kind === 'element';
@@ -602,7 +609,7 @@ function check( model ) { // = XSN
602
609
  * @param {any} xpr The expression to check
603
610
  * @returns {void}
604
611
  */
605
- function checkTokenStreamExpression(xpr, allowAssocTail) {
612
+ function checkTokenStreamExpression( xpr, allowAssocTail ) {
606
613
  const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
607
614
  // Check for illegal argument usage within the expression
608
615
  for (const arg of args) {
@@ -621,7 +628,7 @@ function check( model ) { // = XSN
621
628
  * @param {any} xpr The expression to check
622
629
  * @returns {void}
623
630
  */
624
- function checkTreeLikeExpression(xpr, allowAssocTail) {
631
+ function checkTreeLikeExpression( xpr, allowAssocTail ) {
625
632
  // No further checks regarding associations and $self required if this is a
626
633
  // backlink-like expression (a comparison of $self with an assoc)
627
634
  if (isBinaryDollarSelfComparisonWithAssoc(xpr))
@@ -655,7 +662,7 @@ function check( model ) { // = XSN
655
662
  }
656
663
  }
657
664
  // Return true if 'arg' is an expression argument of type association or composition
658
- function isAssociationOperand(arg) {
665
+ function isAssociationOperand( arg ) {
659
666
  if (!arg.path) {
660
667
  // Not a path, hence not an association (literal, expression, function, whatever ...)
661
668
  return false;
@@ -666,7 +673,7 @@ function check( model ) { // = XSN
666
673
  }
667
674
 
668
675
  // Return true if 'arg' is an expression argument denoting "$self" || "$projection"
669
- function isDollarSelfOrProjectionOperand(arg) {
676
+ function isDollarSelfOrProjectionOperand( arg ) {
670
677
  return arg.path && arg.path.length === 1 &&
671
678
  (arg.path[0].id === '$self' || arg.path[0].id === '$projection');
672
679
  }
@@ -677,7 +684,7 @@ function check( model ) { // = XSN
677
684
  * @param {any} xpr The expression to check
678
685
  * @returns {Boolean}
679
686
  */
680
- function isBinaryDollarSelfComparisonWithAssoc(xpr) {
687
+ function isBinaryDollarSelfComparisonWithAssoc( xpr ) {
681
688
  // Must be an expression with arguments
682
689
  if (!xpr.op || !xpr.args)
683
690
  return false;
@@ -735,7 +742,7 @@ function check( model ) { // = XSN
735
742
  // Perform checks for annotation assignment 'anno', using corresponding annotation declaration,
736
743
  // made of 'annoDecl' (artifact or undefined) and 'elementDecl' (annotation or element
737
744
  // or undefined). Report errors on 'options.messages.
738
- function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) {
745
+ function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) {
739
746
  // Nothing to check if no actual annotation declaration was found
740
747
  if (!annoDecl || annoDecl.artifacts && !elementDecl)
741
748
  return;
@@ -779,7 +786,7 @@ function check( model ) { // = XSN
779
786
  // Check that annotation assignment 'value' (having 'path or 'literal' and
780
787
  // 'val') is potentially assignable to element 'element'. Complain on 'loc'
781
788
  // if not
782
- function checkValueAssignableTo(value, elementDecl, art) {
789
+ function checkValueAssignableTo( value, elementDecl, art ) {
783
790
  // FIXME: We currently do not have any element declaration that could match
784
791
  // a 'path' value, so we simply leave those alone
785
792
  if (value.path)
@@ -840,7 +847,7 @@ function check( model ) { // = XSN
840
847
  else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
841
848
  warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
842
849
  }
843
- else {
850
+ else if (!elementDecl._effectiveType.enum) {
844
851
  throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
845
852
  }
846
853
 
@@ -861,13 +868,23 @@ function check( model ) { // = XSN
861
868
  }
862
869
  else if (expectedEnum) {
863
870
  // Enum symbol not provided but expected
864
- if (!Object.keys(expectedEnum).some(symbol => expectedEnum[symbol].value.val === value.val)) {
871
+ const hasValidValue = Object.keys(expectedEnum)
872
+ .some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
873
+ if (!hasValidValue) {
865
874
  // ... and none of the valid enum symbols matches the value
866
875
  warning(null, loc, {}, 'An enum value is required here');
867
876
  }
868
877
  }
869
878
  }
870
879
 
880
+ function getEnumValue( enumSymbol ) {
881
+ if (enumSymbol.value)
882
+ return enumSymbol.value?.val;
883
+ if (enumSymbol._effectiveType)
884
+ return enumSymbol._effectiveType?.value?.val;
885
+ return null;
886
+ }
887
+
871
888
  // TODO: remove the following
872
889
 
873
890
  // Return the artifact (and possibly, its element) found by following 'path'
@@ -877,7 +894,7 @@ function check( model ) { // = XSN
877
894
  // represented by the full path (or 'undefined' if not found). Note that
878
895
  // only elements and artifacts are considered for path traversal (no actions,
879
896
  // functions, parameters etc.)
880
- function resolvePathFrom(path, from, result = {}) {
897
+ function resolvePathFrom( path, from, result = {} ) {
881
898
  // Keep last encountered artifacts
882
899
  if (from && !from._main)
883
900
  result.artifact = from;
@@ -896,11 +913,11 @@ function check( model ) { // = XSN
896
913
 
897
914
  // Return the absolute name of the final type of 'node'. May return 'undefined' for
898
915
  // anonymous types
899
- function getFinalTypeNameOf(node) {
916
+ function getFinalTypeNameOf( node ) {
900
917
  let type = node._effectiveType;
901
918
  if (type.type)
902
919
  type = type.type._artifact;
903
- return type && type.name && type.name.absolute;
920
+ return type?.name?.absolute;
904
921
  }
905
922
  }
906
923
 
@@ -928,7 +945,7 @@ function checkSapCommonLocale( model, messageFunctions ) {
928
945
  // For each property named 'path' in 'node' (recursively), call callback(path, node)
929
946
  //
930
947
  // TODO: remove - this is not a good way to traverse expressions
931
- function foreachPath(node, callback) {
948
+ function foreachPath( node, callback ) {
932
949
  if (node === null || typeof node !== 'object') {
933
950
  // Primitive node
934
951
  return;
@@ -672,14 +672,17 @@ function define( model ) {
672
672
  setLink( col, '_block', parent._block );
673
673
  initAnnotations( col, parent._block );
674
674
  if (col.inline) { // `@anno elem.{ * }` does not work
675
- if (col.doc)
676
- warning( 'syntax-ignoring-anno', [ col.doc.location, col ], { '#': 'doc' } );
677
-
675
+ if (col.doc) {
676
+ warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
677
+ { '#': 'doc', code: '.{ ‹inline› }' } );
678
+ }
678
679
  // col.$annotations no available for CSN input, have to search.
679
680
  // Warning about first annotation should be enough to avoid spam.
680
681
  const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
681
- if (firstAnno)
682
- warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ] );
682
+ if (firstAnno) {
683
+ warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
684
+ { code: '.{ ‹inline› }' } );
685
+ }
683
686
  }
684
687
  // TODO: allow sub queries? at least in top-level expand without parallel ref
685
688
  if (columns)
@@ -738,7 +741,7 @@ function define( model ) {
738
741
  *
739
742
  * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
740
743
  */
741
- function approveExistsInChildren(exprOrPathElement) {
744
+ function approveExistsInChildren( exprOrPathElement ) {
742
745
  if (!exprOrPathElement) // may be null in case of parse error
743
746
  return;
744
747
  if (exprOrPathElement.$expected === 'exists')
@@ -53,10 +53,8 @@ function extend( model ) {
53
53
 
54
54
  applyExtensions();
55
55
 
56
- const commonLanguagesEntity = options.addTextsLanguageAssoc &&
57
- model.definitions['sap.common.Languages'];
58
- const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
59
- commonLanguagesEntity.elements.code);
56
+ const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
57
+
60
58
  Object.keys( model.definitions ).forEach( processArtifact );
61
59
 
62
60
  lateExtensions( false );
@@ -107,7 +105,7 @@ function extend( model ) {
107
105
  const processed = new WeakSet();
108
106
  forEachDefinition(model, processCompositionPersistence);
109
107
 
110
- function processCompositionPersistence(def) {
108
+ function processCompositionPersistence( def ) {
111
109
  if (def.$inferred === 'composition-entity' && !processed.has(def)) {
112
110
  if (def._parent)
113
111
  processCompositionPersistence(def._parent);
@@ -207,7 +205,7 @@ function extend( model ) {
207
205
  * @param {XSN.Definition} art
208
206
  * @param {boolean|'gen'} [noIncludes=false]
209
207
  */
210
- function extendArtifact( extensions, art, noIncludes = false) {
208
+ function extendArtifact( extensions, art, noIncludes = false ) {
211
209
  if (!noIncludes && !(canApplyIncludes( art, art ) &&
212
210
  extensions.every( ext => canApplyIncludes(ext, art) )))
213
211
  return false;
@@ -332,7 +330,7 @@ function extend( model ) {
332
330
  *
333
331
  * @param art
334
332
  */
335
- function applyTypeExtensions(art) {
333
+ function applyTypeExtensions( art ) {
336
334
  /**
337
335
  * Contains the previous extension for each property that was applied
338
336
  * successfully.
@@ -742,7 +740,7 @@ function extend( model ) {
742
740
 
743
741
  if (isKey && isLocalized) { // key with localized is wrong - ignore localized
744
742
  const errpos = elem.localized || elem.type || elem.name;
745
- warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
743
+ warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
746
744
  'Keyword $(KEYWORD) is ignored for primary keys' );
747
745
  }
748
746
  }
@@ -750,7 +748,7 @@ function extend( model ) {
750
748
  return false;
751
749
 
752
750
  if (!keys) {
753
- warning( null, [ art.name.location, art ], {},
751
+ warning( 'def-expecting-key', [ art.name.location, art ], {},
754
752
  'No texts entity can be created when no key element exists' );
755
753
  return false;
756
754
  }
@@ -800,6 +798,48 @@ function extend( model ) {
800
798
  * @param {boolean} fioriEnabled
801
799
  */
802
800
  function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
801
+ const art = createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled );
802
+
803
+ const { location } = base.name;
804
+ const { locale } = art.elements;
805
+
806
+ // assertUnique array value, first entry is 'locale'
807
+ const assertUniqueValue = [ {
808
+ path: [ { id: locale.name.id, location: locale.location } ],
809
+ location: locale.location,
810
+ } ];
811
+
812
+ for (const orig of textElems) {
813
+ const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
814
+ if (orig.key && orig.key.val) {
815
+ // elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
816
+ // TODO: the previous would be better, but currently not supported in toCDL
817
+ if (!fioriEnabled) {
818
+ elem.key = { val: true, $inferred: 'localized', location };
819
+ // If the propagated elements remain key (that is not fiori.draft.enabled)
820
+ // they should be omitted from OData containment EDM
821
+ annotateWith( elem, '@odata.containment.ignore', location );
822
+ }
823
+ else {
824
+ // add the former key paths to the unique constraint
825
+ assertUniqueValue.push({
826
+ path: [ { id: orig.name.id, location: orig.location } ],
827
+ location: orig.location,
828
+ });
829
+ }
830
+ }
831
+ if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
832
+ const localized = orig.localized || orig.type || orig.name;
833
+ elem.localized = { val: null, $inferred: 'localized', location: localized.location };
834
+ }
835
+ }
836
+ if (fioriEnabled)
837
+ annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
838
+
839
+ return art;
840
+ }
841
+
842
+ function createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled ) {
803
843
  const elements = Object.create(null);
804
844
  const { location } = base.name;
805
845
  const art = {
@@ -811,8 +851,7 @@ function extend( model ) {
811
851
  };
812
852
  // If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
813
853
  // If not, use the default `cds.String` with a length of 14.
814
- const hasLocaleType = model.definitions['sap.common.Locale'] &&
815
- model.definitions['sap.common.Locale'].kind === 'type';
854
+ const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
816
855
  const locale = {
817
856
  name: { location, id: 'locale' },
818
857
  kind: 'element',
@@ -863,39 +902,6 @@ function extend( model ) {
863
902
  model.definitions[absolute] = art;
864
903
  initArtifact( art );
865
904
 
866
- // assertUnique array value, first entry is 'locale'
867
- const assertUniqueValue = [ {
868
- path: [ { id: locale.name.id, location: locale.location } ],
869
- location: locale.location,
870
- } ];
871
-
872
- for (const orig of textElems) {
873
- const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
874
- if (orig.key && orig.key.val) {
875
- // elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
876
- // TODO: the previous would be better, but currently not supported in toCDL
877
- if (!fioriEnabled) {
878
- elem.key = { val: true, $inferred: 'localized', location };
879
- // If the propagated elements remain key (that is not fiori.draft.enabled)
880
- // they should be omitted from OData containment EDM
881
- annotateWith( elem, '@odata.containment.ignore', location );
882
- }
883
- else {
884
- // add the former key paths to the unique constraint
885
- assertUniqueValue.push({
886
- path: [ { id: orig.name.id, location: orig.location } ],
887
- location: orig.location,
888
- });
889
- }
890
- }
891
- if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
892
- const localized = orig.localized || orig.type || orig.name;
893
- elem.localized = { val: null, $inferred: 'localized', location: localized.location };
894
- }
895
- }
896
- if (fioriEnabled)
897
- annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
898
-
899
905
  return art;
900
906
  }
901
907
 
@@ -1268,7 +1274,7 @@ function compareAssignments( a, b ) {
1268
1274
  * @param {object} source
1269
1275
  * @param {CSN.Options} options
1270
1276
  */
1271
- function copyPersistenceAnnotations(target, source, options) {
1277
+ function copyPersistenceAnnotations( target, source, options ) {
1272
1278
  if (!source)
1273
1279
  return;
1274
1280
 
@@ -1325,4 +1331,24 @@ function storeTypeExtension( ext, art ) {
1325
1331
  art._extendType.push( ext );
1326
1332
  }
1327
1333
 
1334
+
1335
+ function checkTextsLanguageAssocOption( model, options ) {
1336
+ const languages = model.definitions['sap.common.Languages'];
1337
+ const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
1338
+
1339
+ if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
1340
+ const variant = !languages ? 'std' : 'code';
1341
+ const loc = model.definitions['sap.common.Languages']?.name?.location || null;
1342
+ model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
1343
+ '#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
1344
+ }, {
1345
+ std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
1346
+ code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
1347
+ });
1348
+ }
1349
+
1350
+ return !!commonLanguagesEntity;
1351
+ }
1352
+
1353
+
1328
1354
  module.exports = extend;
@@ -67,7 +67,7 @@ function finalizeParseCdl( model ) {
67
67
  * @param {*} artifact
68
68
  * @param {XSN.Artifact} main
69
69
  */
70
- function resolveTypesForParseCdl(artifact, main) {
70
+ function resolveTypesForParseCdl( artifact, main ) {
71
71
  if (!artifact || typeof artifact !== 'object')
72
72
  return;
73
73
 
@@ -160,7 +160,7 @@ function finalizeParseCdl( model ) {
160
160
  * @param {object} artWithType
161
161
  * @param {XSN.Artifact} user
162
162
  */
163
- function resolveTypeUnchecked(artWithType, user) {
163
+ function resolveTypeUnchecked( artWithType, user ) {
164
164
  const root = artWithType.type.path && artWithType.type.path[0];
165
165
  if (!root) // parse error
166
166
  return;
@@ -202,7 +202,7 @@ function finalizeParseCdl( model ) {
202
202
  }
203
203
  }
204
204
 
205
- function chooseAndReportDuplicateAnnotation(artifact, annoName) {
205
+ function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
206
206
  for (const anno of artifact[annoName])
207
207
  message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
208
208
 
@@ -45,6 +45,7 @@ const {
45
45
  dependsOn,
46
46
  traverseQueryPost,
47
47
  setExpandStatus,
48
+ setExpandStatusAnnotate,
48
49
  } = require('./utils');
49
50
 
50
51
  const $inferred = Symbol.for('cds.$inferred');
@@ -163,7 +164,6 @@ function populate( model ) {
163
164
  const chain = [];
164
165
  while (art && !('_effectiveType' in art) &&
165
166
  (art.type || art._origin || art.value?.path || art.value?.type) &&
166
- // TODO: really stop at art.enum? See #8942
167
167
  !art.target && !art.enum && !art.elements && !art.items) {
168
168
  chain.push( art );
169
169
  setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
@@ -192,7 +192,6 @@ function populate( model ) {
192
192
  // collect the "latest" cardinality (calculate lazily if necessary)
193
193
  let cardinality = art.cardinality ||
194
194
  art._effectiveType && (() => getCardinality( art._effectiveType ));
195
- let prev = art;
196
195
  for (const a of chain) {
197
196
  if (a.cardinality)
198
197
  cardinality = a.cardinality;
@@ -201,8 +200,10 @@ function populate( model ) {
201
200
  art.elements && expandElements( a, art ) ||
202
201
  art.items && expandItems( a, art ))
203
202
  art = a;
204
- else if (art.enum && expandEnum( a, prev ))
205
- prev = a; // do not set art - effective type is base
203
+
204
+ else if (art.enum && expandEnum( a, art ))
205
+ art = a;
206
+
206
207
  setLink( a, '_effectiveType', art );
207
208
  }
208
209
  }
@@ -469,11 +470,21 @@ function populate( model ) {
469
470
  'Element $(ID) is missing in specified elements' );
470
471
  }
471
472
  else {
473
+ let wasAnnotated = false;
472
474
  for (const prop in selem) {
473
475
  // just annotation assignments and doc comments for the moment
474
- if (prop.charAt(0) === '@' || prop === 'doc')
476
+ if (prop.charAt(0) === '@' || prop === 'doc') {
475
477
  ielem[prop] = selem[prop];
478
+ // required for gensrc mode of to-csn.js, otherwise the annotation
479
+ // may be lost during recompilation.
480
+ ielem[prop].$priority = 'annotate';
481
+ wasAnnotated = true;
482
+ }
476
483
  }
484
+
485
+ if (wasAnnotated)
486
+ setExpandStatusAnnotate(art, 'annotate');
487
+
477
488
  selem.$replacement = true;
478
489
  if (selem.elements) {
479
490
  setLink(ielem, 'elements$', selem.elements);
@@ -63,6 +63,7 @@ const {
63
63
  storeExtension,
64
64
  dependsOn,
65
65
  dependsOnSilent,
66
+ setExpandStatusAnnotate,
66
67
  testExpr,
67
68
  targetMaxNotOne,
68
69
  traverseQueryPost,
@@ -618,20 +619,6 @@ function resolve( model ) {
618
619
  }
619
620
  }
620
621
 
621
- function setExpandStatusAnnotate( elem, status ) {
622
- for (;;) {
623
- if (elem.$expand === status)
624
- return; // already set
625
- elem.$expand = status; // meaning: expanded, containing annos
626
- for (let line = elem.items; line; line = line.items)
627
- line.$expand = status; // to-csn just uses the innermost $expand
628
- if (!elem._main)
629
- return;
630
- elem = elem._parent;
631
- }
632
- }
633
-
634
-
635
622
  function expandParameters( action ) {
636
623
  // see also expandElements()
637
624
  if (!effectiveType( action ))
@@ -1291,7 +1278,7 @@ function resolve( model ) {
1291
1278
  }
1292
1279
  }
1293
1280
 
1294
- function resolveExpr( expr, expected, user, extDict, expandOrInline) {
1281
+ function resolveExpr( expr, expected, user, extDict, expandOrInline ) {
1295
1282
  // TODO: when we have rewritten the resolvePath functions,
1296
1283
  // define a traverseExpr() in ./utils.js
1297
1284
  // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`