@sap/cds-compiler 4.0.2 → 4.2.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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -23,10 +23,12 @@ const {
23
23
  } = require('./utils');
24
24
  const layers = require('./moduleLayers');
25
25
  const { CompilerAssertion } = require('../base/error');
26
+ const { CsnLocation } = require('./classes');
26
27
 
27
- const $location = Symbol.for('cds.$location');
28
+ const $location = Symbol.for( 'cds.$location' );
28
29
 
29
- const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
30
+ // attach stupid location - TODO: remove in v5
31
+ const genLocation = new CsnLocation( '' );
30
32
 
31
33
  // Array.prototype.spread = 42; // prototype-polluted JS classes
32
34
 
@@ -41,6 +43,7 @@ function extend( model ) {
41
43
  resolveTypeArgumentsUnchecked,
42
44
  attachAndEmitValidNames,
43
45
  initMembers,
46
+ initSelectItems,
44
47
  } = model.$functions;
45
48
 
46
49
  Object.assign( model.$functions, {
@@ -50,10 +53,10 @@ function extend( model ) {
50
53
  applyIncludes, // TODO: re-check
51
54
  } );
52
55
 
53
- const includesNonShadowedFirst = isDeprecatedEnabled(model.options, 'includesNonShadowedFirst');
56
+ const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
54
57
 
55
58
  sortModelSources();
56
- const extensionsDict = Object.create(null); // TODO TMP
59
+ const extensionsDict = Object.create( null ); // TODO TMP
57
60
  forEachDefinition( model, tagIncludes ); // TODO TMP
58
61
 
59
62
  forEachDefinition( model, extendArtifactBefore );
@@ -131,46 +134,32 @@ function extend( model ) {
131
134
  delete art.$typeExts;
132
135
  }
133
136
 
134
- if (art.kind === 'annotate' && !art.returns && extensionsMap.returns)
137
+ if (art.kind === 'annotate' && !art.returns && extensionsMap.returns && !art._parent?.returns)
135
138
  annotateCreate( art, '', art, 'returns' );
136
139
 
140
+ moveDictExtensions( art, extensionsMap, 'actions' );
137
141
  moveDictExtensions( art, extensionsMap, 'params' );
138
142
  moveReturnsExtensions( art, extensionsMap );
139
- const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;
140
- if (sub) {
141
- if (art.returns) { // after having applied params!
143
+
144
+ if (art.returns) {
145
+ pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
146
+ pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
147
+ if (art.kind !== 'annotate') {
142
148
  extendHandleReturns( extensionsMap.elements, art );
143
149
  extendHandleReturns( extensionsMap.enum, art );
150
+ return;
144
151
  }
145
- // care about 'ext-unexpected-returns' in a later change if we have XSN returns
146
- pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'elements'));
147
- pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'enum') );
152
+ }
153
+ const sub = art.items || art.targetAspect?.elements && art.targetAspect;
154
+ if (sub) {
155
+ pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
156
+ pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
148
157
  }
149
158
  else {
150
159
  moveDictExtensions( art, extensionsMap,
151
160
  (art.enum && art.kind !== 'annotate' ? 'enum' : 'elements'), 'elements' );
152
161
  moveDictExtensions( art, extensionsMap, 'enum' );
153
162
  }
154
- moveDictExtensions( art, extensionsMap, 'actions' );
155
- }
156
-
157
- /**
158
- * FIXME: Remove this workaround. This workaround avoids endless recursion for annotate statements
159
- * that have both "returns" and "elements". The endless recursion happens due to the
160
- * pushDict on `sub`. The `elements` extensions will point to the same extension on which
161
- * `returns` exist.
162
- *
163
- * @param extensionsMap
164
- * @param {string} prop
165
- * @return {XSN.Extension[]}
166
- */
167
- function avoidRecursiveReturns( extensionsMap, prop ) {
168
- if (!extensionsMap[prop])
169
- return [];
170
- const exts = [];
171
- for (const ext of extensionsMap[prop])
172
- exts.push({ ...ext, returns: undefined });
173
- return exts;
174
163
  }
