@sap/cds-compiler 3.7.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 (70) hide show
  1. package/CHANGELOG.md +63 -4
  2. package/bin/cdsc.js +3 -0
  3. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  4. package/doc/CHANGELOG_BETA.md +15 -0
  5. package/doc/DeprecatedOptions_v2.md +1 -1
  6. package/doc/NameResolution.md +1 -1
  7. package/lib/api/main.js +61 -22
  8. package/lib/api/options.js +1 -0
  9. package/lib/api/validate.js +5 -0
  10. package/lib/base/dictionaries.js +5 -3
  11. package/lib/base/keywords.js +2 -0
  12. package/lib/base/message-registry.js +64 -22
  13. package/lib/base/messages.js +12 -7
  14. package/lib/base/model.js +3 -2
  15. package/lib/checks/arrayOfs.js +1 -1
  16. package/lib/checks/defaultValues.js +1 -1
  17. package/lib/checks/hasPersistedElements.js +1 -1
  18. package/lib/checks/invalidTarget.js +1 -1
  19. package/lib/checks/onConditions.js +9 -6
  20. package/lib/checks/sql-snippets.js +2 -2
  21. package/lib/checks/types.js +1 -2
  22. package/lib/compiler/assert-consistency.js +24 -5
  23. package/lib/compiler/base.js +49 -2
  24. package/lib/compiler/builtins.js +15 -6
  25. package/lib/compiler/checks.js +4 -4
  26. package/lib/compiler/define.js +59 -80
  27. package/lib/compiler/extend.js +701 -498
  28. package/lib/compiler/finalize-parse-cdl.js +4 -3
  29. package/lib/compiler/index.js +1 -1
  30. package/lib/compiler/kick-start.js +2 -2
  31. package/lib/compiler/populate.js +17 -9
  32. package/lib/compiler/propagator.js +12 -5
  33. package/lib/compiler/resolve.js +26 -173
  34. package/lib/compiler/shared.js +12 -53
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +2 -2
  37. package/lib/edm/annotations/genericTranslation.js +124 -46
  38. package/lib/edm/csn2edm.js +22 -1
  39. package/lib/edm/edmPreprocessor.js +41 -21
  40. package/lib/gen/Dictionary.json +4 -0
  41. package/lib/gen/language.checksum +1 -1
  42. package/lib/gen/language.interp +3 -1
  43. package/lib/gen/languageLexer.js +1 -1
  44. package/lib/gen/languageParser.js +4810 -4482
  45. package/lib/inspect/inspectPropagation.js +20 -36
  46. package/lib/json/from-csn.js +55 -5
  47. package/lib/json/to-csn.js +71 -110
  48. package/lib/language/errorStrategy.js +1 -0
  49. package/lib/language/genericAntlrParser.js +47 -8
  50. package/lib/language/language.g4 +88 -62
  51. package/lib/language/textUtils.js +13 -0
  52. package/lib/main.d.ts +43 -3
  53. package/lib/main.js +4 -2
  54. package/lib/model/csnRefs.js +14 -2
  55. package/lib/model/csnUtils.js +11 -74
  56. package/lib/model/revealInternalProperties.js +3 -0
  57. package/lib/optionProcessor.js +3 -0
  58. package/lib/render/toCdl.js +203 -104
  59. package/lib/render/toHdbcds.js +0 -1
  60. package/lib/render/toRename.js +14 -51
  61. package/lib/transform/braceExpression.js +6 -0
  62. package/lib/transform/db/rewriteCalculatedElements.js +55 -14
  63. package/lib/transform/forOdataNew.js +20 -15
  64. package/lib/transform/forRelationalDB.js +21 -14
  65. package/lib/transform/parseExpr.js +2 -0
  66. package/lib/transform/transformUtilsNew.js +36 -9
  67. package/lib/transform/translateAssocsToJoins.js +11 -4
  68. package/lib/transform/universalCsn/coreComputed.js +15 -7
  69. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  70. package/package.json +2 -1
@@ -9,14 +9,17 @@ 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, dictAddArray } = 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
20
  copyExpr,
19
- annotateWith,
21
+ setAnnotation,
22
+ setExpandStatusAnnotate,
20
23
  linkToOrigin,
21
24
  setMemberParent,
22
25
  dependsOnSilent,
@@ -27,8 +30,10 @@ const {
27
30
  annotationHasEllipsis,
28
31
  } = require('./utils');
29
32
  const layers = require('./moduleLayers');
30
- const { typeParameters } = require('./builtins');
31
- const { CompilerAssertion } = require('../base/error');
33
+
34
+ const $location = Symbol.for('cds.$location');
35
+
36
+ const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
32
37
 
