@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -9,21 +9,31 @@ const { searchName, weakLocation } = require('../base/messages');
9
9
  const {
10
10
  isDeprecatedEnabled,
11
11
  forEachGeneric, forEachInOrder, forEachDefinition,
12
+ forEachMember,
13
+ isBetaEnabled,
12
14
  } = require('../base/model');
13
- const { dictAdd } = require('../base/dictionaries');
15
+ const { dictAdd, pushToDict } = require('../base/dictionaries');
14
16
  const { kindProperties, dictKinds } = require('./base');
15
17
  const {
16
18
  setLink,
17
19
  setArtifactLink,
18
- annotateWith,
20
+ copyExpr,
21
+ setAnnotation,
22
+ setExpandStatusAnnotate,
19
23
  linkToOrigin,
20
24
  setMemberParent,
21
25
  dependsOnSilent,
22
26
  augmentPath,
23
- splitIntoPath, isDirectComposition,
27
+ pathName,
28
+ splitIntoPath,
29
+ isDirectComposition,
30
+ annotationHasEllipsis,
24
31
  } = require('./utils');
25
32
  const layers = require('./moduleLayers');
26
- const { typeParameters } = require('./builtins');
33
+
34
+ const $location = Symbol.for('cds.$location');
35
+
36
+ const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
27
37
 
