@sap/cds-compiler 4.9.6 → 5.1.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -216,7 +216,7 @@ function define( model ) {
216
216
 
217
217
  const { $self } = model.definitions;
218
218
  if ($self) {
219
- message( 'name-deprecated-$self', [ $self.location, $self ], { name: '$self' },
219
+ message( 'name-deprecated-$self', [ $self.name.location, $self ], { name: '$self' },
220
220
  'Do not use $(NAME) as name for an artifact definition' );
221
221
  }
222
222
  }
@@ -364,7 +364,7 @@ function define( model ) {
364
364
  // must be called after addUsing().
365
365
  function addNamespace( namespace, src ) {
366
366
  // create using for own namespace:
367
- // TODO: should we really do that in v5? See also initNamespaceAndUsing().
367
+ // TODO: should we really do that (in v6)? See also initNamespaceAndUsing().
368
368
  const last = namespace.path[namespace.path.length - 1];
369
369
  const { id } = last;
370
370
  if (src.artifacts[id] || last.id.includes( '.' ))
@@ -507,9 +507,10 @@ function define( model ) {
507
507
  // TODO: message ids
508
508
  function checkRedefinition( art ) {
509
509
  if (!art.$duplicates || !art.name.id ||
510
- art.$errorReported === 'syntax-duplicate-extend' ||
511
- art.$errorReported === 'syntax-duplicate-annotate')
510
+ art.$errorReported === 'syntax-duplicate-extend')
512
511
  return;
512
+ if (art.kind === 'annotate' || art.kind === 'extend')
513
+ return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
513
514
  if (art._main) {
514
515
  error( 'duplicate-definition', [ art.name.location, art ], {
515
516
  name: art.name.id,
@@ -637,7 +638,6 @@ function define( model ) {
637
638
  }
638
639
 
639
640
  function initDollarParameters( art ) {
640
- // TODO: remove $parameters in v5?
641
641
  // TODO: use setMemberParent() ?
642
642
  const parameters = {
643
643
  name: { id: '$parameters' },
@@ -939,14 +939,14 @@ function define( model ) {
939
939
  setLink( col, '_block', parent._block );
940
940
  if (col.inline) { // `@anno elem.{ * }` does not work
941
941
  if (col.doc) {
942
- message( 'syntax-ignoring-anno', [ col.doc.location, col ],
942
+ message( 'syntax-unexpected-anno', [ col.doc.location, col ],
943
943
  { '#': 'doc', code: '.{ ‹inline› }' } );
944
944
  }
945
945
  // col.$annotations no available for CSN input, have to search.
946
946
  // Message about first annotation should be enough to avoid spam.
947
947
  const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
948
948
  if (firstAnno) {
949
- message( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
949
+ message( 'syntax-unexpected-anno', [ col[firstAnno].name.location, col ],
950
950
  { code: '.{ ‹inline› }' } );
951
951
  }
952
952
  }
@@ -1083,7 +1083,7 @@ function define( model ) {
1083
1083
  hasElement = true; // warning with CDL input or `name: {}` in CSN input
1084
1084
  }
1085
1085
  if (hasElement) {
1086
- // This message is similar to the one above. In v5/6, we could probably
1086
+ // This message is similar to the one above. In v6, we could probably
1087
1087
  // turn this warning into an error, remove `$syntax: 'element' (also in
1088
1088
  // language.g4), and use the above `ext-unexpected-element` only for CSN input.
1089
1089
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
@@ -8,8 +8,8 @@ const {
8
8
  forEachInOrder,
9
9
  forEachDefinition,
10
10
  forEachMember,
11
+ forEachGeneric,
11
12
  isDeprecatedEnabled,
12
- isBetaEnabled,
13
13
  } = require('../base/model');
14
14
  const { dictAdd, pushToDict } = require('../base/dictionaries');
15
15
  const { kindProperties, dictKinds } = require('./base');
@@ -27,10 +27,11 @@ const {
27
27
  const layers = require('./moduleLayers');
28
28
  const { CompilerAssertion } = require('../base/error');
29
29
  const { Location } = require('../base/location');
30
+ const { typeParameters } = require('./builtins');
30
31
 
31
32
  const $location = Symbol.for( 'cds.$location' );
32
33
 
33
- // attach stupid location - TODO: remove in v5
34
+ // attach stupid location - TODO: remove in v6
34
35
  const genLocation = new Location( '' );
35
36
 
36
37
  const draftElements = [
@@ -59,6 +60,7 @@ function extend( model ) {
59
60
  resolvePath,
60
61
  resolveUncheckedPath,
61
62
  resolveTypeArgumentsUnchecked,
63
+ resolveDefinitionName,
62
64
  attachAndEmitValidNames,
63
65
  initMembers,
64
66
  initSelectItems,
@@ -68,11 +70,11 @@ function extend( model ) {
68
70
  createRemainingAnnotateStatements,
69
71
  extendArtifactBefore,
70
72
  extendArtifactAfter,
73
+ extendForeignKeys,
71
74
  applyIncludes, // TODO: re-check
72
75
  } );
73
76
 
74
77
  const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
75
- const isV5preview = isBetaEnabled( model.options, 'v5preview' );
76
78
 
77
79
  sortModelSources();
78
80
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -91,7 +93,8 @@ function extend( model ) {
91
93
  //-----------------------------------------------------------------------------
92
94
  // Extensions: general algorithm
93
95
  //-----------------------------------------------------------------------------
94
- // extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements
96
+ // extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements,
97
+ // extendForeignKeys
95
98
 
96
99
  /**
97
100
  * Goes through all (applied) annotations in the given artifact and chooses one
@@ -161,6 +164,7 @@ function extend( model ) {
161
164
  moveReturnsExtensions( art, extensionsMap );
162
165
 
163
166
  if (art.returns) {
167
+ ensureArtifactNotProcessed( art.returns );
164
168
  pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
165
169
  pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
166
170
  if (art.kind !== 'annotate') {
@@ -171,16 +175,66 @@ function extend( model ) {
171
175
  }
172
176
  const sub = art.items || art.targetAspect?.elements && art.targetAspect;
173
177
  if (sub) {
178
+ ensureArtifactNotProcessed( sub );
174
179
  pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
175
180
  pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
176
181
  }
177
182
  else {
178
- moveDictExtensions( art, extensionsMap,
179
- (art.enum && art.kind !== 'annotate' ? 'enum' : 'elements'), 'elements' );
183
+ let elementsProp = 'elements';
184
+ if (art.kind !== 'annotate')
185
+ elementsProp = art.enum && 'enum' || art.target && 'foreignKeys' || 'elements';
186
+
187
+ // keys are handled in tweak-assocs.js; don't push them down; see extendForeignKeys()
188
+ if (elementsProp !== 'foreignKeys')
189
+ moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
180
190
  moveDictExtensions( art, extensionsMap, 'enum' );
181
191
  }
182
192
  }
183
193
 
194
+ /**
195
+ * Apply foreign key extensions. Because foreign keys are handled late in the compiler
196
+ * (in tweak-assocs.js), we can't apply them in effectiveType(), yet.
197
+ * Instead, we postpone applying them until all foreign keys were generated.
198
+ *
199
+ * @param art
200
+ */
201
+ function extendForeignKeys( art ) {
202
+ // See extendArtifactAfter() for targetAspect/items handling.
203
+ const sub = art.items || art.targetAspect?.elements && art.targetAspect;
204
+ if (!art._extensions || sub)
205
+ return;
206
+
207
+ // push down foreign keys
208
+ moveDictExtensions( art, art._extensions, 'foreignKeys', 'elements' );
209
+ if (!art.foreignKeys)
210
+ return;
211
+
212
+ forEachGeneric(art, 'foreignKeys', (key) => {
213
+ if (!key._effectiveType)
214
+ throw new CompilerAssertion('foreign key should have been processed');
215
+ extendArtifactBefore( key );
216
+ extendArtifactAfter( key );
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Applying extensions is handled in extendArtifactAfter(). And only afterward,
222
+ * an effective sequence number is set. Meaning that if a sub-artifact already
223
+ * has a sequence number, then extensions would be lost.
224
+ *
225
+ * A special case are foreign keys, see extendForeignKeys().
226
+ */
227
+ function ensureArtifactNotProcessed( art ) {
228
+ if (!model.options.testMode)
229
+ return;
230
+
231
+ if (art.kind !== 'key' && art.$effectiveSeqNo !== 0 && art.$effectiveSeqNo !== undefined) {
232
+ // if the artifact already has a sequence number, then
233
+ // extendArtifactAfter() was already called -> annotations would be lost.
234
+ throw new CompilerAssertion('artifact already processed; extensions would be lost');
235
+ }
236
+ }
237
+
184
238
  /**
185
239
  * Create super annotate statements for remaining extensions
186
240
  */
@@ -196,15 +250,25 @@ function extend( model ) {
196
250
  }
197
251
 
198
252
  // TODO: delete again - if not, what about extensions in contexts/services?
253
+ // Check test.lsp-api.js! Links in extensions are needed.
199
254
  function setArtifactLinkForExtensions( source ) {
200
255
  if (!source.extensions)
201
256
  return;
202
- for (const ext of source.extensions ) {
257
+ for (const ext of source.extensions) {
258
+ if (!ext.name?.id)
259
+ continue;
260
+
203
261
  const { name } = ext;
204
- if (name?.id && name._artifact === undefined) {
262
+ const { path } = name;
263
+ if (name._artifact === undefined) {
205
264
  const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
206
265
  resolvePath( name, refCtx, ext ); // for LSP
207
266
  }
267
+ else if (model.options.lspMode && path?.[0]._artifact === undefined) {
268
+ // we don't use resolvePath(…,'extend'), as that would add a dependency
269
+ resolveDefinitionName( ext );
270
+ setArtifactLink( path[path.length - 1], name._artifact );
271
+ }
208
272
  }
209
273
  }
210
274
 
@@ -224,7 +288,7 @@ function extend( model ) {
224
288
  code: 'extend … with definitions',
225
289
  keyword: 'extend service',
226
290
  };
227
- // TODO(v5): Discuss: make this an error?
291
+ // TODO(v6): Discuss: make this an error?
228
292
  warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
229
293
  std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
230
294
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
@@ -327,7 +391,7 @@ function extend( model ) {
327
391
 
328
392
  function extensionOverwrites( ext, prop ) {
329
393
  return (prop.charAt(0) !== '@')
330
- ? [ 'doc', 'length', 'precision', 'scale', 'srid' ].includes( prop )
394
+ ? (prop === 'doc' || typeParameters.list.includes(prop))
331
395
  : !annotationHasEllipsis( ext[prop] );
332
396
  }
333
397
 
@@ -393,6 +457,9 @@ function extend( model ) {
393
457
  for (const col of ext.columns)
394
458
  col.$extended = true;
395
459
 
460
+ if (art.kind === 'annotate' && art.$inferred === '')
461
+ return; // internal super-annotate for unknown artifacts
462
+
396
463
  if (!query?.from?.path) {
397
464
  const variant = (query?.from || query)?.op?.val || 'std';
398
465
  error( 'extend-columns', [ ext.columns[$location], ext ], { '#': variant, art } );
@@ -404,7 +471,7 @@ function extend( model ) {
404
471
  query.columns.push( ...ext.columns );
405
472
  initSelectItems( query, ext.columns, query, true );
406
473
  }
407
- else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
474
+ else if (typeParameters.list.includes( prop )) {
408
475
  const typeExts = art.$typeExts || (art.$typeExts = {});
409
476
  typeExts[prop] = ext;
410
477
  }
@@ -490,7 +557,7 @@ function extend( model ) {
490
557
  if ('val' in upToSpec) {
491
558
  if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
492
559
  return true;
493
- // TODO v5: delete the speciao UP TO comparison?
560
+ // TODO v6: delete the special UP TO comparison?
494
561
  const upToVal = upToSpec.val;
495
562
  const prevVal = previousItem.val;
496
563
  // eslint-disable-next-line eqeqeq
@@ -540,7 +607,7 @@ function extend( model ) {
540
607
  // - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
541
608
  // - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
542
609
  //
543
- // TODO v5: do not allow `extend … with (precision: …)` alone if original def also has `scale`
610
+ // TODO v6: do not allow `extend … with (precision: …)` alone if original def also has `scale`
544
611
  function applyTypeExtensions( art, ext, prop, scaleDiff ) {
545
612
  // console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
546
613
  if (!ext?.[prop])
@@ -614,20 +681,21 @@ function extend( model ) {
614
681
  const extensions = extensionsMap[extProp];
615
682
  if (!extensions)
616
683
  return;
684
+
617
685
  const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
618
686
 
619
687
  for (const ext of extensions) {
620
- const extDict = ext[extProp];
621
688
  let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
622
- for (const name in extDict) {
623
- const elemExt = extDict[name];
689
+ forEachGeneric(ext, extProp, (elemExt, name) => {
624
690
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
625
- continue; // definitions inside extend, already handled
691
+ return; // definitions inside extend, already handled
626
692
  dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
627
693
  const elem = artDict[name] || annotateFor( art, extProp, name );
628
694
  setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
629
- pushToDict( elem, '_extensions', elemExt );
630
- }
695
+ ensureArtifactNotProcessed( elem );
696
+ if (elem.$duplicates !== true)
697
+ pushToDict( elem, '_extensions', elemExt );
698
+ });
631
699
  }
632
700
  }
633
701
 
@@ -729,10 +797,11 @@ function extend( model ) {
729
797
  const dict = parent[prop];
730
798
  if (!dict) {
731
799
  // TODO: check - for each name? - better locations
732
- const location = ext._parent[prop][$location] || ext.name.location;
800
+ const location = ext._parent[prop]?.[$location] || ext.name.location;
733
801
  // Remark: no `elements` dict location with `annotate Main:elem`
734
802
  switch (prop) {
735
803
  // TODO: change texts, somehow similar to checkDefinitions() ?
804
+ case 'foreignKeys':
736
805
  case 'elements':
737
806
  case 'enum': // TODO: extra?
738
807
  warning( 'anno-unexpected-elements', [ location, ext._parent ],
@@ -778,6 +847,10 @@ function extend( model ) {
778
847
  { '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
779
848
  parent.enum );
780
849
  break;
850
+ case 'foreignKeys':
851
+ notFound( 'ext-undefined-key', ext.name.location, ext,
852
+ { name }, parent.foreignKeys );
853
+ break;
781
854
  case 'params':
782
855
  notFound( 'ext-undefined-param', ext.name.location, ext,
783
856
  { '#': 'param', art: parent, name },
@@ -791,7 +864,8 @@ function extend( model ) {
791
864
  parent.actions );
792
865
  break;
793
866
  default:
794
- // assert
867
+ if (model.options.testMode)
868
+ throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
795
869
  }
796
870
  }
797
871
  return true;
@@ -832,8 +906,9 @@ function extend( model ) {
832
906
  }
833
907
 
834
908
  function checkRemainingMainExtensions( art, ext, localized ) {
909
+ const isExtend = ext.kind === 'extend';
835
910
  if (localized) {
836
- if (isV5preview && ext.kind === 'extend') {
911
+ if (isExtend) {
837
912
  // In v5, reject any `extend` on localized.
838
913
  error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
839
914
  { '#': 'localized', keyword: 'annotate' } );
@@ -847,22 +922,21 @@ function extend( model ) {
847
922
  if (art?.builtin) {
848
923
  info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
849
924
  }
850
- else if (art?.kind === 'namespace') {
925
+ else if (isExtend && art?.kind === 'namespace') {
926
+ // `annotate` on namespaces already handled before
851
927
  const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
852
928
  const firstAnno = ext[hasAnnotations];
853
929
  // In v5, extending namespaces is only allowed for `extend with definitions`.
854
930
  // Neither annotations nor other extensions are allowed.
855
931
  // Non-artifact extensions are reported in resolvePath() already (for v5).
856
- if ((hasAnnotations || !ext.artifacts) ) {
857
- if (isV5preview) {
858
- error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
859
- '#': 'namespace', art: ext,
860
- } );
861
- }
862
- else {
863
- info( 'anno-namespace', [ (firstAnno?.name || ext.name).location, ext ], {},
864
- 'Namespaces can\'t be annotated nor extended' );
865
- }
932
+ // Because "namespaces" are the same as "unknown" artifacts in CSN, we don't report
933
+ // an error for `annotate`s.
934
+ // FIXME: The compiler generates empty `annotate` statements for
935
+ // `extend ns with definitions {…}`. That's why we check the frontend.
936
+ if (hasAnnotations || (!ext.artifacts && ext._block.$frontend !== 'json')) {
937
+ error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
938
+ '#': 'namespace', art: ext,
939
+ } );
866
940
  }
867
941
  }
868
942
  }
@@ -1101,7 +1175,6 @@ function extend( model ) {
1101
1175
  { art: artName },
1102
1176
  {
1103
1177
  std: 'Unknown $(ART) - nothing to extend',
1104
- // eslint-disable-next-line max-len
1105
1178
  element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
1106
1179
  action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
1107
1180
  } );
@@ -1232,7 +1305,7 @@ function extend( model ) {
1232
1305
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1233
1306
  dictAdd( ext[prop], name, elem );
1234
1307
  elem.$inferred = 'include';
1235
- if (origin.masked) // TODO(v5): remove 'masked'
1308
+ if (origin.masked) // TODO(v6): remove 'masked'
1236
1309
  elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
1237
1310
  if (origin.key)
1238
1311
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
@@ -1269,8 +1342,6 @@ function extend( model ) {
1269
1342
  /**
1270
1343
  * Report duplicates in parent[prop] that happen due to multiple includes having the
1271
1344
  * same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
1272
- *
1273
- * TODO(v5): Make this a hard error; see checkRedefinition(); maybe combine both;
1274
1345
  */
1275
1346
  function checkRedefinitionThroughIncludes( parent, prop ) {
1276
1347
  if (!parent[prop])
@@ -656,7 +656,7 @@ function generate( model ) {
656
656
 
657
657
  if (elem.type && !isDirectComposition( elem )) {
658
658
  // Only issue warning for direct usages, not for projections, includes, etc.
659
- // TODO: Make it configurable error; v5: error
659
+ // TODO: Make it configurable error; v6: error
660
660
  // TODO: move to resolve.js where we test the targetAspect,
661
661
  warning( 'type-expecting-composition', [ elem.type.location, elem ],
662
662
  { newcode: 'Composition of', code: 'Association to' },
@@ -34,7 +34,7 @@ const { Location, emptyWeakLocation } = require('../base/location');
34
34
  const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
35
35
  const { checkRemovedDeprecatedFlags } = require('../base/model');
36
36
  const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
37
- const { cdsFs } = require('../utils/file');
37
+ const { cdsFs, fileExtension } = require('../utils/file');
38
38
 
39
39
  const fs = require('fs');
40
40
  const path = require('path');
@@ -82,12 +82,8 @@ class ArgumentError extends Error {
82
82
  function parseX( source, filename, options = {}, messageFunctions = null ) {
83
83
  if (!messageFunctions)
84
84
  messageFunctions = createMessageFunctions( options, 'parse' );
85
- const ext = path.extname( filename ).slice(1).toLowerCase();
86
- // eslint-disable-next-line no-nested-ternary
87
- const parser = options.fallbackParser === 'auto!'
88
- ? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
89
- : (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
90
- source.startsWith( '{' ) && parseCsn.parse);
85
+ const ext = fileExtension( filename );
86
+ const parser = parserForFile( source, ext, options );
91
87
  if (parser)
92
88
  return parser( source, filename, options, messageFunctions );
93
89
 
@@ -101,6 +97,27 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
101
97
  return model;
102
98
  }
103
99
 
100
+ /**
101
+ * Get the correct parser for the given source / file extension.
102
+ * Respects the set fallback parser.
103
+ *
104
+ * @param {string} source
105
+ * @param {string} ext
106
+ * @param {object} options
107
+ */
108
+ function parserForFile( source, ext, options ) {
109
+ // 'auto!' ignores the file's extension
110
+ if (options.fallbackParser === 'auto!')
111
+ return (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage);
112
+
113
+ if (options.fallbackParser === 'csn!')
114
+ return parseCsn.parse;
115
+
116
+ return extensionParsers[ext] ||
117
+ extensionParsers[options.fallbackParser] ||
118
+ (source.startsWith( '{' ) && parseCsn.parse);
119
+ }
120
+
104
121
  // Main function: Compile the sources from the files given by the array of
105
122
  // `filenames`. As usual with the `fs` library, relative file names are
106
123
  // relative to the working directory `process.cwd()`. With argument `dir`, the
@@ -149,10 +166,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
149
166
  .then( () => promiseAllDoNotRejectImmediately( input.files.map( readAndParse ) ) )
150
167
  .then( testInvocation, (reason) => {
151
168
  // do not reject with PromiseAllError, use InvocationError:
152
- const errs = reason.valuesOrErrors.filter( e => e instanceof Error );
169
+ const errs = reason.valuesOrErrors?.filter( e => e instanceof Error ) || [ reason ];
153
170
  // internal error if no file IO error (has property `path`)
154
171
  return Promise.reject( errs.find( e => !e.path ) ||
155
- new InvocationError( [ ...input.repeated, ...errs ]) );
172
+ new InvocationError( [ ...(input?.repeated || []), ...errs ]) );
156
173
  } );
157
174
 
158
175
  if (!options.parseOnly && !options.parseCdl)
@@ -530,7 +547,7 @@ function processFilenamesSync( filenames, dir ) {
530
547
  // already handles non-existent files.
531
548
  name = fs.realpathSync.native( name );
532
549
  }
533
- catch (e) {
550
+ catch {
534
551
  // Ignore the not-found (ENOENT) error
535
552
  }
536
553
  filenameMap[originalName] = name;