33
38
  function extend( model ) {
34
39
  const { options } = model;
@@ -39,23 +44,22 @@ function extend( model ) {
39
44
  const {
40
45
  resolvePath,
41
46
  resolveUncheckedPath,
42
- initAnnotations,
43
- checkAnnotate,
47
+ resolveTypeArgumentsUnchecked,
44
48
  attachAndEmitValidNames,
45
- checkDefinitions,
46
49
  initArtifact,
47
50
  initMembers,
48
- extensionsDict, // not a function - TODO
49
51
  } = model.$functions;
50
52
 
51
53
  Object.assign( model.$functions, {
52
54
  lateExtensions,
53
- applyTypeExtensions,
54
55
  chooseAnnotationsInArtifact,
55
- extensionFor,
56
- copyAnnotationsForExtensions,
56
+ extendArtifactAfter,
57
57
  } );
58
58
 
59
+ const extensionsDict = Object.create(null);
60
+ forEachDefinition( model, tagIncludes ); // TODO TMP
61
+
62
+ forEachDefinition( model, chooseAnnotationsInArtifact );
59
63
  applyExtensions();
60
64
 
61
65
  const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
@@ -63,9 +67,8 @@ function extend( model ) {
63
67
 
64
68
  Object.keys( model.definitions ).forEach( processArtifact );
65
69
 
66
- lateExtensions( false );
67
-
68
70
  compositionChildPersistence();
71
+ return;
69
72
 
70
73
  /**
71
74
  * Process "composition of" artifacts.
@@ -115,90 +118,442 @@ function extend( model ) {
115
118
  if (def.$inferred === 'composition-entity' && !processed.has(def)) {
116
119
  if (def._parent)
117
120
  processCompositionPersistence(def._parent);
118
- copyPersistenceAnnotations(def, def._parent, options);
121
+ copyPersistenceAnnotations( def, def._parent );
119
122
  processed.add(def);
120
123
  }
121
124
  }
122
125
  }
123
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
+
124
507
  // extend ------------------------------------------------------------------
125
508
 
126
509
  /**
127
510
  * Apply the extensions inside the extensionsDict on the model.
128
511
  *
129
- * Phase 1: context extends, 2: extends with structure includes, 3: extends
130
- * without structure includes (in the case of cyclic includes)
131
- *
132
- * Before phase 1: all artifact extensions have been collected (even those
133
- * inside extend context), only "empty" ones from structure includes are still unknown.
134
- * 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.
135
514
  */
136
515
  function applyExtensions() {
137
- let phase = 1; // TODO: basically remove phase 1
516
+ let noIncludes = false;
138
517
  let extNames = Object.keys( extensionsDict ).sort();
139
- // Remark: The sort() makes sure that an extend for artifact C.E is applied
140
- // after the extend for C has been applied (which could have defined C.E).
141
- // Looping over model.definitions in Phase 1 would miss the `extend
142
- // context` for a context C.C defined in an `extend context C`.
143
- //
144
- // TODO: no need to sort anymore
518
+
145
519
  while (extNames.length) {
146
520
  const { length } = extNames;
147
521
  for (const name of extNames) {
148
522
  const art = model.definitions[name];
149
- if (!art || art.kind === 'namespace') {
150
- model.$lateExtensions[name] = extensionsDict[name];
151
- delete extensionsDict[name];
152
- }
153
- else if (art.$duplicates) { // cannot extend redefinitions
523
+ if (art && art.kind !== 'namespace' &&
524
+ extendArtifact( extensionsDict[name], art, noIncludes ))
154
525
  delete extensionsDict[name];
155
- }
156
- else if (phase === 1
157
- ? extendContext( name, art )
158
- : extendArtifact( extensionsDict[name], art, Array.isArray( phase ) && phase )) {
159
- delete extensionsDict[name];
160
- }
161
526
  }
162
527
  extNames = Object.keys( extensionsDict ); // no sort() required anymore
163
- if (phase === 1)
164
- phase = 2;
165
- else if (extNames.length >= length)
166
- phase = Object.keys( extensionsDict ); // = no includes
528
+ if (extNames.length >= length)
529
+ noIncludes = Object.keys( extensionsDict ); // = no includes
167
530
  }
168
531
  }
169
532
 
170
- function extendContext( name, art ) {
171
- // TODO: remove this function - add remains to define.js
172
- // (ext.expectedKind == art.kind) already checked by parser except for context/service
173
- if (!kindProperties[art.kind].artifacts) {
174
- // no context or service => warn about context extensions
175
- for (const ext of extensionsDict[name]) {
176
- if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
177
- const loc = ext.name.location;
178
- // TODO: warning is enough
179
- error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
180
- std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
181
- service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
182
- 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',
183
552
  });
184
553
  }
554
+ // TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
185
555
  }
186
- return false;
187
556
  }
188
-
189
- for (const ext of extensionsDict[name]) {
190
- setArtifactLink( ext.name, art );
191
- checkDefinitions( ext, art, 'elements'); // error for elements etc
192
- checkDefinitions( ext, art, 'enum');
193
- checkDefinitions( ext, art, 'actions');
194
- checkDefinitions( ext, art, 'params');
195
- checkDefinitions( ext, art, 'columns');
196
- if (ext.includes)
197
- applyIncludes( ext, art ); // emits error if `includes` is set
198
- checkAnnotate( ext, art );
199
- copyAnnotationsForExtensions( ext, art );
200
- }
201
- return true;
202
557
  }
