@sap/cds-compiler 6.6.0 → 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 (45) hide show
  1. package/CHANGELOG.md +34 -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/effective/associations.js +1 -1
  38. package/lib/transform/forOdata.js +7 -124
  39. package/lib/transform/odata/fioriTreeViews.js +173 -0
  40. package/lib/transform/odata/flattening.js +2 -2
  41. package/lib/transform/translateAssocsToJoins.js +7 -4
  42. package/package.json +1 -1
  43. package/share/messages/message-explanations.json +0 -2
  44. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  45. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -44,7 +44,7 @@ function fns( model ) {
44
44
  const referenceSemantics = {
45
45
  // global: ------------------------------------------------------------------
46
46
  using: { // only used to produce error message
47
- isMainRef: 'all',
47
+ isMainRef: 'no-leaf-gap',
48
48
  lexical: null,
49
49
  dynamic: modelDefinitions,
50
50
  notFound: undefinedDefinition,
@@ -58,14 +58,14 @@ function fns( model ) {
58
58
  },
59
59
  // only used for the main annotate/extend statements, not inner ones:
60
60
  annotate: {
61
- isMainRef: 'all',
61
+ isMainRef: 'no-leaf-gap',
62
62
  lexical: userBlock,
63
63
  dynamic: modelDefinitions,
64
64
  notFound: undefinedForAnnotate,
65
65
  accept: extendableArtifact,
66
66
  },
67
67
  'annotate-sec': {
68
- isMainRef: 'all',
68
+ isMainRef: 'no-leaf-gap',
69
69
  lexical: userBlock,
70
70
  dynamic: modelDefinitions,
71
71
  notFound: undefinedDefinition,
@@ -660,7 +660,6 @@ function fns( model ) {
660
660
  if (!root)
661
661
  return setArtifactLink( ref, root );
662
662
 
663
- // how many path items are for artifacts (rest: elements)
664
663
  let art = getPathItem( ref, semantics, user );
665
664
  if (!art)
666
665
  return setArtifactLink( ref, art );
@@ -838,16 +837,19 @@ function fns( model ) {
838
837
  const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
839
838
  if (!dynamicDict) // avoid consequential errors
840
839
  return setArtifactLink( head, null );
840
+
841
841
  const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
842
842
  const dict = (isVar) ? model.$magicVariables.elements : dynamicDict;
843
- const r = dict[head.id];
843
+ const r = dict[head.id] || // or a ref like "MyEntity.texts" in CSN
844
+ isMainRef && Functions.generateOnDemand?.( head.id );
844
845
  if (r)
845
846
  return setArtifactLink( head, r );
846
847
 
847
848
  if (!semantics.dollar) {
848
849
  valid.push( dynamicDict );
850
+ const noGen = !definedViaCdl( ruser );
849
851
  if (isMainRef) // eslint-disable-next-line no-return-assign
850
- valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d )) );
852
+ valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d, noGen )) );
851
853
  }
