@sap/cds-compiler 6.6.2 → 6.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 (52) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +13 -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/checks/manyExpand.js +28 -0
  9. package/lib/checks/validator.js +2 -0
  10. package/lib/compiler/assert-consistency.js +3 -4
  11. package/lib/compiler/base.js +8 -0
  12. package/lib/compiler/builtins.js +8 -9
  13. package/lib/compiler/checks.js +27 -6
  14. package/lib/compiler/cycle-detector.js +4 -4
  15. package/lib/compiler/define.js +65 -83
  16. package/lib/compiler/extend.js +357 -325
  17. package/lib/compiler/finalize-parse-cdl.js +3 -4
  18. package/lib/compiler/generate.js +205 -203
  19. package/lib/compiler/kick-start.js +34 -49
  20. package/lib/compiler/populate.js +95 -28
  21. package/lib/compiler/propagator.js +3 -5
  22. package/lib/compiler/resolve.js +17 -13
  23. package/lib/compiler/shared.js +52 -20
  24. package/lib/compiler/tweak-assocs.js +2 -4
  25. package/lib/compiler/utils.js +84 -31
  26. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  27. package/lib/gen/BaseParser.js +924 -1055
  28. package/lib/gen/CdlGrammar.checksum +1 -1
  29. package/lib/gen/CdlParser.js +5 -2
  30. package/lib/json/from-csn.js +25 -16
  31. package/lib/main.d.ts +13 -0
  32. package/lib/model/revealInternalProperties.js +18 -0
  33. package/lib/parsers/AstBuildingParser.js +22 -5
  34. package/lib/render/toHdbcds.js +2 -2
  35. package/lib/render/toSql.js +1 -1
  36. package/lib/render/utils/sql.js +2 -2
  37. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  38. package/lib/transform/db/constraints.js +3 -4
  39. package/lib/transform/db/expansion.js +2 -44
  40. package/lib/transform/db/killAnnotations.js +1 -1
  41. package/lib/transform/db/processSqlServices.js +10 -11
  42. package/lib/transform/effective/main.js +2 -1
  43. package/lib/transform/featureFlags.js +6 -1
  44. package/lib/transform/forOdata.js +7 -124
  45. package/lib/transform/forRelationalDB.js +2 -1
  46. package/lib/transform/odata/fioriTreeViews.js +173 -0
  47. package/lib/transform/odata/flattening.js +2 -2
  48. package/lib/transform/translateAssocsToJoins.js +7 -4
  49. package/package.json +1 -1
  50. package/share/messages/message-explanations.json +0 -2
  51. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  52. 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,42 @@ 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.2 - 2026-02-04
17
+
18
+ ### Fixed
19
+
20
+ - **compiler:** Just issue warning for `using` declaration referring to nothing
21
+ (fixes regression introduced with v6.7.0 if there is a file containing
22
+ a `namespace` declaration, but no definitions)
23
+ - **effective:** clean up internal Symbols from meta section
24
+ - **sql:** clean up internal Symbols from meta section
25
+
26
+ ## Version 6.7.1 - 2026-01-28
27
+
28
+ ### Fixed
29
+
30
+ - compiler: Properly accept aspects as composition targets in an `extend`
31
+ (fixes regression introduced with v6.7.0)
32
+
33
+ ## Version 6.7.0 - 2026-01-23
34
+
35
+ ### Added
36
+
37
+ - to.hdi: Support .hdbprojectionview for Data Product Production
38
+
39
+ ### Changed
40
+
41
+ - compiler: Change internal processing sequence (extensions and entity generation) for
42
+ potentially upcoming compiler features; messages for erroneous models might differ slightly
43
+
44
+ - for.odata/to.edm(x): Enhancements for Fiori Tree Views: support managed associations with explicit foreign keys,
45
+ raise messages when the `@hierarchy` annotation cannot be applied.
46
+
47
+ ### Fixed
48
+
49
+ - to.sql: portable hana functions `*_between` with dates
50
+
51
+
16
52
  ## Version 6.6.2 - 2026-01-16
17
53
 
18
54
  ### 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 ?
