@sap/cds-compiler 4.0.0 → 4.1.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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. 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"
@@ -63,46 +64,36 @@ const centralMessages = {
63
64
  'anno-undefined-param': { severity: 'Warning' },
64
65
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
65
66
 
66
- 'args-expecting-named': { severity: 'Error' },
67
- 'args-no-params': { severity: 'Error' },
68
- 'args-undefined-param': { severity: 'Error' },
69
-
70
67
  'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
71
68
 
72
69
  'type-invalid-items': { severity: 'Error' }, // not supported yet
73
70
  'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
71
+ 'def-unexpected-nested-proj': { severity: 'Error', configurableFor: 'v4' },
74
72
  'def-unexpected-paramview-assoc': { severity: 'Error' },
75
73
  'def-unexpected-calcview-assoc': { severity: 'Error' },
76
74
  'chained-array-of': { severity: 'Error' },
77
- 'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
75
+ 'def-missing-type': { severity: 'Error', configurableFor: [ 'compile' ] },
78
76
  'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
79
77
 
80
78
  'def-duplicate-autoexposed': { severity: 'Error' },
81
79
  'def-unexpected-default': { severity: 'Error', configurableFor: 'test' },
82
80
 
83
- 'expr-no-filter': { severity: 'Error' },
81
+ 'expr-unexpected-filter': { severity: 'Error' },
84
82
 
85
83
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
86
84
 
87
85
  'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
88
- 'ref-expecting-type': { severity: 'Error' },
89
- 'ref-sloppy-type': { severity: 'Error' },
86
+ 'ref-invalid-type': { severity: 'Error' },
90
87
  'ref-unexpected-self': { severity: 'Error' },
91
- 'ref-expecting-bare-aspect': { severity: 'Error' },
88
+ 'ref-invalid-include': { severity: 'Error' },
92
89
  'type-unexpected-typeof': { severity: 'Error' },
93
90
  'type-ignoring-argument': { severity: 'Error', configurableFor: true },
94
91
  'type-expected-builtin': { severity: 'Error', configurableFor: true },
95
92
  '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
93
  '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' },
94
+ 'ref-invalid-source': { severity: 'Error' },
95
+ 'ref-invalid-target': { severity: 'Error' },
96
+ 'ref-sloppy-target': { severity: 'Error', configurableFor: 'v4' },
106
97
 
107
98
  'extend-repeated-intralayer': { severity: 'Warning' },
108
99
  'extend-unrelated-layer': { severity: 'Info' },
