@sap/cds-compiler 6.6.2 → 6.7.1

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 (44) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +6 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/compiler/assert-consistency.js +3 -4
  9. package/lib/compiler/base.js +8 -0
  10. package/lib/compiler/builtins.js +8 -9
  11. package/lib/compiler/checks.js +27 -6
  12. package/lib/compiler/cycle-detector.js +4 -4
  13. package/lib/compiler/define.js +65 -83
  14. package/lib/compiler/extend.js +357 -325
  15. package/lib/compiler/finalize-parse-cdl.js +3 -4
  16. package/lib/compiler/generate.js +205 -203
  17. package/lib/compiler/kick-start.js +34 -49
  18. package/lib/compiler/populate.js +95 -28
  19. package/lib/compiler/propagator.js +3 -5
  20. package/lib/compiler/resolve.js +17 -13
  21. package/lib/compiler/shared.js +47 -19
  22. package/lib/compiler/tweak-assocs.js +2 -4
  23. package/lib/compiler/utils.js +84 -31
  24. package/lib/gen/BaseParser.js +924 -1055
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +5 -2
  27. package/lib/json/from-csn.js +25 -16
  28. package/lib/main.d.ts +13 -0
  29. package/lib/model/revealInternalProperties.js +18 -0
  30. package/lib/parsers/AstBuildingParser.js +22 -5
  31. package/lib/render/toHdbcds.js +2 -2
  32. package/lib/render/utils/sql.js +2 -2
  33. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  34. package/lib/transform/db/constraints.js +3 -4
  35. package/lib/transform/db/killAnnotations.js +1 -1
  36. package/lib/transform/db/processSqlServices.js +10 -11
  37. package/lib/transform/forOdata.js +7 -124
  38. package/lib/transform/odata/fioriTreeViews.js +173 -0
  39. package/lib/transform/odata/flattening.js +2 -2
  40. package/lib/transform/translateAssocsToJoins.js +7 -4
  41. package/package.json +1 -1
  42. package/share/messages/message-explanations.json +0 -2
  43. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  44. package/share/messages/type-unexpected-on-condition.md +0 -52
package/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- # ChangeLog for cds compiler and backends
1
+ # Changelog
2
2
 
3
3
  <!-- markdownlint-disable MD024 -->
4
4
  <!-- markdownlint-disable MD004 -->
@@ -13,6 +13,33 @@ we might not list every change in its behavior here.
13
13
  Productive code should never require a `beta` flag to be set, and
14
14
  might use a deprecated flag only for a limited period of time.
15
15
 
16
+ ## Version 6.7.1 - 2026-01-28
17
+
18
+ ### Fixed
19
+
20
+ - compiler: Properly accept aspects as composition targets in an `extend`
21
+ (fixes regression introduced with v6.7.0)
22
+
23
+
24
+ ## Version 6.7.0 - 2026-01-23
25
+
26
+ ### Added
27
+
28
+ - to.hdi: Support .hdbprojectionview for Data Product Production
29
+
30
+ ### Changed
31
+
32
+ - compiler: Change internal processing sequence (extensions and entity generation) for
33
+ potentially upcoming compiler features; messages for erroneous models might differ slightly
34
+
35
+ - for.odata/to.edm(x): Enhancements for Fiori Tree Views: support managed associations with explicit foreign keys,
36
+ raise messages when the `@hierarchy` annotation cannot be applied.
37
+
38
+ ### Fixed
39
+
40
+ - to.sql: portable hana functions `*_between` with dates
41
+
42
+
16
43
  ## Version 6.6.2 - 2026-01-16
17
44
 
18
45
  ### Fixed
package/bin/cdsc.js CHANGED
@@ -11,6 +11,8 @@
11
11
  // For recursive *.cds expansion, use
12
12
  // cdsc $(find . -name '*.cds' -type f)
13
13
 
14
+ // Remark: for LSP support output, use `node test/lsp/lsp.crawler.js FILE.cds`
15
+
14
16
  'use strict';
15
17
 
16
18
  /* eslint no-console:off */
package/bin/cdsse.js CHANGED
@@ -126,7 +126,7 @@ function find( err, buf ) {
126
126
  const vn = messageAt( messages, 'validNames', off.col ) || Object.create(null);
127
127
  let art = vn[buf.substring( off.prefix, off.cursor )];
128
128
  while (art?._origin && art.$inferred && art._effectiveType ||
129
- autoNavigateKinds[art.kind]?.( art ))
129
+ autoNavigateKinds[art?.kind]?.( art ))
130
130
  art = art._origin || art.extern._artifact;
