@sap/cds-compiler 4.1.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 (74) hide show
  1. package/CHANGELOG.md +101 -1
  2. package/bin/cdsc.js +6 -3
  3. package/doc/CHANGELOG_BETA.md +5 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +2 -2
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +24 -24
  8. package/lib/base/message-registry.js +41 -6
  9. package/lib/base/messages.js +7 -0
  10. package/lib/base/model.js +37 -8
  11. package/lib/checks/elements.js +11 -10
  12. package/lib/checks/manyNavigations.js +33 -0
  13. package/lib/checks/onConditions.js +5 -2
  14. package/lib/checks/queryNoDbArtifacts.js +2 -3
  15. package/lib/checks/selectItems.js +4 -55
  16. package/lib/checks/utils.js +3 -2
  17. package/lib/checks/validator.js +3 -1
  18. package/lib/compiler/.eslintrc.json +2 -1
  19. package/lib/compiler/assert-consistency.js +27 -24
  20. package/lib/compiler/base.js +6 -2
  21. package/lib/compiler/builtins.js +34 -34
  22. package/lib/compiler/checks.js +179 -208
  23. package/lib/compiler/classes.js +2 -2
  24. package/lib/compiler/cycle-detector.js +6 -6
  25. package/lib/compiler/define.js +66 -45
  26. package/lib/compiler/extend.js +81 -72
  27. package/lib/compiler/finalize-parse-cdl.js +26 -26
  28. package/lib/compiler/generate.js +61 -45
  29. package/lib/compiler/index.js +47 -49
  30. package/lib/compiler/kick-start.js +8 -7
  31. package/lib/compiler/moduleLayers.js +1 -1
  32. package/lib/compiler/populate.js +42 -35
  33. package/lib/compiler/propagator.js +6 -6
  34. package/lib/compiler/resolve.js +170 -126
  35. package/lib/compiler/shared.js +122 -45
  36. package/lib/compiler/tweak-assocs.js +93 -40
  37. package/lib/compiler/utils.js +15 -12
  38. package/lib/edm/.eslintrc.json +40 -1
  39. package/lib/edm/annotations/genericTranslation.js +721 -707
  40. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  41. package/lib/edm/csn2edm.js +389 -378
  42. package/lib/edm/edm.js +678 -772
  43. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  44. package/lib/edm/edmInboundChecks.js +29 -27
  45. package/lib/edm/edmPreprocessor.js +686 -646
  46. package/lib/edm/edmUtils.js +277 -296
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +1 -1
  49. package/lib/gen/languageParser.js +1253 -1276
  50. package/lib/json/from-csn.js +34 -4
  51. package/lib/json/to-csn.js +4 -4
  52. package/lib/language/language.g4 +2 -5
  53. package/lib/main.d.ts +61 -1
  54. package/lib/model/csnUtils.js +31 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +37 -2
  57. package/lib/modelCompare/utils/filter.js +1 -1
  58. package/lib/optionProcessor.js +15 -3
  59. package/lib/render/toCdl.js +30 -4
  60. package/lib/render/toSql.js +5 -9
  61. package/lib/render/utils/common.js +8 -6
  62. package/lib/transform/db/applyTransformations.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/constraints.js +47 -17
  65. package/lib/transform/db/expansion.js +121 -47
  66. package/lib/transform/db/flattening.js +75 -7
  67. package/lib/transform/forOdata.js +4 -1
  68. package/lib/transform/forRelationalDB.js +80 -62
  69. package/lib/transform/localized.js +91 -54
  70. package/lib/transform/transformUtils.js +9 -10
  71. package/lib/utils/file.js +7 -7
  72. package/lib/utils/moduleResolve.js +210 -121
  73. package/lib/utils/objectUtils.js +1 -1
  74. package/package.json +5 -5
@@ -24,8 +24,8 @@ class XsnSource {
24
24
  location;
25
25
  usings = [];
26
26
  dependencies = [];
27
- artifacts = Object.create(null);
28
- vocabularies = Object.create(null);
27
+ artifacts = Object.create( null );
28
+ vocabularies = Object.create( null );
29
29
  extensions = [];
30
30
  }
31
31
 