175
164
 
176
165
  /**
@@ -221,7 +210,7 @@ function extend( model ) {
221
210
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
222
211
  // do not mention 'extend context', that is not in CAPire
223
212
  service: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) or $(KEYWORD) instead',
224
- });
213
+ } );
225
214
  }
226
215
  // TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
227
216
  }
@@ -231,7 +220,7 @@ function extend( model ) {
231
220
  // TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
232
221
  function transformArtifactExtensions( art ) {
233
222
  const hasOnlySubExtensions = art._outer; // items, anonymous aspects
234
- const dict = Object.create(null);
223
+ const dict = Object.create( null );
235
224
  for (const ext of art._extensions) {
236
225
  for (const prop in ext) {
237
226
  if (ext[prop] === undefined) // deleted property
@@ -383,12 +372,16 @@ function extend( model ) {
383
372
  const { query } = art;
384
373
  for (const col of ext.columns)
385
374
  col.$extended = true;
386
- if (!query?.from?.path)
375
+
376
+ if (!query?.from?.path) {
387
377
  error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
388
- else if (!query.columns)
378
+ return;
379
+ }
380
+ if (!query.columns)
389
381
  query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
390
382
  else
391
383
  query.columns.push( ...ext.columns );
384
+ initSelectItems( query, ext.columns, query, true );
392
385
  }
393
386
  else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
394
387
  const typeExts = art.$typeExts || (art.$typeExts = {});
@@ -428,7 +421,7 @@ function extend( model ) {
428
421
  while (prevPos < previousValue.length) {
429
422
  const prevItem = previousValue[prevPos++];
430
423
  result.push( prevItem );
431
- if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
424
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo )) {
432
425
  upToSpec = false;
433
426
  break;
434
427
  }
@@ -599,32 +592,41 @@ function extend( model ) {
599
592
  continue; // definitions inside extend, already handled
600
593
  dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
601
594
  const elem = artDict[name] || annotateFor( art, extProp, name );
602
- setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
595
+ setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
603
596
  pushToDict( elem, '_extensions', elemExt );
604
597
  }
605
598
  }
606
599
  }
607
600
 
608
601
  function moveReturnsExtensions( art, extensionsMap ) {
609
- if (!extensionsMap.returns)
602
+ const extensions = extensionsMap.returns;
603
+ if (!extensions)
610
604
  return;
605
+ const artReturns = art.returns;
606
+ let extReturns = artReturns;
607
+ const isAction = art.kind === 'action' || art.kind === 'function';
611
608
 
612
- for (const ext of extensionsMap.returns) {
613
- if (!art.returns && art.kind !== 'annotate') { // no check in super annotate statement
614
- const variant = art.kind === 'action' || art.kind === 'function' ? art.kind : 'std';
609
+ for (const ext of extensions) {
610
+ if (!artReturns && art.kind !== 'annotate') {
615
611
  warning( 'ext-unexpected-returns', [ ext.returns.location, ext ], {
616
- '#': variant, keyword: 'returns',
612
+ '#': (isAction ? art.kind : 'std'), keyword: 'returns',
617
613
  }, {
618
614
  std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
619
615
  action: 'Unexpected $(KEYWORD) for action without return parameter',
620
616
  // function without `returns` can happen via CSN input!
621
617
  function: 'Unexpected $(KEYWORD) for function without return parameter',
622
- });
618
+ } );
619
+ // Do not put completely wrong returns into a “super annotate” statement;
620
+ // this could induce consequential errors with [..., …]:
621
+ if (!isAction)
622
+ continue; // do not put into 'extensions'
623
+ // add to 'extensions' for action/function without returns:
624
+ extReturns ??= annotateFor( art, 'params', '' );
625
+ }
626
+ if (extReturns) {
627
+ setLink( ext.name, '_artifact', (isAction ? artReturns : null ) );
628
+ pushToDict( extReturns, '_extensions', ext.returns );
623
629
  }
624
- // If `!art.returns`, then it could be CSN from SQL, where actions are replaced by dummies.
625
- const elem = art.returns || annotateFor( art, 'params', '' );
626
- setLink( ext.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
627
- pushToDict(elem, '_extensions', ext.returns);
628
630
  }
629
631
  }
630
632
 
@@ -713,12 +715,17 @@ function extend( model ) {
713
715
  { '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
714
716
  std: 'Elements only exist in entities, types or typed constructs',
715
717
  entity: 'Elements of entity types can\'t be annotated',
716
- });
718
+ } );
717
719
  break;
718
720
  case 'params':
719
721
  warning( 'anno-unexpected-params', [ location, ext._parent ], {},
720
722
  'Parameters only exist for actions or functions' );
721
723
  break;
724
+ case 'actions':
725
+ // TODO: check if artifact can have actions, similar to `anno-unexpected-actions`
726
+ notFound( 'anno-undefined-action', ext.name.location, ext,
727
+ { '#': 'action', art: parent, name } );
728
+ break;
722
729
  default:
723
730
  if (model.options.testMode)
724
731
  throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
@@ -796,8 +803,6 @@ function extend( model ) {
796
803
  return;
797
804
  if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
798
805
  return;
799
- // else if (ext.kind === 'extend') { // TODO v4 - add error
800
- // }
801
806
  if (art?.kind === 'namespace') {
802
807
  // TODO: not at all different to having no definition
803
808
  info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
@@ -822,13 +827,13 @@ function extend( model ) {
822
827
  }
823
828
  else if (!construct.returns &&
824
829
  (art.kind === 'action' || art.kind === 'function') && construct.elements) {
825
- warning('ext-expecting-returns', [ construct.name.location, construct ], {
830
+ warning( 'ext-expecting-returns', [ construct.name.location, construct ], {
826
831
  '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
827
832
  }, {
828
833
  std: 'Expected $(CODE)', // unused variant
829
834
  action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
830
835
  function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
831
- });
836
+ } );
832
837
  }
833
838
  }
834
839
  }
@@ -871,7 +876,7 @@ function extend( model ) {
871
876
  */
