@sap/cds-compiler 4.7.4 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/bin/cdsc.js +15 -1
  3. package/bin/cdshi.js +13 -3
  4. package/doc/CHANGELOG_BETA.md +5 -1
  5. package/lib/api/main.js +61 -23
  6. package/lib/api/options.js +40 -0
  7. package/lib/base/builtins.js +89 -0
  8. package/lib/base/keywords.js +5 -1
  9. package/lib/base/location.js +91 -14
  10. package/lib/base/message-registry.js +50 -33
  11. package/lib/base/messages.js +71 -16
  12. package/lib/base/model.js +0 -2
  13. package/lib/checks/actionsFunctions.js +1 -1
  14. package/lib/checks/elements.js +2 -1
  15. package/lib/checks/enricher.js +2 -2
  16. package/lib/checks/queryNoDbArtifacts.js +2 -1
  17. package/lib/checks/utils.js +1 -1
  18. package/lib/checks/validator.js +6 -22
  19. package/lib/compiler/assert-consistency.js +3 -5
  20. package/lib/compiler/builtins.js +0 -74
  21. package/lib/compiler/checks.js +61 -11
  22. package/lib/compiler/define.js +3 -3
  23. package/lib/compiler/extend.js +2 -2
  24. package/lib/compiler/index.js +9 -9
  25. package/lib/compiler/populate.js +13 -5
  26. package/lib/compiler/propagator.js +3 -0
  27. package/lib/compiler/resolve.js +6 -20
  28. package/lib/compiler/shared.js +1 -1
  29. package/lib/compiler/tweak-assocs.js +2 -2
  30. package/lib/compiler/utils.js +3 -3
  31. package/lib/compiler/{classes.js → xsn-model.js} +0 -16
  32. package/lib/edm/annotations/edmJson.js +7 -5
  33. package/lib/edm/annotations/genericTranslation.js +113 -55
  34. package/lib/edm/csn2edm.js +25 -9
  35. package/lib/edm/edm.js +3 -3
  36. package/lib/edm/edmInboundChecks.js +24 -5
  37. package/lib/edm/edmPreprocessor.js +46 -20
  38. package/lib/edm/edmUtils.js +3 -16
  39. package/lib/gen/Dictionary.json +9 -0
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +1941 -1850
  43. package/lib/json/csnVersion.js +7 -4
  44. package/lib/json/from-csn.js +8 -7
  45. package/lib/json/to-csn.js +12 -7
  46. package/lib/language/antlrParser.js +1 -1
  47. package/lib/language/genericAntlrParser.js +9 -10
  48. package/lib/language/multiLineStringParser.js +2 -2
  49. package/lib/language/textUtils.js +1 -1
  50. package/lib/main.d.ts +23 -0
  51. package/lib/main.js +8 -1
  52. package/lib/model/cloneCsn.js +15 -6
  53. package/lib/model/csnRefs.js +141 -35
  54. package/lib/model/csnUtils.js +1 -4
  55. package/lib/model/enrichCsn.js +1 -1
  56. package/lib/modelCompare/compare.js +106 -92
  57. package/lib/optionProcessor.js +23 -1
  58. package/lib/render/toCdl.js +3 -2
  59. package/lib/render/toHdbcds.js +4 -48
  60. package/lib/render/toSql.js +6 -3
  61. package/lib/transform/addTenantFields.js +58 -35
  62. package/lib/transform/db/applyTransformations.js +1 -1
  63. package/lib/transform/db/expansion.js +3 -0
  64. package/lib/transform/db/flattening.js +71 -46
  65. package/lib/transform/db/views.js +1 -4
  66. package/lib/transform/draft/odata.js +16 -17
  67. package/lib/transform/effective/main.js +6 -3
  68. package/lib/transform/effective/misc.js +18 -8
  69. package/lib/transform/effective/types.js +4 -3
  70. package/lib/transform/forOdata.js +8 -7
  71. package/lib/transform/forRelationalDB.js +103 -112
  72. package/lib/transform/odata/flattening.js +82 -44
  73. package/lib/transform/odata/toFinalBaseType.js +9 -25
  74. package/lib/transform/odata/typesExposure.js +28 -15
  75. package/lib/transform/parseExpr.js +0 -3
  76. package/lib/transform/transformUtils.js +12 -8
  77. package/lib/transform/translateAssocsToJoins.js +2 -2
  78. package/lib/transform/universalCsn/coreComputed.js +2 -1
  79. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -1
  80. package/package.json +2 -2
  81. package/share/messages/README.md +4 -0
  82. package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/def-duplicate-autoexposed.md +1 -1
  85. package/share/messages/extend-repeated-intralayer.md +3 -16
  86. package/share/messages/extend-unrelated-layer.md +1 -1
  87. package/share/messages/message-explanations.json +1 -0
  88. package/share/messages/redirected-to-ambiguous.md +1 -1
  89. package/share/messages/redirected-to-complex.md +1 -1
  90. package/share/messages/redirected-to-unrelated.md +1 -1
  91. package/share/messages/rewrite-not-supported.md +1 -1
  92. package/share/messages/syntax-expecting-unsigned-int.md +2 -2
  93. package/share/messages/type-missing-enum-value.md +59 -0
  94. package/share/messages/wildcard-excluding-one.md +1 -1
