@sap/cds-compiler 3.6.2 → 3.7.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 (68) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +9 -5
  4. package/doc/CHANGELOG_BETA.md +20 -2
  5. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  6. package/lib/api/main.js +2 -1
  7. package/lib/base/dictionaries.js +10 -0
  8. package/lib/base/message-registry.js +56 -12
  9. package/lib/base/messages.js +39 -20
  10. package/lib/base/model.js +1 -0
  11. package/lib/base/shuffle.js +2 -1
  12. package/lib/checks/elements.js +29 -1
  13. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
  14. package/lib/checks/nonexpandableStructured.js +1 -1
  15. package/lib/checks/onConditions.js +8 -5
  16. package/lib/checks/types.js +6 -1
  17. package/lib/checks/validator.js +7 -3
  18. package/lib/compiler/assert-consistency.js +20 -23
  19. package/lib/compiler/base.js +1 -2
  20. package/lib/compiler/builtins.js +2 -2
  21. package/lib/compiler/checks.js +237 -242
  22. package/lib/compiler/define.js +63 -75
  23. package/lib/compiler/extend.js +325 -22
  24. package/lib/compiler/finalize-parse-cdl.js +1 -55
  25. package/lib/compiler/kick-start.js +6 -7
  26. package/lib/compiler/populate.js +284 -288
  27. package/lib/compiler/propagator.js +15 -13
  28. package/lib/compiler/resolve.js +136 -306
  29. package/lib/compiler/shared.js +42 -44
  30. package/lib/compiler/tweak-assocs.js +29 -27
  31. package/lib/compiler/utils.js +29 -3
  32. package/lib/edm/annotations/genericTranslation.js +7 -13
  33. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  34. package/lib/edm/csn2edm.js +0 -4
  35. package/lib/edm/edm.js +6 -4
  36. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  37. package/lib/edm/edmPreprocessor.js +1 -5
  38. package/lib/gen/Dictionary.json +34 -2
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -1
  41. package/lib/gen/languageParser.js +2429 -2401
  42. package/lib/inspect/inspectPropagation.js +2 -0
  43. package/lib/json/from-csn.js +87 -41
  44. package/lib/json/to-csn.js +47 -16
  45. package/lib/language/errorStrategy.js +1 -0
  46. package/lib/language/genericAntlrParser.js +109 -28
  47. package/lib/language/language.g4 +20 -4
  48. package/lib/model/csnRefs.js +1 -1
  49. package/lib/model/csnUtils.js +1 -0
  50. package/lib/model/revealInternalProperties.js +1 -2
  51. package/lib/modelCompare/compare.js +2 -1
  52. package/lib/render/manageConstraints.js +5 -2
  53. package/lib/render/toCdl.js +20 -7
  54. package/lib/render/toHdbcds.js +2 -8
  55. package/lib/render/toSql.js +4 -3
  56. package/lib/render/utils/common.js +9 -5
  57. package/lib/transform/db/assertUnique.js +2 -1
  58. package/lib/transform/db/expansion.js +2 -0
  59. package/lib/transform/db/flattening.js +37 -36
  60. package/lib/transform/db/rewriteCalculatedElements.js +559 -0
  61. package/lib/transform/db/transformExists.js +4 -0
  62. package/lib/transform/db/views.js +40 -37
  63. package/lib/transform/forRelationalDB.js +38 -28
  64. package/lib/transform/odata/typesExposure.js +50 -15
  65. package/lib/transform/parseExpr.js +14 -8
  66. package/lib/transform/transformUtilsNew.js +6 -5
  67. package/lib/transform/translateAssocsToJoins.js +49 -33
  68. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,47 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+
