@sap/cds-compiler 4.3.0 → 4.4.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 (81) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/api/main.js +14 -24
  3. package/lib/api/options.js +1 -0
  4. package/lib/api/trace.js +38 -0
  5. package/lib/base/location.js +46 -1
  6. package/lib/base/message-registry.js +68 -16
  7. package/lib/base/messages.js +8 -3
  8. package/lib/checks/.eslintrc.json +1 -0
  9. package/lib/checks/actionsFunctions.js +1 -1
  10. package/lib/checks/annotationsOData.js +2 -2
  11. package/lib/checks/selectItems.js +4 -1
  12. package/lib/compiler/assert-consistency.js +3 -2
  13. package/lib/compiler/base.js +1 -1
  14. package/lib/compiler/builtins.js +25 -1
  15. package/lib/compiler/checks.js +6 -5
  16. package/lib/compiler/define.js +12 -10
  17. package/lib/compiler/extend.js +22 -22
  18. package/lib/compiler/finalize-parse-cdl.js +1 -1
  19. package/lib/compiler/generate.js +70 -53
  20. package/lib/compiler/kick-start.js +9 -5
  21. package/lib/compiler/populate.js +31 -22
  22. package/lib/compiler/propagator.js +6 -2
  23. package/lib/compiler/resolve.js +52 -17
  24. package/lib/compiler/shared.js +74 -38
  25. package/lib/compiler/tweak-assocs.js +64 -23
  26. package/lib/compiler/utils.js +40 -23
  27. package/lib/edm/.eslintrc.json +2 -0
  28. package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +75 -421
  31. package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
  32. package/lib/edm/csn2edm.js +12 -5
  33. package/lib/edm/edm.js +14 -73
  34. package/lib/edm/edmPreprocessor.js +6 -0
  35. package/lib/gen/Dictionary.json +187 -16
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageLexer.interp +1 -1
  39. package/lib/gen/languageLexer.js +1129 -671
  40. package/lib/gen/languageParser.js +4285 -4283
  41. package/lib/json/from-csn.js +13 -18
  42. package/lib/json/to-csn.js +11 -6
  43. package/lib/language/antlrParser.js +0 -1
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/errorStrategy.js +95 -30
  46. package/lib/language/genericAntlrParser.js +21 -1
  47. package/lib/main.js +13 -3
  48. package/lib/model/csnRefs.js +42 -8
  49. package/lib/model/csnUtils.js +14 -2
  50. package/lib/model/enrichCsn.js +33 -5
  51. package/lib/model/revealInternalProperties.js +5 -0
  52. package/lib/modelCompare/compare.js +76 -14
  53. package/lib/modelCompare/utils/filter.js +19 -12
  54. package/lib/optionProcessor.js +2 -0
  55. package/lib/render/.eslintrc.json +1 -1
  56. package/lib/render/manageConstraints.js +1 -0
  57. package/lib/render/toHdbcds.js +3 -0
  58. package/lib/render/toRename.js +3 -1
  59. package/lib/render/toSql.js +46 -92
  60. package/lib/render/utils/common.js +76 -0
  61. package/lib/render/utils/delta.js +17 -3
  62. package/lib/sql-identifier.js +1 -1
  63. package/lib/transform/db/.eslintrc.json +1 -0
  64. package/lib/transform/db/applyTransformations.js +30 -4
  65. package/lib/transform/db/associations.js +22 -10
  66. package/lib/transform/db/backlinks.js +6 -2
  67. package/lib/transform/db/expansion.js +2 -2
  68. package/lib/transform/db/transformExists.js +13 -39
  69. package/lib/transform/draft/db.js +14 -3
  70. package/lib/transform/draft/odata.js +5 -18
  71. package/lib/transform/effective/associations.js +46 -15
  72. package/lib/transform/effective/main.js +7 -2
  73. package/lib/transform/effective/misc.js +43 -24
  74. package/lib/transform/effective/queries.js +20 -22
  75. package/lib/transform/effective/types.js +6 -2
  76. package/lib/transform/forOdata.js +5 -2
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +24 -16
  80. package/lib/utils/term.js +2 -2
  81. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@
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
+ ## Version 4.4.0 - 2023-11-09
11
+
12
+ ### Added
13
+
14
+ - compiler: International letters such as `ä` can now be used in CDS identifiers without quoting.
15
+ Unicode letters similar to JavaScript are allowed.
16
+ - to.edm(x):
17
+ + Allow to render all complex types within a requested service as `OpenType=true` with option `--odata-open-type`.
18
+ Explicit `@open: false` annotations are not overwritten.
19
+ + Allow to annotate the generated draft artifacts but not generated foreign keys (as with any other managed association).
20
+ - to.sql|hdi|hdbcds: Allow annotating the generated `.drafts` tables.
21
+
22
+ ### Changed
23
+
24
+ - CDL parser: improve error recovery inside structured annotation values
25
+ - Update OData vocabularies: 'Aggregation', 'Common', 'Core', 'Hierarchy', 'ODM', 'UI'.
26
+
27
+ ### Fixed
28
+
29
+ - parser:
30
+ + `/**/` was incorrectly parsed as an unterminated doc-comment, leading to parse errors.
31
+ + Doc-comments consisting only of `*` were not correctly parsed.
32
+ - compiler: do not propagate `default`s in a CSN of flavor `xtended`/`gensrc`.
33
+ - to.hana: Fix various bugs in association to join translation. Support `$self` references
34
+ in filter expressions.
35
+ - to.edm(x): Omit `EntitySet` attribute on `Edm.FunctionImport` and `Edm.ActionImport` that return
36
+ a singleton.
37
+ - to.sql|hdi.migration: Improve handling of primary key changes - detect them and render corresponding drop-create.
38
+
39
+ ## Version 4.3.2 - 2023-10-25
40
+
41
+ ### Fixed
42
+
43
+ - compiler: Fix auto-exposure of composition target entities inside anonymous composition target aspects.
44
+ - to.hana: Fix a bug in association to join translation, expect ON condition operand to be a function without arguments.
45
+
10
46
  ## Version 4.3.0 - 2023-09-29
