@sap/cds-compiler 4.0.2 → 4.2.2

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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -13,16 +13,23 @@ const {
13
13
  pathName,
14
14
  userQuery,
15
15
  definedViaCdl,
16
+ isDirectComposition,
17
+ pathStartsWithSelf,
18
+ columnRefStartsWithSelf,
19
+ isAssocToPrimaryKeys,
20
+ artifactRefLocation,
16
21
  } = require('./utils');
17
22
 
23
+ const $inferred = Symbol.for( 'cds.$inferred' );
24
+ const $location = Symbol.for( 'cds.$location' );
25
+
18
26
  /**
19
27
  * Main export function of this file. Attach "resolve" functions shared for phase
20
28
  * "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
21
29
  *
22
30
  * Before calling `resolvePath`, make sure that the following function
23
31
  * in model.$function is set:
24
- * - `navigationEnv`: a function which returns the search environment defined by
25
- * its argument, e.g. a function which returns the dictionary of subartifacts.
32
+ * - `effectiveType`
26
33
  *
27
34
  * @param {XSN.Model} model
28
35
  */
@@ -30,7 +37,7 @@ const {
30
37
  function fns( model ) {
31
38
  const { options } = model;
32
39
  const {
33
- info, warning, error, message,
40
+ info, error, warning, message,
34
41
  } = model.$messageFunctions;
35
42
  const Functions = model.$functions;
36
43
 
@@ -54,58 +61,74 @@ function fns( model ) {
54
61
  lexical: userBlock,
55
62
  dynamic: modelDefinitions,
56
63
  notFound: undefinedDefinition,
64
+ accept: acceptRealArtifact,
57
65
  },
58
66
  _extensions: {
59
67
  isMainRef: 'all',
60
68
  lexical: userBlock,
61
69
  dynamic: modelDefinitions,
62
- notFound: false, // without message
70
+ notFound: () => null, // without message
63
71
  },
64
72
  include: {
65
73
  isMainRef: 'no-generated',
66
74
  lexical: userBlock,
67
75
  dynamic: modelBuiltinsOrDefinitions,
68
76
  notFound: undefinedDefinition,
77
+ accept: acceptStructOrBare,
78
+ },
79
+ _include: { // cyclic include: no accept
80
+ isMainRef: 'no-generated',
81
+ lexical: userBlock,
82
+ dynamic: modelBuiltinsOrDefinitions,
83
+ notFound: undefinedDefinition,
69
84
  },
70
- viewInclude: 'include', // TODO: do differently
71
85
  target: {
72
86
  isMainRef: 'no-autoexposed',
73
87
  lexical: userBlock,
74
88
  dynamic: modelBuiltinsOrDefinitions,
75
89
  notFound: undefinedDefinition,
76
- // special `scope`s for redirections:
90
+ accept: acceptEntity,
91
+ noDep: true,
92
+ // special `scope`s for auto-redirections:
77
93
  global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
78
94
  },
79
- targetAspect: { // TODO: do differently
95
+ targetAspect: {
80
96
  isMainRef: 'no-autoexposed',
81
97
  lexical: userBlock,
82
98
  dynamic: modelBuiltinsOrDefinitions,
83
99
  notFound: undefinedDefinition,
100
+ accept: acceptAspect,
84
101
  },
85
102
  from: {
86
103
  isMainRef: 'no-autoexposed',
87
104
  lexical: userBlock,
88
105
  dynamic: modelBuiltinsOrDefinitions,
89
- notFound: undefinedDefinition,
90
106
  navigation: environment,
107
+ notFound: undefinedDefinition,
108
+ accept: acceptEntityOrAssoc,
109
+ noDep: '', // dependency special for from
91
110
  },
92
111
  type: {
93
112
  isMainRef: 'no-autoexposed',
94
113
  lexical: userBlock,
95
114
  dynamic: modelBuiltinsOrDefinitions,
115
+ navigation: staticTarget,
96
116
  notFound: undefinedDefinition,
97
- navigation: targetAspectOnly,
117
+ accept: acceptTypeOrElement,
98
118
  // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
99
119
  typeOf: typeOfSemantics,
100
- global: () => ({ isMainRef: 'no-autoexposed', dynamic: modelDefinitions }),
120
+ global: () => ({
121
+ isMainRef: 'no-autoexposed',
122
+ dynamic: modelDefinitions,
123
+ navigation: staticTarget, // TODO: Object.assign() with main
124
+ }),
101
125
  },
102
- actionParamType: 'type', // TODO: do differently
103
- eventType: 'type', // TODO: do differently
104
126
  // element references without lexical scope (except $self/$projection): -----
105
127
  targetElement: {
106
128
  lexical: null,
107
129
  dollar: false,
108
130
  dynamic: targetElements,
131
+ navigation: targetNavigation,
109
132
  notFound: undefinedTargetElement,
110
133
  param: paramSemantics,
111
134
  },
@@ -120,6 +143,7 @@ function fns( model ) {
120
143
  lexical: justDollarSelf,
121
144
  dollar: true,
122
145
  dynamic: targetElements,
146
+ navigation: calcElemNavigation,
123
147
  notFound: undefinedTargetElement,
124
148
  param: paramUnsupported,
125
149
  },
@@ -130,22 +154,42 @@ function fns( model ) {
130
154
  notFound: undefinedVariable,
131
155
  param: paramUnsupported,
132
156
  },
157
+ 'limit-rows': {
158
+ lexical: null,
159
+ dollar: true,
160
+ dynamic: () => Object.create( null ),
161
+ notFound: undefinedVariable,
162
+ param: paramSemantics,
163
+ },
164
+ 'limit-offset': 'limit-rows',
133
165
  // general element references -----------------------------------------------
134
- expr: { // TODO: this is too general -> column
166
+ where: {
167
+ lexical: tableAliasesAndSelf,
168
+ dollar: true,
169
+ dynamic: combinedSourcesOrParentElements,
170
+ notFound: undefinedSourceElement,
171
+ check: checkRefInQuery,
172
+ param: paramSemantics,
173
+ },
174
+ having: 'where',
175
+ groupBy: 'where',
176
+ column: {
135
177
  lexical: tableAliasesIfNotExtendAndSelf,
136
178
  dollar: true,
137
179
  dynamic: combinedSourcesOrParentElements,
138
180
  notFound: undefinedSourceElement,
181
+ check: checkColumnRef,
139
182
  param: paramSemantics,
140
183
  nestedColumn: () => ({ // in expand and inline
141
184
  lexical: justDollarSelf,
142
185
  dollar: true,
143
186
  dynamic: nestedElements,
144
187
  notFound: undefinedNestedElement,
188
+ check: checkColumnRef,
145
189
  param: paramSemantics,
146
190
  }),
147
191
  },
148
- 'param-only': {
192
+ 'from-args': {
149
193
  lexical: null,
150
194
  dollar: true,
151
195
  dynamic: () => Object.create( null ),
@@ -156,268 +200,115 @@ function fns( model ) {
156
200
  lexical: justDollarSelf,
157
201
  dollar: true,
158
202
  dynamic: parentElements,
203
+ navigation: calcElemNavigation,
159
204
  notFound: undefinedParentElement,
160
205
  param: paramUnsupported,
161
206
  },
162
- joinOn: {
207
+ 'join-on': {
163
208
  lexical: tableAliasesAndSelf,
164
209
  dollar: true,
165
- dynamic: combinedSourcesOrParentElements, // TODO: source alone...
210
+ dynamic: combinedSourcesOrParentElements,
211
+ rejectRoot: rejectOwnExceptVisibleAliases,
166
212
  notFound: undefinedSourceElement,
167
213
  param: paramSemantics,
168
214
  },
169
215
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
170
216
  lexical: justDollarSelf,
171
- allowBareSelf: true,
172
217
  dollar: true,
173
218
  dynamic: parentElements,
219
+ navigation: assocOnNavigation,
174
220
  notFound: undefinedParentElement,
221
+ accept: acceptElemOrVarOrSelf,
222
+ check: checkAssocOn,
175
223
  param: paramUnsupported,
176
224
  nestedColumn: () => ({ // in expand and inline
177
225
  lexical: justDollarSelf,
178
226
  dollar: true,
179
227
  dynamic: parentElements,
228
+ navigation: assocOnNavigation,
180
229
  notFound: undefinedParentElement,
181
230
  }),
182
231
  },
183
232
  'mixin-on': {
184
233
  lexical: tableAliasesAndSelf,
185
- allowBareSelf: true,
186
234
  dollar: true,
187
235
  dynamic: combinedSourcesOrParentElements,
236
+ navigation: assocOnNavigation,
188
237
  notFound: undefinedSourceElement,
238
+ accept: acceptElemOrVarOrSelf,
239
+ check: checkAssocOn,
189
240
  param: paramSemantics, // TODO: check that assocs containing param in ON is not published
190
241
  },
191
- 'order-by-ref': {
242
+ 'orderBy-ref': {
192
243
  lexical: tableAliasesAndSelf,
193
244
  dollar: true,
194
245
  dynamic: parentElements,
195
246
  notFound: undefinedOrderByElement,
247
+ check: checkOrderByRef,
196
248
  param: paramSemantics,
197
249
  },
198
- 'order-by-expr': {
250
+ 'orderBy-expr': {
199
251
  lexical: tableAliasesAndSelf,
200
252
  dollar: true,
201
253
  dynamic: combinedSourcesOrParentElements,
202
254
  notFound: undefinedSourceElement,
255
+ check: checkRefInQuery,
203
256
  param: paramSemantics,
204
257
  },
205
- 'order-by-set-ref': {
258
+ 'orderBy-set-ref': {
206
259
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
207
260
  dollar: true,
208
261
  dynamic: queryElements,
262
+ rejectRoot: rejectOwnAliasesAndMixins,
209
263
  notFound: undefinedParentElement,
264
+ check: checkOrderByRef,
210
265
  param: paramSemantics,
211
266
  },
212
- 'order-by-set-expr': {
267
+ 'orderBy-set-expr': {
213
268
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
214
269
  dollar: true,
215
270
  dynamic: () => Object.create( null ),
271
+ rejectRoot: rejectAllOwn,
216
272
  notFound: undefinedVariable,
273
+ check: checkRefInQuery,
217
274
  param: paramSemantics,
218
275
  },
219
276
  };
220
277
 
221
- // TODO: combine envFn and assoc ?
222
- const specExpected = {
223
- using: {}, // for using declaration
224
- // TODO: artifact references ---------------------------------------------
225
- extend: {},
226
- // ref in top-level EXTEND
227
- annotate: {},
228
- _extensions: {},
229
- type: { // TODO: more detailed later (e.g. for enum base type?)
230
- check: checkTypeRef,
231
- expectedMsgId: 'ref-expecting-type',
232
- sloppyMsgId: 'ref-sloppy-type',
233
- },
234
- actionParamType: {
235
- check: checkActionParamTypeRef,
236
- expectedMsgId: 'ref-expecting-action-param-type',
237
- sloppyMsgId: 'ref-sloppy-actionparam-type',
238
- },
239
- eventType: {
240
- check: checkEventTypeRef,
241
- expectedMsgId: 'ref-expecting-event-type',
242
- sloppyMsgId: 'ref-sloppy-event-type',
243
- },
244
- include: {
245
- check: checkIncludesRef,
246
- expectedMsgId: 'ref-expecting-struct',
247
- },
248
- viewInclude: {
249
- check: checkViewIncludesRef,
250
- expectedMsgId: 'ref-expecting-bare-aspect',
251
- },
252
- target: {
253
- check: checkEntityRef,
254
- expectedMsgId: 'ref-expecting-entity',
255
- noDep: true,
256
- },
257
- targetAspect: {
258
- check: checkTargetRef,
259
- expectedMsgId: 'ref-expecting-target',
260
- sloppyMsgId: 'ref-sloppy-target',
261
- noDep: 'only-entity',
262
- },
263
- from: {
264
- check: checkSourceRef,
265
- expectedMsgId: 'ref-expecting-source',
266
- assoc: 'from',
267
- argsSpec: 'expr',
268
- },
269
- // element references ----------------------------------------------------
270
- // TODO: dep for (explicit+implicit!) foreign keys
271
- // TODO: also check that we do not follow associations in foreign key? no args, no filter
272
- targetElement: { assoc: false },
273
- filter: {
274
- escape: 'param',
275
- },
276
- 'calc-filter': {
277
- escape: 'param',
278
- },
279
- default: {
280
- check: checkConstRef,
281
- expectedMsgId: 'ref-expecting-const',
282
- },
283
- expr: {
284
- escape: 'param', assoc: 'nav',
285
- },
286
- 'param-only': {
287
- escape: 'param',
288
- },
289
- calc: {
290
- assoc: 'nav',
291
- },
292
- joinOn: { // ON condition for JOIN: should be different to 'expr'!
293
- escape: 'param', assoc: 'nav',
294
- },
295
- on: { // TODO: there will also be a 'from-on' (see 'expr')
296
- noDep: true, // do not set dependency for circular-check
297
- allowSelf: true,
298
- }, // TODO: special assoc for only on user
299
- 'mixin-on': {
300
- escape: 'param', // TODO: extra check that assocs containing param in ON is not published
301
- noDep: true, // do not set dependency for circular-check
302
- allowSelf: true,
303
- }, // TODO: special assoc for only on user
304
- // ---------marker for getPathRoot replaced-----------
305
- 'order-by-ref': {
306
- next: '_$next',
307
- dollar: true,
308
- escape: 'param',
309
- assoc: 'nav',
310
- dynamic: 'query',
311
- deprecatedSourceRefs: true,
312
- },
313
- 'order-by-expr': {
314
- next: '_$next',
315
- dollar: true,
316
- escape: 'param',
317
- assoc: 'nav',
318
- },
319
- 'order-by-set-ref': {
320
- next: '_$next',
321
- dollar: true,
322
- escape: 'param',
323
- noDep: true,
324
- dynamic: 'query',
325
- lexical: 'next',
326
- },
327
- 'order-by-set-expr': {
328
- next: '_$next',
329
- dollar: true,
330
- escape: 'param',
331
- noDep: true,
332
- dynamic: false,
333
- lexical: 'next',
334
- },
335
- // expr TODO: better - on condition for assoc, other on
336
- // expr TODO: write dependency, but care for $self
337
- param: {
338
- check: checkConstRef,
339
- expectedMsgId: 'ref-expecting-const',
340
- },
341
- };
342
-
343
278
  Object.assign( model.$functions, {
279
+ traverseExpr,
344
280
  resolveUncheckedPath,
345
- resolveTypeArgumentsUnchecked,
281
+ resolveTypeArgumentsUnchecked, // TODO: move to some other file
346
282
  resolvePath,
283
+ checkExpr,
284
+ checkOnCondition,
285
+ nestedElements,
347
286
  attachAndEmitValidNames,
348
287
  } );
349
288
  return;
350
289
 
351
- function checkConstRef( art ) {
352
- return art.kind !== 'builtin' && art.kind !== 'param';
353
- }
354
-
355
- function checkIncludesRef( art ) {
356
- // We currently disallow using
357
- // - derived structure types: would have to follow type in extend/include;
358
- // - entities with params: clarify inheritance, use of param in ON/DEFAULT;
359
- // - query entities/events: difficult sequence of resolve steps
360
- // - aspect without elements (useful for actions/annotations)
361
- return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
362
- }
363
-
364
- /**
365
- * Returns true, if the given artifact can be included by a query entity / view.
366
- *
367
- * We currently allow:
368
- * - aspects without elements (the aspect may have actions):
369
- * either no `elements` property or empty dictionary
370
- *
371
- * @param {XSN.Artifact} art
372
- * @return {boolean}
373
- */
374
- function checkViewIncludesRef( art ) {
375
- return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
376
- }
377
-
378
- /**
379
- * @returns {boolean|string}
380
- */
381
- function checkTypeRef( art ) {
382
- if (art.kind === 'type' || art.kind === 'element')
383
- return false;
384
- return ![ 'entity', 'aspect', 'event' ].includes( art.kind ) || 'sloppy';
385
- }
386
-
387
- /**
388
- * @returns {boolean|string}
389
- */
390
- function checkActionParamTypeRef( art ) {
391
- return !(art.kind === 'entity' && art._service) && checkTypeRef( art );
392
- }
393
-
394
- /**
395
- * @returns {boolean|string}
396
- */
397
- function checkEventTypeRef( art ) {
398
- return art.kind !== 'event' && checkActionParamTypeRef( art );
399
- }
400
-
401
- function checkEntityRef( art ) {
402
- return art.kind !== 'entity';
403
- }
290
+ // Expression traversal function ----------------------------------------------
291
+ function traverseExpr( expr, exprCtx, user, callback ) {
292
+ if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
293
+ return;
404
294
 
405
- /**
406
- * @returns {boolean|string}
407
- */
408
- function checkTargetRef( art ) {
409
- if (art.kind === 'entity' || art.kind === 'aspect')
410
- return false;
411
- return art.kind !== 'type' || 'sloppy';
412
- }
295
+ if (expr.path) {
296
+ callback( expr, exprCtx, user );
297
+ // TODO: move arguments and filter traversal to here
298
+ return;
299
+ }
300
+ else if (expr.type || expr.query) {
301
+ callback( expr, exprCtx, user );
302
+ }
413
303
 
414
- function checkSourceRef( art, path ) { // for FROM
415
- if (!art._main)
416
- return (art.kind !== 'entity');
417
- const elem = path.find( item => item._artifact._main )._artifact;
418
- // at least the last main definition should be an entity
419
- // an additional check for target would need effectiveType()
420
- return (elem && elem._main.kind !== 'entity');
304
+ if (expr.args) {
305
+ const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
306
+ // TODO: re-think $expected
307
+ if (!callback.traverse?.( args, exprCtx, user, callback ))
308
+ args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
309
+ }
310
+ if (expr.suffix) // fn( ) OVER …
311
+ expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
421
312
  }
422
313
 
423
314
  // Return absolute name for unchecked path `ref`. We first try searching for
@@ -441,16 +332,18 @@ function fns( model ) {
441
332
  if (Array.isArray( art ))
442
333
  art = art[0];
443
334
  if (!art)
444
- return (semantics.notFound) ? art : pathName( path );
335
+ return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
445
336
  if (path.length === 1)
446
337
  return art.name.absolute; // TODO: name.id
447
338
  return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
448
339
  }
449
340
 
450
- // Return artifact or element referred by the path in `ref`. The first
451
- // environment we search in is `env`. If no such artifact or element exist,
452
- // complain with message and return `undefined`. Record a dependency from
453
- // `user` to the found artifact if `user` is provided.
341
+ /**
342
+ * Return artifact or element referred by the path in `ref`. The first
343
+ * environment we search in is `env`. If no such artifact or element exist,
344
+ * complain with message and return `undefined`. Record a dependency from
345
+ * `user` to the found artifact if `user` is provided.
346
+ */
454
347
  function resolvePath( ref, expected, user ) {
455
348
  const origUser = user;
456
349
  user = user._user || user;
@@ -465,67 +358,46 @@ function fns( model ) {
465
358
  return setArtifactLink( ref, undefined );
466
359
  }
467
360
 
468
- const s = referenceSemantics[expected]; // TODO: temp indirection
361
+ const s = referenceSemantics[expected];
469
362
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
470
363
 
471
364
  const r = getPathRoot( ref, semantics, origUser );
472
- const root = r && acceptPathRoot( r, ref, semantics, user );
365
+ const root = r && acceptPathRoot( r, ref, semantics, origUser );
473
366
  if (!root)
474
367
  return setArtifactLink( ref, root );
475
368
 
476
- let spec = specExpected[expected];
477
- if (ref.scope === 'param') {
478
- if (!spec.escape)
479
- throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
480
- spec = specExpected[spec.escape];
481
- }
482
-
483
369
  // how many path items are for artifacts (rest: elements)
484
370
  // console.log(expected, ref.path.map(a=>a.id),artItemsCount)
485
- let art = getPathItem( ref, semantics, spec, user );
371
+ let art = getPathItem( ref, semantics, user );
486
372
  if (!art)
487
373
  return setArtifactLink( ref, art );
488
374
 
489
- if (art.$autoElement) {
490
- const { location } = path[path.length - 1];
491
- const step = { id: art.$autoElement, $inferred: '$autoElement', location };
492
- art = art.elements[step.id];
493
- setArtifactLink( step, art );
494
- path.push( step );
495
- }
496
- if (spec.check && !ref.$expected) { // do not check with exists path (is already error)
497
- const fail = spec.check( art, path );
498
- if (fail === true) {
499
- signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
500
- return setArtifactLink( ref, false );
501
- }
502
- else if (fail) {
503
- signalNotFound( spec.sloppyMsgId, [ ref.location, user ], null );
504
- // no return!
505
- }
506
- }
507
- if (spec.warn) {
508
- const msgId = spec.warn( art, user );
509
- if (msgId)
510
- warning( msgId, [ ref.location, user ] );
511
- }
512
- if (user && (!spec.noDep ||
513
- spec.noDep === 'only-entity' && art.kind !== 'entity')) {
514
- const { location } = ref; // || combinedLocation( head, path[tail.length] );
515
- // TODO: location of last path item if not main artifact
516
- if (spec.assoc === 'from' && art._main) {
517
- dependsOn( user, art._main, location );
375
+ // TODO: use isMainRef string value here?
376
+ const acceptFn = semantics.accept || (semantics.isMainRef ? a => a : acceptElemOrVar);
377
+ art = setArtifactLink( ref, acceptFn( art, user, ref, semantics ) );
378
+
379
+ // TODO TMP: remove noDep: an association does not depend on the target, only
380
+ // -- on its keys/on, which depend on certain target elements
381
+ if (art && user && !semantics.noDep) {
382
+ const location = artifactRefLocation( ref );
383
+ if (semantics.noDep === '' && art._main) { // assoc in FROM
518
384
  environment( art, location, user );
385
+ const target = art._effectiveType?.target?._artifact;
386
+ if (target)
387
+ dependsOn( user._main, target, location, user );
519
388
  }
520
- else if (art.kind !== 'select') { // no real dependency to bare $self
389
+ else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
390
+ // no real dependency to bare $self (or actually: the underlying query)
521
391
  dependsOn( user, art, location );
522
392
  // Without on-demand resolve, we can simply signal 'undefined "x"'
523
393
  // instead of 'illegal cycle' in the following case:
524
394
  // element elem: type of elem.x;
525
395
  }
396
+ // TODO: really write dependency with expand/inline? write test
397
+ // (removing it is not incompatible => not urgent)
526
398
  }
527
399
  // TODO: follow FROM here, see csnRef - fromRef
528
- return setArtifactLink( ref, art );
400
+ return art;
529
401
  }
530
402
 
531
403
  /**
@@ -533,7 +405,7 @@ function fns( model ) {
533
405
  * User is used for semantic message location.
534
406
  *
535
407
  * For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
536
- * in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
408
+ * from `art.$typeArgs` (a vector of numbers with locations) to `artifact.<prop>`.
537
409
  *
538
410
  * For non-builtins, we take either one or two arguments and interpret them
539
411
  * as `length` or `precision`/`scale`.
@@ -550,16 +422,14 @@ function fns( model ) {
550
422
  let args = artifact.$typeArgs || [];
551
423
  const parameters = typeArtifact?.parameters || [];
552
424
 
553
- if (parameters.length > 0) {
425
+ if (args.length > 0 && parameters.length > 0) {
554
426
  // For Builtins
555
427
  for (let i = 0; i < parameters.length; ++i) {
556
- let par = parameters[i];
557
- if (!(par instanceof Object))
558
- par = { name: par };
559
- if (!artifact[par.name] && i < args.length)
560
- artifact[par.name] = args[i];
428
+ const par = parameters[i].name || parameters[i];
429
+ if (!artifact[par] && i < args.length)
430
+ artifact[par] = args[i];
561
431
  }
562
- args = args.slice(parameters.length);
432
+ args = args.slice( parameters.length );
563
433
  }
564
434
  else if (args.length > 0 && !typeArtifact?.builtin) {
565
435
  // One or two arguments are interpreted as either length or precision/scale.
@@ -622,9 +492,9 @@ function fns( model ) {
622
492
  const [ nextProp, dictProp ] = (isMainRef)
623
493
  ? [ '_block', 'artifacts' ]
624
494
  : [ '_$next', '$tableAliases' ];
625
- // let notApplicable = ...; // for table aliases in JOIN-ON and UNION order-by
495
+ // let notApplicable = ...; // for table aliases in JOIN-ON and UNION orderBy
626
496
  for (let env = lexical; env; env = env[nextProp]) {
627
- const dict = env[dictProp] || Object.create(null);
497
+ const dict = env[dictProp] || Object.create( null );
628
498
  const r = dict[head.id];
629
499
  if (acceptLexical( r, path, semantics, user ))
630
500
  return setArtifactLink( head, r );
@@ -646,8 +516,6 @@ function fns( model ) {
646
516
  valid.push( dynamicDict );
647
517
  else
648
518
  valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
649
- if (semantics.notFound === false)
650
- return setArtifactLink( head, null );
651
519
  // TODO: streamline function arguments (probably: user, path, semantics )
652
520
  const undef = semantics.notFound( ruser, head, valid, dynamicDict,
653
521
  !isMainRef && user._user && user._artifact, path );
@@ -661,7 +529,7 @@ function fns( model ) {
661
529
  // TODO - think about setting _navigation for all $navElement – the
662
530
  // "ref: ['tabAlias']: inline: […]" handling might be easier
663
531
  // (no _pathHead consultation for key prop and renaming support)
664
- function getPathItem( ref, semantics, spec, user ) {
532
+ function getPathItem( ref, semantics, user ) {
665
533
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
666
534
  const { path } = ref;
667
535
  let artItemsCount = 0;
@@ -671,11 +539,10 @@ function fns( model ) {
671
539
  (ref.scope ? 1 : path.length);
672
540
  }
673
541
  let art = null;
674
- let nav = spec.assoc !== '$keys' && null; // false for '$keys'
675
- const last = path[path.length - 1];
676
- // TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
677
542
  const elementsEnv = semantics.navigation || environment;
543
+ let index = -1;
678
544
  for (const item of path) {
545
+ ++index;
679
546
  --artItemsCount;
680
547
  if (!item?.id) // incomplete AST due to parse error
681
548
  return undefined;
@@ -686,33 +553,24 @@ function fns( model ) {
686
553
 
687
554
  const prev = art;
688
555
  const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
689
- const env = envFn( art, item.location, user, spec.assoc );
690
- art = setArtifactLink( item, env?.[item.id] );
556
+ // TOOD: call envFn with location of last item (for dependency error)
557
+ const env = envFn( art, path[index - 1].location, user );
558
+ const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
559
+ // Reject `$self.$_column_1`: TODO: necessary to do here again?
560
+ art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
691
561
 
692
562
  if (!art) {
693
- // element was not found in environment
694
-
695
563
  // TODO (done?): if `env` was 0, we might set a dependency to induce an
696
564
  // illegal-cycle error instead of reporting via `errorNotFound`.
697
- if (prev.$uncheckedElements) { // magic variable / replacement variable
698
- signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
699
- { id: pathName( path ) } );
700
- }
701
- else if (isMainRef && artItemsCount >= 0) { // artifact ref
702
- if (semantics.notFound === false)
703
- return null;
704
- // TODO: streamline funtion arguments (probably: user, path, semantics, prev )
705
- semantics.notFound( user, item, [ env ], null, prev, path );
706
- }
707
- else {
708
- errorNotFound( item, env, prev, spec, user );
709
- }
565
+ const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
566
+ // TODO: streamline function arguments (probably: user, path, semantics, prev )
567
+ // false returned by semantics.navigation: no further error:
568
+ if (env !== false)
569
+ notFound( user, item, [ env ], null, prev, path );
710
570
  return null;
711
571
  }
712
- // TODO: what what about extra dependencies if we navigate along
713
- // associations? See also extra args for environment()
714
- nav = setSomeNavigationLinkForAssoc( nav, item, art, prev?.target, last, user );
715
572
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
573
+ // TODO: but Service.AutoExposed.NotAuto should be fine
716
574
  if (isMainRef !== 'all' && artItemsCount === 0 &&
717
575
  art.$inferred === 'autoexposed' && !user.$inferred) {
718
576
  // Depending on the processing sequence, the following could be a
@@ -721,92 +579,12 @@ function fns( model ) {
721
579
  error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
722
580
  // eslint-disable-next-line max-len
723
581
  'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
724
- // return null;
582
+ return null; // continuation semantics: like “not found”
725
583
  }
726
584
  }
727
- // Final checks on artifact (could be done in an extra function)
728
- if (art.$requireElementAccess) { // on some CDS variables
729
- // Path with only one item, but we expect an element, e.g. `$at.from`.
730
- signalMissingElementAccess(art, [ last.location, user ]);
731
- }
732
585
  return art;
733
586
  }
734
587
 
735
- // To be analysed what it does exactly, see changes #3660, #3666:
736
- function setSomeNavigationLinkForAssoc( nav, item, sub, target, last, user ) {
737
- if (nav) { // we have already "pseudo-followed" a managed association
738
- // We currently rely on the check that targetElement references do
739
- // not (pseudo-) follow associations, otherwise potential redirection
740
- // there had to be considered, too. Also, fk refs to sub elements in
741
- // combinations with redirections of the target which directly access
742
- // the potentially renamed sub elements would be really complex.
743
- // With our restriction, no renaming must be considered for item.id.
744
- setTargetReferenceKey( item.id );
745
- }
746
- // Now set an _navigation link for managed assocs in ON condition etc
747
- else if (target && nav != null) {
748
- // Find the original ref for sub and the original foreign key
749
- // definition. This way, we do not need the foreign keys with
750
- // rewritten target element path, which might not be available at
751
- // this point (rewriteKeys in Resolver Phase 5). If we want to
752
- // follow associations in foreign key definitions, rewriteKeys must
753
- // be moved to the on-demand Resolver Phase 2.
754
- let orig; // for the original target element
755
- for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
756
- orig = o;
757
- nav = (orig._effectiveType || orig).$keysNavigation;
758
- setTargetReferenceKey( orig.name.id );
759
- }
760
- return nav;
761
-
762
- function setTargetReferenceKey( id ) {
763
- const node = nav && nav[id];
764
- nav = null;
765
- if (node) {
766
- if (node._artifact) {
767
- // set the original(!) foreign key for the assoc - the "right" ones
768
- // after rewriteKeys() is the one with the same name.id
769
- setLink( item, '_navigation', node._artifact );
770
- if (item === last)
771
- return;
772
- }
773
- else if (item !== last) {
774
- nav = node.$keysNavigation;
775
- return;
776
- }
777
- }
778
- error( null, [ item.location, user ], {},
779
- // eslint-disable-next-line max-len
780
- 'You can\'t follow associations other than to elements referred to in a managed association\'s key' );
781
- }
782
- }
783
-
784
- function errorNotFound( item, env, art, spec, user ) {
785
- if (art.name && art.name.select && art.name.select > 1) {
786
- // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
787
- // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
788
- // TODO: probably not extra messageId, but text variant
789
- // TODO: views elements are proxies to query-0 elements, not the same
790
- // TODO: better message text
791
- signalNotFound( 'query-undefined-element', [ item.location, user ],
792
- [ env ], { id: item.id } );
793
- }
794
- else if (art.kind === '$parameters') {
795
- signalNotFound( 'ref-undefined-param', [ item.location, user ],
796
- [ env ], { art: art._main, id: item.id } );
797
- }
798
- else {
799
- const variant = art.kind === 'aspect' && !art.name && 'aspect';
800
- signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
801
- [ env ], {
802
- '#': variant,
803
- art: (variant ? '' : searchName( art, item.id, 'element' )),
804
- id: item.id,
805
- } );
806
- }
807
- return null;
808
- }
809
-
810
588
  // Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
811
589
 
812
590
  function acceptLexical( art, path, semantics, user ) {
@@ -815,14 +593,13 @@ function fns( model ) {
815
593
  // Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
816
594
  // Do not accept a lonely table alias and `$projection`
817
595
  // TODO: test table alias and mixin named `$projection`
818
- if (path.length === 1 && !user.expand && !user.inline) { // accept lonely…
819
- // allow mixins, and `up_` in anonymous target aspect:
820
- if (art.kind !== '$self' || path[0].id !== '$self')
821
- return art.kind === 'mixin' || art.kind === '$navElement';
822
- return true;
823
- }
824
- // return !art.$internal && art;
825
- return art.$inferred !== '$internal';
596
+ if (path.length !== 1 || user.expand || user.inline)
597
+ return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
598
+
599
+ // allow mixins, $self, and `up_` in anonymous target aspect (is $navElement):
600
+ return art.kind === 'mixin' ||
601
+ art.kind === '$self' && path[0].id === '$self' ||
602
+ art.kind === '$navElement';
826
603
  }
827
604
 
828
605
  function acceptPathRoot( art, ref, semantics, user ) {
@@ -830,6 +607,9 @@ function fns( model ) {
830
607
  const [ head ] = path;
831
608
  if (Array.isArray( art ))
832
609
  return getAmbiguousRefLink( art, head, user );
610
+ if (semantics.rejectRoot?.( art, user, ref, semantics ))
611
+ return null;
612
+
833
613
  switch (art.kind) {
834
614
  case 'using': {
835
615
  const def = model.definitions[art.name.absolute];
@@ -843,8 +623,8 @@ function fns( model ) {
843
623
  return setLink( head, '_navigation', art );
844
624
  }
845
625
  case '$navElement': {
846
- if (head.id === user.$extended)
847
- path.$prefix = user.$extended;
626
+ if (head.id === (user._user || user).$extended)
627
+ path.$prefix = head.id;
848
628
  setLink( head, '_navigation', art );
849
629
  return setArtifactLink( head, art._origin );
850
630
  }
@@ -858,13 +638,7 @@ function fns( model ) {
858
638
  // that the corresponding entity should not be put as $origin into the CSN.
859
639
  // TODO: remove again, should be easy enough in to-csn without.
860
640
  if (path.length === 1 && art.kind === '$tableAlias')
861
- user.$noOrigin = true;
862
- if (path.length === 1 && !semantics.allowBareSelf && !user.expand && !user.inline) {
863
- // TODO: better ref-invalid-self
864
- error( 'ref-unexpected-self', [ head.location, user ], { id: head.id } );
865
- // TODO: reject bare $projection here (new message id, configurable)
866
- // not really helpful to attach valid names here (would include $self)
867
- }
641
+ (user._user || user).$noOrigin = true;
868
642
  return art;
869
643
  }
870
644
  case '$parameters': { // TODO: remove from CC
@@ -886,14 +660,14 @@ function fns( model ) {
886
660
  return false;
887
661
  // only complain about ambiguous source elements if we do not have
888
662
  // duplicate table aliases, only mention non-ambiguous source elems
889
- const uniqueNames = arr.filter( e => !e.$duplicates);
663
+ const uniqueNames = arr.filter( e => !e.$duplicates );
890
664
  if (uniqueNames.length) {
891
665
  const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
892
666
  .map( e => `${ e.name.alias }.${ e.name.element }` );
893
667
  let variant = names.length === uniqueNames.length ? 'std' : 'few';
894
668
  if (names.length === 0)
895
669
  variant = 'none';
896
- error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
670
+ error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names } );
897
671
  }
898
672
  return false;
899
673
  }
@@ -902,11 +676,13 @@ function fns( model ) {
902
676
 
903
677
  function typeOfSemantics( user, [ head ] ) {
904
678
  // `type of` is only allowed for (sub) elements of main artifacts
679
+ while (!user.kind && user._outer)
680
+ user = user._outer;
905
681
  let struct = user;
906
682
  while (struct.kind === 'element')
907
683
  struct = struct._parent;
908
684
  if (struct === user._main && struct.kind !== 'annotation')
909
- return { dynamic: typeOfParentDict };
685
+ return { dynamic: typeOfParentDict, navigation: staticTarget };
910
686
  error( 'type-unexpected-typeof', [ head.location, user ],
911
687
  { keyword: 'type of', '#': struct.kind } );
912
688
  return false;
@@ -916,7 +692,7 @@ function fns( model ) {
916
692
  return { dynamic: artifactParams, notFound: undefinedParam };
917
693
  }
918
694
  function paramUnsupported( user, _path, location ) {
919
- error( 'ref-unexpected-scope', [ location, user ],
695
+ error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
920
696
  // why an extra text for calculated elements? or separate for all?
921
697
  { '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
922
698
  return false;
@@ -933,7 +709,7 @@ function fns( model ) {
933
709
  return user._main || user;
934
710
  // query.$tableAliases contains both aliases and $self/$projection
935
711
  const aliases = query.$tableAliases;
936
- const r = Object.create(null);
712
+ const r = Object.create( null );
937
713
  if (aliases.$self.kind === '$self')
938
714
  r.$self = aliases.$self;
939
715
  // TODO: disallow $projection for ON conditions all together
@@ -979,9 +755,10 @@ function fns( model ) {
979
755
  }
980
756
 
981
757
  function targetElements( user, pathItemArtifact ) {
982
- // const assoc = user._parent;
983
- // const target = resolvePath( assoc.target, 'target', assoc );
984
- return environment( pathItemArtifact || user._parent, null, null, null, true );
758
+ // has already been computed - no further `navigationEnv` args needed
759
+ const env = navigationEnv( pathItemArtifact || user._parent );
760
+ // do not use env?.elements: a `0` should stay a `0`:
761
+ return env && env.elements;
985
762
  }
986
763
 
987
764
  function combinedSourcesOrParentElements( user ) {
@@ -999,27 +776,58 @@ function fns( model ) {
999
776
  }
1000
777
 
1001
778
  function nestedElements( user ) {
1002
- Functions.navigationEnv( user._pathHead ); // set _origin
1003
- return environment( user._pathHead._origin );
779
+ const colParent = user._pathHead;
780
+ Functions.effectiveType( colParent ); // set _origin
781
+ const path = colParent?.value?.path;
782
+ if (!path?.length)
783
+ return undefined;
784
+ // also set dependency when navigating along assoc → provide location
785
+ return environment( colParent._origin, path[path.length - 1].location, colParent );
786
+ }
787
+
788
+ // Function called via semantics.navigation: ----------------------------------
789
+ // default is function `environment`
790
+
791
+ function artifactsEnv( art ) {
792
+ return art._subArtifacts || Object.create( null );
1004
793
  }
1005
794
 
1006
- function targetAspectOnly( prev ) {
1007
- let env = Functions.navigationEnv( prev, null, null, 'targetAspectOnly' );
1008
- while (env?.target && !env.targetAspect)
1009
- env = env._origin || env.type?._artifact;
795
+ function staticTarget( prev ) {
796
+ let env = navigationEnv( prev ); // we do not write dependencies for assoc navigation
1010
797
  if (env === 0)
1011
798
  return 0;
799
+ // Last try - Composition with targetAspect only (in aspect def):
1012
800
  const target = env?.targetAspect;
1013
801
  if (target) {
1014
802
  if (target.elements)
1015
803
  return target.elements;
1016
804
  env = resolvePath( env.targetAspect, 'targetAspect', env );
1017
805
  }
1018
- return env?.elements || Object.create(null);
806
+ return env?.elements || Object.create( null );
1019
807
  }
1020
808
 
1021
- function artifactsEnv( art ) {
1022
- return art._subArtifacts || Object.create(null);
809
+ function targetNavigation( art, location, user ) {
810
+ const env = navigationEnv( art, location, user, false );
811
+ // do not use env?.elements: a `0`/false should stay a `0`/false:
812
+ return env && env.elements;
813
+ }
814
+
815
+ function assocOnNavigation( art, location, user ) {
816
+ const env = navigationEnv( art, location, user, null );
817
+ // `null` means: do not write a dependency from target of any association
818
+ // otherwise “following” own assoc would lead to cycle.
819
+ // TODO: disallow navigation other than of own assoc, and to foreign keys
820
+ // This way (not here though, but later in resolve.js)
821
+ if (env === 0)
822
+ return 0;
823
+ return env?.elements || Object.create( null );
824
+ }
825
+
826
+ function calcElemNavigation( art, location, user ) {
827
+ const env = navigationEnv( art, location, user, 'calc' );
828
+ if (env === 0)
829
+ return 0;
830
+ return env?.elements || Object.create( null );
1023
831
  }
1024
832
 
1025
833
  // Return effective search environment provided by artifact `art`, i.e. the
@@ -1027,13 +835,47 @@ function fns( model ) {
1027
835
  // chain and resolve the association `target`. View elements are calculated
1028
836
  // on demand.
1029
837
  // TODO: what about location/user when called from getPath ?
1030
- // TODO: think of always acting as if falsyIfNone would be true
838
+ // TODO: think of removing `|| Object.create(null)`.
1031
839
  // (if not possible, move to second param position)
1032
- function environment( art, location, user, assocSpec, falsyIfNone ) {
1033
- const env = Functions.navigationEnv( art, location, user, assocSpec );
840
+ function environment( art, location, user ) {
841
+ const env = navigationEnv( art, location, user, 'nav' );
1034
842
  if (env === 0)
1035
843
  return 0;
1036
- return env?.elements || !falsyIfNone && Object.create(null);
844
+ return env?.elements || Object.create( null );
845
+ }
846
+
847
+ function navigationEnv( art, location, user, assocSpec ) {
848
+ // = effectiveType() on from-path, TODO: should actually already part of
849
+ // resolvePath() on FROM
850
+ if (!art)
851
+ return undefined;
852
+ let type = Functions.effectiveType( art );
853
+ while (type?.items) // TODO: disallow navigation to many sometimes
854
+ type = Functions.effectiveType( type.items );
855
+ if (!type?.target)
856
+ return type;
857
+
858
+ if (assocSpec === false) { // TODO: move to getPathItem
859
+ error( null, [ location, user ], {},
860
+ 'Following an association is not allowed in an association key definition' );
861
+ return false;
862
+ } // TODO: else warning for assoc usage with falsy assocSpec
863
+ const target = type?.target._artifact;
864
+ if (!target)
865
+ return target;
866
+ // TODO: really write final dependency with expand/inline?
867
+ if (target && assocSpec && user) {
868
+ if (assocSpec !== 'calc')
869
+ dependsOn( user._main || user, target, location || user.location, user );
870
+ else // (TODO: users of) calc elements must depend on navigation target
871
+ dependsOn( user, target, location || user.location );
872
+ // TODO: have some _delayedDeps for calc elements
873
+ }
874
+ const effectiveTarget = Functions.effectiveType( target );
875
+ // if (effectiveTarget === 0 && location)
876
+ // dependsOn( user, user, (user.target || user.type || user.value || user).location );
877
+ // console.log('NT:',assocSpec,!!user,target)
878
+ return effectiveTarget;
1037
879
  }
1038
880
 
1039
881
  // Functions called via semantics.notFound: -----------------------------------
@@ -1072,10 +914,12 @@ function fns( model ) {
1072
914
  // TODO: avoid message if we have already complained about `(exists …)`?
1073
915
  const { id } = head;
1074
916
  const isVar = id.charAt( 0 ) === '$' && id !== '$self';
1075
- signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
917
+ // TODO: for wrong $self, also use ref-undefined-var, but with extra msg id
918
+ // otherwise, use s/th like ref-unexpected-element
919
+ signalNotFound( ( isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
1076
920
  [ head.location, user ],
1077
921
  valid, { '#': 'std', id } );
1078
- // TODO: use s/th better than 'ref-expecting-const'?
922
+ // TODO: use s/th better than 'ref-expecting-const' !!
1079
923
  }
1080
924
 
1081
925
  function undefinedSourceElement( user, head, valid, dynamicDict ) {
@@ -1110,7 +954,7 @@ function fns( model ) {
1110
954
  }
1111
955
  else {
1112
956
  // TODO: extra msg like ref-rejected-on if elem found in source elements?
1113
- // also whether users wronly tried to refer to aliases/mixins?
957
+ // also whether users wrongly tried to refer to aliases/mixins?
1114
958
  const msgVar = userQuery( user ) ? 'query' : null;
1115
959
  // TODO: better with ON in expand if that is supported
1116
960
  signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
@@ -1121,7 +965,7 @@ function fns( model ) {
1121
965
  function undefinedOrderByElement( user, head, valid, dynamicDict, _art, path ) {
1122
966
  const { id } = head;
1123
967
  const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
1124
- if (src && !Array.isArray(src)) {
968
+ if (src && !Array.isArray( src )) {
1125
969
  path.$prefix = src.name.alias; // pushing it to path directly could be problematic
1126
970
  // configurable error:
1127
971
  signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
@@ -1132,16 +976,550 @@ function fns( model ) {
1132
976
  return null;
1133
977
  }
1134
978
 
1135
- function undefinedNestedElement( user, head, valid ) {
1136
- // environment( user._pathHead ); // set _origin
979
+ function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
1137
980
  const art = user._pathHead._origin;
1138
- // if (!art) console.log('UNE:',user,user._pathHead)
1139
981
  if (!art)
1140
- return; // no consequential error
1141
- // TODO: better message with $ref
1142
- signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
1143
- { art: searchName( art, head.id, 'element' ) } );
1144
- // TODO: remove use of searchName() ?
982
+ return null; // no consequential error
983
+ return undefinedItemElement( user, head, valid, null, art, path );
984
+ }
985
+
986
+ function undefinedItemElement( user, item, valid, _dict, art, path ) {
987
+ if (art.name && art.name.select && art.name.select > 1) {
988
+ // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
989
+ // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
990
+ // both as text variants to ref-undefined-element
991
+ signalNotFound( 'query-undefined-element', [ item.location, user ],
992
+ valid, { id: item.id } );
993
+ }
994
+ else if (art.kind === '$parameters') {
995
+ signalNotFound( 'ref-undefined-param', [ item.location, user ],
996
+ valid, { art: art._main, id: item.id } );
997
+ }
998
+ else if (art.kind === 'builtin') { // magic variable / replacement variable
999
+ const id = (item === path[path.length - 1])
1000
+ ? item.id
1001
+ : pathName( path.slice( path.indexOf( item ) ) );
1002
+ signalNotFound( ( art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
1003
+ [ item.location, user ], valid,
1004
+ { id: `${ art.name.element }.${ id }` } );
1005
+ }
1006
+ else {
1007
+ const variant = art.kind === 'aspect' && !art.name && 'aspect';
1008
+ signalNotFound( 'ref-undefined-element', [ item.location, user ],
1009
+ valid, {
1010
+ '#': variant,
1011
+ art: (variant ? '' : searchName( art, item.id, 'element' )),
1012
+ id: item.id,
1013
+ } );
1014
+ }
1015
+ return null;
1016
+ }
1017
+
1018
+ // Functions called via semantics.accept: -------------------------------------
1019
+ // function arguments ( art, user, ref, semantics ),
1020
+ // default (for elements only): acceptElemOrVar
1021
+
1022
+ function rejectOwnAliasesAndMixins( art, user, ref, semantics ) { // orderBy-set-ref
1023
+ switch (art.kind) {
1024
+ case '$tableAlias':
1025
+ case 'mixin':
1026
+ if (art._parent !== user)
1027
+ return false;
1028
+ break;
1029
+ case '$self':
1030
+ if (!semantics) // orderBy-set-expr
1031
+ break;
1032
+ // FALLTHROUGH
1033
+ default:
1034
+ return false;
1035
+ }
1036
+ error( 'ref-invalid-element', [ ref.path[0].location, user._user ],
1037
+ { '#': art.kind, id: art.name.id } );
1038
+ return true;
1039
+ }
1040
+
1041
+ function rejectAllOwn( art, user, ref ) { // orderBy-set-expr
1042
+ return rejectOwnAliasesAndMixins( art, user, ref, null );
1043
+ }
1044
+
1045
+ function rejectOwnExceptVisibleAliases( art, user, ref ) { // for join-on
1046
+ switch (art.kind) {
1047
+ case '$navElement':
1048
+ art = art._parent;
1049
+ // FALLTHROUGH
1050
+ case '$tableAlias':
1051
+ case 'mixin':
1052
+ if (art._parent !== user._user || user.$tableAliases[art.name.id])
1053
+ return false;
1054
+ break;
1055
+ case '$self':
1056
+ // in the SQL backend, the $self.elem references are replaced by the
1057
+ // corresponding column expression; this might have references to elements
1058
+ // of invisible table aliases; at least one stakeholder uses this,
1059
+ // so it can't be an error (yet).
1060
+ warning( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1061
+ // eslint-disable-next-line max-len
1062
+ 'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
1063
+ return false;
1064
+ default:
1065
+ return false;
1066
+ }
1067
+ error( 'ref-invalid-element', [ ref.path[0].location, user._user ],
1068
+ { '#': art.kind, id: art.name.id } );
1069
+ return true;
1070
+ }
1071
+
1072
+ function acceptElemOrVarOrSelf( art, user, ref ) {
1073
+ // TODO: make $self._artifact point to the $self alias, not the entity
1074
+ return (!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self')
1075
+ ? art
1076
+ : acceptElemOrVar( art, user, ref );
1077
+ }
1078
+
1079
+ function acceptElemOrVar( art, user, ref ) {
1080
+ const { path } = ref;
1081
+ if (art.kind === 'builtin') {
1082
+ if (user.expand || user.inline) {
1083
+ const location = (user.expand || user.inline)[$location];
1084
+ const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
1085
+ message( 'def-unexpected-nested-proj', [ location, user ], { '#': 'var', code } );
1086
+ }
1087
+ else if (art.$requireElementAccess) { // on some CDS variables
1088
+ // Path with only one item, but we expect an element, e.g. `$at.from`.
1089
+ signalMissingElementAccess( art, [ path[0].location, user ] );
1090
+ return null;
1091
+ }
1092
+ else if (art.$autoElement) {
1093
+ const { location } = path[0];
1094
+ const step = { id: art.$autoElement, $inferred: '$autoElement', location };
1095
+ path.push( step );
1096
+ art = art.elements[step.id];
1097
+ return setArtifactLink( step, art );
1098
+ }
1099
+ }
1100
+ // TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
1101
+ else if (!user.expand && !user.inline && // $self._artifact to main artifact
1102
+ !(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
1103
+ // TODO: better ref-invalid-self
1104
+ const { location, id } = path[0];
1105
+ error( 'ref-unexpected-self', [ location, user ], { id } );
1106
+ // TODO: reject bare $projection here (new message id, configurable)
1107
+ // TODO: should we also attach valid names? Probably not...
1108
+ // TODO: return false; ??
1109
+ // return false;
1110
+ }
1111
+ return art;
1112
+ }
1113
+
1114
+ function acceptRealArtifact( art, user, ref ) {
1115
+ // For compatibility, we accept `extend Unknown` without elements/actions/includes
1116
+ if (art.kind !== 'namespace' || !(user.elements || user.actions || user.includes))
1117
+ return art;
1118
+ const { location } = ref.path[ref.path.length - 1];
1119
+ signalNotFound( 'ref-undefined-def', [ location, user ], null, { art } );
1120
+ return false;
1121
+ }
1122
+
1123
+ function acceptStructOrBare( art, user, ref ) { // for includes[]
1124
+ // It had been checked before that `includes` is already forbidden for
1125
+ // non-entity/aspect/type/event.
1126
+ //
1127
+ // We currently disallow as include:
1128
+ // - non-structured types or derived type of structured:
1129
+ // would have to follow type in extend/include;
1130
+ // - entities with params: clarify inheritance, use of param in ON/DEFAULT;
1131
+ // - query entities/events: difficult sequence of resolve steps
1132
+ // - aspect with one ore more elements on query entities / events
1133
+ // - aspect with `elements` property on non-structured types
1134
+
1135
+ // TODO: adapt `user` if it is an `extend`? NOTE: we cannot call
1136
+ // effectiveType() on user - it might be in the process of being computed!
1137
+ // Also, it is not clear whether `art.elements` has been completed → testing
1138
+ // its length might be processing-sequence dependent, see #11346. We must
1139
+ // ensure that an include does not add the `elements` property!
1140
+ const base = (user.kind === 'extend' ? user.name._artifact : user);
1141
+ if (!base)
1142
+ return art;
1143
+ if (base.query || base.type || !base.elements) {
1144
+ // Remark: it is not necessary to test for user.elements[$inferred], because
1145
+ // the type could only have inferred elements if it has a type expression.
1146
+ // Including aspects with elements is forbidden for aspects without the
1147
+ // `elements` property. Testing for the length of `art.elements` requires
1148
+ // that we have applied potential `includes` of `art` before!
1149
+ // We might allow includes with elements in the future, they'd probably
1150
+ // count as specified elements with lower priority, i.e. annos, types, key
1151
+ // etc on columns beat those inherited from the include.
1152
+ if (art.kind === 'aspect' &&
1153
+ (!art.elements || base.query && !Object.keys( art.elements ).length))
1154
+ return art;
1155
+ signalNotFound( 'ref-invalid-include', [ ref.location, user ], null,
1156
+ { '#': 'bare' } );
1157
+ }
1158
+ else {
1159
+ if (!art.query && !art.type && !art.params && (art.elements || art.kind === 'aspect'))
1160
+ return art;
1161
+ signalNotFound( 'ref-invalid-include', [ ref.location, user ], null );
1162
+ }
1163
+ return false;
1164
+ }
1165
+
1166
+ // Remember: an aspect should have already been moved to XSN targetAspect, but
1167
+ // the error messages should still talk about potential aspects
1168
+ function acceptEntity( art, user, ref ) { // for target
1169
+ if (art.kind === 'entity')
1170
+ return art;
1171
+ // Extra msg text with Composition of NeitherEntityNorAspect:
1172
+ const bare = !art.elements || art.elements[$inferred];
1173
+ const std = (art.targetAspect || !isDirectComposition( user ) || user.kind === 'mixin');
1174
+ const msg = std && 'std' || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
1175
+ signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
1176
+ { '#': msg } );
1177
+ return false;
1178
+ }
1179
+
1180
+ function acceptAspect( art, user, ref ) { // for targetAspect
1181
+ const bare = !art.elements || art.elements[$inferred];
1182
+ if (!bare) {
1183
+ if (art.kind === 'aspect')
1184
+ return art;
1185
+ if (art.kind === 'type') { // v4: Warning → config Error
1186
+ signalNotFound( 'ref-sloppy-target', [ ref.location, user ], null );
1187
+ return art;
1188
+ }
1189
+ }
1190
+ signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
1191
+ { '#': (bare ? 'bare' : 'aspect'), prop: 'targetAspect' } );
1192
+ return false;
1193
+ }
1194
+
1195
+ function acceptEntityOrAssoc( art, user, ref ) { // for FROM
1196
+ const { path, scope } = ref;
1197
+ // see getPathItem(): how many path items are for the main artifact ref?
1198
+ const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
1199
+ // at least the last main definition should be an entity
1200
+ // an additional check for target would need effectiveType()
1201
+ const source = path[artItemsCount - 1]._artifact;
1202
+ if (source.kind !== 'entity') {
1203
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1204
+ return false;
1205
+ }
1206
+ if (source === art)
1207
+ return art;
1208
+ const assoc = Functions.effectiveType( art );
1209
+ if (assoc.target)
1210
+ return art; // TODO: use target here
1211
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1212
+ return false;
1213
+ }
1214
+
1215
+ function acceptTypeOrElement( art, user, ref ) { // for type
1216
+ // was ['action', 'function'].includes( user._parent?.kind ))
1217
+ while (user._outer)
1218
+ user = user._outer;
1219
+ const kind = (user.kind !== 'param' || user._parent?.kind !== 'entity')
1220
+ ? user.kind
1221
+ : 'entity-param';
1222
+ switch (art.kind) {
1223
+ case 'type':
1224
+ case 'element':
1225
+ return art;
1226
+ case 'entity':
1227
+ if (kind === 'param' && art._service)
1228
+ return art;
1229
+ // FALLTHROUGH
1230
+ case 'event':
1231
+ if (kind === 'event')
1232
+ return art;
1233
+ break;
1234
+ default:
1235
+ break;
1236
+ }
1237
+ signalNotFound( 'ref-invalid-type', [ ref.location, user ], null, { '#': kind } );
1238
+ return false;
1239
+ }
1240
+
1241
+ // Functions called via semantics.check by checkExpr(): -----------------------
1242
+ //
1243
+ // function arguments: ( expr, exprCtx, user )
1244
+ // default: tbd (nothing for main artifac ref)
1245
+
1246
+ // Performs checks which would be too early to do via semantics.accept. It is
1247
+ // actually assumed that the foreign-keys / ON-condition rewrite has already
1248
+ // been done.
1249
+
1250
+ // Main check area "navigation" (see also semantics.navigation):
1251
+ // - navigation along any assoc
1252
+ // - navigation only along foreign keys
1253
+ // - (no navigation already via semantics.navigation for target refs of foreign keys)
1254
+ // - special (ON-condition of unmanaged associations)
1255
+
1256
+ // Main check area: checks on the referred artifact
1257
+ // - all artifacts are allowed
1258
+ // - all except unmanaged associations
1259
+ // - ...
1260
+
1261
+ function checkExpr( expr, exprCtx, user ) {
1262
+ if (!expr)
1263
+ return;
1264
+ const s = referenceSemantics[exprCtx];
1265
+ const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
1266
+ const checkFn = semantics.check; // || !semantics.isMainRef && checkElementStd;
1267
+
1268
+ if (checkFn)
1269
+ traverseExpr( expr, exprCtx, user, checkFn );
1270
+ }
1271
+
1272
+ // TODO: Don't allow path args and filter!
1273
+ function checkOnCondition( expr, exprCtx, user ) {
1274
+ if (!expr || expr.$inferred)
1275
+ return;
1276
+ const { op } = expr;
1277
+ let { args } = expr;
1278
+ if (!op || !args) {
1279
+ checkExpr( expr, exprCtx, user );
1280
+ return;
1281
+ }
1282
+ if (op?.val === '=') // TMP
1283
+ args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
1284
+
1285
+ for (let index = 0; index < args.length; ++index) {
1286
+ const item = args[index];
1287
+ const eq = args[index + 1];
1288
+ if (eq?.val === '=' && eq.literal === 'token' && item.path && !item.scope) {
1289
+ const right = args[index + 2];
1290
+ if (right?.path && !right.scope &&
1291
+ (isDollarSelfPair( item, right, user ) || isDollarSelfPair( right, item, user ))) {
1292
+ checkAssocOnSelf( item, exprCtx, user );
1293
+ checkAssocOnSelf( right, exprCtx, user );
1294
+ index += 2;
1295
+ continue;
1296
+ }
1297
+ }
1298
+ checkOnCondition( item, exprCtx, user );
1299
+ }
1300
+ }
1301
+
1302
+ // // standard element reference check;
1303
+ // function checkElementStd( art, _user, ref, semantics ) {
1304
+ // // No further checks on navigation: nothing to do
1305
+ // // Must not end with any association (TODO: allow managed?):
1306
+ // if (art.target) { // && art.on
1307
+ // // error
1308
+ // }
1309
+ // }
1310
+
1311
+ function checkColumnRef( expr, exprCtx, user ) {
1312
+ if (!expr.path)
1313
+ return;
1314
+ if (expr === user.value && // is already a syntax error with non-ref expression
1315
+ (user.expand || user.inline))
1316
+ checkExpandInlineRef( expr._artifact, user, expr );
1317
+
1318
+ const self = pathStartsWithSelf( expr );
1319
+ // console.log('NAV:',expr.path.map(r=>r.id),self)
1320
+ if (self || self == null && columnRefStartsWithSelf( user )) {
1321
+ checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1322
+ checkNoUnmanaged( expr, user, 'self-unmanaged' );
1323
+ }
1324
+ // TODO: set navigation dependencies later to avoid both ref-cyclic and
1325
+ // ref-invalid-navigation/ref-unexpected-assoc
1326
+ }
1327
+
1328
+ function checkOrderByRef( expr, exprCtx, user ) {
1329
+ const { path } = expr;
1330
+ if (!path)
1331
+ return;
1332
+ if (path?.[0]?._navigation?.kind !== '$tableAlias')
1333
+ checkOnlyForeignKeyNavigation( user, expr.path );
1334
+ checkNoUnmanaged( expr, user );
1335
+ }
1336
+
1337
+ function checkRefInQuery( expr, exprCtx, user ) {
1338
+ const { path } = expr;
1339
+ if (!path)
1340
+ return;
1341
+ if (pathStartsWithSelf( expr ))
1342
+ checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1343
+ checkNoUnmanaged( expr, user );
1344
+ }
1345
+
1346
+ function checkExpandInlineRef( art, user, ref ) {
1347
+ if (!art || !art._main || // $self has entity as _artifact
1348
+ art.kind === 'builtin') // no repeated error for CDS variables
1349
+ return;
1350
+ const effective = art._effectiveType;
1351
+ if (!effective || effective.target || effective.elements)
1352
+ return;
1353
+ const { path } = ref;
1354
+ const location = (user.expand || user.inline)[$location];
1355
+ // mention `table alias` in text only with initial single path item ref,
1356
+ // but do not mention that $self { … } is allowed, shouldn't be advertised:
1357
+ const txt = (path.length > 1 || user._pathHead) ? 'struct' : 'init';
1358
+ const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
1359
+ message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
1360
+ }
1361
+
1362
+ function isDollarSelfPair( left, right, user ) {
1363
+ if (!left.path || !right.path || left.scope || right.scope)
1364
+ return false; // param ref `:$self` is not $self
1365
+ // $self in entity (TODO: mixin? new assoc in col? in aspect?)
1366
+ const kind = right._artifact?.kind;
1367
+ if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
1368
+ return false; // (ok, this would also return `false` for `:$self`)
1369
+ const { path } = left;
1370
+ // TODO: we might return true and issue an extra error here
1371
+ return userTargetElementPathIndex( user, path ) > 0;
1372
+ }
1373
+
1374
+ function checkAssocOn( ref, exprCtx, user ) {
1375
+ const { path } = ref;
1376
+ if (!path)
1377
+ return;
1378
+ if (path.length === 1 && path[0]._navigation?.kind === '$self') {
1379
+ // resolvePath() for `on` allowed bare $self: disallow except in checkAssocOnSelf
1380
+ const { location, id } = path[0];
1381
+ error( 'ref-unexpected-self', [ location, user ], { '#': 'on', id } );
1382
+ return;
1383
+ }
1384
+ const index = userTargetElementPathIndex( user, path );
1385
+ checkOnlyForeignKeyNavigation( user, path, index );
1386
+ if (ref._artifact?.on) {
1387
+ const target = index > 0 && index < path.length && ref._artifact?.target;
1388
+ const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
1389
+ const last = path[path.length - 1];
1390
+ error( 'ref-unexpected-assoc', [ last.location, user ],
1391
+ { '#': msg, code: '= $self' } );
1392
+ }
1393
+ }
1394
+
1395
+ function checkAssocOnSelf( ref, exprCtx, user ) {
1396
+ // TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
1397
+ // target of ‹backlink› is not the current entity.
1398
+ // - what does it mean in an aspect
1399
+ // - how about auto-redirections/rewrite
1400
+ const { path } = ref;
1401
+ if (path.length === 1 && path[0]._navigation?.kind === '$self') {
1402
+ const query = userQuery( user );
1403
+ const main = query?._main;
1404
+ if (query && query !== main._leadingQuery) {
1405
+ const { txt, op } = getQueryOperatorName( query );
1406
+ const { location, id } = path[0];
1407
+ error( 'ref-unexpected-self', [ location, user ], { '#': txt, id, op } );
1408
+ }
1409
+ return;
1410
+ }
1411
+ const index = userTargetElementPathIndex( user, path );
1412
+ checkOnlyForeignKeyNavigation( user, path, index );
1413
+ const target = index > 0 && index < path.length && ref._artifact?.target;
1414
+ if (!target) {
1415
+ const last = path[path.length - 1];
1416
+ error( 'ref-expecting-target-assoc', [ last.location, user ], { id: '$self' },
1417
+ 'Only an association of the target side can be compared to $(ID)' );
1418
+ }
1419
+ // in entity: target must match
1420
+ // in aspect: must end with assoc, TODO: target must include aspect (+ add/ check)
1421
+ else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
1422
+ const last = path[path.length - 1];
1423
+ warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
1424
+ // eslint-disable-next-line max-len
1425
+ 'The target $(ART) of the association is not the current entity represented by $(ID)' );
1426
+ }
1427
+ }
1428
+
1429
+
1430
+ // right of union: parent = main → get correct operator
1431
+ // FROM subquery: parent = tab alias of outer query
1432
+ // other sub query: parent = outer query
1433
+ function getQueryOperatorName( query ) {
1434
+ if (query._parent !== query._main)
1435
+ return { txt: 'subQuery', op: '' };
1436
+
1437
+ for (let set = query._main.query; set.op; set = set.args[0]) {
1438
+ const right = set.args[1];
1439
+ if (query.name.select >= (right._leadingQuery || right).name.select)
1440
+ return { txt: 'setQuery', op: set.op.val };
1441
+ }
1442
+ throw new CompilerAssertion( 'Did we pass the leading query as argument?' );
1443
+ }
1444
+
1445
+ function userTargetElementPathIndex( user, path ) {
1446
+ const head = path[0];
1447
+ if (head._artifact === user) // standard case
1448
+ return 1;
1449
+ if (head._navigation?.kind !== '$self')
1450
+ return 0;
1451
+ for (let index = 1; index < path.length; ++index) {
1452
+ const assoc = path[index]?._artifact;
1453
+ if (assoc?.target)
1454
+ return (assoc === user) ? index + 1 : 0;
1455
+ }
1456
+ return 0;
1457
+ }
1458
+
1459
+ function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
1460
+ // has to be run after foreign-key rewrite
1461
+ const outer = user._pathHead?._origin;
1462
+ let assoc = outer?.foreignKeys &&
1463
+ pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
1464
+ outer;
1465
+ for (let index = startIndex; index < path.length; ++index) {
1466
+ if (assoc?.target) {
1467
+ if (!assoc.foreignKeys) {
1468
+ error( 'ref-unexpected-assoc', [ path[index - 1].location, user ],
1469
+ { '#': `${ msgPrefix }unmanaged`, alias: '$self' } );
1470
+ return;
1471
+ }
1472
+ if (!assoc.$keysNavigation)
1473
+ Functions.addForeignKeyNavigations( assoc, true );
1474
+ index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
1475
+ }
1476
+ assoc = path[index]?._artifact;
1477
+ }
1478
+ }
1479
+
1480
+ function checkCoveredByForeignKey( nav, path, index, user, msgPrefix = '' ) {
1481
+ const assoc = nav;
1482
+ while (index < path.length) {
1483
+ const item = path[index];
1484
+ nav = nav.$keysNavigation?.[item.id];
1485
+ if (!nav)
1486
+ break;
1487
+ if (nav._artifact)
1488
+ return index;
1489
+ ++index;
1490
+ }
1491
+ const last = path[index] || path[index - 1];
1492
+ // TODO: or just location of `last`?
1493
+ // TODO: extra text variant if the foreign keys are the key elements !
1494
+ // eslint-disable-next-line no-nested-ternary
1495
+ const txt = index >= path.length
1496
+ ? 'complete'
1497
+ : (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
1498
+ // eslint-disable-next-line max-len
1499
+ error( 'ref-invalid-navigation', [ last.location, user ], {
1500
+ '#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
1501
+ }, {
1502
+ std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
1503
+ keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1504
+ complete: 'The reference must cover a full foreign key reference of association $(ART)',
1505
+ // eslint-disable-next-line max-len
1506
+ 'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
1507
+ // eslint-disable-next-line max-len
1508
+ 'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1509
+ // eslint-disable-next-line max-len
1510
+ 'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
1511
+ } );
1512
+ // TODO later: mention allowed ones
1513
+ return path.length;
1514
+ }
1515
+
1516
+ function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
1517
+ if (ref._artifact?.on && !ref.$expected) {
1518
+ const { path } = ref;
1519
+ const last = path[path.length - 1];
1520
+ error( 'ref-unexpected-assoc', [ last.location, user ],
1521
+ { '#': messageVariant, alias: '$self' } );
1522
+ }
1145
1523
  }
1146
1524
 
1147
1525
  // Low-level functions --------------------------------------------------------
@@ -1155,7 +1533,7 @@ function fns( model ) {
1155
1533
  * @param {object} [textParams]
1156
1534
  */
1157
1535
  function signalNotFound( msgId, location, valid, textParams ) {
1158
- if (location.$notFound)
1536
+ if (location.$notFound) // TODO: still necessary?
1159
1537
  return;
1160
1538
  location.$notFound = true;
1161
1539
  /** @type {object} */
@@ -1177,13 +1555,14 @@ function fns( model ) {
1177
1555
  * @param {any} location
1178
1556
  */
1179
1557
  function signalMissingElementAccess( art, location ) {
1558
+ // TODO: ref-undefined-var ?
1180
1559
  const err = message( 'ref-expected-element', location,
1181
1560
  { '#': 'magicVar', id: art.name.id } );
1182
1561
  // Mapping for better valid names: from -> $at.from
1183
- const valid = Object.keys(art.elements || {}).reduce((prev, curr) => {
1562
+ const valid = Object.keys( art.elements || {} ).reduce( (prev, curr) => {
1184
1563
  prev[`${ art.name.id }.${ curr }`] = true;
1185
1564
  return prev;
1186
- }, Object.create(null));
1565
+ }, Object.create( null ) );
1187
1566
  attachAndEmitValidNames( err, valid );
1188
1567
  }
1189
1568
 
@@ -1205,15 +1584,15 @@ function fns( model ) {
1205
1584
  const art = valid[name];
1206
1585
  // ignore internal types such as cds.Association, ignore names with dot for
1207
1586
  // CDL references to main artifacts:
1208
- if (!art.internal && !art.deprecated && art.$inferred !== '$internal' &&
1209
- (viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
1587
+ if (!art.internal && !art.deprecated && art.name?.$inferred !== '$internal' &&
1588
+ (viaCdl ? art._main || !name.includes( '.' ) : art.kind !== 'namespace'))
1210
1589
  msg.validNames[name] = art;
1211
1590
  }
1212
1591
 
1213
1592
  if (options.testMode && !options.$recompile) {
1214
1593
  // no semantic location => either first of [loc, semantic loc] pair or just location.
1215
1594
  const loc = msg.$location[0] || msg.$location;
1216
- const names = Object.keys(msg.validNames);
1595
+ const names = Object.keys( msg.validNames );
1217
1596
  names.sort();
1218
1597
  if (names.length > 22) {
1219
1598
  names.length = 20;
@@ -1221,7 +1600,7 @@ function fns( model ) {
1221
1600
  }
1222
1601
  info( null, [ loc, null ],
1223
1602
  { '#': !names.length ? 'zero' : 'std' },
1224
- { std: `Valid: ${ names.join(', ') }`, zero: 'No valid names' });
1603
+ { std: `Valid: ${ names.join( ', ' ) }`, zero: 'No valid names' } );
1225
1604
  }
1226
1605
  }
1227
1606
  }