131
131
  // TODO: set _origin in using proxies
132
132
  // TODO: why no _effectiveType for $navElement ?
@@ -207,6 +207,7 @@ const centralMessages = {
207
207
  'syntax-unexpected-many-one': { severity: 'Error' },
208
208
  'syntax-deprecated-ref-virtual': { severity: 'Error' },
209
209
  'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
210
+ 'syntax-unexpected-with-returns': { severity: 'Warning' }, // errorFor: [ 'v8' ] }, or v7?
210
211
  'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
211
212
  'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
212
213
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
@@ -914,9 +915,6 @@ const centralMessageTexts = {
914
915
  min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',
915
916
  'incorrect-type': 'Expected $(NAMES) for argument $(PROP), but found $(CODE)',
916
917
  },
917
- 'type-unexpected-foreign-keys': 'A managed aspect composition can\'t have a foreign keys specification. Use composition-of-entity or remove foreign keys',
918
- 'type-unexpected-on-condition': 'A managed aspect composition can\'t have a specified ON-condition. Use composition-of-entity or remove the ON-condition',
919
-
920
918
  'type-invalid-items': {
921
919
  std: 'Unexpected $(PROP)', // unused
922
920
  nested: 'Unexpected $(PROP) inside $(PROP)',
@@ -1060,10 +1058,9 @@ const centralMessageTexts = {
1060
1058
 
1061
1059
  'def-invalid-texts-aspect': {
1062
1060
  std: '$(ART) is not valid', // unused
1063
- 'no-aspect': '$(ART) must be an aspect',
1064
- key: '$(ART) must be a key',
1065
- 'no-key': '$(ART) must not be key',
1066
- missing: '$(ART) must have an element $(NAME)',
1061
+ 'no-aspect': '$(ART) must be an aspect with elements',
1062
+ key: 'Element $(NAME) of $(ART) must be a key',
1063
+ missing: '$(ART) must have a direct element $(NAME)',
1067
1064
  },
1068
1065
  'def-invalid-element-type': {
1069
1066
  std: 'Element $(ELEMREF) of $(ART) must be of type $(TYPE)',
@@ -1114,6 +1111,8 @@ const centralMessageTexts = {
1114
1111
  'ref-invalid-target': {
1115
1112
  std: 'Expecting an entity as target',
1116
1113
  composition: 'Expecting an entity or aspect as composition target',
1114
+ on: 'Expecting an entity as composition target when followed by an ON-condition',
1115
+ keys: 'Expecting an entity as composition target when followed by foreign keys',
1117
1116
  bare: 'Expecting the target aspect to have elements',
1118
1117
  aspect: 'Expecting an aspect in property $(PROP)', // `targetAspect` in CSN input
1119
1118
  redirected: 'Expecting an entity as target; a target aspect can\'t be specified for redirections',
package/lib/base/model.js CHANGED
@@ -30,7 +30,6 @@ const availableBetaFlags = {
30
30
  v7preview: true,
31
31
  rewriteAnnotationExpressionsViaType: true,
32
32
  sqlServiceDummies: true,
33
- projectionViews: true,
34
33
  // disabled by --beta-mode
35
34
  nestedServices: false,
36
35
  };
@@ -119,72 +118,6 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
119
118
  });
120
119
  }
121
120
 
122
- /**
123
- * Apply function `callback` to all artifacts in dictionary
124
- * `model.definitions`. See function `forEachGeneric` for details.
125
- * TODO: should we skip "namespaces" already here?
126
- */
127
- function forEachDefinition( model, callback ) {
128
- forEachGeneric( model, 'definitions', callback );
129
- }
130
-
131
- /**
132
- * Apply function `callback` to all members of object `obj` (main artifact or
133
- * parent member). Members are considered those in dictionaries `elements`,
134
- * `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
135
- * searched inside property `items` (array of). See function `forEachGeneric`
136
- * for details.
137
- *
138
- * @param {XSN.Artifact} construct
139
- * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
140
- * @param {XSN.Artifact} [target]
141
- */
142
- function forEachMember( construct, callback, target ) {
143
- let obj = construct;
144
- while (obj.items)
145
- obj = obj.items;
146
- forEachGeneric( target || obj, 'elements', callback );
147
- forEachGeneric( obj, 'enum', callback );
148
- forEachGeneric( obj, 'foreignKeys', callback );
149
- forEachGeneric( construct, 'actions', callback );
150
- forEachGeneric( construct, 'params', callback );
151
- if (construct.returns)
152
- callback( construct.returns, '', 'params' );
153
- }
154
-
155
- /**
156
- * Same as forEachMember, but inside each member, calls itself recursively, i.e.
157
- * sub members are traversed as well.
158
- *
159
- * @param {XSN.Artifact} construct
160
- * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
161
- * @param {XSN.Artifact} [target]
162
- */
163
- function forEachMemberRecursively( construct, callback, target ) {
164
- forEachMember( construct, ( member, memberName, prop ) => {
165
- callback( member, memberName, prop );
166
- // Descend into nested members, too
167
- forEachMemberRecursively( member, callback );
168
- }, target);
169
- }
170
-
171
- // Apply function `callback` to all objects in dictionary `dict`, including all
172
- // duplicates (found under the same name). Function `callback` is called with
173
- // the following arguments: the object, the name, and - if it is a duplicate -
174
- // the array index and the array containing all duplicates.
175
- function forEachGeneric( obj, prop, callback ) {
176
- const dict = obj[prop];
177
- for (const name in dict) {
178
- obj = dict[name];
179
- const { $duplicates } = obj;
180
- callback( obj, name, prop );
181
- if (Array.isArray($duplicates)) // redefinitions
182
- $duplicates.forEach( o => callback( o, name, prop ) );
183
- }
184
- }
185
-
186
- const forEachInOrder = forEachGeneric;
187
-
188
121
  /**
189
122
  * Like `obj.prop = value`, but not contained in JSON / CSN
190
123
  * It's important to set enumerable explicitly to false (although 'false' is the default),
@@ -208,10 +141,5 @@ module.exports = {
208
141
  availableBetaFlags,
209
142
  isDeprecatedEnabled,
210
143
  checkRemovedDeprecatedFlags,
211
- forEachDefinition,
212
- forEachMember,
213
- forEachMemberRecursively,
214
- forEachGeneric,
215
- forEachInOrder,
216
144
  setProp,
217
145
  };
@@ -129,7 +129,7 @@ function checkManagedAssoc( member ) {
129
129
  // Special case for "--with-mocks" of the cds cli: For testing databases such as H2 and SQLite, we allow
130
130
  // associations with neither on-condition nor foreign keys if the CSN is mocked.
131
131
  // See #13916 for details.
132
- const allowForMocked = this.csn._mocked && (this.options.sqlDialect === 'h2' || this.options.sqlDialect === 'sqlite');
132
+ const allowForMocked = this.csn._mocked && (this.options.sqlDialect in { sqlite: 1, h2: 1, plain: 1 });
133
133
 
134
134
  const isPersisted = !hasPersistenceSkipAnnotation(this.artifact) && !this.artifact['@cds.persistence.exists'];
135
135
  if (isPersisted && !allowForMocked && !member.keys && (targetMax === '*' || Number(targetMax) > 1) && this.options.transformation === 'sql') {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { setProp } = require('../base/model');
4
4
  const { featureFlags } = require('../transform/featureFlags');
5
- const { isSqlService, isDummyService, isDataProductService } = require('../transform/db/processSqlServices');
5
+ const { isSqlService, isDummyService, isDataProductProductionService } = require('../transform/db/processSqlServices');
6
6
 
7
7
  /**
8
8
  *
@@ -33,7 +33,7 @@ module.exports = {
33
33
  if (isDummyService(artifact, this.options))
34
34
  setFeatureFlag( '$dummyService' ).call(this);
35
35
 
36
- if (isDataProductService(artifact, this.options))
36
+ if (isDataProductProductionService(artifact, this.options))
37
37
  setFeatureFlag( '$dataProductService' ).call(this);
38
38
  },
39
39
  };
@@ -104,7 +104,7 @@ function assertConsistency( model, stage ) {
104
104
  '$internal',
105
105
  '$compositionTargets',
106
106
  '$collectedExtensions',
107
- '_entities', '$entity',
107
+ '_entities',
108
108
  '$blocks',
109
109
  '$messageFunctions',
110
110
  '$functions',
@@ -407,9 +407,8 @@ function assertConsistency( model, stage ) {
407
407
  func: { test: TODO },
408
408
  suffix: { test: TODO },
409
409
  kind: {
410
- isRequired: !stageParser && (() => true),
410
+ isRequired: () => true, // required even with parse or other errors
411
411
  kind: true,
412
- // required to be set by Core Compiler even with parse errors
413
412
  test: isString,
414
413
  enum: [
415
414
  'context', 'service', 'entity', 'type', 'aspect', 'annotation',
@@ -675,7 +674,6 @@ function assertConsistency( model, stage ) {
675
674
  _status: { kind: true, test: TODO }, // TODO: $status
676
675
  _projections: { kind: true, test: TODO },
677
676
  _complexProjections: { kind: true, test: TODO }, // for projected paths with filters
678
- $entity: { kind: true, test: TODO },
679
677
  _entities: { test: TODO },
680
678
  $compositionTargets: { test: isDictionary( isBoolean ) },
681
679
  $collectedExtensions: { test: TODO },
@@ -684,6 +682,7 @@ function assertConsistency( model, stage ) {
684
682
  // for implicit redirection - direct and indirect query sources of simple
685
683
  // projections/views without @(cds.redirection.target: false):
686
684
  _ancestors: { kind: [ 'type', 'entity' ], test: isArray( TODO ) },
685
+ _entityIncludes: { kind: [ 'entity' ], test: isArray( TODO ) },
687
686
  // for implicit redirection - maps service name to simple projections/views
688
687
  // in that service which have the current artifact in _ancestors
689
688
  // (it can contain the artifact itself with no/failed autoexposure):
@@ -134,8 +134,16 @@ function getMemberNameProp( elem, kind ) {
134
134
  throw new CompilerAssertion( `Member not found in parent properties ${ Object.keys( obj ).join( '+' ) }` );
135
135
  }
136
136
 
137
+ /**
138
+ * Return true if an element's `value` is also a valid (recommended) enum value.
139
+ */
140
+ function isValidEnumValue( value ) {
141
+ return !value || !value.stored && typeof value.val in { string: 1, number: 1 };
142
+ }
143
+
137
144
  module.exports = {
138
145
  dictKinds,
139
146
  kindProperties,
140
147
  getArtifactName,
148
+ isValidEnumValue,
141
149
  };
@@ -8,7 +8,7 @@
8
8
  'use strict';
9
9
 
10
10
  const { builtinLocation } = require('../base/location');
11
- const { setLink: setProp } = require('./utils');
11
+ const { setLink } = require('./utils');
12
12
  const { isBetaEnabled } = require('../base/model');
13
13
 
14
14
  // TODO: make type parameters a dict
@@ -395,7 +395,7 @@ function initBuiltins( model ) {
395
395
  builtin,
396
396
  location: builtinLocation(),
397
397
  };
398
- setProp( art, '_subArtifacts', Object.create( null ) );
398
+ setLink( art, '_subArtifacts', Object.create( null ) );
399
399
  return art;
400
400
  }
401
401
 
@@ -418,9 +418,8 @@ function initBuiltins( model ) {
418
418
  };
419
419
  if (parent)
420
420
  parent._subArtifacts[name] = art;
421
- setProp( art, '_origin', '' );
422
- setProp( art, '_effectiveType', art );
423
- setProp( art, '_deps', [] );
421
+ setLink( art, '_origin', '' );
422
+ setLink( art, '_deps', [] );
424
423
  Object.assign( art, builtins[name] );
425
424
  if (!art.internal)
426
425
  artifacts[name] = art;
@@ -443,7 +442,7 @@ function initBuiltins( model ) {
443
442
  name: { id },
444
443
  };
445
444
  elements[id] = art;
446
- setProp( art, '_parent', model.$magicVariables );
445
+ setLink( art, '_parent', model.$magicVariables );
447
446
 
448
447
  if (magic.$autoElement)
449
448
  art.$autoElement = magic.$autoElement;
@@ -459,7 +458,7 @@ function initBuiltins( model ) {
459
458
  createMagicElements( art, magic.elements );
460
459
  if (options.variableReplacements?.[id])
461
460
  createMagicElements( art, options.variableReplacements[id] );
462
- // setProp( art, '_effectiveType', art );
461
+ // setLink( art, '_effectiveType', art );
463
462
  }
464
463
  }
465
464
 
@@ -481,8 +480,8 @@ function initBuiltins( model ) {
481
480
  magic.$uncheckedElements = art.$uncheckedElements;
482
481
  if (art.$restricted)
483
482
  magic.$restricted = art.$restricted;
484
- setProp( magic, '_parent', art );
485
- // setProp( magic, '_effectiveType', magic );
483
+ setLink( magic, '_parent', art );
484
+ // setLink( magic, '_effectiveType', magic );
486
485
  if (elements[id] && typeof elements[id] === 'object')
487
486
  createMagicElements( magic, elements[id] );
488
487
 
@@ -9,16 +9,15 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ const { isDeprecatedEnabled } = require('../base/model');
13
+ const { propagationRules, acceptsExprValues } = require('../base/builtins');
14
+ const { typeParameters } = require('./builtins');
12
15
  const {
13
16
  forEachGeneric,
14
17
  forEachDefinition,
15
18
  forEachMember,
16
- forEachMemberRecursively,
17
- isDeprecatedEnabled,
18
- } = require('../base/model');
19
- const { typeParameters } = require('./builtins');
20
- const { propagationRules, acceptsExprValues } = require('../base/builtins');
21
- const { annotationVal } = require('./utils');
19
+ annotationVal,
20
+ } = require('./utils');
22
21
 
23
22
  const $location = Symbol.for( 'cds.$location' );
24
23
 
@@ -50,6 +49,8 @@ function check( model ) {
50
49
  checkGenericConstruct( def );
51
50
  if (def.includes && def.elements)
52
51
  checkElementIncludeOverride( def );
52
+ while (def.items)
53
+ def = def.items;
53
54
  forEachMember( def, member => checkMember( member ) );
54
55
  if (def.$queries)
55
56
  def.$queries.forEach( checkQuery );
@@ -107,6 +108,8 @@ function check( model ) {
107
108
  if (member.kind === 'element')
108
109
  checkElement( member, parentProps );
109
110
 
111
+ while (member.items)
112
+ member = member.items;
110
113
  forEachMember( member, m => checkMember( m, parentProps ) );
111
114
  }
112
115
 
@@ -1419,4 +1422,22 @@ function isComplexView( art ) {
1419
1422
  return (!art.query.from?._artifact || art.query.from._artifact.kind === 'element');
1420
1423
  }
1421
1424
 
1425
+ /**
1426
+ * Same as forEachMember, but inside each member, calls itself recursively, i.e.
1427
+ * sub members are traversed as well.
1428
+ *
1429
+ * @param {XSN.Artifact} construct
1430
+ * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
1431
+ * @param {XSN.Artifact} [target]
1432
+ */
1433
+ function forEachMemberRecursively( construct, callback ) {
1434
+ while (construct.items)
1435
+ construct = construct.items;
1436
+ forEachMember( construct, ( member, memberName, prop ) => {
1437
+ callback( member, memberName, prop );
1438
+ // Descend into nested members, too
1439
+ forEachMemberRecursively( member, callback );
1440
+ } );
1441
+ }
1442
+
1422
1443
  module.exports = check;
@@ -16,7 +16,7 @@
16
16
 
17
17
  'use strict';
18
18
 
19
- const { setLink: setProp } = require('./utils'); // check enum/non-enum
19
+ const { setLink } = require('./utils'); // check enum/non-enum
20
20
 
21
21
  // Detect cyclic dependencies between all nodes reachable from `definitions`.
22
22
  // If such a dependency is found, call `reportCycle` with arguments `dep.art`
@@ -51,7 +51,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
51
51
  // console.log('CALL: ', v.kind,v.name)
52
52
  ++index;
53
53
  if (!v._scc) {
54
- setProp( v, '_scc', {
54
+ setLink( v, '_scc', {
55
55
  index,
56
56
  lowlink: index,
57
57
  onStack: true,
@@ -61,14 +61,14 @@ function detectCycles( definitions, reportCycle, cbScc ) {
61
61
  // console.log('PUSH: ', v.kind,v.name)
62
62
  }
63
63
  if (!v._deps) // builtins, otherwise forgotten (TODO: assert in --test-mode)
64
- setProp( v, '_deps', [] );
64
+ setLink( v, '_deps', [] );
65
65
  // assert( v._scc.onStack );
66
66
 
67
67
  // Now consider successors of v (called w):
68
68
  while (v._scc.depIndex < v._deps.length) {
69
69
  const w = v._deps[v._scc.depIndex++].art;
70
70
  if (!w._scc) { // node has not yet been visited
71
- setProp( w, '_sccCaller', v );
71
+ setLink( w, '_sccCaller', v );
72
72
  // console.log('CALL: ', v._scc.depIndex )
73
73
  return w; // recursive call with w in recursive algorithm
74
74
  }