872
877
  function extendArtifact( extensions, art, noIncludes = false ) {
873
878
  if (!noIncludes && !(canApplyIncludes( art, art ) &&
874
- extensions.every( ext => canApplyIncludes(ext, art) )))
879
+ extensions.every( ext => canApplyIncludes( ext, art ) )))
875
880
  return false;
876
881
  if (Array.isArray( noIncludes )) {
877
882
  canApplyIncludes( art, art, noIncludes );
@@ -879,7 +884,7 @@ function extend( model ) {
879
884
  }
880
885
  else if (!noIncludes &&
881
886
  !(canApplyIncludes( art, art ) &&
882
- extensions.every( ext => canApplyIncludes( ext, art) ))) {
887
+ extensions.every( ext => canApplyIncludes( ext, art ) ))) {
883
888
  // console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
884
889
  return false;
885
890
  }
@@ -887,8 +892,15 @@ function extend( model ) {
887
892
  model._entities.push( art ); // add structure with includes in dep order
888
893
  art.$entity = ++model.$entity;
889
894
  }
890
- if (!noIncludes && art.includes)
891
- applyIncludes( art, art );
895
+ if (art.includes) {
896
+ if (!noIncludes) {
897
+ applyIncludes( art, art );
898
+ }
899
+ else {
900
+ for (const ref of art.includes)
901
+ resolvePath( ref, 'include', art );
902
+ }
903
+ }
892
904
  // checkExtensionsKind( extensions, art );
893
905
  extendMembers( extensions, art, noIncludes === 'gen' );