28
38
  function extend( model ) {
29
39
  const { options } = model;
@@ -34,23 +44,22 @@ function extend( model ) {
34
44
  const {
35
45
  resolvePath,
36
46
  resolveUncheckedPath,
37
- checkAnnotate,
38
- initAnnotations,
39
- copyAnnotationsForExtensions,
47
+ resolveTypeArgumentsUnchecked,
40
48
  attachAndEmitValidNames,
41
- checkDefinitions,
42
49
  initArtifact,
43
50
  initMembers,
44
- extensionsDict, // not a function - TODO
45
51
  } = model.$functions;
46
52
 
47
53
  Object.assign( model.$functions, {
48
54
  lateExtensions,
49
- layeredAssignments,
50
- assignmentsOfHighestLayers,
51
- applyTypeExtensions,
55
+ chooseAnnotationsInArtifact,
56
+ extendArtifactAfter,
52
57
  } );
53
58
 
59
+ const extensionsDict = Object.create(null);
60
+ forEachDefinition( model, tagIncludes ); // TODO TMP
61
+
62
+ forEachDefinition( model, chooseAnnotationsInArtifact );
54
63
  applyExtensions();
55
64
 
56
65
  const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
@@ -58,9 +67,8 @@ function extend( model ) {
58
67
 
59
68
  Object.keys( model.definitions ).forEach( processArtifact );
60
69
 
61
- lateExtensions( false );
62
-
63
70
  compositionChildPersistence();
71
+ return;
64
72
 
65
73
  /**
66
74
  * Process "composition of" artifacts.
@@ -110,90 +118,442 @@ function extend( model ) {
110
118
  if (def.$inferred === 'composition-entity' && !processed.has(def)) {
111
119
  if (def._parent)
112
120
  processCompositionPersistence(def._parent);
113
- copyPersistenceAnnotations(def, def._parent, options);
121
+ copyPersistenceAnnotations( def, def._parent );
114
122
  processed.add(def);
115
123
  }
116
124
  }
117
125
  }
118
126
 
127
+ // TMP:
128
+ function tagIncludes( art ) {
129
+ if (art.includes)
130
+ extensionsDict[art.name.absolute] = [];
131
+ }
132
+
133
+ //-----------------------------------------------------------------------------
134
+ // Extensions: general algorithm
135
+ //-----------------------------------------------------------------------------
136
+ // extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements
137
+
138
+ // TODO: assert that we have not yet transformed/used _extensions on sub elements
139
+ // TODO necessary(?): transformArtifactExtensions must ensure that each annotate
140
+ // is in either returns,items,elements,enum
141
+ function extendArtifactAfter( art ) {
142
+ const extensionsMap = art._extensions;
143
+ if (!extensionsMap || art.builtin) // builtin members handled via "super annotate"
144
+ return;
145
+ // type extensions after having “populated” the artifact ($typeArgs -> length,
146
+ // …, TODO: do that there) and setting an _effectiveType:
147
+ if (art.$typeExts) {
148
+ const { type } = art; // if the type is not inferred, it is the origin...
149
+ if (type?._artifact && !type.$inferred) // ...and thus is resolved
150
+ resolveTypeArgumentsUnchecked( art, type._artifact, art );
151
+ const exts = art.$typeExts;
152
+ applyTypeExtensions( art, exts.length, 'length' );
153
+ const scaleDiff = applyTypeExtensions( art, exts.scale, 'scale' );
154
+ applyTypeExtensions( art, exts.precision, 'precision', scaleDiff );
155
+ applyTypeExtensions( art, exts.srid, 'srid' );
156
+ delete art.$typeExts;
157
+ }
158
+ // TODO tmp: no proper XSN representation yet for annotate … with returns:
159
+ if (art.kind === 'annotate' && !art.returns &&
160
+ (extensionsMap.elements?.some( e => e.$syntax === 'returns' ) ||
161
+ extensionsMap.enum?.some( e => e.$syntax === 'returns' )))
162
+ annotateCreate( art, '', art, 'returns' );
163
+
164
+ moveDictExtensions( art, extensionsMap, 'params' );
165
+ // moveReturnsExtensions( art, extensionsMap );
166
+ const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;
167
+ if (sub) {
168
+ if (art.returns) { // after having applied params!
169
+ extendHandleReturns( extensionsMap.elements, art );
170
+ extendHandleReturns( extensionsMap.enum, art );
171
+ }
172
+ // care about 'ext-unexpected-returns' in a later change if we have XSN returns
173
+ pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
174
+ pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
175
+ }
176
+ else {
177
+ moveDictExtensions( art, extensionsMap,
178
+ (art.enum && art.kind !== 'annotate' ? 'enum' : 'elements'), 'elements' );
179
+ moveDictExtensions( art, extensionsMap, 'enum' );
180
+ }
181
+ moveDictExtensions( art, extensionsMap, 'actions' );
182
+ }
183
+
184
+ /**
185
+ * Create super annotate statements for remaining extensions
186
+ */
187
+ function lateExtensions() { // -> createRemainingAnnotateStatements
188
+ model.extensions = Object.values( model.$lateExtensions );
189
+ // TODO: testMode sort?
190
+ model.extensions.forEach( createSuperAnnotate );
191
+ }
192
+
193
+ // For extendArtifactAfter(): -------------------------------------------------
194
+
195
+ // Remarks on messages: we allow the type extensions only if the artifact
196
+ // originally had that property → any check of the kind “type prop can only be
197
+ // used with FooBar” is independent from `extend … with type`. Function
198
+ // checkTypeArguments() in resolve.js reports 'type-unexpected-argument', but
199
+ // that is currently incomplete.
200
+ //
201
+ // We then report (in the future), use the first message of:
202
+ // - the usual messages if a type argument is wrong, independently from `extend`
203
+ // - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
204
+ // - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
205
+ //
206
+ // TODO v4: do not allow `extend … with (precision: …)` alone if original def also has `scale`
207
+ function applyTypeExtensions( art, ext, prop, scaleDiff ) {
208
+ // console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
209
+ if (!ext?.[prop])
210
+ return 0;
211
+ if (!art[prop]) {
212
+ const isBuiltin = art._effectiveType?.builtin;
213
+ if (isBuiltin && !allowsTypeArgument( art, prop )) {
214
+ // Let checkTypeArguments() in resolve.js report a message, is incomplete
215
+ // though, i.e. can only safely be used for scalars at the moment. But we
216
+ // will improve that function and not try to do extra things here.
217
+ art[prop] = ext[prop]; // enable checkTypeArguments() doing its job
218
+ return 0;
219
+ }
220
+ // TODO: think about 'ext-unexpected-type-argument'
221
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
222
+ { '#': (isBuiltin ? 'indirect' : 'new-prop'), prop } );
223
+ return 0;
224
+ }
225
+ const artVal = art[prop].val;
226
+ const extVal = ext[prop].val;
227
+ if (prop === 'srid') {
228
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'prop', prop } );
229
+ }
230
+ else if (typeof artVal !== 'number' || typeof extVal !== 'number' ) {
231
+ // Users can't change from/to string value for property,
232
+ // e.g. `variable`/`floating` for Decimal
233
+ // TODO: Shouldn't the text distinguish between orig string and extension string?
234
+ // Not sure whether to talk about strings if we have a keyword in CDL
235
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'string', prop } );
236
+ }
237
+ else if (extVal < artVal + (scaleDiff || 0)) {
238
+ const number = artVal + (scaleDiff || 0);
239
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
240
+ // eslint-disable-next-line object-curly-newline
241
+ { '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale' } );
242
+ }
243
+ else {
244
+ art[prop] = ext[prop];
245
+ return extVal - artVal;
246
+ }
247
+ return 0;
248
+ }
249
+
250
+ function allowsTypeArgument( art, prop ) {
251
+ const { parameters } = art._effectiveType;
252
+ if (!parameters)
253
+ return false;
254
+ return parameters.includes( prop ) || parameters[0]?.name === prop;
255
+ }
256
+
257
+ function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
258
+ // TODO: setExpandStatusAnnotate
259
+ const extensions = extensionsMap[extProp];
260
+ if (!extensions)
261
+ return;
262
+ const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
263
+
264
+ for (const ext of extensions) {
265
+ const extDict = ext[extProp];
266
+ for (const name in extDict) {
267
+ let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
268
+ const elemExt = extDict[name];
269
+ if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
270
+ continue; // definitions inside extend, already handled
271
+ dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
272
+ const elem = artDict[name] || annotateFor( art, extProp, name );
273
+ setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
274
+ pushToDict( elem, '_extensions', elemExt );
275
+ }
276
+ }
277
+ }
278
+
279
+ // function moveReturnsExtensions( art, extensionsMap ) {
280
+ // const artReturns = art.returns;
281
+ // const extensions = extensionsMap.returns;
282
+ // // TODO: artItem is null
283
+ // for (const ext of extensions)
284
+ // pushToDict( artReturns, '_extensions', ext.returns );
285
+ // }
286
+
287
+ function annotateFor( art, prop, name ) {
288
+ const base = annotateBase( art );
289
+ if (name === '' && prop === 'params')
290
+ return base.returns || annotateCreate( base, name, base, 'returns' );
291
+ const dict = base[prop] || (base[prop] = Object.create( null ));
292
+ if (name == null)
293
+ return dict;
294
+ return dict[name] || annotateCreate( dict, name, base );
295
+ }
296
+
297
+ function annotateBase( art ) {
298
+ while (art._outer) // TOOD: think about anonymous target aspect
299
+ art = art._outer;
300
+ // if (art._annotateS)
301
+ // return art._annotateS;
302
+ if (art.kind === 'annotate')
303
+ return art;
304
+
305
+ // TODO: more to do if annotate can have `returns` property
306
+ if (art.kind === 'select')
307
+ art = art._parent;
308
+ if (art._main)
309
+ return annotateFor( art._parent, kindProperties[art.kind].dict, art.name.id );
310
+
311
+ const { absolute } = art.name;
312
+ return model.$lateExtensions[absolute] || annotateCreate( model.$lateExtensions, absolute );
313
+ }
314
+
315
+ function annotateCreate( dict, id, parent, prop ) {
316
+ const annotate = {
317
+ kind: 'annotate',
318
+ name: { id, location: genLocation },
319
+ $inferred: '',
320
+ location: genLocation,
321
+ };
322
+ if (parent) {
323
+ setLink( annotate, '_parent', parent );
324
+ setLink( annotate, '_main', parent._main || parent );
325
+ }
326
+ else {
327
+ annotate.name.absolute = id; // TODO later (if all names are sparse): delete absolute
328
+ }
329
+ dict[prop || id] = annotate;
330
+ return annotate;
331
+ }
332
+
333
+ function extendHandleReturns( extensions, art ) {
334
+ for (const ext of extensions || []) {
335
+ if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
336
+ ext.$syntax = '$inside-returns';
337
+ delete ext.params;
338
+ }
339
+ else {
340
+ warning( 'ext-expected-returns', [ ext.name.location, ext ], {
341
+ '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
342
+ }, {
343
+ std: 'Expected $(CODE)', // unused variant
344
+ action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
345
+ function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
346
+ } );
347
+ }
348
+ }
349
+ }
350
+
351
+ // const unexpected_props = {
352
+ // elements: 'anno-unexpected-elements',
353
+ // enum: 'anno-unexpected-elements', // TODO
354
+ // params: 'anno-unexpected-params',
355
+ // actions: 'anno-unexpected-actions',
356
+ // };
357
+ // const undefined_props = {
358
+ // elements: 'anno-undefined-element',
359
+ // enum: 'anno-undefined-element', // TODO
360
+ // params: 'anno-undefined-param',
361
+ // actions: 'anno-undefined-action',
362
+ // };
363
+
364
+ function checkRemainingMemberExtensions( parent, ext, prop, name ) {
365
+ // console.log('CRME:',prop,name,parent,ext)
366
+ const dict = parent[prop];
367
+ if (!dict) {
368
+ // TODO: check - for each name? - better locations
369
+ const location = ext._parent[prop][$location] || ext.name.location;
370
+ // Remark: no `elements` dict location with `annotate Main:elem`
371
+ switch (prop) {
372
+ // TODO: change texts, somehow similar to checkDefinitions() ?
373
+ case 'elements':
374
+ case 'enum': // TODO: extra?
375
+ warning( 'anno-unexpected-elements', [ location, ext._parent ],
376
+ { '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
377
+ std: 'Elements only exist in entities, types or typed constructs',
378
+ entity: 'Elements of entity types can\'t be annotated',
379
+ });
380
+ break;
381
+ case 'params':
382
+ warning( 'anno-unexpected-params', [ location, ext._parent ], {},
383
+ 'Parameters only exist for actions or functions' );
384
+ break;
385
+ case 'actions':
386
+ warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
387
+ 'Actions and functions only exist top-level and for entities' );
388
+ break;
389
+ default:
390
+ // assert
391
+ }
392
+ return false;
393
+ }
394
+ else if (!dict[name]) {
395
+ // TODO: make variant `returns` an auto-variant for ($ART) ?
396
+ const inReturns = parent._parent?.returns && parent._parent;
397
+ const art = inReturns || parent;
398
+ switch (prop) {
399
+ case 'elements':
400
+ notFound( 'anno-undefined-element', ext.name.location, ext,
401
+ { '#': (inReturns ? 'returns' : 'element'), art, name },
402
+ parent.elements );
403
+ break;
404
+ case 'enum': // TODO: extra msg id?
405
+ notFound( 'anno-undefined-element', ext.name.location, ext,
406
+ { '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
407
+ parent.enum );
408
+ break;
409
+ case 'params':
410
+ notFound( 'anno-undefined-param', ext.name.location, ext,
411
+ { '#': 'param', art: parent, name },
412
+ parent.params );
413
+ break;
414
+ case 'actions':
415
+ notFound( 'anno-undefined-action', ext.name.location, ext,
416
+ { '#': 'action', art: parent, name },
417
+ parent.actions );
418
+ break;
419
+ default:
420
+ // assert
421
+ }
422
+ }
423
+ return true;
424
+ }
425
+
426
+ function notFound( msgId, location, address, args, validDict ) {
427
+ const msg = message( msgId, [ location, address ], args );
428
+ attachAndEmitValidNames( msg, validDict );
429
+ }
430
+
431
+ // For createRemainingAnnotateStatements(): -----------------------------------
432
+
433
+ function createSuperAnnotate( annotate ) {
434
+ const extensions = annotate._extensions;
435
+ if (extensions && !annotate._main) {
436
+ const { absolute } = annotate.name;
437
+ const isLocalized = absolute.startsWith( 'localized.' ); // TODO: && anno
438
+ const art = model.definitions[absolute];
439
+ for (const ext of extensions)
440
+ checkRemainingMainExtensions( art, ext, isLocalized );
441
+ if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
442
+ setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
443
+ // direct annotations on builtins or on the builtins for propagation, and
444
+ // also shallow-copied to $collectedExtensions for to-csn
445
+ for (const prop in art) {
446
+ if (prop.charAt(0) === '@' || prop === 'doc')
447
+ annotate[prop] = art[prop];
448
+ }
449
+ }
450
+ if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
451
+ annotate.location = extensions[0].location;
452
+ annotate.name.location = extensions[0].name.location;
453
+ }
454
+ }
455
+ chooseAnnotationsInArtifact( annotate );
456
+ extendArtifactAfter( annotate );
457
+ forEachMember( annotate, createSuperAnnotate );
458
+ }
459
+
460
+ function checkRemainingMainExtensions( art, ext, localized ) {
461
+ if (localized) // TODO v4: ignore only for annotate
462
+ return;
463
+ if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
464
+ return;
465
+ // else if (ext.kind === 'extend') { // TODO v4 - add error
466
+ // }
467
+ if (art?.kind === 'namespace') {
468
+ // TODO: not at all different to having no definition
469
+ info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
470
+ 'Namespaces can\'t be annotated' );
471
+ }
472
+ else if (art?.builtin) {
473
+ info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
474
+ 'Builtin types should not be annotated. Use custom type instead' );
475
+ }
476
+ }
477
+
478
+ // Issue messages for annotations on namespaces and builtins
479
+ // (TODO: really here?, probably split main artifacts vs returns)
480
+ // see also lateExtensions() where similar messages are reported
481
+ function checkAnnotate( construct, art ) {
482
+ // TODO: Handle extend statements properly: Different message for empty extend?
483
+
484
+ // --> without art._block, art not found
485
+ if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
486
+ if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
487
+ // `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
488
+ // for non-actions. We can't only check for !art.returns, because `action A();` is valid.
489
+ // `art._block` ensures that `art` is a defined def.
490
+ return;
491
+ // warning('ext-unexpected-returns', [ construct.name.location, construct ],
492
+ // { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
493
+ }
494
+ else if (construct.$syntax !== 'returns' &&
495
+ (art.kind === 'action' || art.kind === 'function') && construct.elements) {
496
+ warning('ext-expected-returns', [ construct.name.location, construct ], {
497
+ '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
498
+ }, {
499
+ std: 'Expected $(CODE)', // unused variant
500
+ action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
501
+ function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
502
+ });
503
+ }
504
+ }
505
+ }
506
+
119
507
  // extend ------------------------------------------------------------------
