@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -22,7 +22,8 @@ const {
22
22
  augmentPath,
23
23
  splitIntoPath,
24
24
  } = require('./utils');
25
- const { compareLayer, layer } = require('./moduleLayers');
25
+ const layers = require('./moduleLayers');
26
+ const { typeParameters } = require('./builtins');
26
27
 
27
28
  function extend( model ) {
28
29
  const { options } = model;
@@ -34,7 +35,8 @@ function extend( model ) {
34
35
  resolvePath,
35
36
  resolveUncheckedPath,
36
37
  checkAnnotate,
37
- defineAnnotations,
38
+ initAnnotations,
39
+ copyAnnotationsForExtensions,
38
40
  attachAndEmitValidNames,
39
41
  checkDefinitions,
40
42
  initArtifact,
@@ -44,6 +46,9 @@ function extend( model ) {
44
46
 
45
47
  Object.assign( model.$functions, {
46
48
  lateExtensions,
49
+ layeredAssignments,
50
+ assignmentsOfHighestLayers,
51
+ applyTypeExtensions,
47
52
  } );
48
53
 
49
54
  applyExtensions();
@@ -74,6 +79,9 @@ function extend( model ) {
74
79
  }
75
80
 
76
81
  /**
82
+ * Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
83
+ * if they have the property.
84
+ *
77
85
  * @param {XSN.Definition} art
78
86
  * @param {string} prop
79
87
  */
@@ -156,6 +164,7 @@ function extend( model ) {
156
164
  }
157
165
 
158
166
  function extendContext( name, art ) {
167
+ // TODO: remove this function - add remains to define.js
159
168
  // (ext.expectedKind == art.kind) already checked by parser except for context/service
160
169
  if (!kindProperties[art.kind].artifacts) {
161
170
  // no context or service => warn about context extensions
@@ -163,10 +172,10 @@ function extend( model ) {
163
172
  if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
164
173
  const loc = ext.name.location;
165
174
  // TODO: warning is enough
166
- error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind }, {
167
- std: 'Cannot extend non-context / non-service $(NAME) with artifacts',
168
- service: 'Cannot extend non-service $(NAME) with artifacts',
169
- context: 'Cannot extend non-context $(NAME) with artifacts',
175
+ error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
176
+ std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
177
+ service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
178
+ context: 'Can\'t extend non-context $(NAME) with $(KEYWORD)',
170
179
  });
171
180
  }
172
181
  }
@@ -182,9 +191,8 @@ function extend( model ) {
182
191
  checkDefinitions( ext, art, 'columns');
183
192
  if (ext.includes)
184
193
  applyIncludes( ext, art ); // emits error if `includes` is set
185
- if (ext.kind === 'annotate')
186
- checkAnnotate( ext, art );
187
- defineAnnotations( ext, art, ext._block, ext.kind );
194
+ checkAnnotate( ext, art );
195
+ copyAnnotationsForExtensions( ext, art );
188
196
  }
189
197
  return true;
190
198
  }
@@ -200,7 +208,8 @@ function extend( model ) {
200
208
  * @param {boolean|'gen'} [noIncludes=false]
201
209
  */
202
210
  function extendArtifact( extensions, art, noIncludes = false) {
203
- if (!noIncludes && !(canApplyIncludes( art ) && extensions.every( canApplyIncludes )))
211
+ if (!noIncludes && !(canApplyIncludes( art, art ) &&
212
+ extensions.every( ext => canApplyIncludes(ext, art) )))
204
213
  return false;
205
214
  if (!art.query) {
206
215
  model._entities.push( art ); // add structure with includes in dep order
@@ -221,7 +230,8 @@ function extend( model ) {
221
230
  function extendMembers( extensions, art, noExtend ) {
222
231
  // TODO: do the whole extension stuff lazily if the elements are requested
223
232
  const elemExtensions = [];
224
- extensions.sort( compareLayer );
233
+ extensions.sort( layers.compareLayer );
234
+ // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
225
235
  for (const ext of extensions) {
226
236
  // console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
227
237
  // 'Info', 'EXT').toString())
@@ -243,18 +253,19 @@ function extend( model ) {
243
253
  art.includes = [ ...ext.includes ];
244
254
  applyIncludes( ext, art );
245
255
  }
246
- if (ext.kind === 'annotate')
247
- checkAnnotate( ext, art );
248
- defineAnnotations( ext, art, ext._block, ext.kind );
256
+ checkAnnotate( ext, art );
257
+ initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
258
+ copyAnnotationsForExtensions( ext, art );
249
259
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
250
260
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
261
+ storeTypeExtension( ext, art );
251
262
  dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
252
263
  }
253
264
  for (const name in ext.elements) {
254
265
  const elem = ext.elements[name];
255
266
  if (elem.kind === 'element') { // i.e. not extend or annotate
256
267
  elemExtensions.push( elem );
257
- break;
268
+ break; // more than one elem in same EXTEND is fine
258
269
  }
259
270
  }
260
271
 
@@ -263,6 +274,8 @@ function extend( model ) {
263
274
  }
264
275
  if (elemExtensions.length > 1)
265
276
  reportUnstableExtensions( elemExtensions );
277
+ if (art._extendType && art._extendType.length > 0)
278
+ reportTypeExtensionsInSameLayer( art._extendType );
266
279
 
267
280
  [ 'elements', 'actions' ].forEach( (prop) => {
268
281
  const dict = art._extend && art._extend[prop];
@@ -291,9 +304,6 @@ function extend( model ) {
291
304
  function extendColumns( ext, art ) {
292
305
  // TODO: consider reportUnstableExtensions
293
306
 
294
- for (const col of ext.columns)
295
- defineAnnotations( col, col, ext._block, ext.kind );
296
-
297
307
  const { location } = ext.name;
298
308
  const { query } = art;
299
309
  if (!query) {
@@ -315,14 +325,115 @@ function extend( model ) {
315
325
  }
316
326
  }
317
327
 
328
+ /**
329
+ * Similar to chooseAssignment for annotations, this function applies type extensions in order,
330
+ * such that hierarchies are respected.
331
+ * Order already set in `extendMembers()` using `compareLayers()`.
332
+ *
333
+ * @param art
334
+ */
335
+ function applyTypeExtensions(art) {
336
+ /**
337
+ * Contains the previous extension for each property that was applied
338
+ * successfully.
339
+ */
340
+ const previousSuccess = Object.create(null);
341
+ const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
342
+ const artType = art.type?._artifact;
343
+
344
+ for (const ext of art._extendType) {
345
+ if (art.$inferred) {
346
+ // Only report first extension for $inferred artifact. Reduces noise.
347
+ error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
348
+ break;
349
+ }
350
+
351
+ const baseType = art._effectiveType; // may be an ENUM
352
+ if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
353
+ // Only report first extension for non-scalar base type. Reduces noise.
354
+ error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
355
+ break;
356
+ }
357
+ else if (!allowedBuiltinCategories.includes(baseType.category)) {
358
+ // Only report first extension for non-scalar type. Reduces noise.
359
+ error('ref-expected-scalar-type', [ ext.name.location, ext ],
360
+ { '#': 'unsupported', prop: baseType.category });
361
+ continue;
362
+ }
363
+
364
+ for (const prop of typeParameters.list) {
365
+ if (!ext[prop])
366
+ continue;
367
+
368
+ if (!baseType.parameters?.includes(prop)) {
369
+ // For `extend T with type (length:10)` where T does not expect a length.
370
+ error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
371
+ '#': 'type', prop, art, type: baseType,
372
+ });
373
+ break; // one error for first property is enough
374
+ }
375
+ else if (!art[prop]) {
376
+ // Can only extend properties that exist directly on the artifact.
377
+ error('ext-invalid-type-property', [ ext[prop].location, ext ],
378
+ { prop, '#': 'new-prop' });
379
+ break; // one error for first property is enough
380
+ }
381
+ else if (art[prop].val === ext[prop].val) {
382
+ // Ignore extensions with same value
383
+ }
384
+ else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
385
+ // Users can't change from/to string value for property,
386
+ // e.g. `variable`/`floating` for Decimal
387
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
388
+ { '#': 'string', prop } );
389
+ }
390
+ else if (art[prop].val > ext[prop].val) {
391
+ // TODO: Future: Sub-message that points to previous extension?
392
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
393
+ prop,
394
+ value: ext[prop].val,
395
+ number: art[prop].val,
396
+ '#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
397
+ } );
398
+ break; // one error for first property is enough
399
+ }
400
+ else if (art[prop].val < ext[prop].val) {
401
+ art[prop] = ext[prop];
402
+ previousSuccess[prop] = ext;
403
+ }
404
+ }
405
+
406
+ // If `scale` is increased, but `precision` is not, data may be lost in a migration.
407
+ // e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
408
+ // invalid and only allow `1.234`.
409
+ // TODO: Should we actually check that the increase is correct?
410
+ if (ext.scale && !ext.precision) {
411
+ error('ext-invalid-type-property', [ ext.scale.location, ext ], {
412
+ prop: 'scale',
413
+ otherprop: 'precision',
414
+ '#': 'scale',
415
+ });
416
+ }
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Report 'Warning: Unstable element order due to repeated extensions'
422
+ * except if all extensions are in the same file.
423
+ *
424
+ * @param {XSN.Extension[]} extensions
425
+ */
318
426
  function reportUnstableExtensions( extensions ) {
319
- // Report 'Warning: Unstable element order due to repeated extensions'.
427
+ // No message if all extensions are in the same file:
428
+ const file = layers.realname( extensions[0] );
429
+ if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
430
+ return;
320
431
  // Similar to chooseAssignment(), TODO there: also extra intralayer message
321
432
  // as this is a modeling error
322
433
  let lastExt = null;
323
434
  let open = []; // the "highest" layers
324
435
  for (const ext of extensions) {
325
- const extLayer = layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
436
+ const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
326
437
  if (!open.length) {
327
438
  lastExt = ext;
328
439
  open = [ extLayer.realname ];
@@ -346,6 +457,28 @@ function extend( model ) {
346
457
  }
347
458
  }
348
459
  }
460
+
461
+ /**
462
+ * Report type extensions in same layer, similar mechanism to chooseAssignment()
463
+ * for annotations.
464
+ *
465
+ * @param {XSN.Extension[]} extensions
466
+ */
467
+ function reportTypeExtensionsInSameLayer( extensions ) {
468
+ // Group assignments by layer
469
+ const extLayers = layeredAssignments( extensions );
470
+ // We only care about the highest layer
471
+ const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
472
+
473
+ if (issue) {
474
+ const id = (issue === 'unrelated')
475
+ ? 'ext-duplicate-extend-type-unrelated-layer'
476
+ : 'ext-duplicate-extend-type';
477
+ for (const ext of assignments)
478
+ message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
479
+ }
480
+ }
481
+
349
482
  /**
350
483
  * @param {XSN.Extension[]} extensions
351
484
  * @param {string} prop
@@ -354,12 +487,14 @@ function extend( model ) {
354
487
  * @param {object} validDict
355
488
  */
356
489
  function extendNothing( extensions, prop, name, art, validDict ) {
490
+ const artName = searchName( art, name, dictKinds[prop] );
357
491
  for (const ext of extensions) {
358
492
  // TODO: use shared functionality with notFound in resolver.js
359
493
  const { location } = ext.name;
494
+ const extName = { ...artName, kind: ext.kind };
360
495
  const msg
361
- = error( 'extend-undefined', [ location, ext ],
362
- { art: searchName( art, name, dictKinds[prop] ) },
496
+ = error( 'extend-undefined', [ location, extName ],
497
+ { art: artName },
363
498
  {
364
499
  std: 'Unknown $(ART) - nothing to extend',
365
500
  // eslint-disable-next-line max-len
@@ -444,12 +579,20 @@ function extend( model ) {
444
579
  // includes ----------------------------------------------------------------
445
580
 
446
581
  /**
582
+ * Returns true, if `art.includes` can be applied on `target`.
583
+ * They can't be applied if any of the artifacts referenced in
584
+ * `art.includes` are yet to be extended.
585
+ * `art !== target` if `art` is an extension.
586
+ *
447
587
  * @param {XSN.Definition} art
588
+ * @param {XSN.Artifact} target
589
+ * @returns {boolean}
448
590
  */
449
- function canApplyIncludes( art ) {
591
+ function canApplyIncludes( art, target ) {
450
592
  if (art.includes) {
593
+ const isView = !!target.query;
451
594
  for (const ref of art.includes) {
452
- const template = resolvePath( ref, 'include', art );
595
+ const template = resolvePath( ref, isView ? 'viewInclude' : 'include', art );
453
596
  if (template && template.name.absolute in extensionsDict)
454
597
  return false;
455
598
  }
@@ -458,50 +601,64 @@ function extend( model ) {
458
601
  }
459
602
 
460
603
  /**
604
+ * Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
605
+ * If `ext === art`, then includes of the artifact itself are applied.
606
+ * If `ext !== art`, applies includes on the extensions, not artifact.
607
+ * Sets `_ancestor` links on `art`.
608
+ *
609
+ * Examples:
610
+ * ext === art: `entity E : F {}` => add elements of F to E
611
+ * ext !== art: `extend E with F` => add elements of F to extension on E
612
+ *
461
613
  * @param {XSN.Extension} ext
462
614
  * @param {XSN.Artifact} art
463
615
  */
464
616
  function applyIncludes( ext, art ) {
465
617
  if (kindProperties[art.kind].include !== true) {
466
- error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
467
- 'Can\'t extend $(KIND) with includes');
618
+ error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
619
+ { kind: art.kind });
468
620
  return;
469
621
  }
470
622
 
471
623
  if (!art._ancestors)
472
624
  setLink( art, '_ancestors', [] ); // recursive array of includes
473
625
  for (const ref of ext.includes) {
474
- const template = ref._artifact; // already resolved
626
+ const template = ref._artifact;
627
+ // !template -> non-includable, e.g. scalar type, or cyclic
475
628
  if (template) {
476
629
  if (template._ancestors)
477
630
  art._ancestors.push( ...template._ancestors );
478
631
  art._ancestors.push( template );
479
632
  }
480
633
  }
481
- includeMembers( ext, 'elements', forEachInOrder, ext === art && art );
482
- includeMembers( ext, 'actions', forEachGeneric, ext === art && art );
634
+ includeMembers( ext, art, 'elements' );
635
+ includeMembers( ext, art, 'actions' );
483
636
  }
484
637
 
485
638
  /**
639
+ * Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`
640
+ * if `parent` is `false` or to `parent` with appropriate _origin links otherwise.
641
+ * Included members are prepended to existing ones.
642
+ *
486
643
  * @param {XSN.Extension} ext
644
+ * @param {XSN.Artifact} art
487
645
  * @param {string} prop
488
- * @param {function} forEach
489
- * @param {XSN.Artifact} parent
490
646
  */
491
- function includeMembers( ext, prop, forEach, parent ) {
647
+ function includeMembers( ext, art, prop ) {
492
648
  // TODO two kind of messages:
493
649
  // Error 'More than one include defines element "A"' (at include ref)
494
650
  // Warning 'Overwrites definition from include "I" (at elem def)
651
+ const parent = ext === art && art;
495
652
  const members = ext[prop];
496
653
  ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
497
654
  for (const ref of ext.includes) {
498
655
  const template = ref._artifact; // already resolved
499
656
  if (template) { // be robust
500
- forEach( template, prop, ( origin, name ) => {
657
+ forEachInOrder( template, prop, ( origin, name ) => {
501
658
  if (members && name in members)
502
659
  return; // TODO: warning for overwritten element
503
660
  const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
504
- if (!parent) // not yet set for EXTEND foo WITH bar
661
+ if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
505
662
  dictAdd( ext[prop], name, elem );
506
663
  elem.$inferred = 'include';
507
664
  if (origin.masked)
@@ -514,7 +671,7 @@ function extend( model ) {
514
671
  }
515
672
  // TODO: expand elements having direct elements (if needed)
516
673
  if (members) {
517
- forEach( { [prop]: members }, prop, ( elem, name ) => {
674
+ forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
518
675
  dictAdd( ext[prop], name, elem );
519
676
  });
520
677
  }
@@ -523,6 +680,9 @@ function extend( model ) {
523
680
  // localized texts entities
524
681
 
525
682
  /**
683
+ * Process localized data for `art`. This includes creating `.texts` entities
684
+ * and `locale` associations.
685
+ *
526
686
  * @param {XSN.Artifact} art
527
687
  */
528
688
  function processLocalizedData( art ) {
@@ -542,9 +702,13 @@ function extend( model ) {
542
702
  }
543
703
 
544
704
  /**
705
+ * Returns `false`, if there is no localized data or an array of elements
706
+ * that are required for `.texts` entities such as keys and localized elements.
707
+ *
545
708
  * @param {XSN.Artifact} art
546
709
  * @param {XSN.Artifact|undefined} textsEntity
547
710
  * @param {boolean} fioriEnabled
711
+ * @returns {false|XSN.Element[]}
548
712
  */
549
713
  function localizedData( art, textsEntity, fioriEnabled ) {
550
714
  let keys = 0;
@@ -626,7 +790,7 @@ function extend( model ) {
626
790
  }
627
791
 
628
792
  /**
629
- * TODO: set _parent also for main artifacts!
793
+ * Create the `.texts` entity for the given base artifact.
630
794
  *
631
795
  * @param {XSN.Artifact} base
632
796
  * @param {string} absolute
@@ -772,16 +936,20 @@ function extend( model ) {
772
936
  }
773
937
 
774
938
  /**
939
+ * Returns whether `art` directly or indirectly has the property 'prop',
940
+ * following the 'origin' and the 'type' (not involving elements).
941
+ *
942
+ * DON'T USE FOR ANNOTATIONS (see TODO below)
943
+ *
944
+ * TODO: we should issue a warning if we get localized via TYPE OF
945
+ * TODO: XSN: for anno short form, use { val: true, location, <no literal prop> }
946
+ * ...then this function also works with annotations
947
+ *
775
948
  * @param {XSN.Artifact} art
776
949
  * @param {string} prop
950
+ * @returns {boolean}
777
951
  */
778
952
  function hasTruthyProp( art, prop ) {
779
- // Returns whether art directly or indirectly has the property 'prop',
780
- // following the 'origin' and the 'type' (not involving elements).
781
- //
782
- // TODO: we should issue a warning if we get localized via TYPE OF
783
- // TODO XSN: for anno short form, use { val: true, location, <no literal prop> }
784
- // ...then this function also works with annotations
785
953
  const processed = Object.create(null); // avoid infloops with circular refs
786
954
  let name = art.name.absolute; // is ok, since no recursive type possible
787
955
  while (art && !processed[name]) {
@@ -1008,6 +1176,88 @@ function extend( model ) {
1008
1176
  }
1009
1177
  }
1010
1178
 
1179
+ /**
1180
+ * Group assignments by their layers. An assignment provided with a definition
1181
+ * is considered to be provided in a layer named '', the lowest layer.
1182
+ *
1183
+ * TODO: make this usable for extend (elements), too =
1184
+ * do not use $priority, make assignments on define do not have own _block
1185
+ *
1186
+ * @param {object[]} assignments Array of assignments, e.g. extensions.
1187
+ * @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
1188
+ */
1189
+ function layeredAssignments( assignments ) {
1190
+ const layered = Object.create(null);
1191
+ for (const a of assignments) {
1192
+ const layer = a.$priority !== false && layers.layer( a );
1193
+ // just consider layer if Extend/Annotate, not Define
1194
+ const name = (layer) ? layer.realname : '';
1195
+ const done = layered[name];
1196
+ if (done)
1197
+ done.assignments.push( a );
1198
+ else
1199
+ layered[name] = { name, layer, assignments: [ a ] };
1200
+ // TODO: file - if set: unique in layer
1201
+ }
1202
+ return layered;
1203
+ }
1204
+
1205
+ /**
1206
+ * Return assignments of the highest layers.
1207
+ * Also returns whether there could be an issue:
1208
+ * - false: there is just one assignment
1209
+ * - 'unrelated': there is just one assignment per layer
1210
+ * - true: there is at least one layer with two or more assignments
1211
+ * TODO: make this usable for extend (elements), too.
1212
+ *
1213
+ * @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
1214
+ * @returns {{assignments, issue: boolean|string}}
1215
+ */
1216
+ function assignmentsOfHighestLayers( layeredAnnos ) {
1217
+ const layerNames = Object.keys( layeredAnnos );
1218
+ // console.log('HIB:',layerNames)
1219
+ if (layerNames.length <= 1) {
1220
+ const name = layerNames[0];
1221
+ const { assignments } = layeredAnnos[name] || { assignments: [] };
1222
+ delete layeredAnnos[name];
1223
+ return { assignments, issue: assignments.length > 1 };
1224
+ }
1225
+
1226
+ // collect all layers which are lower than another layer
1227
+ const allExtends = Object.create(null);
1228
+ allExtends[''] = {}; // the "Define" layer
1229
+ for (const name of layerNames) {
1230
+ if (name) // not the "Define" layer
1231
+ Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
1232
+ }
1233
+ // console.log('HIE:',Object.keys(allExtends))
1234
+ const assignments = [];
1235
+ const highest = [];
1236
+ for (const name of layerNames) {
1237
+ if (!(name in allExtends)) {
1238
+ const layer = layeredAnnos[name];
1239
+ delete layeredAnnos[name];
1240
+ highest.push( layer );
1241
+ assignments.push( ...layer.assignments );
1242
+ }
1243
+ }
1244
+ assignments.sort( compareAssignments );
1245
+ const good = highest.every( layer => layer.assignments.length === 1 );
1246
+ // TODO: use layer.file instead
1247
+ const issue = !good || highest.length > 1 && 'unrelated';
1248
+ // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
1249
+ return { assignments, issue };
1250
+ }
1251
+
1252
+ function compareAssignments( a, b ) {
1253
+ const fileA = layers.realname( a._block );
1254
+ const fileB = layers.realname( b._block );
1255
+ if (fileA !== fileB)
1256
+ return (fileA > fileB) ? 1 : -1;
1257
+ return (a?.location?.line || 0) - (b?.location?.line || 0) ||
1258
+ (a?.location?.col || 0) - (b?.location?.col || 0);
1259
+ }
1260
+
1011
1261
  /**
1012
1262
  * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1013
1263
  * source to target if present on source but not target.
@@ -1019,14 +1269,16 @@ function extend( model ) {
1019
1269
  function copyPersistenceAnnotations(target, source, options) {
1020
1270
  if (!source)
1021
1271
  return;
1022
- // Copy @cds.persistence.skip/exists annotation.
1023
- const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1024
- const existsAnno = '@cds.persistence.exists';
1025
- const skipAnno = '@cds.persistence.skip';
1026
- if (!noCopyExists && source[existsAnno] && !target[existsAnno])
1027
- target[existsAnno] = source[existsAnno];
1028
- if (source[skipAnno] && !target[skipAnno])
1029
- target[skipAnno] = source[skipAnno];
1272
+
1273
+ const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1274
+ if (copyExists)
1275
+ copy( '@cds.persistence.exists' );
1276
+ copy( '@cds.persistence.skip' );
1277
+
1278
+ function copy(anno) {
1279
+ if ( source[anno] && !target[anno] )
1280
+ target[anno] = { ...source[anno], $inferred: 'parent-origin' };
1281
+ }
1030
1282
  }
1031
1283
 
1032
1284
  function augmentEqual( location, assocname, relations, prefix = '' ) {
@@ -1054,4 +1306,21 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
1054
1306
  }
1055
1307
  }
1056
1308
 
1309
+ /**
1310
+ * If the given extension is a `EXTEND <def> WITH TYPE` extension, store
1311
+ * it in the given artifact. resolve.js will resolve types and call
1312
+ * `typeExtensions()` later.
1313
+ *
1314
+ * @param {XSN.Extension} ext
1315
+ * @param {object} art
1316
+ */
1317
+ function storeTypeExtension( ext, art ) {
1318
+ // If there are no parameters to apply, don't store the extension.
1319
+ if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
1320
+ return;
1321
+ else if (!art._extendType)
1322
+ setLink( art, '_extendType', [] );
1323
+ art._extendType.push( ext );
1324
+ }
1325
+
1057
1326
  module.exports = extend;
@@ -1,5 +1,16 @@
1
1
  // Things which needs to done for parse.cdl after define()
2
2
 
3
+ // For transforming a single CDL file into CSN, parse.cdl resolves block-relative
4
+ // _artifact_ references (in `type`, `includes`, `target`, `targetAspect`, `from`,
5
+ // `extend`, `annotate`) to absolute names. This is the task of the export
6
+ // function of this file, which the compiler calls after the export function of
7
+ // './define.js'.
8
+
9
+ // There should be no need to do other things here – if you think there is,
10
+ // consider adding it to './define.js'. For “semantic locations” in error
11
+ // messages, we set also “structural” links and names inside `extensions` (by
12
+ // calling functions from './define.js').
13
+
3
14
  'use strict';
4
15
 
5
16
  const { dictAddArray } = require('../base/dictionaries');
@@ -18,7 +29,6 @@ function finalizeParseCdl( model ) {
18
29
  const {
19
30
  resolveUncheckedPath,
20
31
  resolveTypeArgumentsUnchecked,
21
- defineAnnotations,
22
32
  initMembers,
23
33
  extensionsDict,
24
34
  } = model.$functions;
@@ -29,20 +39,13 @@ function finalizeParseCdl( model ) {
29
39
  function resolveTypesAndExtensionsForParseCdl() {
30
40
  const extensions = [];
31
41
 
32
- // TODO: probably better to loop over extensions of all sources (there is just one)
33
42
  for (const name in extensionsDict) {
34
43
  for (const ext of extensionsDict[name]) {
35
44
  ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
36
- // Define annotations of this top-level extension
37
- defineAnnotations( ext, ext, ext._block, 'extend' );
38
- mergeAnnotatesForSameArtifact( ext );
45
+ mergeAnnotatesForSameArtifact( ext ); // TODO: should not be necessary anymore
39
46
  // Initialize members and define annotations in sub-elements.
40
47
  initMembers( ext, ext, ext._block, true );
41
48
  extensions.push( ext );
42
- for (const col of ext.columns || []) {
43
- // Note, no `priority` argument, since we don't apply the extension in the end.
44
- defineAnnotations( col, col, ext._block );
45
- }
46
49
  }
47
50
  }
48
51
 
@@ -50,6 +53,8 @@ function finalizeParseCdl( model ) {
50
53
  forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
51
54
 
52
55
  if (extensions.length > 0) {
56
+ // TODO: do a sort based on the location in case there were two extensions
57
+ // on the same definition?
53
58
  model.extensions = extensions;
54
59
  model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
55
60
  }