11
+ ## Version 3.7.2 - 2023-02-24
12
+
13
+ ### Fixed
14
+
15
+ - CSN parser: Structured annotations containing `=` were accidentally interpreted as expressions,
16
+ even though the corresponding beta flag was not set.
17
+
18
+ ## Version 3.7.0 - 2023-02-22
19
+
20
+ ### Added
21
+
22
+ - Several `annotate` statement can append/prepend values
23
+ to the same array-valued annotation without an `anno-duplicate` error,
24
+ even if there is no `using from` dependency between the involved sources
25
+ - SQL methods such as `point.ST_X()` can be used in views.
26
+ - The SQL `new` keyword can be used for `ST_*` types such as `new ST_POINT('Point(0.5 0.5)') )`
27
+
28
+ ### Changed
29
+
30
+ - Update OData vocabularies 'Common', 'Core', 'Measures', 'PDF', 'UI'.
31
+ - to.edm(x): Empty complex types are no longer warned about as they are allowed.
32
+
33
+ ### Fixed
34
+
35
+ - `parse.cql` and `parse.expr` no longer ignore type arguments such as `cast(field as String(12))`.
36
+ One argument is interpreted as `length` and two are interpreted as `precision` and `scale`, similar to
37
+ how custom types and their arguments are interpreted.
38
+ - Previously, the compiler could not always find a unique redirection target if there were
39
+ one direct projection on the model target and two or more projections on that projection.
40
+ - The performance of compiler-checks for deeply nested expressions/queries has been improved
41
+ - Fix various bugs in Association to Join translation:
42
+ + Recursive `$self` dereferencing
43
+ + Correct resolution of table alias in non-bijective `$self` backlink associations in combination with
44
+ explicit redirection.
45
+ - to.edm(x): Process value help list convenience annotations on unbound action parameters.
46
+
47
+ ### Removed
48
+
49
+ - tbd
50
+
10
51
  ## Version 3.6.2 - 2023-02-06
11
52
 
12
53
  ### Fixed
@@ -365,6 +406,14 @@ The compiler behavior concerning `beta` features can change at any time without
365
406
  it is now only allowed for `count`, `min`, `max`, `sum`, `avg`, `stddev`, `var`.
366
407
  - All non-SNAPI options.
367
408
 
409
+ ## Version 2.15.10 - 2023-01-26
410
+
411
+ ### Fixed
412
+
413
+ - If an entity with parameters is auto-exposed, the generated projection now has
414
+ the same formal parameters and its query forwards these parameters to the origin entity.
415
+ - to.edm(x): Respect record type hint `$Type` in EDM JSON as full qualified `@type` URI property.
416
+
368
417
  ## Version 2.15.8 - 2022-08-02
369
418
 
370
419
  ### Fixed
package/README.md CHANGED
@@ -29,6 +29,9 @@ Or maintain your package.json dependencies as follows:
29
29
 
