@sap/cds-compiler 3.4.2 → 3.5.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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -20,7 +20,7 @@ const {
20
20
  setMemberParent,
21
21
  dependsOnSilent,
22
22
  augmentPath,
23
- splitIntoPath,
23
+ splitIntoPath, isDirectComposition,
24
24
  } = require('./utils');
25
25
  const layers = require('./moduleLayers');
26
26
  const { typeParameters } = require('./builtins');
@@ -53,10 +53,9 @@ 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
+ const useTextsAspects = checkTextsAspects();
58
+
60
59
  Object.keys( model.definitions ).forEach( processArtifact );
61
60
 
62
61
  lateExtensions( false );
@@ -107,7 +106,7 @@ function extend( model ) {
107
106
  const processed = new WeakSet();
108
107
  forEachDefinition(model, processCompositionPersistence);
109
108
 
110
- function processCompositionPersistence(def) {
109
+ function processCompositionPersistence( def ) {
111
110
  if (def.$inferred === 'composition-entity' && !processed.has(def)) {
112
111
  if (def._parent)
113
112
  processCompositionPersistence(def._parent);
@@ -207,7 +206,7 @@ function extend( model ) {
207
206
  * @param {XSN.Definition} art
208
207
  * @param {boolean|'gen'} [noIncludes=false]
209
208
  */
210
- function extendArtifact( extensions, art, noIncludes = false) {
209
+ function extendArtifact( extensions, art, noIncludes = false ) {
211
210
  if (!noIncludes && !(canApplyIncludes( art, art ) &&
212
211
  extensions.every( ext => canApplyIncludes(ext, art) )))
213
212
  return false;
@@ -295,6 +294,97 @@ function extend( model ) {
295
294
  });
296
295
  }
297
296
 
297
+ /**
298
+ * Check that special `sap.common.*` aspects for `.texts` entities are
299
+ * consistent with compiler expectations. Emits messages and returns
300
+ * false if the aspects are not valid.
301
+ *
302
+ * @return {boolean}
303
+ */
304
+ function checkTextsAspects() {
305
+ const textsAspect = model.definitions['sap.common.TextsAspect'];
306
+ const fioriTextsAspect = model.definitions['sap.common.FioriTextsAspect'];
307
+
308
+ let hasError = false;
309
+
310
+ if (textsAspect) {
311
+ const specialElements = { locale: { key: true } };
312
+ if (!checkTextsAspect(textsAspect, specialElements))
313
+ hasError = true;
314
+ }
315
+
316
+ if (fioriTextsAspect) {
317
+ const specialElements = { ID_texts: { key: true }, locale: { key: false } };
318
+ if (!checkTextsAspect(fioriTextsAspect, specialElements))
319
+ hasError = true;
320
+ }
321
+
322
+ return !hasError;
323
+ }
324
+
325
+ function checkTextsAspect( art, specialElements ) {
326
+ if (art.kind !== 'aspect' || !art.elements) {
327
+ error('def-invalid-texts-aspect', [ art.name.location, art ], { '#': 'no-aspect', art });
328
+ return false;
329
+ }
330
+
331
+ let hasError = false;
332
+ if (addTextsLanguageAssoc && art.elements.language) {
333
+ const lang = art.elements.language;
334
+ error('def-unexpected-element', [ lang.name.location, lang ],
335
+ { option: 'addTextsLanguageAssoc', art, name: 'language' },
336
+ // eslint-disable-next-line max-len
337
+ '$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
338
+ hasError = true;
339
+ }
340
+
341
+ for (const name in specialElements) {
342
+ const expected = specialElements[name];
343
+ const elem = art.elements[name];
344
+ if (!elem) {
345
+ error('def-invalid-texts-aspect', [ art.name.location, art ],
346
+ { '#': 'missing', art, name });
347
+ hasError = true;
348
+ }
349
+ else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
350
+ const loc = elem.key?.location || elem.name?.location || art.name.location;
351
+ error('def-invalid-texts-aspect', [ loc, elem ],
352
+ { '#': expected.key ? 'key' : 'no-key', art: elem });
353
+ hasError = true;
354
+ }
355
+ }
356
+
357
+ if (hasError) // avoid subsequent errors, if the special elements are already wrong
358
+ return false;
359
+
360
+ for (const name in art.elements) {
361
+ const elem = art.elements[name];
362
+ const include = elem.$inferred === 'include';
363
+ if (!specialElements[name] && elem.key) {
364
+ const loc = include ? elem.location : elem.key.location;
365
+ error( 'def-unexpected-key', [ loc, elem ],
366
+ { '#': !include ? 'std' : 'include', art } );
367
+ hasError = true;
368
+ }
369
+ else if (hasTruthyProp( elem, 'localized' )) {
370
+ // TODO: T:loc, i.e. "localized" from other type (needs resolver?)
371
+ // Not supported anyway, but important for recompilation (which fails correctly).
372
+ const loc = elem.localized?.location || elem.location;
373
+ error( 'def-unexpected-localized', [ loc, elem ],
374
+ { '#': !include ? 'std' : 'include', art } );
375
+ hasError = true;
376
+ }
377
+ else if (elem.targetAspect) {
378
+ error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ], { art },
379
+ '$(ART) can\'t have composition of aspects' );
380
+ hasError = true;
381
+ }
382
+ }
383
+
384
+ return !hasError;
385
+ }
386
+
387
+
298
388
  /**
299
389
  * Copy columns for EXTEND PROJECTION
300
390
  *
@@ -332,7 +422,7 @@ function extend( model ) {
332
422
  *
333
423
  * @param art
334
424
  */
335
- function applyTypeExtensions(art) {
425
+ function applyTypeExtensions( art ) {
336
426
  /**
337
427
  * Contains the previous extension for each property that was applied
338
428
  * successfully.
@@ -616,7 +706,7 @@ function extend( model ) {
616
706
  function applyIncludes( ext, art ) {
617
707
  if (kindProperties[art.kind].include !== true) {
618
708
  error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
619
- { kind: art.kind });
709
+ { meta: art.kind });
620
710
  return;
621
711
  }
622
712
 
@@ -698,9 +788,8 @@ function extend( model ) {
698
788
  return;
699
789
  if (textsEntity) // expanded localized data in source
700
790
  return; // -> make it idempotent
701
- const newTextsEntity = createTextsEntity( art, textsName, localized, fioriEnabled );
791
+ createTextsEntity( art, textsName, localized, fioriEnabled );
702
792
  addTextsAssociations( art, textsName, localized );
703
- copyPersistenceAnnotations(newTextsEntity, art, options);
704
793
  }
705
794
 
706
795
  /**
@@ -716,6 +805,9 @@ function extend( model ) {
716
805
  let keys = 0;
717
806
  const textElems = [];
718
807
  const conflictingElements = [];
808
+ // These elements are required or the localized-mechanism does not work.
809
+ // Other elements from sap.common.TextsAspect may be "overridden" as per
810
+ // usual include-mechanism.
719
811
  const protectedElements = [ 'locale', 'texts', 'localized' ];
720
812
  if (fioriEnabled)
721
813
  protectedElements.push('ID_texts');
@@ -742,7 +834,7 @@ function extend( model ) {
742
834
 
743
835
  if (isKey && isLocalized) { // key with localized is wrong - ignore localized
744
836
  const errpos = elem.localized || elem.type || elem.name;
745
- warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
837
+ warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
746
838
  'Keyword $(KEYWORD) is ignored for primary keys' );
747
839
  }
748
840
  }
@@ -750,7 +842,7 @@ function extend( model ) {
750
842
  return false;
751
843
 
752
844
  if (!keys) {
753
- warning( null, [ art.name.location, art ], {},
845
+ warning( 'def-expecting-key', [ art.name.location, art ], {},
754
846
  'No texts entity can be created when no key element exists' );
755
847
  return false;
756
848
  }
@@ -800,46 +892,15 @@ function extend( model ) {
800
892
  * @param {boolean} fioriEnabled
801
893
  */
802
894
  function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
803
- const elements = Object.create(null);
804
- const { location } = base.name;
805
- const art = {
806
- kind: 'entity',
807
- name: { path: splitIntoPath( location, absolute ), absolute, location },
808
- location: base.location,
809
- elements,
810
- $inferred: 'localized-entity',
811
- };
812
- // If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
813
- // 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';
816
- const locale = {
817
- name: { location, id: 'locale' },
818
- kind: 'element',
819
- type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
820
- location,
821
- };
822
- if (!hasLocaleType)
823
- locale.length = { literal: 'number', val: 14, location };
895
+ const name = (fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect');
896
+ const withTextsAspect = useTextsAspects && model.definitions[name];
824
897
 
825
- if (!fioriEnabled) {
826
- locale.key = { val: true, location };
827
- // To be compatible, we switch off draft without @fiori.draft.enabled
828
- // TODO (next major version): remove?
829
- annotateWith( art, '@odata.draft.enabled', art.location, false );
830
- }
831
- else {
832
- const textId = {
833
- name: { location, id: 'ID_texts' },
834
- kind: 'element',
835
- key: { val: true, location },
836
- type: augmentPath( location, 'cds.UUID' ),
837
- location,
838
- };
839
- dictAdd( art.elements, 'ID_texts', textId );
840
- }
898
+ const art = withTextsAspect
899
+ ? createTextsEntityWithInclude( base, absolute, fioriEnabled )
900
+ : createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
901
+
902
+ const { location } = base.name;
841
903
 
842
- dictAdd( art.elements, 'locale', locale );
843
904
  if (addTextsLanguageAssoc) {
844
905
  const language = {
845
906
  name: { location, id: 'language' },
@@ -859,15 +920,9 @@ function extend( model ) {
859
920
  setLink( language, '_block', model.$internal );
860
921
  dictAdd( art.elements, 'language', language );
861
922
  }
862
- setLink( art, '_block', model.$internal );
863
- model.definitions[absolute] = art;
864
- initArtifact( art );
865
923
 
866
924
  // assertUnique array value, first entry is 'locale'
867
- const assertUniqueValue = [ {
868
- path: [ { id: locale.name.id, location: locale.location } ],
869
- location: locale.location,
870
- } ];
925
+ const assertUniqueValue = [];
871
926
 
872
927
  for (const orig of textElems) {
873
928
  const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
@@ -893,8 +948,114 @@ function extend( model ) {
893
948
  elem.localized = { val: null, $inferred: 'localized', location: localized.location };
894
949
  }
895
950
  }
896
- if (fioriEnabled)
951
+
952
+ initArtifact( art );
953
+ if (art.includes) {
954
+ // add elements `locale`, etc. which are required below.
955
+ applyIncludes(art, art);
956
+ }
957
+
958
+ if (fioriEnabled ) {
959
+ const { locale } = art.elements;
960
+ assertUniqueValue.unshift({
961
+ path: [ { id: locale.name.id, location: locale.location } ],
962
+ location: locale.location,
963
+ });
897
964
  annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
965
+ }
966
+
967
+ copyPersistenceAnnotations(art, base, options);
968
+ return art;
969
+ }
970
+
971
+ /**
972
+ * Create the `.texts` entity for the given base artifact.
973
+ * In contrast to createTextsEntityWithDefaultElements(), this one creates
974
+ * an include for `sap.common.TextsAspect`.
975
+ *
976
+ * Does NOT apply the include!
977
+ *
978
+ * TODO: When beta flag textsAspect is removed, update caller-site and remove old coding.
979
+ *
980
+ * @param {XSN.Artifact} base
981
+ * @param {string} absolute
982
+ * @param {boolean} fioriEnabled
983
+ */
984
+ function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
985
+ const elements = Object.create(null);
986
+ const { location } = base.name;
987
+
988
+ const include = fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect';
989
+ const art = {
990
+ kind: 'entity',
991
+ name: { path: splitIntoPath( location, absolute ), absolute, location },
992
+ includes: [ createInclude( include, base.location ) ],
993
+ location: base.location,
994
+ elements,
995
+ $inferred: 'localized-entity',
996
+ };
997
+
998
+ if (!fioriEnabled) {
999
+ // To be compatible, we switch off draft without @fiori.draft.enabled
1000
+ // TODO (next major version): remove?
1001
+ annotateWith( art, '@odata.draft.enabled', art.location, false );
1002
+ }
1003
+
1004
+ if (addTextsLanguageAssoc && art.elements.language)
1005
+ art.elements.language = undefined; // TODO: Message? Ignore?
1006
+
1007
+ setLink( art, '_block', model.$internal );
1008
+ model.definitions[absolute] = art;
1009
+ return art;
1010
+ }
1011
+
1012
+ /**
1013
+ * @param {XSN.Artifact} base
1014
+ * @param {string} absolute
1015
+ * @param {boolean} fioriEnabled
1016
+ */
1017
+ function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
1018
+ const elements = Object.create(null);
1019
+ const { location } = base.name;
1020
+ const art = {
1021
+ kind: 'entity',
1022
+ name: { path: splitIntoPath( location, absolute ), absolute, location },
1023
+ location: base.location,
1024
+ elements,
1025
+ $inferred: 'localized-entity',
1026
+ };
1027
+ // If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
1028
+ // If not, use the default `cds.String` with a length of 14.
1029
+ const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
1030
+ const locale = {
1031
+ name: { location, id: 'locale' },
1032
+ kind: 'element',
1033
+ type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
1034
+ location,
1035
+ };
1036
+ if (!hasLocaleType)
1037
+ locale.length = { literal: 'number', val: 14, location };
1038
+
1039
+ if (!fioriEnabled) {
1040
+ locale.key = { val: true, location };
1041
+ // To be compatible, we switch off draft without @fiori.draft.enabled
1042
+ // TODO (next major version): remove?
1043
+ annotateWith( art, '@odata.draft.enabled', art.location, false );
1044
+ }
1045
+ else {
1046
+ const textId = {
1047
+ name: { location, id: 'ID_texts' },
1048
+ kind: 'element',
1049
+ key: { val: true, location },
1050
+ type: augmentPath( location, 'cds.UUID' ),
1051
+ location,
1052
+ };
1053
+ dictAdd( art.elements, 'ID_texts', textId );
1054
+ }
1055
+
1056
+ dictAdd( art.elements, 'locale', locale );
1057
+ setLink( art, '_block', model.$internal );
1058
+ model.definitions[absolute] = art;
898
1059
 
899
1060
  return art;
900
1061
  }
@@ -937,6 +1098,22 @@ function extend( model ) {
937
1098
  setLink( localized, '_block', model.$internal );
938
1099
  }
939
1100
 
1101
+ /**
1102
+ * Create a structure that can be used as an item in `includes`.
1103
+ *
1104
+ * @param {string} name
1105
+ * @param {XSN.Location} location
1106
+ */
1107
+ function createInclude( name, location ) {
1108
+ const include = {
1109
+ path: [ { id: name, location } ],
1110
+ location,
1111
+ };
1112
+ setArtifactLink( include.path[0], model.definitions[name] );
1113
+ setArtifactLink( include, model.definitions[name] );
1114
+ return include;
1115
+ }
1116
+
940
1117
  /**
941
1118
  * Returns whether `art` directly or indirectly has the property 'prop',
942
1119
  * following the 'origin' and the 'type' (not involving elements).
@@ -1086,6 +1263,15 @@ function extend( model ) {
1086
1263
  });
1087
1264
  return false;
1088
1265
  }
1266
+
1267
+ if (elem.type && !isDirectComposition(elem)) {
1268
+ // Only issue warning for direct usages, not for projections, includes, etc.
1269
+ // TODO: Make it configurable error; v4: error
1270
+ warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
1271
+ { prop: 'Composition of', otherprop: 'Association to' },
1272
+ 'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
1273
+ }
1274
+
1089
1275
  return true;
1090
1276
  }
1091
1277
 
@@ -1268,7 +1454,7 @@ function compareAssignments( a, b ) {
1268
1454
  * @param {object} source
1269
1455
  * @param {CSN.Options} options
1270
1456
  */
1271
- function copyPersistenceAnnotations(target, source, options) {
1457
+ function copyPersistenceAnnotations( target, source, options ) {
1272
1458
  if (!source)
1273
1459
  return;
1274
1460
 
@@ -1325,4 +1511,24 @@ function storeTypeExtension( ext, art ) {
1325
1511
  art._extendType.push( ext );
1326
1512
  }
1327
1513
 
1514
+
1515
+ function checkTextsLanguageAssocOption( model, options ) {
1516
+ const languages = model.definitions['sap.common.Languages'];
1517
+ const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
1518
+
1519
+ if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
1520
+ const variant = !languages ? 'std' : 'code';
1521
+ const loc = model.definitions['sap.common.Languages']?.name?.location || null;
1522
+ model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
1523
+ '#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
1524
+ }, {
1525
+ std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
1526
+ code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
1527
+ });
1528
+ }
1529
+
1530
+ return !!commonLanguagesEntity;
1531
+ }
1532
+
1533
+
1328
1534
  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;
@@ -186,8 +186,13 @@ function finalizeParseCdl( model ) {
186
186
  // For better error messages, check for invalid TYPE OFs similarly
187
187
  // to how `resolveType()` does.
188
188
  let struct = artWithType;
189
- while (struct.kind === 'element')
190
- struct = struct._parent;
189
+ // `items` have no kind, but need to be skipped as well
190
+ while (struct.kind === 'element' || struct._outer?.items) {
191
+ if (struct._outer?.items)
192
+ struct = struct._outer;
193
+ else
194
+ struct = struct._parent;
195
+ }
191
196
  if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
192
197
  message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
193
198
  { keyword: 'type of', '#': struct.kind } );
@@ -202,7 +207,7 @@ function finalizeParseCdl( model ) {
202
207
  }
203
208
  }
204
209
 
205
- function chooseAndReportDuplicateAnnotation(artifact, annoName) {
210
+ function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
206
211
  for (const anno of artifact[annoName])
207
212
  message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
208
213
 
@@ -40,8 +40,14 @@ const { cdsFs } = require('../utils/file');
40
40
  const fs = require('fs');
41
41
  const path = require('path');
42
42
 
43
- const csnExtensions = [ '.json', '.csn' ];
44
- const cdlExtensions = [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ];
43
+ const extensionParsers = {
44
+ csn: parseCsn.parse,
45
+ json: parseCsn.parse,
46
+ cds: parseLanguage,
47
+ cdl: parseLanguage,
48
+ hdbcds: parseLanguage,
49
+ hdbdd: parseLanguage,
50
+ };
45
51
 
46
52
  // Class for command invocation errors. Additional members:
47
53
  // `errors`: vector of errors (file IO or ArgumentError)
@@ -75,21 +81,18 @@ class ArgumentError extends Error {
75
81
  function parseX( source, filename, options = {}, messageFunctions = null ) {
76
82
  if (!messageFunctions)
77
83
  messageFunctions = createMessageFunctions( options, 'parse' );
78
- const ext = path.extname( filename ).toLowerCase();
79
- if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
80
- return parseCsn.parse( source, filename, options, messageFunctions );
81
- if (cdlExtensions.includes(ext))
82
- return parseLanguage( source, filename, options, messageFunctions );
83
- if (options.fallbackParser === 'csn')
84
- return parseCsn.parse( source, filename, options, messageFunctions );
85
- if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
86
- return parseLanguage( source, filename, options, messageFunctions );
87
- if (source.startsWith('{')) // Source may be JSON.
88
- return parseCsn.parse( source, filename, options, messageFunctions );
84
+ const ext = path.extname( filename ).slice(1).toLowerCase();
85
+ // eslint-disable-next-line no-nested-ternary
86
+ const parser = options.fallbackParser === 'auto!'
87
+ ? (source?.startsWith('{') ? parseCsn.parse : parseLanguage)
88
+ : (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
89
+ source.startsWith('{') && parseCsn.parse);
90
+ if (parser)
91
+ return parser( source, filename, options, messageFunctions );
89
92
 
90
93
  const model = { location: { file: filename } };
91
94
  messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
92
- { file: ext && ext.slice(1), '#': !ext && 'none' }, {
95
+ { file: ext, '#': !ext && 'none' }, {
93
96
  std: 'Unknown file extension $(FILE)',
94
97
  none: 'No file extension',
95
98
  } );
@@ -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,17 +192,15 @@ 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;
199
198
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
200
199
  art.target && redirectImplicitly( a, art ) ||
201
200
  art.elements && expandElements( a, art ) ||
202
- art.items && expandItems( a, art ))
201
+ art.items && expandItems( a, art ) ||
202
+ art.enum && expandEnum( a, art ))
203
203
  art = a;
204
- else if (art.enum && expandEnum( a, prev ))
205
- prev = a; // do not set art - effective type is base
206
204
  setLink( a, '_effectiveType', art );
207
205
  }
208
206
  }
@@ -469,11 +467,21 @@ function populate( model ) {
469
467
  'Element $(ID) is missing in specified elements' );
470
468
  }
471
469
  else {
470
+ let wasAnnotated = false;
472
471
  for (const prop in selem) {
473
472
  // just annotation assignments and doc comments for the moment
474
- if (prop.charAt(0) === '@' || prop === 'doc')
473
+ if (prop.charAt(0) === '@' || prop === 'doc') {
475
474
  ielem[prop] = selem[prop];
475
+ // required for gensrc mode of to-csn.js, otherwise the annotation
476
+ // may be lost during recompilation.
477
+ ielem[prop].$priority = 'annotate';
478
+ wasAnnotated = true;
479
+ }
476
480
  }
481
+
482
+ if (wasAnnotated)
483
+ setExpandStatusAnnotate(art, 'annotate');
484
+
477
485
  selem.$replacement = true;
478
486
  if (selem.elements) {
479
487
  setLink(ielem, 'elements$', selem.elements);
@@ -15,6 +15,7 @@ const {
15
15
  isDeprecatedEnabled,
16
16
  } = require( '../base/model');
17
17
  const { setLink, linkToOrigin, withAssociation } = require('./utils');
18
+ const $inferred = Symbol.for('cds.$inferred');
18
19
  // const { refString } = require( '../base/messages')
19
20
 
20
21
  // Note that propagation here is also used for deep-copying (function `onlyViaParent`)
@@ -225,6 +226,7 @@ function propagate( model ) {
225
226
  member.$inferred = 'proxy';
226
227
  setEffectiveType(member, dict[name]);
227
228
  }
229
+ target[prop][$inferred] = 'prop';
228
230
  }
229
231
 
230
232
  function onlyViaParent( prop, target, source ) {
@@ -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`