@sap/cds-compiler 5.1.2 → 5.3.0

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 (68) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/bin/cdsc.js +7 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/doc/CHANGELOG_BETA.md +9 -4
  6. package/lib/api/main.js +19 -2
  7. package/lib/api/options.js +4 -1
  8. package/lib/api/validate.js +5 -0
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/message-registry.js +40 -3
  11. package/lib/base/messages.js +1 -1
  12. package/lib/base/model.js +0 -11
  13. package/lib/checks/actionsFunctions.js +0 -12
  14. package/lib/checks/structuredAnnoExpressions.js +10 -14
  15. package/lib/compiler/assert-consistency.js +21 -13
  16. package/lib/compiler/builtins.js +2 -2
  17. package/lib/compiler/checks.js +25 -6
  18. package/lib/compiler/define.js +27 -31
  19. package/lib/compiler/extend.js +16 -18
  20. package/lib/compiler/generate.js +3 -3
  21. package/lib/compiler/populate.js +22 -16
  22. package/lib/compiler/propagator.js +3 -2
  23. package/lib/compiler/resolve.js +87 -94
  24. package/lib/compiler/shared.js +12 -13
  25. package/lib/compiler/tweak-assocs.js +390 -86
  26. package/lib/compiler/utils.js +41 -33
  27. package/lib/compiler/xpr-rewrite.js +45 -58
  28. package/lib/edm/annotations/genericTranslation.js +17 -13
  29. package/lib/edm/csn2edm.js +28 -4
  30. package/lib/edm/edm.js +68 -28
  31. package/lib/edm/edmInboundChecks.js +5 -8
  32. package/lib/edm/edmPreprocessor.js +66 -40
  33. package/lib/edm/edmUtils.js +1 -1
  34. package/lib/gen/BaseParser.js +778 -0
  35. package/lib/gen/CdlParser.js +4477 -0
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageParser.js +4072 -4024
  39. package/lib/inspect/inspectPropagation.js +1 -1
  40. package/lib/json/from-csn.js +5 -3
  41. package/lib/json/to-csn.js +7 -10
  42. package/lib/language/antlrParser.js +96 -0
  43. package/lib/language/errorStrategy.js +1 -1
  44. package/lib/language/genericAntlrParser.js +32 -4
  45. package/lib/language/multiLineStringParser.js +1 -1
  46. package/lib/main.d.ts +23 -0
  47. package/lib/model/cloneCsn.js +22 -13
  48. package/lib/model/csnUtils.js +2 -0
  49. package/lib/model/revealInternalProperties.js +2 -0
  50. package/lib/modelCompare/utils/filter.js +70 -42
  51. package/lib/optionProcessor.js +16 -10
  52. package/lib/parsers/AstBuildingParser.js +1290 -0
  53. package/lib/parsers/CdlGrammar.g4 +2013 -0
  54. package/lib/parsers/Lexer.js +249 -0
  55. package/lib/render/toCdl.js +46 -45
  56. package/lib/render/toSql.js +5 -5
  57. package/lib/transform/addTenantFields.js +4 -4
  58. package/lib/transform/db/applyTransformations.js +54 -16
  59. package/lib/transform/draft/odata.js +10 -11
  60. package/lib/transform/effective/flattening.js +10 -14
  61. package/lib/transform/forRelationalDB.js +7 -6
  62. package/lib/transform/odata/flattening.js +42 -31
  63. package/lib/transform/odata/toFinalBaseType.js +7 -6
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  65. package/lib/utils/moduleResolve.js +1 -1
  66. package/package.json +2 -2
  67. package/share/messages/redirected-to-ambiguous.md +5 -4
  68. package/share/messages/redirected-to-complex.md +6 -3
@@ -122,7 +122,6 @@ function assertConsistency( model, stage ) {
122
122
  'dependencies', // for USING..FROM
123
123
  'kind', // TODO: remove from parser
124
124
  'meta',
125
- '@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version'
126
125
  '$withLocalized',
127
126
  '$sources',
128
127
  'tokenStream',
@@ -289,7 +288,8 @@ function assertConsistency( model, stage ) {
289
288
  '$calcDepElement',
290
289
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '$limit',
291
290
  '_origin', '_block', '$contains',
292
- '_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
291
+ '_projections', '_complexProjections',
292
+ '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
293
293
  '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
294
294
  ],
295
295
  },