203
558
 
204
559
  /**
@@ -231,6 +586,7 @@ function extend( model ) {
231
586
  }
232
587
  if (!noIncludes && art.includes)
233
588
  applyIncludes( art, art );
589
+ // checkExtensionsKind( extensions, art );
234
590
  extendMembers( extensions, art, noIncludes === 'gen' );
235
591
  if (!noIncludes && art.includes) {
236
592
  // early propagation of specific annotation assignments
@@ -244,12 +600,14 @@ function extend( model ) {
244
600
  function extendMembers( extensions, art, noExtend ) {
245
601
  // TODO: do the whole extension stuff lazily if the elements are requested
246
602
  const elemExtensions = [];
247
- extensions.sort( layers.compareLayer );
603
+ if (art._main) // extensions already sorted for main artifacts
604
+ extensions.sort( layers.compareLayer );
248
605
  // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
249
- for (const ext of extensions) {
606
+ // console.log('EM:',art.name,extensions,art._extensions)
607
+ for (const ext of extensions) { // those in extMap.includes
250
608
  // console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
251
609
  // 'Info', 'EXT').toString())
252
- if (!('_artifact' in ext.name)) { // not already applied
610
+ if (ext.name._artifact === undefined) { // not already applied
253
611
  setArtifactLink( ext.name, art );
254
612
  if (noExtend && ext.kind === 'extend') {
255
613
  error( 'extend-for-generated', [ ext.name.location, ext ], { art },
@@ -267,12 +625,10 @@ function extend( model ) {
267
625
  art.includes = [ ...ext.includes ];
268
626
  applyIncludes( ext, art );
269
627
  }
628
+ // console.log(ext,art)
270
629
  checkAnnotate( ext, art );
271
- initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
272
- copyAnnotationsForExtensions( ext, art );
273
630
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
274
631
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
275
- storeTypeExtension( ext, art );
276
632
  dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
277
633
  }
278
634
  for (const name in ext.elements) {
@@ -282,17 +638,24 @@ function extend( model ) {
282
638
  break; // more than one elem in same EXTEND is fine
283
639
  }
284
640
  }
285
-
286
- if (ext.columns) // extend projection
287
- extendColumns( ext, art );
288
641
  }
289
642
  if (elemExtensions.length > 1)
290
643
  reportUnstableExtensions( elemExtensions );
291
- if (art._extendType && art._extendType.length > 0)
292
- reportTypeExtensionsInSameLayer( art._extendType );
293
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
+ }
294
657
  [ 'elements', 'actions' ].forEach( (prop) => {
295
- const dict = art._extend && art._extend[prop];
658
+ const dict = extsTmp[prop];
296
659
  for (const name in dict) {
297
660
  let obj = art;
298
661
  if (obj.targetAspect)
@@ -386,129 +749,6 @@ function extend( model ) {
386
749
  return !hasError;
387
750
  }
388
751
 
389
-
390
- /**
391
- * Copy columns for EXTEND PROJECTION
392
- *
393
- * @param {XSN.Extension} ext
394
- * @param {XSN.Artifact} art
395
- */
396
- function extendColumns( ext, art ) {
397
- // TODO: consider reportUnstableExtensions
398
-
399
- const { location } = ext.name;
400
- const { query } = art;
401
- if (!query) {
402
- if (art.kind !== 'annotate')
403
- error( 'extend-columns', [ location, ext ], { art } );
404
- return;
405
- }
406
- if (!query.from || !query.from.path) {
407
- error( 'extend-columns', [ location, ext ], { art } );
408
- }
409
- else {
410
- if (!query.columns)
411
- query.columns = [ { location, val: '*' } ];
412
-
413
- for (const column of ext.columns) {
414
- setLink( column, '_block', ext._block );
415
- query.columns.push(column);
416
- }
417
- }
418
- }
419
-
420
- /**
421
- * Similar to chooseAssignment for annotations, this function applies type extensions in order,
422
- * such that hierarchies are respected.
423
- * Order already set in `extendMembers()` using `compareLayers()`.
424
- *
425
- * @param art
426
- */
427
- function applyTypeExtensions( art ) {
428
- /**
429
- * Contains the previous extension for each property that was applied
430
- * successfully.
431
- */
432
- const previousSuccess = Object.create(null);
433
- const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
434
- const artType = art.type?._artifact;
435
-
436
- for (const ext of art._extendType) {
437
- if (art.$inferred) {
438
- // Only report first extension for $inferred artifact. Reduces noise.
439
- error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
440
- break;
441
- }
442
-
443
- const baseType = art._effectiveType; // may be an ENUM -- TODO: not here!
444
- if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
445
- // Only report first extension for non-scalar base type. Reduces noise.
446
- error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
447
- break;
448
- }
449
- else if (!allowedBuiltinCategories.includes(baseType.category)) {
450
- // Only report first extension for non-scalar type. Reduces noise.
451
- error('ref-expected-scalar-type', [ ext.name.location, ext ],
452
- { '#': 'unsupported', prop: baseType.category });
453
- continue;
454
- }
455
-
456
- for (const prop of typeParameters.list) {
457
- if (!ext[prop])
458
- continue;
459
-
460
- if (!baseType.parameters?.includes(prop)) {
461
- // For `extend T with type (length:10)` where T does not expect a length.
462
- error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
463
- '#': 'type', prop, art, type: baseType,
464
- });
465
- break; // one error for first property is enough
466
- }
467
- else if (!art[prop]) {
468
- // Can only extend properties that exist directly on the artifact.
469
- error('ext-invalid-type-property', [ ext[prop].location, ext ],
470
- { prop, '#': 'new-prop' });
471
- break; // one error for first property is enough
472
- }
473
- else if (art[prop].val === ext[prop].val) {
474
- // Ignore extensions with same value
475
- }
476
- else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
477
- // Users can't change from/to string value for property,
478
- // e.g. `variable`/`floating` for Decimal
479
- error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
480
- { '#': 'string', prop } );
481
- }
482
- else if (art[prop].val > ext[prop].val) {
483
- // TODO: Future: Sub-message that points to previous extension?
484
- error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
485
- prop,
486
- value: ext[prop].val,
487
- number: art[prop].val,
488
- '#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
489
- } );
490
- break; // one error for first property is enough
491
- }
492
- else if (art[prop].val < ext[prop].val) {
493
- art[prop] = ext[prop];
494
- previousSuccess[prop] = ext;
495
- }
496
- }
497
-
498
- // If `scale` is increased, but `precision` is not, data may be lost in a migration.
499
- // e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
500
- // invalid and only allow `1.234`.
501
- // TODO: Should we actually check that the increase is correct?
502
- if (ext.scale && !ext.precision) {
503
- error('ext-invalid-type-property', [ ext.scale.location, ext ], {
504
- prop: 'scale',
505
- otherprop: 'precision',
506
- '#': 'scale',
507
- });
508
- }
509
- }
510
- }
511
-
512
752
  /**
513
753
  * Report 'Warning: Unstable element order due to repeated extensions'
514
754
  * except if all extensions are in the same file.
@@ -550,26 +790,6 @@ function extend( model ) {
550
790
  }
551
791
  }
552
792
 
553
- /**
554
- * Report type extensions in same layer, similar mechanism to chooseAssignment()
555
- * for annotations.
556
- *
557
- * @param {XSN.Extension[]} extensions
558
- */
559
- function reportTypeExtensionsInSameLayer( extensions ) {
560
- // Group assignments by layer
561
- const extLayers = layeredAssignments( extensions );
562
- // We only care about the highest layer
563
- const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
564
-
565
- if (issue || assignments.length > 1) { // TODO: allow in same file?
566
- const id = (issue === 'unrelated')
567
- ? 'ext-duplicate-extend-type-unrelated-layer'
568
- : 'ext-duplicate-extend-type';
569
- for (const ext of assignments)
570
- message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
571
- }
572
- }
573
793
 