@@ -160,10 +160,12 @@ const centralMessages = {
160
160
  'type-ambiguous-target': { severity: 'Warning' },
161
161
 
162
162
  'ref-unexpected-autoexposed': { severity: 'Error' },
163
+ 'ref-unexpected-many-expand': { severity: 'Error' },
163
164
  'ref-unexpected-many-navigation': { severity: 'Error' },
164
165
  // Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
165
166
  'ref-undefined-art': { severity: 'Error' },
166
167
  'ref-undefined-def': { severity: 'Error' },
168
+ 'ref-undefined-using': { severity: 'Warning' },
167
169
  'ref-undefined-var': { severity: 'Error' },
168
170
  'ref-undefined-element': { severity: 'Error' },
169
171
  'anno-undefined-element': { severity: 'Error' },
@@ -207,6 +209,7 @@ const centralMessages = {
207
209
  'syntax-unexpected-many-one': { severity: 'Error' },
208
210
  'syntax-deprecated-ref-virtual': { severity: 'Error' },
209
211
  'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
212
+ 'syntax-unexpected-with-returns': { severity: 'Warning' }, // errorFor: [ 'v8' ] }, or v7?
210
213
  'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
211
214
  'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
212
215
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
@@ -737,6 +740,7 @@ const centralMessageTexts = {
737
740
  std: 'No artifact has been found with name $(ART)',
738
741
  localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
739
742
  },
743
+ 'ref-undefined-using': 'There is no definition in the model whose name starts with $(ART)',
740
744
  // TODO: proposal 'No definition found for $(NAME)',
741
745
  'ref-undefined-element': {
742
746
  std: 'Element $(ART) has not been found',
@@ -785,6 +789,10 @@ const centralMessageTexts = {
785
789
  std: '$(ART) can\'t be extended because it originates from an include',
786
790
  elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
787
791
  },
792
+ 'ref-unexpected-many-expand': {
793
+ std: 'Unexpected expand with to-many association',
794
+ composition: 'Unexpected expand with to-many composition',
795
+ },
788
796
  'ref-unexpected-many-navigation': {
789
797
  std: 'Unexpected navigation into arrayed structure',
790
798
  },
@@ -914,9 +922,6 @@ const centralMessageTexts = {
914
922
  min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',
915
923
  'incorrect-type': 'Expected $(NAMES) for argument $(PROP), but found $(CODE)',
916
924
  },
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
925
  'type-invalid-items': {
921
926
  std: 'Unexpected $(PROP)', // unused
922
927
  nested: 'Unexpected $(PROP) inside $(PROP)',
@@ -1060,10 +1065,9 @@ const centralMessageTexts = {
1060
1065
 
1061
1066
  'def-invalid-texts-aspect': {
1062
1067
  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)',
1068
+ 'no-aspect': '$(ART) must be an aspect with elements',
1069
+ key: 'Element $(NAME) of $(ART) must be a key',
1070
+ missing: '$(ART) must have a direct element $(NAME)',
1067
1071
  },
1068
1072
  'def-invalid-element-type': {
1069
1073
  std: 'Element $(ELEMREF) of $(ART) must be of type $(TYPE)',
@@ -1114,6 +1118,8 @@ const centralMessageTexts = {
1114
1118
  'ref-invalid-target': {
1115
1119
  std: 'Expecting an entity as target',
1116
1120
  composition: 'Expecting an entity or aspect as composition target',
1121
+ on: 'Expecting an entity as composition target when followed by an ON-condition',
1122
+ keys: 'Expecting an entity as composition target when followed by foreign keys',
1117
1123
  bare: 'Expecting the target aspect to have elements',
1118
1124
  aspect: 'Expecting an aspect in property $(PROP)', // `targetAspect` in CSN input
1119
1125
  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
  };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
4
+
5
+ /**
6
+ * Check for nested projections (expand) on to-many associations or compositions.
7
+ * Nested projections are only valid for to-one relationships.
8
+ *
9
+ * @param {object} parent Object with the expression as a property
10
+ * @param {string} propOnParent Name of the expression property on parent
11
+ * @param {Array} _e Expression to check (unused)
12
+ * @param {CSN.Path} path
13
+ */
14
+ function expandToMany( parent, propOnParent, _e, path ) {
15
+ applyTransformationsOnNonDictionary(parent, propOnParent, {
16
+ expand: (_parent, _prop, _expand, _path) => {
17
+ const last = _parent._links?.at(-1).art;
18
+ if (last?.cardinality && last.cardinality.max !== 1) {
19
+ this.message('ref-unexpected-many-expand', _path,
20
+ { '#': last.type === 'cds.Composition' ? 'composition' : 'std' });
21
+ }
22
+ },
23
+ }, null, path);
24
+ }
25
+
26
+ module.exports = {
27
+ columns: expandToMany,
28
+ };
@@ -12,6 +12,7 @@ const { validateSelectItems } = require('./selectItems');
12
12
  const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
13
13
  const validateCdsPersistenceAnnotation = require('./cdsPersistence');
14
14
  const navigationIntoMany = require('./manyNavigations');
15
+ const expandToMany = require('./manyExpand');
15
16
  const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
16
17
  const validateHasPersistedElements = require('./hasPersistedElements');
17
18
  const checkForHanaTypes = require('./checkForTypes');
@@ -91,6 +92,7 @@ const forRelationalDBCsnValidators = [
91
92
  existsMustNotStartWithDollarSelf,
92
93
  assertFilterOfExists,
93
94
  navigationIntoMany,
95
+ expandToMany,
94
96
  checkPathsInStoredCalcElement,
95
97
  featureFlags,
96
98
  ];
@@ -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
  }