@@ -30,15 +30,15 @@ function detectCycles( definitions, reportCycle, cbScc ) {
30
30
 
31
31
  for (const name in definitions) {
32
32
  const a = definitions[name];
33
- if (Array.isArray(a))
33
+ if (Array.isArray( a ))
34
34
  a.forEach( strongConnectRec );
35
35
  else
36
36
  strongConnectRec( a );
37
37
  }
38
38
  // now the cleanup
39
- let nodes = Object.getOwnPropertyNames(definitions).map( n => definitions[n] );
39
+ let nodes = Object.getOwnPropertyNames( definitions ).map( n => definitions[n] );
40
40
  while (nodes.length) { // still nodes to cleaned
41
- nodes = cleanup(nodes);
41
+ nodes = cleanup( nodes );
42
42
  }
43
43
  return;
44
44
 
@@ -46,7 +46,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
46
46
  if (a._scc) // already processed
47
47
  return;
48
48
  while (a)
49
- a = strongConnect(a);
49
+ a = strongConnect( a );
50
50
  }
51
51
 
52
52
  // Try to build a SCC starting from the node `v`.
@@ -60,7 +60,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
60
60
  onStack: true,
61
61
  depIndex: 0,
62
62
  } );
63
- stack.push(v);
63
+ stack.push( v );
64
64
  // console.log('PUSH: ', v.kind,v.name)
65
65
  }
66
66
  if (!v._deps) // builtins, otherwise forgotten (TODO: assert in --test-mode)
@@ -127,7 +127,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
127
127
  delete v._sccCaller;
128
128
  for (const w of v._deps) {
129
129
  if (w.art._scc)
130
- todos.push(w.art);
130
+ todos.push( w.art );
131
131
  }
132
132
  }
133
133
  }