574
794
  /**
575
795
  * @param {XSN.Extension[]} extensions
@@ -597,77 +817,6 @@ function extend( model ) {
597
817
  }
598
818
  }
599
819
 
600
- /**
601
- * @param {Function|false} [veryLate]
602
- */
603
- function lateExtensions( veryLate ) {
604
- for (const name in model.$lateExtensions) {
605
- const art = model.definitions[name];
606
- const exts = model.$lateExtensions[name];
607
- if (art && art.kind !== 'namespace') {
608
- if (art.builtin) {
609
- for (const ext of exts)
610
- info( 'anno-builtin', [ ext.name.location, ext ] );
611
- }
612
- // created texts entity, auto-exposed entity
613
- if (exts) {
614
- extendArtifact( exts, art, 'gen' );
615
- if (veryLate)
616
- veryLate( art );
617
- model.$lateExtensions[name] = null; // done
618
- }
619
- }
620
- else if (veryLate) {
621
- // Complain about unused extensions, i.e. those
622
- // which do not point to a valid artifact
623
- for (const ext of exts) {
624
- delete ext.name.path[0]._artifact; // get message for root
625
- // TODO: make resolvePath('extend'/'annotate') ignore namespaces
626
- // Don't try to apply annotations in the `localized.` namespace.
627
- // That's done in `localized.js`.
628
- if (!name.startsWith('localized.') &&
629
- resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
630
- // should issue error for cds extensions (annotate ok)
631
- if (art.kind === 'namespace') {
632
- // TODO: Emit error if namespace is extended by non-definitions.
633
- info( 'anno-namespace', [ ext.name.location, ext ], {},
634
- 'Namespaces can\'t be annotated' );
635
- }
636
- // Builtin annotations would be represented as annotations in to-csn.js
637
- else if (art.builtin) {
638
- info( 'anno-builtin', [ ext.name.location, ext ] );
639
- }
640
- }
641
- // TODO: warning for context/service extension on non-correct
642
- if (ext.kind === 'annotate')
643
- delete ext.name._artifact; // make it be considered by extendArtifact()
644
- }
645
- // create "super" ANNOTATE containing all non-applied ones
646
- const first = exts[0];
647
- const { location } = first.name;
648
-
649
- /** @type {XSN.Definition} */
650
- const annotationArtifact = {
651
- kind: 'annotate',
652
- name: { path: [ { id: name, location } ], absolute: name, location },
653
- location: first.location,
654
- };
655
-
656
- if (!model.extensions)
657
- model.extensions = [];
658
-
659
- model.extensions.push(annotationArtifact);
660
- extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
661
- // if one of the annotate statement mentions 'returns', assume it
662
- // TODO: with warning/info?
663
- for (const ext of exts) {
664
- if (ext.$syntax === 'returns')
665
- annotationArtifact.$syntax = 'returns';
666
- }
667
- }
668
- }
669
- }
670
-
671
820
  // includes ----------------------------------------------------------------
