@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -60,7 +60,7 @@ const centralMessages = {
60
60
  'anno-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
61
61
  'anno-undefined-element': { severity: 'Warning' },
62
62
  'anno-undefined-param': { severity: 'Warning' },
63
- 'anno-unexpected-ellipsis-layers': { severity: 'Error', configurableFor: true }, // TODO(v3): Merge with anno-unexpected-ellipsis and make non-configurable
63
+ 'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'v3' },
64
64
 
65
65
  'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
66
66
  'args-no-params': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
@@ -159,6 +159,8 @@ const centralMessages = {
159
159
 
160
160
  'def-missing-element': { severity: 'Error' },
161
161
 
162
+ 'def-unsupported-calc-elem': { severity: 'Error', configurableFor: true },
163
+
162
164
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
163
165
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
164
166
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
@@ -183,6 +185,7 @@ const centralMessages = {
183
185
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
184
186
  'odata-anno-preproc': { severity: 'Warning', configurableFor: true },
185
187
  'odata-anno-dict': { severity: 'Warning', configurableFor: true },
188
+ 'odata-anno-vocref': { severity: 'Warning', configurableFor: true },
186
189
  'odata-anno-dict-enum': { severity: 'Error' },
187
190
  'odata-anno-value': { severity: 'Warning', configurableFor: true },
188
191
  'odata-anno-type': { severity: 'Warning', configurableFor: true },
@@ -218,27 +221,33 @@ for (const oldName in oldMessageIds) {
218
221
 
219
222
  const centralMessageTexts = {
220
223
  'api-invalid-option': {
221
- // TODO: too many different error situations for one message id,
222
- // TODO: use message parameter - do not use quotes in message texts
223
- std: 'Option $(NAME) is no longer supported! Use SNAPI options instead',
224
- magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
225
- user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
226
- locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
227
- 'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
224
+ std: 'Invalid option $(NAME)!',
225
+ deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
226
+ magicVars: 'Option $(PROP) is no longer supported! Use $(OTHERPROP) instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
227
+ },
228
+
229
+ 'api-invalid-variable-replacement': {
230
+ std: 'Option $(OPTION) does not support $(NAME)',
231
+ user: 'Option $(OPTION) expects $(PROP) instead of $(OTHERPROP). See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
232
+ locale: 'Option $(OPTION) expects $(PROP) instead of $(OTHERPROP). See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
233
+ 'noDollar': 'Option $(OPTION) does not know $(NAME). Did you forget a leading $(CODE)?'
228
234
  },
229
235
 
230
236
  'anno-duplicate': {
231
237
  std: 'Duplicate assignment with $(ANNO)',
232
238
  doc: 'Duplicate assignment with a doc comment',
233
239
  },
240
+ 'anno-duplicate-same-file': {
241
+ std: 'Duplicate assignment with $(ANNO), using last',
242
+ doc: 'Duplicate assignment with a doc comment, using last',
243
+ },
234
244
  'anno-duplicate-unrelated-layer': {
235
245
  std: 'Duplicate assignment with $(ANNO)',
236
246
  doc: 'Duplicate assignment with a doc comment',
237
247
  },
238
- 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
248
+ 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO)',
239
249
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
240
250
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
241
- 'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
242
251
  'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
243
252
 
244
253
  'check-proper-type-of': {
@@ -262,6 +271,12 @@ const centralMessageTexts = {
262
271
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name',
263
272
  },
264
273
 
274
+ 'name-missing-alias': {
275
+ std: 'Missing table alias for this subquery',
276
+ duplicate: 'Missing table alias for this subquery; add $(CODE) to fix name clash of internal and explicit table alias',
277
+ hdbcds: 'Missing table alias for a subquery; SAP HANA CDS requires table aliases'
278
+ },
279
+
265
280
  // Syntax messages, both CDL and CSN parser: ----------------------------------
266
281
  'syntax-deprecated-abstract': {
267
282
  std: 'Abstract entity definitions are deprecated; use aspect definitions instead',
@@ -294,9 +309,9 @@ const centralMessageTexts = {
294
309
  'std': 'Invalid literal value',
295
310
  'uneven-hex': 'A binary literal must have an even number of characters',
296
311
  'invalid-hex': 'A binary literal must only contain characters ‹0-9›, ‹a-f› and ‹A-F›',
297
- 'time': 'A time literal must look like ‹hh:mm:ss› or ‹hh:mm› where each letter represents a digit',
312
+ 'time': 'A time literal must look like ‹hh:mm:ss› or ‹hh:mm› where each letter represents a digit. A timezone is optional',
298
313
  'date': 'A date literal must look like ‹YYYY-MM-DD› where each letter represents a digit',
299
- 'timestamp': 'A timestamp literal must look like ‹YYYY-MM-DD hh:mm:ss.u…u› or ‹YYYY-MM-DD hh:mm› where each letter represents a digit, ‹u…u› represents 1 to 7 digits',
314
+ 'timestamp': 'A timestamp literal must look like ‹YYYY-MM-DD hh:mm:ss.u…u› or ‹YYYY-MM-DD hh:mm› where each letter represents a digit, ‹u…u› represents 1 to 7 digits. A timezone is optional',
300
315
  'number': 'The string value in property $(PROP) does not represent a number',
301
316
  'expecting': 'Expecting literal type $(OP) for the value in property $(OTHERPROP)',
302
317
  'typeof': 'String $(RAWVALUE) is no valid literal type for the string value in property $(OTHERPROP)',
@@ -360,7 +375,6 @@ const centralMessageTexts = {
360
375
  dynamic: 'Dynamic parameter $(CODE) is not supported',
361
376
  positional: 'Positional parameter $(CODE) is not supported',
362
377
  },
363
- // 'syntax-unsupported-method', 'syntax-unsupported-new'
364
378
 
365
379
  // Syntax messages, CSN parser - default: Error ------------------------------
366
380
  'syntax-deprecated-dollar-syntax': {
@@ -379,6 +393,10 @@ const centralMessageTexts = {
379
393
  'zero-parens': 'Deprecated CSN v.0.1.0 representation of expressions in parentheses',
380
394
  'zero-replace': 'Replace CSN v0.1.0 value in $(PROP) by $(VALUE)',
381
395
  },
396
+ 'syntax-deprecated-type-ref': {
397
+ std: 'Expecting a string as value for property $(PROP) for a reference to a definition',
398
+ 'ref-item': 'Expecting a string as value for property $(PROP) for a type reference to an element',
399
+ },
382
400
 
383
401
  'syntax-expecting-object': {
384
402
  std: 'Expecting object for property $(PROP)',
@@ -420,7 +438,7 @@ const centralMessageTexts = {
420
438
  extend: 'CSN property $(PROP) is not expected by an extend in $(PARENTPROP)',
421
439
  annotate: 'CSN property $(PROP) is not expected by an annotate in $(PARENTPROP)',
422
440
  },
423
- 'syntax-invalid-calc-elem': {
441
+ 'def-invalid-calc-elem': {
424
442
  std: 'Invalid calculated element',
425
443
  key: 'A primary key element can\'t be calculated',
426
444
  virtual: 'A virtual element can\'t be calculated',
@@ -430,7 +448,18 @@ const centralMessageTexts = {
430
448
  type: 'A type can\'t have calculated elements',
431
449
  action: 'An action can\'t have calculated elements',
432
450
  function: 'A function can\'t have calculated elements',
433
- annotation: 'Annotation definitions can\'t have calculated elements'
451
+ annotation: 'Annotation definitions can\'t have calculated elements',
452
+ param: 'Parameters can\'t have calculated elements',
453
+ },
454
+ 'ref-invalid-calc-elem': {
455
+ std: 'Can\'t include artifact with calculated element',
456
+ event: 'An event can\'t include an entity with calculated elements',
457
+ type: 'A type can\'t include an entity with calculated elements',
458
+ annotation: 'An annotation can\'t include an entity with calculated elements',
459
+ },
460
+ 'def-unsupported-calc-elem': {
461
+ std: 'Calculated elements are not supported',
462
+ nested: 'Calculated elements in structures are not supported, yet'
434
463
  },
435
464
  // 'syntax-unknown-property' (Warning? Better configurable Error)
436
465
 
@@ -489,6 +518,10 @@ const centralMessageTexts = {
489
518
  std: '$(ART) can\'t be extended because it originates from an include',
490
519
  elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
491
520
  },
521
+ 'ref-unexpected-scope': {
522
+ std: 'Unexpected parameter reference',
523
+ calc: 'Calculated elements can\'t use parameter references',
524
+ },
492
525
  'ref-unexpected-structured': {
493
526
  std: 'Unexpected usage of structured type $(ELEMREF)',
494
527
  expr: 'Structured elements can\'t be used in expressions',
@@ -503,6 +536,17 @@ const centralMessageTexts = {
503
536
  expr: 'Associations can\'t be used as values in expressions',
504
537
  cast: 'Casting to an association is not supported',
505
538
  },
539
+ 'ref-unexpected-calculated': {
540
+ std: 'Unexpected reference to calculated element',
541
+ on: 'Calculated elements can\'t be used in ON-conditions of unmanaged associations',
542
+ fkey: 'Calculated elements can\'t be used as foreign keys for managed associations',
543
+ },
544
+
545
+ 'ref-unexpected-navigation': {
546
+ std: 'Can\'t follow association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to, but not $(NAME)',
547
+ unmanaged: 'Can\'t follow unmanaged association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to',
548
+ unmanagedleaf: 'Unexpected unmanged association as final path step of $(ELEMREF) in an ON-condition',
549
+ },
506
550
 
507
551
  'type-unexpected-typeof': {
508
552
  std: 'Unexpected $(KEYWORD) for the type reference here',
@@ -533,26 +577,32 @@ const centralMessageTexts = {
533
577
  element: 'Artifact $(ART) has no element $(NAME)',
534
578
  enum: 'Artifact $(ART) has no enum $(NAME)',
535
579
  returns: 'Return value of $(ART) has no element $(NAME)',
536
- 'entity-element': 'Elements of entity types can\'t be annotated',
580
+ 'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
537
581
  },
538
582
  'anno-undefined-action': {
539
583
  std: 'Action $(ART) has not been found',
540
- action: 'Artifact $(ART) has no action $(MEMBER)'
584
+ action: 'Artifact $(ART) has no action $(NAME)'
541
585
  },
542
586
  'anno-undefined-param': {
543
587
  std: 'Parameter $(ART) has not been found',
544
- param: 'Artifact $(ART) has no parameter $(MEMBER)'
588
+ param: 'Artifact $(ART) has no parameter $(NAME)'
589
+ },
590
+
591
+ // annotation checks against their definition
592
+ 'anno-expecting-value': {
593
+ 'std': 'Expecting a value for the annotation; see annotation definition for $(ANNO)',
594
+ 'type': 'Expecting a value of type $(TYPE) for the annotation'
545
595
  },
546
596
 
547
597
  'def-unexpected-paramview-assoc': {
548
- std: 'SAP HANA does no support associations in/to parameterized entities',
549
- view: 'SAP HANA does no support associations in parameterized entities',
550
- target: 'SAP HANA does no support associations to parameterized entities',
598
+ std: 'SAP HANA doesn\'t support associations in/to parameterized entities',
599
+ view: 'SAP HANA doesn\'t support associations in parameterized entities',
600
+ target: 'SAP HANA doesn\'t support associations to parameterized entities',
551
601
  },
552
602
  'def-unexpected-calcview-assoc': {
553
- std: 'SAP HANA does not allow associations in/to entities annotated with $(ANNO)',
554
- 'entity-persistence': 'SAP HANA does not allow associations in entities annotated with $(ANNO)',
555
- 'target-persistence': 'SAP HANA does not allow associations pointing to entities annotated with $(ANNO)',
603
+ std: 'SAP HANA doesn\'t allow associations in/to entities annotated with $(ANNO)',
604
+ 'entity-persistence': 'SAP HANA doesn\'t allow associations in entities annotated with $(ANNO)',
605
+ 'target-persistence': 'SAP HANA doesn\'t allow associations pointing to entities annotated with $(ANNO)',
556
606
  },
557
607
  'def-unexpected-key': {
558
608
  std: '$(ART) can\'t have additional keys',
@@ -566,9 +616,13 @@ const centralMessageTexts = {
566
616
  include: '$(ART) can\'t have localized elements (through include)',
567
617
  },
568
618
  'def-unexpected-localized-anno': 'Annotations can\'t have localized elements',
619
+ 'type-unexpected-structure': {
620
+ std: 'Unexpected structured type', // unused variant
621
+ calc: 'A structured type can\'t be used for calculated elements',
622
+ },
569
623
 
570
624
  'def-missing-element': {
571
- std: 'Expecting entity to have at least one non-virtual element',
625
+ std: 'Expecting entity to have at least one element which is neither virtual nor calculated',
572
626
  view: 'Expecting view to have at least one non-virtual element'
573
627
  },
574
628
 
@@ -594,6 +648,14 @@ const centralMessageTexts = {
594
648
  action: 'Duplicate definition of action or function $(NAME)',
595
649
  param: 'Duplicate definition of parameter $(NAME)',
596
650
  alias: 'Duplicate definition of table alias or mixin $(NAME)',
651
+ 'include-elements': 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
652
+ 'include-actions': 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
653
+ },
654
+ // TODO: Remove in v4, use duplicate-definition
655
+ 'ref-duplicate-include-member': {
656
+ std: 'Duplicate member $(NAME) through multiple includes $(SORTED_ARTS)',
657
+ elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
658
+ actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
597
659
  },
598
660
 
599
661
  // TODO: rename to ref-expected-XYZ
@@ -612,18 +674,18 @@ const centralMessageTexts = {
612
674
  'extend-unexpected-include': 'Can\'t extend $(META) with includes',
613
675
  'ref-expecting-bare-aspect': 'An aspect without elements is expected here',
614
676
 
677
+ 'ext-duplicate-same-file': 'Duplicate extension with $(PROP) in same file',
615
678
  'ext-duplicate-extend-type': 'Duplicate type extension for type $(TYPE)',
616
679
  'ext-duplicate-extend-type-unrelated-layer': 'Duplicate type extension for type $(TYPE)',
617
680
  'ext-invalid-type-property': {
618
- std: 'Property $(PROP) can only be extended',
619
- 'new-prop': 'Property $(PROP) can only be extended, not added',
681
+ std: 'Type property $(PROP) can only be extended',
682
+ 'indirect': 'Type property $(PROP) can only be extended if directly provided at the definition',
683
+ 'new-prop': 'Type property $(PROP) can only be extended, not added',
684
+ string: 'Only numerical properties can be extended, but found string for $(PROP)',
620
685
  // eslint-disable-next-line max-len
621
- smaller: 'Property $(PROP) can only be extended, but $(VALUE) is smaller than $(NUMBER) of type definition',
686
+ number: 'Value of type property $(PROP) must be $(NUMBER) or higher, it can\'t be smaller than originally provided',
622
687
  // eslint-disable-next-line max-len
623
- 'ext-smaller': 'Property $(PROP) can only be extended, but extended value $(VALUE) is smaller than $(NUMBER) from previous type extension',
624
- prop: 'Type property $(PROP) can\'t be extended',
625
- scale: 'If property $(PROP) is increased, then so must $(OTHERPROP)',
626
- string: 'Only numerical properties can be extended, but found string for $(PROP)',
688
+ scale: 'With the extension for type property $(OTHERPROP), the value of $(PROP) must be $(NUMBER) or higher',
627
689
  },
628
690
  'ref-expected-scalar-type': {
629
691
  std: 'Only scalar type definitions can be extended with type properties',
@@ -645,12 +707,21 @@ const centralMessageTexts = {
645
707
  std: 'Expected identifier for select item',
646
708
  assoc: 'Expected identifier as the association\'s name',
647
709
  },
648
-
710
+ 'query-unsupported-calc': {
711
+ std: 'Using nested projections next to calculated elements is not supported, yet',
712
+ inside: 'Using calculated elements in nested projections is not supported, yet'
713
+ },
649
714
  'ref-sloppy-type': 'A type or an element is expected here',
650
715
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
651
716
  'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
652
717
  'ref-sloppy-event-type': 'A type, an element, an event, or a service entity is expected here',
653
718
 
719
+ 'ref-ambiguous': {
720
+ std: 'Replace ambiguous $(ID) by $(NAMES)',
721
+ few: 'Replace ambiguous $(ID) by $(NAMES) or a new table alias for sub-queries that don\'t have one',
722
+ none: 'Ambiguous $(ID) requires an explicit table alias, but there are none: add table aliases to all sub-queries to disambiguate $(ID)',
723
+ },
724
+
654
725
  'type-managed-composition': {
655
726
  std: 'Managed compositions can\'t be used in types', // yet
656
727
  sub: 'Managed compositions can\'t be used in sub elements',
@@ -658,6 +729,14 @@ const centralMessageTexts = {
658
729
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
659
730
  },
660
731
 
732
+ // -----------------------------------------------------------------------------------
733
+ // Expressions
734
+ // -----------------------------------------------------------------------------------
735
+ 'expr-invalid-operator': 'Comparing $(ID) is only allowed with $(OP)',
736
+ 'expr-missing-comparison': {
737
+ std: 'Expected a comparison with $(OP) when using $(ID) in an ON-condition',
738
+ },
739
+
661
740
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
662
741
 
663
742
  // OData version dependent messages
@@ -669,7 +748,8 @@ const centralMessageTexts = {
669
748
  'odata-spec-violation-id': {
670
749
  std: 'Expected EDM name $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
671
750
  'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
672
- 'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
751
+ 'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
752
+ 'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
673
753
  },
674
754
  // version independent messages
675
755
  'odata-spec-violation-key-array': {
@@ -738,6 +818,12 @@ const centralMessageTexts = {
738
818
  'experimental': '$(ANNO) is experimental and can be changed or removed at any time, do not use productively!',
739
819
  'redefinition': '$(ANNO) is an official OASIS/SAP annotation and can\'t be redefined'
740
820
  },
821
+ 'odata-anno-vocref': {
822
+ 'std': 'Vocabulary reference $(ID) doesn\'t match alias $(NAME), reference is ignored',
823
+ 'redef': 'Vocabulary reference $(ID) is the alias of the official OASIS/SAP vocabulary $(TYPE) which can\'t be redefined, reference is ignored',
824
+ 'service': 'Vocabulary reference collides with service $(NAME), reference is ignored',
825
+ 'malformed': 'Vocabulary reference $(ID) has invalid or missing value for attribute $(NAME), reference is ignored'
826
+ },
741
827
  'odata-anno-dict-enum': {
742
828
  'std' : 'Unexpected annotation definition $(NAME) with many enum type',
743
829
  'type': 'Unexpected annotation definition $(NAME) with many enum type $(TYPE)',
@@ -11,6 +11,7 @@ const { centralMessages, centralMessageTexts, oldMessageIds } = require('./messa
11
11
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
12
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
13
13
  const { CompilerAssertion } = require('./error');
14
+ const { getArtifactName } = require('../compiler/base');
14
15
 
15
16
  const fs = require('fs');
16
17
  const path = require('path');
@@ -766,6 +767,12 @@ function transformManyWith( t, sorted ) {
766
767
  };
767
768
  }
768
769
 
770
+ /**
771
+ * Quote the given string. Performs a type sanity check.
772
+ *
773
+ * @param {string} name
774
+ * @return {string}
775
+ */
769
776
  function quoted( name ) {
770
777
  if (typeof name === 'string')
771
778
  return quote.double( name );
@@ -835,6 +842,7 @@ const nameProp = {
835
842
  function: 'action',
836
843
  };
837
844
 
845
+ // TODO: very likely delete this function
838
846
  function searchName( art, id, variant ) {
839
847
  if (!variant) {
840
848
  // used to mention the "effective" type in the message, not the
@@ -912,16 +920,16 @@ function weakLocation( loc ) {
912
920
  * @param {boolean} [noHome]
913
921
  * @returns {string}
914
922
  */
915
- function messageString( err, normalizeFilename, noMessageId, noHome ) {
916
- return (err.$location && err.$location.file
917
- ? `${ locationString( err.$location, normalizeFilename ) }: `
918
- : '') +
919
- (err.severity || 'Error') +
920
- // TODO: use [message-id]
921
- (err.messageId && !noMessageId ? ` ${ err.messageId }: ` : ': ') +
922
- err.message +
923
- // even with noHome, print err.home if the location is weak
924
- (!err.home || noHome && err.$location && err.$location.endLine ? '' : ` (in ${ err.home })`);
923
+ function messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) {
924
+ const location = (err.$location?.file ? `${ locationString( err.$location, normalizeFilename ) }: ` : '');
925
+ const severity = err.severity || 'Error';
926
+ const downgradable = severity === 'Error' && moduleName &&
927
+ isDowngradable(err.messageId, moduleName, false) ? '‹↓›' : '';
928
+ // even with noHome, print err.home if the location is weak
929
+ const home = !err.home || noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
930
+ // TODO: the plan was with brackets = `Error[ref-undefined-def]`
931
+ const id = err.messageId && !noMessageId ? ` ${ err.messageId }` : '';
932
+ return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;
925
933
  }
926
934
 
927
935
  /**
@@ -1180,7 +1188,7 @@ function deduplicateMessages( messages ) {
1180
1188
  }
1181
1189
 
1182
1190
  function shortArtName( art ) {
1183
- const { name } = art;
1191
+ const name = getArtifactName( art );
1184
1192
  if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1185
1193
  !name.absolute.includes(':'))
1186
1194
  return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
@@ -1188,13 +1196,13 @@ function shortArtName( art ) {
1188
1196
  }
1189
1197
 
1190
1198
  function artName( art, omit ) {
1191
- const { name } = art;
1199
+ const name = getArtifactName( art );
1192
1200
  const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1193
1201
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1194
1202
  r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
1195
1203
  if (name.action && omit !== 'action')
1196
1204
  r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1197
- if (name.alias && art.kind !== '$self')
1205
+ if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
1198
1206
  r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1199
1207
  if (name.param != null && omit !== 'param')
1200
1208
  r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
@@ -1225,7 +1233,7 @@ function memberActionName( art ) {
1225
1233
  function homeName( art, absoluteOnly ) {
1226
1234
  if (!art)
1227
1235
  return art;
1228
- if (art._outer) // in returns / items property
1236
+ if (art._outer) // in items property
1229
1237
  return homeName( art._outer, absoluteOnly );
1230
1238
  else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
1231
1239
  return null;
@@ -1245,7 +1253,10 @@ function homeName( art, absoluteOnly ) {
1245
1253
 
1246
1254
  // The "home" for extensions is handled differently because `_artifact` is not
1247
1255
  // set for unknown extensions, and we could have nested extensions.
1256
+ // TODO: delete this function, just set correct name/_parent for extensions
1248
1257
  function homeNameForExtend( art ) {
1258
+ if (!art.name.absolute && art._main) // new-style member name
1259
+ return `${ art._main.kind }:${ artName( art ) }`;
1249
1260
  const kind = art.kind || 'extend';
1250
1261
  // TODO: fix the following - do like in collectArtifactExtensions() or
1251
1262
  // basically resolveUncheckedPath()
@@ -1309,6 +1320,12 @@ function homeNameForExtend( art ) {
1309
1320
  function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1310
1321
  if (!model)
1311
1322
  return null;
1323
+
1324
+ if (options.testMode)
1325
+ sanitizeCsnPath(csnPath);
1326
+
1327
+ const _quoted = options.testMode ? quoted : quote.double;
1328
+
1312
1329
  let result = '';
1313
1330
  const csnDictionaries = [
1314
1331
  'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
@@ -1338,12 +1355,12 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1338
1355
  if (step === 'definitions') {
1339
1356
  next(); // "definitions"
1340
1357
  const kind = currentThing?.kind || 'artifact';
1341
- result += `${ kind }:${ quoted(step) }`;
1358
+ result += `${ kind }:${ _quoted(step) }`;
1342
1359
  }
1343
1360
  else if (step === 'vocabularies') {
1344
1361
  next(); // dictionary name
1345
1362
  if (index < csnPath.length)
1346
- result += `annotation:${ quoted(csnPath[index]) }`;
1363
+ result += `annotation:${ _quoted(csnPath[index]) }`;
1347
1364
  else
1348
1365
  result += 'vocabularies';
1349
1366
  }
@@ -1355,7 +1372,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1355
1372
  else {
1356
1373
  const name = currentThing.annotate || currentThing.extend;
1357
1374
  const kind = currentThing.annotate ? 'annotate' : 'extend';
1358
- result += `${ kind }:${ quoted(name) }`;
1375
+ result += `${ kind }:${ _quoted(name) }`;
1359
1376
  }
1360
1377
  }
1361
1378
 
@@ -1381,7 +1398,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1381
1398
  next();
1382
1399
  }
1383
1400
  if (elementHierarchy.length > 0)
1384
- result += `/element:${quoted(elementHierarchy.join('.'))}`;
1401
+ result += `/element:${ _quoted(elementHierarchy.join('.')) }`;
1385
1402
  // no trailing /elements or /items
1386
1403
  continue;
1387
1404
  }
@@ -1389,7 +1406,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1389
1406
  next(); // "actions"
1390
1407
  if (index < csnPath.length) {
1391
1408
  const kind = currentThing?.kind || 'action';
1392
- result += `/${ kind }:${ quoted(csnPath[index]) }`;
1409
+ result += `/${ kind }:${ _quoted(csnPath[index]) }`;
1393
1410
  }
1394
1411
  else { // actions is last segment
1395
1412
  result += '/actions';
@@ -1419,7 +1436,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1419
1436
  }
1420
1437
  else if (step === 'target') {
1421
1438
  if (currentThing)
1422
- result += '/target:' + quoted(currentThing);
1439
+ result += '/target:' + _quoted(currentThing);
1423
1440
  else
1424
1441
  result += '/target';
1425
1442
  break;
@@ -1447,14 +1464,14 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1447
1464
  next(); // "keys"
1448
1465
  if (index < csnPath.length) {
1449
1466
  const key = aliasOrReference();
1450
- result += `/key:${ key ? quoted(key) : step }`;
1467
+ result += `/key:${ key ? _quoted(key) : step }`;
1451
1468
  }
1452
1469
  break;
1453
1470
  }
1454
1471
  else if (step[0] === '@') {
1455
1472
  // Annotations are always the last step.
1456
1473
  // Nothing comes after them, everything is user defined.
1457
- result += `/${ quoted(csnPath[index]) }`;
1474
+ result += `/${ _quoted(csnPath[index]) }`;
1458
1475
  break;
1459
1476
  }
1460
1477
  else {
@@ -1470,7 +1487,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1470
1487
  function dictEntry( prefix ) {
1471
1488
  next(); // dictionary name
1472
1489
  if (index < csnPath.length)
1473
- result += `/${ prefix }:${ quoted(csnPath[index]) }`;
1490
+ result += `/${ prefix }:${ _quoted(csnPath[index]) }`;
1474
1491
  }
1475
1492
 
1476
1493
  /**
@@ -1548,7 +1565,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1548
1565
  } while (index < csnPath.length);
1549
1566
 
1550
1567
  if (elementHierarchy.length > 0)
1551
- result += `/column:${ quoted(elementHierarchy.join('.')) }`;
1568
+ result += `/column:${ _quoted(elementHierarchy.join('.')) }`;
1552
1569
  }
1553
1570
  else if(step === 'args') {
1554
1571
  // Should only be reached for cases, where no SELECT in a union is picked.
@@ -1578,11 +1595,11 @@ function queryDepthForMessage( csnPath, model, view ) {
1578
1595
  if (!targetQuery)
1579
1596
  return 0;
1580
1597
  const rootQuery = view.query || { SELECT: view.projection };
1581
- let depth = 1;
1598
+ let depth = 0;
1582
1599
  let totalDepth = 0;
1583
1600
  let isFound = false;
1584
- traverseQuery(rootQuery, null, null, (q, querySelect) => {
1585
- if (querySelect) {
1601
+ traverseQuery(rootQuery, null, null, (q) => {
1602
+ if (q.SELECT) {
1586
1603
  totalDepth += 1;
1587
1604
  if (!isFound)
1588
1605
  depth += 1;
@@ -1595,6 +1612,13 @@ function queryDepthForMessage( csnPath, model, view ) {
1595
1612
  return 0;
1596
1613
  }
1597
1614
 
1615
+ function sanitizeCsnPath(csnPath) {
1616
+ for (const step of csnPath) {
1617
+ if (typeof step !== 'string' && typeof step !== 'number')
1618
+ throw new CompilerAssertion(`Found CSN path step that is neither string nor number: ${step} ${ JSON.stringify(csnPath) }`)
1619
+ }
1620
+ }
1621
+
1598
1622
  /**
1599
1623
  * Get the explanation string for the given message-id.
1600
1624
  * Ensure to have called hasMessageExplanation() before.
package/lib/base/model.js CHANGED
@@ -21,7 +21,7 @@ const queryOps = {
21
21
  */
22
22
  const availableBetaFlags = {
23
23
  // enabled by --beta-mode
24
- calculatedElements: true,
24
+ annotationExpressions: true,
25
25
  toRename: true,
26
26
  assocsWithParams: true,
27
27
  hanaAssocRealCardinality: true,
@@ -35,6 +35,7 @@ const availableBetaFlags = {
35
35
  optionalActionFunctionParameters: true,
36
36
  // disabled by --beta-mode
37
37
  nestedServices: false,
38
+ v4preview: false,
38
39
  };
39
40
 
40
41
  const availableDeprecatedFlags = {
@@ -110,7 +111,7 @@ function isDeprecatedEnabled( options, feature = null ) {
110
111
  * to change their code, emit an error if one of such removed flags was used.
111
112
  *
112
113
  * @param {CSN.Options} options
113
- * @param error Error message function returned by makeMessageFunctions().
114
+ * @param error Error message function returned by makeMessageFunction().
114
115
  */
115
116
  function checkRemovedDeprecatedFlags( options, { error } ) {
116
117
  // Assume that we emitted these errors once if a message with this ID was found.
@@ -127,6 +128,7 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
127
128
 
128
129
  // Apply function `callback` to all artifacts in dictionary
129
130
  // `model.definitions`. See function `forEachGeneric` for details.
131
+ // TODO: should we skip "namespaces" already here?
130
132
  function forEachDefinition( model, callback ) {
131
133
  forEachGeneric( model, 'definitions', callback );
132
134
  }
@@ -2,7 +2,8 @@
2
2
 
3
3
  // By <https://github.com/bryc/code/blob/c97a26ad27a9f9d4f48cd3307fd8ee6f1772d4eb/jshash/PRNGs.md>:
4
4
 
5
- // The random seed must be an integer between 0 and 4294967295 (or 1 and 4294967296 = 2**32)
5
+ // The random seed must be an integer between 1 and 4294967296 (= 2**32),
6
+ // otherwise no shuffling takes place.
6
7
  function shuffleGen( seed ) {
7
8
  return (Number.isSafeInteger( seed ) && seed > 0)
8
9
  ? { shuffleArray, shuffleDict }
@@ -32,7 +32,7 @@ function validateAssociationsInItems( member ) {
32
32
  }
33
33
  }
34
34
  };
35
- if (this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items && member.$path[2] === 'elements') {
35
+ if (this.artifact && this.artifact.kind === 'entity' && member && member.items && member.$path[2] === 'elements') {
36
36
  if (member.items.type) {
37
37
  const type = member.items.type.ref
38
38
  ? this.artifactRef(member.items.type)
@@ -53,7 +53,7 @@ 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 && this.options.transformation === 'hdbcds' && member.target && member.default) {
56
+ if (!art.query && !art.projection && this.options.transformation === 'hdbcds' && member.target && member.default) {
57
57
  this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
58
58
  {
59
59
  std: 'Unexpected default defined on association',
@@ -208,6 +208,34 @@ function checkRecursiveTypeUsage( art ) {
208
208
  }
209
209
  }
210
210
  }
211
+
212
+ /**
213
+ * Member validator to check that certain annotations (@cds.valid { from, to, key }) are not
214
+ * assigned to calculated elements in an entity.
215
+ *
216
+ * TODO: Allow @cds.valid on persisted calculated elements (when they become available).
217
+ *
218
+ * @param {CSN.Element} member the element to be checked
219
+ * @param {string} _memberName the elements name
220
+ * @param {string} _prop which kind of member are we looking at -> only prop "elements"
221
+ * @param {CSN.Path} _path the path to the member
222
+ */
223
+ function rejectAnnotationsOnCalcElement( member, _memberName, _prop, _path ) {
224
+ if (this.artifact.kind === 'entity' && !(this.artifact.query && this.artifact.projection)) {
225
+ if (member.value) {
226
+ for (const anno in member) {
227
+ if (anno.startsWith('@cds.valid.')) {
228
+ this.error('anno-unexpected-temporal', member.$path, { anno },
229
+ 'Unexpected $(ANNO) assigned to a calculated element');
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
211
235
  module.exports = {
212
- checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
236
+ checkPrimaryKey,
237
+ checkVirtualElement,
238
+ checkManagedAssoc,
239
+ checkRecursiveTypeUsage,
240
+ rejectAnnotationsOnCalcElement,
213
241
  };