@@ -141,8 +141,8 @@ const {
141
141
  const { compareLayer } = require('./moduleLayers');
142
142
  const { initBuiltins, isInReservedNamespace } = require('./builtins');
143
143
 
144
- const $location = Symbol.for('cds.$location');
145
- const $inferred = Symbol.for('cds.$inferred');
144
+ const $location = Symbol.for( 'cds.$location' );
145
+ const $inferred = Symbol.for( 'cds.$inferred' );
146
146
 
147
147
  /**
148
148
  * Export function of this file. Transform argument `sources` = dictionary of
@@ -194,11 +194,11 @@ function define( model ) {
194
194
  beta: 'With option $(PROP), beta features and many other newer features are disabled',
195
195
  } );
196
196
  }
197
- model.definitions = Object.create(null);
197
+ model.definitions = Object.create( null );
198
198
  setLink( model, '_entities', [] ); // for entities with includes
199
199
  model.$entity = 0;
200
- model.$compositionTargets = Object.create(null);
201
- model.$collectedExtensions = Object.create(null);
200
+ model.$compositionTargets = Object.create( null );
201
+ model.$collectedExtensions = Object.create( null );
202
202
 
203
203
  initBuiltins( model );
204
204
  const sourceNames = shuffleArray( Object.keys( model.sources ) );
@@ -228,7 +228,7 @@ function define( model ) {
228
228
 
229
229
  let namespace = src.namespace && src.namespace.path;
230
230
  let prefix = namespace ? `${ pathName( namespace ) }.` : '';
231
- if (isInReservedNamespace(prefix)) {
231
+ if (isInReservedNamespace( prefix )) {
232
232
  error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], { name: 'cds' },
233
233
  'The namespace $(NAME) is reserved for CDS builtins' );
234
234
  namespace = null;
@@ -241,7 +241,7 @@ function define( model ) {
241
241
  addPathPrefixes( src.artifacts, prefix ); // before addUsing
242
242
  }
243
243
  else if (src.usings || src.namespace) {
244
- src.artifacts = Object.create(null);
244
+ src.artifacts = Object.create( null );
245
245
  }
246
246
  if (src.usings)
247
247
  shuffleArray( src.usings ).forEach( u => addUsing( u, src ) );
@@ -254,11 +254,11 @@ function define( model ) {
254
254
  }
255
255
  else if (src.definitions) { // CSN input
256
256
  prefix = '';
257
- dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ));
257
+ dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ) );
258
258
  }
259
259
  if (src.vocabularies) {
260
260
  if (!model.vocabularies)
261
- model.vocabularies = Object.create(null);
261
+ model.vocabularies = Object.create( null );
262
262
  dictForEach( shuffleDict( src.vocabularies ), v => addVocabulary( v, src, prefix ) );
263
263
  }
264
264
  if (src.extensions) { // requires using to be known!
@@ -269,11 +269,11 @@ function define( model ) {
269
269
  function addDefinition( art, block, prefix ) {
270
270
  if (!art.name.absolute) {
271
271
  // TODO: art.name.absolute = art.name.id || …
272
- art.name.absolute = (!art.name.path) ? art.name.id : prefix + pathName(art.name.path);
272
+ art.name.absolute = (!art.name.path) ? art.name.id : prefix + pathName( art.name.path );
273
273
  }
274
274
  const { absolute } = art.name;
275
275
  // TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
276
- if (absolute === 'cds' || isInReservedNamespace(absolute)) {
276
+ if (absolute === 'cds' || isInReservedNamespace( absolute )) {
277
277
  error( 'reserved-namespace-cds', [ art.name.location, art ], { name: 'cds' },
278
278
  'The namespace $(NAME) is reserved for CDS builtins' );
279
279
  const builtin = model.definitions[absolute];
@@ -298,7 +298,7 @@ function define( model ) {
298
298
  function addPathPrefixes( artifacts, prefix ) {
299
299
  for (const name in artifacts) {
300
300
  const d = artifacts[name];
301
- const a = Array.isArray(d) ? d[0] : d;
301
+ const a = Array.isArray( d ) ? d[0] : d;
302
302
  if (!a.name.absolute)
303
303
  a.name.absolute = prefix + name;
304
304
  const index = name.indexOf( '.' );
@@ -363,7 +363,7 @@ function define( model ) {
363
363
  // create using for own namespace:
364
364
  const last = path[path.length - 1];
365
365
  const { id } = last;
366
- if (src.artifacts[id] || last.id.includes('.'))
366
+ if (src.artifacts[id] || last.id.includes( '.' ))
367
367
  // not used as we have a definition/using with that name, or dotted last path id
368
368
  return;
369
369
  src.artifacts[id] = {
@@ -451,7 +451,9 @@ function define( model ) {
451
451
  const { name } = vocab;
452
452
  if (!name.absolute) {
453
453
  // TODO: art.name.absolute = vocab.name.id || …
454
- vocab.name.absolute = (!vocab.name.path) ? vocab.name.id : prefix + pathName(vocab.name.path);
454
+ vocab.name.absolute = (!vocab.name.path)
455
+ ? vocab.name.id
456
+ : prefix + pathName( vocab.name.path );
455
457
  }
456
458
  dictAdd( model.vocabularies, name.absolute, vocab );
457
459
  }
@@ -461,8 +463,8 @@ function define( model ) {
461
463
  */
462
464
  function addI18nBlocks() {
463
465
  // TODO: the sequence should be in sync with extend / annotate / future $sources
464
- const sortedSources = Object.keys(model.sources)
465
- .filter(name => !!model.sources[name].i18n)
466
+ const sortedSources = Object.keys( model.sources )
467
+ .filter( name => !!model.sources[name].i18n )
466
468
  .sort( (a, b) => compareLayer( model.sources[a], model.sources[b] ) );
467
469
 
468
470
  if (sortedSources.length === 0)
@@ -557,7 +559,7 @@ function define( model ) {
557
559
  return;
558
560
  for (const name in src.artifacts) {
559
561
  const entry = src.artifacts[name];
560
- if (!Array.isArray(entry)) // no local name duplicate
562
+ if (!Array.isArray( entry )) // no local name duplicate
561
563
  continue;
562
564
  for (const decl of entry) {
563
565
  if (!decl.$duplicates) { // do not have two duplicate messages
@@ -603,7 +605,7 @@ function define( model ) {
603
605
  function initArtifactParentLink( art, definitions ) {
604
606
  setLink( art, '_parent', null );
605
607
  const { absolute } = art.name;
606
- const dot = absolute.lastIndexOf('.');
608
+ const dot = absolute.lastIndexOf( '.' );
607
609
  if (dot < 0)
608
610
  return;
609
611
  art.name.id = absolute.substring( dot + 1 ); // XSN TODO: remove name.id for artifacts
@@ -617,7 +619,7 @@ function define( model ) {
617
619
  }
618
620
  setLink( art, '_parent', parent );
619
621
  if (!parent._subArtifacts)
620
- setLink( parent, '_subArtifacts', Object.create(null) );
622
+ setLink( parent, '_subArtifacts', Object.create( null ) );
621
623
  if (art.$duplicates !== true) // no redef or "first def"
622
624
  parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
623
625
  }
@@ -638,7 +640,7 @@ function define( model ) {
638
640
  setLink( self, '_parent', art );
639
641
  setLink( self, '_main', art ); // used on main artifact
640
642
  setLink( self, '_origin', art );
641
- art.$tableAliases = Object.create(null);
643
+ art.$tableAliases = Object.create( null );
642
644
  art.$tableAliases[selfname] = self;
643
645
  }
644
646
 
@@ -702,13 +704,17 @@ function define( model ) {
702
704
  for (const q of query.args.slice(1))
703
705
  initQueryExpression( q, art );
704
706
  setLink( query, '_leadingQuery', leading );
705
- if (leading && query.orderBy) {
706
- if (leading.$orderBy)
707
+ if (leading) {
708
+ if (query.orderBy) {
709
+ leading.$orderBy ??= [ ];
707
710
  leading.$orderBy.push( ...query.orderBy );
708
- else
709
- leading.$orderBy = [ ...query.orderBy ];
711
+ }
712
+ if (query.limit) {
713
+ leading.$limit ??= [ ];
714
+ leading.$limit.push( query.limit );
715
+ }
710
716
  }
711
- // ORDER BY to be evaluated in leading query (LIMIT is literal)
717
+ // ORDER BY and LIMIT to be evaluated in leading query
712
718
  }
713
719
  else { // with parse error (`select from <EOF>`, `select from E { *, ( select }`)
714
720
  return undefined;
@@ -718,7 +724,7 @@ function define( model ) {
718
724
  function initQuery() {
719
725
  const main = art._main || art;
720
726
  setLink( query, '_$next',
721
- (art.kind === '$tableAlias' ? art._parent._$next : art) );
727
+ (art.kind === '$tableAlias' ? art._parent._$next : art ) );
722
728
  setLink( query, '_block', art._block );
723
729
  query.kind = 'select';
724
730
  query.name = { location: query.location };
@@ -742,7 +748,7 @@ function define( model ) {
742
748
  return;
743
749
  if (!table.name) {
744
750
  const last = table.path[table.path.length - 1];
745
- const dot = last?.id?.lastIndexOf('.');
751
+ const dot = last?.id?.lastIndexOf( '.' );
746
752
  const id = (dot >= 0) ? last.id.substring( dot + 1 ) : last.id || '';
747
753
  // TODO: if we have too much time, we can calculate the real location with '.'
748
754
  table.name = { $inferred: 'as', id, location: last.location };
@@ -795,6 +801,7 @@ function define( model ) {
795
801
  // (internal) query, should only be relevant for --raw-output, not for
796
802
  // user messages or references - TODO: correct if join on left?
797
803
  table.name.param = aliases[1] || aliases[0] || '<unknown>';
804
+ setLink( table, '_user', query ); // TODO: do not set kind/name
798
805
  setLink( table, '_$next', query._$next );
799
806
  // TODO: probably set this to query if we switch to name restriction in JOIN
800
807
  }
@@ -809,7 +816,7 @@ function define( model ) {
809
816
  if (tableAlias.$inferred === '$internal') {
810
817
  const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
811
818
  error( 'name-missing-alias', [ tableAlias.location, semanticLoc ],
812
- { '#': 'duplicate', code: 'as ‹alias›' });
819
+ { '#': 'duplicate', code: 'as ‹alias›' } );
813
820
  }
814
821
  else {
815
822
  error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
@@ -845,7 +852,7 @@ function define( model ) {
845
852
 
846
853
  function initExprForQuery( expr, query ) {
847
854
  // TODO: use traverseExpr()
848
- if (Array.isArray(expr)) { // TODO: old-style $parens ?
855
+ if (Array.isArray( expr )) { // TODO: old-style $parens ?
849
856
  expr.forEach( e => initExprForQuery( e, query ) );
850
857
  }
851
858
  else if (!expr) {
@@ -855,13 +862,13 @@ function define( model ) {
855
862
  initQueryExpression( expr.query, query );
856
863
  }
857
864
  else if (expr.args) {
858
- const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
865
+ const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
859
866
  args.forEach( e => initExprForQuery( e, query ) );
860
867
  }
861
868
  else if (expr.path && expr.$expected === 'exists') {
862
869
  // TODO: does really the parser has to set $expected?
863
870
  expr.$expected = 'approved-exists';
864
- approveExistsInChildren(expr);
871
+ approveExistsInChildren( expr );
865
872
  }
866
873
  }
867
874
 
@@ -938,7 +945,7 @@ function define( model ) {
938
945
  }
939
946
  // col.$annotations no available for CSN input, have to search.
940
947
  // Warning about first annotation should be enough to avoid spam.
941
- const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
948
+ const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
942
949
  if (firstAnno) {
943
950
  warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
944
951
  { code: '.{ ‹inline› }' } );
@@ -956,7 +963,7 @@ function define( model ) {
956
963
  // TODO: use `parent` for semantic location, use `-excluding`
957
964
  warning( 'query-ignoring-exclude', [ parent.excludingDict[$location], user ],
958
965
  { prop: '*' },
959
- 'Excluding elements without wildcard $(PROP) has no effect');
966
+ 'Excluding elements without wildcard $(PROP) has no effect' );
960
967
  }
961
968
  }
962
969
 
@@ -983,11 +990,11 @@ function define( model ) {
983
990
  exprOrPathElement.$expected = 'approved-exists';
984
991
  // Drill down
985
992
  if (exprOrPathElement.args)
986
- exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
993
+ exprOrPathElement.args.forEach( elem => approveExistsInChildren( elem ) );
987
994
  else if (exprOrPathElement.where && exprOrPathElement.where.args)
988
- exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
995
+ exprOrPathElement.where.args.forEach( elem => approveExistsInChildren( elem ) );
989
996
  else if (exprOrPathElement.path)
990
- exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
997
+ exprOrPathElement.path.forEach( elem => approveExistsInChildren( elem ) );
991
998
  }
992
999
  // TODO: we might issue 'expr-unexpected-exists' and 'expr-no-subquery' already in
993
1000
  // define.js (using a to-be-written expression traversal function in utils.js)
@@ -1219,18 +1226,25 @@ function define( model ) {
1219
1226
  // - artifacts (CDL-only anyway) only inside [extend] context|service
1220
1227
  if (!dict)
1221
1228
  return false;
1222
- const names = Object.keys( dict );
1223
- if (!names.length) // TODO: re-check - really allow empty dict if no other?
1224
- return false;
1225
1229
  const feature = kindProperties[parent.kind][prop];
1226
1230
  if (feature &&
1227
1231
  (feature === true || construct.kind !== 'extend' || feature( prop, parent )))
1228
1232
  return true;
1229
1233
  const location = dict[$location];
1234
+
1235
+ // TODO: a bit inconsistent = not a simple switch on `prop`…
1230
1236
  if (prop === 'actions') {
1231
- error( 'def-unexpected-actions', [ location, construct ], {},
1232
- 'Actions and functions only exist top-level and for entities' );
1237
+ if (Object.keys( dict ).length) {
1238
+ error( 'def-unexpected-actions', [ location, construct ], {}, // TODO: ext-
1239
+ 'Actions and functions only exist top-level and for entities' ); // or aspects
1240
+ }
1241
+ else {
1242
+ warning( 'ext-ignoring-actions', [ location, construct ], {},
1243
+ 'Actions and functions only exist top-level and for entities' );
1244
+ return false;
1245
+ }
1233
1246
  }
1247
+ //
1234
1248
  else if (parent.kind === 'action' || parent.kind === 'function') {
1235
1249
  error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
1236
1250
  std: 'Actions and functions can\'t be extended, only annotated',
@@ -1252,10 +1266,15 @@ function define( model ) {
1252
1266
  }
1253
1267
  }
1254
1268
  else if (feature) { // allowed in principle, but not with extend
1255
- if (parent.$inferred === 'include') { // special case for better error message
1269
+ if (!Object.keys( dict ).length) {
1270
+ warning( 'ext-ignoring-elements', [ location, construct ], {},
1271
+ 'Only structures with directly specified elements can be extended by elements' );
1272
+ return false;
1273
+ }
1274
+ else if (parent.$inferred === 'include') { // special case for better error message
1256
1275
  const variant = (construct.enum || construct.elements) ? 'elements' : 'std';
1257
1276
  error( 'ref-expected-direct-structure', [ location, construct ],
1258
- { '#': variant, art: parent });
1277
+ { '#': variant, art: parent } );
1259
1278
  }
1260
1279
  else {
1261
1280
  error( 'extend-type', [ location, construct ], {},
@@ -1276,8 +1295,10 @@ function define( model ) {
1276
1295
  return construct === parent;
1277
1296
  }
1278
1297
 
1279
- // Return whether the `target` is actually a `targetAspect`
1280
- // TODO: really do that here and not in kick-start.js?
1298
+ /**
1299
+ * Return whether the `target` is actually a `targetAspect`
1300
+ * TODO: really do that here and not in kick-start.js?
1301
+ */
1281
1302
  function targetIsTargetAspect( elem ) {
1282
1303
  const { target } = elem;
1283
1304
  if (target.elements) {