672
821
 
673
822
  /**
@@ -693,6 +842,7 @@ function extend( model ) {
693
842
  delete ref._artifact;
694
843
  }
695
844
  else if (name && name in extensionsDict) {
845
+ // one of the includes has itself extensions that need to be applied first
696
846
  return false;
697
847
  }
698
848
  else if (ref._artifact) {
@@ -775,6 +925,7 @@ function extend( model ) {
775
925
  // all usages in the expressions? Possibly just the first one?
776
926
  elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
777
927
  elem.$syntax = 'calc';
928
+ setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
778
929
  }
779
930
  // TODO: also complain if elem is just defined in art
780
931
  });
@@ -801,8 +952,15 @@ function extend( model ) {
801
952
  forEachInOrder(parent, prop, ( member, name ) => {
802
953
  if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
803
954
  const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
804
- warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
805
- { '#': prop, name, sorted_arts: includes } );
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
+ }
806
964
  }
807
965
  });
808
966
  }
@@ -933,6 +1091,7 @@ function extend( model ) {
933
1091
  const art = useTextsAspect
934
1092
  ? createTextsEntityWithInclude( base, absolute, fioriEnabled )
935
1093
  : createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
1094
+ // both functions are rather similar...
936
1095
 
937
1096
  const { location } = base.name;
938
1097
 
@@ -968,7 +1127,7 @@ function extend( model ) {
968
1127
  elem.key = { val: true, $inferred: 'localized', location };
969
1128
  // If the propagated elements remain key (that is not fiori.draft.enabled)
970
1129
  // they should be omitted from OData containment EDM
971
- annotateWith( elem, '@odata.containment.ignore', location );
1130
+ setAnnotation( elem, '@odata.containment.ignore', location );
972
1131
  }
973
1132
  else {
974
1133
  // add the former key paths to the unique constraint
@@ -1005,10 +1164,10 @@ function extend( model ) {
1005
1164
  path: [ { id: locale.name.id, location: locale.location } ],
1006
1165
  location: locale.location,
1007
1166
  });
1008
- annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
1167
+ setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
1009
1168
  }
1010
1169
 
1011
- copyPersistenceAnnotations(art, base, options);
1170
+ copyPersistenceAnnotations( art, base );
1012
1171
  return art;
1013
1172
  }
1014
1173
 
@@ -1040,7 +1199,7 @@ function extend( model ) {
1040
1199
  if (!fioriEnabled) {
1041
1200
  // To be compatible, we switch off draft without @fiori.draft.enabled
1042
1201
  // TODO (next major version): remove?
1043
- annotateWith( art, '@odata.draft.enabled', art.location, false );
1202
+ setAnnotation( art, '@odata.draft.enabled', art.location, false );
1044
1203
  }
1045
1204
  else {
1046
1205
  // @fiori.draft.enabled artifacts need default elements ID_texts and locale.
@@ -1061,9 +1220,11 @@ function extend( model ) {
1061
1220
 
1062
1221
  if (addTextsLanguageAssoc && art.elements.language)
1063
1222
  art.elements.language = undefined; // TODO: Message? Ignore?
1223
+ // TODO: what is this necessary? We do not create a text entity in this case
1064
1224
 
1065
1225
  setLink( art, '_block', model.$internal );
1066
1226
  model.definitions[absolute] = art;
1227
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1067
1228
  return art;
1068
1229
  }
1069
1230
 
@@ -1098,7 +1259,7 @@ function extend( model ) {
1098
1259
  locale.key = { val: true, location };
1099
1260
  // To be compatible, we switch off draft without @fiori.draft.enabled
1100
1261
  // TODO (next major version): remove?
1101
- annotateWith( art, '@odata.draft.enabled', art.location, false );
1262
+ setAnnotation( art, '@odata.draft.enabled', art.location, false );
1102
1263
  }
1103
1264
  else {
1104
1265
  const textId = {
@@ -1110,11 +1271,11 @@ function extend( model ) {
1110
1271
  };
1111
1272
  dictAdd( art.elements, 'ID_texts', textId );
1112
1273
  }
1113
-
1114
1274
  dictAdd( art.elements, 'locale', locale );
1275
+
1115
1276
  setLink( art, '_block', model.$internal );
1116
1277
  model.definitions[absolute] = art;
1117
-
1278
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1118
1279
  return art;
1119
1280
  }
1120
1281
 
@@ -1398,9 +1559,9 @@ function extend( model ) {
1398
1559
  model.definitions[entityName] = art;
1399
1560
  initArtifact( art );
1400
1561
 
1562
+ chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
1401
1563
  // Copy persistence annotations from aspect.
1402
- copyPersistenceAnnotations(art, target, options);
1403
-
1564
+ copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
1404
1565
  return art;
1405
1566
  }
1406
1567
 
@@ -1416,138 +1577,204 @@ function extend( model ) {
1416
1577
  if (origin.key)
1417
1578
  proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
1418
1579
  if (anno)
1419
- annotateWith( proxy, anno );
1580
+ setAnnotation( proxy, anno );
1420
1581
  dictAdd( proxyDict.elements, pname, proxy );
1421
1582
  }
1422
1583
  }
1423
1584
 
1424
1585
 
1425
1586
  // Phase 4 - annotations ---------------------------------------------------
1426
-
1427
- function extensionFor( art ) {
1428
- if (art.kind === 'annotate')
1429
- return art;
1430
- if (art._extension)
1431
- return art._extension;
1432
-
1433
- // $extension means: already applied
1434
- const ext = {
1435
- kind: art.kind, // set kind for setMemberParent()
1436
- $extension: 'exists',
1437
- location: art.location, // location( extension to existing art ) = location(art)
1438
- };
1439
- const { location } = art.name;
1440
- if (!art._main) {
1441
- ext.name = {
1442
- path: [ { id: art.name.absolute, location } ],
1443
- location,
1444
- absolute: art.name.absolute,
1445
- };
1446
- if (model.extensions)
1447
- model.extensions.push(ext);
1448
- else
1449
- model.extensions = [ ext ];
1450
- }
1451
- else {
1452
- ext.name = { id: art.name.id, location };
1453
- const parent = extensionFor( art._parent );
1454
- const kind = kindProperties[art.kind].normalized || art.kind;
1455
- // enums would be first in elements
1456
- if ( parent[kindProperties[kind].dict]?.[art.name.id] )
1457
- throw new CompilerAssertion(art.name.id);
1458
- setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
1459
- }
1460
- ext.kind = 'annotate'; // after setMemberParent()!
1461
- setLink( art, '_extension', ext );
1462
- setArtifactLink( ext.name, art );
1463
- if (art.returns)
1464
- ext.$syntax = 'returns';
1465
- return ext;
1466
- }
1587
+ // move to top
1467
1588
 
1468
1589
  /**
1469
1590
  * Goes through all (applied) annotations in the given artifact and chooses one
1470
1591
  * if multiple exist according to the module layer.
1592
+ * TODO: rename to extendArtifactBefore
1471
1593
  *
1472
1594
  * @param {XSN.Artifact} art
1473
1595
  */