11
47
 
12
48
  ### Added
package/lib/api/main.js CHANGED
@@ -37,6 +37,7 @@ const { ModelError } = require('../base/error');
37
37
  const { forEach, forEachKey } = require('../utils/objectUtils');
38
38
  const { checkRemovedDeprecatedFlags } = require('../base/model');
39
39
  const { csn2edm, csn2edmAll } = require('../edm/csn2edm');
40
+ const { traceApi } = require('./trace');
40
41
 
41
42
  const relevantGeneralOptions = [ /* for future generic options */ ];
42
43
  const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];
@@ -146,7 +147,7 @@ function odataInternal( csn, internalOptions ) {
146
147
  * @returns {oDataCSN} Return an oData-pre-processed CSN
147
148
  */
148
149
  function odata( csn, options = {} ) {
149
- traceApi("Options passed into 'for.odata'", options);
150
+ traceApi('for.odata', options);
150
151
  const internalOptions = prepareOptions.for.odata(options);
151
152
  return odataInternal(csn, internalOptions);
152
153
  }
@@ -159,7 +160,7 @@ function odata( csn, options = {} ) {
159
160
  * @returns {object} { model: string, namespace: string }
160
161
  */
161
162
  function cdl( csn, options = {} ) {
162
- traceApi("Options passed into 'to.cdl'", options);
163
+ traceApi('to.cdl', options);
163
164
  const internalOptions = prepareOptions.to.cdl(options);
164
165
  return toCdl.csnToCdl(csn, internalOptions);
165
166
  }
@@ -251,7 +252,7 @@ function forEffective( csn, options = {} ) {
251
252
  * @returns {SQL[]} Array of SQL statements, tables first, views second
252
253
  */
253
254
  function sql( csn, options = {} ) {
254
- traceApi("Options passed into 'to.sql'", options);
255
+ traceApi('to.sql', options);
255
256
  const internalOptions = prepareOptions.to.sql(options);
256
257
  internalOptions.transformation = 'sql';
257
258
 
@@ -274,7 +275,7 @@ function sql( csn, options = {} ) {
274
275
  * @returns {HDIArtifacts} { <filename>:<content>, ...}
275
276
  */
276
277
  function hdi( csn, options = {} ) {
277
- traceApi("Options passed into 'to.hdi'", options);
278
+ traceApi('to.hdi', options);
278
279
  const internalOptions = prepareOptions.to.hdi(options);
279
280
 
280
281
  // we need the CSN for view sorting
@@ -377,7 +378,7 @@ function remapName( key, csn, filter = () => true ) {
377
378
  * - createsAndAlters: An array of SQL statements to ALTER/CREATE tables/views
378
379
  */
379
380
  function sqlMigration( csn, options, beforeImage ) {
380
- traceApi("Options passed into 'to.sql.migration'", options);
381
+ traceApi('to.sql.migration', options);
381
382
  const internalOptions = prepareOptions.to.sql(options);
382
383
  const {
383
384
  error, warning, info, throwWithError, message,
@@ -398,6 +399,8 @@ function sqlMigration( csn, options, beforeImage ) {
398
399
  error, warning, info, throwWithError, message,
399
400
  }));
400
401
  Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, error));
402
+ diff.changedPrimaryKeys = diff.changedPrimaryKeys
403
+ .filter(an => diffFilterObj.changedPrimaryKeys(an));
401
404
  }
402
405
 
403
406
  const identifierUtils = sqlUtils.getIdentifierUtils(csn, internalOptions);
@@ -530,7 +533,7 @@ function sqlMigration( csn, options, beforeImage ) {
530
533
  * @returns {migration} The migration result
531
534
  */
532
535
  function hdiMigration( csn, options, beforeImage ) {
533
- traceApi("Options passed into 'to.hdi.migration'", options);
536
+ traceApi('to.hdi.migration', options);
534
537
  const internalOptions = prepareOptions.to.hdi(options);
535
538
 
536
539
  // Prepare after-image.
@@ -615,7 +618,7 @@ sql.migration = sqlMigration;
615
618
  * @returns {HDBCDS} { <filename>:<content>, ...}
616
619
  */
617
620
  function hdbcds( csn, options = {} ) {
618
- traceApi("Options passed into 'to.hdbcds'", options);
621
+ traceApi('to.hdbcds', options);
619
622
  const internalOptions = prepareOptions.to.hdbcds(options);
620
623
  internalOptions.transformation = 'hdbcds';
621
624
 
@@ -632,7 +635,7 @@ function hdbcds( csn, options = {} ) {
632
635
  * @returns {edm} The JSON representation of the service
633
636
  */
634
637
  function edm( csn, options = {} ) {
635
- traceApi("Options passed into 'to.edm'", options);
638
+ traceApi('to.edm', options);
636
639
  // If not provided at all, set service to undefined to trigger validation
637
640
  const internalOptions = prepareOptions.to.edm(
638
641
  // eslint-disable-next-line comma-dangle
@@ -663,7 +666,7 @@ edm.all = edmall;
663
666
  * @returns {edms} { <service>:<JSON representation>, ...}
664
667
  */
665
668
  function edmall( csn, options = {} ) {
666
- traceApi("Options passed into 'to.edm.all'", options);
669
+ traceApi('to.edm.all', options);
667
670
  const internalOptions = prepareOptions.to.edm(options);
668
671
  const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
669
672
 
@@ -694,7 +697,7 @@ function edmall( csn, options = {} ) {
694
697
  * @returns {edmx} The XML representation of the service
695
698
  */
696
699
  function edmx( csn, options = {} ) {
697
- traceApi("Options passed into 'to.edmx'", options);
700
+ traceApi('to.edmx', options);
698
701
  // If not provided at all, set service to undefined to trigger validation
699
702
  const internalOptions = prepareOptions.to.edmx(
700
703
  // eslint-disable-next-line comma-dangle
@@ -726,7 +729,7 @@ edmx.all = edmxall;
726
729
  * @returns {edmxs} { <service>:<XML representation>, ...}
727
730
  */
728
731
  function edmxall( csn, options = {} ) {
729
- traceApi("Options passed into 'to.edmx.all'", options);
732
+ traceApi('to.edmx.all', options);
730
733
  const internalOptions = prepareOptions.to.edmx(options);
731
734
 
732
735
  const result = {};
@@ -843,19 +846,6 @@ function flattenResultStructure( toProcess ) {
843
846
  return result;
844
847
  }
845
848
 
846
- /**
847
- * Print args to stderr if CDSC_TRACE_API is set
848
- *
849
- * @param {...any} args to be logged to stderr
850
- */
851
- function traceApi( ...args ) {
852
- if (process?.env?.CDSC_TRACE_API !== undefined) {
853
- for (const arg of args) {
854
- // eslint-disable-next-line no-console
855
- console.error( `${ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg }`);
856
- }
857
- }
858
- }
859
849
 
860
850
  module.exports = {
861
851
  odata: publishCsnProcessor(odata, 'for.odata'),
@@ -42,6 +42,7 @@ const publicOptionsNewAPI = [
42
42
  'odataXServiceRefs',
43
43
  'odataV2PartialConstr',
44
44
  'odataVocabularies',
45
+ 'odataOpenType',
45
46
  'service',
46
47
  'serviceNames',
47
48
  //
@@ -0,0 +1,38 @@
1
+
2
+ 'use strict';
3
+
4
+ const shouldTraceApi = process?.env?.CDSC_TRACE_API;
5
+
6
+ /**
7
+ * Placeholder for disabled tracing (no-op).
8
+ *
9
+ * @param {string} apiName API name
10
+ * @param {object} options Options passed to the API.
11
+ * @param {...any} [args] Arguments to be logged to stderr
12
+ */
13
+ // eslint-disable-next-line no-unused-vars
14
+ function noOp( apiName, options, ...args ) {
15
+ // no-op
16
+ }
17
+
18
+ /**
19
+ * Print args to stderr if CDSC_TRACE_API is set
20
+ *
21
+ * @param {string} apiName API name
22
+ * @param {object} options Options passed to the API.
23
+ * @param {...any} [args] Arguments to be logged to stderr
24
+ */
25
+ function traceApi( apiName, options, ...args ) {
26
+ const optStr = typeof options === 'object' ? JSON.stringify(options, null, 2) : options;
27
+ const argsStr = args.map(val => JSON.stringify(val)).join(', ');
28
+ const rest = args.length > 0 ? ` | ${ argsStr }` : '';
29
+ // Local require: Only load on-demand, not when tracing is disabled.
30
+ // eslint-disable-next-line global-require
31
+ const { version } = require('../../package.json');
32
+ // eslint-disable-next-line no-console
33
+ console.error( `CDSC_TRACE_API | ${ version } | ${ apiName }(…) | options: ${ optStr }${ rest }`);
34
+ }
35
+
36
+ module.exports = {
37
+ traceApi: shouldTraceApi ? traceApi : noOp,
38
+ };
@@ -76,7 +76,35 @@ function emptyWeakLocation( filename ) {
76
76
  * @returns {CsnLocation}
77
77
  */
78
78
  function weakLocation( loc ) {
79
- return {
79
+ return (!loc?.endLine) ? loc : {
80
+ __proto__: CsnLocation.prototype,
81
+ file: loc.file,
82
+ line: loc.line,
83
+ col: loc.col,
84
+ endLine: undefined,
85
+ endCol: undefined,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Return a location to be used for compiler-generated artifacts whose location is
91
+ * best derived from a reference (`type`, `includes`, `target`, `value`) or a name.
92
+ * Omit the end position to indicate that this is just an approximate location.
93
+ *
94
+ * If represented by a `path` (not always the case for a `name`), use the location
95
+ * of its last item. Reason: think of an IDE functionality “Go to Definition” – only
96
+ * a double-click on the _last_ identifier token of the reference jumps to the artifact
97
+ * represented by the complete reference.
98
+ *
99
+ * @param {CsnLocation} loc
100
+ * @returns {CsnLocation}
101
+ */
102
+ function weakRefLocation( ref ) {
103
+ if (!ref)
104
+ return ref;
105
+ const { path } = ref;
106
+ const loc = path?.length ? path[path.length - 1].location : ref.location;
107
+ return (!loc?.endLine) ? loc : {
80
108
  __proto__: CsnLocation.prototype,
81
109
  file: loc.file,
82
110
  line: loc.line,
@@ -86,6 +114,21 @@ function weakLocation( loc ) {
86
114
  };
87
115
  }
88
116
 
117
+ /**
118
+ * @param {CsnLocation} loc
119
+ * @returns {CsnLocation}
120
+ */
121
+ function weakEndLocation( loc ) {
122
+ return loc && {
123
+ __proto__: CsnLocation.prototype,
124
+ file: loc.file,
125
+ line: loc.endLine,
126
+ col: loc.endCol && loc.endCol - 1,
127
+ endline: undefined,
128
+ endCol: undefined,
129
+ };
130
+ }
131
+
89
132
  /**
90
133
  * Returns a dummy location for built-in definitions.
91
134
  *
@@ -172,6 +215,8 @@ module.exports = {
172
215
  emptyLocation,
173
216
  emptyWeakLocation,
174
217
  weakLocation,
218
+ weakRefLocation,
219
+ weakEndLocation,
175
220
  builtinLocation,
176
221
  dictLocation,
177
222
  locationString,
@@ -57,11 +57,11 @@ const centralMessages = {
57
57
  'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
58
58
  'anno-invalid-sql-view': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
59
59
  'anno-invalid-sql-view-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
60
- 'anno-undefined-action': { severity: 'Warning' },
61
- 'anno-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
62
- 'anno-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
63
- 'anno-undefined-element': { severity: 'Warning' },
64
- 'anno-undefined-param': { severity: 'Warning' },
60
+ 'ext-undefined-action': { severity: 'Warning' },
61
+ 'ext-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
62
+ 'ext-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
63
+ 'ext-undefined-element': { severity: 'Warning' },
64
+ 'ext-undefined-param': { severity: 'Warning' },
65
65
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
66
66
  'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
67
67
 
@@ -105,7 +105,6 @@ const centralMessages = {
105
105
 
106
106
  'param-default': { severity: 'Error' }, // not supported yet
107
107
 
108
- 'query-undefined-element': { severity: 'Error' },
109
108
  'query-unexpected-assoc-hdbcds': { severity: 'Error' },
110
109
  'query-unexpected-structure-hdbcds': { severity: 'Error' },
111
110
  'query-ignoring-param-nullability': { severity: 'Info' },
@@ -122,10 +121,12 @@ const centralMessages = {
122
121
  'ref-undefined-def': { severity: 'Error' },
123
122
  'ref-undefined-var': { severity: 'Error' },
124
123
  'ref-undefined-element': { severity: 'Error' },
124
+ 'anno-undefined-element': { severity: 'Warning' },
125
125
  'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
126
126
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: 'v4' },
127
127
  // does not hurt us, but makes it tedious to detect parameter refs
128
128
  'ref-undefined-param': { severity: 'Error' },
129
+ 'anno-undefined-param': { severity: 'Warning' },
129
130
  'ref-rejected-on': { severity: 'Error' },
130
131
  'ref-expected-element': { severity: 'Error' },
131
132
 
@@ -159,7 +160,6 @@ const centralMessages = {
159
160
  'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
160
161
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
161
162
 
162
- 'type-managed-composition': { severity: 'Error' },
163
163
  'type-unsupported-precision-change': { severity: 'Error' },
164
164
  'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
165
165
 
@@ -537,6 +537,8 @@ const centralMessageTexts = {
537
537
  on: 'Unexpected $(ID) reference; is valid only if compared to be equal to an association of the target side',
538
538
  subQuery: 'Unexpected $(ID) reference in a sub query',
539
539
  setQuery: 'Unexpected $(ID) reference in a query on the right side of $(OP)',
540
+ exists: 'With $(NAME), path steps must not start with $(ID)',
541
+ 'exists-filter': 'Unexpected $(ID) reference in filter of path $(ELEMREF) following “EXISTS” predicate',
540
542
  },
541
543
  'ref-undefined-def': {
542
544
  std: 'Artifact $(ART) has not been found',
@@ -544,16 +546,29 @@ const centralMessageTexts = {
544
546
  element: 'Artifact $(ART) has no element $(MEMBER)',
545
547
  },
546
548
  'ref-undefined-param': 'Entity $(ART) has no parameter $(ID)',
549
+ 'anno-undefined-param': {
550
+ std: 'Entity $(ART) has no parameter $(ID)',
551
+ entity: 'Entity $(ART) has no parameter $(ID)',
552
+ action: 'Action $(ART) has no parameter $(ID)',
553
+ },
547
554
  'ref-undefined-art': 'No artifact has been found with name $(ART)',
548
555
  // TODO: proposal 'No definition found for $(NAME)',
549
556
  'ref-undefined-element': {
550
557
  std: 'Element $(ART) has not been found',
551
558
  element: 'Artifact $(ART) has no element $(MEMBER)',
552
559
  target: 'Target entity $(ART) has no element $(ID)',
553
- aspect: 'Element $(ID) has not been found in the anonymous target aspect',
560
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect', // TODO: still?
554
561
  query: 'The current query has no element $(ART)',
562
+ alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
555
563
  virtual: 'Element $(ART) has not been found. Use $(CODE) to add virtual elements in queries'
556
564
  },
565
+ 'anno-undefined-element': {
566
+ std: 'Element $(ART) has not been found',
567
+ element: 'Artifact $(ART) has no element $(MEMBER)',
568
+ target: 'Target entity $(ART) has no element $(ID)',
569
+ query: 'The current query has no element $(ART)',
570
+ alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
571
+ },
557
572
  'ref-undefined-var': {
558
573
  std: 'Variable $(ID) has not been found',
559
574
  alias: 'Variable $(ID) has not been found. Use table alias $(ALIAS) to refer an element with the same name',
@@ -642,6 +657,9 @@ const centralMessageTexts = {
642
657
  select: 'Unexpected $(KEYWORD) for type references in queries',
643
658
  annotation: '$(KEYWORD) can\'t be used in annotation definitions',
644
659
  },
660
+ 'type-unexpected-assoc': {
661
+ std: 'An unmanaged association can\'t be used as type',
662
+ },
645
663
 
646
664
  'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
647
665
  'type-ignoring-argument': 'Too many arguments for type $(ART)',
@@ -672,20 +690,20 @@ const centralMessageTexts = {
672
690
  },
673
691
 
674
692
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
675
- 'anno-undefined-def': 'Artifact $(ART) has not been found', // TODO: ext-
676
- 'anno-undefined-art': 'No artifact has been found with name $(ART)',
677
- 'anno-undefined-element': {
693
+ 'ext-undefined-def': 'Artifact $(ART) has not been found',
694
+ 'ext-undefined-art': 'No artifact has been found with name $(ART)',
695
+ 'ext-undefined-element': {
678
696
  std: 'Element $(NAME) has not been found',
679
697
  element: 'Artifact $(ART) has no element $(NAME)',
680
698
  enum: 'Artifact $(ART) has no enum $(NAME)',
681
699
  returns: 'Return value of $(ART) has no element $(NAME)',
682
700
  'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
683
701
  },
684
- 'anno-undefined-action': {
702
+ 'ext-undefined-action': {
685
703
  std: 'Action $(ART) has not been found',
686
704
  action: 'Artifact $(ART) has no action $(NAME)'
687
705
  },
688
- 'anno-undefined-param': {
706
+ 'ext-undefined-param': {
689
707
  std: 'Parameter $(ART) has not been found',
690
708
  param: 'Artifact $(ART) has no parameter $(NAME)'
691
709
  },
@@ -793,10 +811,20 @@ const centralMessageTexts = {
793
811
  'ref-expecting-const': 'A constant expression or variable is expected here',
794
812
  'ref-expecting-foreign-key': 'Expecting foreign key access after managed association $(NAME) in filter expression of $(ID), but found $(ALIAS)',
795
813
  'ref-invalid-target': {
796
- std: 'An entity, projection or view is expected here', // TODO: change text
814
+ std: 'Expecting an entity as target',
797
815
  composition: 'Expecting an entity or aspect as composition target',
798
816
  bare: 'Expecting the target aspect to have elements',
799
- aspect: 'Expecting the name of an aspect in property $(PROP)', // only CSN input
817
+ aspect: 'Expecting an aspect in property $(PROP)', // `targetAspect` in CSN input
818
+ redirected: 'Expecting an entity as target; a target aspect can\'t be specified for redirections',
819
+ // a `target aspect alone` would be more correct, but confusing if in `target`
820
+ // property, which is the standard (extra text variants would be too much):
821
+ entity: 'Expecting an entity as target; a target aspect can\'t be specified for projection elements',
822
+ event: 'Expecting an entity as target; a target aspect can\'t be specified in an event',
823
+ select: 'Expecting an entity as target; a target aspect can\'t be specified in a query',
824
+ type: 'Expecting an entity as target; a target aspect can\'t be specified for a type',
825
+ param: 'Expecting an entity as target; a target aspect can\'t be specified for a parameter',
826
+ annotation: 'Expecting an entity as target; a target aspect can\'t be specified for an annotation',
827
+ sub: 'Expecting an entity as target; a target aspect can\'t be specified for a sub element',
800
828
  },
801
829
  'ref-invalid-include': {
802
830
  std: 'A type, entity, aspect or event with direct elements is expected here',
@@ -965,9 +993,12 @@ const centralMessageTexts = {
965
993
  'odata-spec-violation-type': {
966
994
  std: 'Expected element to have a type',
967
995
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
996
+ incompatible_anno: 'Unexpected EDM Type $(TYPE) for OData $(VERSION) in $(ANNO)',
968
997
  facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
998
+ facet_anno: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION) in $(ANNO)',
969
999
  external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)',
970
- scale: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE)'
1000
+ scale: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE)',
1001
+ scale_anno: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE) in $(ANNO)'
971
1002
  },
972
1003
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(META)',
973
1004
  'odata-spec-violation-namespace': {
@@ -1050,6 +1081,27 @@ const centralMessageTexts = {
1050
1081
  'deprecated': '$(ANNO) is deprecated. $(DEPR)',
1051
1082
  'notapplied': '$(ANNO) is not applied (AppliesTo: $(RAWVALUES))',
1052
1083
  },
1084
+ 'odata-anno-xpr': {
1085
+ 'std': 'unused',
1086
+ 'notadynexpr': '$(OP) is not a renderable dynamic expression in $(ANNO)',
1087
+ 'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO), please use $(CODE) instead',
1088
+ 'canonfuncalias': 'Expected function name $(CODE) to be of the form $(META).$(OTHERMETA) for $(OP) in $(ANNO)',
1089
+ }
1090
+ ,
1091
+ 'odata-anno-xpr-type': {
1092
+ 'std': 'Expected one qualified type name for $(OP) in $(ANNO)'
1093
+ },
1094
+ 'odata-anno-xpr-args': {
1095
+ 'std': 'Unexpected arguments for $(OP) in $(ANNO)',
1096
+ 'exactly': 'Expected exactly $(COUNT) argument(s) for $(OP) in $(ANNO)',
1097
+ 'atleast': 'Expected at least $(COUNT) argument(s) for $(OP) in $(ANNO)',
1098
+ 'atmost': 'Expected at most $(COUNT) argument(s) for $(OP) in $(ANNO)',
1099
+ 'wrongcount': 'Expected exactly one $(PROP) for $(OP) in $(ANNO)',
1100
+ 'wrongref': 'Unexpected arguments or filters in $(ELEMREF) in $(ANNO)',
1101
+ 'wrongval': 'Unexpected value for $(OP) in $(ANNO)',
1102
+ 'wrongval_meta': 'Expected value for $(OP) to be a $(META) in $(ANNO)',
1103
+ 'wrongval_meta_list': 'Expected value for $(OP) to be a $(META) or $(RAWVALUES) in $(ANNO)',
1104
+ }
1053
1105
  // -----------------------------------------------------------------------------------
1054
1106
  // OData Message section ends here, no messages below this line
1055
1107
  // -----------------------------------------------------------------------------------
@@ -428,8 +428,9 @@ function makeMessageFunction( model, options, moduleName = null ) {
428
428
  }
429
429
 
430
430
  let semanticLocation = location[1] ? homeName( location[1], false ) : null;
431
- if (location[2]) // optional suffix
432
- semanticLocation += `/${ location[2] }`;
431
+ if (location[2]) { // optional suffix, e.g. annotation
432
+ semanticLocation += `/${ (typeof location[2] === 'string') ? location[2]: homeName(location[2]) }`;
433
+ }
433
434
 
434
435
  const definition = location[1] ? homeName( location[1], true ) : null;
435
436
 
@@ -676,6 +677,7 @@ const paramsTransform = {
676
677
  newcode: quote.single,
677
678
  kind: quote.single,
678
679
  meta: quote.angle,
680
+ othermeta: quote.angle,
679
681
  keyword,
680
682
  // more complex convenience:
681
683
  names: transformManyWith( quoted ),
@@ -1573,6 +1575,9 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1573
1575
  result += '/target';
1574
1576
  break;
1575
1577
  }
1578
+ else if (step === 'targetAspect') {
1579
+ // skip
1580
+ }
1576
1581
  else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
1577
1582
  break; // don't go into xprs, refs, aliases, values, etc.
1578
1583
  }
@@ -1608,7 +1613,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1608
1613
  }
1609
1614
  else {
1610
1615
  if (options.testMode)
1611
- throw new CompilerAssertion(`semantic location: Missing segment: ${ csnPath[index] } for path ${ JSON.stringify( csnPath) }`);
1616
+ throw new CompilerAssertion(`semantic location: Unhandled segment: ${ csnPath[index] } for path ${ JSON.stringify( csnPath) }`);
1612
1617
  break;
1613
1618
  }
1614
1619
  next();
@@ -5,6 +5,7 @@
5
5
  "jsdoc"
6
6
  ],
7
7
  "rules": {
8
+ "cds-compiler/message-no-quotes": "off",
8
9
  "quotes": ["error", "single", {
9
10
  "avoidEscape": true,
10
11
  "allowTemplateLiterals": true
@@ -137,7 +137,7 @@ function checkActionOrFunction( art, artName, prop, path ) {
137
137
  // if (!(isMultiSchema && serviceOfType)) {
138
138
  this.error(null, currPath,
139
139
  { type: typeName, kind: type.kind, service: serviceName },
140
- '$(TYPE) of kind $(KIND) is defined outside a service and can\'t be used in $(SERVICE)');
140
+ 'Referenced $(KIND) $(TYPE) can\'t be used in service $(SERVICE) because it is not defined in $(SERVICE)');
141
141
  // }
142
142
  }
143
143
  }
@@ -24,8 +24,8 @@ function checkCoreMediaTypeAllowance( member ) {
24
24
  'cds.hana.BINARY': 1,
25
25
  };
26
26
  if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalTypeInfo(member.type)?.type in allowedCoreMediaTypes)) {
27
- this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
28
- 'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
27
+ this.warning(null, member.$path, { anno: '@Core.MediaType', names: [ 'Edm.String', 'Edm.Binary' ] },
28
+ 'Element annotated with $(ANNO) should be of a type mapped to $(NAMES)');
29
29
  }
30
30
  }
31
31
 
@@ -45,11 +45,14 @@ function validateSelectItems( query ) {
45
45
  };
46
46
 
47
47
  const transformers = {
48
+ /*
48
49
  columns: aTCB,
49
50
  groupBy: aTCB,
50
- orderBy: aTCB,
51
51
  having: aTCB,
52
52
  where: aTCB,
53
+ */
54
+ orderBy: aTCB, // filters in order by imply a join, not allowed
55
+ from: aTCB, // $self refs in from clause filters are not allowed
53
56
  };
54
57
 
55
58
  if (this.options.transformation === 'hdbcds') {
@@ -473,7 +473,7 @@ function assertConsistency( model, stage ) {
473
473
  optional: [
474
474
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
475
475
  // expressions as annotation values
476
- '$tokenTexts', 'op', 'args', 'func',
476
+ '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type',
477
477
  ],
478
478
  // TODO: restrict path to #simplePath
479
479
  },
@@ -679,6 +679,7 @@ function assertConsistency( model, stage ) {
679
679
  'keys',
680
680
  'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
681
681
  'localized-entity', // `.texts` entity
682
+ 'localized-origin', // `.texts` entity
682
683
  'nav', // only used for MASKED, TODO(v5): Remove
683
684
  'none', // only used in ensureColumnName(): Used in object representing empty alias
684
685
  'query', // inferred query properties, e.g. `key`
@@ -966,7 +967,7 @@ function assertConsistency( model, stage ) {
966
967
 
967
968
  function isOneOf( values ) {
968
969
  return function isOneOfInner( node, parent, prop ) {
969
- if (!values.includes( node ))
970
+ if (!values.includes( node ) && node !== undefined)
970
971
  throw new InternalConsistencyError( `Unexpected value '${ node }', expected ${ JSON.stringify( values ) }${ at( [ node, parent ], prop ) }` );
971
972
  };
972
973
  }
@@ -82,7 +82,7 @@ function getArtifactName( art ) {
82
82
  const { name } = art;
83
83
  if (!name) // no name
84
84
  return name;
85
- if (!art.kind) // annotation assignments
85
+ if (!art.kind) // annotation assignments, $self param type
86
86
  return { ...art.name, absolute: art.name.id };
87
87
  if (art.kind === 'using')
88
88
  return { ...art.name, absolute: art.extern.id };
@@ -62,6 +62,19 @@ const typeParameters = {
62
62
  // a.k.a "typeProperties"
63
63
  typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
64
64
 
65
+ /**
66
+ * Properties that are required next to `=` to make an annotation value an actual expression
67
+ * and not some foreign structure.
68
+ *
69
+ * @type {string[]}
70
+ */
71
+ const xprInAnnoProperties = [
72
+ 'ref', 'xpr', 'list', 'literal', 'val',
73
+ '#', 'func', 'args', 'SELECT', 'SET',
74
+ 'cast',
75
+ ];
76
+
77
+
65
78
  // const hana = {
66
79
  // BinaryFloat: {},
67
80
  // LocalDate: {},
@@ -178,7 +191,7 @@ function compileArg( src ) {
178
191
  const magicVariables = {
179
192
  $user: {
180
193
  // id and locale are always available
181
- elements: { id: {}, locale: {} },
194
+ elements: { id: {}, locale: {}, tenant: {} },
182
195
  // Allow $user.<any>
183
196
  $uncheckedElements: true,
184
197
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
@@ -301,6 +314,15 @@ function checkDate( year, month, day ) {
301
314
  return !Number.isNaN( year ) && month > 0 && month < 13 && day > 0 && day < 32;
302
315
  }
303
316
 
317
+ /**
318
+ * Return whether JSON object `val` is a representation for an annotation expression
319
+ */
320
+ function isAnnotationExpression( val ) {
321
+ // TODO: we might allow `'=': true`, not just a string, for expressions, to be
322
+ // decided → just check truthy at the moment
323
+ return val['='] && xprInAnnoProperties.some( prop => val[prop] !== undefined );
324
+ }
325
+
304
326
  /**
305
327
  * Check that the given time is within boundaries.
306
328
  * Checks according to ISO 8601.
@@ -535,10 +557,12 @@ function initBuiltins( model ) {
535
557
 
536
558
  module.exports = {
537
559
  typeParameters,
560
+ xprInAnnoProperties,
538
561
  functionsWithoutParens,
539
562
  specialFunctions,
540
563
  quotedLiteralPatterns,
541
564
  initBuiltins,
565
+ isAnnotationExpression,
542
566
  isInReservedNamespace,
543
567
  isBuiltinType,
544
568
  isIntegerTypeName,