@sap/cds-compiler 3.4.2 → 3.5.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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "root": true,
3
- "extends": "../../.eslintrc-ydkjsi.json"
3
+ "extends": "../../.eslintrc-ydkjsi.json",
4
+ "rules": {
5
+ "cds-compiler/message-no-quotes": "warn"
6
+ }
4
7
  }
@@ -404,7 +404,6 @@ function assertConsistency( model, stage ) {
404
404
  // TODO: "yes" instead "none": val: true, optional literal/location
405
405
  val: {
406
406
  requires: [ 'literal', 'location' ],
407
- // TODO: rename symbol to sym
408
407
  // TODO: struct and variant only for annotation assignments
409
408
  optional: [
410
409
  'val', 'sym', 'name', '$inferred', '$parens',
@@ -418,7 +417,6 @@ function assertConsistency( model, stage ) {
418
417
  'args',
419
418
  'func',
420
419
  'suffix',
421
- 'quantifier',
422
420
  '$inferred',
423
421
  '$parens',
424
422
  '_artifact', // _artifact with "localized data"s 'coalesce'
@@ -441,7 +439,7 @@ function assertConsistency( model, stage ) {
441
439
  test: isVal, // the following for array/struct value
442
440
  requires: [ 'location' ],
443
441
  optional: [
444
- 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
442
+ 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
445
443
  ],
446
444
  // TODO: restrict path to #simplePath
447
445
  },
@@ -462,7 +460,10 @@ function assertConsistency( model, stage ) {
462
460
  join: { test: locationVal( isString ) },
463
461
  quantifier: { test: locationVal( isString ) },
464
462
  // preliminary -----------------------------------------------------------
465
- doc: { kind: true, test: locationVal( isStringOrNull ) }, // doc comment
463
+ doc: {
464
+ kind: true,
465
+ test: locationVal( isStringOrNull ),
466
+ }, // doc comment
466
467
  '@': {
467
468
  kind: true,
468
469
  inherits: 'value',
@@ -888,7 +889,7 @@ function assertConsistency( model, stage ) {
888
889
  return function valWithLocation( node, parent, prop, spec, name ) {
889
890
  const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
890
891
  const requires = [ 'val', 'location' ];
891
- const optional = [ 'literal', '$inferred', '_pathHead' ];
892
+ const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
892
893
  standard( node, parent, prop, { schema: valSchema, requires, optional }, name );
893
894
  };
894
895
  }
@@ -912,7 +913,7 @@ function assertConsistency( model, stage ) {
912
913
  isString(node, parent, prop, spec);
913
914
  }
914
915
 
915
- function isOneOf(values) {
916
+ function isOneOf( values ) {
916
917
  return function isOneOfInner( node, parent, prop ) {
917
918
  if (!values.includes(node))
918
919
  throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
@@ -959,7 +960,7 @@ function assertConsistency( model, stage ) {
959
960
  }
960
961
  }
961
962
 
962
- function isScope(node, parent, prop) {
963
+ function isScope( node, parent, prop ) {
963
964
  // artifact refs in CDL have scope:0 in XSN
964
965
  if (Number.isInteger(node))
965
966
  return;
@@ -259,7 +259,7 @@ const quotedLiteralPatterns = {
259
259
  *
260
260
  * @returns {boolean} True if the date is valid.
261
261
  */
262
- function checkDate(year, month, day) {
262
+ function checkDate( year, month, day ) {
263
263
  // Negative years are allowed
264
264
  year = Math.abs(Number.parseInt(year, 10));
265
265
  month = Number.parseInt(month, 10);
@@ -275,7 +275,7 @@ function checkDate(year, month, day) {
275
275
  *
276
276
  * @returns {boolean} True if the date is valid.
277
277
  */
278
- function checkTime(hour, minutes, seconds) {
278
+ function checkTime( hour, minutes, seconds ) {
279
279
  hour = Number.parseInt(hour, 10);
280
280
  minutes = Number.parseInt(minutes, 10);
281
281
  seconds = seconds ? Number.parseInt(seconds, 10) : 0;
@@ -311,35 +311,35 @@ Object.keys(coreHana).forEach((type) => {
311
311
  });
312
312
 
313
313
  /** @param {string} typeName */
314
- function isIntegerTypeName(typeName) {
314
+ function isIntegerTypeName( typeName ) {
315
315
  return typeCategories.integer.includes(typeName);
316
316
  }
317
317
  /** @param {string} typeName */
318
- function isDecimalTypeName(typeName) {
318
+ function isDecimalTypeName( typeName ) {
319
319
  return typeCategories.decimal.includes(typeName);
320
320
  }
321
321
  /** @param {string} typeName */
322
- function isNumericTypeName(typeName) {
322
+ function isNumericTypeName( typeName ) {
323
323
  return isIntegerTypeName(typeName) || isDecimalTypeName(typeName);
324
324
  }
325
325
  /** @param {string} typeName */
326
- function isStringTypeName(typeName) {
326
+ function isStringTypeName( typeName ) {
327
327
  return typeCategories.string.includes(typeName);
328
328
  }
329
329
  /** @param {string} typeName */
330
- function isDateOrTimeTypeName(typeName) {
330
+ function isDateOrTimeTypeName( typeName ) {
331
331
  return typeCategories.dateTime.includes(typeName);
332
332
  }
333
333
  /** @param {string} typeName */
334
- function isBooleanTypeName(typeName) {
334
+ function isBooleanTypeName( typeName ) {
335
335
  return typeCategories.boolean.includes(typeName);
336
336
  }
337
337
  /** @param {string} typeName */
338
- function isBinaryTypeName(typeName) {
338
+ function isBinaryTypeName( typeName ) {
339
339
  return typeCategories.binary.includes(typeName);
340
340
  }
341
341
  /** @param {string} typeName */
342
- function isGeoTypeName(typeName) {
342
+ function isGeoTypeName( typeName ) {
343
343
  return typeCategories.geo.includes(typeName);
344
344
  }
345
345
  /**
@@ -347,7 +347,7 @@ function isGeoTypeName(typeName) {
347
347
  *
348
348
  * @param {string} typeName
349
349
  */
350
- function isRelationTypeName(typeName) {
350
+ function isRelationTypeName( typeName ) {
351
351
  return typeCategories.relation.includes(typeName);
352
352
  }
353
353
 
@@ -357,7 +357,7 @@ function isRelationTypeName(typeName) {
357
357
  * @param {string} absolute
358
358
  * @returns {boolean}
359
359
  */
360
- function isInReservedNamespace(absolute) {
360
+ function isInReservedNamespace( absolute ) {
361
361
  return absolute.startsWith( 'cds.') &&
362
362
  !absolute.match(/^cds\.foundation(\.|$)/) &&
363
363
  !absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
@@ -375,7 +375,7 @@ function isInReservedNamespace(absolute) {
375
375
  * @param {string} type
376
376
  * @returns {boolean}
377
377
  */
378
- function isBuiltinType(type) {
378
+ function isBuiltinType( type ) {
379
379
  return typeof type === 'string' && isInReservedNamespace(type);
380
380
  }
381
381
 
@@ -451,7 +451,7 @@ function initBuiltins( model ) {
451
451
  for (const name in builtins) {
452
452
  const magic = builtins[name];
453
453
  // TODO: rename to $builtinFunction
454
- const art = { kind: 'builtin', name: { element: name, id: name } };
454
+ const art = { kind: 'builtin', name: { id: name, absolute: name } };
455
455
  artifacts[name] = art;
456
456
 
457
457
  if (magic.$autoElement)
@@ -23,10 +23,12 @@ const { pathName } = require('./utils');
23
23
 
24
24
  function check( model ) { // = XSN
25
25
  const {
26
- error, warning, message,
26
+ error, warning, message, info,
27
27
  } = model.$messageFunctions;
28
28
  forEachDefinition( model, checkArtifact );
29
- checkSapCommonLocale( model, model.$messageFunctions );
29
+ forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
30
+ checkSapCommonLocale( model );
31
+ checkSapCommonTextsAspects( model );
30
32
  return;
31
33
 
32
34
  function checkArtifact( art ) {
@@ -36,6 +38,11 @@ function check( model ) { // = XSN
36
38
  art.$queries.forEach( checkQuery );
37
39
  }
38
40
 
41
+ function checkAnnotationDefinition( art ) {
42
+ checkEnumType( art );
43
+ // TODO: Should we check elements similar to definition-elements as well?
44
+ }
45
+
39
46
  function checkGenericConstruct( art ) {
40
47
  checkName( art );
41
48
  if (model.vocabularies) {
@@ -80,14 +87,15 @@ function check( model ) { // = XSN
80
87
  }
81
88
  }
82
89
 
83
- function checkLocalizedElement(elem) {
90
+ function checkLocalizedElement( elem ) {
84
91
  // if it is directly a localized element
85
92
  if (elem.localized && elem.localized.val) {
86
93
  const type = elem._effectiveType;
87
94
  // See discussion issue #6520: should we allow all scalar types?
88
95
  if (!type || !type.builtin || type.category !== 'string') {
89
- warning(null, [ elem.type?.location, elem ], { keyword: 'localized' },
90
- 'Keyword $(KEYWORD) should only be used in combination with string types');
96
+ info('ref-expecting-localized-string', [ elem.type?.location, elem ],
97
+ { keyword: 'localized' },
98
+ 'Expecting a string type in combination with keyword $(KEYWORD)');
91
99
  }
92
100
  }
93
101
  // "key" keyword at localized element in SELECT list.
@@ -151,7 +159,7 @@ function check( model ) { // = XSN
151
159
  *
152
160
  * @param {XSN.Artifact} enumNode
153
161
  */
154
- function checkEnum(enumNode) {
162
+ function checkEnum( enumNode ) {
155
163
  if (!enumNode.value)
156
164
  return;
157
165
 
@@ -167,7 +175,7 @@ function check( model ) { // = XSN
167
175
  }
168
176
  }
169
177
 
170
- function checkEnumType(enumNode) {
178
+ function checkEnumType( enumNode ) {
171
179
  // Either the type is an enum or an arrayed enum. We are only interested in
172
180
  // the enum and don't care whether the enum is arrayed.
173
181
  enumNode = enumNode.enum ? enumNode : enumNode.items;
@@ -222,7 +230,7 @@ function check( model ) { // = XSN
222
230
  *
223
231
  * @param {XSN.Definition} enumNode
224
232
  */
225
- function checkEnumValue(enumNode) {
233
+ function checkEnumValue( enumNode ) {
226
234
  const type = enumNode.type && enumNode.type._artifact &&
227
235
  enumNode.type._artifact._effectiveType;
228
236
  if (!enumNode.enum || !type || !type.builtin)
@@ -293,7 +301,7 @@ function check( model ) { // = XSN
293
301
  *
294
302
  * @param {XSN.Artifact} element
295
303
  */
296
- function checkLocalizedSubElement(element) {
304
+ function checkLocalizedSubElement( element ) {
297
305
  if (element._parent.kind !== 'element')
298
306
  return;
299
307
 
@@ -310,7 +318,7 @@ function check( model ) { // = XSN
310
318
  return;
311
319
 
312
320
  // TODO: Recursive check
313
- function isTypeLocalized(type) {
321
+ function isTypeLocalized( type ) {
314
322
  return (type && type.localized && type.localized.val);
315
323
  }
316
324
  }
@@ -321,7 +329,7 @@ function check( model ) { // = XSN
321
329
  *
322
330
  * @param {any} element Element to check recursively
323
331
  */
324
- function checkForUnmanagedAssociations(element, keyObj) {
332
+ function checkForUnmanagedAssociations( element, keyObj ) {
325
333
  if (element.targetAspect) {
326
334
  // TODO: bad location / message
327
335
  message('composition-as-key', [ keyObj.location, element ], {},
@@ -342,7 +350,7 @@ function check( model ) { // = XSN
342
350
 
343
351
  // Check that min and max cardinalities of 'elem' in 'art' have legal values
344
352
  // TODO: move to define.js or parsers
345
- function checkCardinality(elem) {
353
+ function checkCardinality( elem ) {
346
354
  if (!elem.cardinality)
347
355
  return;
348
356
 
@@ -352,13 +360,13 @@ function check( model ) { // = XSN
352
360
  const { literal, val, location } = elem.cardinality[prop];
353
361
  if (!(literal === 'number' && val > 0 ||
354
362
  literal === 'string' && val === '*')) {
355
- error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
363
+ error('invalid-cardinality', [ location, elem ], { '#': prop, code: val, newcode: '*' }, {
356
364
  // eslint-disable-next-line max-len
357
- std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
365
+ std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or $(NEWCODE)',
358
366
  // eslint-disable-next-line max-len
359
- sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
367
+ sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or $(NEWCODE)',
360
368
  // eslint-disable-next-line max-len
361
- targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
369
+ targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or $(NEWCODE)',
362
370
  });
363
371
  }
364
372
  }
@@ -444,7 +452,7 @@ function check( model ) { // = XSN
444
452
 
445
453
  // Traverses 'node' recursively and applies 'checkExpression' to all expressions
446
454
  // found within paths (e.g. filters, parameters, ...)
447
- function checkExpressionsInPaths(node) {
455
+ function checkExpressionsInPaths( node ) {
448
456
  foreachPath(node, (path) => {
449
457
  for (const pathStep of path) {
450
458
  if (pathStep.where)
@@ -458,7 +466,7 @@ function check( model ) { // = XSN
458
466
  });
459
467
  }
460
468
 
461
- function checkAssociation(elem) {
469
+ function checkAssociation( elem ) {
462
470
  // TODO: yes, a check similar to this could make it into the compiler)
463
471
  // when virtual element is part of association
464
472
  if (elem.foreignKeys) {
@@ -474,24 +482,30 @@ function check( model ) { // = XSN
474
482
  checkAssociationCondition(elem, elem.on);
475
483
  }
476
484
 
477
- function checkAssociationCondition(elem, onCond) {
485
+ function checkAssociationCondition( elem, onCond ) {
478
486
  if (Array.isArray(onCond)) // condition in brackets results an array
479
487
  onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
480
488
  else
481
- checkAssociationConditionArgs(elem, onCond.args, onCond.op);
489
+ checkAssociationConditionArgs(elem, onCond.args, getBinaryOp( onCond ));
482
490
  }
483
491
 
484
- function checkAssociationConditionArgs(elem, args, op) {
492
+ function getBinaryOp( cond ) {
493
+ const { op, args } = cond;
494
+ return op?.val === 'ixpr' && args.length === 3 && args[1].literal === 'token' &&
495
+ args[1] || op;
496
+ }
497
+
498
+ function checkAssociationConditionArgs( elem, args, op ) {
485
499
  if (args)
486
500
  args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
487
501
  }
488
502
 
489
- function checkAssociationOnCondArg(elem, arg, op) {
503
+ function checkAssociationOnCondArg( elem, arg, op ) {
490
504
  if (Array.isArray(arg)) {
491
- arg.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
505
+ arg.forEach(Arg => checkAssociationCondition(elem, Arg));
492
506
  }
493
507
  else {
494
- checkAssociationConditionArgs(elem, arg.args, arg.op);
508
+ checkAssociationCondition(elem, arg);
495
509
  singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op);
496
510
  }
497
511
  }
@@ -503,7 +517,7 @@ function check( model ) { // = XSN
503
517
  // integration into name resolution - did the first step.
504
518
  // It is also incomplete, as associations in structures are not checked.
505
519
  // Additionally, `$self.assoc` references are also not found.
506
- function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
520
+ function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
507
521
  if (!arg.path)
508
522
  return;
509
523
  const path0 = arg.path[0];
@@ -530,7 +544,7 @@ function check( model ) { // = XSN
530
544
  }
531
545
 
532
546
  function checkAssociationArgumentStartingWithSelf( op, elem ) {
533
- if (op && op.val === 'xpr') // no check for xpr
547
+ if (op?.val === 'xpr') // no check for xpr, would require re-structuring
534
548
  return;
535
549
  if (op && op.val !== '=')
536
550
  error(null, [ op.location, elem ], {}, '$self comparison is only allowed with \'=\'');
@@ -545,7 +559,7 @@ function check( model ) { // = XSN
545
559
  *
546
560
  * @param {XSN.Artifact} artifact
547
561
  */
548
- function checkTypeStructure(artifact) {
562
+ function checkTypeStructure( artifact ) {
549
563
  // Just a basic check. We do not check that the inner structure of `items`
550
564
  // is the same as the type but only that all are arrayed or structured.
551
565
  if (artifact.type && artifact.type._artifact) {
@@ -575,10 +589,10 @@ function check( model ) { // = XSN
575
589
  * @param {Boolean} allowAssocTail
576
590
  * @returns {void}
577
591
  */
578
- function checkExpression(xpr, allowAssocTail = false) {
592
+ function checkExpression( xpr, allowAssocTail = false ) {
579
593
  // Since the checks for tree-like and token-stream expressions differ,
580
594
  // check here what kind of expression we are looking at
581
- if (xpr.op && xpr.op.val === 'xpr')
595
+ if (xpr.op?.val === 'xpr')
582
596
  return checkTokenStreamExpression(xpr, allowAssocTail);
583
597
  return checkTreeLikeExpression(xpr, allowAssocTail);
584
598
  }
@@ -590,7 +604,7 @@ function check( model ) { // = XSN
590
604
  * @param {any} arg Argument to check (part of an expression)
591
605
  * @returns {Boolean}
592
606
  */
593
- function isVirtualElement(arg) {
607
+ function isVirtualElement( arg ) {
594
608
  return arg.path &&
595
609
  arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true &&
596
610
  arg._artifact.kind && arg._artifact.kind === 'element';
@@ -602,7 +616,7 @@ function check( model ) { // = XSN
602
616
  * @param {any} xpr The expression to check
603
617
  * @returns {void}
604
618
  */
605
- function checkTokenStreamExpression(xpr, allowAssocTail) {
619
+ function checkTokenStreamExpression( xpr, allowAssocTail ) {
606
620
  const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
607
621
  // Check for illegal argument usage within the expression
608
622
  for (const arg of args) {
@@ -621,7 +635,7 @@ function check( model ) { // = XSN
621
635
  * @param {any} xpr The expression to check
622
636
  * @returns {void}
623
637
  */
624
- function checkTreeLikeExpression(xpr, allowAssocTail) {
638
+ function checkTreeLikeExpression( xpr, allowAssocTail ) {
625
639
  // No further checks regarding associations and $self required if this is a
626
640
  // backlink-like expression (a comparison of $self with an assoc)
627
641
  if (isBinaryDollarSelfComparisonWithAssoc(xpr))
@@ -655,7 +669,7 @@ function check( model ) { // = XSN
655
669
  }
656
670
  }
657
671
  // Return true if 'arg' is an expression argument of type association or composition
658
- function isAssociationOperand(arg) {
672
+ function isAssociationOperand( arg ) {
659
673
  if (!arg.path) {
660
674
  // Not a path, hence not an association (literal, expression, function, whatever ...)
661
675
  return false;
@@ -666,7 +680,7 @@ function check( model ) { // = XSN
666
680
  }
667
681
 
668
682
  // Return true if 'arg' is an expression argument denoting "$self" || "$projection"
669
- function isDollarSelfOrProjectionOperand(arg) {
683
+ function isDollarSelfOrProjectionOperand( arg ) {
670
684
  return arg.path && arg.path.length === 1 &&
671
685
  (arg.path[0].id === '$self' || arg.path[0].id === '$projection');
672
686
  }
@@ -677,7 +691,7 @@ function check( model ) { // = XSN
677
691
  * @param {any} xpr The expression to check
678
692
  * @returns {Boolean}
679
693
  */
680
- function isBinaryDollarSelfComparisonWithAssoc(xpr) {
694
+ function isBinaryDollarSelfComparisonWithAssoc( xpr ) {
681
695
  // Must be an expression with arguments
682
696
  if (!xpr.op || !xpr.args)
683
697
  return false;
@@ -735,7 +749,7 @@ function check( model ) { // = XSN
735
749
  // Perform checks for annotation assignment 'anno', using corresponding annotation declaration,
736
750
  // made of 'annoDecl' (artifact or undefined) and 'elementDecl' (annotation or element
737
751
  // or undefined). Report errors on 'options.messages.
738
- function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) {
752
+ function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) {
739
753
  // Nothing to check if no actual annotation declaration was found
740
754
  if (!annoDecl || annoDecl.artifacts && !elementDecl)
741
755
  return;
@@ -779,7 +793,7 @@ function check( model ) { // = XSN
779
793
  // Check that annotation assignment 'value' (having 'path or 'literal' and
780
794
  // 'val') is potentially assignable to element 'element'. Complain on 'loc'
781
795
  // if not
782
- function checkValueAssignableTo(value, elementDecl, art) {
796
+ function checkValueAssignableTo( value, elementDecl, art ) {
783
797
  // FIXME: We currently do not have any element declaration that could match
784
798
  // a 'path' value, so we simply leave those alone
785
799
  if (value.path)
@@ -840,7 +854,7 @@ function check( model ) { // = XSN
840
854
  else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
841
855
  warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
842
856
  }
843
- else {
857
+ else if (!elementDecl._effectiveType.enum) {
844
858
  throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
845
859
  }
846
860
 
@@ -861,13 +875,23 @@ function check( model ) { // = XSN
861
875
  }
862
876
  else if (expectedEnum) {
863
877
  // Enum symbol not provided but expected
864
- if (!Object.keys(expectedEnum).some(symbol => expectedEnum[symbol].value.val === value.val)) {
878
+ const hasValidValue = Object.keys(expectedEnum)
879
+ .some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
880
+ if (!hasValidValue) {
865
881
  // ... and none of the valid enum symbols matches the value
866
882
  warning(null, loc, {}, 'An enum value is required here');
867
883
  }
868
884
  }
869
885
  }
870
886
 
887
+ function getEnumValue( enumSymbol ) {
888
+ if (enumSymbol.value)
889
+ return enumSymbol.value?.val;
890
+ if (enumSymbol._effectiveType)
891
+ return enumSymbol._effectiveType?.value?.val;
892
+ return null;
893
+ }
894
+
871
895
  // TODO: remove the following
872
896
 
873
897
  // Return the artifact (and possibly, its element) found by following 'path'
@@ -877,7 +901,7 @@ function check( model ) { // = XSN
877
901
  // represented by the full path (or 'undefined' if not found). Note that
878
902
  // only elements and artifacts are considered for path traversal (no actions,
879
903
  // functions, parameters etc.)
880
- function resolvePathFrom(path, from, result = {}) {
904
+ function resolvePathFrom( path, from, result = {} ) {
881
905
  // Keep last encountered artifacts
882
906
  if (from && !from._main)
883
907
  result.artifact = from;
@@ -896,11 +920,64 @@ function check( model ) { // = XSN
896
920
 
897
921
  // Return the absolute name of the final type of 'node'. May return 'undefined' for
898
922
  // anonymous types
899
- function getFinalTypeNameOf(node) {
923
+ function getFinalTypeNameOf( node ) {
900
924
  let type = node._effectiveType;
901
925
  if (type.type)
902
926
  type = type.type._artifact;
903
- return type && type.name && type.name.absolute;
927
+ return type?.name?.absolute;
928
+ }
929
+ }
930
+
931
+ /**
932
+ * Ensure that the sap.common.[Fiori]TextsAspect has proper types for
933
+ * e.g. `locale` and `ID_texts`.
934
+ *
935
+ * @param {XSN.Model} model
936
+ */
937
+ function checkSapCommonTextsAspects( model ) {
938
+ checkSapCommonTextsAspectLocale( model, 'sap.common.TextsAspect' );
939
+ checkSapCommonTextsAspectLocale( model, 'sap.common.FioriTextsAspect' );
940
+
941
+ // Check ID_texts: Fiori requires it to be UUID.
942
+ const fioriTextsAspect = model.definitions['sap.common.FioriTextsAspect'];
943
+ const id = fioriTextsAspect?.elements?.ID_texts;
944
+ if (id) {
945
+ const idType = id._effectiveType;
946
+ if (!idType || idType.name?.absolute !== 'cds.UUID') {
947
+ const { error } = model.$messageFunctions;
948
+ error('def-invalid-element-type', [ id.type.location, id ], {
949
+ '#': 'std',
950
+ art: 'sap.common.FioriTextsAspect',
951
+ elemref: 'ID_texts',
952
+ type: 'cds.UUID',
953
+ });
954
+ }
955
+ }
956
+ }
957
+
958
+ /**
959
+ * Ensure that the `locale` element of sap.common.[Fiori]TextsAspects
960
+ * is a string type. This is required by CAP runtimes to work properly.
961
+ *
962
+ * @param {XSN.Model} model
963
+ * @param {string} name Either sap.common.TextsAspects or sap.common.FioriTextsAspects
964
+ */
965
+ function checkSapCommonTextsAspectLocale( model, name ) {
966
+ const locale = model.definitions[name]?.elements?.locale;
967
+ if (locale) {
968
+ // `locale` could also be `sap.common.Locale`, which must also be a string.
969
+ const type = locale._effectiveType;
970
+ if (type?.name?.absolute !== 'cds.String') {
971
+ const hasCommonLocale = !!model.definitions['sap.common.Locale'];
972
+ const { error } = model.$messageFunctions;
973
+ error('def-invalid-element-type', [ locale.type.location, locale ], {
974
+ '#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
975
+ art: name,
976
+ elemref: 'locale',
977
+ type: 'cds.String',
978
+ othertype: 'sap.common.Locale',
979
+ });
980
+ }
904
981
  }
905
982
  }
906
983
 
@@ -909,15 +986,13 @@ function check( model ) { // = XSN
909
986
  * be lifted later on.
910
987
  *
911
988
  * @param {XSN.Model} model
912
- * @param {object} messageFunctions
913
989
  */
914
- function checkSapCommonLocale( model, messageFunctions ) {
990
+ function checkSapCommonLocale( model ) {
915
991
  const localeArt = model.definitions['sap.common.Locale'];
916
992
  if (localeArt) {
917
993
  const type = localeArt._effectiveType;
918
- const isCdsString = type && type.name && type.name.absolute === 'cds.String';
919
- if (!isCdsString) {
920
- const { message } = messageFunctions;
994
+ if (type?.name?.absolute !== 'cds.String') {
995
+ const { message } = model.$messageFunctions;
921
996
  message('type-expected-builtin', [ localeArt.name.location, localeArt ],
922
997
  { name: 'sap.common.Locale' },
923
998
  'Expected $(NAME) to be a string type');
@@ -928,7 +1003,7 @@ function checkSapCommonLocale( model, messageFunctions ) {
928
1003
  // For each property named 'path' in 'node' (recursively), call callback(path, node)
929
1004
  //
930
1005
  // TODO: remove - this is not a good way to traverse expressions
931
- function foreachPath(node, callback) {
1006
+ function foreachPath( node, callback ) {
932
1007
  if (node === null || typeof node !== 'object') {
933
1008
  // Primitive node
934
1009
  return;
@@ -132,6 +132,7 @@ const {
132
132
  pathName,
133
133
  splitIntoPath,
134
134
  annotationHasEllipsis,
135
+ isDirectComposition,
135
136
  } = require('./utils');
136
137
  const { compareLayer } = require('./moduleLayers');
137
138
  const { initBuiltins, isInReservedNamespace } = require('./builtins');
@@ -672,14 +673,17 @@ function define( model ) {
672
673
  setLink( col, '_block', parent._block );
673
674
  initAnnotations( col, parent._block );
674
675
  if (col.inline) { // `@anno elem.{ * }` does not work
675
- if (col.doc)
676
- warning( 'syntax-ignoring-anno', [ col.doc.location, col ], { '#': 'doc' } );
677
-
676
+ if (col.doc) {
677
+ warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
678
+ { '#': 'doc', code: '.{ ‹inline› }' } );
679
+ }
678
680
  // col.$annotations no available for CSN input, have to search.
679
681
  // Warning about first annotation should be enough to avoid spam.
680
682
  const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
681
- if (firstAnno)
682
- warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ] );
683
+ if (firstAnno) {
684
+ warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
685
+ { code: '.{ ‹inline› }' } );
686
+ }
683
687
  }
684
688
  // TODO: allow sub queries? at least in top-level expand without parallel ref
685
689
  if (columns)
@@ -738,7 +742,7 @@ function define( model ) {
738
742
  *
739
743
  * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
740
744
  */
741
- function approveExistsInChildren(exprOrPathElement) {
745
+ function approveExistsInChildren( exprOrPathElement ) {
742
746
  if (!exprOrPathElement) // may be null in case of parse error
743
747
  return;
744
748
  if (exprOrPathElement.$expected === 'exists')
@@ -834,7 +838,7 @@ function define( model ) {
834
838
  dictAddArray( p.$tableAliases, table.name.id, table );
835
839
  }
836
840
  if (table.name.id[0] === '$') {
837
- warning( 'syntax-dollar-ident', [ table.name.location, table ], {
841
+ warning( 'name-invalid-dollar-alias', [ table.name.location, table ], {
838
842
  '#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
839
843
  name: '$',
840
844
  keyword: 'as',
@@ -921,7 +925,7 @@ function define( model ) {
921
925
  error( 'duplicate-definition', [ loc, query ], { name: dupName, '#': 'alias' } );
922
926
  } );
923
927
  if (mixin.name.id[0] === '$') {
924
- warning( 'syntax-dollar-ident', [ mixin.name.location, mixin ],
928
+ warning( 'name-invalid-dollar-alias', [ mixin.name.location, mixin ],
925
929
  { '#': 'mixin', name: '$' } );
926
930
  }
927
931
  }
@@ -929,11 +933,6 @@ function define( model ) {
929
933
  }
930
934
  }
931
935
 
932
- function isDirectComposition( art ) {
933
- const type = art.type && art.type.path;
934
- return type && type[0] && type[0].id === 'cds.Composition';
935
- }
936
-
937
936
  // Return whether the `target` is actually a `targetAspect`
938
937
  // TODO: really do that here and not in kick-start.js?
939
938
  function targetIsTargetAspect( elem ) {