1474
1596
  function chooseAnnotationsInArtifact( art ) {
1475
- for (const prop in art) {
1476
- if (prop.charAt(0) === '@')
1477
- chooseAssignment( prop, 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 );
1478
1623
  }
1479
- if (art.doc)
1480
- chooseAssignment( 'doc', art );
1481
1624
  }
1482
1625
 
1483
- function chooseAssignment( annoName, art ) {
1484
- let anno = art[annoName];
1485
- if (!Array.isArray( anno )) { // just one assignment -> use it
1486
- if (!annotationHasEllipsis( anno ))
1487
- return;
1488
- anno = [ anno ];
1489
- }
1490
- // console.log('ASSIGN:',art.name.absolute,annoName)
1491
- const scheduledAssignments = [];
1492
- // sort assignment according to layer (define is bottom layer):
1493
- const layeredAnnos = layeredAssignments( anno );
1494
- let cont = true;
1495
- while (cont) {
1496
- const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
1497
- // console.log( 'CA:', annoName, issue, assignments)
1498
- let index = assignments.length;
1499
- cont = !!index; // safety
1500
- while (--index >= 0) {
1501
- const a = assignments[index];
1502
- scheduledAssignments.push( a );
1503
- if (!annotationHasEllipsis( a )) {
1504
- cont = false;
1505
- break;
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 );
1506
1646
  }
1507
1647
  }
1508
- if (issue) {
1509
- // eslint-disable-next-line no-nested-ternary
1510
- const msg = (index < 0)
1511
- ? 'anno-unstable-array'
1512
- : (issue === true)
1513
- ? 'anno-duplicate'
1514
- : 'anno-duplicate-unrelated-layer';
1515
- const variant = annoName === 'doc' ? 'doc' : 'std';
1516
- for (const a of assignments) {
1517
- if (!a.$errorReported) {
1518
- message( msg, [ a.name?.location || a.location, art ],
1519
- { '#': variant, anno: annoName } );
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;
1520
1675
  }
1521
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)
1522
1713
  }
1523
- else if (index > 0) { // more than one set (not just ...)
1524
- const variant = annoName === 'doc' ? 'doc' : 'std';
1525
- while (index >= 0) { // do not report for trailing [...]
1526
- const a = assignments[index--];
1527
- warning( 'anno-duplicate-same-file', [ a.name?.location || a.location, art ],
1528
- { '#': variant, anno: annoName } );
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 } );
1529
1720
  }