120
508
 
121
509
  /**
122
510
  * Apply the extensions inside the extensionsDict on the model.
123
511
  *
124
- * Phase 1: context extends, 2: extends with structure includes, 3: extends
125
- * without structure includes (in the case of cyclic includes)
126
- *
127
- * Before phase 1: all artifact extensions have been collected (even those
128
- * inside extend context), only "empty" ones from structure includes are still unknown.
129
- * After phase 1, all main artifacts are known, also "empty" extensions are known.
512
+ * First try normally: extends with structure includes; with remaining cyclic
513
+ * includes, do so without includes.
130
514
  */
131
515
  function applyExtensions() {
132
- let phase = 1; // TODO: basically remove phase 1
516
+ let noIncludes = false;
133
517
  let extNames = Object.keys( extensionsDict ).sort();
134
- // Remark: The sort() makes sure that an extend for artifact C.E is applied
135
- // after the extend for C has been applied (which could have defined C.E).
136
- // Looping over model.definitions in Phase 1 would miss the `extend
137
- // context` for a context C.C defined in an `extend context C`.
138
- //
139
- // TODO: no need to sort anymore
518
+
140
519
  while (extNames.length) {
141
520
  const { length } = extNames;
142
521
  for (const name of extNames) {
143
522
  const art = model.definitions[name];
144
- if (!art || art.kind === 'namespace') {
145
- model.$lateExtensions[name] = extensionsDict[name];
523
+ if (art && art.kind !== 'namespace' &&
524
+ extendArtifact( extensionsDict[name], art, noIncludes ))
146
525
  delete extensionsDict[name];
147
- }
148
- else if (art.$duplicates) { // cannot extend redefinitions
149
- delete extensionsDict[name];
150
- }
151
- else if (phase === 1
152
- ? extendContext( name, art )
153
- : extendArtifact( extensionsDict[name], art, phase > 2 )) { // >2: no self-include
154
- delete extensionsDict[name];
155
- }
156
526
  }
157
527
  extNames = Object.keys( extensionsDict ); // no sort() required anymore
158
- if (phase === 1)
159
- phase = 2;
160
- else if (extNames.length >= length)
161
- phase = 3;
528
+ if (extNames.length >= length)
529
+ noIncludes = Object.keys( extensionsDict ); // = no includes
162
530
  }
163
531
  }
164
532
 
165
- function extendContext( name, art ) {
166
- // TODO: remove this function - add remains to define.js
167
- // (ext.expectedKind == art.kind) already checked by parser except for context/service
168
- if (!kindProperties[art.kind].artifacts) {
169
- // no context or service => warn about context extensions
170
- for (const ext of extensionsDict[name]) {
171
- if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
172
- const loc = ext.name.location;
173
- // TODO: warning is enough
174
- error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
175
- std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
176
- service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
177
- context: 'Can\'t extend non-context $(NAME) with $(KEYWORD)',
533
+ function checkExtensionsKind( extensions, art ) {
534
+ for (const ext of extensions) {
535
+ const kind = ext.expectedKind?.val;
536
+ if (kind && kind !== art.kind) {
537
+ const loc = ext.expectedKind.location;
538
+ if (kind === 'context' || kind === 'service') {
539
+ // We have no real artifact during the construction of a super-annotate statement:
540
+ const msgArgs = {
541
+ '#': (art.kind === 'service' || art.kind === 'annotate') ? art.kind : 'std',
542
+ art,
543
+ kind,
544
+ code: 'extend with definitions',
545
+ keyword: 'extend service',
546
+ };
547
+ warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
548
+ std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
549
+ annotate: 'There is no artifact $(ART), use $(CODE) instead',
550
+ // do not mention 'extend context', that is not in CAPire
551
+ service: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) or $(KEYWORD) instead',
178
552
  });
179
553
  }
554
+ // TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
180
555
  }
181
- return false;
182
556
  }
183
-
184
- for (const ext of extensionsDict[name]) {
185
- setArtifactLink( ext.name, art );
186
- checkDefinitions( ext, art, 'elements'); // error for elements etc
187
- checkDefinitions( ext, art, 'enum');
188
- checkDefinitions( ext, art, 'actions');
189
- checkDefinitions( ext, art, 'params');
190
- checkDefinitions( ext, art, 'columns');
191
- if (ext.includes)
192
- applyIncludes( ext, art ); // emits error if `includes` is set
193
- checkAnnotate( ext, art );
194
- copyAnnotationsForExtensions( ext, art );
195
- }
196
- return true;
197
557
  }
198
558
 