@@ -75,7 +75,7 @@ const forRelationalDBArtifactValidators = [
75
75
  // sql.prepend/append
76
76
  checkSqlAnnotationOnArtifact,
77
77
  // strip down CSN to reduce it's size by removing non-sql relevant parts
78
- stripNonDbRelevant,
78
+ deleteBoundActions,
79
79
  ];
80
80
 
81
81
  const forRelationalDBCsnValidators = [
@@ -282,33 +282,17 @@ function forOdata( csn, that ) {
282
282
  });
283
283
  }
284
284
 
285
- const dbRelevantKinds = {
286
- entity: true,
287
- type: true,
288
- aspect: true,
289
- service: true,
290
- context: true,
291
- };
292
-
293
285
  /**
294
286
  * Shrink the CSN by
295
287
  * - deleting bound actions
296
- * - turning all non-entity/type/aspect/service/context entities into dummies
288
+ *
289
+ * Artifacts can only be shrunk later, when types are resolved
297
290
  *
298
291
  * @param {CSN.Artifact} artifact
299
- * @param {string} artifactName
300
292
  */
301
- function stripNonDbRelevant( artifact, artifactName ) {
302
- if (this.options.transformation !== 'effective') {
303
- if ( !dbRelevantKinds[artifact.kind] ) {
304
- this.csn.definitions[artifactName] = {
305
- kind: 'action',
306
- };
307
- }
308
- else if (artifact.actions) {
309
- delete artifact.actions;
310
- }
311
- }
293
+ function deleteBoundActions( artifact ) {
294
+ if (this.options.transformation !== 'effective')
295
+ delete artifact.actions;
312
296
  }
313
297
 
314
298
  module.exports = { forRelationalDB, forOdata };
@@ -66,10 +66,9 @@
66
66
 
67
67
  'use strict';
68
68
 
69
+ const { Location } = require('../base/location');
69
70
  const { locationString, hasErrors } = require('../base/messages');
70
- const {
71
- XsnSource, XsnName, XsnArtifact, CsnLocation,
72
- } = require('./classes');
71
+ const { XsnSource, XsnName, XsnArtifact } = require('./xsn-model');
73
72
 
74
73
 
75
74
  // Properties that can appear where a type can have type arguments.
@@ -135,7 +134,7 @@ function assertConsistency( model, stage ) {
135
134
  // with syntax errors (currently even internal artifacts like $using):
136
135
  isRequired: parent => noSyntaxErrors() || parent && parent.kind,
137
136
  kind: true,
138
- instanceOf: CsnLocation,
137
+ instanceOf: Location,
139
138
  requires: [ 'file' ], // line is optional in top-level location
140
139
  optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
141
140
  schema: {
@@ -317,7 +316,6 @@ function assertConsistency( model, stage ) {
317
316
  'kind', 'name', '_block', '_parent', '_main', 'elements',
318
317
  '_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
319
318
  '$duplicates', // duplicate query in FROM clause
320
- '$inferred', // table alias with $inferred: '$internal'
321
319
  ],
322
320
  },
323
321
  none: { optional: () => true }, // parse error
@@ -64,18 +64,6 @@ const typeParameters = {
64
64
  // a.k.a "typeProperties"
65
65
  typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
66
66
 
67
- /**
68
- * Properties that are required next to `=` to make an annotation value an actual expression
69
- * and not some foreign structure.
70
- *
71
- * @type {string[]}
72
- */
73
- const xprInAnnoProperties = [
74
- 'ref', 'xpr', 'list', 'literal', 'val',
75
- '#', 'func', 'args', 'SELECT', 'SET',
76
- 'cast',
77
- ];
78
-
79
67
 
80
68
  // const hana = {
81
69
  // BinaryFloat: {},
@@ -87,15 +75,6 @@ const xprInAnnoProperties = [
87
75
  // hana: { kind: 'context' },
88
76
  // };
89
77
 
90
- /**
91
- * Functions without parentheses in CDL (common standard SQL-92 functions)
92
- * (do not add more - make it part of the SQL renderer to remove parentheses for
93
- * other funny SQL functions like CURRENT_UTCTIMESTAMP).
94
- */
95
- const functionsWithoutParens = [
96
- 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
97
- 'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
98
- ];
99
78
 
100
79
  const specialFunctions = compileFunctions( {
101
80
  '': [ // the default
@@ -317,13 +296,6 @@ function checkDate( year, month, day ) {
317
296
  return !Number.isNaN( year ) && month > 0 && month < 13 && day > 0 && day < 32;
318
297
  }
319
298
 
320
- /**
321
- * Return whether JSON object `val` is a representation for an annotation expression
322
- */
323
- function isAnnotationExpression( val ) {
324
- return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
325
- }
326
-
327
299
  /**
328
300
  * Check that the given time is within boundaries.
329
301
  * Checks according to ISO 8601.
@@ -371,44 +343,6 @@ function isGeoTypeName( typeName ) {
371
343
  return typeCategories.geo.includes( typeName );
372
344
  }
373
345
 
374
- /**
375
- * Checks whether the given absolute path is inside a reserved namespace.
376
- *
377
- * @param {string} absolute
378
- * @returns {boolean}
379
- */
380
- function isInReservedNamespace( absolute ) {
381
- return absolute === 'cds' || absolute.startsWith( 'cds.' ) &&
382
- !absolute.match( /^cds\.foundation(\.|$)/ ) &&
383
- !absolute.match( /^cds\.outbox(\.|$)/ ) && // Requested by Node runtime
384
- !absolute.match( /^cds\.xt(\.|$)/ ); // Requested by Mtx
385
- }
386
-
387
- /**
388
- * Tell if a type is (directly) a builtin type
389
- * Note that in CSN builtins are not in the definition of the model, so we can only
390
- * check against their absolute names. Builtin types are "cds.<something>", i.e. they
391
- * are directly in 'cds', but not for example in 'cds.foundation'.
392
- *
393
- * TODO: Handle `{ ref: [ "cds.Integer" ] }`
394
- *
395
- * @param {string} type
396
- * @returns {boolean}
397
- */
398
- function isBuiltinType( type ) {
399
- return typeof type === 'string' && isInReservedNamespace( type );
400
- }
401
-
402
- /**
403
- * Tell if a name is a magic variable
404
- *
405
- * @param {string} name
406
- * @returns {boolean}
407
- */
408
- function isMagicVariable( name ) {
409
- return typeof name === 'string' && Object.prototype.hasOwnProperty.call(magicVariables, name);
410
- }
411
-
412
346
  /**
413
347
  * Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
414
348
  * `definitions` of the XSN model as well as to `$builtins`.
@@ -424,8 +358,6 @@ function initBuiltins( model ) {
424
358
 
425
359
  // Also add the core artifacts to model.definitions`
426
360
  const c = { ...core };
427
- if (!isBetaEnabled( model.options, 'vectorType' ))
428
- delete c.Vector;
429
361
  model.$builtins = env( c, 'cds.', cds );
430
362
  model.$builtins.cds = cds;
431
363
 
@@ -541,14 +473,8 @@ function initBuiltins( model ) {
541
473
 
542
474
  module.exports = {
543
475
  typeParameters,
544
- xprInAnnoProperties,
545
- functionsWithoutParens,
546
476
  specialFunctions,
547
477
  quotedLiteralPatterns,
548
478
  initBuiltins,
549
- isAnnotationExpression,
550
- isInReservedNamespace,
551
- isBuiltinType,
552
- isMagicVariable,
553
479
  isGeoTypeName,
554
480
  };
@@ -29,7 +29,7 @@ const $location = Symbol.for( 'cds.$location' );
29
29
  */
30
30
  function check( model ) {
31
31
  const {
32
- error, warning, info,
32
+ error, warning, info, message,
33
33
  } = model.$messageFunctions;
34
34
 
35
35
  checkSapCommonLocale( model );
@@ -41,6 +41,7 @@ function check( model ) {
41
41
  return;
42
42
 
43
43
  function checkDefinition( def ) {
44
+ checkEvent( def );
44
45
  checkGenericConstruct( def );
45
46
  if (def.includes && def.elements)
46
47
  checkElementIncludeOverride( def );
@@ -49,6 +50,16 @@ function check( model ) {
49
50
  def.$queries.forEach( checkQuery );
50
51
  }
51
52
 
53
+ function checkEvent( def ) {
54
+ // Ensure that events are structured. Up to compiler v4, we allowed non-structured events,
55
+ // because when we introduced them, it was not fully specified what they are.
56
+ // TODO(v5):
57
+ // - Make this a (configurable) error; move to acceptTypeOrElement
58
+ // - require type/elements to be set in parser
59
+ if (def.kind === 'event' && !def._effectiveType?.elements && !def._effectiveType?.projection)
60
+ message( 'def-expected-structured', [ (def.type || def.name).location, def ] );
61
+ }
62
+
52
63
  function checkAnnotationDefinition( art ) {
53
64
  // TODO: Should we check elements similar to definition-elements as well?
54
65
  checkEnumType( art );
@@ -61,6 +72,10 @@ function check( model ) {
61
72
  function checkGenericConstruct( art ) {
62
73
  checkName( art );
63
74
  checkTypeArguments( art );
75
+
76
+ if (art.value && !art.$calcDepElement && art.type)
77
+ checkTypeCast( art.value, art );
78
+
64
79
  if (model.vocabularies) {
65
80
  Object.keys( art )
66
81
  .filter( a => a.startsWith( '@' ) )
@@ -202,13 +217,38 @@ function check( model ) {
202
217
  }
203
218
  }
204
219
 
205
- function requireExplicitTypeInSqlCast( art, user ) {
206
- if (!art.type) {
207
- error( 'expr-missing-type', [ art.location, user ], { },
220
+ /**
221
+ * Check the type in an SQL cast expression.
222
+ *
223
+ * @param xpr
224
+ * @param {XSN.Artifact} user
225
+ */
226
+ function requireExplicitTypeInSqlCast( xpr, user ) {
227
+ if (!xpr.type) {
228
+ error( 'expr-missing-type', [ xpr.location, user ], { },
208
229
  'Missing type in SQL cast function' );
209
230
  }
210
231
  }
211
232
 
233
+ function checkTypeCast( xpr, user ) {
234
+ const isCast = (xpr.op?.val === 'cast');
235
+ const elem = isCast
236
+ ? xpr.args[0]?._artifact
237
+ : xpr._artifact;
238
+ const type = isCast ? xpr.type : user.type;
239
+ if (elem && type) { // has explicit type
240
+ if (type._artifact?.elements)
241
+ error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
242
+ else if (elem.elements) // TODO: calc elements
243
+ error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
244
+ else if (elem.target && !type._artifact?.target)
245
+ error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-assoc' } );
246
+ else if (!elem.target && type._artifact?.target && !user.type?.$inferred)
247
+ // $inferred already reported in resolve.js
248
+ error( 'type-invalid-cast', [ type.location, user ], { '#': 'assoc' } );
249
+ }
250
+ }
251
+
212
252
  function checkLocalizedElement( elem ) {
213
253
  if (elem.localized?.val) {
214
254
  const type = elem._effectiveType;
@@ -340,11 +380,8 @@ function check( model ) {
340
380
  .find( name => !enumNode.enum[name].value );
341
381
  if (emptyValue) {
342
382
  const failedEnum = enumNode.enum[emptyValue];
343
- warning( 'type-missing-value', [ failedEnum.location, failedEnum ], {
383
+ message( 'type-missing-enum-value', [ failedEnum.location, failedEnum ], {
344
384
  '#': isNumeric ? 'numeric' : 'std', name: emptyValue,
345
- }, {
346
- std: 'Missing value for non-string enum element $(NAME)',
347
- numeric: 'Missing value for numeric enum element $(NAME)',
348
385
  } );
349
386
  }
350
387
  }
@@ -551,15 +588,26 @@ function check( model ) {
551
588
  if (xorElements) {
552
589
  // one of the two elements is not structured
553
590
  const prop = !elem.elements ? 'new-not-structured' : 'old-not-structured';
554
- const name = elem.name.id;
555
591
  // Position at type/struct, not name
556
592
  const loc = elem.type?.location || elem.elements?.[$location] || elem.location;
557
593
  error( 'ref-invalid-override', [ loc, elem ],
558
- { '#': prop, art: original._main, name } );
594
+ { '#': prop, art: original._main, name: elem.name.id } );
559
595
  return false;
560
596
  }
561
597
  else if (original.elements &&
562
- !checkSubStructureOverride( elem, elem.elements, original.elements )) {
598
+ !checkSubStructureOverride( elem, elem.elements, original.elements )) {
599
+ return false;
600
+ }
601
+
602
+ const xorTarget = !(elem.target || elem.targetAspect) !==
603
+ !(original.target || original.targetAspect);
604
+ if (xorTarget) {
605
+ // one of the two elements is not an association
606
+ const prop = !elem.target ? 'new-not-target' : 'old-not-target';
607
+ // Position at type/assoc, not name
608
+ const loc = elem.target?.location || elem.type?.location || elem.location;
609
+ error( 'ref-invalid-override', [ loc, elem ],
610
+ { '#': prop, art: original._main, name: elem.name.id } );
563
611
  return false;
564
612
  }
565
613
  return true;
@@ -599,6 +647,7 @@ function check( model ) {
599
647
  checkExpressionAssociationUsage( xpr, user, false );
600
648
  if (xpr.op?.val === 'cast') {
601
649
  requireExplicitTypeInSqlCast( xpr, user );
650
+ checkTypeCast( xpr, user );
602
651
  checkTypeArguments( xpr, user );
603
652
  }
604
653
  }
@@ -631,6 +680,7 @@ function check( model ) {
631
680
  // the cast, as otherwise we will check it twice (once here, once via element).
632
681
  if (elem.value?.op?.val === 'cast' && elem.type?.$inferred !== 'cast') {
633
682
  requireExplicitTypeInSqlCast( elem.value, elem );
683
+ checkTypeCast( elem.value, elem );
634
684
  checkTypeArguments( elem.value, elem );
635
685
  }
636
686
  visitSubExpression( elem.value, elem, (xpr) => {
@@ -140,7 +140,8 @@ const {
140
140
  targetCantBeAspect,
141
141
  } = require('./utils');
142
142
  const { compareLayer } = require('./moduleLayers');
143
- const { initBuiltins, isInReservedNamespace } = require('./builtins');
143
+ const { initBuiltins } = require('./builtins');
144
+ const { isInReservedNamespace } = require('../base/builtins');
144
145
 
145
146
  const $location = Symbol.for( 'cds.$location' );
146
147
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -747,7 +748,6 @@ function define( model ) {
747
748
  // We don't worry about duplicate names here.
748
749
  const id = `$_select_${ query._main.$queries.length + 1 }__`;
749
750
  table.name = { id, location: table.location, $inferred: '$internal' };
750
- table.$inferred = '$internal';
751
751
  }
752
752
  addAsAlias();
753
753
  // Store _origin to leading query of table.query for name resolution
@@ -796,7 +796,7 @@ function define( model ) {
796
796
  setMemberParent( table, table.name.id, query );
797
797
  setLink( table, '_block', query._block );
798
798
  dictAdd( query.$tableAliases, table.name.id, table, ( name, loc, tableAlias ) => {
799
- if (tableAlias.$inferred === '$internal') {
799
+ if (tableAlias.name.$inferred === '$internal') {
800
800
  const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
801
801
  // TODO: the semanticLoc query is not initialized yet, and thus cannot
802
802
  // be used here
@@ -25,12 +25,12 @@ const {
25
25
  } = require('./utils');
26
26
  const layers = require('./moduleLayers');
27
27
  const { CompilerAssertion } = require('../base/error');
28
- const { CsnLocation } = require('./classes');
28
+ const { Location } = require('../base/location');
29
29
 
30
30
  const $location = Symbol.for( 'cds.$location' );
31
31
 
32
32
  // attach stupid location - TODO: remove in v5
33
- const genLocation = new CsnLocation( '' );
33
+ const genLocation = new Location( '' );
34
34
 
35
35
  const draftElements = [
36
36
  'IsActiveEntity',
@@ -30,7 +30,7 @@ const tweakAssocs = require('./tweak-assocs');
30
30
  const propagator = require('./propagator');
31
31
  const check = require('./checks');
32
32
 
33
- const { emptyWeakLocation } = require('../base/location');
33
+ const { Location, emptyWeakLocation } = require('../base/location');
34
34
  const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
35
35
  const { checkRemovedDeprecatedFlags } = require('../base/model');
36
36
  const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
@@ -38,7 +38,7 @@ const { cdsFs } = require('../utils/file');
38
38
 
39
39
  const fs = require('fs');
40
40
  const path = require('path');
41
- const { CsnLocation, XsnSource } = require('./classes');
41
+ const { XsnSource } = require('./xsn-model');
42
42
 
43
43
  const extensionParsers = {
44
44
  csn: parseCsn.parse,
@@ -93,7 +93,7 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
93
93
  return parser( source, filename, options, messageFunctions );
94
94
 
95
95
  const model = new XsnSource();
96
- model.location = new CsnLocation( filename );
96
+ model.location = new Location( filename );
97
97
  messageFunctions.error( 'file-unknown-ext', emptyWeakLocation( filename ),
98
98
  { file: ext, '#': !ext && 'none' }, {
99
99
  std: 'Unknown file extension $(FILE)',
@@ -174,12 +174,12 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
174
174
  return []; // no further dependency processing
175
175
  // no parallel readAndParse with same resolved filename should read the file,
176
176
  // also ensure deterministic sequence in sources:
177
- sources[filename] = { location: new CsnLocation( rel ) };
177
+ sources[filename] = { location: new Location( rel ) };
178
178
 
179
179
  const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
180
180
  const ast = parseX( source, rel, options, model.$messageFunctions );
181
181
  sources[filename] = ast;
182
- ast.location = new CsnLocation( rel );
182
+ ast.location = new Location( rel );
183
183
  ast.dirname = path.dirname( filename );
184
184
  assertConsistency( ast, options );
185
185
 
@@ -293,7 +293,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
293
293
  }
294
294
  // no parallel readAndParse with same resolved filename should read the file,
295
295
  // also ensure deterministic sequence in a.sources:
296
- a.sources[filename] = { location: new CsnLocation( rel ) };
296
+ a.sources[filename] = { location: new Location( rel ) };
297
297
 
298
298
  cdsFs( fileCache, options.traceFs ).readFileSync( filename, 'utf8', (err, source) => {
299
299
  if (err) {
@@ -303,7 +303,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
303
303
  try {
304
304
  const ast = parseX( source, rel, options, model.$messageFunctions );
305
305
  a.sources[filename] = ast;
306
- ast.location = new CsnLocation( rel );
306
+ ast.location = new Location( rel );
307
307
  ast.dirname = path.dirname( filename );
308
308
  assertConsistency( ast, options );
309
309
  cb( null, ast );
@@ -374,7 +374,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
374
374
  if (typeof source === 'string') {
375
375
  const ast = parseX( source, filename, options, model.$messageFunctions );
376
376
  sources[filename] = ast;
377
- ast.location = new CsnLocation( filename );
377
+ ast.location = new Location( filename );
378
378
  assertConsistency( ast, options );
379
379
  }
380
380
  else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
@@ -383,7 +383,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
383
383
  else { // source is a CSN object
384
384
  const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
385
385
  sources[filename] = ast;
386
- ast.location = new CsnLocation( filename );
386
+ ast.location = new Location( filename );
387
387
  assertConsistency( ast, options );
388
388
  }
389
389
 
@@ -633,8 +633,14 @@ function populate( model ) {
633
633
  }
634
634
 
635
635
  function resolveExcluding( name, env, excludingDict, user ) {
636
- if (env[name])
636
+ const found = env[name];
637
+ if (found) { // set links for LSP; if Array, then via multiple query sources ($navElement)
638
+ const art = (Array.isArray(found) && found.map(f => f._origin)) ||
639
+ (found.kind === '$navElement' && found._origin) ||
640
+ found;
641
+ setArtifactLink( excludingDict[name].name, art );
637
642
  return;
643
+ }
638
644
  /** @type {object} */
639
645
  // console.log(name,Object.keys(env),Object.keys(excludingDict))
640
646
  const compileMessageRef = info(
@@ -745,7 +751,6 @@ function populate( model ) {
745
751
  location: col.value.location || col.location,
746
752
  $inferred: '$internal',
747
753
  };
748
- col.$inferred = '$internal';
749
754
  return col.name.id;
750
755
  }
751
756
  // invent a name for code completion in expression, see also #10596
@@ -880,9 +885,12 @@ function populate( model ) {
880
885
  if (envParent || query.kind !== 'select') {
881
886
  // already done in populateQuery (TODO: change that and check whether
882
887
  // `*` is allowed at all in definer)
883
- const user = colParent || query;
884
- for (const name in user.excludingDict)
885
- resolveExcluding( name, env, excludingDict, query );
888
+ if (!colParent || colParent.value._artifact) {
889
+ // avoid "not found" messages if pathHead can't be found
890
+ const user = colParent || query;
891
+ for (const name in user.excludingDict)
892
+ resolveExcluding( name, env, excludingDict, query );
893
+ }
886
894
  }
887
895
  }
888
896
 
@@ -13,6 +13,7 @@ const {
13
13
  forEachMember,
14
14
  forEachGeneric,
15
15
  isDeprecatedEnabled,
16
+ isBetaEnabled,
16
17
  } = require( '../base/model');
17
18
  const {
18
19
  setLink,
@@ -77,6 +78,8 @@ function propagate( model ) {
77
78
  const { warning, throwWithError } = model.$messageFunctions;
78
79
 
79
80
  forEachDefinition( model, run );
81
+ if (isBetaEnabled( model.options, 'v5preview' ))
82
+ forEachGeneric( model, 'vocabularies', run );
80
83
 
81
84
  // TODO: move 'virtual' handling/checks to resolver if
82
85
  // 'deprecated.oldVirtualNotNullPropagation' is gone
@@ -519,8 +519,6 @@ function resolve( model ) {
519
519
  if (art.value) {
520
520
  if (art.$syntax === 'calc')
521
521
  checkCalculatedElement( art );
522
- else if (art.type && !art.$inferred)
523
- checkColumnTypeCast( art );
524
522
  }
525
523
 
526
524
  resolveExprInAnnotations( art );
@@ -849,21 +847,6 @@ function resolve( model ) {
849
847
  }
850
848
  }
851
849
 
852
- function checkColumnTypeCast( art ) {
853
- const elem = (art.value.op?.val === 'cast')
854
- ? art.value.args[0]?._artifact
855
- : art.value._artifact;
856
- if (elem && art.type) { // has explicit type
857
- if (art.type._artifact?.elements)
858
- error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
859
- else if (elem.elements) // TODO: calc elements
860
- error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
861
- else if (elem.target && !art.type._artifact?.target)
862
- // allow cast to association -> that is already checked and denied elsewhere
863
- error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-assoc' } );
864
- }
865
- }
866
-
867
850
  /**
868
851
  * Return type containing the assoc spec (keys, on); note that no
869
852
  * propagation/rewrite has been done yet, cyclic dependency must have been
@@ -1223,8 +1206,9 @@ function resolve( model ) {
1223
1206
  'Only an association can be redirected' );
1224
1207
  return;
1225
1208
  }
1226
- else if ((elem.value || elem.expand) && elem.type && !elem.type.$inferred) {
1227
- error( 'ref-unexpected-assoc', [ elem.type.location, elem ], { '#': 'cast' } );
1209
+ else if ((elem.value || elem.expand) && elem.type &&
1210
+ (!elem.type.$inferred || elem.type.$inferred === 'cast')) {
1211
+ error( 'type-invalid-cast', [ elem.type.location, elem ], { '#': 'assoc' } );
1228
1212
  return;
1229
1213
  }
1230
1214
  // console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
@@ -1399,7 +1383,9 @@ function resolve( model ) {
1399
1383
  const alreadyReported = model.$messageFunctions.messages
1400
1384
  .find(msg => msg.messageId === 'anno-experimental-expressions');
1401
1385
  if (!alreadyReported) {
1402
- info( 'anno-experimental-expressions', [ expr.location ], {
1386
+ // due to test mode and shuffling, we would get a different location on each compilation.
1387
+ const loc = model.options.testMode ? null : [ expr.location ];
1388
+ info( 'anno-experimental-expressions', loc, {
1403
1389
  option: 'annotationExpressions',
1404
1390
  // eslint-disable-next-line max-len
1405
1391
  }, 'Expressions in annotation values are a beta feature. Use at your own risk. (This message can be suppressed with beta flag $(OPTION))' );
@@ -710,7 +710,7 @@ function fns( model ) {
710
710
  // duplicate table aliases, only mention non-ambiguous source elems
711
711
  const uniqueNames = arr.filter( e => !e.$duplicates );
712
712
  if (uniqueNames.length) {
713
- const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
713
+ const names = uniqueNames.filter( e => e._parent.name?.$inferred !== '$internal' )
714
714
  .map( e => `${ e._parent.name.id }.${ e.name.id }` );
715
715
  let variant = names.length === uniqueNames.length ? 'std' : 'few';
716
716
  if (names.length === 0)
@@ -20,7 +20,7 @@ const {
20
20
  traverseQueryExtra,
21
21
  setExpandStatus,
22
22
  } = require('./utils');
23
- const { CsnLocation } = require('./classes');
23
+ const { Location } = require('../base/location');
24
24
  const { CompilerAssertion } = require('../base/error');
25
25
 
26
26
  const $location = Symbol.for( 'cds.$location' );
@@ -287,7 +287,7 @@ function tweakAssocs( model ) {
287
287
  const loc = otherAssoc.foreignKeys[$location] || dictLocation( otherAssoc.foreignKeys );
288
288
  const location = loc && (!loc.endCol
289
289
  ? loc
290
- : new CsnLocation( loc.file, loc.endLine, loc.endCol - 1, loc.endLine, loc.endCol ));
290
+ : new Location( loc.file, loc.endLine, loc.endCol - 1, loc.endLine, loc.endCol ));
291
291
  const baseAssoc = assocWithExplicitSpec( thisAssoc );
292
292
  if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
293
293
  error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
@@ -11,8 +11,8 @@
11
11
  'use strict';
12
12
 
13
13
  const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
14
- const { weakLocation } = require('../base/location');
15
- const { XsnName, XsnArtifact, CsnLocation } = require('./classes');
14
+ const { Location, weakLocation } = require('../base/location');
15
+ const { XsnName, XsnArtifact } = require('./xsn-model');
16
16
 
17
17
  const $inferred = Symbol.for( 'cds.$inferred' );
18
18
 
@@ -262,7 +262,7 @@ function copyExpr( expr, location ) {
262
262
  const proto = Object.getPrototypeOf( expr );
263
263
  if (proto && proto !== Object.prototype && proto !== XsnName.prototype &&
264
264
  // do not copy object from special classes outside the compiler domain&&
265
- proto !== XsnArtifact.prototype && proto !== CsnLocation.prototype)
265
+ proto !== XsnArtifact.prototype && proto !== Location.prototype)
266
266
  return expr;
267
267
  const r = Object.create( proto );
268
268
  for (const prop of Object.getOwnPropertyNames( expr )) {
@@ -39,24 +39,8 @@ class XsnName {
39
39
  location;
40
40
  }
41
41
 
42
- class CsnLocation {
43
- file;
44
- line;
45
- col;
46
- endLine;
47
- endCol;
48
- constructor(file, line, col, endLine, endCol) {
49
- this.file = file;
50
- this.line = line;
51
- this.col = col;
52
- this.endLine = endLine;
53
- this.endCol = endCol;
54
- }
55
- }
56
-
57
42
  module.exports = {
58
43
  XsnSource,
59
44
  XsnArtifact,
60
45
  XsnName,
61
- CsnLocation,
62
46
  };