@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
@@ -1,5 +1,504 @@
1
1
  'use strict';
2
2
 
3
- // Placeholder for future LSP API.
3
+ // API for `@sap/cds-lsp`.
4
+ //
5
+ // THIS FILE IS CONSIDERED INTERNAL!
6
+ // We do not guarantee stability for any project besides the CAP LSP server.
7
+ //
8
+ // This files includes an iterator over "semantic tokens" in an XSN model.
9
+ // "Semantic tokens" are identifiers, but also the "return" parameter.
10
+ // See `internalDoc/lsp/IdentifierCrawling.md` for details.
4
11
 
5
- module.exports = {};
12
+ const { CompilerAssertion } = require('../base/error');
13
+ const $inferred = Symbol.for( 'cds.$inferred' );
14
+
15
+ // TODO: Remove hints; they should not be necessary in the best case
16
+ const HINTS = {
17
+ USING_ALIAS: 'using-alias',
18
+ DEFINITION_NAME: 'definition',
19
+ NAMESPACE_STATEMENT: 'namespace-statement',
20
+ };
21
+
22
+ // eslint-disable-next-line no-unused-vars
23
+ class LspSemanticTokenEvent {
24
+ event; // 'reference' | 'definition',
25
+ semanticToken;
26
+ node;
27
+ hint; // TODO: Remove
28
+ }
29
+
30
+ /**
31
+ * All actions to report semantic tokens in a model.
32
+ */
33
+ const artifactActions = {
34
+ __proto__: null,
35
+
36
+ // e.g. sources or services
37
+ artifacts: dictOf( artifactTokens ),
38
+ extensions: arrayOf( extensionTokens ),
39
+ namespace: namespaceTokens,
40
+ // e.g. via CSN input
41
+ vocabularies: dictOf( artifactTokens ),
42
+ definitions: dictOf( artifactTokens ),
43
+
44
+ extern: artifactTokens,
45
+ name: definitionNameTokens,
46
+ path: pathReferenceTokens,
47
+
48
+ type: artifactTokens,
49
+ target: artifactTokens,
50
+ targetAspect: artifactTokens,
51
+ targetElement: artifactTokens,
52
+ returns: returnsTokens,
53
+ items: artifactTokens,
54
+ elements: elementsTokens,
55
+
56
+ enum: dictOf( artifactTokens ),
57
+ foreignKeys: dictOf( artifactTokens ),
58
+ actions: dictOf( artifactTokens ),
59
+ params: dictOf( artifactTokens ),
60
+ mixin: dictOf( artifactTokens ),
61
+ excludingDict: dictOf( nameAsReference ),
62
+
63
+ // Don't crawl `$tableAliases`, as they are set multiple times in queries
64
+ // via different `$tableAliases`.
65
+ // $tableAliases: null,
66
+
67
+ // NOT $queries, as that doesn't cover UNIONs (e.g. `orderBy` vs `$orderBy`)
68
+ query: artifactTokens,
69
+
70
+ from: artifactTokens,
71
+ includes: arrayOf( artifactTokens ),
72
+ columns: arrayOf( artifactTokens ),
73
+ expand: arrayOf( artifactTokens ),
74
+ inline: arrayOf( artifactTokens ),
75
+
76
+ args: argsTokens,
77
+ on: artifactTokens,
78
+ default: artifactTokens,
79
+ value: artifactTokens,
80
+ sym: enumSymToken,
81
+ where: artifactTokens,
82
+ groupBy: artifactTokens,
83
+ orderBy: artifactTokens,
84
+ having: artifactTokens,
85
+ suffix: artifactTokens,
86
+ limit: artifactTokens,
87
+ rows: artifactTokens,
88
+ offset: artifactTokens,
89
+
90
+ '@': annotationTokens,
91
+ };
92
+
93
+ /** Returns a generator that applies the given function on all entries and yields the result. */
94
+ function dictOf( func ) {
95
+ return function* dictionary( dict ) {
96
+ for (const [ item ] of iterateGeneric({ dict }, 'dict'))
97
+ yield* func( item );
98
+ };
99
+ }
100
+
101
+ /** Returns a generator that applies the given function on all entries and yields the result. */
102
+ function arrayOf( func ) {
103
+ return function* array( arr ) {
104
+ if (!Array.isArray(arr))
105
+ return;
106
+ for (const item of arr)
107
+ yield* func( item );
108
+ };
109
+ }
110
+
111
+ /** Generator equivalent of iterateGeneric of forEachGeneric() */
112
+ function* iterateGeneric( obj, prop ) {
113
+ const dict = obj[prop];
114
+ if (!dict)
115
+ return;
116
+
117
+ for (const name in dict) {
118
+ obj = dict[name];
119
+ if (Array.isArray( obj )) {
120
+ for (const item of obj)
121
+ yield [ item, name, prop ]; // parser or source duplicates (e.g. USING vs definition)
122
+ }
123
+ else {
124
+ yield [ obj, name, prop ];
125
+ if (Array.isArray( obj.$duplicates )) { // redefinitions
126
+ for (const dup of obj.$duplicates)
127
+ yield [ dup, name, prop ];
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * A generator that yields all semantic tokens in an XSN model.
135
+ * Semantic tokens include identifiers (references/definitions) and the "returns" parameter.
136
+ *
137
+ * @param {XSN.Model} xsn
138
+ * @param {CSN.Options} options
139
+ * @returns {Generator<LspSemanticTokenEvent>}
140
+ */
141
+ function* traverseSemanticTokens( xsn, options ) {
142
+ if (!xsn)
143
+ throw new CompilerAssertion('Expected valid XSN model for traverseSemanticTokens(…)');
144
+ if (!options)
145
+ throw new CompilerAssertion('Expected valid options for traverseSemanticTokens(…)');
146
+
147
+ if (xsn.sources)
148
+ yield* dictOf( artifactTokens )( xsn.sources );
149
+ }
150
+
151
+ /**
152
+ * Report semantic tokens in artifacts, including definitions, elements, params, etc.
153
+ *
154
+ * @param {XSN.Artifact} art
155
+ * @returns {Generator<LspSemanticTokenEvent>}
156
+ */
157
+ function* artifactTokens( art ) {
158
+ if (!art || art.builtin || art.$inferred)
159
+ return null;
160
+
161
+ if (Array.isArray( art )) {
162
+ for (const entry of art)
163
+ yield* artifactTokens( entry );
164
+ return null;
165
+ }
166
+
167
+ for (const prop in art) {
168
+ if (artifactActions[prop])
169
+ yield* artifactActions[prop](art[prop], art);
170
+ else if (prop.charAt(0) === '@')
171
+ yield* artifactActions['@'](art[prop], art);
172
+ }
173
+
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * For an extension, yield all semantic tokens.
179
+ * We don't use `artifactTokens` for it, because extensions are a special case:
180
+ * - they have a name, but actually refer to some other artifact.
181
+ * - their artifacts such as elements may overlap with existing definitions, because
182
+ * extensions are applied; if they were applied, `_parent` does not point to the
183
+ * extension, which means we can't use it to skip them in `artifactTokens`.
184
+ * - we only need to handle `annotate` and `extend` kinds specifically:
185
+ * if an extension was not applied, pass it to `artifactTokens`;
186
+ * if an extension was applied, we only need to report its name (i.e. reference)
187
+ * and traverse over all artifacts
188
+ *
189
+ * @param {XSN.Extension} ext
190
+ * @returns {Generator<LspSemanticTokenEvent>}
191
+ */
192
+ function* extensionTokens( ext ) {
193
+ if (ext.kind !== 'extend' && ext.kind !== 'annotate')
194
+ return null;
195
+
196
+ const wasApplied = ext.name._artifact && !ext.name._artifact.$inferred;
197
+ if (!wasApplied) {
198
+ yield* artifactTokens( ext );
199
+ return null;
200
+ }
201
+
202
+ yield* nameAsReference( ext );
203
+
204
+ // We need to traverse all dictionaries that could themselves contain
205
+ // extensions. Enum extensions or columns don't need to be traversed,
206
+ // for example, because there can't be inner extensions.
207
+ yield* dictOf( extensionTokens )( ext.params );
208
+ yield* dictOf( extensionTokens )( ext.actions );
209
+ yield* dictOf( extensionTokens )( ext.elements );
210
+
211
+ if (ext.returns)
212
+ yield* extensionTokens( ext.returns );
213
+
214
+ // Artifact extensions are always definitions, and can't have nested `extend`s,
215
+ // hence no need to traverse them with `extensionTokens`.
216
+ yield* dictOf( artifactTokens )( ext.artifacts );
217
+
218
+ return null;
219
+ }
220
+
221
+ /**
222
+ * Report all semantic tokens in an annotation assignment.
223
+ *
224
+ * @param {XSN.Artifact} anno
225
+ * @returns {Generator<LspSemanticTokenEvent>}
226
+ */
227
+ function* annotationTokens( anno ) {
228
+ // TODO: Also report annotation names
229
+ if (anno.kind === '$annotation')
230
+ yield* annotationValueTokens( anno );
231
+ }
232
+
233
+ function* argsTokens( args, art ) {
234
+ if (Array.isArray(args)) {
235
+ // e.g. unnamed function arguments
236
+ yield* arrayOf( artifactTokens )( args );
237
+ }
238
+ else {
239
+ // e.g. named arguments
240
+ for (const [ param ] of iterateGeneric( art, 'args' )) {
241
+ yield* nameAsReference( param );
242
+ yield* artifactTokens( param );
243
+ }
244
+ }
245
+ }
246
+
247
+ function* enumSymToken( sym, expr ) {
248
+ yield {
249
+ event: 'reference',
250
+ semanticToken: expr.sym,
251
+ node: expr,
252
+ hint: undefined,
253
+ };
254
+ }
255
+
256
+ /**
257
+ * A namespace is always considered a reference and not a definition.
258
+ *
259
+ * @param {XSN.Artifact} def
260
+ * @returns {Generator<LspSemanticTokenEvent>}
261
+ */
262
+ function* namespaceTokens( def ) {
263
+ if (!def.name)
264
+ return null;
265
+
266
+ for (let i = 0; i < def.name.path.length; ++i) {
267
+ yield {
268
+ event: 'reference',
269
+ semanticToken: def.name.path[i],
270
+ node: def,
271
+ hint: (i === def.name.path.length - 1) ? HINTS.NAMESPACE_STATEMENT : null,
272
+ };
273
+ }
274
+
275
+ return null;
276
+ }
277
+
278
+ /**
279
+ * An annotation value may contain expressions which we need to report.
280
+ *
281
+ * @param {object} anno
282
+ * @returns {Generator<LspSemanticTokenEvent>}
283
+ */
284
+ function* annotationValueTokens( anno ) {
285
+ if (Array.isArray(anno)) {
286
+ for (const entry of anno)
287
+ yield* annotationValueTokens( entry );
288
+ }
289
+ else if (anno.$tokenTexts) {
290
+ yield* artifactTokens( anno );
291
+ }
292
+ else if (Array.isArray(anno.val)) {
293
+ yield* annotationValueTokens( anno.val );
294
+ }
295
+ else if (anno.struct) {
296
+ for (const [ struct ] of iterateGeneric( anno, 'struct' ))
297
+ yield* annotationValueTokens( struct );
298
+ }
299
+ }
300
+
301
+ /**
302
+ * A `returns` structure may contain sub-elements. But we report the `returns`
303
+ * token as well, as it is considered a token with semantic value.
304
+ *
305
+ * @param {XSN.Artifact} art
306
+ * @returns {Generator<LspSemanticTokenEvent>}
307
+ */
308
+ function* returnsTokens( art ) {
309
+ if (art.kind === 'param') {
310
+ // report the `returns` parameter
311
+ yield {
312
+ event: 'definition',
313
+ semanticToken: art.name,
314
+ node: art,
315
+ hint: undefined,
316
+ };
317
+ yield* artifactTokens( art );
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Report elements if they should be traversed. They are not always traversed
323
+ * to avoid duplication due to `expand` and `columns` also being traversed.
324
+ *
325
+ * @param {Record<string, XSN.Artifact>} elements
326
+ * @param {XSN.Artifact} art
327
+ * @returns {Generator<LspSemanticTokenEvent>}
328
+ */
329
+ function* elementsTokens( elements, art ) {
330
+ if (shouldTraverseElements( art ))
331
+ yield* dictOf( artifactTokens )( elements );
332
+ }
333
+
334
+ /**
335
+ * Report all references in `ref`.
336
+ *
337
+ * @returns {Generator<LspSemanticTokenEvent>}
338
+ */
339
+ function* pathReferenceTokens( path, ref, user = ref, hint = null ) {
340
+ if (!path)
341
+ return null;
342
+
343
+ // don't report cds.Association/cds.Composition
344
+ // TODO: Or report the `Association` keyword, similar to `returns`?
345
+ if (path.length === 1 && ref._artifact?.category === 'relation')
346
+ return null;
347
+
348
+ yield* artifactTokens( path );
349
+
350
+ // parser prepends a fake `type of` segment, which we need to skip
351
+ const root = ref.scope === 'typeOf' ? 1 : 0;
352
+ for (let i = root; i < path.length; ++i) {
353
+ if (!path[i].$inferred) { // e.g. `id` when expanded from `$user`
354
+ yield {
355
+ event: 'reference',
356
+ semanticToken: path[i],
357
+ node: user,
358
+ hint,
359
+ };
360
+ }
361
+ }
362
+
363
+ return null;
364
+ }
365
+
366
+ /**
367
+ * Some XSN nodes such as entries in `excludingDict` or named arguments are references
368
+ * but don't have a `path` property, only a `name` property. Report such names
369
+ * as references.
370
+ *
371
+ * @returns {Generator<LspSemanticTokenEvent>}
372
+ */
373
+ function* nameAsReference( ref, hint = null ) {
374
+ if (!ref.name || ref.name.$inferred)
375
+ return null;
376
+
377
+ if (ref.name.path) {
378
+ yield* pathReferenceTokens( ref.name.path, ref.name, ref, hint );
379
+ }
380
+ else {
381
+ yield {
382
+ event: 'reference',
383
+ semanticToken: ref.name,
384
+ node: ref,
385
+ hint,
386
+ };
387
+ }
388
+ return null;
389
+ }
390
+
391
+ /**
392
+ * Traverse the name of a definition, and report N-1 path steps as references
393
+ * and of course the definition itself.
394
+ *
395
+ * @returns {Generator<LspSemanticTokenEvent>}
396
+ */
397
+ function* definitionNameTokens( name, art ) {
398
+ if (!art.kind)
399
+ return null; // e.g. parameter references
400
+ if (art.kind === '$annotation')
401
+ return null; // annotation name, e.g. in `@anno: (elem)`
402
+
403
+ if ((name.$inferred && name.$inferred !== 'as') ||
404
+ art.kind === 'select' || art.kind === '$join') {
405
+ // Internal names such as numbers for SELECTs or the `$internal` names must
406
+ // not be reported.
407
+ return null;
408
+ }
409
+
410
+ if (art.kind === 'extend' || art.kind === 'annotate') {
411
+ yield* nameAsReference( art );
412
+ return null;
413
+ }
414
+
415
+ // Report references in a name (N-1 path steps).
416
+ for (let i = 0; i < name.path?.length - 1; ++i) {
417
+ yield {
418
+ event: 'reference',
419
+ semanticToken: name.path[i],
420
+ node: art,
421
+ hint: HINTS.DEFINITION_NAME,
422
+ };
423
+ }
424
+
425
+ const hint = art.kind === 'using' ? HINTS.USING_ALIAS : null;
426
+
427
+ if (name.path) {
428
+ // Only take the last path step; all others are considered references.
429
+ const implicitName = name.path[name.path.length - 1];
430
+ yield {
431
+ event: 'definition',
432
+ semanticToken: implicitName,
433
+ node: art,
434
+ hint,
435
+ };
436
+ }
437
+ else if (name.id) {
438
+ // Not all names have a path; some (e.g. parameters) only have an ID.
439
+ yield {
440
+ event: 'definition',
441
+ semanticToken: name,
442
+ node: art,
443
+ hint,
444
+ };
445
+ }
446
+
447
+ return null;
448
+ }
449
+
450
+ /**
451
+ * Returns true if `elements` of the given `art` should be traversed.
452
+ * Elements are _not_ traversed, e.g. for `expand`, to avoid duplicates.
453
+ *
454
+ * @returns {boolean}
455
+ */
456
+ function shouldTraverseElements( art ) {
457
+ return (
458
+ // $expand: 'origin' -> normal expansion
459
+ // $expand: 'annotate' -> additional annotation (needs to traverse annotation expressions)
460
+ art.$expand !== 'origin' && !art.elements[$inferred] && (
461
+ // sub-elements are always traversed except for `expand`, which is handled on its own.
462
+ art.kind === 'element' && !art.expand ||
463
+ // all non-query elements are traversed; because `_main` on bound actions may point
464
+ // to a query, we handle parameters explicitly.
465
+ art.kind === 'param' || !(art._main || art).$queries
466
+ )
467
+ );
468
+ }
469
+
470
+ /**
471
+ * Given a LspSemanticTokenEvent, returns a generator that yields the referenced
472
+ * object and its origin's until the deepest entry is found.
473
+ *
474
+ * @param obj
475
+ * @returns {Generator<*, void, *>}
476
+ */
477
+ function* getSemanticTokenOrigin( obj ) {
478
+ let ref = obj.semanticToken;
479
+ if (obj.event === 'definition') {
480
+ ref = obj.node;
481
+ }
482
+ else {
483
+ if (!ref?._artifact)
484
+ return; // unknown -> abort
485
+ // take first artifact for duplicates (best effort)
486
+ ref = Array.isArray(ref._artifact) ? ref._artifact[0] : ref._artifact;
487
+ yield ref;
488
+ }
489
+
490
+ if (!ref._effectiveType)
491
+ return; // abort for unresolved references and cyclic ones
492
+
493
+ while (ref._origin) {
494
+ yield ref._origin;
495
+ ref = ref._origin;
496
+ if (!ref || typeof ref === 'string')
497
+ break;
498
+ }
499
+ }
500
+
501
+ module.exports = {
502
+ traverseSemanticTokens,
503
+ getSemanticTokenOrigin,
504
+ };
@@ -83,6 +83,7 @@ function populate( model ) {
83
83
  effectiveType,
84
84
  getOrigin,
85
85
  getInheritedProp,
86
+ mergeSpecifiedForeignKeys,
86
87
  } );
87
88
  // let depth = 100;
88
89
 
@@ -115,7 +116,10 @@ function populate( model ) {
115
116
 
116
117
  /** Make sure that effectiveType() is called on all members and items */
117
118
  function traverseElementEnvironments( art ) {
118
- // TODO: we could leave out foreign keys (but they are traversed via forEachMember)
119
+ // We leave out foreign keys (as they are traversed via forEachMember).
120
+ // Keys are handled in tweak-assocs.js
121
+ if (art.kind === 'key')
122
+ return;
119
123
  let type = effectiveType( art );
120
124
  while (type?.items)
121
125
  type = effectiveType( type.items );
@@ -529,9 +533,6 @@ function populate( model ) {
529
533
  if (!selem) {
530
534
  info( 'query-missing-element', [ ielem.name.location, art ], {
531
535
  '#': ielem.kind === 'enum' ? 'enum' : 'std', id,
532
- }, {
533
- std: 'Element $(ID) is missing in specified elements',
534
- enum: 'Enum $(ID) is missing in specified enum values',
535
536
  } );
536
537
  }
537
538
  else {
@@ -560,6 +561,8 @@ function populate( model ) {
560
561
  setLink( ielem, 'elements$', selem.elements );
561
562
  if (selem.enum)
562
563
  setLink( ielem, 'enum$', selem.enum );
564
+ if (selem.foreignKeys)
565
+ setLink( ielem, 'foreignKeys$', selem.foreignKeys );
563
566
  }
564
567
  }
565
568
 
@@ -574,8 +577,57 @@ function populate( model ) {
574
577
  specifiedElement.$isSpecifiedElement = true;
575
578
  if (!specifiedElement.$replacement) {
576
579
  const loc = [ specifiedElement.name.location, specifiedElement ];
577
- error( 'query-unspecified-element', loc, { id },
578
- 'Element $(ID) does not result from the query' );
580
+ error( 'query-unspecified-element', loc, { id } );
581
+ }
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Merge _specified_ foreign keys with _inferred_ foreign keys in the given view/element,
587
+ * where specified elements can appear through CSN.
588
+ *
589
+ * We only copy annotations.
590
+ *
591
+ * This is important to ensure re-compilability.
592
+ *
593
+ * TODO: make this part of a revamped on-demand 'extend' functionality.
594
+ *
595
+ * @param art
596
+ */
597
+ function mergeSpecifiedForeignKeys( art ) {
598
+ if (!art.foreignKeys)
599
+ return; // TODO: Warn if there are no foreign keys?
600
+
601
+ let wasAnnotated = false;
602
+
603
+ for (const id in art.foreignKeys) {
604
+ const ielem = art.foreignKeys[id]; // inferred element
605
+ const selem = art.foreignKeys$[id]; // specified element
606
+ if (!selem) {
607
+ info( 'query-missing-element', [ ielem.name.location, art ], { '#': 'foreignKeys', id } );
608
+ }
609
+ else {
610
+ for (const prop in selem) {
611
+ // just annotation assignments and doc comments for foreign keys
612
+ if (prop.charAt(0) === '@' || prop === 'doc') {
613
+ ielem[prop] = selem[prop];
614
+ // required for gensrc mode of to-csn.js, otherwise the annotation
615
+ // may be lost during recompilation.
616
+ ielem[prop].$priority = 'annotate';
617
+ wasAnnotated = true;
618
+ }
619
+ }
620
+ selem.$replacement = true;
621
+ }
622
+ }
623
+ if (wasAnnotated)
624
+ setExpandStatusAnnotate( art, 'annotate' );
625
+
626
+ for (const id in art.foreignKeys$) {
627
+ const specifiedElement = art.foreignKeys$[id];
628
+ if (!specifiedElement.$replacement) {
629
+ const loc = [ specifiedElement.name.location, specifiedElement ];
630
+ error( 'query-unspecified-element', loc, { '#': 'foreignKeys', id } );
579
631
  }
580
632
  }
581
633
  }
@@ -816,15 +868,10 @@ function populate( model ) {
816
868
  const excludingDict = (colParent || query).excludingDict || Object.create( null );
817
869
 
818
870
  const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
819
- // console.log('S1:',location.line,location.col,
820
- // envParent&&!!envParent._origin&&envParent._origin.name)
821
871
  const env = wildcardColumnEnv( wildcard, query );
822
872
  if (!env)
823
873
  return;
824
874
 
825
- // if (envParent) console.log('S2:',location.line,location.col,
826
- // envParent?.name,envParent?._origin?.name,
827
- // Object.keys(env),Object.keys(elements))
828
875
  for (const name in env) {
829
876
  const navElem = env[name];
830
877
  // TODO: remove all access to masked (use 'grep')
@@ -1069,10 +1116,10 @@ function populate( model ) {
1069
1116
  else {
1070
1117
  // referred (and probably inferred) assoc (without a user-provided target at that place)
1071
1118
  // HINT: consider bin/cdsv2m.js when changing the following message text
1072
- // No grouped and sub messages yet (TODO v5): mention at all target places with all assocs
1119
+ // No grouped and sub messages yet (TODO v6): mention at all target places with all assocs
1073
1120
  const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
1074
1121
  for (const proj of exposed) {
1075
- // TODO: def-ambiguous-target (just v5, as the current is infamous and used in options),
1122
+ // TODO: def-ambiguous-target (just v6, as the current is infamous and used in options),
1076
1123
  message( 'redirected-implicitly-ambiguous',
1077
1124
  [ weakLocation( proj.name.location ), proj ],
1078
1125
  {
@@ -13,7 +13,6 @@ const {
13
13
  forEachMember,
14
14
  forEachGeneric,
15
15
  isDeprecatedEnabled,
16
- isBetaEnabled,
17
16
  } = require( '../base/model');
18
17
  const {
19
18
  setLink,
@@ -50,13 +49,17 @@ function propagate( model ) {
50
49
  targetAspect,
51
50
  cardinality: notWithExpand,
52
51
  on: notWithExpand,
53
- foreignKeys: expensive, // includes "notWithExpand", dictionary copy
52
+ // "expensive" includes "notWithExpand"
53
+ // required for places where we don't handle associations, such as in parameters;
54
+ // otherwise already expanded and rewritten.
55
+ foreignKeys: expensive,
54
56
  items,
57
+ // required for propagation in targetAspect; otherwise already expanded
55
58
  elements: expensive,
56
- enum: expensive,
57
- params: expensive, // actually only with parent action
58
- returns,
59
- $filtered: annotation, // TODO(v5): Remove
59
+ // already expanded if necessary
60
+ // enum: expensive,
61
+ // params: expensive, // actually only with parent action
62
+ // returns,
60
63
  $enclosed: annotation,
61
64
  };
62
65
  const ruleToFunction = {
@@ -75,8 +78,7 @@ function propagate( model ) {
75
78
  const { warning, throwWithError } = model.$messageFunctions;
76
79
 
77
80
  forEachDefinition( model, run );
78
- if (isBetaEnabled( model.options, 'v5preview' ))
79
- forEachGeneric( model, 'vocabularies', run );
81
+ forEachGeneric( model, 'vocabularies', run );
80
82
 
81
83
  // TODO: move 'virtual' handling/checks to resolver if
82
84
  // 'deprecated.oldVirtualNotNullPropagation' is gone