894
906
  if (!noIncludes && art.includes) {
@@ -923,7 +935,7 @@ function extend( model ) {
923
935
  // includes = ['Base1',3,'Base2']
924
936
  // where 3 means adding the next 3 elements before applying include 'Base2'
925
937
  if (art.includes)
926
- art.includes.push(...ext.includes);
938
+ art.includes.push( ...ext.includes );
927
939
  else
928
940
  art.includes = [ ...ext.includes ];
929
941
  applyIncludes( ext, art );
@@ -932,7 +944,7 @@ function extend( model ) {
932
944
  checkAnnotate( ext, art );
933
945
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
934
946
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
935
- dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
947
+ dependsOnSilent( art, ext ); // art depends silently on ext (inverse to normal dep!)
936
948
  }
937
949
  for (const name in ext.elements) {
938
950
  const elem = ext.elements[name];
@@ -946,7 +958,7 @@ function extend( model ) {
946
958
  reportUnstableExtensions( elemExtensions );
947
959
 
948
960
  // This whole function will be removed with a next change - no need to have nice code here:
949
- const dict = Object.create(null);
961
+ const dict = Object.create( null );
950
962
  // actions cannot be extended anyway. TODO: there should be a message
951
963
  // (possible with CSN input), but that was missing before this change, too.
952
964
  for (const e of extensions) {
@@ -988,7 +1000,8 @@ function extend( model ) {
988
1000
  let lastExt = null;
989
1001
  let open = []; // the "highest" layers
990
1002
  for (const ext of extensions) {
991
- const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
1003
+ const extLayer = layers.layer( ext ) ||
1004
+ { realname: '', _layerExtends: Object.create( null ) };
992
1005
  if (!open.length) {
993
1006
  lastExt = ext;
994
1007
  open = [ extLayer.realname ];
@@ -1036,7 +1049,7 @@ function extend( model ) {
1036
1049
  element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
1037
1050
  action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
1038
1051
  } );
1039
- attachAndEmitValidNames(msg, validDict);
1052
+ attachAndEmitValidNames( msg, validDict );
1040
1053
  }
1041
1054
  }
1042
1055
 
@@ -1055,7 +1068,6 @@ function extend( model ) {
1055
1068
  function canApplyIncludes( art, target, justResolveCyclic ) {
1056
1069
  if (!art.includes)
1057
1070
  return true;
1058
- const isView = !!target.query;
1059
1071
  for (const ref of art.includes) {
1060
1072
  const name = resolveUncheckedPath( ref, 'include', art );
1061
1073
  // console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
@@ -1071,7 +1083,6 @@ function extend( model ) {
1071
1083
  else if (ref._artifact) {
1072
1084
  delete ref._artifact;
1073
1085
  }
1074
- resolvePath( ref, isView ? 'viewInclude' : 'include', art );
1075
1086
  }
1076
1087
  return true;
1077
1088
  }
@@ -1080,7 +1091,10 @@ function extend( model ) {
1080
1091
  * Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
1081
1092
  * If `ext === art`, then includes of the artifact itself are applied.
1082
1093
  * If `ext !== art`, applies includes on the extensions, not artifact.
1083
- * Sets `_ancestor` links on `art`.
1094
+ * Sets `_ancestors` links on `art`.
1095
+ *
1096
+ * TODO: try to set `_ancestors` only to entities (but beware “intermediate”
1097
+ * non-entities).
1084
1098
  *
1085
1099
  * Examples:
1086
1100
  * ext === art: `entity E : F {}` => add elements of F to E
@@ -1096,22 +1110,31 @@ function extend( model ) {
1096
1110
  return;
1097
1111
  }
1098
1112
 
1099
- if (!art.query) {
1100
- if (!art._ancestors)
1101
- setLink( art, '_ancestors', [] ); // recursive array of includes
1113
+ if (!art._ancestors && !art.query)
1114
+ setLink( art, '_ancestors', [] ); // recursive array of includes
1115
+ for (const ref of ext.includes) {
1116
+ const template = resolvePath( ref, 'include', art );
1117
+ // !template -> non-includable, e.g. scalar type, or cyclic
1118
+ if (template && !art.query) {
1119
+ if (template._ancestors)
1120
+ art._ancestors.push( ...template._ancestors );
1121
+ art._ancestors.push( template );
1122
+ }
1123
+ }
1124
+ if (!art.query && art.elements) // do not set art.elements and art.enums with query entity!
1125
+ includeMembers( ext, art, 'elements' );
1126
+ if (art.kind !== 'type') {
1127
+ includeMembers( ext, art, 'actions' );
1128
+ }
1129
+ else {
1102
1130
  for (const ref of ext.includes) {
1103
- const template = ref._artifact;
1104
- // !template -> non-includable, e.g. scalar type, or cyclic
1105
- if (template) {
1106
- if (template._ancestors)
1107
- art._ancestors.push( ...template._ancestors );
1108
- art._ancestors.push( template );
1131
+ const template = ref._artifact; // already resolved
1132
+ if (template?.actions && Object.keys( template.actions ).length) {
1133
+ warning( 'ref-ignoring-actions', [ ref.location, ext ], { art: template },
1134
+ 'The actions of $(ART) are not added to the type' );
1109
1135
  }
1110
1136
  }
1111
1137
  }
1112
- if (!art.query) // do not set art.elements and art.enums with query entity!
1113
- includeMembers( ext, art, 'elements' );
1114
- includeMembers( ext, art, 'actions' );
1115
1138
  }
1116
1139
 
1117
1140
  /**
@@ -1128,12 +1151,15 @@ function extend( model ) {
1128
1151
  // Warning 'Overwrites definition from include "I" (at elem def)
1129
1152
  const parent = ext === art && art;
1130
1153
  const members = ext[prop];
1131
- ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
1154
+ if (members)
1155
+ ext[prop] = Object.create( null );
1132
1156
  let hasNewElement = false;
1133
1157
 
1134
1158
  for (const ref of ext.includes) {
1135
1159
  const template = ref._artifact; // already resolved
1136
1160
  if (template) { // be robust
1161
+ if (template[prop] && !ext[prop])
1162
+ ext[prop] = Object.create( null );
1137
1163
  // eslint-disable-next-line no-loop-func
1138
1164
  forEachInOrder( template, prop, ( origin, name ) => {
1139
1165
  if (members && members[name]) {
@@ -1147,7 +1173,7 @@ function extend( model ) {
1147
1173
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1148
1174
  dictAdd( ext[prop], name, elem );
1149
1175
  elem.$inferred = 'include';
1150
- if (origin.masked)
1176
+ if (origin.masked) // TODO(v5): remove 'masked'
1151
1177
  elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
1152
1178
  if (origin.key)
1153
1179
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
@@ -1160,7 +1186,7 @@ function extend( model ) {
1160
1186
  setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
1161
1187
  }
1162
1188
  // TODO: also complain if elem is just defined in art
1163
- });
1189
+ } );
1164
1190
  }
1165
1191
  }
1166
1192
 
@@ -1176,7 +1202,7 @@ function extend( model ) {
1176
1202
  // the element order.
1177
1203
  if (ext[prop][name] !== elem )
1178
1204
  dictAdd( ext[prop], name, elem );
1179
- });
1205
+ } );
1180
1206
  }
1181
1207
  }
1182
1208
 
@@ -1189,13 +1215,13 @@ function extend( model ) {
1189
1215
  function checkRedefinitionThroughIncludes( parent, prop ) {
1190
1216
  if (!parent[prop])
1191
1217
  return;
1192
- forEachInOrder(parent, prop, ( member, name ) => {
1193
- if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
1194
- const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
1218
+ forEachInOrder( parent, prop, ( member, name ) => {
1219
+ if (member.$inferred === 'include' && Array.isArray( member.$duplicates )) {
1220
+ const includes = [ member, ...member.$duplicates ].map( dup => dup._origin._main );
1195
1221
  error( 'duplicate-definition', [ parent.name.location, member ],
1196
1222
  { '#': `include-${ prop }`, name, sorted_arts: includes } );
1197
1223
  }
1198
- });
1224
+ } );
1199
1225
  }
1200
1226
  }
1201
1227
 
@@ -1207,7 +1233,7 @@ function extend( model ) {
1207
1233
  * @returns {Record<string, object>} key: layer name, value: {name, layer, extensions[]}`
1208
1234
  */
1209
1235
  function layeredExtensions( extensions ) {
1210
- const layered = Object.create(null);
1236
+ const layered = Object.create( null );
1211
1237
  for (const ext of extensions) {
1212
1238
  const layer = (ext.kind === 'annotate' || ext.kind === 'extend' || ext.kind === 'source') &&
1213
1239
  layers.layer( ext );
@@ -1239,12 +1265,13 @@ function extensionsOfHighestLayers( layered ) {
1239
1265
  if (layerNames.length <= 1) {
1240
1266
  const name = layerNames[0];
1241
1267
  const highest = layered[name]?.extensions || [];
1268
+ highest.sort( compareExtensions );
1242
1269
  delete layered[name];
1243
1270
  return { highest, issue: inMoreThanOneFile( highest ) };
1244
1271
  }
1245
1272
 
1246
1273
  // collect all layers which are lower than another layer
1247
- const allExtends = Object.create(null);
1274
+ const allExtends = Object.create( null );
1248
1275
  allExtends[''] = {}; // the "Define" layer
1249
1276
  for (const name of layerNames) {
1250
1277
  if (name) // not the "Define" layer
@@ -1262,7 +1289,7 @@ function extensionsOfHighestLayers( layered ) {
1262
1289
  }
1263
1290
  }
1264
1291
  highest.sort( compareExtensions );
1265
- const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ));
1292
+ const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ) );
1266
1293
  // TODO: use layer.file instead
1267
1294
  const issue = !good || highestLayers.length > 1 && 'unrelated';
1268
1295
  // console.log('HI:',highest.map(l=>l.name),issue,issue&&extensions)
@@ -1273,7 +1300,7 @@ function inMoreThanOneFile( extensions ) {
1273
1300
  if (extensions.length <= 1)
1274
1301
  return false;
1275
1302
  const file = extensions[0].location?.file;
1276
- return !file || extensions.slice(1).some( e => e.location?.file !== file );
1303
+ return !file || extensions.some( e => e.location?.file !== file );
1277
1304
  }
1278
1305
 
1279
1306
  /**
@@ -47,14 +47,14 @@ function finalizeParseCdl( model ) {
47
47
  }
48
48
  }
49
49
 
50
- forEachGeneric(model, 'definitions', art => resolveTypesForParseCdl(art, art));
51
- forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
50
+ forEachGeneric( model, 'definitions', art => resolveTypesForParseCdl( art, art ) );
51
+ forEachGeneric( model, 'vocabularies', art => resolveTypesForParseCdl( art, art ) );
52
52
 
53
53
  if (extensions.length > 0) {
54
54
  // TODO: do a sort based on the location in case there were two extensions
55
55
  // on the same definition? Yes: anno first outside, then inside service def
56
56
  model.extensions = extensions;
57
- model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
57
+ model.extensions.forEach( ext => resolveTypesForParseCdl( ext, ext ) );
58
58
  }
59
59
  }
60
60
 
@@ -69,9 +69,9 @@ function finalizeParseCdl( model ) {
69
69
  if (!artifact || typeof artifact !== 'object')
70
70
  return;
71
71
 
72
- if (Array.isArray(artifact)) {
72
+ if (Array.isArray( artifact )) {
73
73
  // e.g. `args` array
74
- artifact.forEach(art => resolveTypesForParseCdl(art, main));
74
+ artifact.forEach( art => resolveTypesForParseCdl( art, main ) );
75
75
  return;
76
76
  }
77
77
 
@@ -80,65 +80,66 @@ function finalizeParseCdl( model ) {
80
80
  return;
81
81
 
82
82
  if (artifact.type)
83
- resolveTypeUnchecked(artifact, main);
83
+ resolveTypeUnchecked( artifact, main );
84
84
 
85
85
  for (const include of artifact.includes || [])
86
- resolveUncheckedPath(include, 'include', main);
86
+ resolveUncheckedPath( include, 'include', main );
87
87
 
88
88
  // define.js takes care that `target` is a ref and
89
89
  // `targetAspect` is a structure.
90
90
  if (artifact.target)
91
- resolveUncheckedPath(artifact.target, 'target', main);
91
+ resolveUncheckedPath( artifact.target, 'target', main );
92
92
  if (artifact.targetAspect)
93
- resolveTypesForParseCdl(artifact.targetAspect, main);
93
+ resolveTypesForParseCdl( artifact.targetAspect, main );
94
94
 
95
95
  if (artifact.from) {
96
96
  const { from } = artifact;
97
- resolveUncheckedPath(from, 'from', main);
98
- resolveTypesForParseCdl(from, main);
97
+ resolveUncheckedPath( from, 'from', main );
98
+ resolveTypesForParseCdl( from, main );
99
99
  }
100
100
 
101
101
  // Recursively go through all XSN properties. There are a few that need to be
102
102
  // handled specifically. Refer to the code below this loop for details.
103
103
  for (const prop in artifact) {
104
- if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
104
+ if (artifact[prop] === undefined || parseCdlSpeciallyHandledXsnProps.includes( prop ) ||
105
+ parseCdlIgnoredXsnProps.includes( prop ))
105
106
  continue;
106
107
 
107
- if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
108
+ if (artifact[prop] && Object.getPrototypeOf( artifact[prop] ) === null)
108
109
  // Dictionary in XSN
109
- forEachGeneric(artifact, prop, art => resolveTypesForParseCdl(art, art));
110
+ forEachGeneric( artifact, prop, art => resolveTypesForParseCdl( art, art ) );
110
111
  else
111
- resolveTypesForParseCdl(artifact[prop], main);
112
+ resolveTypesForParseCdl( artifact[prop], main );
112
113
  }
113
114
 
114
115
  // `$queries` has a flat structure that we can use instead of going through all `query`.
115
116
  // For these query-related properties, we need to keep the reference to the artifact
116
117
  // containing it. Otherwise some type's aren't properly resolved.
117
118
  // TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
118
- (artifact.$queries || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
119
- (artifact.columns || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
120
- forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl(art, artifact) );
119
+ (artifact.$queries || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
120
+ (artifact.columns || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
121
+ forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl( art, artifact ) );
121
122
 
122
123
  // For better error messages for `type of`s in `returns`, we pass the object as the new main.
123
- resolveTypesForParseCdl(artifact.returns, artifact.returns);
124
+ resolveTypesForParseCdl( artifact.returns, artifact.returns );
124
125
 
125
126
  // Because `args` can be used in different contexts with different semantics,
126
127
  // it needs to be handled specifically.
127
128
  if (artifact.args && typeof artifact.args === 'object') {
128
- if (Array.isArray(artifact.args)) {
129
+ if (Array.isArray( artifact.args )) {
129
130
  // `args` may either be an array (e.g. query 'from' args) ...
130
- artifact.args.forEach((from) => {
131
+ artifact.args.forEach( (from) => {
131
132
  // ... and could be either inside a `from` ...
132
133
  if (from && from.kind === '$tableAlias')
133
- resolveUncheckedPath(from, 'from', from._main);
134
+ resolveUncheckedPath( from, 'from', from._main );
134
135
 
135
136
  // ... or only params ...
136
- resolveTypesForParseCdl(from, main);
137
- });
137
+ resolveTypesForParseCdl( from, main );
138
+ } );
138
139
  }
139
140
  else {
140
141
  // ... or dictionary (e.g. params)
141
- forEachGeneric(artifact, 'args', obj => resolveTypesForParseCdl(obj, obj));
142
+ forEachGeneric( artifact, 'args', obj => resolveTypesForParseCdl( obj, obj ) );
142
143
  }
143
144
  }
144
145
  }