@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
@@ -11,6 +11,7 @@ const { centralMessages, centralMessageTexts, oldMessageIds } = require('./messa
11
11
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
12
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
13
13
  const { CompilerAssertion } = require('./error');
14
+ const { getArtifactName } = require('../compiler/base');
14
15
 
15
16
  const fs = require('fs');
16
17
  const path = require('path');
@@ -841,6 +842,7 @@ const nameProp = {
841
842
  function: 'action',
842
843
  };
843
844
 
845
+ // TODO: very likely delete this function
844
846
  function searchName( art, id, variant ) {
845
847
  if (!variant) {
846
848
  // used to mention the "effective" type in the message, not the
@@ -1186,7 +1188,7 @@ function deduplicateMessages( messages ) {
1186
1188
  }
1187
1189
 
1188
1190
  function shortArtName( art ) {
1189
- const { name } = art;
1191
+ const name = getArtifactName( art );
1190
1192
  if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1191
1193
  !name.absolute.includes(':'))
1192
1194
  return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
@@ -1194,13 +1196,13 @@ function shortArtName( art ) {
1194
1196
  }
1195
1197
 
1196
1198
  function artName( art, omit ) {
1197
- const { name } = art;
1199
+ const name = getArtifactName( art );
1198
1200
  const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1199
1201
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1200
1202
  r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
1201
1203
  if (name.action && omit !== 'action')
1202
1204
  r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1203
- if (name.alias && art.kind !== '$self')
1205
+ if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
1204
1206
  r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1205
1207
  if (name.param != null && omit !== 'param')
1206
1208
  r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
@@ -1231,7 +1233,7 @@ function memberActionName( art ) {
1231
1233
  function homeName( art, absoluteOnly ) {
1232
1234
  if (!art)
1233
1235
  return art;
1234
- if (art._outer) // in returns / items property
1236
+ if (art._outer) // in items property
1235
1237
  return homeName( art._outer, absoluteOnly );
1236
1238
  else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
1237
1239
  return null;
@@ -1251,7 +1253,10 @@ function homeName( art, absoluteOnly ) {
1251
1253
 
1252
1254
  // The "home" for extensions is handled differently because `_artifact` is not
1253
1255
  // set for unknown extensions, and we could have nested extensions.
1256
+ // TODO: delete this function, just set correct name/_parent for extensions
1254
1257
  function homeNameForExtend( art ) {
1258
+ if (!art.name.absolute && art._main) // new-style member name
1259
+ return `${ art._main.kind }:${ artName( art ) }`;
1255
1260
  const kind = art.kind || 'extend';
1256
1261
  // TODO: fix the following - do like in collectArtifactExtensions() or
1257
1262
  // basically resolveUncheckedPath()
@@ -1590,11 +1595,11 @@ function queryDepthForMessage( csnPath, model, view ) {
1590
1595
  if (!targetQuery)
1591
1596
  return 0;
1592
1597
  const rootQuery = view.query || { SELECT: view.projection };
1593
- let depth = 1;
1598
+ let depth = 0;
1594
1599
  let totalDepth = 0;
1595
1600
  let isFound = false;
1596
- traverseQuery(rootQuery, null, null, (q, querySelect) => {
1597
- if (querySelect) {
1601
+ traverseQuery(rootQuery, null, null, (q) => {
1602
+ if (q.SELECT) {
1598
1603
  totalDepth += 1;
1599
1604
  if (!isFound)
1600
1605
  depth += 1;
package/lib/base/model.js CHANGED
@@ -21,7 +21,6 @@ const queryOps = {
21
21
  */
22
22
  const availableBetaFlags = {
23
23
  // enabled by --beta-mode
24
- calculatedElements: true,
25
24
  annotationExpressions: true,
26
25
  toRename: true,
27
26
  assocsWithParams: true,
@@ -36,6 +35,7 @@ const availableBetaFlags = {
36
35
  optionalActionFunctionParameters: true,
37
36
  // disabled by --beta-mode
38
37
  nestedServices: false,
38
+ v4preview: false,
39
39
  };
40
40
 
41
41
  const availableDeprecatedFlags = {
@@ -111,7 +111,7 @@ function isDeprecatedEnabled( options, feature = null ) {
111
111
  * to change their code, emit an error if one of such removed flags was used.
112
112
  *
113
113
  * @param {CSN.Options} options
114
- * @param error Error message function returned by makeMessageFunctions().
114
+ * @param error Error message function returned by makeMessageFunction().
115
115
  */
116
116
  function checkRemovedDeprecatedFlags( options, { error } ) {
117
117
  // Assume that we emitted these errors once if a message with this ID was found.
@@ -128,6 +128,7 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
128
128
 
129
129
  // Apply function `callback` to all artifacts in dictionary
130
130
  // `model.definitions`. See function `forEachGeneric` for details.
131
+ // TODO: should we skip "namespaces" already here?
131
132
  function forEachDefinition( model, callback ) {
132
133
  forEachGeneric( model, 'definitions', callback );
133
134
  }
@@ -32,7 +32,7 @@ function validateAssociationsInItems( member ) {
32
32
  }
33
33
  }
34
34
  };
35
- if (this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items && member.$path[2] === 'elements') {
35
+ if (this.artifact && this.artifact.kind === 'entity' && member && member.items && member.$path[2] === 'elements') {
36
36
  if (member.items.type) {
37
37
  const type = member.items.type.ref
38
38
  ? this.artifactRef(member.items.type)
@@ -53,7 +53,7 @@ function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
53
53
  */
54
54
  function warnAboutDefaultOnAssociationForHanaCds( member, memberName, prop, path ) {
55
55
  const art = this.csn.definitions[path[1]];
56
- if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
56
+ if (!art.query && !art.projection && this.options.transformation === 'hdbcds' && member.target && member.default) {
57
57
  this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
58
58
  {
59
59
  std: 'Unexpected default defined on association',
@@ -17,7 +17,7 @@ function validateHasPersistedElements( artifact, artifactName, prop, path ) {
17
17
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
18
18
  if (!artifact.elements || !hasRealElements(artifact.elements))
19
19
  // TODO: Maybe check if there are only calc elements and adapt the message?
20
- this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
20
+ this.error('def-missing-element', path, { '#': ( artifact.query || artifact.projection ) ? 'view' : 'std' });
21
21
  }
22
22
  }
23
23
 
@@ -55,7 +55,7 @@ function invalidTarget( member ) {
55
55
 
56
56
  if (
57
57
  this.artifact &&
58
- (this.artifact.kind === 'entity' || this.artifact.query) &&
58
+ this.artifact.kind === 'entity' &&
59
59
  member.$path[2] === 'elements'
60
60
  )
61
61
  checkForInvalidTarget(member);
@@ -95,7 +95,6 @@ function validateOnCondition( member, memberName, property, path ) {
95
95
  }
96
96
  }
97
97
  }
98
-
99
98
  if (_links[j].art.virtual)
100
99
  this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
101
100
 
@@ -116,11 +115,11 @@ function validateOnCondition( member, memberName, property, path ) {
116
115
  // 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
117
116
 
118
117
  // If this path ends structured or on an association, perform the check:
119
- if ((type.target || type.elements) &&
120
- !( /* 1) */ (type.target && type.keys || type.elements) && validStructuredElement ||
121
- /* 2) */ (type.target && validDollarSelf)) &&
122
- !type.virtual) {
123
- // Do nothing - handled by lib/checks/nonexpandableStructured.js
118
+ if (
119
+ ((type.target && type.keys || type.elements) && validStructuredElement ||
120
+ (type.target && validDollarSelf)) && !type.virtual
121
+ ) {
122
+ // Do nothing - handled by lib/checks/nonexpandableStructured.js
124
123
  }
125
124
  else if (type.items && !type.virtual) {
126
125
  this.error(null, onPath, { elemref: { ref } },
@@ -130,6 +129,10 @@ function validateOnCondition( member, memberName, property, path ) {
130
129
  this.error(null, onPath, { elemref: { ref } },
131
130
  'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
132
131
  }
132
+ else if (type.on) {
133
+ // Path leaf is an unmanaged association, can't use an unmanaged assoc as operand
134
+ this.error('ref-unexpected-navigation', onPath, { '#': 'unmanagedleaf', id: logReady(ref[ref.length - 1]), elemref: { ref } });
135
+ }
133
136
  }
134
137
  }
135
138
  }
@@ -18,7 +18,7 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
18
18
  this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
19
19
 
20
20
  if (member['@sql.append']) {
21
- if (this.artifact.query)
21
+ if (this.artifact.query || this.artifact.projection)
22
22
  this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
23
23
  else if (this.csnUtils.isStructured(member))
24
24
  this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
@@ -57,7 +57,7 @@ function checkSqlAnnotationOnArtifact( artifact, artifactName ) {
57
57
  this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
58
58
  }
59
59
  else if (artifact['@sql.prepend']) {
60
- if (artifact.query)
60
+ if (artifact.query || artifact.projection)
61
61
  this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
62
62
  else
63
63
  checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const { hasAnnotationValue } = require('../model/csnUtils');
4
- const { isBetaEnabled } = require('../base/model');
5
4
 
6
5
  // Only to be used with validator.js - a correct this value needs to be provided!
7
6
 
@@ -55,7 +54,7 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
55
54
 
56
55
  // should only happen with csn input, not in cdl
57
56
  // calculated elements may not have a .type (requires beta flag)
58
- if ((!member.value || !isBetaEnabled(this.options, 'calculatedElements')) &&
57
+ if (!member.value &&
59
58
  !parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
60
59
  errorAboutMissingType(this.error, path, memberName, true);
61
60
  return;
@@ -191,7 +191,7 @@ function assertConsistency( model, stage ) {
191
191
  test: isDictionary( definition ),
192
192
  requires: [ 'kind', 'name' ],
193
193
  optional: [
194
- 'elements', '$autoElement', '$uncheckedElements', '_origin',
194
+ 'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
195
195
  '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
196
196
  ],
197
197
  schema: {
@@ -295,6 +295,7 @@ function assertConsistency( model, stage ) {
295
295
  'kind', 'name', '_block', '_parent', '_main', 'elements',
296
296
  '_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
297
297
  '$duplicates', // duplicate query in FROM clause
298
+ '$inferred', // table alias with $inferred: '$internal'
298
299
  ],
299
300
  },
300
301
  none: { optional: () => true }, // parse error
@@ -339,7 +340,7 @@ function assertConsistency( model, stage ) {
339
340
  optional: [
340
341
  'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
341
342
  'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
342
- '_origin', '_effectiveType', '$effectiveSeqNo',
343
+ '_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
343
344
  ],
344
345
  },
345
346
  target: {
@@ -388,12 +389,18 @@ function assertConsistency( model, stage ) {
388
389
  kind: [ 'entity', 'view', 'type', 'aspect' ],
389
390
  test: isString, // CSN parser should check for 'entity', 'view', 'projection'
390
391
  },
392
+ $tokenTexts: {
393
+ parser: true,
394
+ test: isString,
395
+ },
391
396
  value: {
392
397
  optional: [
393
398
  'location', '$inferred', 'sort', 'nulls',
394
399
  'param', 'scope', // for dynamic parameter '?'
395
400
  // A2J wrongly propagates the following into a CAST of the CSN passed to compileX:
396
401
  'elements', 'items', 'enum',
402
+ // CSN parser may let these properties slip through to XSN, even if input is invalid.
403
+ 'args', 'op', 'func', 'suffix',
397
404
  ],
398
405
 
399
406
  kind: true,
@@ -443,6 +450,8 @@ function assertConsistency( model, stage ) {
443
450
  requires: [ 'location' ],
444
451
  optional: [
445
452
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
453
+ // expressions as annotation values
454
+ '$tokenTexts', 'op', 'args', 'func',
446
455
  ],
447
456
  // TODO: restrict path to #simplePath
448
457
  },
@@ -470,7 +479,13 @@ function assertConsistency( model, stage ) {
470
479
  '@': {
471
480
  kind: true,
472
481
  inherits: 'value',
473
- optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
482
+ optional: [
483
+ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
484
+ // annotation values
485
+ '$tokenTexts',
486
+ // CSN parser may let these properties slip through to XSN, even if input is invalid.
487
+ 'args', 'op', 'func', 'suffix',
488
+ ],
474
489
  // TODO: name requires if not in parser?
475
490
  },
476
491
  $priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
@@ -494,7 +509,7 @@ function assertConsistency( model, stage ) {
494
509
  action: { test: isString },
495
510
  param: { test: TODO },
496
511
  alias: { test: isString },
497
- expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
512
+ expectedKind: { kind: [ 'extend' ], test: locationVal( isString ) },
498
513
  virtual: { kind: true, test: locationVal() },
499
514
  key: { kind: true, test: locationVal(), also: [ null, undefined ] },
500
515
  masked: { kind: true, test: locationVal() },
@@ -514,7 +529,7 @@ function assertConsistency( model, stage ) {
514
529
  'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
515
530
  '_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
516
531
  '_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
517
- '$syntax',
532
+ '$syntax', '_extensions',
518
533
  '_status', '_redirected',
519
534
  ...typeProperties,
520
535
  ],
@@ -574,12 +589,14 @@ function assertConsistency( model, stage ) {
574
589
  _leadingQuery: { kind: true, test: TODO },
575
590
  $replacement: { kind: true, test: TODO }, // for smart * in queries
576
591
  _origin: { kind: true, test: TODO },
592
+ _calcOrigin: { kind: true, test: TODO },
577
593
  _pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
578
594
  _from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
579
595
  // array of $tableAlias (or includes) for explicit and implicit redirection:
580
596
  _redirected: { kind: true, test: TODO },
581
597
  // ...array of table aliases for targets from orig to new
582
598
  _$next: { kind: true, test: TODO }, // next lexical search environment for values
599
+ _extensions: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
583
600
  _extend: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
584
601
  _annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
585
602
  _extension: { kind: true, test: TODO }, // on artifact to its "super extend/annotate" statement
@@ -609,6 +626,7 @@ function assertConsistency( model, stage ) {
609
626
  parser: true,
610
627
  kind: true,
611
628
  test: isOneOf([
629
+ '', // constructed “super annotate” statement
612
630
  // Uppercase values are used in logic, lowercase value are "just for us", i.e.
613
631
  // debugging or to add properties such as $generated in Universal CSN.
614
632
  // However, that is no longer true. For example, `autoexposed` is used in populate.js
@@ -620,6 +638,7 @@ function assertConsistency( model, stage ) {
620
638
 
621
639
  '$autoElement', // for magicVars: $user is automatically changed to $user.id
622
640
  '$generated', // compiler generated annotations, e.g. @Core.Computed
641
+ '$internal', // compiler internal; must not reach CSN output
623
642
  '*', // inferred from query wildcard
624
643
  'as', // query alias name
625
644
  'aspect-composition',
@@ -29,14 +29,18 @@ const kindProperties = {
29
29
  type: { elements: propExists, enum: propExists, include: true },
30
30
  aspect: { elements: propExists, actions: true, include: true },
31
31
  annotation: { elements: propExists, enum: propExists },
32
- enum: { normalized: 'element' },
32
+ enum: { normalized: 'element', dict: 'enum' },
33
33
  element: { elements: propExists, enum: propExists, dict: 'elements' },
34
34
  mixin: { normalized: 'alias' },
35
35
  action: {
36
36
  params: () => false, elements: () => false, enum: () => false, dict: 'actions',
37
37
  }, // no extend params, only annotate
38
38
  function: {
39
- params: () => false, elements: () => false, enum: () => false, normalized: 'action',
39
+ params: () => false,
40
+ elements: () => false,
41
+ enum: () => false,
42
+ normalized: 'action',
43
+ dict: 'actions',
40
44
  }, // no extend params, only annotate
41
45
  key: { normalized: 'element' },
42
46
  param: { elements: () => false, enum: () => false, dict: 'params' },
@@ -60,7 +64,50 @@ function propExists( prop, parent ) {
60
64
  return (obj.items || obj.targetAspect || obj)[prop];
61
65
  }
62
66
 
67
+ // Return the "old style" name structure with `absolute`, `action`, `param`,
68
+ // `element`. Later we also need to add `select` and `alias`.
69
+ // (Currently not needed, as only used for extend and annotate statements.)
70
+ function getArtifactName( art ) {
71
+ if (!art.name || art.name.absolute)
72
+ return art.name;
73
+ // extend and annotate statement already have "sparse" names → calculate old one
74
+ const link = art.name._artifact;
75
+ const namePath = [];
76
+ while (art._main && !art.name.absolute) { // until we hit an old-style name or the main artifact
77
+ namePath.push( art );
78
+ art = art._parent;
79
+ }
80
+ namePath.reverse();
81
+ const name = { ...art.name };
82
+ for (const np of namePath) {
83
+ const prop = getMemberNameProp( np, np.kind );
84
+ name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
85
+ name.id = np.name.id;
86
+ name.location = np.name.location;
87
+ }
88
+ if (link !== undefined)
89
+ Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
90
+ return name;
91
+ }
92
+
93
+ // TODO: probably store this prop in name
94
+ function getMemberNameProp( elem, kind ) {
95
+ if (kind !== 'annotate' && kind !== 'extend')
96
+ return kindProperties[kind]?.normalized || kind;
97
+ let obj = elem._parent;
98
+ if (obj.params || obj.returns)
99
+ return 'param';
100
+ if (obj.actions)
101
+ return 'action';
102
+ while (obj.items)
103
+ obj = obj.items;
104
+ if (obj.elements || obj.enum)
105
+ return 'element';
106
+ return 'id';
107
+ }
108
+
63
109
  module.exports = {
64
110
  dictKinds,
65
111
  kindProperties,
112
+ getArtifactName,
66
113
  };
@@ -10,6 +10,7 @@
10
10
  const { builtinLocation } = require('../base/location');
11
11
  const { setLink: setProp } = require('./utils');
12
12
 
13
+ // TODO: make type parameters a dict
13
14
  const core = {
14
15
  String: { parameters: [ 'length' ], category: 'string' },
15
16
  LargeString: { category: 'string' },
@@ -199,7 +200,16 @@ const magicVariables = {
199
200
  },
200
201
  };
201
202
 
202
- // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
203
+ // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
204
+
205
+ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
206
+ // YYYY - MM - dd
207
+ const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
208
+ // T HH : mm : ss TZD
209
+ // eslint-disable-next-line max-len
210
+ const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
211
+ // YYYY - MM - dd T HH : mm : ss . fraction TZD
212
+ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
203
213
 
204
214
  /**
205
215
  * Patterns for literal token tests and creation. The value is a map from the
@@ -227,7 +237,7 @@ const quotedLiteralPatterns = {
227
237
  test_variant: 'time',
228
238
  test_fn: (x) => {
229
239
  // Leading `T` allowed in ISO 8601.
230
- const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
240
+ const match = x.match( timeRegEx );
231
241
  return match !== null && checkTime( match[1], match[2], match[3] );
232
242
  },
233
243
  json_type: 'string',
@@ -235,7 +245,7 @@ const quotedLiteralPatterns = {
235
245
  date: {
236
246
  test_variant: 'date',
237
247
  test_fn: (x) => {
238
- const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
248
+ const match = x.match( dateRegEx );
239
249
  return match !== null && checkDate( match[1], match[2], match[3] );
240
250
  },
241
251
  json_type: 'string',
@@ -243,8 +253,7 @@ const quotedLiteralPatterns = {
243
253
  timestamp: {
244
254
  test_variant: 'timestamp',
245
255
  test_fn: (x) => {
246
- // eslint-disable-next-line max-len
247
- const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
256
+ const match = x.match( timestampRegEx );
248
257
  return match !== null && checkDate( match[1], match[2], match[3] ) &&
249
258
  checkTime( match[4], match[5], match[6] );
250
259
  },
@@ -259,7 +268,7 @@ const quotedLiteralPatterns = {
259
268
  },
260
269
  number: {
261
270
  test_variant: 'number',
262
- test_fn: (x => /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i.test( x )),
271
+ test_fn: (x => numberRegEx.test( x )),
263
272
  json_type: 'number',
264
273
  secondary_json_type: 'string',
265
274
  },
@@ -777,12 +777,12 @@ function check( model ) { // = XSN
777
777
  // Must have literal or path unless it is a boolean
778
778
  if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
779
779
  if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
780
- warning(null, anno.location || anno.name.location, { type: elementDecl.type._artifact },
781
- 'Expecting a value of type $(TYPE) for the annotation');
780
+ warning('anno-expecting-value', anno.location || anno.name.location,
781
+ { '#': 'type', type: elementDecl.type._artifact });
782
782
  }
783
783
  else {
784
- warning(null, anno.location || anno.name.location, {},
785
- 'Expecting a value for the annotation');
784
+ warning('anno-expecting-value', anno.location || anno.name.location,
785
+ { '#': 'std', anno: anno.name.absolute });
786
786
  }
787
787
 
788
788
  return;