1530
1721
  }
1531
1722
  }
1532
- // Now apply the assignments - all but the first have a '...'
1533
- let result = null;
1534
- scheduledAssignments.reverse();
1535
- for (const a of scheduledAssignments)
1536
- result = applyAssignment( result, a, art, annoName );
1537
- art[annoName] = result.name ? result
1538
- : Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
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
+ }
1539
1768
  }
1540
1769
 
1541
1770
  function applyAssignment( previousAnno, anno, art, annoName ) {
1771
+ const firstEllipsis = annotationHasEllipsis( anno );
1772
+ if (!firstEllipsis)
1773
+ return anno;
1542
1774
  const hasBase = previousAnno?.literal === 'array';
1543
1775
  if (!previousAnno) {
1544
- const firstEllipsis = annotationHasEllipsis( anno );
1545
- if (!firstEllipsis)
1546
- return anno;
1547
- if (anno.$priority) { // already complained about with Define
1548
- const loc = firstEllipsis.location || anno.name.location;
1549
- message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
1550
- }
1776
+ const loc = firstEllipsis.location || anno.name.location;
1777
+ message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
1551
1778
  previousAnno = { val: [] };
1552
1779
  }
1553
1780
  else if (previousAnno.literal !== 'array') {
@@ -1641,69 +1868,70 @@ function extend( model ) {
1641
1868
  return node.variant ? `${ ref }#${ node.variant.id }` : ref;
1642
1869
  }
1643
1870
 
1644
- // formerly in shared.js: -----------------------------------------------------
1645
-
1646
- // Copy annotations from `ext` to `art`, overwriting inferred ones.
1647
- // TODO: move to extend.js if not used anymore in define.js
1648
- function copyAnnotationsForExtensions( ext, art ) {
1649
- for (const annoProp in ext) {
1650
- if (annoProp.charAt(0) === '@' || annoProp === 'doc') {
1651
- const extAnno = ext[annoProp];
1652
- if (art[annoProp]?.$inferred)
1653
- art[annoProp] = extAnno; // overwrite $inferred annos
1654
- else
1655
- dictAddArray( art, annoProp, extAnno );
1656
- }
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' };
1657
1890
  }
1658
1891
  }
1659
1892
  }
1660
1893
 
1661
1894
  /**
1662
- * Group assignments by their layers. An assignment provided with a definition
1895
+ * Group extensions by their layers. A definition (for specified elements)
1663
1896
  * is considered to be provided in a layer named '', the lowest layer.
1664
1897
  *
1665
- * TODO: make this usable for extend (elements), too =
1666
- * do not use $priority, make assignments on define do not have own _block
1667
- *
1668
- * @param {object[]} assignments Array of assignments, e.g. extensions.
1669
- * @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[]}`
1670
1900
  */
1671
- function layeredAssignments( assignments ) {
1901
+ function layeredExtensions( extensions ) {
1672
1902
  const layered = Object.create(null);
1673
- for (const a of assignments) {
1674
- 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 );
1675
1905
  // just consider layer if Extend/Annotate, not Define
1676
1906
  const name = (layer) ? layer.realname : '';
1677
1907
  const done = layered[name];
1678
1908
  if (done)
1679
- done.assignments.push( a );
1909
+ done.extensions.push( ext );
1680
1910
  else
1681
- layered[name] = { name, layer, assignments: [ a ] };
1682
- // TODO: file - if set: unique in layer
1911
+ layered[name] = { name, layer, extensions: [ ext ] };
1683
1912
  }
1684
1913
  return layered;
1685
1914
  }
1686
1915
 
1687
1916
  /**
1688
- * Return assignments of the highest layers.
1917
+ * Return extensions of the highest layers.
1689
1918
  * Also returns whether there could be an issue:
1690
- * - false: there are just assignments in one file,
1691
- * - 'unrelated': there is just one assignment per layer
1692
- * - true: there is at least one layer with two or more assignments, and
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
1693
1922
  * at least two files are involved
1694
- * TODO: make this usable for extend (elements), too.
1695
1923
  *
1696
- * @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
1924
+ * @param {Record<string, object>} layered Structure as returned by layeredExtensions()
1697
1925
  * @returns {{assignments, issue: boolean|string}}
1698
1926
  */
1699
- function assignmentsOfHighestLayers( layeredAnnos ) {
1700
- const layerNames = Object.keys( layeredAnnos );
1927
+ function extensionsOfHighestLayers( layered ) {
1928
+ const layerNames = Object.keys( layered );
1701
1929
  // console.log('HIB:',layerNames)
1702
1930
  if (layerNames.length <= 1) {
1703
1931
  const name = layerNames[0];
1704
- const { assignments } = layeredAnnos[name] || { assignments: [] };
1705
- delete layeredAnnos[name];
1706
- return { assignments, issue: inMoreThanOneFile( assignments ) };
1932
+ const highest = layered[name]?.extensions || [];
1933
+ delete layered[name];
1934
+ return { highest, issue: inMoreThanOneFile( highest ) };
1707
1935
  }
1708
1936
 
1709
1937
  // collect all layers which are lower than another layer
@@ -1711,42 +1939,41 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
1711
1939
  allExtends[''] = {}; // the "Define" layer
1712
1940
  for (const name of layerNames) {
1713
1941
  if (name) // not the "Define" layer
1714
- Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
1942
+ Object.assign( allExtends, layered[name].layer._layerExtends );
1715
1943
  }
1716
1944
  // console.log('HIE:',Object.keys(allExtends))
1717
- const assignments = [];
1718
- const highest = [];
1945
+ const highest = []; // extensions
1946
+ const highestLayers = [];
1719
1947
  for (const name of layerNames) {
1720
1948
  if (!(name in allExtends)) {
1721
- const layer = layeredAnnos[name];
1722
- delete layeredAnnos[name];
1723
- highest.push( layer );
1724
- assignments.push( ...layer.assignments );
1949
+ const layer = layered[name];
1950
+ delete layered[name];
1951
+ highestLayers.push( layer );
1952
+ highest.push( ...layer.extensions );
1725
1953
  }
1726
1954
  }
1727
- assignments.sort( compareAssignments );
1728
- const good = highest.every( layer => !inMoreThanOneFile( layer.assignments ));
1955
+ highest.sort( compareExtensions );
1956
+ const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ));
1729
1957
  // TODO: use layer.file instead
1730
- const issue = !good || highest.length > 1 && 'unrelated';
1731
- // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
1732
- 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 };
1733
1961
  }