199
559
  /**
@@ -210,12 +570,23 @@ function extend( model ) {
210
570
  if (!noIncludes && !(canApplyIncludes( art, art ) &&
211
571
  extensions.every( ext => canApplyIncludes(ext, art) )))
212
572
  return false;
573
+ if (Array.isArray( noIncludes )) {
574
+ canApplyIncludes( art, art, noIncludes );
575
+ extensions.forEach( ext => canApplyIncludes( ext, art, noIncludes ) );
576
+ }
577
+ else if (!noIncludes &&
578
+ !(canApplyIncludes( art, art ) &&
579
+ extensions.every( ext => canApplyIncludes( ext, art) ))) {
580
+ // console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
581
+ return false;
582
+ }
213
583
  if (!art.query) {
214
584
  model._entities.push( art ); // add structure with includes in dep order
215
585
  art.$entity = ++model.$entity;
216
586
  }
217
587
  if (!noIncludes && art.includes)
218
588
  applyIncludes( art, art );
589
+ // checkExtensionsKind( extensions, art );
219
590
  extendMembers( extensions, art, noIncludes === 'gen' );
220
591
  if (!noIncludes && art.includes) {
221
592
  // early propagation of specific annotation assignments
@@ -229,12 +600,14 @@ function extend( model ) {
229
600
  function extendMembers( extensions, art, noExtend ) {
230
601
  // TODO: do the whole extension stuff lazily if the elements are requested
231
602
  const elemExtensions = [];
232
- extensions.sort( layers.compareLayer );
603
+ if (art._main) // extensions already sorted for main artifacts
604
+ extensions.sort( layers.compareLayer );
233
605
  // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
234
- for (const ext of extensions) {
606
+ // console.log('EM:',art.name,extensions,art._extensions)
607
+ for (const ext of extensions) { // those in extMap.includes
235
608
  // console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
236
609
  // 'Info', 'EXT').toString())
237
- if (!('_artifact' in ext.name)) { // not already applied
610
+ if (ext.name._artifact === undefined) { // not already applied
238
611
  setArtifactLink( ext.name, art );
239
612
  if (noExtend && ext.kind === 'extend') {
240
613
  error( 'extend-for-generated', [ ext.name.location, ext ], { art },
@@ -252,12 +625,10 @@ function extend( model ) {
252
625
  art.includes = [ ...ext.includes ];
253
626
  applyIncludes( ext, art );
254
627
  }
628
+ // console.log(ext,art)
255
629
  checkAnnotate( ext, art );
256
- initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
257
- copyAnnotationsForExtensions( ext, art );
258
630
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
259
631
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
260
- storeTypeExtension( ext, art );
261
632
  dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
262
633
  }
263
634
  for (const name in ext.elements) {
@@ -267,17 +638,24 @@ function extend( model ) {
267
638
  break; // more than one elem in same EXTEND is fine
268
639
  }
269
640
  }
270
-
271
- if (ext.columns) // extend projection
272
- extendColumns( ext, art );
273
641
  }
274
642
  if (elemExtensions.length > 1)
275
643
  reportUnstableExtensions( elemExtensions );
276
- if (art._extendType && art._extendType.length > 0)
277
- reportTypeExtensionsInSameLayer( art._extendType );
278
644
 
645
+ // This whole function will be removed with a next change - no need to have nice code here:
646
+ const extsTmp = { elements: Object.create(null), actions: Object.create(null) };
647
+ for (const e of extensions) {
648
+ for (const n in e.elements || []) {
649
+ if (e.elements[n].kind === 'extend')
650
+ pushToDict( extsTmp.elements, n, e.elements[n] );
651
+ }
652
+ for (const n in extensions.actions || []) {
653
+ if (e.actions[n].kind === 'extend')
654
+ pushToDict( extsTmp.actions, n, e.actions[n] );
655
+ }
656
+ }
279
657
  [ 'elements', 'actions' ].forEach( (prop) => {
280
- const dict = art._extend && art._extend[prop];
658
+ const dict = extsTmp[prop];
281
659
  for (const name in dict) {
282
660
  let obj = art;
283
661
  if (obj.targetAspect)
@@ -371,129 +749,6 @@ function extend( model ) {
371
749
  return !hasError;
372
750
  }
373
751
 
374
-
375
- /**
376
- * Copy columns for EXTEND PROJECTION
377
- *
378
- * @param {XSN.Extension} ext
379
- * @param {XSN.Artifact} art
380
- */
381
- function extendColumns( ext, art ) {
382
- // TODO: consider reportUnstableExtensions
383
-
384
- const { location } = ext.name;
385
- const { query } = art;
386
- if (!query) {
387
- if (art.kind !== 'annotate')
388
- error( 'extend-columns', [ location, ext ], { art } );
389
- return;
390
- }
391
- if (!query.from || !query.from.path) {
392
- error( 'extend-columns', [ location, ext ], { art } );
393
- }
394
- else {
395
- if (!query.columns)
396
- query.columns = [ { location, val: '*' } ];
397
-
398
- for (const column of ext.columns) {
399
- setLink( column, '_block', ext._block );
400
- query.columns.push(column);
401
- }
402
- }
403
- }
404
-
405
- /**
406
- * Similar to chooseAssignment for annotations, this function applies type extensions in order,
407
- * such that hierarchies are respected.
408
- * Order already set in `extendMembers()` using `compareLayers()`.
409
- *
410
- * @param art
411
- */
412
- function applyTypeExtensions( art ) {
413
- /**
414
- * Contains the previous extension for each property that was applied
415
- * successfully.
416
- */
417
- const previousSuccess = Object.create(null);
418
- const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
419
- const artType = art.type?._artifact;
420
-
421
- for (const ext of art._extendType) {
422
- if (art.$inferred) {
423
- // Only report first extension for $inferred artifact. Reduces noise.
424
- error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
425
- break;
426
- }
427
-
428
- const baseType = art._effectiveType; // may be an ENUM
429
- if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
430
- // Only report first extension for non-scalar base type. Reduces noise.
431
- error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
432
- break;
433
- }
434
- else if (!allowedBuiltinCategories.includes(baseType.category)) {
435
- // Only report first extension for non-scalar type. Reduces noise.
436
- error('ref-expected-scalar-type', [ ext.name.location, ext ],
437
- { '#': 'unsupported', prop: baseType.category });
438
- continue;
439
- }
440
-
441
- for (const prop of typeParameters.list) {
442
- if (!ext[prop])
443
- continue;
444
-
445
- if (!baseType.parameters?.includes(prop)) {
446
- // For `extend T with type (length:10)` where T does not expect a length.
447
- error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
448
- '#': 'type', prop, art, type: baseType,
449
- });
450
- break; // one error for first property is enough
451
- }
452
- else if (!art[prop]) {
453
- // Can only extend properties that exist directly on the artifact.
454
- error('ext-invalid-type-property', [ ext[prop].location, ext ],
455
- { prop, '#': 'new-prop' });
456
- break; // one error for first property is enough
457
- }
458
- else if (art[prop].val === ext[prop].val) {
459
- // Ignore extensions with same value
460
- }
461
- else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
462
- // Users can't change from/to string value for property,
463
- // e.g. `variable`/`floating` for Decimal
464
- error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
465
- { '#': 'string', prop } );
466
- }
467
- else if (art[prop].val > ext[prop].val) {
468
- // TODO: Future: Sub-message that points to previous extension?
469
- error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
470
- prop,
471
- value: ext[prop].val,
472
- number: art[prop].val,
473
- '#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
474
- } );
475
- break; // one error for first property is enough
476
- }
477
- else if (art[prop].val < ext[prop].val) {
478
- art[prop] = ext[prop];
479
- previousSuccess[prop] = ext;
480
- }
481
- }
482
-
483
- // If `scale` is increased, but `precision` is not, data may be lost in a migration.
484
- // e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
485
- // invalid and only allow `1.234`.
486
- // TODO: Should we actually check that the increase is correct?
487
- if (ext.scale && !ext.precision) {
488
- error('ext-invalid-type-property', [ ext.scale.location, ext ], {
489
- prop: 'scale',
490
- otherprop: 'precision',
491
- '#': 'scale',
492
- });
493
- }
494
- }
495
- }
496
-
497
752
  /**
498
753
  * Report 'Warning: Unstable element order due to repeated extensions'
499
754
  * except if all extensions are in the same file.
@@ -535,26 +790,6 @@ function extend( model ) {
535
790
  }
536
791
  }
537
792
 
538
- /**
539
- * Report type extensions in same layer, similar mechanism to chooseAssignment()
540
- * for annotations.
541
- *
542
- * @param {XSN.Extension[]} extensions
543
- */
544
- function reportTypeExtensionsInSameLayer( extensions ) {
545
- // Group assignments by layer
546
- const extLayers = layeredAssignments( extensions );
547
- // We only care about the highest layer
548
- const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
549
-
550
- if (issue) {
551
- const id = (issue === 'unrelated')
552
- ? 'ext-duplicate-extend-type-unrelated-layer'
553
- : 'ext-duplicate-extend-type';
554
- for (const ext of assignments)
555
- message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
556
- }
557
- }
558
793
 