@@ -313,7 +313,7 @@ function assertConsistency( model, stage ) {
313
313
  'kind', 'name', '$syntax', '_block', '_parent', '_main',
314
314
  'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
315
315
  '$parens', '_status', // TODO: only in from
316
- 'scope', '_artifact', '$inferred', 'kind',
316
+ 'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
317
317
  '_effectiveType', '$effectiveSeqNo', // TODO:check this
318
318
  '$duplicates', // In JOIN if both sides are the same.
319
319
  ],
@@ -370,7 +370,8 @@ function assertConsistency( model, stage ) {
370
370
  requires: [ 'location' ],
371
371
  optional: [
372
372
  'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
373
- 'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
373
+ 'scope', '_artifact', '$inferred', '$expand', '$inCycle',
374
+ '$tableAliases', '_$next',
374
375
  '_origin', '_effectiveType', '$effectiveSeqNo', '_extensions', '$contains',
375
376
  ],
376
377
  },
@@ -390,7 +391,8 @@ function assertConsistency( model, stage ) {
390
391
  'args', '$syntax',
391
392
  'where', 'groupBy', 'limit', 'orderBy', 'having',
392
393
  'cardinality',
393
- '_artifact', '_navigation', '_user',
394
+ '_artifact', '_originalArtifact',
395
+ '_navigation', '_user',
394
396
  '$inferred',
395
397
  ],
396
398
  },
@@ -443,7 +445,10 @@ function assertConsistency( model, stage ) {
443
445
  test: expression, // properties below are "sub specifications"
444
446
  ref: {
445
447
  requires: [ 'location', 'path' ],
446
- optional: [ 'scope', 'variant', '_artifact', '$inferred', '$parens', 'sort', 'nulls' ],
448
+ optional: [
449
+ 'scope', 'variant', '_artifact', '_originalArtifact',
450
+ '$inferred', '$parens', 'sort', 'nulls',
451
+ ],
447
452
  },
448
453
  none: { optional: () => true }, // parse error
449
454
  // TODO: why optional / enough in name?
@@ -488,7 +493,7 @@ function assertConsistency( model, stage ) {
488
493
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
489
494
  // expressions as annotation values
490
495
  '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
491
- 'scale', 'srid', 'length', 'precision', 'scope',
496
+ 'scale', 'srid', 'length', 'precision', 'scope', '$parens',
492
497
  ],
493
498
  // TODO: restrict path to #simplePath
494
499
  },
@@ -524,7 +529,7 @@ function assertConsistency( model, stage ) {
524
529
  '_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
525
530
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
526
531
  'args', 'op', 'func', 'suffix',
527
- '$invalidPaths',
532
+ '$invalidPaths', '$parens',
528
533
  ],
529
534
  // TODO: name requires if not in parser?
530
535
  },
@@ -538,7 +543,7 @@ function assertConsistency( model, stage ) {
538
543
  schema: {
539
544
  id: { test: isStringOrNumber },
540
545
  select: { test: TODO }, // TODO: remove
541
- }, // TODO: rename query prop in name
546
+ },
542
547
  requires: [ 'location' ],