30
30
  Please refer to the [official CDS documentation](https://cap.cloud.sap/docs/cds/).
31
31
 
32
+ ## How to Obtain Support
33
+
34
+ In case you find a bug, please report an [incident](https://cap.cloud.sap/docs/resources/#reporting-incidents) on SAP Support Portal.
32
35
 
33
36
  ## License
34
37
 
package/bin/cdsc.js CHANGED
@@ -239,10 +239,13 @@ function displayUsage( error, helpText, code ) {
239
239
  // Display help text first, error at the end (more readable, no scrolling)
240
240
  out.write(`${helpText}\n`);
241
241
  if (error) {
242
- if (error instanceof Array)
243
- out.write(`${error.map(err => `cdsc: ERROR: ${err}`).join('\n')}\n`);
244
- else
242
+ if (error instanceof Array) {
243
+ const errors = error.map(err => `cdsc: ERROR: ${err}`).join('\n');
244
+ out.write(`${errors}\n`);
245
+ }
246
+ else {
245
247
  out.write(`cdsc: ERROR: ${error}\n`);
248
+ }
246
249
  }
247
250
  throw new ProcessExitError(code);
248
251
  }
@@ -389,8 +392,8 @@ function executeCommandLine( command, options, args ) {
389
392
  // Execute the command line option 'manageConstraints' and display the results.
390
393
  function manageConstraints( model ) {
391
394
  const csn = options.directBackend ? model : compactModel(model, options);
392
- const alterConstraintsResult = alterConstraintsWithCsn(csn, options);
393
395
  const { src } = options || {};
396
+ const alterConstraintsResult = alterConstraintsWithCsn(csn, options);
394
397
  Object.keys(alterConstraintsResult).forEach((id) => {
395
398
  const renderedConstraintStatement = alterConstraintsResult[id];
396
399
  if (src === 'hdi')
@@ -499,8 +502,9 @@ function executeCommandLine( command, options, args ) {
499
502
  .forEach(msg => log(msg));
500
503
  }
501
504
  else if (options.noMessageContext) {
505
+ // TODO: currently, ‹↓› = "downgradable" is only shown here
502
506
  messages.filter(msg => (messageLevels[msg.severity] <= options.warning))
503
- .forEach(msg => log(main.messageString(msg, normalizeFilename, !!options.noMessageId)));
507
+ .forEach(msg => log(main.messageString(msg, normalizeFilename, !!options.noMessageId, false, options.testMode && 'compile'))); // TODO: use module name
504
508
  }
505
509
  else {
506
510
  // Contains file-contents that are split at '\n'. Try to avoid multiple `.split()` calls.
@@ -8,6 +8,24 @@ Note: `beta` fixes, changes and features are listed in this ChangeLog just for i
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
  **Don't use `beta` fixes, changes and features in productive mode.**
10
10
 
11
+
12
+ ## Version 3.7.0 - 2023-02-22
13
+
14
+ ### Added `calculatedElements`
15
+
16
+ Allows to define calculated elements in entities and aspects. When used in views, they
17
+ are replaced by their value, for example:
18
+
19
+ ```cds
20
+ entity E { one: Integer; two = one + 1; };
21
+ entity P as projection on E { two };
22
+ // P is the same as:
23
+ entity P as projection on E { one + 1 as two };
24
+ ```
25
+
26
+ This allows to define calculations centrally at the entity, which can be used by
27
+ other views.
28
+
11
29
  ## Version 3.5.0 - 2022-12-07
12
30
 
13
31
  ### Added `odataTerms`
@@ -250,7 +268,7 @@ Render JOIN cardinality in native HANA association if provided. If no cardinalit
250
268
 
251
269
  ### Removed `aspectCompositions`
252
270
 
253
- Aspect compositions aka managed compositions are now avaible without beta option.
271
+ Aspect compositions aka managed compositions are now available without beta option.
254
272
  _Warning_: the CSN representation can still change.
255
273
 
256
274
  ## Version 1.31.0 - 2020-06-26
@@ -286,5 +304,5 @@ This option does not enable:
286
304
  ### Added `keyRefError`
287
305
 
288
306
  Always signal an error (instead of just a warning in some cases),
289
- if not all references in the `keys` of an managed associations
307
+ if not all references in the `keys` of a managed associations
290
308
  are projected in the new target.
@@ -15,7 +15,7 @@ and several new features are not available.**
15
15
 
16
16
  ### Added `autoCorrectOrderBySourceRefs`
17
17
 
18
- When this option is set, calling `compile` auto-corrects direct `order by`
18
+ When this option is set, calling `compile` autocorrects direct `order by`
19
19
  source element references without table alias for SELECT queries by adding the
20
20
  table alias to the `ref`.
21
21
 
@@ -153,7 +153,7 @@ Render old and broken temporal EDM API.
153
153
  ### Added `noElementsExpansion`
154
154
 
155
155
  When setting it, association in sub elements are not automatically redirected,
156
- and the sub elements cannot be annotated indivually.
156
+ and the sub elements cannot be annotated individually.
157
157
 
158
158
  Do not use this. Setting it might avoid some compile errors,
159
159
  but in most cases the reported errors are rightly reported.
package/lib/api/main.js CHANGED
@@ -63,6 +63,7 @@ function attachTransformerCharacteristics( csn, transformation, options,
63
63
  relevant[name] = options[name];
64
64
  }
65
65
 
66
+ // eslint-disable-next-line sonarjs/no-empty-collection
66
67
  for (const name of relevantGeneralOptions ) {
67
68
  if (options[name] !== undefined)
68
69
  relevant[name] = options[name];
@@ -763,7 +764,7 @@ function flattenResultStructure( toProcess ) {
763
764
  /**
764
765
  * Print args to stderr if CDSC_TRACE_API is set
765
766
  *
766
- * @param {...any} args
767
+ * @param {...any} args to be logged to stderr
767
768
  */
768
769
  function traceApi( ...args ) {
769
770
  if (process?.env?.CDSC_TRACE_API !== undefined) {
@@ -81,6 +81,15 @@ function dictAddArray( dict, name, entry, messageCallback ) {
81
81
  return entry;
82
82
  }
83
83
 
84
+ function dictFirst( dict ) {
85
+ if (!dict)
86
+ return dict;
87
+ for (const name in dict) {
88
+ return dict[name];
89
+ }
90
+ return undefined;
91
+ }
92
+
84
93
  // Push `entry` to the array value with key `name` in the dictionary `dict`.
85
94
  function pushToDict( dict, name, entry ) {
86
95
  if (dict[name])
@@ -93,5 +102,6 @@ module.exports = {
93
102
  dictAdd,
94
103
  dictForEach,
95
104
  dictAddArray,
105
+ dictFirst,
96
106
  pushToDict,
97
107
  };
@@ -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
@@ -231,11 +233,15 @@ const centralMessageTexts = {
231
233
  std: 'Duplicate assignment with $(ANNO)',
232
234
  doc: 'Duplicate assignment with a doc comment',
233
235
  },
236
+ 'anno-duplicate-same-file': {
237
+ std: 'Duplicate assignment with $(ANNO), using last',
238
+ doc: 'Duplicate assignment with a doc comment, using last',
239
+ },
234
240
  'anno-duplicate-unrelated-layer': {
235
241
  std: 'Duplicate assignment with $(ANNO)',
236
242
  doc: 'Duplicate assignment with a doc comment',
237
243
  },
238
- 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
244
+ 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO)',
239
245
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
240
246
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
241
247
  'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
@@ -360,7 +366,6 @@ const centralMessageTexts = {
360
366
  dynamic: 'Dynamic parameter $(CODE) is not supported',
361
367
  positional: 'Positional parameter $(CODE) is not supported',
362
368
  },
363
- // 'syntax-unsupported-method', 'syntax-unsupported-new'
364
369
 
365
370
  // Syntax messages, CSN parser - default: Error ------------------------------
366
371
  'syntax-deprecated-dollar-syntax': {
@@ -420,7 +425,7 @@ const centralMessageTexts = {
420
425
  extend: 'CSN property $(PROP) is not expected by an extend in $(PARENTPROP)',
421
426
  annotate: 'CSN property $(PROP) is not expected by an annotate in $(PARENTPROP)',
422
427
  },
423
- 'syntax-invalid-calc-elem': {
428
+ 'def-invalid-calc-elem': {
424
429
  std: 'Invalid calculated element',
425
430
  key: 'A primary key element can\'t be calculated',
426
431
  virtual: 'A virtual element can\'t be calculated',
@@ -430,7 +435,12 @@ const centralMessageTexts = {
430
435
  type: 'A type can\'t have calculated elements',
431
436
  action: 'An action can\'t have calculated elements',
432
437
  function: 'A function can\'t have calculated elements',
433
- annotation: 'Annotation definitions can\'t have calculated elements'
438
+ annotation: 'Annotation definitions can\'t have calculated elements',
439
+ param: 'Parameters can\'t have calculated elements',
440
+ },
441
+ 'def-unsupported-calc-elem': {
442
+ std: 'Calculated elements are not supported',
443
+ nested: 'Calculated elements in structures are not supported, yet'
434
444
  },
435
445
  // 'syntax-unknown-property' (Warning? Better configurable Error)
436
446
 
@@ -489,6 +499,10 @@ const centralMessageTexts = {
489
499
  std: '$(ART) can\'t be extended because it originates from an include',
490
500
  elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
491
501
  },
502
+ 'ref-unexpected-scope': {
503
+ std: 'Unexpected parameter reference',
504
+ calc: 'Calculated elements can\'t use parameter references',
505
+ },
492
506
  'ref-unexpected-structured': {
493
507
  std: 'Unexpected usage of structured type $(ELEMREF)',
494
508
  expr: 'Structured elements can\'t be used in expressions',
@@ -503,6 +517,16 @@ const centralMessageTexts = {
503
517
  expr: 'Associations can\'t be used as values in expressions',
504
518
  cast: 'Casting to an association is not supported',
505
519
  },
520
+ 'ref-unexpected-calculated': {
521
+ std: 'Unexpected reference to calculated element',
522
+ on: 'Calculated elements can\'t be used in ON-conditions of unmanaged associations',
523
+ fkey: 'Calculated elements can\'t be used as foreign keys for managed associations',
524
+ },
525
+
526
+ 'ref-unexpected-navigation': {
527
+ std: 'Can\'t follow association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to, but not $(NAME)',
528
+ unmanaged: 'Can\'t follow unmanaged association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to',
529
+ },
506
530
 
507
531
  'type-unexpected-typeof': {
508
532
  std: 'Unexpected $(KEYWORD) for the type reference here',
@@ -545,14 +569,14 @@ const centralMessageTexts = {
545
569
  },
546
570
 
547
571
  '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',
572
+ std: 'SAP HANA doesn\'t support associations in/to parameterized entities',
573
+ view: 'SAP HANA doesn\'t support associations in parameterized entities',
574
+ target: 'SAP HANA doesn\'t support associations to parameterized entities',
551
575
  },
552
576
  '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)',
577
+ std: 'SAP HANA doesn\'t allow associations in/to entities annotated with $(ANNO)',
578
+ 'entity-persistence': 'SAP HANA doesn\'t allow associations in entities annotated with $(ANNO)',
579
+ 'target-persistence': 'SAP HANA doesn\'t allow associations pointing to entities annotated with $(ANNO)',
556
580
  },
557
581
  'def-unexpected-key': {
558
582
  std: '$(ART) can\'t have additional keys',
@@ -566,9 +590,13 @@ const centralMessageTexts = {
566
590
  include: '$(ART) can\'t have localized elements (through include)',
567
591
  },
568
592
  'def-unexpected-localized-anno': 'Annotations can\'t have localized elements',
593
+ 'type-unexpected-structure': {
594
+ std: 'Unexpected structured type', // unused variant
595
+ calc: 'A structured type can\'t be used for calculated elements',
596
+ },
569
597
 
570
598
  'def-missing-element': {
571
- std: 'Expecting entity to have at least one non-virtual element',
599
+ std: 'Expecting entity to have at least one element which is neither virtual nor calculated',
572
600
  view: 'Expecting view to have at least one non-virtual element'
573
601
  },
574
602
 
@@ -595,6 +623,11 @@ const centralMessageTexts = {
595
623
  param: 'Duplicate definition of parameter $(NAME)',
596
624
  alias: 'Duplicate definition of table alias or mixin $(NAME)',
597
625
  },
626
+ 'ref-duplicate-include-member': {
627
+ std: 'Duplicate member $(NAME) through multiple includes $(SORTED_ARTS)',
628
+ elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
629
+ actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
630
+ },
598
631
 
599
632
  // TODO: rename to ref-expected-XYZ
600
633
  'expected-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -645,7 +678,10 @@ const centralMessageTexts = {
645
678
  std: 'Expected identifier for select item',
646
679
  assoc: 'Expected identifier as the association\'s name',
647
680
  },
648
-
681
+ 'query-unsupported-calc': {
682
+ std: 'Using nested projections next to calculated elements is not supported, yet',
683
+ inside: 'Using calculated elements in nested projections is not supported, yet'
684
+ },
649
685
  'ref-sloppy-type': 'A type or an element is expected here',
650
686
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
651
687
  'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
@@ -658,6 +694,14 @@ const centralMessageTexts = {
658
694
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
659
695
  },
660
696
 
697
+ // -----------------------------------------------------------------------------------
698
+ // Expressions
699
+ // -----------------------------------------------------------------------------------
700
+ 'expr-invalid-operator': 'Comparing $(ID) is only allowed with $(OP)',
701
+ 'expr-missing-comparison': {
702
+ std: 'Expected a comparison with $(OP) when using $(ID) in an ON-condition',
703
+ },
704
+
661
705
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
662
706
 
663
707
  // OData version dependent messages
@@ -766,6 +766,12 @@ function transformManyWith( t, sorted ) {
766
766
  };
767
767
  }
768
768
 
769
+ /**
770
+ * Quote the given string. Performs a type sanity check.
771
+ *
772
+ * @param {string} name
773
+ * @return {string}
774
+ */
769
775
  function quoted( name ) {
770
776
  if (typeof name === 'string')
771
777
  return quote.double( name );
@@ -912,16 +918,16 @@ function weakLocation( loc ) {
912
918
  * @param {boolean} [noHome]
913
919
  * @returns {string}
914
920
  */
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 })`);
921
+ function messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) {
922
+ const location = (err.$location?.file ? `${ locationString( err.$location, normalizeFilename ) }: ` : '');
923
+ const severity = err.severity || 'Error';
924
+ const downgradable = severity === 'Error' && moduleName &&
925
+ isDowngradable(err.messageId, moduleName, false) ? '‹↓›' : '';
926
+ // even with noHome, print err.home if the location is weak
927
+ const home = !err.home || noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
928
+ // TODO: the plan was with brackets = `Error[ref-undefined-def]`
929
+ const id = err.messageId && !noMessageId ? ` ${ err.messageId }` : '';
930
+ return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;
925
931
  }
926
932
 
927
933
  /**
@@ -1309,6 +1315,12 @@ function homeNameForExtend( art ) {
1309
1315
  function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1310
1316
  if (!model)
1311
1317
  return null;
1318
+
1319
+ if (options.testMode)
1320
+ sanitizeCsnPath(csnPath);
1321
+
1322
+ const _quoted = options.testMode ? quoted : quote.double;
1323
+
1312
1324
  let result = '';
1313
1325
  const csnDictionaries = [
1314
1326
  'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
@@ -1338,12 +1350,12 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1338
1350
  if (step === 'definitions') {
1339
1351
  next(); // "definitions"
1340
1352
  const kind = currentThing?.kind || 'artifact';
1341
- result += `${ kind }:${ quoted(step) }`;
1353
+ result += `${ kind }:${ _quoted(step) }`;
1342
1354
  }
1343
1355
  else if (step === 'vocabularies') {
1344
1356
  next(); // dictionary name
1345
1357
  if (index < csnPath.length)
1346
- result += `annotation:${ quoted(csnPath[index]) }`;
1358
+ result += `annotation:${ _quoted(csnPath[index]) }`;
1347
1359
  else
1348
1360
  result += 'vocabularies';
1349
1361
  }
@@ -1355,7 +1367,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1355
1367
  else {
1356
1368
  const name = currentThing.annotate || currentThing.extend;
1357
1369
  const kind = currentThing.annotate ? 'annotate' : 'extend';
1358
- result += `${ kind }:${ quoted(name) }`;
1370
+ result += `${ kind }:${ _quoted(name) }`;
1359
1371
  }
1360
1372
  }
1361
1373
 
@@ -1381,7 +1393,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1381
1393
  next();
1382
1394
  }
1383
1395
  if (elementHierarchy.length > 0)
1384
- result += `/element:${quoted(elementHierarchy.join('.'))}`;
1396
+ result += `/element:${ _quoted(elementHierarchy.join('.')) }`;
1385
1397
  // no trailing /elements or /items
1386
1398
  continue;
1387
1399
  }
@@ -1389,7 +1401,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1389
1401
  next(); // "actions"
1390
1402
  if (index < csnPath.length) {
1391
1403
  const kind = currentThing?.kind || 'action';
1392
- result += `/${ kind }:${ quoted(csnPath[index]) }`;
1404
+ result += `/${ kind }:${ _quoted(csnPath[index]) }`;
1393
1405
  }
1394
1406
  else { // actions is last segment
1395
1407
  result += '/actions';
@@ -1419,7 +1431,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1419
1431
  }
1420
1432
  else if (step === 'target') {
1421
1433
  if (currentThing)
1422
- result += '/target:' + quoted(currentThing);
1434
+ result += '/target:' + _quoted(currentThing);
1423
1435
  else
1424
1436
  result += '/target';
1425
1437
  break;
@@ -1447,14 +1459,14 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1447
1459
  next(); // "keys"
1448
1460
  if (index < csnPath.length) {
1449
1461
  const key = aliasOrReference();
1450
- result += `/key:${ key ? quoted(key) : step }`;
1462
+ result += `/key:${ key ? _quoted(key) : step }`;
1451
1463
  }
1452
1464
  break;
1453
1465
  }
1454
1466
  else if (step[0] === '@') {
1455
1467
  // Annotations are always the last step.
1456
1468
  // Nothing comes after them, everything is user defined.
1457
- result += `/${ quoted(csnPath[index]) }`;
1469
+ result += `/${ _quoted(csnPath[index]) }`;
1458
1470
  break;
1459
1471
  }
1460
1472
  else {
@@ -1470,7 +1482,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1470
1482
  function dictEntry( prefix ) {
1471
1483
  next(); // dictionary name
1472
1484
  if (index < csnPath.length)
1473
- result += `/${ prefix }:${ quoted(csnPath[index]) }`;
1485
+ result += `/${ prefix }:${ _quoted(csnPath[index]) }`;
1474
1486
  }
1475
1487
 
1476
1488
  /**
@@ -1548,7 +1560,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1548
1560
  } while (index < csnPath.length);
1549
1561
 
1550
1562
  if (elementHierarchy.length > 0)
1551
- result += `/column:${ quoted(elementHierarchy.join('.')) }`;
1563
+ result += `/column:${ _quoted(elementHierarchy.join('.')) }`;
1552
1564
  }
1553
1565
  else if(step === 'args') {
1554
1566
  // Should only be reached for cases, where no SELECT in a union is picked.
@@ -1595,6 +1607,13 @@ function queryDepthForMessage( csnPath, model, view ) {
1595
1607
  return 0;
1596
1608
  }
1597
1609
 
1610
+ function sanitizeCsnPath(csnPath) {
1611
+ for (const step of csnPath) {
1612
+ if (typeof step !== 'string' && typeof step !== 'number')
1613
+ throw new CompilerAssertion(`Found CSN path step that is neither string nor number: ${step} ${ JSON.stringify(csnPath) }`)
1614
+ }
1615
+ }
1616
+
1598
1617
  /**
1599
1618
  * Get the explanation string for the given message-id.
1600
1619
  * Ensure to have called hasMessageExplanation() before.
package/lib/base/model.js CHANGED
@@ -22,6 +22,7 @@ const queryOps = {
22
22
  const availableBetaFlags = {
23
23
  // enabled by --beta-mode
24
24
  calculatedElements: true,
25
+ annotationExpressions: true,
25
26
  toRename: true,
26
27
  assocsWithParams: true,
27
28
  hanaAssocRealCardinality: true,
@@ -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 }
@@ -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
  };
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { isPersistedOnDatabase } = require('../model/csnUtils.js');
4
3
  // Only to be used with validator.js - a correct this value needs to be provided!
5
4
  // not relevant for odata - entities need to be checked at the end of the transformation
5
+
6
+ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
7
+
6
8
  /**
7
9
  * Ensure that empty/only virtual entities do not reach the db.
8
10
  *
@@ -11,22 +13,24 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
11
13
  * @param {string} prop Property being looped over
12
14
  * @param {CSN.Path} path Path to the artifact
13
15
  */
14
- function validateEmptyOrOnlyVirtual( artifact, artifactName, prop, path ) {
16
+ function validateHasPersistedElements( artifact, artifactName, prop, path ) {
15
17
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
18
  if (!artifact.elements || !hasRealElements(artifact.elements))
19
+ // TODO: Maybe check if there are only calc elements and adapt the message?
17
20
  this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
18
21
  }
19
22
  }
20
23
 
21
24
  /**
22
- * Check if the provided elements contain elements that will be created on the db.
25
+ * Check if the provided elements contain elements that will be created on the database.
26
+ * This includes virtual and calculated elements.
23
27
  *
24
28
  * @param {CSN.Elements} elements Elements to look through
25
29
  * @returns {boolean} True if something would be created on the db from these elements.
26
30
  */
27
31
  function hasRealElements( elements ) {
28
32
  for (const element of Object.values(elements)) {
29
- if (!element.virtual) {
33
+ if (!element.virtual && !element.value) {
30
34
  if (element.elements) {
31
35
  if (hasRealElements(element.elements))
32
36
  return true;
@@ -41,4 +45,4 @@ function hasRealElements( elements ) {
41
45
  }
42
46
 
43
47
 
44
- module.exports = validateEmptyOrOnlyVirtual;
48
+ module.exports = validateHasPersistedElements;
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression( parent, name, expression ) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
23
  // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
- if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
24
+ if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && ($scope !== '$self' || $scope === '$self' && ref.length > 1)) { // TODO: Use $self to navigate to struct
25
25
  this.error('ref-unexpected-structured',
26
26
  name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
27
27
  { '#': 'std', elemref: { ref } } );