@sap/cds-compiler 4.9.6 → 5.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -32,7 +32,7 @@ function isNewCSN( csn, options ) {
32
32
  function checkCSNVersion( csn, options ) {
33
33
  if (!isNewCSN(csn, options)) {
34
34
  // the new transformer works only with new CSN
35
- // eslint-disable-next-line global-require
35
+
36
36
  const { makeMessageFunction } = require('../base/messages');
37
37
  const { error, throwWithAnyError } = makeMessageFunction(csn, options);
38
38
 
@@ -650,7 +650,7 @@ const schema = compileSchema( {
650
650
  prop: '@‹anno›', // which property name do messages use for annotation assignments?
651
651
  type: annotation,
652
652
  // allowed in all definitions except mixins (including columns and extensions)
653
- inKind: _kind => true, // TODO(v5): (kind !== 'mixin')
653
+ inKind: kind => (kind !== 'mixin'),
654
654
  schema: {
655
655
  '-expr': { // '-expr' and '-' must not exist top-level
656
656
  prop: '@‹anno›',
@@ -1495,9 +1495,6 @@ function annotation( val, spec, xsn, csn, name ) {
1495
1495
  if (!id) // `"@": …` is already syntax-unknown-property
1496
1496
  message( 'syntax-invalid-name', location(true), { '#': '{}' } );
1497
1497
 
1498
- if (xsn && xsn.kind === 'mixin') // TODO(v5): Remove, adapt spec['@'].inKind
1499
- message('anno-unexpected-mixin', location(true));
1500
-
1501
1498
  const n = { id, location: location() };
1502
1499
  const r = annoValue( val, spec );
1503
1500
  r.name = n;
@@ -1784,7 +1781,8 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1784
1781
  return { prop, type: ignore };
1785
1782
  return { prop, type: extra };
1786
1783
  }
1787
- // TODO v5: No warning with --sloppy
1784
+ // TODO v6: No warning with --sloppy (does not exist, yet)
1785
+ // Intention: Ignore unknown properties.
1788
1786
  warning( 'syntax-unknown-property', location(true), { prop },
1789
1787
  'Unknown CSN property $(PROP)' );
1790
1788
  return { type: ignore };
@@ -2076,8 +2074,7 @@ function toXsn( csn, filename, options, messageFunctions ) {
2076
2074
  inExtensions = null;
2077
2075
  vocabInDefinitions = null;
2078
2076
 
2079
- const xsn = new XsnSource();
2080
- xsn.$frontend = 'json';
2077
+ const xsn = new XsnSource( 'json' ); // TODO: 'csn'? LSP does not use $frontend
2081
2078
 
2082
2079
  // eslint-disable-next-line object-curly-newline
2083
2080
  ({ message, error, warning, info } = messageFunctions);
@@ -93,8 +93,7 @@ const transformers = {
93
93
  cardinality, // also in pathItem: after 'id', before 'where'
94
94
  targetAspect,
95
95
  target,
96
- $filtered: value, // assoc+filter v4
97
- $enclosed: value, // comp+filter v5
96
+ $enclosed: value, // comp+filter since v5
98
97
  foreignKeys,
99
98
  enum: enumDict,
100
99
  items,
@@ -120,7 +119,7 @@ const transformers = {
120
119
  // definitions, extensions, members ----------------------------------------
121
120
  notNull: value,
122
121
  default: expression,
123
- // targetElement: ignore, // special display of foreign key, renameTo: select
122
+ targetElement, // special display of foreign key
124
123
  value: enumValueOrCalc, // do not list for select items as elements
125
124
  query,
126
125
  elements,
@@ -344,18 +343,24 @@ function sources( srcDict, csn, model ) {
344
343
  let names = model._sources || Object.keys( srcDict );
345
344
  const $sources = names.length && srcDict[names[0]].$sources;
346
345
  if ($sources) {
347
- setHidden( csn, '$sources', $sources );
346
+ setHidden( csn, '$sources', normalize$sources( $sources ) );
348
347
  return undefined;
349
348
  }
350
349
  if (model._sortedSources)
351
350
  names = model._sortedSources.map( s => s.realname );
352
- setHidden( csn, '$sources', (!strictMode) ? names : names.map( relativeName ) );
351
+ names = (!strictMode) ? names : normalize$sources( names.map( relativeName ) );
352
+ setHidden( csn, '$sources', names );
353
353
  return undefined;
354
354
 
355
355
  function relativeName( name ) {
356
356
  const loc = srcDict[name].location;
357
357
  return loc && loc.file || name;
358
358
  }
359
+ function normalize$sources( src ) {
360
+ return strictMode
361
+ ? src.map( name => locationString( name, true ) )
362
+ : src;
363
+ }
359
364
  }
360
365
 
361
366
  function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
@@ -379,6 +384,8 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
379
384
  attachAnnotations( sub, 'elements', elems, inf, entry.returns );
380
385
  else if (many.enum) // make 'enum' annotations appear in 'elements' annotate
381
386
  attachAnnotations( sub, 'elements', many.enum, inf, entry.returns );
387
+ else if (entry.foreignKeys) // make 'foreignKeys' annotations appear in 'elements' annotate
388
+ attachAnnotations( sub, 'elements', entry.foreignKeys, inf );
382
389
  }
383
390
  if (Object.keys( sub ).length)
384
391
  annoDict[name] = sub;
@@ -666,19 +673,19 @@ function returns( art, csn, node, prop ) {
666
673
  return definition( art, csn, node, prop );
667
674
  }
668
675
 
669
- function definition( art, _csn, _node, prop ) {
676
+ function definition( art, csn, _node, prop ) {
670
677
  if (!art || typeof art !== 'object' || art.builtin)
671
678
  return undefined; // TODO: complain with strict
672
679
  // Do not include namespace definitions or inferred construct (in gensrc):
673
680
  if (art.kind === 'namespace' || art.$inferred && gensrcFlavor)
674
681
  return undefined;
675
- if (art.kind === 'key') { // foreignkey
676
- const key = addExplicitAs( { ref: art.targetElement.path.map( pathItem ) },
677
- art.name, neqPath( art.targetElement ) );
678
- if (!art.$inferred)
682
+ if (art.kind === 'key') { // foreign key
683
+ const key = standard( art );
684
+ if (!art.$inferred) // override location; otherwise only alias would be used
679
685
  addLocation( art.targetElement.location, key );
680
686
  return extra( key, art );
681
687
  }
688
+
682
689
  const c = standard( art );
683
690
  // The XSN of actions in extensions do not contain a returns yet - TODO?
684
691
  const elems = c.elements;
@@ -946,7 +953,7 @@ function originRef( art, user ) {
946
953
  const name = parent.name || parent._outer.name;
947
954
  if (name.id && parent.kind !== '$inline' || !r.length)
948
955
  // Return parameter is in XSN - kind: 'param', name.id: ''
949
- // eslint-disable-next-line no-nested-ternary, max-len
956
+ // eslint-disable-next-line no-nested-ternary
950
957
  r.push( !nkind ? name.id : name.id ? { [nkind]: name.id } : { returns: true } );
951
958
  parent = parent._parent;
952
959
  }
@@ -1142,10 +1149,16 @@ function value( node ) {
1142
1149
  return r;
1143
1150
  }
1144
1151
 
1152
+ function targetElement( val, csn, node ) {
1153
+ const key = addExplicitAs( { ref: val.path.map( pathItem ) },
1154
+ node.name, neqPath( val ) );
1155
+ Object.assign(csn, key);
1156
+ }
1157
+
1145
1158
  function enumValueOrCalc( v, csn, node ) {
1146
1159
  if (v.$inferred && (universalCsn || gensrcFlavor))
1147
1160
  return undefined;
1148
- // Enums values in CSN are without outer `value: { … }`:
1161
+ // Enum values in CSN are without outer `value: { … }`:
1149
1162
  if (node.kind === 'enum') {
1150
1163
  Object.assign( csn, expression( v ) );
1151
1164
  }
@@ -20,7 +20,6 @@ const Lexer = require('../gen/languageLexer').default;
20
20
  // Error listener used for ANTLR4-generated parser
21
21
  class ErrorListener extends antlr4.error.ErrorListener {
22
22
  // method which is called by generated parser with --trace-parser[-amg]:
23
- // eslint-disable-next-line class-methods-use-this
24
23
  syntaxError( recognizer, offendingSymbol, line, column, msg, e ) {
25
24
  if (!(e instanceof CompileMessage)) // not already reported
26
25
  // Ignore warning, because only relevant for --trace-parser
@@ -50,7 +49,7 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
50
49
  }
51
50
  else if (t.type === this.NEW) {
52
51
  const n = super.LT(k + 1);
53
- // TODO v5: rewrite token in grammar via `this.setLocalToken`
52
+ // TODO: rewrite token in grammar via `this.setLocalToken`
54
53
  if (n?.type === this.Identifier) {
55
54
  const o = super.LT(k + 2);
56
55
  if (o?.type === this.PAREN)
@@ -84,6 +83,7 @@ function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
84
83
  ts.AT = tokenTypeOf( recognizer, "'@'" );
85
84
  ts.SEMICOLON = tokenTypeOf( recognizer, "';'" );
86
85
  ts.NEW = Parser.NEW;
86
+ ts.RETURNS = Parser.RETURNS;
87
87
  ts.Identifier = Parser.Identifier;
88
88
  ts.PAREN = tokenTypeOf( recognizer, "'('" );
89
89
 
@@ -29,7 +29,6 @@
29
29
  'use strict';
30
30
 
31
31
  const antlr4 = require('antlr4');
32
- // eslint-disable-next-line camelcase
33
32
  const Antlr4LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
34
33
  const { DefaultErrorStrategy } = require('antlr4/src/antlr4/error/ErrorStrategy');
35
34
  const { InputMismatchException } = require('antlr4/src/antlr4/error/Errors');
@@ -447,6 +447,7 @@ function checkExtensionDict( dict ) {
447
447
  for (const prop of Object.keys( dup )) {
448
448
  if (prop.charAt(0) === '@') {
449
449
  this.addAnnotation( def, prop, dup[prop] );
450
+ delete dup[prop]; // we want to keep $duplicates, but not have duplicate props
450
451
  }
451
452
  else if (prop === 'doc') {
452
453
  // With explicit docComment:false, we don't emit a warning.
@@ -455,11 +456,13 @@ function checkExtensionDict( dict ) {
455
456
  'Doc comment is overwritten by another one below' );
456
457
  }
457
458
  def.doc = dup.doc;
459
+ delete dup[prop]; // we want to keep $duplicates for LSP, but not have duplicate props
458
460
  }
459
461
  else if (extensionDicts[prop]) {
460
462
  if (def[prop])
461
463
  this.message( 'syntax-duplicate-annotate', [ def.name.location ], { name, prop } );
462
464
  def[prop] = dup[prop]; // continuation semantics: last wins
465
+ delete dup[prop]; // we want to keep $duplicates for LSP, but not have duplicate props
463
466
  }
464
467
  }
465
468
  if (dup.$annotations) { // update deprecated $annotations for cds-lsp / annotation modeler
@@ -469,7 +472,9 @@ function checkExtensionDict( dict ) {
469
472
  def.$annotations = dup.$annotations;
470
473
  }
471
474
  }
472
- def.$duplicates = null;
475
+
476
+ // We keep duplicate statements for LSP, as it needs to traverse all identifiers;
477
+ // annotations were removed above to avoid traversing annotations twice.
473
478
  }
474
479
  }
475
480
 
@@ -709,14 +714,26 @@ function docComment( node ) {
709
714
  node.doc = this.valueWithTokenLocation( val, token );
710
715
  }
711
716
 
712
- // Classify token (identifier category) for implicit names,
713
- // to be used in the empty alternative to AS <explicitName>.
714
- function classifyImplicitName( category, ref, tokpos = 1 ) {
715
- if (!ref || ref.path && this.getCurrentToken().text !== '.') {
716
- const implicit = this._input.LT( tokpos - 1 || -1 );
717
- if (implicit.isIdentifier)
717
+ /**
718
+ * Classify token (identifier category) for implicit names. To be used in the
719
+ * empty alternative to AS <explicitName>. If `ref` is given, uses the last
720
+ * path segment's `tokenIndex`. The return value can be used to reset the
721
+ * token's category, e.g. for inline select items.
722
+ *
723
+ * @param {string} category
724
+ * @param [ref]
725
+ */
726
+ function classifyImplicitName( category, ref ) {
727
+ if (!ref || ref.path) {
728
+ const tokenIndex = ref?.path[ref.path.length - 1]?.location.tokenIndex;
729
+ const implicit = (tokenIndex === undefined) ? this._input.LT(-1) : this._input.get(tokenIndex);
730
+ if (implicit.isIdentifier) {
731
+ const previous = implicit.isIdentifier;
718
732
  implicit.isIdentifier = category;
733
+ return { token: implicit, previous };
734
+ }
719
735
  }
736
+ return null;
720
737
  }
721
738
 
722
739
  function fragileAlias( ast, safe = false ) {
@@ -942,8 +959,7 @@ function numberLiteral( sign, text = this._input.LT(-1).text ) {
942
959
  token.stop + 1 === nextToken.start &&
943
960
  (nextToken.type === this.constructor.Identifier ||
944
961
  nextToken.type < this.constructor.Identifier && /^[a-z]+$/i.test( nextToken.text ))) {
945
- // TODO: Make it an error in v5
946
- this.warning('syntax-expecting-space', nextToken, {},
962
+ this.message('syntax-expecting-space', nextToken, {},
947
963
  'Expecting a space between a number and a keyword/identifier');
948
964
  }
949
965
 
@@ -1111,7 +1127,8 @@ function pushItem( array, val ) {
1111
1127
 
1112
1128
  // For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
1113
1129
  function reportUnexpectedSpace( prefix = this._input.LT(-1),
1114
- location = this.tokenLocation( this._input.LT(1) ) ) {
1130
+ location = this.tokenLocation( this._input.LT(1) ),
1131
+ isError = false ) {
1115
1132
  const prefixLoc = this.tokenLocation( prefix );
1116
1133
  if (prefixLoc.endLine !== location.line ||
1117
1134
  prefixLoc.endCol !== location.col) {
@@ -1122,8 +1139,14 @@ function reportUnexpectedSpace( prefix = this._input.LT(-1),
1122
1139
  endLine: location.line,
1123
1140
  endCol: location.col,
1124
1141
  };
1125
- this.warning( 'syntax-unexpected-space', wsLocation, { op: prefix.text },
1126
- 'Delete the whitespace after $(OP)' );
1142
+ if (isError) {
1143
+ this.message( 'syntax-invalid-space', wsLocation, { op: prefix.text },
1144
+ 'Delete the whitespace after $(OP)' );
1145
+ }
1146
+ else {
1147
+ this.warning( 'syntax-unexpected-space', wsLocation, { op: prefix.text },
1148
+ 'Delete the whitespace after $(OP)' );
1149
+ }
1127
1150
  }
1128
1151
  return prefixLoc;
1129
1152
  }
@@ -256,7 +256,7 @@ class MultiLineStringParser {
256
256
  try {
257
257
  this.output.push(String.fromCodePoint(n));
258
258
  }
259
- catch (e) {
259
+ catch {
260
260
  // RangeError is thrown if number isn't a valid code point
261
261
  reportInvalidCodePoint();
262
262
  }
@@ -484,7 +484,7 @@ class MultiLineStringParser {
484
484
  * @param {string} code
485
485
  * @private
486
486
  */
487
- _makeCode(code) { // eslint-disable-line class-methods-use-this
487
+ _makeCode(code) {
488
488
  // For characters that may be rendered as newline,
489
489
  // see <https://www.unicode.org/reports/tr14/tr14-32.html>.
490
490
  //
@@ -500,6 +500,7 @@ class MultiLineStringParser {
500
500
  //
501
501
  // For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
502
502
  // U+23CE: ⏎
503
+ // eslint-disable-next-line no-control-regex
503
504
  const allNewLineCharacters = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
504
505
  return code.replace(allNewLineCharacters, '\u{23CE}');
505
506
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /** Whitespace characters without line-breaks. */
4
+ // eslint-disable-next-line no-control-regex
4
5
  const whitespaceRegEx = /[\t\u{000B}\u{000C} \u{00A0}\u{FEFF}\p{Zs}]/u;
5
6
  const cdlNewLineRegEx = /\r\n?|\n|\u2028|\u2029/u;
6
7
 
package/lib/main.d.ts CHANGED
@@ -68,18 +68,22 @@ declare namespace compiler {
68
68
  * additional "extend" or "annotate" statements, but not suitable
69
69
  * for consumption by clients or backends.
70
70
  * - universal : In development (BETA)
71
+ *
72
+ * @default 'client'
71
73
  */
72
74
  csnFlavor?: string | 'client' | 'gensrc' | 'universal'
73
75
  /**
74
- * If set, backends will not create localized convenience views for those views,
75
- * that only have an association to a localized entity/view. Views will only get
76
- * a convenience view, if they themselves contain localized elements (i.e. either
76
+ * If set to false, backends will create localized convenience views for those views,
77
+ * that only have an association to a localized entity/view. If set to true, views will
78
+ * only get a convenience view, if they themselves contain localized elements (i.e. either
77
79
  * have simple projection on localized elements and CDL-casts to a localized element).
78
80
  *
79
- * The OData backend will not set `$localized: true` markers for such cases.
81
+ * If true, the OData backend will not set `$localized: true` markers for such cases.
80
82
  *
81
83
  * Does not work for backends to.hdi(), to.hdbcds() or to.sql() with `sqlDialect: 'hana'`,
82
84
  * since in all those dialects, associations still exist in generated artifacts.
85
+ *
86
+ * @default true
83
87
  */
84
88
  fewerLocalizedViews?: boolean
85
89
  }
@@ -698,10 +702,6 @@ declare namespace compiler {
698
702
  * @param config.noMessageId
699
703
  * If true, will _not_ show the message ID (+ explanation hint) in the output.
700
704
  *
701
- * @param config.idInBrackets
702
- * If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
703
- * This will be the default in cds-compiler v5.
704
- *
705
705
  * @param config.noHome
706
706
  * If true, will _not_ show message's semantic location.
707
707
  *
@@ -714,7 +714,6 @@ declare namespace compiler {
714
714
  export function messageString(msg: CompileMessage, config?: {
715
715
  normalizeFilename?: boolean
716
716
  noMessageId?: boolean
717
- idInBrackets?: boolean
718
717
  noHome?: boolean
719
718
  module?: string
720
719
  }): string;
@@ -1379,6 +1378,26 @@ declare namespace compiler {
1379
1378
  * @private
1380
1379
  */
1381
1380
  function getArtifactName(artifact: object): object;
1381
+
1382
+ type LspSemanticTokenEvent = {
1383
+ event: 'reference' | 'definition',
1384
+ semanticToken: object,
1385
+ hint?: string
1386
+ node?: object
1387
+ }
1388
+ /**
1389
+ * Traverse the given XSN model and yield all _semantic tokens_ that are required by
1390
+ * the LSP. These semantic tokens mostly include _identifiers_, that is, references
1391
+ * or definitions. They also include the `returns` structure, as it is an annotation
1392
+ * target as well.
1393
+ */
1394
+ function traverseSemanticTokens(xsn: object, options: CompileOptions): Generator<LspSemanticTokenEvent>;
1395
+ /**
1396
+ * Given an XSN reference object, e.g. the `semanticToken` value of a `traverseSemanticTokens`
1397
+ * event, return a generator that yields the reference's target and their origins until the
1398
+ * base definition is reached.
1399
+ */
1400
+ function getSemanticTokenOrigin(obj: LspSemanticTokenEvent): Generator<object>;
1382
1401
  }
1383
1402
 
1384
1403
  /**
package/lib/main.js CHANGED
@@ -29,11 +29,9 @@ const define = lazyload('./compiler/define');
29
29
  const builtins = lazyload('./base/builtins');
30
30
  const base = lazyload('./compiler/base');
31
31
  const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');
32
+ const lsp = lazyload('./compiler/lsp-api');
33
+ const meta = lazyload('./base/meta');
32
34
 
33
- // The compiler version (taken from package.json)
34
- function version() {
35
- return require('../package.json').version;
36
- }
37
35
 
38
36
  const toCsn = lazyload('./json/to-csn')
39
37
 
@@ -75,7 +73,7 @@ function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
75
73
  // ATTENTION: Keep in sync with main.d.ts!
76
74
  module.exports = {
77
75
  // Compiler
78
- version,
76
+ version: () => meta.version(),
79
77
  compile: (filenames, dir, options, fileCache) => { // main function
80
78
  traceApi( 'compile', options );
81
79
  return compiler.compileX(filenames, dir, options, fileCache).then(toCsn.compactModel);
@@ -94,7 +92,7 @@ module.exports = {
94
92
  value: messages.CompilationError,
95
93
  writable: false,
96
94
  configurable: false,
97
- enumerable: false
95
+ enumerable: true
98
96
  });
99
97
  return messages.CompilationError;
100
98
  },
@@ -183,7 +181,9 @@ module.exports = {
183
181
  $lsp: {
184
182
  parse: (...args) => compiler.parseX(...args),
185
183
  compile: (...args) => compiler.compileX(...args),
186
- getArtifactName: (...args) => base.getArtifactName(...args),
184
+ getArtifactName: (art) => base.getArtifactName(art),
185
+ traverseSemanticTokens: (xsn, options) => lsp.traverseSemanticTokens(xsn, options),
186
+ getSemanticTokenOrigin: (obj) => lsp.getSemanticTokenOrigin(obj),
187
187
  },
188
188
 
189
189
  // CSN Model related functionality
@@ -201,7 +201,7 @@ module.exports = {
201
201
  function lazyload(moduleName) {
202
202
  let module;
203
203
  return new Proxy(((...args) => {
204
- if (!module) // eslint-disable-next-line global-require
204
+ if (!module)
205
205
  module = require(moduleName);
206
206
 
207
207
  if (module.apply && typeof module.apply === 'function')
@@ -209,7 +209,7 @@ function lazyload(moduleName) {
209
209
  return module; // for destructured calls
210
210
  }), {
211
211
  get(target, name) {
212
- if (!module) // eslint-disable-next-line global-require
212
+ if (!module)
213
213
  module = require(moduleName);
214
214
 
215
215
  return module[name];
@@ -30,6 +30,7 @@ const internalCsnProps = {
30
30
  $tableConstraints: shallowCopy,
31
31
  $default: shallowCopy, // used for HANA CSN migrations
32
32
  $notNull: shallowCopy, // used for HANA CSN migrations
33
+ $sqlService: shallowCopy,
33
34
  };
34
35
  const internalEnumerableCsnProps = {
35
36
  __proto__: null,
@@ -448,7 +448,7 @@ function csnRefs( csn, universalReady ) {
448
448
  if (!step)
449
449
  return null;
450
450
  if (!effectiveType( art ))
451
- throw new TypeError( 'Cyclic type definition' );
451
+ throw new ModelError( 'Cyclic type definition' );
452
452
  if (typeof step === 'string')
453
453
  return navigationEnv( art, true ).elements[step];
454
454
 
@@ -511,6 +511,8 @@ function csnRefs( csn, universalReady ) {
511
511
 
512
512
  function initNode( art, parent, kind, name ) {
513
513
  setCache( art, '_parent', parent );
514
+ if (art.keys)
515
+ setCache(art, '_keys', getKeysDict( art ));
514
516
  if (kind === 'target') {
515
517
  // Prevent re-initialization of anonymous aspect with initDefinition():
516
518
  // (that would be with parent: null which would be wrong)
@@ -650,9 +652,12 @@ function csnRefs( csn, universalReady ) {
650
652
  return resolvePath( path, elemParent.elements[head], null, 'query' );
651
653
  }
652
654
  if (!query) { // outside queries - TODO: items?
653
- let art = parent.elements[head];
654
- // Ref to up_ in anonymous aspect
655
- if (!art && head === 'up_') {
655
+ let art = parent.elements?.[head];
656
+ if (parent.keys) {
657
+ const keysDict = getCache( parent, '_keys' );
658
+ art = keysDict[head];
659
+ } // Ref to up_ in anonymous aspect
660
+ else if (!art && head === 'up_') {
656
661
  const up = getCache( parent, '_parent' );
657
662
  const target = up && typeof up.target === 'string' && csn.definitions[up.target];
658
663
  if (target && target.elements) {
@@ -989,6 +994,17 @@ function csnRefs( csn, universalReady ) {
989
994
  }
990
995
  }
991
996
 
997
+ /**
998
+ * Foreign keys are stored in an array; for easier name resolution, create
999
+ * a dictionary of them.
1000
+ */
1001
+ function getKeysDict( art ) {
1002
+ const dict = Object.create(null);
1003
+ for (const key of art.keys)
1004
+ dict[key.as || implicitAs( key.ref )] = key;
1005
+ return dict;
1006
+ }
1007
+
992
1008
  /**
993
1009
  * Return value of a query SELECT for the query node, or the main artifact,
994
1010
  * i.e. a value with an `elements` property.
@@ -1180,7 +1196,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1180
1196
 
1181
1197
  const prop = csnPath[index];
1182
1198
  if (refCtx === 'annotation' && typeof obj === 'object') {
1183
- // we do not know yet whether the annotation value is a expression or not →
1199
+ // we do not know yet whether the annotation value is an expression or not →
1184
1200
  // loop over outer array and records (structure values):
1185
1201
  if (Array.isArray( obj ) || !isAnnotationExpression( obj )) {
1186
1202
  obj = obj[prop];
@@ -1308,6 +1324,7 @@ module.exports = {
1308
1324
  traverseQuery,
1309
1325
  artifactProperties,
1310
1326
  implicitAs,
1327
+ getKeysDict,
1311
1328
  analyseCsnPath,
1312
1329
  pathId,
1313
1330
  columnAlias,
@@ -12,7 +12,6 @@ const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
12
12
  const { ModelError, CompilerAssertion } = require('../base/error');
13
13
  const { typeParameters } = require('../compiler/builtins');
14
14
  const { forEach } = require('../utils/objectUtils');
15
- const { version } = require('../../package.json');
16
15
  const { cloneAnnotationValue } = require('./cloneCsn');
17
16
 
18
17
  // Low-level utility functions to work with compact CSN.
@@ -706,7 +705,6 @@ function isEdmPropertyRendered( elementCsn, options ) {
706
705
  * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
707
706
  * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
708
707
  */
709
- // eslint-disable-next-line no-unused-vars
710
708
  function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect = 'plain' ) {
711
709
  if (csn && typeof csn === 'object' && csn.definitions) {
712
710
  isValidMappingDialectCombi(sqlDialect, sqlMapping);
@@ -826,7 +824,6 @@ function isValidMappingDialectCombi( sqlDialect, sqlMapping ) {
826
824
  * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
827
825
  * @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
828
826
  */
829
- // eslint-disable-next-line no-unused-vars
830
827
  function getElementDatabaseNameOf( elemName, sqlMapping, sqlDialect = 'plain' ) {
831
828
  isValidMappingDialectCombi(sqlDialect, sqlMapping);
832
829
  if (sqlMapping === 'hdbcds')
@@ -948,16 +945,6 @@ function isPersistedAsTable( artifact ) {
948
945
  !hasAnnotationValue(artifact, '@cds.persistence.exists');
949
946
  }
950
947
 
951
- /**
952
- * Central generated by cds-compiler string generator function without further decoration
953
- * for unified tagging of generated content
954
- *
955
- * @returns {string} String containing compiler version that was used to generate content
956
- */
957
- function generatedByCompilerVersion() {
958
- return `generated by cds-compiler version ${ version }`;
959
- }
960
-
961
948
  /**
962
949
  * Return the projection to look like a query.
963
950
  *
@@ -1440,7 +1427,6 @@ module.exports = {
1440
1427
  isPersistedOnDatabase,
1441
1428
  isPersistedAsView,
1442
1429
  isPersistedAsTable,
1443
- generatedByCompilerVersion,
1444
1430
  getNormalizedQuery,
1445
1431
  getRootArtifactName,
1446
1432
  getLastPartOfRef,
@@ -73,7 +73,6 @@ function revealInternalProperties( model, nameOrPath ) {
73
73
  artifacts: artifactDictionary,
74
74
  definitions: artifactDictionary,
75
75
  vocabularies: dictionary,
76
- $lateExtensions: dictionary,
77
76
  elements,
78
77
  columns,
79
78
  expand: columns,
@@ -385,7 +384,7 @@ function quoted( name, undef = '‹undefined›' ) {
385
384
  // To be used for tracing, e.g. by
386
385
  // require('../model/revealInternalProperties').log(model, 'E_purposes')
387
386
  function logXsnModel( model, name ) {
388
- // eslint-disable-next-line no-console, global-require
387
+ // eslint-disable-next-line no-console
389
388
  console.log( require('util').inspect( revealInternalProperties( model, name ), false, null ) );
390
389
  }
391
390
 
@@ -67,7 +67,7 @@ function validateCsnVersions(beforeModel, afterModel, options) {
67
67
  }
68
68
  if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
69
69
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
70
- // eslint-disable-next-line global-require
70
+
71
71
  const { version } = require('../../package.json');
72
72
  error(null, null, { value: afterVersion, othervalue: beforeVersion, version },
73
73
  'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
@@ -257,17 +257,19 @@ function getExtensionAndMigrations(beforeModel, options, {
257
257
  function getDeletions(afterModel, options, { deletions }) {
258
258
  return function compareArtifacts(artifact, name) {
259
259
  const otherArtifact = afterModel.definitions[name];
260
- const isPersisted = isPersistedAsTable(artifact);
261
- const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
262
-
263
- // Looking for deleted entities only.
264
- if (isPersisted && !isPersistedOther) {
260
+ const isPersistedTable = isPersistedAsTable(artifact);
261
+ const isPersistedView = isPersistedAsView(artifact);
262
+ const isPersistedTableOther = otherArtifact && isPersistedAsTable(otherArtifact);
263
+ const isPersistedViewOther = otherArtifact && isPersistedAsView(otherArtifact);
264
+
265
+ // Looking for deleted entities or table -> view / view -> table
266
+ if (
267
+ (isPersistedTable && isPersistedViewOther) || // table -> view
268
+ (isPersistedView && isPersistedTableOther) || // view -> table
269
+ ((isPersistedTable || isPersistedView) && // deleted
270
+ !(isPersistedTableOther || isPersistedViewOther))
271
+ ) // view turned into table - need to render a drop for the view
265
272
  deletions[name] = artifact;
266
- }
267
- // eslint-disable-next-line sonarjs/no-duplicated-branches
268
- else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
269
- deletions[name] = artifact;
270
- }
271
273
  };
272
274
  }
273
275