559
794
  /**
560
795
  * @param {XSN.Extension[]} extensions
@@ -582,77 +817,6 @@ function extend( model ) {
582
817
  }
583
818
  }
584
819
 
585
- /**
586
- * @param {Function|false} [veryLate]
587
- */
588
- function lateExtensions( veryLate ) {
589
- for (const name in model.$lateExtensions) {
590
- const art = model.definitions[name];
591
- const exts = model.$lateExtensions[name];
592
- if (art && art.kind !== 'namespace') {
593
- if (art.builtin) {
594
- for (const ext of exts)
595
- info( 'anno-builtin', [ ext.name.location, ext ] );
596
- }
597
- // created texts entity, auto-exposed entity
598
- if (exts) {
599
- extendArtifact( exts, art, 'gen' );
600
- if (veryLate)
601
- veryLate( art );
602
- model.$lateExtensions[name] = null; // done
603
- }
604
- }
605
- else if (veryLate) {
606
- // Complain about unused extensions, i.e. those
607
- // which do not point to a valid artifact
608
- for (const ext of exts) {
609
- delete ext.name.path[0]._artifact; // get message for root
610
- // TODO: make resolvePath('extend'/'annotate') ignore namespaces
611
- // Don't try to apply annotations in the `localized.` namespace.
612
- // That's done in `localized.js`.
613
- if (!name.startsWith('localized.') &&
614
- resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
615
- // should issue error for cds extensions (annotate ok)
616
- if (art.kind === 'namespace') {
617
- // TODO: Emit error if namespace is extended by non-definitions.
618
- info( 'anno-namespace', [ ext.name.location, ext ], {},
619
- 'Namespaces can\'t be annotated' );
620
- }
621
- // Builtin annotations would be represented as annotations in to-csn.js
622
- else if (art.builtin) {
623
- info( 'anno-builtin', [ ext.name.location, ext ] );
624
- }
625
- }
626
- // TODO: warning for context/service extension on non-correct
627
- if (ext.kind === 'annotate')
628
- delete ext.name._artifact; // make it be considered by extendArtifact()
629
- }
630
- // create "super" ANNOTATE containing all non-applied ones
631
- const first = exts[0];
632
- const { location } = first.name;
633
-
634
- /** @type {XSN.Definition} */
635
- const annotationArtifact = {
636
- kind: 'annotate',
637
- name: { path: [ { id: name, location } ], absolute: name, location },
638
- location: first.location,
639
- };
640
-
641
- if (!model.extensions)
642
- model.extensions = [];
643
-
644
- model.extensions.push(annotationArtifact);
645
- extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
646
- // if one of the annotate statement mentions 'returns', assume it
647
- // TODO: with warning/info?
648
- for (const ext of exts) {
649
- if (ext.$syntax === 'returns')
650
- annotationArtifact.$syntax = 'returns';
651
- }
652
- }
653
- }
654
- }
655
-
656
820
  // includes ----------------------------------------------------------------
657
821
 
658
822
  /**
@@ -665,14 +829,26 @@ function extend( model ) {
665
829
  * @param {XSN.Artifact} target
666
830
  * @returns {boolean}
667
831
  */
668
- function canApplyIncludes( art, target ) {
669
- if (art.includes) {
670
- const isView = !!target.query;
671
- for (const ref of art.includes) {
672
- const template = resolvePath( ref, isView ? 'viewInclude' : 'include', art );
673
- if (template && template.name.absolute in extensionsDict)
674
- return false;
832
+ function canApplyIncludes( art, target, justResolveCyclic ) {
833
+ if (!art.includes)
834
+ return true;
835
+ const isView = !!target.query;
836
+ for (const ref of art.includes) {
837
+ const name = resolveUncheckedPath( ref, 'include', art );
838
+ // console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
839
+ if (justResolveCyclic) {
840
+ if (!justResolveCyclic.includes( name ))
841
+ continue;
842
+ delete ref._artifact;
675
843
  }
844
+ else if (name && name in extensionsDict) {
845
+ // one of the includes has itself extensions that need to be applied first
846
+ return false;
847
+ }
848
+ else if (ref._artifact) {
849
+ delete ref._artifact;
850
+ }
851
+ resolvePath( ref, isView ? 'viewInclude' : 'include', art );
676
852
  }
677
853
  return true;
678
854
  }
@@ -710,7 +886,8 @@ function extend( model ) {
710
886
  }
711
887
  }
712
888
  }
713
- includeMembers( ext, art, 'elements' );
889
+ if (!art.query) // do not set art.elements and art.enums with query entity!
890
+ includeMembers( ext, art, 'elements' );
714
891
  includeMembers( ext, art, 'actions' );
715
892
  }
716
893
 
@@ -743,10 +920,18 @@ function extend( model ) {
743
920
  elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
744
921
  if (origin.key)
745
922
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
923
+ if (origin.value && origin.$syntax === 'calc') {
924
+ // TODO: If paths become invalid in the new artifact, should we mark
925
+ // all usages in the expressions? Possibly just the first one?
926
+ elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
927
+ elem.$syntax = 'calc';
928
+ setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
929
+ }
746
930
  // TODO: also complain if elem is just defined in art
747
931
  });
748
932
  }
749
933
  }
934
+ checkRedefinitionThroughIncludes( parent, prop );
750
935
  // TODO: expand elements having direct elements (if needed)
751
936
  if (members) {
752
937
  forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
@@ -755,6 +940,31 @@ function extend( model ) {
755
940
  }
756
941
  }
757
942
 
943
+ /**
944
+ * Report duplicates in parent[prop] that happen due to multiple includes having the
945
+ * same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
946
+ *
947
+ * TODO(v4): Make this a hard error; see checkRedefinition(); maybe combine both;
948
+ */
949
+ function checkRedefinitionThroughIncludes( parent, prop ) {
950
+ if (!parent[prop])
951
+ return;
952
+ forEachInOrder(parent, prop, ( member, name ) => {
953
+ if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
954
+ const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
955
+ if (isBetaEnabled(options, 'v4preview')) {
956
+ error( 'duplicate-definition', [ parent.name.location, member ],
957
+ { '#': `include-${ prop }`, name, sorted_arts: includes } );
958
+ }
959
+ else {
960
+ // Error accidentally removed in v2/v3, therefore only a warning.
961
+ warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
962
+ { '#': prop, name, sorted_arts: includes } );
963
+ }
964
+ }
965
+ });
966
+ }
967
+
758
968
  // localized texts entities
759
969
 
