@sap/cds-compiler 4.0.2 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -52,6 +52,7 @@ const centralMessages = {
52
52
  'anno-unstable-array': { severity: 'Warning' },
53
53
  'anno-invalid-sql-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
54
54
  'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
55
+ 'anno-invalid-sql-assoc': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
55
56
  'anno-invalid-sql-calc': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
56
57
  'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
57
58
  'anno-invalid-sql-view': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
@@ -62,47 +63,38 @@ const centralMessages = {
62
63
  'anno-undefined-element': { severity: 'Warning' },
63
64
  'anno-undefined-param': { severity: 'Warning' },
64
65
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
65
-
66
- 'args-expecting-named': { severity: 'Error' },
67
- 'args-no-params': { severity: 'Error' },
68
- 'args-undefined-param': { severity: 'Error' },
66
+ 'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
69
67
 
70
68
  'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
71
69
 
72
70
  'type-invalid-items': { severity: 'Error' }, // not supported yet
73
71
  'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
72
+ 'def-unexpected-nested-proj': { severity: 'Error', configurableFor: 'v4' },
74
73
  'def-unexpected-paramview-assoc': { severity: 'Error' },
75
74
  'def-unexpected-calcview-assoc': { severity: 'Error' },
76
75
  'chained-array-of': { severity: 'Error' },
77
- 'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
76
+ 'def-missing-type': { severity: 'Error', configurableFor: [ 'compile' ] },
78
77
  'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
79
78
 
80
79
  'def-duplicate-autoexposed': { severity: 'Error' },
81
80
  'def-unexpected-default': { severity: 'Error', configurableFor: 'test' },
82
81
 
83
- 'expr-no-filter': { severity: 'Error' },
82
+ 'expr-unexpected-filter': { severity: 'Error' },
84
83
 
85
84
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
86
85
 
87
86
  'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
88
- 'ref-expecting-type': { severity: 'Error' },
89
- 'ref-sloppy-type': { severity: 'Error' },
87
+ 'ref-invalid-type': { severity: 'Error' },
90
88
  'ref-unexpected-self': { severity: 'Error' },
91
- 'ref-expecting-bare-aspect': { severity: 'Error' },
89
+ 'ref-invalid-include': { severity: 'Error' },
92
90
  'type-unexpected-typeof': { severity: 'Error' },
93
91
  'type-ignoring-argument': { severity: 'Error', configurableFor: true },
94
92
  'type-expected-builtin': { severity: 'Error', configurableFor: true },
95
93
  'type-expecting-service-target': { severity: 'Error', configurableFor: true },
96
- 'ref-expecting-action-param-type': { severity: 'Error' },
97
- 'ref-sloppy-actionparam-type': { severity: 'Error' },
98
- 'ref-expecting-event-type': { severity: 'Error' }, // TODO: Test coverage
99
- 'ref-sloppy-event-type': { severity: 'Error' },
100
- 'ref-expecting-struct': { severity: 'Error' },
101
94
  'ref-expecting-const': { severity: 'Error' },
102
- 'ref-expecting-entity': { severity: 'Error' },
103
- 'ref-expecting-source': { severity: 'Error' },
104
- 'ref-expecting-target': { severity: 'Error' },
105
- 'ref-sloppy-target': { severity: 'Warning' },
95
+ 'ref-invalid-source': { severity: 'Error' },
96
+ 'ref-invalid-target': { severity: 'Error' },
97
+ 'ref-sloppy-target': { severity: 'Error', configurableFor: 'v4' },
106
98
 
107
99
  'extend-repeated-intralayer': { severity: 'Warning' },
108
100
  'extend-unrelated-layer': { severity: 'Info' },
@@ -123,6 +115,7 @@ const centralMessages = {
123
115
  'type-ambiguous-target': { severity: 'Warning' },
124
116
 
125
117
  'ref-unexpected-autoexposed': { severity: 'Error' },
118
+ 'ref-unexpected-many-navigation': { severity: 'Error' },
126
119
  // Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
127
120
  'ref-undefined-art': { severity: 'Error' },
128
121
  'ref-undefined-def': { severity: 'Error' },
@@ -166,15 +159,13 @@ const centralMessages = {
166
159
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
167
160
 
168
161
  'type-managed-composition': { severity: 'Error' },
169
- 'type-unsupported-precision-change': { severity: 'Error'},
162
+ 'type-unsupported-precision-change': { severity: 'Error' },
163
+ 'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
170
164
 
171
165
  'def-missing-element': { severity: 'Error' },
172
166
 
173
167
  'def-unsupported-calc-elem': { severity: 'Error', configurableFor: true },
174
168
 
175
- 'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
176
- 'unmanaged-as-key': { severity: 'Error' }, // is confusing
177
- 'composition-as-key': { severity: 'Error' }, // is confusing and not supported
178
169
  'def-invalid-key-cardinality': { severity: 'Error' },
179
170
  // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
180
171
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
@@ -213,6 +204,8 @@ const oldMessageIds = createDict({
213
204
  'old-anno-duplicate': 'anno-duplicate', // Example
214
205
  'assoc-in-array': 'type-invalid-items',
215
206
  'duplicate-autoexposed': 'def-duplicate-autoexposed',
207
+ 'expr-no-filter': 'expr-unexpected-filter',
208
+ 'check-proper-type': 'def-missing-type',
216
209
  });
217
210
 
218
211
  // Set up the old-to-new message ID mapping in the message registry.
@@ -239,6 +232,8 @@ const centralMessageTexts = {
239
232
  std: 'Invalid option $(NAME)!',
240
233
  deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
241
234
  magicVars: 'Option $(PROP) is no longer supported! Use $(OTHERPROP) instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
235
+ value: 'Expected option $(PROP) to have $(VALUE). Found: $(OTHERVALUE)',
236
+ type: 'Expected option $(OPTION) to be of type $(VALUE). Found: $(OTHERVALUE)',
242
237
  },
243
238
 
244
239
  'api-invalid-variable-replacement': {
@@ -248,6 +243,22 @@ const centralMessageTexts = {
248
243
  'noDollar': 'Option $(OPTION) does not know $(NAME). Did you forget a leading $(CODE)?'
249
244
  },
250
245
 
246
+ 'api-invalid-combination': {
247
+ std: 'Invalid option combination found: $(OPTION) and $(PROP)', // unused
248
+ 'valid-structured': 'Structured OData is only supported with OData version v4',
249
+ 'sql-dialect-and-naming': 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
250
+ 'sql-dialect-and-localized': 'Option $(OPTION) can\'t be combined with SQL dialect $(VALUE) or the to.hdi()/to.hdbcds() backend',
251
+ },
252
+ 'api-unexpected-combination': {
253
+ std: 'Unexpected option combination: $(OPTION) and $(PROP)', // unused
254
+ 'beta-no-test':'Option $(OPTION) was used. This option should not be used in productive scenarios!',
255
+ },
256
+ 'api-invalid-lookup-dir': {
257
+ std: '',
258
+ slash: 'Expected directory $(VALUE) in option $(OPTION) to end with $(OTHERVALUE)',
259
+ relative: 'Expected directory $(VALUE) in option $(OPTION) to not start with $(OTHERVALUE)',
260
+ },
261
+
251
262
  'anno-duplicate': {
252
263
  std: 'Duplicate assignment with $(ANNO)',
253
264
  doc: 'Duplicate assignment with a doc comment',
@@ -511,7 +522,12 @@ const centralMessageTexts = {
511
522
  // Messages for erroneous references -----------------------------------------
512
523
  // location at erroneous reference (if possible)
513
524
  'ref-deprecated-orderby': 'Replace source element reference $(ID) by $(NEWCODE); auto-corrected',
514
- 'ref-unexpected-self': 'Unexpected $(ID) reference; is valid only in ON-conditions',
525
+ 'ref-unexpected-self': {
526
+ std: 'Unexpected $(ID) reference; is valid only in ON-conditions of unmanaged associations',
527
+ on: 'Unexpected $(ID) reference; is valid only if compared to be equal to an association of the target side',
528
+ subQuery: 'Unexpected $(ID) reference in a sub query',
529
+ setQuery: 'Unexpected $(ID) reference in a query on the right side of $(OP)',
530
+ },
515
531
  'ref-undefined-def': {
516
532
  std: 'Artifact $(ART) has not been found',
517
533
  // TODO: proposal 'No definition of $(NAME) found',
@@ -550,6 +566,9 @@ const centralMessageTexts = {
550
566
  std: '$(ART) can\'t be extended because it originates from an include',
551
567
  elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
552
568
  },
569
+ 'ref-unexpected-many-navigation': {
570
+ std: 'Unexpected navigation into arrayed structure',
571
+ },
553
572
  'ref-unexpected-scope': {
554
573
  std: 'Unexpected parameter reference',
555
574
  calc: 'Calculated elements can\'t use parameter references',
@@ -565,7 +584,11 @@ const centralMessageTexts = {
565
584
  },
566
585
  'ref-unexpected-assoc': {
567
586
  std: 'Unexpected reference to association $(NAME)', // "std" currently unused
587
+ unmanaged: 'Unexpected reference to an unmanaged association',
588
+ 'self-unmanaged': 'Unexpected column reference starting with $(ALIAS) to an unmanaged association',
589
+ self: 'A reference to an unmanaged association is only valid when compared via $(CODE)',
568
590
  expr: 'Associations can\'t be used as values in expressions',
591
+ 'expr-comp': 'Compositions can\'t be used as values in expressions',
569
592
  cast: 'Casting to an association is not supported',
570
593
  },
571
594
  'ref-unexpected-calculated': {
@@ -575,7 +598,7 @@ const centralMessageTexts = {
575
598
  },
576
599
  'ref-unexpected-localized': {
577
600
  std: 'Unexpected reference to localized element $(NAME)', // "std" currently unused
578
- calc: 'Calculated elements can\'t refer to localized elements',
601
+ calc: 'Calculated elements "on-write" can\'t refer to localized elements',
579
602
  },
580
603
 
581
604
  'ref-unexpected-navigation': {
@@ -624,6 +647,14 @@ const centralMessageTexts = {
624
647
  comp: 'Unexpected composition inside $(PROP)',
625
648
  },
626
649
 
650
+ 'type-unexpected-default': {
651
+ std: 'Unexpected $(KEYWORD) on an association/composition', // unused
652
+ multi: 'Unexpected $(KEYWORD); expected exactly one foreign key in combination with default value, but found $(COUNT)',
653
+ structured: 'Unexpected $(KEYWORD) in combination with structured foreign key $(NAME); $(KEYWORD) requires a non-structured foreign key',
654
+ 'onCond': 'Unexpected $(KEYWORD) on an association/composition with ON-condition; $(KEYWORD) requires exactly one foreign key',
655
+ 'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
656
+ },
657
+
627
658
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
628
659
  'anno-undefined-def': 'Artifact $(ART) has not been found', // TODO: ext-
629
660
  'anno-undefined-art': 'No artifact has been found with name $(ART)',
@@ -654,14 +685,25 @@ const centralMessageTexts = {
654
685
  source: 'Unexpected definition of an association in an entity with parameters',
655
686
  target: 'Expected association target to have no parameters',
656
687
  },
688
+ 'def-unexpected-nested-proj': {
689
+ std: 'Unexpected $(CODE)',
690
+ var: 'Unexpected $(CODE) after reference to CDS variable',
691
+ struct: 'Unexpected $(CODE); can only be used after a reference to a structure or association',
692
+ init: 'Unexpected $(CODE); can only be used after a reference to a structure, association or table alias',
693
+ },
657
694
  'def-unexpected-calcview-assoc': {
658
695
  std: 'unused',
659
696
  'source': 'Unexpected definition of an association in an entity annotated with $(ANNO)',
660
697
  'target': 'Expected association target not to be annotated with $(ANNO)',
661
698
  },
699
+ 'def-invalid-key': {
700
+ std: 'The current element can\'t be defined as primary key', // (unused)
701
+ unmanaged: 'Unmanaged associations/compositions can\'t be defined as primary key',
702
+ composition: 'Managed aspect compositions can\'t be defined as primary key',
703
+ },
662
704
  'def-unexpected-key': {
663
705
  std: '$(ART) can\'t have additional keys',
664
- virtual: 'Unexpected $(PROP) for virtual element $(NAME)',
706
+ virtual: 'Unexpected $(PROP) for virtual element $(ART)',
665
707
  // TODO: Better message?
666
708
  include: '$(ART) can\'t have additional keys (through include)',
667
709
  },
@@ -718,6 +760,12 @@ const centralMessageTexts = {
718
760
  elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
719
761
  actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
720
762
  },
763
+ 'ref-invalid-element': {
764
+ std: 'Invalid element reference',
765
+ $tableAlias: 'Can\'t refer to source elements of table alias $(ID)',
766
+ mixin: 'Can\'t refer to the query\'s own mixin $(ID)',
767
+ $self: 'Can\'t refer to the query\'s own elements',
768
+ },
721
769
  'ref-invalid-override': {
722
770
  std: 'Overridden element of include must not change element structure', // unused
723
771
  'new-not-structured': 'Expected element $(NAME) to be structured, because it overrides the included element from $(ART)',
@@ -725,19 +773,27 @@ const centralMessageTexts = {
725
773
  missing: 'Expected element $(ID) to have at least all the same sub-elements as included artifacts, but it is missing $(NAME)'
726
774
  },
727
775
 
728
- 'ref-expecting-action-param-type': 'A type, an element, or a service entity is expected here',
729
- 'ref-expecting-const': 'A constant value is expected here',
730
- 'ref-expecting-event-type': 'A type, an element, an event, or a service entity is expected here',
731
- 'ref-expecting-entity': 'An entity, projection or view is expected here',
732
- 'ref-expecting-struct': 'A type, entity, aspect or event with direct elements is expected here',
733
- 'ref-expecting-type': 'A type or an element is expected here',
776
+ 'ref-expecting-const': 'A constant expression or variable is expected here',
777
+ 'ref-invalid-target': {
778
+ std: 'An entity, projection or view is expected here', // TODO: change text
779
+ composition: 'Expecting an entity or aspect as composition target',
780
+ bare: 'Expecting the target aspect to have elements',
781
+ aspect: 'Expecting the name of an aspect in property $(PROP)', // only CSN input
782
+ },
783
+ 'ref-invalid-include': {
784
+ std: 'A type, entity, aspect or event with direct elements is expected here',
785
+ bare : 'An aspect without elements is expected here',
786
+ },
787
+ 'ref-invalid-type': {
788
+ std: 'A type or an element is expected here',
789
+ param: 'A type, an element, or a service entity is expected here',
790
+ event: 'A type, an element, an event, or a service entity is expected here',
791
+ },
734
792
  // TODO: text variant if the association does not start an entity
735
- 'ref-expecting-source': 'A query source must be an entity or an association',
736
- 'ref-expecting-target': 'An entity or an aspect is expected here', // TODO: coverage
793
+ 'ref-invalid-source': 'A query source must be an entity or an association',
737
794
  'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
738
795
  'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
739
796
  'extend-unexpected-include': 'Can\'t extend $(META) with includes',
740
- 'ref-expecting-bare-aspect': 'An aspect without elements is expected here',
741
797
 
742
798
  'ext-duplicate-same-file': 'Duplicate extension with $(PROP) in same file',
743
799
  'ext-duplicate-extend-type': 'Duplicate type extension for type $(TYPE)',
@@ -787,6 +843,8 @@ const centralMessageTexts = {
787
843
  target: 'Expected target $(TARGET) of specified element $(NAME) to be the same as the inferred element\'s target $(ART)',
788
844
  foreignKeys: 'Expected foreign keys of specified element $(NAME) to be the same as the inferred element\'s foreign keys',
789
845
  prop: 'Value for $(PROP) of the specified element $(NAME) does not match the inferred element\'s value',
846
+ enumExtra: 'Specified element $(NAME) differs from inferred element: it has an additional enum element $(ID)',
847
+ enumVal: 'Specified element $(NAME) differs from inferred element: it has a different value for enum element $(ID)',
790
848
  },
791
849
 
792
850
  'query-unexpected-property': {
@@ -794,10 +852,7 @@ const centralMessageTexts = {
794
852
  calculatedElement: 'Unexpected property $(PROP) in the specified element $(NAME); calculated elements are not supported in queries',
795
853
  },
796
854
 
797
- 'ref-sloppy-type': 'A type or an element is expected here',
798
- 'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
799
855
  'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
800
- 'ref-sloppy-event-type': 'A type, an element, an event, or a service entity is expected here',
801
856
 
802
857
  'ref-ambiguous': {
803
858
  std: 'Replace ambiguous $(ID) by $(NAMES)',
@@ -812,16 +867,41 @@ const centralMessageTexts = {
812
867
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
813
868
  },
814
869
 
870
+ 'type-unsupported-key-change': {
871
+ std: 'Added element $(ID) is a primary key change and will not work if the table contains data',
872
+ changed: 'Changed element $(ID) is a primary key change and will not work if the table contains data'
873
+ },
874
+
875
+ 'type-unsupported-key-sqlite': {
876
+ std: 'Added element $(ID) is a primary key change and will not work with dialect $(NAME)',
877
+ changed: 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)'
878
+ },
879
+
880
+ 'type-invalid-cast': {
881
+ std: 'Invalid cast to $(TYPE)', // unused
882
+ 'to-structure': 'Can\'t cast to a structured type',
883
+ 'from-structure': 'Structured elements can\'t be cast to a different type',
884
+ 'expr-to-structure': 'Can\'t cast an expression to a structured type',
885
+ 'val-to-structure': 'Can\'t cast $(VALUE) to a structured type'
886
+ },
887
+
815
888
  // -----------------------------------------------------------------------------------
816
889
  // Expressions
817
890
  // -----------------------------------------------------------------------------------
818
- 'expr-invalid-operator': 'Comparing $(ID) is only allowed with $(OP)',
819
- 'expr-missing-comparison': {
820
- std: 'Expected a comparison with $(OP) when using $(ID) in an ON-condition',
891
+ 'type-invalid-cardinality': {
892
+ std: 'Invalid value $(VALUE) for cardinality', // unused variant
893
+ sourceMax: 'Invalid value $(PROP) for maximum source cardinality, expecting a positive number or $(OTHERPROP)',
894
+ targetMax: 'Invalid value $(PROP) for maximum target cardinality, expecting a positive number or $(OTHERPROP)',
895
+ targetMin: 'Invalid value $(PROP) for minimum target cardinality, expecting a non-negative number',
896
+ sourceMin: 'Invalid value $(PROP) for minimum source cardinality, expecting a non-negative number',
897
+ sourceVal: 'Source minimum cardinality must not be greater than source maximum cardinality',
898
+ targetVal: 'Target minimum cardinality must not be greater than target maximum cardinality',
821
899
  },
822
900
 
823
901
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
824
902
 
903
+ 'expr-missing-foreign-key': 'Path step $(ID) of $(ELEMREF) has no valid foreign keys',
904
+
825
905
  // OData version dependent messages
826
906
  'odata-spec-violation-array': 'Unexpected array type for OData $(VERSION)',
827
907
  'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for OData $(VERSION)',
@@ -854,6 +934,7 @@ const centralMessageTexts = {
854
934
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
855
935
  facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
856
936
  external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)',
937
+ scale: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE)'
857
938
  },
858
939
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(META)',
859
940
  'odata-spec-violation-namespace': {
@@ -919,7 +1000,7 @@ const centralMessageTexts = {
919
1000
  // -----------------------------------------------------------------------------------
920
1001
  'enum': 'Value $(VALUE) is not one out of $(RAWVALUES) for $(ANNO) of type $(TYPE)',
921
1002
  'std': 'Unexpected value $(VALUE) for $(ANNO) of type $(TYPE)',
922
- 'struct': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
1003
+ 'incompval': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
923
1004
  'nestedcollection': 'Nested collections are not supported for $(ANNO)',
924
1005
  'enumincollection': 'Enum inside collection is not supported for $(ANNO)',
925
1006
  'multexpr': 'EDM JSON code contains more than one dynamic expression: $(RAWVALUES) for $(ANNO)',
@@ -948,7 +1029,7 @@ const centralMessageTexts = {
948
1029
  *
949
1030
  * @typedef {object} MessageConfig
950
1031
  * @property {MessageSeverity} severity Default severity for the message.
951
- * @property {string[]|'deprecated'|'v4'|true} [configurableFor]
1032
+ * @property {string[]|'deprecated'|'v4'|'test'|true} [configurableFor]
952
1033
  * Whether the error can be reclassified to a warning or lower.
953
1034
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
954
1035
  * Only has an effect if default severity is 'Error'.
@@ -17,6 +17,7 @@ const { cdlNewLineRegEx } = require('../language/textUtils');
17
17
  const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { inspect } = require('util')
20
+ const { CsnLocation } = require('../compiler/classes');
20
21
 
21
22
  // term instance for messages
22
23
  const colorTerm = term();
@@ -94,7 +95,7 @@ class CompilationError extends Error {
94
95
  super( `CDS compilation failed\n${messages?.map( m => m.toString() ).join('\n') || ''}` );
95
96
  /** @since v4.0.0 */
96
97
  this.code = 'ERR_CDS_COMPILATION_FAILURE';
97
- this.messages = messages;
98
+ this.messages = [ ...messages ].sort(compareMessageSeverityAware);
98
99
 
99
100
  // TODO: remove this bin/cdsc.js specifics
100
101
  Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
@@ -128,7 +129,7 @@ class CompilationError extends Error {
128
129
  class CompileMessage {
129
130
  /**
130
131
  * Creates an instance of CompileMessage.
131
- * @param {any} location Location of the message
132
+ * @param {CSN.Location} location Location of the message
132
133
  * @param {string} msg The message text
133
134
  * @param {MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
134
135
  * @param {string} [id] The ID of the message - visible as property messageId
@@ -139,7 +140,7 @@ class CompileMessage {
139
140
  */
140
141
  constructor(location, msg, severity = 'Error', id = null, home = null, moduleName = null) {
141
142
  this.message = msg;
142
- this.$location = { ...location, address: undefined };
143
+ this.$location = { __proto__: CsnLocation.prototype, ...location, address: undefined };
143
144
  this.validNames = null;
144
145
  this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
145
146
  this.severity = severity;
@@ -892,14 +893,14 @@ function replaceInString( text, params ) {
892
893
  }
893
894
 
894
895
  /**
895
- * @param {CSN.Location} loc
896
- * @returns {CSN.Location}
896
+ * @param {CsnLocation} loc
897
+ * @returns {CsnLocation}
897
898
  */
898
899
  function weakLocation( loc ) {
899
900
  if (!loc)
900
- return {};
901
+ return new CsnLocation();
901
902
  // no endLine/endCol
902
- return { file: loc.file, line: loc.line, col: loc.col };
903
+ return new CsnLocation( loc.file, loc.line, loc.col, undefined, undefined );
903
904
  }
904
905
 
905
906
  /**
@@ -1299,6 +1300,13 @@ function deduplicateMessages( messages ) {
1299
1300
 
1300
1301
  function shortArtName( art ) {
1301
1302
  const name = getArtifactName( art );
1303
+ if (!name) {
1304
+ const loc = art.location ? ` at ${ locationString( art.location ) }` : '';
1305
+ throw new CompilerAssertion(
1306
+ art.path
1307
+ ? `No artifact for ${ art.path.map( i => i.id ).join( '.' )}${ loc }`
1308
+ : `No name found in ${ Object.keys( art ).join( '+' )}${ loc }` );
1309
+ }
1302
1310
  if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1303
1311
  !name.absolute.includes(':'))
1304
1312
  return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
@@ -1310,14 +1318,14 @@ function artName( art, omit ) {
1310
1318
  const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1311
1319
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1312
1320
  r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
1313
- if (name.action && omit !== 'action')
1321
+ if (name.action != null && omit !== 'action')
1314
1322
  r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1315
- if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
1323
+ if (name.alias != null && art.kind !== '$self' && name.$inferred !== '$internal')
1316
1324
  r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1317
1325
  if (name.param != null && omit !== 'param')
1318
1326
  r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
1319
1327
 
1320
- if (name.element && omit !== 'element') {
1328
+ if (name.element != null && omit !== 'element') {
1321
1329
  if (name.select != null && !art.$inferred)
1322
1330
  r.push( 'column:' + quoted( name.element ) );
1323
1331
  else
package/lib/base/model.js CHANGED
@@ -1,3 +1,8 @@
1
+ // module- and csn/XSN-independent definitions
2
+
3
+ // TODO: move XSN-specific things to lib/compiler/utils/
4
+ // TODO: move CSN-specific things to ???
5
+
1
6
  'use strict';
2
7
 
3
8
  const { forEach } = require('../utils/objectUtils');
@@ -22,14 +27,14 @@ const queryOps = {
22
27
  const availableBetaFlags = {
23
28
  // enabled by --beta-mode
24
29
  annotationExpressions: true,
25
- toRename: true, // Removes once it's publicly documented
26
30
  assocsWithParams: true, // beta, because runtimes don't support it, yet.
27
31
  hanaAssocRealCardinality: true,
28
32
  mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
29
33
  enableUniversalCsn: true,
30
- aspectWithoutElements: true, // TODO: do not just remove beta flag - remove elements, too!
31
34
  odataTerms: true,
32
35
  optionalActionFunctionParameters: true, // not supported by runtime, yet.
36
+ associationDefault: true,
37
+ annotateForeignKeys: true,
33
38
  // disabled by --beta-mode
34
39
  nestedServices: false,
35
40
  };
@@ -40,6 +45,7 @@ const availableDeprecatedFlags = {
40
45
  downgradableErrors: true,
41
46
  includesNonShadowedFirst: true,
42
47
  eagerPersistenceForGeneratedEntities: true,
48
+ noKeyPropagationWithExpansions: true,
43
49
  }
44
50
 
45
51
  const oldDeprecatedFlags_v2 = [
@@ -126,18 +132,26 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
126
132
  });
127
133
  }
128
134
 
129
- // Apply function `callback` to all artifacts in dictionary
130
- // `model.definitions`. See function `forEachGeneric` for details.
131
- // TODO: should we skip "namespaces" already here?
135
+ /**
136
+ * Apply function `callback` to all artifacts in dictionary
137
+ * `model.definitions`. See function `forEachGeneric` for details.
138
+ * TODO: should we skip "namespaces" already here?
139
+ */
132
140
  function forEachDefinition( model, callback ) {
133
141
  forEachGeneric( model, 'definitions', callback );
134
142
  }
135
143
 
136
- // Apply function `callback` to all members of object `obj` (main artifact or
137
- // parent member). Members are considered those in dictionaries `elements`,
138
- // `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
139
- // searched inside property `items` (array of). See function `forEachGeneric`
140
- // for details.
144
+ /**
145
+ * Apply function `callback` to all members of object `obj` (main artifact or
146
+ * parent member). Members are considered those in dictionaries `elements`,
147
+ * `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
148
+ * searched inside property `items` (array of). See function `forEachGeneric`
149
+ * for details.
150
+ *
151
+ * @param {XSN.Artifact} construct
152
+ * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
153
+ * @param {XSN.Artifact} [target]
154
+ */
141
155
  function forEachMember( construct, callback, target ) {
142
156
  let obj = construct;
143
157
  while (obj.items)
@@ -151,6 +165,24 @@ function forEachMember( construct, callback, target ) {
151
165
  callback( construct.returns, '', 'params' );
152
166
  }
153
167
 
168
+ /**
169
+ * Same as forEachMember, but inside each member, calls itself recursively, i.e.
170
+ * sub members are traversed as well.
171
+ *
172
+ * @param {XSN.Artifact} construct
173
+ * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
174
+ * @param {XSN.Artifact} [target]
175
+ */
176
+ function forEachMemberRecursively( construct, callback, target ) {
177
+ forEachMember( construct, ( member, memberName, prop ) => {
178
+ callback( member, memberName, prop );
179
+ if (Array.isArray(member.$duplicates)) // redefinitions
180
+ member.$duplicates.forEach( o => callback( o, memberName, prop ) );
181
+ // Descend into nested members, too
182
+ forEachMemberRecursively( member, callback );
183
+ }, target);
184
+ }
185
+
154
186
  // Apply function `callback` to all objects in dictionary `dict`, including all
155
187
  // duplicates (found under the same name). Function `callback` is called with
156
188
  // the following arguments: the object, the name, and -if it is a duplicate-
@@ -193,6 +225,7 @@ module.exports = {
193
225
  queryOps,
194
226
  forEachDefinition,
195
227
  forEachMember,
228
+ forEachMemberRecursively,
196
229
  forEachGeneric,
197
230
  forEachInOrder,
198
231
  setProp,
@@ -53,12 +53,12 @@ function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
53
53
  */
54
54
  function warnAboutDefaultOnAssociationForHanaCds( member, memberName, prop, path ) {
55
55
  const art = this.csn.definitions[path[1]];
56
- if (!art.query && !art.projection && this.options.transformation === 'hdbcds' && member.target && member.default) {
57
- this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
58
- {
59
- std: 'Unexpected default defined on association',
60
- comp: 'Unexpected default defined on composition',
61
- });
56
+ if (this.options.transformation === 'hdbcds' && !art.query && !art.projection && member.target && member.default) {
57
+ const type = member._type?.type || member.type || 'cds.Association';
58
+ this.warning('type-invalid-default', path, { '#': type === 'cds.Association' ? 'std' : 'comp' }, {
59
+ std: 'Default on associations is not supported for HDBCDS',
60
+ comp: 'Default on compositions is not supported for HDBCDS',
61
+ });
62
62
  }
63
63
  }
64
64
 
@@ -119,18 +119,19 @@ function checkManagedAssoc( art ) {
119
119
  forEachMemberRecursively(art, (member) => {
120
120
  if (this.csnUtils.isAssocOrComposition(member) &&
121
121
  !isManagedComposition.bind(this)(member)) {
122
+ // Implementation note: Imported services (i.e. external ones) may contain to-many associations
123
+ // with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
124
+ // foreign key array, we won't emit a warning to avoid spamming the user.
122
125
  const max = member.cardinality?.max ? member.cardinality.max : 1;
123
- if (max !== 1 && !member.on) {
126
+ if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
124
127
  const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
125
- this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path,
126
- {
127
- value: cardinality2str(member, false),
128
- '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
129
- },
130
- {
131
- std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
132
- comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
133
- });
128
+ this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
129
+ value: cardinality2str(member, false),
130
+ '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
131
+ }, {
132
+ std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
133
+ comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
134
+ });
134
135
  }
135
136
  }
136
137
  });
@@ -16,8 +16,6 @@ const { setProp } = require('../base/model');
16
16
  function validateForeignKeys( member, memberName ) {
17
17
  // We have a managed association
18
18
  const isManagedAssoc = mem => mem && mem.target && !mem.on;
19
- // We have an unmanaged association
20
- const isUnmanagedAssoc = mem => mem && mem.target && mem.on && !mem.keys;
21
19
 
22
20
  // Declared as arrow-function to keep scope the same (this value)
23
21
  const handleAssociation = (mem) => {
@@ -46,9 +44,6 @@ function validateForeignKeys( member, memberName ) {
46
44
  if (mem.items) {
47
45
  this.error(null, member.$path, {}, 'Array-like properties must not be foreign keys');
48
46
  }
49
- else if (isUnmanagedAssoc(mem)) {
50
- this.error(null, member.$path, {}, 'Unmanaged association must not be a foreign key');
51
- }
52
47
  else if (mem.keys) {
53
48
  handleAssociation(mem);
54
49
  }
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
4
+
5
+ /**
6
+ * Check all refs in the given parent for the traversal of paths
7
+ * into `.items`
8
+ *
9
+ * @param {object} parent Object with the expression as a property
10
+ * @param {string} propOnParent Name of the expression property on parent
11
+ * @param {Array} e Expression to check - see module.exports
12
+ * @param {CSN.Path} path
13
+ */
14
+ function navigationIntoMany( parent, propOnParent, e, path ) {
15
+ applyTransformationsOnNonDictionary(parent, propOnParent, {
16
+ ref: (_parent, _prop, ref, _path) => {
17
+ const itemNavigationIndex = _parent._links?.findIndex(l => l.art.items);
18
+ if (itemNavigationIndex !== -1 && _parent.ref.length > itemNavigationIndex + 1)
19
+ this.message('ref-unexpected-many-navigation', _path);
20
+ },
21
+ }, { skipStandard: { type: true } }, path);
22
+ }
23
+
24
+ module.exports = {
25
+ columns: navigationIntoMany,
26
+ from: navigationIntoMany,
27
+ on: navigationIntoMany,
28
+ having: navigationIntoMany,
29
+ groupBy: navigationIntoMany,
30
+ orderBy: navigationIntoMany,
31
+ where: navigationIntoMany,
32
+ xpr: navigationIntoMany,
33
+ };