543
548
  optional: [
544
549
  'path', 'id', '$delimited', 'variant', // TODO: req path, opt id for main, req id for member
@@ -572,6 +577,7 @@ function assertConsistency( model, stage ) {
572
577
  'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
573
578
  '_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
574
579
  '_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
580
+ 'localized', // really? see #13135
575
581
  '$calcDepElement',
576
582
  '$syntax', '_extensions',
577
583
  '_status', '_redirected',
@@ -607,6 +613,7 @@ function assertConsistency( model, stage ) {
607
613
  // - on a path item with a filter condition to the user of the ref (not nested)
608
614
  // - on a JOIN node to the query (TODO: _outer?)
609
615
  _artifact: { test: TODO },
616
+ _originalArtifact: { test: TODO },
610
617
  _navigation: { test: TODO },
611
618
  _effectiveType: { kind: true, test: TODO },
612
619
  $effectiveSeqNo: { kind: true, test: isNumber },
@@ -641,7 +648,7 @@ function assertConsistency( model, stage ) {
641
648
  $replacement: { kind: true, test: TODO }, // for smart * in queries
642
649
  _origin: { kind: true, test: TODO },
643
650
  _calcOrigin: { kind: true, test: TODO },
644
- _pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
651
+ _columnParent: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
645
652
  _from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
646
653
  // array of $tableAlias (or includes) for explicit and implicit redirection:
647
654
  _redirected: { kind: true, test: TODO },
@@ -658,7 +665,8 @@ function assertConsistency( model, stage ) {
658
665
  _scc: { kind: true, test: TODO }, // for cyclic calculation
659
666
  _sccCaller: { kind: true, test: TODO }, // for cyclic calculation
660
667
  _status: { kind: true, test: TODO }, // TODO: $status
661
- _projections: { kind: true, test: TODO }, // for mixin definitions
668
+ _projections: { kind: true, test: TODO },
669
+ _complexProjections: { kind: true, test: TODO }, // for projected paths with filters
662
670
  $entity: { kind: true, test: TODO },
663
671
  _entities: { test: TODO },
664
672
  $compositionTargets: { test: isDictionary( isBoolean ) },
@@ -972,7 +980,7 @@ function assertConsistency( model, stage ) {
972
980
  return function valWithLocation( node, parent, prop, spec, name ) {
973
981
  const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
974
982
  const requires = [ 'val', 'location' ];
975
- const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
983
+ const optional = [ 'literal', '$inferred', '$priority', '_columnParent' ];
976
984
  standard( node, parent, prop, {
977
985
  schema: valSchema, requires, optional, instanceOf: spec.instanceOf,
978
986
  }, name );
@@ -1033,7 +1041,7 @@ function assertConsistency( model, stage ) {
1033
1041
  // TODO
1034
1042
  // else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
1035
1043
  // Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
1036
- // eslint-disable-next-line max-len
1044
+ // eslint-disable-next-line @stylistic/js/max-len
1037
1045
  // throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
1038
1046
  }
1039
1047
 
@@ -77,6 +77,7 @@ typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
77
77
 
78
78
 
79
79
  const specialFunctions = compileFunctions( {
80
+ // TODO: use lower-case
80
81
  '': [ // the default
81
82
  {
82
83
  intro: [ 'ALL', 'DISTINCT' ],
@@ -218,7 +219,7 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
218
219
  // YYYY - MM - dd
219
220
  const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
220
221
  // T HH : mm : ss TZD
221
- // eslint-disable-next-line max-len
222
+ // eslint-disable-next-line @stylistic/js/max-len
222
223
  const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
223
224
  // YYYY - MM - dd T HH : mm : ss . fraction TZD
224
225
  const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
@@ -231,7 +232,6 @@ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
231
232
  * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
232
233
  * - `unexpected_char`: regular expression matching an illegal character in `value`,
233
234
  * the error location is only correct for a literal <prefix>'<value>'
234
- * - `literal`: the value which is used instead of `prefix` in the AST
235
235
  * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
236
236
  * but always allow Feb 29 (no leap year computation)
237
237
  * Notes:
@@ -33,6 +33,8 @@ function check( model ) {
33
33
  error, warning, info, message,
34
34
  } = model.$messageFunctions;
35
35
 
36
+ const { getOrigin } = model.$functions;
37
+
36
38
  checkSapCommonLocale( model );
37
39
  checkSapCommonTextsAspects( model );
38
40
 
@@ -400,13 +402,18 @@ function check( model ) {
400
402
 
401
403
  const expectedType = isNumeric ? 'number' : 'string';
402
404
 
405
+ let art = enumNode;
406
+ while (art?._effectiveType && art.length === undefined)
407
+ art = getOrigin( art );
408
+ const maxLength = art.length?.val ?? model.options.defaultStringLength;
409
+
403
410
  // Do not check elements that don't have a value at all or are
404
411
  // references to other enum elements. There are other checks for that.
405
412
  const hasWrongType = element => element.value &&
406
413
  (element.value.literal !== expectedType) &&
407
414
  (element.value.literal !== 'enum');
408
415
 
409
- for (const key of Object.keys( enumNode.enum )) {
416
+ for (const key in enumNode.enum) {
410
417
  const element = enumNode.enum[key];
411
418
  if (hasWrongType( element )) {
412
419
  const actualType = element.value.literal;
@@ -418,6 +425,18 @@ function check( model ) {
418
425
  string: 'Expected string value for enum element $(NAME) but was $(PROP)',
419
426
  } );
420
427
  }
428
+ else if (isString && maxLength !== undefined) {
429
+ const value = element.value?.val ?? element.name.id;
430
+ if (value.length > maxLength) {
431
+ const loc = element.value?.location ?? element.name.location;
432
+ warning( 'def-invalid-value', [ loc, element ], {
433
+ '#': element.value ? 'std' : 'implicit', name: element.name.id, value: maxLength,
434
+ }, {
435
+ std: 'Enum value $(NAME) exceeds specified length $(VALUE)',
436
+ implicit: 'Implicit enum value $(NAME) exceeds specified length $(VALUE)',
437
+ } );
438
+ }
439
+ }
421
440
  }
422
441
  }
423
442
 
@@ -850,16 +869,16 @@ function check( model ) {
850
869
  // One argument must be "$self" and the other an assoc
851
870
  if (xpr.op.val === '=' && xpr.args.length === 2) {
852
871
  // Tree-ish expression from the compiler (not augmented)
853
- // eslint-disable-next-line max-len
872
+ // eslint-disable-next-line @stylistic/js/max-len
854
873
  return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
855
- // eslint-disable-next-line max-len
874
+ // eslint-disable-next-line @stylistic/js/max-len
856
875
  isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
857
876
  }
858
877
  else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
859
878
  // Tree-ish expression from the compiler (not augmented)
860
- // eslint-disable-next-line max-len
879
+ // eslint-disable-next-line @stylistic/js/max-len
861
880
  return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
862
- // eslint-disable-next-line max-len
881
+ // eslint-disable-next-line @stylistic/js/max-len
863
882
  isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
864
883
  }
865
884
 
@@ -1054,7 +1073,7 @@ function check( model ) {
1054
1073
  value.literal !== 'timestamp' && value.literal !== 'string') {
1055
1074
  // Hm, actually date and time cannot be mixed
1056
1075
  warning( null, loc, { type, anno },
1057
- // eslint-disable-next-line max-len
1076
+ // eslint-disable-next-line @stylistic/js/max-len
1058
1077
  'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
1059
1078
  }
1060
1079
  }
@@ -174,7 +174,6 @@ function define( model ) {
174
174
  shuffleArray,
175
175
  initArtifact,
176
176
  initMembers,
177
- checkDefinitions, // TODO: remove
178
177
  initSelectItems,
179
178
  } );
180
179
 
@@ -188,13 +187,12 @@ function define( model ) {
188
187
  function doDefine() {
189
188
  if (options.deprecated &&
190
189
  messages.every( m => m.messageId !== 'api-deprecated-option' )) {
191
- warning( 'api-deprecated-option', {},
192
- { prop: 'deprecated', '#': (options.beta ? 'beta' : 'std') }, {
193
- // TODO: make the text scarier in future versions
194
- std: 'With option $(PROP), many newer features are disabled',
195
- // eslint-disable-next-line max-len
196
- beta: 'With option $(PROP), beta features and many other newer features are disabled',
197
- } );
190
+ warning( 'api-deprecated-option', {}, {
191
+ prop: 'deprecated', '#': (options.beta ? 'beta' : 'std'),
192
+ }, {
193
+ std: 'With option $(PROP), recent features are disabled',
194
+ beta: 'With option $(PROP), beta features and other recent features are disabled',
195
+ } );
198
196
  }
199
197
  model.definitions = Object.create( null );
200
198
  setLink( model, '_entities', [] ); // for entities with includes
@@ -317,7 +315,7 @@ function define( model ) {
317
315
  if (artifacts[using])
318
316
  continue;
319
317
  // TODO: enable optional locations
320
- const location = a.name.path && a.name.path[0].location || a.location;
318
+ const location = a.name.path?.[0]?.location || a.location;
321
319
  const absolute = prefix + using;
322
320
  artifacts[using] = {
323
321
  kind: 'using', // !, not namespace - we do not know artifact yet
@@ -350,7 +348,7 @@ function define( model ) {
350
348
  return;
351
349
  decl.extern.id = pathName( path );
352
350
  if (!decl.name)
353
- decl.name = { ...path[path.length - 1], $inferred: 'as' };
351
+ decl.name = { ...path.at(-1), $inferred: 'as' };
354
352
  const name = decl.name.id;
355
353
  // TODO: check name: no "."
356
354
  const found = src.artifacts[name];
@@ -365,7 +363,7 @@ function define( model ) {
365
363
  function addNamespace( namespace, src ) {
366
364
  // create using for own namespace:
367
365
  // TODO: should we really do that (in v6)? See also initNamespaceAndUsing().
368
- const last = namespace.path[namespace.path.length - 1];
366
+ const last = namespace.path.at(-1);
369
367
  const { id } = last;
370
368
  if (src.artifacts[id] || last.id.includes( '.' ))
371
369
  // not used as we have a definition/using with that name, or dotted last path id
@@ -437,8 +435,8 @@ function define( model ) {
437
435
  } );
438
436
  if (parent.kind !== 'extend')
439
437
  return;
440
- if (parent.columns) // TODO: sub queries? expand/inline?
441
- parent.columns.forEach( c => setLink( c, '_block', parent._block ) );
438
+ // TODO: sub queries? expand/inline?
439
+ parent.columns?.forEach( c => setLink( c, '_block', parent._block ) );
442
440
  if (parent.scale && !parent.precision) {
443
441
  // TODO: where could we store the location of the name?
444
442
  error( 'syntax-missing-type-property', [ parent.scale.location ],
@@ -794,7 +792,7 @@ function define( model ) {
794
792
  setMemberParent( table, query.name.id, query ); // sets _parent,_main
795
793
  initSubQuery( table ); // init sub queries in ON
796
794
  const aliases = Object.keys( table.$tableAliases || {} );
797
- // Use first tabalias name on the right side of the join to name the
795
+ // Use first table alias name on the right side of the join to name the
798
796
  // (internal) query, should only be relevant for --raw-output, not for
799
797
  // user messages or references - TODO: correct if join on left?
800
798
  table.name.id = aliases[1] || aliases[0] || '<unknown>';
@@ -903,9 +901,9 @@ function define( model ) {
903
901
  hasItems = true;
904
902
  if (!columns) { // expand or inline
905
903
  if (parent.value)
906
- setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline
907
- else if (parent._pathHead)
908
- setLink( col, '_pathHead', parent._pathHead );
904
+ setLink( col, '_columnParent', parent ); // also set for '*' in expand/inline
905
+ else if (parent._columnParent)
906
+ setLink( col, '_columnParent', parent._columnParent );
909
907
  }
910
908
  if (col.val === '*') {
911
909
  if (!wildcard) {
@@ -919,16 +917,15 @@ function define( model ) {
919
917
  else {
920
918
  // a late syntax error (this code also runs with parse-cdl), i.e.
921
919
  // no semantic loc (wouldn't be available for expand/inline anyway)
922
- error( 'syntax-duplicate-wildcard', [ col.location, null ],
923
- {
924
- '#': (wildcard.location.col ? 'col' : 'std'),
925
- prop: '*',
926
- line: wildcard.location.line,
927
- col: wildcard.location.col,
928
- }, {
929
- std: 'You have provided a $(PROP) already in line $(LINE)',
930
- col: 'You have provided a $(PROP) already at line $(LINE), column $(COL)',
931
- } );
920
+ error( 'syntax-duplicate-wildcard', [ col.location, null ], {
921
+ '#': (wildcard.location.col ? 'col' : 'std'),
922
+ prop: '*',
923
+ line: wildcard.location.line,
924
+ col: wildcard.location.col,
925
+ }, {
926
+ std: 'You have provided a $(PROP) already in line $(LINE)',
927
+ col: 'You have provided a $(PROP) already at line $(LINE), column $(COL)',
928
+ } );
932
929
  // TODO: extra text variants for expand/inline? - probably not
933
930
  col.val = null; // do not consider it for expandWildcard()
934
931
  }
@@ -1073,7 +1070,7 @@ function define( model ) {
1073
1070
  // We do not want to complain separately about all element properties:
1074
1071
  error( 'ext-unexpected-element', [ e.location, construct ],
1075
1072
  { name: e.name.id, code: 'extend … with enum' },
1076
- // eslint-disable-next-line max-len
1073
+ // eslint-disable-next-line @stylistic/js/max-len
1077
1074
  'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
1078
1075
  // Don't emit 'ext-expecting-enum' if this error is emitted.
1079
1076
  return;
@@ -1094,7 +1091,7 @@ function define( model ) {
1094
1091
 
1095
1092
  function initAnonymousAspect() {
1096
1093
  // TODO: main?
1097
- const inEntity = parent._main && parent._main.kind === 'entity';
1094
+ const inEntity = parent._main?.kind === 'entity';
1098
1095
  // TODO: also allow indirectly (component in component in entity)?
1099
1096
  setLink( targetAspect, '_outer', obj );
1100
1097
  setLink( targetAspect, '_parent', parent._parent );
@@ -1205,7 +1202,7 @@ function define( model ) {
1205
1202
  if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
1206
1203
  const $self = main.$tableAliases?.$self ||
1207
1204
  main.kind === 'extend' && { name: { id: '$self' } };
1208
- // remark: an extend has no "table alias" `$self` (relevant for parse-cdl)
1205
+ // remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
1209
1206
  setLink( type, '_artifact', $self );
1210
1207
  setLink( path[0], '_artifact', $self );
1211
1208
  }
@@ -1258,7 +1255,6 @@ function define( model ) {
1258
1255
  return false;
1259
1256
  }
1260
1257
  }
1261
- //
1262
1258
  else if (parent.kind === 'action' || parent.kind === 'function') {
1263
1259
  error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
1264
1260
  std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
@@ -441,8 +441,8 @@ function extend( model ) {
441
441
  function applySingleExtension( art, ext, prop ) {
442
442
  if (prop === 'includes') {
443
443
  if (ext.kind === 'extend' && art.$inferred) {
444
- error( 'extend-for-generated', [ ext.name.location, ext ], { art },
445
- 'You can\'t use EXTEND on the generated $(ART)' );
444
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
445
+ 'You can\'t use $(KEYWORD) on the generated $(ART)' );
446
446
  }
447
447
  else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
448
448
  const { id } = art.name;
@@ -543,7 +543,7 @@ function extend( model ) {
543
543
  {
544
544
  std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
545
545
  array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
546
- // eslint-disable-next-line max-len
546
+ // eslint-disable-next-line @stylistic/js/max-len
547
547
  struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
548
548
  boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
549
549
  null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
@@ -640,9 +640,9 @@ function extend( model ) {
640
640
  }
641
641
  else if (extVal < artVal + (scaleDiff || 0)) {
642
642
  const number = artVal + (scaleDiff || 0);
643
- error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
644
- // eslint-disable-next-line object-curly-newline
645
- { '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale' } );
643
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
644
+ '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale',
645
+ } );
646
646
  }
647
647
  else {
648
648
  art[prop] = ext[prop];
@@ -797,7 +797,7 @@ function extend( model ) {
797
797
  const dict = parent[prop];
798
798
  if (!dict) {
799
799
  // TODO: check - for each name? - better locations
800
- const location = ext._parent[prop]?.[$location] || ext.name.location;
800
+ const location = ext._parent?.[prop]?.[$location] || ext.name.location;
801
801
  // Remark: no `elements` dict location with `annotate Main:elem`
802
802
  switch (prop) {
803
803
  // TODO: change texts, somehow similar to checkDefinitions() ?
@@ -1052,8 +1052,8 @@ function extend( model ) {
1052
1052
  if (ext.name._artifact === undefined) { // not already applied
1053
1053
  setArtifactLink( ext.name, art );
1054
1054
  if (noExtend && ext.kind === 'extend') {
1055
- error( 'extend-for-generated', [ ext.name.location, ext ], { art },
1056
- 'You can\'t use EXTEND on the generated $(ART)' );
1055
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
1056
+ 'You can\'t use $(KEYWORD) on the generated $(ART)' );
1057
1057
  continue;
1058
1058
  }
1059
1059
  if (ext.includes) {
@@ -1170,14 +1170,11 @@ function extend( model ) {
1170
1170
  // TODO: use shared functionality with notFound in resolver.js
1171
1171
  const { location } = ext.name;
1172
1172
  extMain.kind = ext.kind;
1173
- const msg
1174
- = error( 'extend-undefined', [ location, artName ],
1175
- { art: artName },
1176
- {
1177
- std: 'Unknown $(ART) - nothing to extend',
1178
- element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
1179
- action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
1180
- } );
1173
+ const msg = error( 'extend-undefined', [ location, artName ], { art: artName }, {
1174
+ std: 'Unknown $(ART) - nothing to extend',
1175
+ element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
1176
+ action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
1177
+ } );
1181
1178
  attachAndEmitValidNames( msg, validDict );
1182
1179
  }
1183
1180
  }
@@ -1192,6 +1189,7 @@ function extend( model ) {
1192
1189
  *
1193
1190
  * @param {XSN.Definition} art
1194
1191
  * @param {XSN.Artifact} target
1192
+ * @param {string[]} [justResolveCyclic]
1195
1193
  * @returns {boolean}
1196
1194
  */
1197
1195
  function canApplyIncludes( art, target, justResolveCyclic ) {
@@ -1272,7 +1270,7 @@ function extend( model ) {
1272
1270
  *
1273
1271
  * @param {XSN.Extension} ext
1274
1272
  * @param {XSN.Artifact} art
1275
- * @param {string} prop: 'elements' or 'actions'
1273
+ * @param {string} prop 'elements' or 'actions'
1276
1274
  */
1277
1275
  function includeMembers( ext, art, prop ) {
1278
1276
  // TODO two kind of messages:
@@ -103,7 +103,7 @@ function generate( model ) {
103
103
  const lang = textsAspect.elements.language;
104
104
  error( 'def-unexpected-element', [ lang.name.location, lang ],
105
105
  { option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
106
- // eslint-disable-next-line max-len
106
+ // eslint-disable-next-line @stylistic/js/max-len
107
107
  '$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
108
108
  hasError = true;
109
109
  }
@@ -244,7 +244,7 @@ function generate( model ) {
244
244
  (fioriEnabled && art.elements.ID_texts)) {
245
245
  // TODO if we have too much time: check all elements of texts entity for safety
246
246
  warning( null, [ art.name.location, art ], { art: textsEntity },
247
- // eslint-disable-next-line max-len
247
+ // eslint-disable-next-line @stylistic/js/max-len
248
248
  'Texts entity $(ART) can\'t be created as there is another definition with that name' );
249
249
  info( null, [ textsEntity.name.location, textsEntity ], { art },
250
250
  'Texts entity for $(ART) can\'t be created with this definition' );
@@ -639,7 +639,7 @@ function generate( model ) {
639
639
  }
640
640
  if (model.definitions[entityName]) {
641
641
  error( null, [ location, elem ], { art: entityName },
642
- // eslint-disable-next-line max-len
642
+ // eslint-disable-next-line @stylistic/js/max-len
643
643
  'Target entity $(ART) can\'t be created as there is another definition with this name' );
644
644
  return false;
645
645
  }
@@ -632,6 +632,12 @@ function populate( model ) {
632
632
  }
633
633
  }
634
634
 
635
+ /**
636
+ * Set type properties of specified elements on the inferred artifact, but only
637
+ * assign them if their values differs from the inferred ones (for better locations).
638
+ *
639
+ * @param {XSN.Artifact} art
640
+ */
635
641
  function setSpecifiedElementTypeProperties( art ) {
636
642
  for (const prop in art.typeProps$) {
637
643
  let o = art;
@@ -789,8 +795,8 @@ function populate( model ) {
789
795
  return col.name.id;
790
796
  }
791
797
  }
792
- else if (col.expand || col.value && (col._pathHead || query._parent.kind !== 'select')) {
793
- // _pathHead => inline/expand; _parent -> only allowed in sub-selects
798
+ else if (col.expand || col.value && (col._columnParent || query._parent.kind !== 'select')) {
799
+ // _columnParent => inline/expand; _parent -> only allowed in sub-selects
794
800
  error( 'query-req-name', [ col.value?.location || col.location, query ], {},
795
801
  'Alias name is required for this select item' );
796
802
  }
@@ -867,7 +873,7 @@ function populate( model ) {
867
873
  const inferred = query._main.$inferred;
868
874
  const excludingDict = (colParent || query).excludingDict || Object.create( null );
869
875
 
870
- const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
876
+ const envParent = wildcard._columnParent;
871
877
  const env = wildcardColumnEnv( wildcard, query );
872
878
  if (!env)
873
879
  return;
@@ -931,7 +937,7 @@ function populate( model ) {
931
937
  // already done in populateQuery (TODO: change that and check whether
932
938
  // `*` is allowed at all in definer)
933
939
  if (!colParent || colParent.value._artifact) {
934
- // avoid "not found" messages if pathHead can't be found
940
+ // avoid "not found" messages if columnParent can't be found
935
941
  const user = colParent || query;
936
942
  for (const name in user.excludingDict)
937
943
  resolveExcluding( name, env, excludingDict, query );
@@ -939,9 +945,9 @@ function populate( model ) {
939
945
  }
940
946
  }
941
947
 
942
- function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._pathHead;
948
+ function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._columnParent;
943
949
  // if (envParent) console.log( 'CE:', envParent._origin, query );
944
- const colParent = wildcard._pathHead;
950
+ const colParent = wildcard._columnParent;
945
951
  if (!colParent)
946
952
  return userQuery( query )._combined; // see combinedSourcesOrParentElements
947
953
 
@@ -972,27 +978,27 @@ function populate( model ) {
972
978
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
973
979
  info( 'wildcard-excluding-many', [ sibling.name.location, query ],
974
980
  { id, keyword: 'excluding' },
975
- // eslint-disable-next-line max-len
981
+ // eslint-disable-next-line @stylistic/js/max-len
976
982
  'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
977
983
  }
978
984
  else {
979
985
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
980
986
  info( 'wildcard-excluding-one', [ sibling.name.location, query ],
981
987
  { id, alias: navElem._parent.name.id, keyword: 'excluding' },
982
- // eslint-disable-next-line max-len
988
+ // eslint-disable-next-line @stylistic/js/max-len
983
989
  'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
984
990
  }
985
991
  }
986
992
  }
987
993
 
988
- function setWildcardExpandInline( queryElem, pathHead, origin, name, location ) {
989
- setLink( queryElem, '_pathHead', pathHead );
994
+ function setWildcardExpandInline( queryElem, columnParent, origin, name, location ) {
995
+ setLink( queryElem, '_columnParent', columnParent );
990
996
  const path = [ { id: name, location } ];
991
997
  queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
992
998
  setArtifactLink( path[0], origin );
993
999
  setLink( queryElem, '_origin', origin );
994
1000
  // set _projections when inline with table alias:
995
- // const alias = pathHead?.value?.path?.[0]?._navigation;
1001
+ // const alias = columnParent?.value?.path?.[0]?._navigation;
996
1002
  // if (alias?.kind === '$tableAlias')
997
1003
  // pushLink( alias.elements[name], '_projections', queryElem );
998
1004
  }
@@ -1106,9 +1112,9 @@ function populate( model ) {
1106
1112
  // art: definitionScope( target ), - TODO extra debug info in message
1107
1113
  sorted_arts: exposed,
1108
1114
  }, {
1109
- // eslint-disable-next-line max-len
1115
+ // eslint-disable-next-line @stylistic/js/max-len
1110
1116
  std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
1111
- // eslint-disable-next-line max-len
1117
+ // eslint-disable-next-line @stylistic/js/max-len
1112
1118
  two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
1113
1119
  } );
1114
1120
  // continuation semantics: no auto-redirection
@@ -1130,11 +1136,11 @@ function populate( model ) {
1130
1136
  anno: 'cds.redirection.target',
1131
1137
  sorted_arts: exposed,
1132
1138
  }, {
1133
- // eslint-disable-next-line max-len
1139
+ // eslint-disable-next-line @stylistic/js/max-len
1134
1140
  std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
1135
- // eslint-disable-next-line max-len
1141
+ // eslint-disable-next-line @stylistic/js/max-len
1136
1142
  two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
1137
- // eslint-disable-next-line max-len
1143
+ // eslint-disable-next-line @stylistic/js/max-len
1138
1144
  justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
1139
1145
  } );
1140
1146
  }
@@ -66,6 +66,7 @@ function propagate( model ) {
66
66
  __proto__: null,
67
67
  never,
68
68
  onlyViaArtifact,
69
+ onlyViaParent,
69
70
  notWithPersistenceTable,
70
71
  };
71
72
  for (const rule in propagationRules)
@@ -73,7 +74,7 @@ function propagate( model ) {
73
74
 
74
75
  const { options } = model;
75
76
  const { rewriteAnnotationsRefs } = xprRewriteFns( model );
76
- // eslint-disable-next-line max-len
77
+ // eslint-disable-next-line @stylistic/js/max-len
77
78
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
78
79
  const { warning, throwWithError } = model.$messageFunctions;
79
80
 
@@ -367,7 +368,7 @@ function propagate( model ) {
367
368
  const art = item && item._artifact;
368
369
  if (art && art.virtual && art.virtual.val) {
369
370
  warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
370
- // eslint-disable-next-line max-len
371
+ // eslint-disable-next-line @stylistic/js/max-len
371
372
  'Prepend $(KEYWORD) to current select item - referred element $(ART) is virtual which is not inherited' );
372
373
  return;
373
374
  }