@@ -166,15 +157,13 @@ const centralMessages = {
166
157
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
167
158
 
168
159
  'type-managed-composition': { severity: 'Error' },
169
- 'type-unsupported-precision-change': { severity: 'Error'},
160
+ 'type-unsupported-precision-change': { severity: 'Error' },
161
+ 'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
170
162
 
171
163
  'def-missing-element': { severity: 'Error' },
172
164
 
173
165
  'def-unsupported-calc-elem': { severity: 'Error', configurableFor: true },
174
166
 
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
167
  'def-invalid-key-cardinality': { severity: 'Error' },
179
168
  // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds
180
169
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
@@ -213,6 +202,8 @@ const oldMessageIds = createDict({
213
202
  'old-anno-duplicate': 'anno-duplicate', // Example
214
203
  'assoc-in-array': 'type-invalid-items',
215
204
  'duplicate-autoexposed': 'def-duplicate-autoexposed',
205
+ 'expr-no-filter': 'expr-unexpected-filter',
206
+ 'check-proper-type': 'def-missing-type',
216
207
  });
217
208
 
218
209
  // Set up the old-to-new message ID mapping in the message registry.
@@ -511,7 +502,12 @@ const centralMessageTexts = {
511
502
  // Messages for erroneous references -----------------------------------------
512
503
  // location at erroneous reference (if possible)
513
504
  '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',
505
+ 'ref-unexpected-self': {
506
+ std: 'Unexpected $(ID) reference; is valid only in ON-conditions of unmanaged associations',
507
+ on: 'Unexpected $(ID) reference; is valid only if compared to be equal to an association of the target side',
508
+ subQuery: 'Unexpected $(ID) reference in a sub query',
509
+ setQuery: 'Unexpected $(ID) reference in a query on the right side of $(OP)',
510
+ },
515
511
  'ref-undefined-def': {
516
512
  std: 'Artifact $(ART) has not been found',
517
513
  // TODO: proposal 'No definition of $(NAME) found',
@@ -565,7 +561,11 @@ const centralMessageTexts = {
565
561
  },
566
562
  'ref-unexpected-assoc': {
567
563
  std: 'Unexpected reference to association $(NAME)', // "std" currently unused
564
+ unmanaged: 'Unexpected reference to an unmanaged association',
565
+ 'self-unmanaged': 'Unexpected column reference starting with $(ALIAS) to an unmanaged association',
566
+ self: 'A reference to an unmanaged association is only valid when compared via $(CODE)',
568
567
  expr: 'Associations can\'t be used as values in expressions',
568
+ 'expr-comp': 'Compositions can\'t be used as values in expressions',
569
569
  cast: 'Casting to an association is not supported',
570
570
  },
571
571
  'ref-unexpected-calculated': {
@@ -575,7 +575,7 @@ const centralMessageTexts = {
575
575
  },
576
576
  'ref-unexpected-localized': {
577
577
  std: 'Unexpected reference to localized element $(NAME)', // "std" currently unused
578
- calc: 'Calculated elements can\'t refer to localized elements',
578
+ calc: 'Calculated elements "on-write" can\'t refer to localized elements',
579
579
  },
580
580
 
581
581
  'ref-unexpected-navigation': {
@@ -624,6 +624,14 @@ const centralMessageTexts = {
624
624
  comp: 'Unexpected composition inside $(PROP)',
625
625
  },
626
626
 
627
+ 'type-unexpected-default': {
628
+ std: 'Unexpected $(KEYWORD) on an association/composition', // unused
629
+ multi: 'Unexpected $(KEYWORD); expected exactly one foreign key in combination with default value, but found $(COUNT)',
630
+ structured: 'Unexpected $(KEYWORD) in combination with structured foreign key $(NAME); $(KEYWORD) requires a non-structured foreign key',
631
+ 'onCond': 'Unexpected $(KEYWORD) on an association/composition with ON-condition; $(KEYWORD) requires exactly one foreign key',
632
+ 'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
633
+ },
634
+
627
635
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
628
636
  'anno-undefined-def': 'Artifact $(ART) has not been found', // TODO: ext-
629
637
  'anno-undefined-art': 'No artifact has been found with name $(ART)',
@@ -654,14 +662,25 @@ const centralMessageTexts = {
654
662
  source: 'Unexpected definition of an association in an entity with parameters',
655
663
  target: 'Expected association target to have no parameters',
656
664
  },
665
+ 'def-unexpected-nested-proj': {
666
+ std: 'Unexpected $(CODE)',
667
+ var: 'Unexpected $(CODE) after reference to CDS variable',
668
+ struct: 'Unexpected $(CODE); can only be used after a reference to a structure or association',
669
+ init: 'Unexpected $(CODE); can only be used after a reference to a structure, association or table alias',
670
+ },
657
671
  'def-unexpected-calcview-assoc': {
658
672
  std: 'unused',
659
673
  'source': 'Unexpected definition of an association in an entity annotated with $(ANNO)',
660
674
  'target': 'Expected association target not to be annotated with $(ANNO)',
661
675
  },
676
+ 'def-invalid-key': {
677
+ std: 'The current element can\'t be defined as primary key', // (unused)
678
+ unmanaged: 'Unmanaged associations/compositions can\'t be defined as primary key',
679
+ composition: 'Managed aspect compositions can\'t be defined as primary key',
680
+ },
662
681
  'def-unexpected-key': {
663
682
  std: '$(ART) can\'t have additional keys',
664
- virtual: 'Unexpected $(PROP) for virtual element $(NAME)',
683
+ virtual: 'Unexpected $(PROP) for virtual element $(ART)',
665
684
  // TODO: Better message?
666
685
  include: '$(ART) can\'t have additional keys (through include)',
667
686
  },
@@ -725,19 +744,27 @@ const centralMessageTexts = {
725
744
  missing: 'Expected element $(ID) to have at least all the same sub-elements as included artifacts, but it is missing $(NAME)'
726
745
  },
727
746
 
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',
747
+ 'ref-expecting-const': 'A constant expression or variable is expected here',
748
+ 'ref-invalid-target': {
749
+ std: 'An entity, projection or view is expected here', // TODO: change text
750
+ composition: 'Expecting an entity or aspect as composition target',
751
+ bare: 'Expecting the target aspect to have elements',
752
+ aspect: 'Expecting the name of an aspect in property $(PROP)', // only CSN input
753
+ },
754
+ 'ref-invalid-include': {
755
+ std: 'A type, entity, aspect or event with direct elements is expected here',
756
+ bare : 'An aspect without elements is expected here',
757
+ },
758
+ 'ref-invalid-type': {
759
+ std: 'A type or an element is expected here',
760
+ param: 'A type, an element, or a service entity is expected here',
761
+ event: 'A type, an element, an event, or a service entity is expected here',
762
+ },
734
763
  // 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
764
+ 'ref-invalid-source': 'A query source must be an entity or an association',
737
765
  'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
738
766
  'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
739
767
  'extend-unexpected-include': 'Can\'t extend $(META) with includes',
740
- 'ref-expecting-bare-aspect': 'An aspect without elements is expected here',
741
768
 
742
769
  'ext-duplicate-same-file': 'Duplicate extension with $(PROP) in same file',
743
770
  'ext-duplicate-extend-type': 'Duplicate type extension for type $(TYPE)',
@@ -787,6 +814,8 @@ const centralMessageTexts = {
787
814
  target: 'Expected target $(TARGET) of specified element $(NAME) to be the same as the inferred element\'s target $(ART)',
788
815
  foreignKeys: 'Expected foreign keys of specified element $(NAME) to be the same as the inferred element\'s foreign keys',
789
816
  prop: 'Value for $(PROP) of the specified element $(NAME) does not match the inferred element\'s value',
817
+ enumExtra: 'Specified element $(NAME) differs from inferred element: it has an additional enum element $(ID)',
818
+ enumVal: 'Specified element $(NAME) differs from inferred element: it has a different value for enum element $(ID)',
790
819
  },
791
820
 
792
821
  'query-unexpected-property': {
@@ -794,10 +823,7 @@ const centralMessageTexts = {
794
823
  calculatedElement: 'Unexpected property $(PROP) in the specified element $(NAME); calculated elements are not supported in queries',
795
824
  },
796
825
 
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
826
  '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
827
 
802
828
  'ref-ambiguous': {
803
829
  std: 'Replace ambiguous $(ID) by $(NAMES)',
@@ -812,6 +838,16 @@ const centralMessageTexts = {
812
838
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
813
839
  },
814
840
 
841
+ 'type-unsupported-key-change': {
842
+ std: 'Added element $(ID) is a primary key change and will not work if the table contains data',
843
+ changed: 'Changed element $(ID) is a primary key change and will not work if the table contains data'
844
+ },
845
+
846
+ 'type-unsupported-key-sqlite': {
847
+ std: 'Added element $(ID) is a primary key change and will not work with dialect $(NAME)',
848
+ changed: 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)'
849
+ },
850
+
815
851
  // -----------------------------------------------------------------------------------
816
852
  // Expressions
817
853
  // -----------------------------------------------------------------------------------
@@ -820,6 +856,16 @@ const centralMessageTexts = {
820
856
  std: 'Expected a comparison with $(OP) when using $(ID) in an ON-condition',
821
857
  },
822
858
 
859
+ 'type-invalid-cardinality': {
860
+ std: 'Invalid value $(VALUE) for cardinality', // unused variant
861
+ sourceMax: 'Invalid value $(PROP) for maximum source cardinality, expecting a positive number or $(OTHERPROP)',
862
+ targetMax: 'Invalid value $(PROP) for maximum target cardinality, expecting a positive number or $(OTHERPROP)',
863
+ targetMin: 'Invalid value $(PROP) for minimum target cardinality, expecting a non-negative number',
864
+ sourceMin: 'Invalid value $(PROP) for minimum source cardinality, expecting a non-negative number',
865
+ sourceVal: 'Source minimum cardinality must not be greater than source maximum cardinality',
866
+ targetVal: 'Target minimum cardinality must not be greater than target maximum cardinality',
867
+ },
868
+
823
869
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
824
870
 
825
871
  // OData version dependent messages
@@ -948,7 +994,7 @@ const centralMessageTexts = {
948
994
  *
949
995
  * @typedef {object} MessageConfig
950
996
  * @property {MessageSeverity} severity Default severity for the message.
951
- * @property {string[]|'deprecated'|'v4'|true} [configurableFor]
997
+ * @property {string[]|'deprecated'|'v4'|'test'|true} [configurableFor]
952
998
  * Whether the error can be reclassified to a warning or lower.
953
999
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
954
1000
  * 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
  /**
@@ -1310,14 +1311,14 @@ function artName( art, omit ) {
1310
1311
  const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1311
1312
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1312
1313
  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')
1314
+ if (name.action != null && omit !== 'action')
1314
1315
  r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1315
- if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
1316
+ if (name.alias != null && art.kind !== '$self' && name.$inferred !== '$internal')
1316
1317
  r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1317
1318
  if (name.param != null && omit !== 'param')
1318
1319
  r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
1319
1320
 
1320
- if (name.element && omit !== 'element') {
1321
+ if (name.element != null && omit !== 'element') {
1321
1322
  if (name.select != null && !art.$inferred)
1322
1323
  r.push( 'column:' + quoted( name.element ) );
1323
1324
  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,13 @@ 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,
33
37
  // disabled by --beta-mode
34
38
  nestedServices: false,
35
39
  };
@@ -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
 
@@ -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
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { forEachGeneric } = require('../model/csnUtils');
4
4
  const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
5
+ const { pathId } = require('../model/csnRefs');
5
6
 
6
7
  // Only to be used with validator.js - a correct this value needs to be provided!
7
8
 
@@ -93,7 +94,7 @@ function validateOnCondition( member, memberName, property, path ) {
93
94
  }
94
95
  else {
95
96
  // It's a managed association - access of the foreign keys is allowed
96
- checkForeignKeyAccess(member.on[i], j, csnPath, (errorIndex) => {
97
+ requireForeignKeyAccess(member.on[i], j, (errorIndex) => {
97
98
  this.error('ref-unexpected-navigation', csnPath, {
98
99
  '#': 'std', id, elemref, name: ref[errorIndex].id || ref[errorIndex],
99
100
  });
@@ -158,22 +159,26 @@ function validateOnCondition( member, memberName, property, path ) {
158
159
 
159
160
  /**
160
161
  * Ensure that only foreign keys of the association `parent.ref[refIndex]` are accessed in `parent.ref`.
161
- * If a non-fk field is accessed, `callback` is invoked.
162
+ * If a non-fk field is accessed, `noForeignKeyCallback` is invoked.
162
163
  *
163
- * @param {object} parent Object containing `ref` and `_links` from csnRefs.
164
- * @param {number} refIndex Index of the to-be-checked association in `parent.ref`
165
- * @param {CSN.Path} csnPath
166
- * @param {(errorIndex: number) => void} callback Called if there are non-fk path steps. Argument is index in
167
- * `parent.ref` that is faulty. If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
164
+ * @param {object} parent
165
+ * Object containing `ref` and `_links` from csnRefs.
166
+ *
167
+ * @param {number} refIndex
168
+ * Index of the to-be-checked association in `parent.ref`
169
+ *
170
+ * @param {(errorIndex: number) => void} noForeignKeyCallback
171
+ * Called if there are non-fk path steps. Argument is index in `parent.ref` that is faulty.
172
+ * If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
168
173
  */
169
- function checkForeignKeyAccess( parent, refIndex, csnPath, callback ) {
174
+ function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
170
175
  const { ref, _links } = parent;
171
176
  const assoc = _links[refIndex].art;
172
177
 
173
- const next = ref[refIndex + 1].id || ref[refIndex + 1];
178
+ const next = pathId(ref[refIndex + 1]);
174
179
  let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
175
180
  if (!possibleKeys || possibleKeys.length === 0) {
176
- callback(refIndex + 1);
181
+ noForeignKeyCallback(refIndex + 1);
177
182
  }
178
183
  else {
179
184
  // For cases where `Association to T { struct.one, struct.two };` is used instead of `{ struct }`.
@@ -195,7 +200,7 @@ function checkForeignKeyAccess( parent, refIndex, csnPath, callback ) {
195
200
  ++fkIndex;
196
201
  }
197
202
  if (!success)
198
- callback(refIndex + fkIndex);
203
+ noForeignKeyCallback(refIndex + fkIndex);
199
204
  }
200
205
  }
201
206
 
@@ -211,4 +216,4 @@ function validateMixinOnCondition( query, path ) {
211
216
  forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
212
217
  }
213
218
 
214
- module.exports = { validateOnCondition, validateMixinOnCondition, checkForeignKeyAccess };
219
+ module.exports = { validateOnCondition, validateMixinOnCondition, requireForeignKeyAccess };