852
854
  else {
853
855
  const filterFn = semantics.variableFilter || removeRestrictedVariables;
@@ -871,6 +873,7 @@ function fns( model ) {
871
873
  function getPathItem( ref, semantics, user ) {
872
874
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
873
875
  const { path } = ref;
876
+ // how many path items are for artifacts (the rest are for elements):
874
877
  let artItemsCount = 0;
875
878
  const { isMainRef } = semantics;
876
879
  if (isMainRef) {
@@ -894,7 +897,10 @@ function fns( model ) {
894
897
  const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
895
898
  // TOOD: call envFn with location of last item (for dependency error)
896
899
  const env = envFn( art, path[index - 1].location, user );
897
- const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
900
+ let found = env && env[item.id]; // not env?.[] - keep a `0`
901
+ if (!found && found !== 0 && artItemsCount >= 0)
902
+ found = Functions.generateOnDemand?.( `${ prev.name.id }.${ item.id }` );
903
+
898
904
  // Reject `$self.$_column_1`: TODO: necessary to do here again?
899
905
  art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
900
906
 
@@ -904,24 +910,33 @@ function fns( model ) {
904
910
  const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
905
911
  // TODO: streamline function arguments (probably: user, path, semantics, prev )
906
912
  // false returned by semantics.navigation: no further error:
907
- if (env !== false)
908
- notFound( user, item, [ env ], null, prev, path, semantics );
913
+ if (env !== false) {
914
+ const exp = (artItemsCount >= 0)
915
+ ? removeGapArtifact( env, !definedViaCdl( user ) )
916
+ : env;
917
+ notFound( user, item, [ exp ], null, prev, path, semantics );
918
+ }
909
919
  return null;
910
920
  }
911
921
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
912
922
  // TODO: but Service.AutoExposed.NotAuto should be fine
913
923
  if (isMainRef && isMainRef !== 'all' && artItemsCount === 0) {
914
924
  if (art.kind === 'namespace') {
925
+ if (Functions.generateOnDemand?.( art ) ||
926
+ isMainRef === 'no-leaf-gap' && art._subArtifacts)
927
+ continue; // art is generated entity assigned to gap object
915
928
  if (env !== false) {
916
- semantics.notFound( user, item, [ removeGapArtifact( env ) ],
929
+ semantics.notFound( user, item, [ removeGapArtifact( env, !definedViaCdl( user ) ) ],
917
930
  null, prev, path, semantics );
918
931
  }
919
932
  return null;
920
933
  }
921
- else if (art.$inferred === 'autoexposed' && !user.$inferred) {
934
+ else if (art.$inferred === 'autoexposed' && !user.$inferred &&
935
+ isMainRef !== 'no-leaf-gap') {
922
936
  // Depending on the processing sequence, the following could be a
923
937
  // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
924
938
  // could "change" to this message at the end of compile():
939
+ // HINT: this does not appear anymore
925
940
  error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
926
941
  'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
927
942
  return null; // continuation semantics: like “not found”
@@ -983,9 +998,8 @@ function fns( model ) {
983
998
 
984
999
  switch (art.kind) {
985
1000
  case 'using': {
986
- const def = model.definitions[art.extern.id];
987
- if (!def)
988
- return def;
1001
+ const def = model.definitions[art.extern.id] ||
1002
+ Functions.createGapArtifact( art.extern.id, art.extern.location );
989
1003
  if (def.$duplicates)
990
1004
  return false;
991
1005
  art = setArtifactLink( head, def ); // we do not want to see the using
@@ -994,15 +1008,21 @@ function fns( model ) {
994
1008
  }
995
1009
  /* FALLTHROUGH */
996
1010
  case 'namespace': {
997
- if (semantics.isMainRef === 'all' || path.length !== 1 && ref.scope !== 1)
1011
+ if (semantics.isMainRef === 'all' ||
1012
+ semantics.isMainRef === 'no-leaf-gap' && art._subArtifacts ||
1013
+ path.length !== 1 && ref.scope !== 1 ||
1014
+ // "MyEntity.texts" in CSN or via `using` which is (at the moment)
1015
+ // just a gap artifact
1016
+ Functions.generateOnDemand?.( art ))
998
1017
  return art;
1018
+
999
1019
  const valid = [];
1000
1020
  const lexical = userBlock( user );
1001
1021
  if (lexical) {
1002
1022
  for (let env = lexical; env; env = env._block)
1003
1023
  valid.push( removeGapArtifact( env.artifacts || Object.create( null ) ) );
1004
1024
  }
1005
- valid.push( removeGapArtifact( model.definitions ) );
1025
+ valid.push( removeGapArtifact( model.definitions, !definedViaCdl( user ) ) );
1006
1026
  semantics.notFound?.( user._user || user, head, valid, model.definitions,
1007
1027
  null, path, semantics );
1008
1028
 
@@ -1488,7 +1508,7 @@ function fns( model ) {
1488
1508
  }
1489
1509
  else {
1490
1510
  const target = art._effectiveType?.target;
1491
- if (target?._artifact) {
1511
+ if (resolvePath( target, 'target', art )) {
1492
1512
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1493
1513
  { '#': 'target', art: target, id: item.id }, semantics );
1494
1514
  }
@@ -1622,6 +1642,9 @@ function fns( model ) {
1622
1642
  }
1623
1643
 
1624
1644
  function acceptStructOrBare( art, user, ref ) { // for includes[]
1645
+ // eslint-disable-next-line @stylistic/object-curly-newline
1646
+ if (user.kind in { context: 1, service: 1, action: 1, function: 1 })
1647
+ return false;
1625
1648
  // It had been checked before that `includes` is already forbidden for
1626
1649
  // non-entity/aspect/type/event.
1627
1650
  //
@@ -1650,6 +1673,9 @@ function fns( model ) {
1650
1673
  // We might allow includes with elements in the future, they'd probably
1651
1674
  // count as specified elements with lower priority, i.e. annos, types, key
1652
1675
  // etc on columns beat those inherited from the include.
1676
+ // TODO v7 forbid `extend Proj with ZeroElemAspect` even if ZeroElemAspect has
1677
+ // 0 elements. Making the element-length check work requires that effectiveType()
1678
+ // has been called on the include already before.
1653
1679
  if (art.kind === 'aspect' &&
1654
1680
  (!art.elements || base.query && !Object.keys( art.elements ).length))
1655
1681
  return art;
@@ -2181,12 +2207,14 @@ function removeDollarNames( dict ) {
2181
2207
  return r;
2182
2208
  }
2183
2209
 
2184
- function removeGapArtifact( dict ) {
2210
+ function removeGapArtifact( dict, alsoRemoveInferred ) {
2185
2211
  const r = Object.create( null );
2186
2212
  for (const name in dict) {
2187
2213
  const art = dict[name];
2188
- // TODO: for gaps with sub artifacts, we use use `${name}.` as name
2214
+ // TODO: for gaps with sub artifacts, use `${name}.` as name
2189
2215
  // TODO: clarify with LSP
2216
+ if (alsoRemoveInferred && art.$inferred)
2217
+ continue;
2190
2218
  if (art.kind !== 'namespace' || art._subArtifacts)
2191
2219
  r[name] = dict[name];
2192
2220
  }
@@ -2,10 +2,6 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const {
6
- forEachGeneric,
7
- forEachInOrder,
8
- } = require('../base/model');
9
5
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
10
6
 
11
7
  const {
@@ -19,6 +15,8 @@ const {
19
15
  traverseQueryExtra,
20
16
  setExpandStatus,
21
17
  getUnderlyingBuiltinType,
18
+ forEachGeneric,
19
+ forEachInOrder,
22
20
  } = require('./utils');
23
21
  const { Location } = require('../base/location');
24
22
  const { CompilerAssertion } = require('../base/error');
@@ -10,7 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
13
+ const { dictAdd, dictFirst } = require('../base/dictionaries');
14
14
  const { Location, weakLocation } = require('../base/location');
15
15
  const { XsnName, XsnArtifact } = require('./xsn-model');
16
16
 
@@ -145,10 +145,11 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
145
145
  * @return {XSN.Artifact}
146
146
  */
147
147
  function initItemsLinks( obj, block ) {
148
+ const parent = obj._parent;
148
149
  let { items } = obj;
149
150
  while (items) {
150
151
  setLink( items, '_outer', obj );
151
- setLink( items, '_parent', obj._parent );
152
+ setLink( items, '_parent', parent );
152
153
  setLink( items, '_block', block );
153
154
  obj = items;
154
155
  items = obj.items;
@@ -162,7 +163,7 @@ function initItemsLinks( obj, block ) {
162
163
  * is most often the property `elem.name.id`.
163
164
  *
164
165
  * If argument `prop` is provided, add `elem` to the dictionary of that name,
165
- * e.g. `elements`.
166
+ * e.g. `elements`. TODO: remove `prop`.
166
167
  */
167
168
  function setMemberParent( elem, name, parent, prop ) {
168
169
  if (prop) { // extension or structure include
@@ -286,21 +287,6 @@ function dependsOnSilent( user, art ) {
286
287
  user._deps.push( { art } );
287
288
  }
288
289
 
289
- function storeExtension( elem, name, prop, parent ) {
290
- if (prop === 'enum')
291
- prop = 'elements';
292
- const kind = `_${ elem.kind }`; // _extend or _annotate
293
- if (!parent[kind])
294
- setLink( parent, kind, {} );
295
- // if (name === '' && prop === 'params') {
296
- // pushToDict( parent[kind], 'returns', elem ); // not really a dict
297
- // return;
298
- // }
299
- if (!parent[kind][prop])
300
- parent[kind][prop] = Object.create( null );
301
- pushToDict( parent[kind][prop], name, elem );
302
- }
303
-
304
290
  /** @type {(a: any, b: any) => boolean} */
305
291
  const testFunctionPlaceholder = () => true;
306
292
 
@@ -348,11 +334,11 @@ function augmentPath( location, ...args ) {
348
334
  return { path: args.map( id => ({ id, location }) ), location };
349
335
  }
350
336
 
351
- function copyExpr( expr, location ) {
337
+ function copyExpr( expr, location, noLinks = false ) {
352
338
  if (!expr || typeof expr !== 'object')
353
339
  return expr;
354
340
  else if (Array.isArray( expr ))
355
- return expr.map( e => copyExpr( e, location ) );
341
+ return expr.map( e => copyExpr( e, location, noLinks ) );
356
342
 
357
343
  const proto = Object.getPrototypeOf( expr );
358
344
  if (proto && proto !== Object.prototype && proto !== XsnName.prototype &&
@@ -363,16 +349,21 @@ function copyExpr( expr, location ) {
363
349
  for (const prop of Object.getOwnPropertyNames( expr )) {
364
350
  const pd = Object.getOwnPropertyDescriptor( expr, prop );
365
351
  if (!proto)
366
- r[prop] = copyExpr( pd.value, location );
367
-
368
- else if (!pd.enumerable || prop.charAt(0) === '$')
369
- Object.defineProperty( r, prop, pd );
352
+ r[prop] = copyExpr( pd.value, location, noLinks );
370
353
 
371
354
  else if (prop === 'location')
372
355
  r[prop] = location || pd.value;
373
356
 
374
- else
375
- r[prop] = copyExpr( pd.value, location );
357
+ else if (prop.charAt(0) === '$')
358
+ Object.defineProperty( r, prop, pd );
359
+
360
+ else if (pd.enumerable)
361
+ r[prop] = copyExpr( pd.value, location, noLinks );
362
+
363
+ else if (!noLinks)
364
+ Object.defineProperty( r, prop, pd );
365
+ // TODO: probably consider noLinks just for element references (_artifact of
366
+ // first path item has _main) - test calc element with `cast`
376
367
  }
377
368
  return r;
378
369
  }
@@ -660,22 +651,37 @@ function isDirectComposition( art ) {
660
651
  return path?.length === 1 && path[0].id === 'cds.Composition';
661
652
  }
662
653
 
663
- function targetCantBeAspect( elem, calledForTargetAspectProp ) {
654
+ function targetCantBeAspect( elem, calledForTargetAspectProp, definitions ) { // see #11658
664
655
  // Remark: we do not check `on` and `keys` here - the error should complain
665
656
  // at the `on`/`keys`, not the aspect
666
657
  if (!isDirectComposition( elem ) || elem.targetAspect && !calledForTargetAspectProp)
667
658
  return (elem.type && !elem.type.$inferred) ? 'std' : 'redirected';
659
+ // TODO: better msg if due to calledForTargetAspectProp
668
660
  if (!elem._main)
669
661
  return elem.kind; // type or annotation
670
662
  // TODO: extra for "in many"?
671
663
  let art = elem;
672
664
  while (art.kind === 'element')
673
665
  art = art._parent;
666
+ // Remark: if we include entity/aspect, we might still have a targetAspects in a
667
+ // type/event - see Error[type-managed-composition]
668
+ // With a future feature “managed compositions in sub elements“, we might allow
669
+ // moving an aspect to targetAspect independently from the main artifact
670
+ if (definitions && art.kind === 'extend')
671
+ art = definitions[art.name.id];
674
672
  if (![ 'entity', 'aspect', 'event' ].includes( art.kind ))
675
673
  return (art.kind !== 'mixin') ? art.kind : 'select';
676
- return ((art.query || art.kind === 'event') && !(calledForTargetAspectProp && elem.target))
677
- ? art.kind
678
- : elem._parent.kind === 'element' && 'sub';
674
+ if ((art.query || art.kind === 'event') && !(calledForTargetAspectProp && elem.target))
675
+ return art.kind;
676
+ if (elem._parent.kind === 'element')
677
+ return 'sub';
678
+ if (!calledForTargetAspectProp) {
679
+ if (elem.on)
680
+ return 'on';
681
+ if (elem.foreignKeys)
682
+ return 'keys';
683
+ }
684
+ return null;
679
685
  }
680
686
 
681
687
  function userQuery( user ) {
@@ -781,6 +787,50 @@ function compositionTextVariant( art, composition, association = 'std' ) {
781
787
  : association;
782
788
  }
783
789
 
790
+ // Looping: ---------------------------------------------------------------------
791
+
792
+ /**
793
+ * Apply function `callback` to all artifacts in dictionary
794
+ * `model.definitions`. See function `forEachGeneric` for details.
795
+ * TODO: should we skip "namespaces" already here?
796
+ */
797
+ function forEachDefinition( model, callback ) {
798
+ forEachGeneric( model, 'definitions', callback );
799
+ }
800
+
801
+ /**
802
+ * Apply function `callback` to all members of object `construct` (main artifact
803
+ * or parent member). Members are considered those in dictionaries `elements`,
804
+ * `enum`, `actions` and `params` of `obj`.
805
+ *
806
+ * @param {XSN.Artifact} construct
807
+ * @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
808
+ * @param {XSN.Artifact} [target]
809
+ */
810
+ function forEachMember( construct, callback ) {
811
+ forEachGeneric( construct, 'elements', callback );
812
+ forEachGeneric( construct, 'enum', callback );
813
+ forEachGeneric( construct, 'foreignKeys', callback );
814
+ forEachGeneric( construct, 'actions', callback );
815
+ forEachGeneric( construct, 'params', callback );
816
+ if (construct.returns)
817
+ callback( construct.returns, '', 'params' );
818
+ }
819
+
820
+ // Apply function `callback` to all objects in dictionary `parent[prop]`,
821
+ // including all duplicates (found under the same name). Function `callback` is
822
+ // called with the following arguments: the object, the name and `prop`.
823
+ function forEachGeneric( parent, prop, callback ) {
824
+ const dict = parent[prop];
825
+ for (const name in dict) {
826
+ const obj = dict[name];
827
+ const { $duplicates } = obj;
828
+ callback( obj, name, prop );
829
+ if (Array.isArray( $duplicates )) // redefinitions
830
+ $duplicates.forEach( o => callback( o, name, prop ) );
831
+ }
832
+ }
833
+
784
834
  module.exports = {
785
835
  pushLink,
786
836
  annotationVal,
@@ -801,7 +851,6 @@ module.exports = {
801
851
  initDollarSelf,
802
852
  initDollarParameters,
803
853
  initBoundSelfParam,
804
- storeExtension,
805
854
  withAssociation,
806
855
  pathName,
807
856
  augmentPath,
@@ -827,4 +876,8 @@ module.exports = {
827
876
  definedViaCdl,
828
877
  artifactRefLocation,
829
878
  compositionTextVariant,
879
+ forEachDefinition,
880
+ forEachMember,
881
+ forEachGeneric,
882
+ forEachInOrder: forEachGeneric,
830
883
  };