@sap/cds-compiler 2.15.8 → 3.1.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 (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -97,7 +97,6 @@ function assertConsistency( model, stage ) {
97
97
  '$lateExtensions',
98
98
  '_entities', '$entity',
99
99
  '$blocks',
100
- '$newfeatures',
101
100
  '$messageFunctions',
102
101
  '$functions',
103
102
  '$volatileFunctions',
@@ -118,8 +117,10 @@ function assertConsistency( model, stage ) {
118
117
  '$sources',
119
118
  ],
120
119
  },
121
- location: { // location req if at least one property:
122
- isRequired: parent => noSyntaxErrors() || Object.keys( parent ).length,
120
+ location: {
121
+ // every thing with a $location in CSN must have a XSN location even
122
+ // with syntax errors (currently even internal artifacts like $using):
123
+ isRequired: parent => noSyntaxErrors() || parent && parent.kind,
123
124
  kind: true,
124
125
  requires: [ 'file' ], // line is optional in top-level location
125
126
  optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
@@ -141,7 +142,6 @@ function assertConsistency( model, stage ) {
141
142
  },
142
143
  fileDep: { test: TODO }, // in usings
143
144
  $frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
144
- $newfeatures: { test: TODO }, // if new features have been used which break the old backends
145
145
  messages: {
146
146
  enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
147
147
  test: isArray( TODO ),
@@ -173,7 +173,6 @@ function assertConsistency( model, stage ) {
173
173
  return innerDict( val, parent, lang, spec );
174
174
  } ),
175
175
  },
176
- _assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
177
176
  $magicVariables: {
178
177
  // $magicVariables contains "builtin" artifacts that differ from
179
178
  // "normal artifacts" and therefore have a custom schema
@@ -185,12 +184,16 @@ function assertConsistency( model, stage ) {
185
184
  // are missing the location property
186
185
  test: isDictionary( definition ),
187
186
  requires: [ 'kind', 'name' ],
188
- optional: [ 'elements', '$autoElement', '$uncheckedElements', '_effectiveType', '_deps' ],
187
+ optional: [
188
+ 'elements', '$autoElement', '$uncheckedElements',
189
+ '$requireElementAccess', '_effectiveType', '_deps',
190
+ ],
189
191
  schema: {
190
192
  kind: { test: isString, enum: [ 'builtin' ] },
191
193
  name: { test: isObject, requires: [ 'id', 'element' ] },
192
194
  $autoElement: { test: isString },
193
195
  $uncheckedElements: { test: isBoolean },
196
+ $requireElementAccess: { test: isBoolean },
194
197
  // missing location for normal "elements"
195
198
  elements: { test: TODO },
196
199
  },
@@ -217,8 +220,7 @@ function assertConsistency( model, stage ) {
217
220
  usings: {
218
221
  test: isArray(),
219
222
  requires: [ 'kind', 'location' ],
220
- optional: [ 'name', 'extern', 'usings', '$annotations', 'fileDep' ],
221
- // TODO: get rid of $annotations: []
223
+ optional: [ 'name', 'extern', 'usings', 'fileDep' ],
222
224
  },
223
225
  extern: {
224
226
  requires: [ 'location', 'path' ],
@@ -251,6 +253,7 @@ function assertConsistency( model, stage ) {
251
253
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
252
254
  '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
253
255
  '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
256
+ '_extension', // for unapplied extensions
254
257
  ],
255
258
  },
256
259
  none: { optional: () => true }, // parse error
@@ -304,7 +307,6 @@ function assertConsistency( model, stage ) {
304
307
  kind: 'element',
305
308
  test: isDictionary( definition ), // definition since redef
306
309
  requires: [ 'location', 'name' ],
307
- optional: [ '$annotations' ], // TODO: get rid of annos: []
308
310
  },
309
311
  orderBy: { inherits: 'value', test: isArray( expression ) },
310
312
  sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
@@ -371,13 +373,21 @@ function assertConsistency( model, stage ) {
371
373
  },
372
374
  // locations of parentheses pairs around expression:
373
375
  $parens: { parser: true, test: TODO },
376
+ $prefix: { test: isString }, // compiler-corrected path prefix
374
377
  $syntax: {
375
378
  parser: true,
376
379
  kind: [ 'entity', 'view', 'type', 'aspect' ],
377
380
  test: isString, // CSN parser should check for 'entity', 'view', 'projection'
378
381
  },
379
382
  value: {
380
- optional: [ 'location', '$inferred', 'sort', 'nulls' ],
383
+ optional: [
384
+ 'location', '$inferred', 'sort', 'nulls',
385
+ 'param', 'scope', // for dynamic parameter '?'
386
+ // through cast() with enum through CSN->XSN
387
+ // TODO: re-check #9225, this should be directly in the query element,
388
+ // not inside value, no `enum` inside `cast`!
389
+ 'elements', 'items', 'enum', '$expand', 'target',
390
+ ],
381
391
 
382
392
  kind: true,
383
393
  test: expression, // properties below are "sub specifications"
@@ -435,7 +445,10 @@ function assertConsistency( model, stage ) {
435
445
  struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
436
446
  args: {
437
447
  inherits: 'value',
438
- optional: [ 'name', '$duplicate', '$expected', 'args', 'suffix' ],
448
+ optional: [
449
+ 'name', '$duplicate', '$expected', 'args', 'suffix',
450
+ 'param', 'scope', // for dynamic parameter '?'
451
+ ],
439
452
  test: args,
440
453
  },
441
454
  on: { kind: true, inherits: 'value', test: expression },
@@ -449,11 +462,11 @@ function assertConsistency( model, stage ) {
449
462
  '@': {
450
463
  kind: true,
451
464
  inherits: 'value',
452
- optional: [ 'name', '_block', '$priority', '$duplicate', '$inferred', '$duplicates' ],
465
+ optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
453
466
  // TODO: name requires if not in parser?
454
467
  },
455
- $priority: { test: TODO }, // TODO: rename to $priority
456
- $annotations: { parser: true, kind: true, test: TODO },
468
+ $priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
469
+ $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
457
470
  name: {
458
471
  isRequired: stageParser && (() => false), // not required in parser
459
472
  kind: true,
@@ -468,10 +481,10 @@ function assertConsistency( model, stage ) {
468
481
  ],
469
482
  },
470
483
  absolute: { test: isString },
471
- variant: { test: TODO }, // TODO: not set in CDL parser, only in $annotations
484
+ variant: { test: TODO }, // TODO: not set in CDL parser
472
485
  element: { test: TODO }, // TODO: { test: isString },
473
486
  action: { test: isString },
474
- param: { test: isString },
487
+ param: { test: TODO },
475
488
  alias: { test: isString },
476
489
  expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
477
490
  virtual: { kind: true, test: locationVal() },
@@ -524,7 +537,6 @@ function assertConsistency( model, stage ) {
524
537
  _service: { kind: true, test: TODO },
525
538
  _main: { kind: true, test: TODO },
526
539
  _artifact: { test: TODO },
527
- _base: { test: TODO, kind: true },
528
540
  _navigation: { test: TODO },
529
541
  _effectiveType: { kind: true, test: TODO },
530
542
  _joinParent: { test: TODO },
@@ -548,6 +560,7 @@ function assertConsistency( model, stage ) {
548
560
  'where', 'columns', 'mixin', 'quantifier', 'offset',
549
561
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
550
562
  'limit', '_status',
563
+ '_extension', // for unapplied extensions
551
564
  ],
552
565
  },
553
566
  _leadingQuery: { kind: true, test: TODO },
@@ -581,20 +594,57 @@ function assertConsistency( model, stage ) {
581
594
  // (it can contain the artifact itself with no/failed autoexposure):
582
595
  _descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
583
596
 
597
+ $errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
584
598
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
585
599
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
586
- $inferred: { parser: true, kind: true, test: isString },
600
+ $inferred: {
601
+ parser: true,
602
+ kind: true,
603
+ test: isOneOf([
604
+ // Uppercase values are used in logic, lowercase value are "just for us", i.e.
605
+ // debugging or to add non-enumerable properties such as $generated in Universal CSN.
606
+ // However, that is no longer true. For example, `autoexposed` is used in populate.js
607
+ // as well.
608
+ 'IMPLICIT',
609
+ 'REDIRECTED',
610
+
611
+ '$autoElement', // for magicVars: $user is automatically changed to $user.id
612
+ '$generated', // compiler generated annotations, e.g. @Core.Computed
613
+ '*', // inferred from query wildcard
614
+ 'as', // query alias name
615
+ 'aspect-composition',
616
+ 'autoexposed', // for auto-exposed entities (they can't be referred to)
617
+ 'cast', // type from cast() function
618
+ 'composition-entity',
619
+ 'copy', // only used in rewriteCondition(): On-condition is copied
620
+ 'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
621
+ 'expand-element', // expanded elements
622
+ 'expand-param', // expanded params (difference to expand-element only for debugging)
623
+ 'include', // through includes, e.g. `entity E : F {}`
624
+ 'keys',
625
+ 'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
626
+ 'localized-entity', // `.texts` entity
627
+ 'nav', // only used for MASKED, TODO(v4): Remove
628
+ 'none', // only used in ensureColumnName(): Used in object representing empty alias
629
+ 'query', // inferred query properties, e.g. `key`
630
+ 'rewrite', // on-conditions or FKeys are rewritten
631
+ ]),
632
+ },
587
633
 
588
634
  // Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
589
635
  // client, universal: render expanded elements? gensrc: produce annotate statements?
590
- $expand: { kind: true, test: isString }, // TODO: rename it to $elementsExpand ?
636
+ // TODO: rename it to $elementsExpand ?
637
+ $expand: {
638
+ kind: true,
639
+ // See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
640
+ test: isOneOf([ 'origin', 'annotate', 'target' ]),
641
+ },
591
642
 
592
643
  $autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
593
- $a2j: { kind: true, enumerable: true, test: TODO },
594
644
  $extra: { parser: true, test: TODO }, // for unexpected properties in CSN
595
645
  $withLocalized: { test: isBoolean },
596
646
  $sources: { parser: true, test: isArray( isString ) },
597
- $expected: { parser: true, test: isString },
647
+ $expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
598
648
  $messageFunctions: { test: TODO },
599
649
  $functions: { test: TODO },
600
650
  $volatileFunctions: { test: TODO },
@@ -854,6 +904,13 @@ function assertConsistency( model, stage ) {
854
904
  isString(node, parent, prop, spec);
855
905
  }
856
906
 
907
+ function isOneOf(values) {
908
+ return function isOneOfInner( node, parent, prop ) {
909
+ if (!values.includes(node))
910
+ throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
911
+ };
912
+ }
913
+
857
914
  function isString( node, parent, prop, spec ) {
858
915
  if (typeof node !== 'string')
859
916
  throw new Error( `Expected string${ at( [ node, parent ], prop ) }` );
@@ -16,16 +16,18 @@ const kindProperties = {
16
16
  namespace: { artifacts: true }, // on-the-fly context
17
17
  context: { artifacts: true, normalized: 'namespace' },
18
18
  service: { artifacts: true, normalized: 'namespace' },
19
- entity: { elements: true, actions: true, params: () => false },
19
+ entity: {
20
+ elements: true, actions: true, params: () => false, include: true,
21
+ },
20
22
  select: { normalized: 'select', elements: true },
21
23
  $join: { normalized: 'select' },
22
24
  $tableAlias: { normalized: 'alias' }, // table alias in select
23
25
  $self: { normalized: 'alias' }, // table alias in select
24
26
  $navElement: { normalized: 'element' },
25
27
  $inline: { normalized: 'element' }, // column with inline property
26
- event: { elements: true },
27
- type: { elements: propExists, enum: propExists },
28
- aspect: { elements: propExists },
28
+ event: { elements: true, include: true },
29
+ type: { elements: propExists, enum: propExists, include: true },
30
+ aspect: { elements: propExists, actions: true, include: true },
29
31
  annotation: { elements: propExists, enum: propExists },
30
32
  enum: { normalized: 'element' },
31
33
  element: { elements: propExists, enum: propExists, dict: 'elements' },
@@ -1,7 +1,7 @@
1
1
  // The builtin artifacts of CDS
2
2
 
3
3
  // TODO: split this file
4
- // - in base/: common definitions
4
+ // - in base/: common definitions, datetime formats
5
5
  // - in compiler/: XSN-specific
6
6
  // - in ?: CSN-specific
7
7
 
@@ -76,19 +76,94 @@ const functionsWithoutParens = [
76
76
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
77
77
  ];
78
78
 
79
- const specialFunctions = {
79
+ const specialFunctions = compileFunctions( {
80
+ '': [ // the default
81
+ {
82
+ intro: [ 'ALL', 'DISTINCT' ],
83
+ introMsg: [], // do not list them in code completion
84
+ },
85
+ {},
86
+ ],
80
87
  ROUND: [
81
88
  null, null, { // 3rd argument: rounding mode
82
- ROUND_HALF_UP: 'argFull',
83
- ROUND_HALF_DOWN: 'argFull',
84
- ROUND_HALF_EVEN: 'argFull',
85
- ROUND_UP: 'argFull',
86
- ROUND_DOWN: 'argFull',
87
- ROUND_CEILING: 'argFull',
88
- ROUND_FLOOR: 'argFull',
89
+ expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
90
+ 'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
89
91
  },
90
92
  ],
91
- };
93
+ TRIM: [
94
+ {
95
+ intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
96
+ expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
97
+ separator: [ 'FROM' ],
98
+ },
99
+ ],
100
+ EXTRACT: [
101
+ {
102
+ expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
103
+ separator: [ 'FROM' ],
104
+ },
105
+ ],
106
+ COUNT: [
107
+ {
108
+ expr: [ '*' ],
109
+ intro: [ 'ALL', 'DISTINCT' ],
110
+ },
111
+ ],
112
+ MIN: 'COUNT',
113
+ MAX: 'COUNT',
114
+ SUM: 'COUNT',
115
+ AVG: 'COUNT',
116
+ STDDDEV: 'COUNT',
117
+ VAR: 'COUNT',
118
+ LOCATE_REGEXPR: [
119
+ {
120
+ intro: [ 'START', 'AFTER' ],
121
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
122
+ },
123
+ ],
124
+ OCCURRENCES_REGEXPR: [
125
+ {
126
+ separator: [ 'FLAG', 'IN', 'FROM' ],
127
+ },
128
+ ],
129
+ REPLACE_REGEXPR: [
130
+ {
131
+ separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
132
+ expr: [ 'ALL' ],
133
+ },
134
+ ],
135
+ SUBSTRING_REGEXPR: [
136
+ {
137
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
138
+ },
139
+ ],
140
+ } );
141
+
142
+ function compileFunctions( special ) {
143
+ const compiled = {};
144
+ for (const [ name, val ] of Object.entries( special ))
145
+ compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
146
+ return compiled;
147
+ }
148
+
149
+ function compileArg( src ) {
150
+ if (!src)
151
+ return src;
152
+ const tgt = {
153
+ intro: src.intro || [],
154
+ expr: src.expr || [],
155
+ separator: src.separator || [],
156
+ };
157
+ for (const generic of [ 'intro', 'expr', 'separator' ]) {
158
+ // intro before expr: if both intro and expr, tag as 'expr'
159
+ for (const token of src[generic] || [])
160
+ tgt[token] = generic;
161
+ }
162
+ // As GenericIntro is always together with GenericExpr, only mention those
163
+ // which are not already proposed for GenericExpr:
164
+ tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
165
+ return tgt;
166
+ }
92
167
 
93
168
  /**
94
169
  * Variables that have special meaning in CDL/CSN.
@@ -106,17 +181,106 @@ const magicVariables = {
106
181
  elements: {
107
182
  from: {}, to: {},
108
183
  },
184
+ // Require that elements are accessed, i.e. no $at, only $at.<element>.
185
+ $requireElementAccess: true,
109
186
  },
110
187
  $now: {}, // Dito
111
188
  $session: {
112
189
  // In ABAP CDS session variables are accessed in a generic way via
113
190
  // the pseudo variable $session.
114
191
  $uncheckedElements: true,
192
+ $requireElementAccess: true,
115
193
  },
116
194
  };
117
195
 
118
196
  // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
119
197
 
198
+ /**
199
+ * Patterns for literal token tests and creation. The value is a map from the
200
+ * `prefix` argument of function `quotedliteral` to the following properties:
201
+ * - `test_msg`: error message which is issued if `test_fn` fails.
202
+ * - `test_fn`: function called with argument `value`, fails falsy return value
203
+ * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
204
+ * - `unexpected_char`: regular expression matching an illegal character in `value`,
205
+ * the error location is only correct for a literal <prefix>'<value>'
206
+ * - `literal`: the value which is used instead of `prefix` in the AST
207
+ * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
208
+ * but always allow Feb 29 (no leap year computation)
209
+ * Notes:
210
+ * - Dates/Times as defined in ISO 8601, see <https://en.wikipedia.org/wiki/ISO_8601>
211
+ */
212
+ const quotedLiteralPatterns = {
213
+ x: {
214
+ test_variant: 'uneven-hex',
215
+ test_fn: (str => Number.isInteger(str.length / 2)),
216
+ unexpected_variant: 'invalid-hex',
217
+ unexpected_char: /[^0-9a-f]/i,
218
+ json_type: 'string',
219
+ },
220
+ time: {
221
+ test_variant: 'time',
222
+ test_fn: (x) => {
223
+ // Leading `T` allowed in ISO 8601.
224
+ const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
225
+ return match !== null && checkTime( match[1], match[2], match[3] );
226
+ },
227
+ json_type: 'string',
228
+ },
229
+ date: {
230
+ test_variant: 'date',
231
+ test_fn: (x) => {
232
+ const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
233
+ return match !== null && checkDate( match[1], match[2], match[3] );
234
+ },
235
+ json_type: 'string',
236
+ },
237
+ timestamp: {
238
+ test_variant: 'timestamp',
239
+ test_fn: (x) => {
240
+ // eslint-disable-next-line max-len
241
+ const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
242
+ return match !== null && checkDate( match[1], match[2], match[3] ) &&
243
+ checkTime( match[4], match[5], match[6] );
244
+ },
245
+ json_type: 'string',
246
+ },
247
+ };
248
+
249
+ /**
250
+ * Check that the given date is within boundaries.
251
+ * We can't use Date.parse() since that also allows non-standard values (2022-02-31 for example).
252
+ * Checks according to ISO 8601.
253
+ *
254
+ * @returns {boolean} True if the date is valid.
255
+ */
256
+ function checkDate(year, month, day) {
257
+ // Negative years are allowed
258
+ year = Math.abs(Number.parseInt(year, 10));
259
+ month = Number.parseInt(month, 10);
260
+ day = Number.parseInt(day, 10);
261
+ // If any is NaN, the condition will be false.
262
+ // Year 0 does not exist, but ISO 8601 allows it and defines it as 1 BC.
263
+ return !Number.isNaN(year) && month > 0 && month < 13 && day > 0 && day < 32;
264
+ }
265
+
266
+ /**
267
+ * Check that the given time is within boundaries.
268
+ * Checks according to ISO 8601.
269
+ *
270
+ * @returns {boolean} True if the date is valid.
271
+ */
272
+ function checkTime(hour, minutes, seconds) {
273
+ hour = Number.parseInt(hour, 10);
274
+ minutes = Number.parseInt(minutes, 10);
275
+ seconds = seconds ? Number.parseInt(seconds, 10) : 0;
276
+ if (hour === 24) // allow 24:00:00 (ISO 8601 version earlier than 2019)
277
+ return minutes === 0 && seconds === 0;
278
+ // If any is NaN, the condition will be false.
279
+ return hour >= 0 && hour < 24 &&
280
+ minutes >= 0 && minutes < 60 &&
281
+ seconds >= 0 && seconds < 61; // we allow 60 for lead seconds
282
+ }
283
+
120
284
  /** All types belong to one category. */
121
285
  const typeCategories = {
122
286
  string: [],
@@ -286,6 +450,8 @@ function initBuiltins( model ) {
286
450
  art.$autoElement = magic.$autoElement;
287
451
  if (magic.$uncheckedElements)
288
452
  art.$uncheckedElements = magic.$uncheckedElements;
453
+ if (magic.$requireElementAccess)
454
+ art.$requireElementAccess = magic.$requireElementAccess;
289
455
 
290
456
  createMagicElements( art, magic.elements );
291
457
  if (options.variableReplacements)
@@ -325,6 +491,7 @@ module.exports = {
325
491
  typeParameters,
326
492
  functionsWithoutParens,
327
493
  specialFunctions,
494
+ quotedLiteralPatterns,
328
495
  initBuiltins,
329
496
  isInReservedNamespace,
330
497
  isBuiltinType,
@@ -414,7 +414,7 @@ function check( model ) { // = XSN
414
414
  }
415
415
  }
416
416
 
417
- // TODO: make this part of the the name resolution in the compiler
417
+ // TODO: make this part of the name resolution in the compiler
418
418
  // Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
419
419
  function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
420
420
  const art = query._main; // TODO - remove, use query for semantic location
@@ -109,7 +109,7 @@
109
109
 
110
110
  'use strict';
111
111
 
112
- const { isDeprecatedEnabled, forEachGeneric, forEachInOrder } = require('../base/model');
112
+ const { forEachGeneric, forEachInOrder } = require('../base/model');
113
113
  const {
114
114
  dictAdd, dictAddArray, dictForEach, pushToDict,
115
115
  } = require('../base/dictionaries');
@@ -147,6 +147,7 @@ function define( model ) {
147
147
  } = model.$messageFunctions;
148
148
  const {
149
149
  resolveUncheckedPath,
150
+ checkAnnotate,
150
151
  defineAnnotations,
151
152
  } = model.$functions;
152
153
 
@@ -197,11 +198,12 @@ function define( model ) {
197
198
  }
198
199
 
199
200
  // Phase 1: ----------------------------------------------------------------
201
+ // Functions called from top-level: addSource()
200
202
 
201
203
  /**
202
204
  * Add definitions of the given source AST, both CDL and CSN
203
205
  *
204
- * @param {XSN.AST} src
206
+ * @param {XSN.SourceAst} src
205
207
  */
206
208
  function addSource( src ) {
207
209
  // handle sub model from parser
@@ -266,7 +268,7 @@ function define( model ) {
266
268
  return;
267
269
  }
268
270
  setLink( art, '_block', block );
269
- // dictAdd might set $duplicates to true
271
+ // dictAdd might set $duplicates
270
272
  dictAdd( model.definitions, absolute, art );
271
273
  }
272
274
 
@@ -306,7 +308,7 @@ function define( model ) {
306
308
  * declaration.
307
309
  *
308
310
  * @param {XSN.Using} decl Node to be expanded and added to `src`
309
- * @param {XSN.AST} src
311
+ * @param {XSN.SourceAst} src
310
312
  */
311
313
  function addUsing( decl, src ) {
312
314
  if (decl.usings) {
@@ -390,14 +392,17 @@ function define( model ) {
390
392
  setLink( vocab, '_block', block );
391
393
  const { name } = vocab;
392
394
  if (!name.absolute)
393
- name.absolute = prefix + name.path.map( id => id.id ).join('.');
395
+ name.absolute = prefix + pathName( name.path );
394
396
  dictAdd( model.vocabularies, name.absolute, vocab );
395
397
  }
396
398
 
397
399
  // Phase 2 ("init") --------------------------------------------------------
400
+ // Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
401
+ // initVocabulary()
398
402
 
399
403
  function checkRedefinition( art ) {
400
- if (!art.$duplicates)
404
+ if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
405
+ art.$errorReported === 'syntax-duplicate-annotate')
401
406
  return;
402
407
  if (art._main) {
403
408
  error( 'duplicate-definition', [ art.name.location, art ], {
@@ -504,18 +509,6 @@ function define( model ) {
504
509
  definitions[prefix] = parent;
505
510
  initParentLink( parent, definitions );
506
511
  }
507
- if (art.kind !== 'namespace' &&
508
- isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
509
- let p = parent;
510
- while (p && kindProperties[p.kind].artifacts)
511
- p = p._parent;
512
- if (p) {
513
- error( 'subartifacts-not-supported', [ art.name.location, art ],
514
- { art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
515
- // eslint-disable-next-line max-len
516
- 'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
517
- }
518
- }
519
512
  setLink( art, '_parent', parent );
520
513
  if (!parent._subArtifacts)
521
514
  setLink( parent, '_subArtifacts', Object.create(null) );
@@ -611,7 +604,17 @@ function define( model ) {
611
604
  // Either expression (value), expand or new association (target && type)
612
605
  else if (col.value || col.expand || (col.target && col.type)) {
613
606
  setLink( col, '_block', parent._block );
614
- defineAnnotations( col, col, parent._block ); // TODO: complain with inline
607
+ defineAnnotations( col, col, parent._block );
608
+ if (col.inline) { // `@anno elem.{ * }` does not work
609
+ if (col.doc)
610
+ warning( 'syntax-anno-ignored', [ col.doc.location, col ], { '#': 'doc' } );
611
+
612
+ // col.$annotations no available for CSN input, have to search.
613
+ // Warning about first annotation should be enough to avoid spam.
614
+ const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
615
+ if (firstAnno)
616
+ warning( 'syntax-anno-ignored', [ col[firstAnno].name.location, col ] );
617
+ }
615
618
  // TODO: allow sub queries? at least in top-level expand without parallel ref
616
619
  if (columns)
617
620
  initExprForQuery( col.value, parent );
@@ -670,6 +673,8 @@ function define( model ) {
670
673
  * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
671
674
  */
672
675
  function approveExistsInChildren(exprOrPathElement) {
676
+ if (!exprOrPathElement) // may be null in case of parse error
677
+ return;
673
678
  if (exprOrPathElement.$expected === 'exists')
674
679
  exprOrPathElement.$expected = 'approved-exists';
675
680
  // Drill down
@@ -880,9 +885,7 @@ function define( model ) {
880
885
 
881
886
  /**
882
887
  * Set property `_parent` for all elements in `parent` to `parent` and do so
883
- * recursively for all sub elements. Also set the property
884
- * `name.component` of the element with the help of argument `prefix`
885
- * (which is basically the component name of the `parent` element plus a dot).
888
+ * recursively for all sub elements.
886
889
  */
887
890
  // If not for extensions: construct === parent
888
891
  function initMembers( construct, parent, block, initExtensions = false ) {
@@ -1014,6 +1017,8 @@ function define( model ) {
1014
1017
  setMemberParent( elem, name, parent, construct !== parent && prop );
1015
1018
  // console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
1016
1019
  checkRedefinition( elem );
1020
+ if (elem.kind === 'annotate')
1021
+ checkAnnotate( elem, elem );
1017
1022
  defineAnnotations( elem, elem, bl );
1018
1023
  initMembers( elem, elem, bl, initExtensions );
1019
1024
 
@@ -1125,7 +1130,7 @@ function mergeI18nBlocks( model ) {
1125
1130
  * Add the source's translations to the model. Warns if the sources translations
1126
1131
  * do not match the ones from previous sources.
1127
1132
  *
1128
- * @param {XSN.AST} src
1133
+ * @param {XSN.SourceAst} src
1129
1134
  */
1130
1135
  function initI18nFromSource( src ) {
1131
1136
  for (const langKey of Object.keys( src.i18n )) {