760
970
  /**
@@ -881,6 +1091,7 @@ function extend( model ) {
881
1091
  const art = useTextsAspect
882
1092
  ? createTextsEntityWithInclude( base, absolute, fioriEnabled )
883
1093
  : createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
1094
+ // both functions are rather similar...
884
1095
 
885
1096
  const { location } = base.name;
886
1097
 
@@ -916,7 +1127,7 @@ function extend( model ) {
916
1127
  elem.key = { val: true, $inferred: 'localized', location };
917
1128
  // If the propagated elements remain key (that is not fiori.draft.enabled)
918
1129
  // they should be omitted from OData containment EDM
919
- annotateWith( elem, '@odata.containment.ignore', location );
1130
+ setAnnotation( elem, '@odata.containment.ignore', location );
920
1131
  }
921
1132
  else {
922
1133
  // add the former key paths to the unique constraint
@@ -953,10 +1164,10 @@ function extend( model ) {
953
1164
  path: [ { id: locale.name.id, location: locale.location } ],
954
1165
  location: locale.location,
955
1166
  });
956
- annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
1167
+ setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
957
1168
  }
958
1169
 
959
- copyPersistenceAnnotations(art, base, options);
1170
+ copyPersistenceAnnotations( art, base );
960
1171
  return art;
961
1172
  }
962
1173
 
@@ -988,7 +1199,7 @@ function extend( model ) {
988
1199
  if (!fioriEnabled) {
989
1200
  // To be compatible, we switch off draft without @fiori.draft.enabled
990
1201
  // TODO (next major version): remove?
991
- annotateWith( art, '@odata.draft.enabled', art.location, false );
1202
+ setAnnotation( art, '@odata.draft.enabled', art.location, false );
992
1203
  }
993
1204
  else {
994
1205
  // @fiori.draft.enabled artifacts need default elements ID_texts and locale.
@@ -1009,9 +1220,11 @@ function extend( model ) {
1009
1220
 
1010
1221
  if (addTextsLanguageAssoc && art.elements.language)
1011
1222
  art.elements.language = undefined; // TODO: Message? Ignore?
1223
+ // TODO: what is this necessary? We do not create a text entity in this case
1012
1224
 
1013
1225
  setLink( art, '_block', model.$internal );
1014
1226
  model.definitions[absolute] = art;
1227
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1015
1228
  return art;
1016
1229
  }
1017
1230
 
@@ -1046,7 +1259,7 @@ function extend( model ) {
1046
1259
  locale.key = { val: true, location };
1047
1260
  // To be compatible, we switch off draft without @fiori.draft.enabled
1048
1261
  // TODO (next major version): remove?
1049
- annotateWith( art, '@odata.draft.enabled', art.location, false );
1262
+ setAnnotation( art, '@odata.draft.enabled', art.location, false );
1050
1263
  }
1051
1264
  else {
1052
1265
  const textId = {
@@ -1058,11 +1271,11 @@ function extend( model ) {
1058
1271
  };
1059
1272
  dictAdd( art.elements, 'ID_texts', textId );
1060
1273
  }
1061
-
1062
1274
  dictAdd( art.elements, 'locale', locale );
1275
+
1063
1276
  setLink( art, '_block', model.$internal );
1064
1277
  model.definitions[absolute] = art;
1065
-
1278
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1066
1279
  return art;
1067
1280
  }
1068
1281
 
@@ -1346,9 +1559,9 @@ function extend( model ) {
1346
1559
  model.definitions[entityName] = art;
1347
1560
  initArtifact( art );
1348
1561
 
1562
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1349
1563
  // Copy persistence annotations from aspect.
1350
- copyPersistenceAnnotations(art, target, options);
1351
-
1564
+ copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
1352
1565
  return art;
1353
1566
  }
1354
1567
 
@@ -1364,57 +1577,361 @@ function extend( model ) {
1364
1577
  if (origin.key)
1365
1578
  proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
1366
1579
  if (anno)
1367
- annotateWith( proxy, anno );
1580
+ setAnnotation( proxy, anno );
1368
1581
  dictAdd( proxyDict.elements, pname, proxy );
1369
1582
  }
1370
1583
  }
1584
+
1585
+
1586
+ // Phase 4 - annotations ---------------------------------------------------
1587
+ // move to top
1588
+
1589
+ /**
1590
+ * Goes through all (applied) annotations in the given artifact and chooses one
1591
+ * if multiple exist according to the module layer.
1592
+ * TODO: rename to extendArtifactBefore
1593
+ *
1594
+ * @param {XSN.Artifact} art
1595
+ */
1596
+ function chooseAnnotationsInArtifact( art ) {
1597
+ // for main artifacts, move extensions from `$lateExtensions` model dictionary:
1598
+ if (!art._main && !art._outer && art._extensions === undefined &&
1599
+ art.kind !== 'namespace') {
1600
+ // if (!art.name) console.log(art)
1601
+ const { absolute } = art.name;
1602
+ setLink( art, '_extensions', model.$lateExtensions[absolute]?._extensions || null );
1603
+ if (art._extensions && !art.builtin) { // keep extensions for builtin in $lateExtensions
1604
+ delete model.$lateExtensions[absolute];
1605
+ // TODO: if the extension mechanism has been completed, we could uncomment:
1606
+ // art._extensions.forEach( ext => resolvePath( ext.name, ext.kind, ext )); // for LSP
1607
+ // (if we do it now, the old extend functionality might think “already applied”)
1608
+ }
1609
+ }
1610
+ if (art._extensions) {
1611
+ // TODO: the following function can now be simplified
1612
+ // if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
1613
+ // With extensions, member appears in CSN, affects directly the rendering of
1614
+ // elements etc. TODO: do that more specifically on the dicts (via symbol)
1615
+ // Probably better: we could use the _extensions dict prop directly in to-csn
1616
+ if (art.$inferred)
1617
+ setExpandStatusAnnotate( art, 'annotate' );
1618
+ if (Array.isArray( art._extensions )) {
1619
+ checkExtensionsKind( art._extensions, art ); // TODO: check with builtins
1620
+ transformArtifactExtensions( art );
1621
+ }
1622
+ applyAllExtensions( art );
1623
+ }
1624
+ }
1625
+
1626
+ // TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
1627
+ function transformArtifactExtensions( art ) {
1628
+ const hasOnlySubExtensions = art._outer; // items, anonymous aspects
1629
+ const dict = Object.create(null);
1630
+ for (const ext of art._extensions) {
1631
+ for (const prop in ext) {
1632
+ if (ext[prop] === undefined) // deleted propery
1633
+ continue;
1634
+ // TODO: do this check nicer (after complete move to new extensions mechanism)
1635
+ if (prop.charAt(0) === '@' || prop === 'doc' ||
1636
+ prop === 'includes' || prop === 'columns' ||
1637
+ prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
1638
+ if (!hasOnlySubExtensions)
1639
+ pushToDict( dict, prop, ext );
1640
+ }
1641
+ else if (prop === 'elements' || prop === 'enum' || prop === 'actions' ||
1642
+ prop === 'params' || prop === 'returns') {
1643
+ if (ext.kind === 'extend')
1644
+ pushToDict( dict, 'includes', ext );
1645
+ pushToDict( dict, prop, ext );
1646
+ }
1647
+ }
1648
+ }
1649
+ art._extensions = dict;
1650
+ }
1651
+
1652
+ function applyAllExtensions( art ) {
1653
+ const extensions = art._extensions;
1654
+ for (const prop in extensions) {
1655
+ // TODO: do the following `if` in a nicer way
1656
+ if ([ 'elements', 'enum', 'actions', 'params', 'returns' ].includes( prop ))
1657
+ continue; // currently just annotates on sub elements - TODO: error here
1658
+ // annotations, `doc`, `includes`, `columns`, `length`, ...
1659
+ const scheduled = [];
1660
+ // sort extensions according to layer (specified elements are bottom layer):
1661
+ const layered = layeredExtensions( extensions[prop] );
1662
+
1663
+ let cont = true;
1664
+ while (cont) {
1665
+ const { highest, issue } = extensionsOfHighestLayers( layered );
1666
+ // console.log( 'CA:', annoName, issue, extensions)
1667
+ let index = highest.length;
1668
+ cont = !!index; // safety
1669
+ while (--index >= 0) {
1670
+ const ext = highest[index];
1671
+ scheduled.push( ext );
1672
+ if (extensionOverwrites( ext, prop )) {
1673
+ cont = false;
1674
+ break;
1675
+ }
1676
+ }
1677
+ if (issue || index > 0)
1678
+ reportDuplicateExtensions( highest, prop, issue, index, art );
1679
+ }
1680
+ // Now apply the relevant extensions
1681
+ scheduled.reverse();
1682
+ for (const ext of scheduled)
1683
+ applySingleExtension( art, ext, prop );
1684
+ delete extensions[prop];
1685
+ }
1686
+ }
1687
+
1688
+ function extensionOverwrites( ext, prop ) {
1689
+ return (prop.charAt(0) !== '@')
1690
+ ? [ 'doc', 'length', 'precision', 'scale', 'srid' ].includes( prop )
1691
+ : !annotationHasEllipsis( ext[prop] );
1692
+ }
1693
+
1694
+ // TODO: still a bit annotation assignment specific
1695
+ function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
1696
+ // TODO: think about messages for these
1697
+ if (prop === 'elements' || prop === 'enum' || prop === 'actions' || prop === 'columns' ||
1698
+ prop === 'params' || prop === 'returns' || prop === 'includes' )
1699
+ return; // extensions currently handled extra
1700
+ if (issue) {
1701
+ // eslint-disable-next-line no-nested-ternary
1702
+ let msg = (index < 0)
1703
+ ? 'anno-unstable-array'
1704
+ : (issue === true)
1705
+ ? 'anno-duplicate'
1706
+ : 'anno-duplicate-unrelated-layer';
1707
+ if (prop.charAt(0) !== '@' && prop !== 'doc') {
1708
+ msg = (issue === true)
1709
+ ? 'ext-duplicate-extend-type'
1710
+ : 'ext-duplicate-extend-type-unrelated-layer';
1711
+ // not sure whether to repeat the extended artifact in the message (we
1712
+ // have the semantic location, after all)
1713
+ }
1714
+ const variant = prop === 'doc' ? 'doc' : 'std';
1715
+ for (const ext of extensions) {
1716
+ const anno = ext[prop];
1717
+ if (anno && !anno.$errorReported) {
1718
+ message( msg, [ anno.name?.location || anno.location, ext ],
1719
+ { '#': variant, anno: prop, type: art } );
1720
+ }
1721
+ }
1722
+ }
1723
+ else if (index > 0) { // more than one set (not just ...)
1724
+ const variant = prop === 'doc' ? 'doc' : 'std';
1725
+ const msgid = (prop.charAt(0) === '@' || prop === 'doc')
1726
+ ? 'anno-duplicate-same-file' // TODO: always ext-duplicate-…
1727
+ : 'ext-duplicate-same-file';
1728
+ while (index >= 0) { // do not report for trailing [...]
1729
+ const ext = extensions[index--];
1730
+ const anno = ext[prop];
1731
+ warning( msgid, [ anno.name?.location || anno.location, ext ],
1732
+ { '#': variant, prop, anno: prop } );
1733
+ }
1734
+ }
1735
+ }
1736
+
1737
+ function applySingleExtension( art, ext, prop ) {
1738
+ if (prop === 'includes') {
1739
+ if (ext.kind === 'extend' && art.$inferred) {
1740
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art },
1741
+ 'You can\'t use EXTEND on the generated $(ART)' );
1742
+ }
1743
+ else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
1744
+ const { absolute } = art.name;
1745
+ const dict = extensionsDict[absolute] || (extensionsDict[absolute] = []);
1746
+ dict.push( ext ); // TODO: change
1747
+ // console.log( 'ASI:',prop,art.name,ext,extensionsDict[absolute])
1748
+ }
1749
+ // art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
1750
+ }
1751
+ else if (prop === 'columns') {
1752
+ const { query } = art;
1753
+ if (!query?.from?.path)
1754
+ error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
1755
+ else if (!query.columns)
1756
+ query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
1757
+ else
1758
+ query.columns.push( ...ext.columns );
1759
+ }
1760
+ else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
1761
+ const typeExts = art.$typeExts || (art.$typeExts = {});
1762
+ typeExts[prop] = ext;
1763
+ }
1764
+ else {
1765
+ const result = applyAssignment( art[prop], ext[prop], ext, prop );
1766
+ art[prop] = (result.name) ? result : Object.assign( {}, art[prop], result );
1767
+ }
1768
+ }
1769
+
1770
+ function applyAssignment( previousAnno, anno, art, annoName ) {
1771
+ const firstEllipsis = annotationHasEllipsis( anno );
1772
+ if (!firstEllipsis)
1773
+ return anno;
1774
+ const hasBase = previousAnno?.literal === 'array';
1775
+ if (!previousAnno) {
1776
+ const loc = firstEllipsis.location || anno.name.location;
1777
+ message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
1778
+ previousAnno = { val: [] };
1779
+ }
1780
+ else if (previousAnno.literal !== 'array') {
1781
+ // TODO: If we introduce sub-messages, point to the non-array base value.
1782
+ error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
1783
+ previousAnno = { val: [] };
1784
+ }
1785
+ const previousValue = previousAnno.val;
1786
+ let prevPos = 0;
1787
+ const result = [];
1788
+ for (const item of anno.val) {
1789
+ const ell = item && item.literal === 'token' && item.val === '...';
1790
+ if (!ell) {
1791
+ result.push( item );
1792
+ }
1793
+ else {
1794
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
1795
+ while (prevPos < previousValue.length) {
1796
+ const prevItem = previousValue[prevPos++];
1797
+ result.push( prevItem );
1798
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
1799
+ upToSpec = false;
1800
+ break;
1801
+ }
1802
+ }
1803
+ if (upToSpec && hasBase) {
1804
+ // non-matched UP TO; if there is no base to apply to, there is already an error.
1805
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
1806
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
1807
+ }
1808
+ }
1809
+ }
1810
+ // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
1811
+ return { val: result, literal: 'array' };
1812
+ }
1813
+ // function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
1814
+
1815
+ function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
1816
+ const { literal } = upToSpec;
1817
+ if (!isFullUpTo) { // inside struct of UP TO
1818
+ if (literal !== 'struct' && literal !== 'array' )
1819
+ return true;
1820
+ }
1821
+ else if (literal === 'struct') {
1822
+ return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
1823
+ }
1824
+ else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
1825
+ return true;
1826
+ }
1827
+ error( null, [ upToSpec.location, art ],
1828
+ { anno: annoName, code: '... up to', '#': literal },
1829
+ {
1830
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
1831
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
1832
+ // eslint-disable-next-line max-len
1833
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
1834
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
1835
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
1836
+ } );
1837
+ return false;
1838
+ }
1839
+
1840
+ function equalUpTo( previousItem, upToSpec ) {
1841
+ if (!previousItem)
1842
+ return false;
1843
+ if ('val' in upToSpec) {
1844
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
1845
+ return true;
1846
+ const typeUpTo = typeof upToSpec.val;
1847
+ const typePrev = typeof previousItem.val;
1848
+ if (typeUpTo === 'number')
1849
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
1850
+ if (typePrev === 'number')
1851
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
1852
+ }
1853
+ else if (upToSpec.path) {
1854
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
1855
+ }
1856
+ else if (upToSpec.sym) {
1857
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
1858
+ }
1859
+ else if (upToSpec.struct && previousItem.struct) {
1860
+ return Object.entries( upToSpec.struct )
1861
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
1862
+ }
1863
+ return false;
1864
+ }
1865
+
1866
+ function normalizeRef( node ) { // see to-csn.js
1867
+ const ref = pathName( node.path );
1868
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
1869
+ }
1870
+
1871
+ /**
1872
+ * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1873
+ * source to target if present on source but not target.
1874
+ *
1875
+ * @param {object} target
1876
+ * @param {object} source
1877
+ */
1878
+ function copyPersistenceAnnotations( target, source ) {
1879
+ if (!source)
1880
+ return;
1881
+
1882
+ const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1883
+ if (copyExists)
1884
+ copy( '@cds.persistence.exists' );
1885
+ copy( '@cds.persistence.skip' );
1886
+
1887
+ function copy( anno ) {
1888
+ if ( source[anno] && !target[anno] )
1889
+ target[anno] = { ...source[anno], $inferred: 'parent-origin' };
1890
+ }
1891
+ }
1371
1892
  }