1734
1962
 
1735
- function inMoreThanOneFile( assignments ) {
1736
- if (assignments.length <= 1)
1963
+ function inMoreThanOneFile( extensions ) {
1964
+ if (extensions.length <= 1)
1737
1965
  return false;
1738
- // annotations have no location with implicit `true`, doc have no name:
1739
- const file = (assignments[0].name || assignments[0]).location?.file;
1740
- return !file || assignments.slice(1).some( a => (a.name || a).location?.file !== file );
1966
+ const file = extensions[0].location?.file;
1967
+ return !file || extensions.slice(1).some( e => e.location?.file !== file );
1741
1968
  }
1742
1969
 
1743
1970
  /**
1744
- * Compare two assignments which are not comparable via layering:
1745
- * - via the fs.realpath of the file (not layer!) of the assignments, then
1746
- * - via the line, then column of the assignments.
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.
1747
1974
  * Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
1748
1975
  */
1749
- function compareAssignments( a, b ) {
1976
+ function compareExtensions( a, b ) {
1750
1977
  const fileA = layers.realname( a._block );
1751
1978
  const fileB = layers.realname( b._block );
1752
1979
  if (fileA !== fileB)
@@ -1755,29 +1982,6 @@ function compareAssignments( a, b ) {
1755
1982
  (a?.location?.col || 0) - (b?.location?.col || 0);
1756
1983
  }
1757
1984
 
1758
- /**
1759
- * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1760
- * source to target if present on source but not target.
1761
- *
1762
- * @param {object} target
1763
- * @param {object} source
1764
- * @param {CSN.Options} options
1765
- */
1766
- function copyPersistenceAnnotations( target, source, options ) {
1767
- if (!source)
1768
- return;
1769
-
1770
- const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1771
- if (copyExists)
1772
- copy( '@cds.persistence.exists' );
1773
- copy( '@cds.persistence.skip' );
1774
-
1775
- function copy( anno ) {
1776
- if ( source[anno] && !target[anno] )
1777
- target[anno] = { ...source[anno], $inferred: 'parent-origin' };
1778
- }
1779
- }
1780
-
1781
1985
  function augmentEqual( location, assocname, relations, prefix = '' ) {
1782
1986
  const args = relations.map( eq );
1783
1987
  return (args.length === 1)
@@ -1811,15 +2015,14 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
1811
2015
  * @param {XSN.Extension} ext
1812
2016
  * @param {object} art
1813
2017
  */
1814
- function storeTypeExtension( ext, art ) {
1815
- // If there are no parameters to apply, don't store the extension.
1816
- if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
1817
- return;
1818
- else if (!art._extendType)
1819
- setLink( art, '_extendType', [] );
1820
- art._extendType.push( ext );
1821
- }
1822
-
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
+ // }
1823
2026
 
1824
2027
  function checkTextsLanguageAssocOption( model, options ) {
1825
2028
  const languages = model.definitions['sap.common.Languages'];