@sap/cds-compiler 4.9.4 → 5.0.6

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 (87) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +15 -11
  4. package/bin/cdshi.js +1 -0
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +6 -18
  7. package/lib/api/options.js +3 -11
  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 +29 -29
  12. package/lib/base/messages.js +22 -26
  13. package/lib/base/model.js +0 -2
  14. package/lib/base/node-helpers.js +0 -1
  15. package/lib/checks/enricher.js +1 -5
  16. package/lib/checks/structuredAnnoExpressions.js +30 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +4 -1
  19. package/lib/compiler/base.js +1 -1
  20. package/lib/compiler/builtins.js +18 -2
  21. package/lib/compiler/checks.js +2 -5
  22. package/lib/compiler/define.js +7 -7
  23. package/lib/compiler/extend.js +68 -33
  24. package/lib/compiler/generate.js +1 -1
  25. package/lib/compiler/index.js +23 -6
  26. package/lib/compiler/lsp-api.js +501 -2
  27. package/lib/compiler/populate.js +2 -2
  28. package/lib/compiler/propagator.js +1 -4
  29. package/lib/compiler/resolve.js +2 -15
  30. package/lib/compiler/shared.js +112 -31
  31. package/lib/compiler/tweak-assocs.js +2 -16
  32. package/lib/compiler/utils.js +2 -1
  33. package/lib/compiler/xsn-model.js +4 -0
  34. package/lib/edm/annotations/genericTranslation.js +95 -42
  35. package/lib/edm/csn2edm.js +16 -4
  36. package/lib/edm/edm.js +2 -3
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  38. package/lib/edm/edmPreprocessor.js +1 -7
  39. package/lib/gen/Dictionary.json +29 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +2 -1
  42. package/lib/gen/languageParser.js +4995 -4817
  43. package/lib/json/csnVersion.js +1 -1
  44. package/lib/json/from-csn.js +4 -7
  45. package/lib/json/to-csn.js +23 -12
  46. package/lib/language/antlrParser.js +2 -2
  47. package/lib/language/errorStrategy.js +0 -1
  48. package/lib/language/genericAntlrParser.js +35 -12
  49. package/lib/language/multiLineStringParser.js +3 -2
  50. package/lib/language/textUtils.js +1 -0
  51. package/lib/main.d.ts +28 -9
  52. package/lib/main.js +7 -4
  53. package/lib/model/csnRefs.js +20 -4
  54. package/lib/model/csnUtils.js +0 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +1 -1
  57. package/lib/optionProcessor.js +28 -9
  58. package/lib/render/manageConstraints.js +1 -1
  59. package/lib/render/toCdl.js +36 -7
  60. package/lib/render/toSql.js +1 -0
  61. package/lib/render/utils/common.js +12 -9
  62. package/lib/render/utils/stringEscapes.js +1 -0
  63. package/lib/transform/db/applyTransformations.js +13 -8
  64. package/lib/transform/db/associations.js +62 -54
  65. package/lib/transform/db/expansion.js +1 -6
  66. package/lib/transform/db/flattening.js +89 -111
  67. package/lib/transform/db/temporal.js +3 -4
  68. package/lib/transform/db/views.js +0 -1
  69. package/lib/transform/draft/odata.js +51 -3
  70. package/lib/transform/effective/annotations.js +3 -2
  71. package/lib/transform/effective/flattening.js +135 -0
  72. package/lib/transform/effective/main.js +6 -6
  73. package/lib/transform/effective/types.js +13 -9
  74. package/lib/transform/forOdata.js +0 -2
  75. package/lib/transform/forRelationalDB.js +0 -19
  76. package/lib/transform/localized.js +7 -8
  77. package/lib/transform/odata/flattening.js +39 -31
  78. package/lib/transform/odata/typesExposure.js +5 -17
  79. package/lib/transform/transformUtils.js +1 -1
  80. package/lib/transform/translateAssocsToJoins.js +21 -3
  81. package/lib/utils/file.js +13 -7
  82. package/lib/utils/moduleResolve.js +59 -8
  83. package/lib/utils/term.js +3 -2
  84. package/package.json +7 -3
  85. package/share/messages/message-explanations.json +2 -0
  86. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  87. 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 ) {
@@ -666,19 +671,19 @@ function returns( art, csn, node, prop ) {
666
671
  return definition( art, csn, node, prop );
667
672
  }
668
673
 
669
- function definition( art, _csn, _node, prop ) {
674
+ function definition( art, csn, _node, prop ) {
670
675
  if (!art || typeof art !== 'object' || art.builtin)
671
676
  return undefined; // TODO: complain with strict
672
677
  // Do not include namespace definitions or inferred construct (in gensrc):
673
678
  if (art.kind === 'namespace' || art.$inferred && gensrcFlavor)
674
679
  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)
680
+ if (art.kind === 'key') { // foreign key
681
+ const key = standard( art );
682
+ if (!art.$inferred) // override location; otherwise only alias would be used
679
683
  addLocation( art.targetElement.location, key );
680
684
  return extra( key, art );
681
685
  }
686
+
682
687
  const c = standard( art );
683
688
  // The XSN of actions in extensions do not contain a returns yet - TODO?
684
689
  const elems = c.elements;
@@ -946,7 +951,7 @@ function originRef( art, user ) {
946
951
  const name = parent.name || parent._outer.name;
947
952
  if (name.id && parent.kind !== '$inline' || !r.length)
948
953
  // Return parameter is in XSN - kind: 'param', name.id: ''
949
- // eslint-disable-next-line no-nested-ternary, max-len
954
+ // eslint-disable-next-line no-nested-ternary
950
955
  r.push( !nkind ? name.id : name.id ? { [nkind]: name.id } : { returns: true } );
951
956
  parent = parent._parent;
952
957
  }
@@ -1142,10 +1147,16 @@ function value( node ) {
1142
1147
  return r;
1143
1148
  }
1144
1149
 
1150
+ function targetElement( val, csn, node ) {
1151
+ const key = addExplicitAs( { ref: val.path.map( pathItem ) },
1152
+ node.name, neqPath( val ) );
1153
+ Object.assign(csn, key);
1154
+ }
1155
+
1145
1156
  function enumValueOrCalc( v, csn, node ) {
1146
1157
  if (v.$inferred && (universalCsn || gensrcFlavor))
1147
1158
  return undefined;
1148
- // Enums values in CSN are without outer `value: { … }`:
1159
+ // Enum values in CSN are without outer `value: { … }`:
1149
1160
  if (node.kind === 'enum') {
1150
1161
  Object.assign( csn, expression( v ) );
1151
1162
  }
@@ -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,6 +29,7 @@ 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');
32
33
 
33
34
  // The compiler version (taken from package.json)
34
35
  function version() {
@@ -94,7 +95,7 @@ module.exports = {
94
95
  value: messages.CompilationError,
95
96
  writable: false,
96
97
  configurable: false,
97
- enumerable: false
98
+ enumerable: true
98
99
  });
99
100
  return messages.CompilationError;
100
101
  },
@@ -183,7 +184,9 @@ module.exports = {
183
184
  $lsp: {
184
185
  parse: (...args) => compiler.parseX(...args),
185
186
  compile: (...args) => compiler.compileX(...args),
186
- getArtifactName: (...args) => base.getArtifactName(...args),
187
+ getArtifactName: (art) => base.getArtifactName(art),
188
+ traverseSemanticTokens: (xsn, options) => lsp.traverseSemanticTokens(xsn, options),
189
+ getSemanticTokenOrigin: (obj) => lsp.getSemanticTokenOrigin(obj),
187
190
  },
188
191
 
189
192
  // CSN Model related functionality
@@ -201,7 +204,7 @@ module.exports = {
201
204
  function lazyload(moduleName) {
202
205
  let module;
203
206
  return new Proxy(((...args) => {
204
- if (!module) // eslint-disable-next-line global-require
207
+ if (!module)
205
208
  module = require(moduleName);
206
209
 
207
210
  if (module.apply && typeof module.apply === 'function')
@@ -209,7 +212,7 @@ function lazyload(moduleName) {
209
212
  return module; // for destructured calls
210
213
  }), {
211
214
  get(target, name) {
212
- if (!module) // eslint-disable-next-line global-require
215
+ if (!module)
213
216
  module = require(moduleName);
214
217
 
215
218
  return module[name];
@@ -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];
@@ -706,7 +706,6 @@ function isEdmPropertyRendered( elementCsn, options ) {
706
706
  * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
707
707
  * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
708
708
  */
709
- // eslint-disable-next-line no-unused-vars
710
709
  function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect = 'plain' ) {
711
710
  if (csn && typeof csn === 'object' && csn.definitions) {
712
711
  isValidMappingDialectCombi(sqlDialect, sqlMapping);
@@ -826,7 +825,6 @@ function isValidMappingDialectCombi( sqlDialect, sqlMapping ) {
826
825
  * @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
827
826
  * @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
828
827
  */
829
- // eslint-disable-next-line no-unused-vars
830
828
  function getElementDatabaseNameOf( elemName, sqlMapping, sqlDialect = 'plain' ) {
831
829
  isValidMappingDialectCombi(sqlDialect, sqlMapping);
832
830
  if (sqlMapping === 'hdbcds')
@@ -385,7 +385,7 @@ function quoted( name, undef = '‹undefined›' ) {
385
385
  // To be used for tracing, e.g. by
386
386
  // require('../model/revealInternalProperties').log(model, 'E_purposes')
387
387
  function logXsnModel( model, name ) {
388
- // eslint-disable-next-line no-console, global-require
388
+ // eslint-disable-next-line no-console
389
389
  console.log( require('util').inspect( revealInternalProperties( model, name ), false, null ) );
390
390
  }
391
391
 
@@ -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?');
@@ -1,3 +1,11 @@
1
+ // Compiler options
2
+
3
+ // Remarks:
4
+ // - The specification is client-tool centric (bin/cdsc.js):
5
+ // an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
6
+ // - Also list the option in the `help` text, used with `cdsc -h`.
7
+ // - Specify valid values for non-boolean options in lib/api/validate.js.
8
+
1
9
  'use strict';
2
10
 
3
11
  const { createOptionProcessor } = require('./base/optionProcessorHelper');
@@ -34,7 +42,7 @@ optionProcessor
34
42
  .option(' --beta <list>')
35
43
  .option(' --deprecated <list>')
36
44
  .option(' --direct-backend')
37
- .option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
45
+ .option(' --fallback-parser <type>', { valid: [ 'auto!', 'cdl', 'csn', 'csn!' ] })
38
46
  .option(' --shuffle <seed>') // 0 | 1..4294967296
39
47
  .option(' --test-mode')
40
48
  .option(' --test-sort-csn')
@@ -117,9 +125,10 @@ optionProcessor
117
125
  eagerPersistenceForGeneratedEntities
118
126
  --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
119
127
  parser as a fallback. Valid values are:
120
- cdl : Use CDL parser
121
- csn : Use CSN parser
122
- csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
128
+ cdl : Use CDL parser
129
+ csn : Use CSN parser
130
+ csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
131
+ auto! : Ignore file extension; use CSN parser if file content starts with '{'
123
132
  --direct-backend Do not compile the given CSN but directly pass it to the backend.
124
133
  Can only be used with certain new CSN based backends. Combination with
125
134
  other flags is limited, e.g. --test-mode will not run a consistency check.
@@ -161,6 +170,8 @@ optionProcessor
161
170
  Environment variables
162
171
  NO_COLOR If set, compiler messages (/output) will not be colored.
163
172
  Can be overwritten by '--color'
173
+ FORCE_COLOR If set, compiler messages (/output) will be colored. Overrides NO_COLOR.
174
+ Can be overwritten by '--color'
164
175
  CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
165
176
  CDSC_TRACE_API If set, additional API calling information is printed to stderr.
166
177
  `);
@@ -318,9 +329,9 @@ optionProcessor.command('Q, toSql')
318
329
  .option(' --pre2134ReferentialConstraintNames')
319
330
  .option(' --disable-hana-comments')
320
331
  .option(' --generated-by-comment')
321
- .option(' --better-sqlite-session-variables')
332
+ .option(' --better-sqlite-session-variables <bool>')
322
333
  .option(' --fewer-localized-views')
323
- .option(' --without-hana-associations')
334
+ .option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
324
335
  .help(`
325
336
  Usage: cdsc toSql [options] <files...>
326
337
 
@@ -372,11 +383,17 @@ optionProcessor.command('Q, toSql')
372
383
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
373
384
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
374
385
  --generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
375
- --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
376
- active if sqlDialect is \`sqlite\`
386
+ --better-sqlite-session-variables <bool>
387
+ Enable better-sqlite compatible rendering of $user. Only
388
+ active if sqlDialect is \`sqlite\`:
389
+ true : (default) Render better-sqlite session_context(…)
390
+ false : Render session variables as string literals, used e.g. with sqlite3 driver
377
391
  --fewer-localized-views If set, the backends will not create localized convenience views for
378
392
  those views, that only have an association to a localized entity/view.
379
- --without-hana-associations If set, the backend will not render a "WITH ASSOCIATIONS" for sqlDialect 'hana'
393
+ --with-hana-associations <bool>
394
+ Enable and disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
395
+ true : (default) Render "WITH ASSOCIATIONS"
396
+ false : Do not render "WITH ASSOCIATIONS"
380
397
  `);
381
398
 
382
399
  optionProcessor.command('toRename')
@@ -482,6 +499,7 @@ optionProcessor.command('toCsn')
482
499
  optionProcessor.command('parseCdl')
483
500
  .option('-h, --help')
484
501
  .positionalArgument('<file>')
502
+ .option(' --with-locations')
485
503
  .help(`
486
504
  Usage: cdsc parseCdl [options] <file>
487
505
 
@@ -489,6 +507,7 @@ optionProcessor.command('parseCdl')
489
507
  resolve imports, apply extensions or expand any queries.
490
508
 
491
509
  Options
510
+ --with-locations Add $location to CSN artifacts.
492
511
  -h, --help Show this help text
493
512
  `);
494
513
 
@@ -58,7 +58,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
58
58
  // TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
59
59
  function _transformSqlOptions( csn, options ) {
60
60
  const { src } = options;
61
- // eslint-disable-next-line global-require
61
+
62
62
  const prepareOptions = require('../api/options');
63
63
  options = prepareOptions.to.sql(options);
64
64
  options.src = src;