1372
1893
 
1373
1894
  /**
1374
- * Group assignments by their layers. An assignment provided with a definition
1895
+ * Group extensions by their layers. A definition (for specified elements)
1375
1896
  * is considered to be provided in a layer named '', the lowest layer.
1376
1897
  *
1377
- * TODO: make this usable for extend (elements), too =
1378
- * do not use $priority, make assignments on define do not have own _block
1379
- *
1380
- * @param {object[]} assignments Array of assignments, e.g. extensions.
1381
- * @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
1898
+ * @param {object[]} extensions Array of extensions.
1899
+ * @returns {Record<string, object>} key: layer name, value: {name, layer, extensions[]}`
1382
1900
  */
1383
- function layeredAssignments( assignments ) {
1901
+ function layeredExtensions( extensions ) {
1384
1902
  const layered = Object.create(null);
1385
- for (const a of assignments) {
1386
- const layer = a.$priority !== false && layers.layer( a );
1903
+ for (const ext of extensions) {
1904
+ const layer = (ext.kind === 'annotate' || ext.kind === 'extend') && layers.layer( ext );
1387
1905
  // just consider layer if Extend/Annotate, not Define
1388
1906
  const name = (layer) ? layer.realname : '';
1389
1907
  const done = layered[name];
1390
1908
  if (done)
1391
- done.assignments.push( a );
1909
+ done.extensions.push( ext );
1392
1910
  else
1393
- layered[name] = { name, layer, assignments: [ a ] };
1394
- // TODO: file - if set: unique in layer
1911
+ layered[name] = { name, layer, extensions: [ ext ] };
1395
1912
  }
1396
1913
  return layered;
1397
1914
  }
1398
1915
 
1399
1916
  /**
1400
- * Return assignments of the highest layers.
1917
+ * Return extensions of the highest layers.
1401
1918
  * Also returns whether there could be an issue:
1402
- * - false: there is just one assignment
1403
- * - 'unrelated': there is just one assignment per layer
1404
- * - true: there is at least one layer with two or more assignments
1405
- * TODO: make this usable for extend (elements), too.
1919
+ * - false: there are just extensions in one file,
1920
+ * - 'unrelated': there is just one extension per layer
1921
+ * - true: there is at least one layer with two or more extensions, and
1922
+ * at least two files are involved
1406
1923
  *
1407
- * @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
1924
+ * @param {Record<string, object>} layered Structure as returned by layeredExtensions()
1408
1925
  * @returns {{assignments, issue: boolean|string}}
1409
1926
  */
1410
- function assignmentsOfHighestLayers( layeredAnnos ) {
1411
- const layerNames = Object.keys( layeredAnnos );
1927
+ function extensionsOfHighestLayers( layered ) {
1928
+ const layerNames = Object.keys( layered );
1412
1929
  // console.log('HIB:',layerNames)
1413
1930
  if (layerNames.length <= 1) {
1414
1931
  const name = layerNames[0];
1415
- const { assignments } = layeredAnnos[name] || { assignments: [] };
1416
- delete layeredAnnos[name];
1417
- return { assignments, issue: assignments.length > 1 };
1932
+ const highest = layered[name]?.extensions || [];
1933
+ delete layered[name];
1934
+ return { highest, issue: inMoreThanOneFile( highest ) };
1418
1935
  }
1419
1936
 
1420
1937
  // collect all layers which are lower than another layer
@@ -1422,28 +1939,41 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
1422
1939
  allExtends[''] = {}; // the "Define" layer
1423
1940
  for (const name of layerNames) {
1424
1941
  if (name) // not the "Define" layer
1425
- Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
1942
+ Object.assign( allExtends, layered[name].layer._layerExtends );
1426
1943
  }
1427
1944
  // console.log('HIE:',Object.keys(allExtends))
1428
- const assignments = [];
1429
- const highest = [];
1945
+ const highest = []; // extensions
1946
+ const highestLayers = [];
1430
1947
  for (const name of layerNames) {
1431
1948
  if (!(name in allExtends)) {
1432
- const layer = layeredAnnos[name];
1433
- delete layeredAnnos[name];
1434
- highest.push( layer );
1435
- assignments.push( ...layer.assignments );
1949
+ const layer = layered[name];
1950
+ delete layered[name];
1951
+ highestLayers.push( layer );
1952
+ highest.push( ...layer.extensions );
1436
1953
  }
1437
1954
  }
1438
- assignments.sort( compareAssignments );
1439
- const good = highest.every( layer => layer.assignments.length === 1 );
1955
+ highest.sort( compareExtensions );
1956
+ const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ));
1440
1957
  // TODO: use layer.file instead
1441
- const issue = !good || highest.length > 1 && 'unrelated';
1442
- // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
1443
- return { assignments, issue };
1958
+ const issue = !good || highestLayers.length > 1 && 'unrelated';
1959
+ // console.log('HI:',highest.map(l=>l.name),issue,issue&&extensions)
1960
+ return { highest, issue };
1444
1961
  }
1445
1962
 
1446
- function compareAssignments( a, b ) {
1963
+ function inMoreThanOneFile( extensions ) {
1964
+ if (extensions.length <= 1)
1965
+ return false;
1966
+ const file = extensions[0].location?.file;
1967
+ return !file || extensions.slice(1).some( e => e.location?.file !== file );
1968
+ }
1969
+
1970
+ /**
1971
+ * Compare two extensions which are not comparable via layering:
1972
+ * - via the fs.realpath of the file (not layer!) of the extensions, then
1973
+ * - via the line, then column of the extensions.
1974
+ * Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
1975
+ */
1976
+ function compareExtensions( a, b ) {
1447
1977
  const fileA = layers.realname( a._block );
1448
1978
  const fileB = layers.realname( b._block );
1449
1979
  if (fileA !== fileB)
@@ -1452,29 +1982,6 @@ function compareAssignments( a, b ) {
1452
1982
  (a?.location?.col || 0) - (b?.location?.col || 0);
1453
1983
  }
1454
1984
 
1455
- /**
1456
- * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1457
- * source to target if present on source but not target.
1458
- *
1459
- * @param {object} target
1460
- * @param {object} source
1461
- * @param {CSN.Options} options
1462
- */
1463
- function copyPersistenceAnnotations( target, source, options ) {
1464
- if (!source)
1465
- return;
1466
-
1467
- const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1468
- if (copyExists)
1469
- copy( '@cds.persistence.exists' );
1470
- copy( '@cds.persistence.skip' );
1471
-
1472
- function copy( anno ) {
1473
- if ( source[anno] && !target[anno] )
1474
- target[anno] = { ...source[anno], $inferred: 'parent-origin' };
1475
- }
1476
- }
1477
-
1478
1985
  function augmentEqual( location, assocname, relations, prefix = '' ) {
1479
1986
  const args = relations.map( eq );
1480
1987
  return (args.length === 1)
@@ -1508,15 +2015,14 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
1508
2015
  * @param {XSN.Extension} ext
1509
2016
  * @param {object} art
1510
2017
  */
1511
- function storeTypeExtension( ext, art ) {
1512
- // If there are no parameters to apply, don't store the extension.
1513
- if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
1514
- return;
1515
- else if (!art._extendType)
1516
- setLink( art, '_extendType', [] );
1517
- art._extendType.push( ext );
1518
- }
1519
-
2018
+ // function storeTypeExtension( ext, art ) {
2019
+ // // If there are no parameters to apply, don't store the extension.
2020
+ // if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
2021
+ // return;
2022
+ // else if (!art._extendType)
2023
+ // setLink( art, '_extendType', [] );
2024
+ // art._extendType.push( ext );
2025
+ // }
1520
2026
 
1521
2027
  function checkTextsLanguageAssocOption( model, options ) {
1522
2028
  const languages = model.definitions['sap.common.Languages'];