@sap/cds-compiler 3.9.4 → 4.0.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 (95) hide show
  1. package/CHANGELOG.md +107 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +55 -9
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +41 -5
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +25 -18
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/model/sortViews.js +4 -2
  63. package/lib/modelCompare/compare.js +1 -1
  64. package/lib/modelCompare/utils/filter.js +40 -2
  65. package/lib/optionProcessor.js +0 -3
  66. package/lib/render/toCdl.js +247 -214
  67. package/lib/render/toHdbcds.js +197 -181
  68. package/lib/render/toSql.js +325 -289
  69. package/lib/render/utils/common.js +42 -4
  70. package/lib/render/utils/delta.js +1 -1
  71. package/lib/render/utils/sql.js +3 -3
  72. package/lib/transform/braceExpression.js +2 -2
  73. package/lib/transform/db/.eslintrc.json +1 -1
  74. package/lib/transform/db/applyTransformations.js +3 -3
  75. package/lib/transform/db/associations.js +24 -12
  76. package/lib/transform/db/expansion.js +17 -18
  77. package/lib/transform/db/flattening.js +17 -21
  78. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  79. package/lib/transform/db/views.js +3 -4
  80. package/lib/transform/draft/db.js +21 -12
  81. package/lib/transform/draft/odata.js +4 -0
  82. package/lib/transform/forOdataNew.js +62 -47
  83. package/lib/transform/forRelationalDB.js +12 -7
  84. package/lib/transform/localized.js +4 -2
  85. package/lib/transform/odata/toFinalBaseType.js +5 -5
  86. package/lib/transform/odata/typesExposure.js +3 -3
  87. package/lib/transform/parseExpr.js +3 -0
  88. package/lib/transform/transformUtilsNew.js +43 -23
  89. package/lib/transform/translateAssocsToJoins.js +7 -6
  90. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  91. package/lib/transform/universalCsn/coreComputed.js +7 -5
  92. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  93. package/package.json +2 -2
  94. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  95. package/share/messages/message-explanations.json +1 -1
@@ -3,27 +3,25 @@
3
3
 
4
4
  'use strict';
5
5
 
6
+ const { CompilerAssertion } = require('../base/error');
6
7
  const { searchName } = require('../base/messages');
7
- const { isDeprecatedEnabled } = require('../base/model');
8
8
 
9
9
  const {
10
10
  setLink,
11
11
  setArtifactLink,
12
12
  dependsOn,
13
13
  pathName,
14
+ userQuery,
15
+ definedViaCdl,
14
16
  } = require('./utils');
15
17
 
16
- function artifactsEnv( art ) {
17
- return art._subArtifacts || Object.create(null);
18
- }
19
-
20
18
  /**
21
19
  * Main export function of this file. Attach "resolve" functions shared for phase
22
20
  * "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
23
21
  *
24
- * Before calling these functions, make sure that the following function
25
- * in model.$volatileFunctions is set:
26
- * - `environment`: a function which returns the search environment defined by
22
+ * Before calling `resolvePath`, make sure that the following function
23
+ * in model.$function is set:
24
+ * - `navigationEnv`: a function which returns the search environment defined by
27
25
  * its argument, e.g. a function which returns the dictionary of subartifacts.
28
26
  *
29
27
  * @param {XSN.Model} model
@@ -34,153 +32,314 @@ function fns( model ) {
34
32
  const {
35
33
  info, warning, error, message,
36
34
  } = model.$messageFunctions;
35
+ const Functions = model.$functions;
36
+
37
+ const referenceSemantics = {
38
+ // global: ------------------------------------------------------------------
39
+ using: { // only used to produce error message
40
+ isMainRef: 'all',
41
+ lexical: null,
42
+ dynamic: modelDefinitions,
43
+ notFound: undefinedDefinition,
44
+ },
45
+ // only used for the main annotate/extend statements, not inner ones:
46
+ annotate: {
47
+ isMainRef: 'all',
48
+ lexical: userBlock,
49
+ dynamic: modelDefinitions,
50
+ notFound: undefinedForAnnotate,
51
+ },
52
+ extend: {
53
+ isMainRef: 'no-generated',
54
+ lexical: userBlock,
55
+ dynamic: modelDefinitions,
56
+ notFound: undefinedDefinition,
57
+ },
58
+ _extensions: {
59
+ isMainRef: 'all',
60
+ lexical: userBlock,
61
+ dynamic: modelDefinitions,
62
+ notFound: false, // without message
63
+ },
64
+ include: {
65
+ isMainRef: 'no-generated',
66
+ lexical: userBlock,
67
+ dynamic: modelBuiltinsOrDefinitions,
68
+ notFound: undefinedDefinition,
69
+ },
70
+ viewInclude: 'include', // TODO: do differently
71
+ target: {
72
+ isMainRef: 'no-autoexposed',
73
+ lexical: userBlock,
74
+ dynamic: modelBuiltinsOrDefinitions,
75
+ notFound: undefinedDefinition,
76
+ // special `scope`s for redirections:
77
+ global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
78
+ },
79
+ targetAspect: { // TODO: do differently
80
+ isMainRef: 'no-autoexposed',
81
+ lexical: userBlock,
82
+ dynamic: modelBuiltinsOrDefinitions,
83
+ notFound: undefinedDefinition,
84
+ },
85
+ from: {
86
+ isMainRef: 'no-autoexposed',
87
+ lexical: userBlock,
88
+ dynamic: modelBuiltinsOrDefinitions,
89
+ notFound: undefinedDefinition,
90
+ navigation: environment,
91
+ },
92
+ type: {
93
+ isMainRef: 'no-autoexposed',
94
+ lexical: userBlock,
95
+ dynamic: modelBuiltinsOrDefinitions,
96
+ notFound: undefinedDefinition,
97
+ navigation: targetAspectOnly,
98
+ // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
99
+ typeOf: typeOfSemantics,
100
+ global: () => ({ isMainRef: 'no-autoexposed', dynamic: modelDefinitions }),
101
+ },
102
+ actionParamType: 'type', // TODO: do differently
103
+ eventType: 'type', // TODO: do differently
104
+ // element references without lexical scope (except $self/$projection): -----
105
+ targetElement: {
106
+ lexical: null,
107
+ dollar: false,
108
+ dynamic: targetElements,
109
+ notFound: undefinedTargetElement,
110
+ param: paramSemantics,
111
+ },
112
+ filter: {
113
+ lexical: justDollarSelf,
114
+ dollar: true,
115
+ dynamic: targetElements,
116
+ notFound: undefinedTargetElement,
117
+ param: paramSemantics,
118
+ },
119
+ 'calc-filter': {
120
+ lexical: justDollarSelf,
121
+ dollar: true,
122
+ dynamic: targetElements,
123
+ notFound: undefinedTargetElement,
124
+ param: paramUnsupported,
125
+ },
126
+ default: {
127
+ lexical: null,
128
+ dollar: true,
129
+ dynamic: () => Object.create( null ),
130
+ notFound: undefinedVariable,
131
+ param: paramUnsupported,
132
+ },
133
+ // general element references -----------------------------------------------
134
+ expr: { // TODO: this is too general -> column
135
+ lexical: tableAliasesIfNotExtendAndSelf,
136
+ dollar: true,
137
+ dynamic: combinedSourcesOrParentElements,
138
+ notFound: undefinedSourceElement,
139
+ param: paramSemantics,
140
+ nestedColumn: () => ({ // in expand and inline
141
+ lexical: justDollarSelf,
142
+ dollar: true,
143
+ dynamic: nestedElements,
144
+ notFound: undefinedNestedElement,
145
+ param: paramSemantics,
146
+ }),
147
+ },
148
+ 'param-only': {
149
+ lexical: null,
150
+ dollar: true,
151
+ dynamic: () => Object.create( null ),
152
+ notFound: undefinedVariable,
153
+ param: paramSemantics,
154
+ },
155
+ calc: {
156
+ lexical: justDollarSelf,
157
+ dollar: true,
158
+ dynamic: parentElements,
159
+ notFound: undefinedParentElement,
160
+ param: paramUnsupported,
161
+ },
162
+ joinOn: {
163
+ lexical: tableAliasesAndSelf,
164
+ dollar: true,
165
+ dynamic: combinedSourcesOrParentElements, // TODO: source alone...
166
+ notFound: undefinedSourceElement,
167
+ param: paramSemantics,
168
+ },
169
+ on: { // unmanaged assoc: outside query, redirected or new assoc in column
170
+ lexical: justDollarSelf,
171
+ allowBareSelf: true,
172
+ dollar: true,
173
+ dynamic: parentElements,
174
+ notFound: undefinedParentElement,
175
+ param: paramUnsupported,
176
+ nestedColumn: () => ({ // in expand and inline
177
+ lexical: justDollarSelf,
178
+ dollar: true,
179
+ dynamic: parentElements,
180
+ notFound: undefinedParentElement,
181
+ }),
182
+ },
183
+ 'mixin-on': {
184
+ lexical: tableAliasesAndSelf,
185
+ allowBareSelf: true,
186
+ dollar: true,
187
+ dynamic: combinedSourcesOrParentElements,
188
+ notFound: undefinedSourceElement,
189
+ param: paramSemantics, // TODO: check that assocs containing param in ON is not published
190
+ },
191
+ 'order-by-ref': {
192
+ lexical: tableAliasesAndSelf,
193
+ dollar: true,
194
+ dynamic: parentElements,
195
+ notFound: undefinedOrderByElement,
196
+ param: paramSemantics,
197
+ },
198
+ 'order-by-expr': {
199
+ lexical: tableAliasesAndSelf,
200
+ dollar: true,
201
+ dynamic: combinedSourcesOrParentElements,
202
+ notFound: undefinedSourceElement,
203
+ param: paramSemantics,
204
+ },
205
+ 'order-by-set-ref': {
206
+ lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
207
+ dollar: true,
208
+ dynamic: queryElements,
209
+ notFound: undefinedParentElement,
210
+ param: paramSemantics,
211
+ },
212
+ 'order-by-set-expr': {
213
+ lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
214
+ dollar: true,
215
+ dynamic: () => Object.create( null ),
216
+ notFound: undefinedVariable,
217
+ param: paramSemantics,
218
+ },
219
+ };
220
+
37
221
  // TODO: combine envFn and assoc ?
38
222
  const specExpected = {
39
- global: { // for using declaration
40
- envFn: artifactsEnv,
41
- artItemsCount: Number.MAX_SAFE_INTEGER,
42
- useDefinitions: true,
43
- global: 'definitions',
44
- },
45
- // TODO: re-check --------------------------------------------------------
46
- annotation: { useDefinitions: true, noMessage: true, global: 'vocabularies' },
223
+ using: {}, // for using declaration
47
224
  // TODO: artifact references ---------------------------------------------
48
- extend: {
49
- useDefinitions: true,
50
- envFn: artifactsEnv,
51
- artItemsCount: Number.MAX_SAFE_INTEGER,
52
- },
225
+ extend: {},
53
226
  // ref in top-level EXTEND
54
- annotate: {
55
- useDefinitions: true,
56
- envFn: artifactsEnv,
57
- artItemsCount: Number.MAX_SAFE_INTEGER,
58
- undefinedDef: 'anno-undefined-def',
59
- undefinedArt: 'anno-undefined-art',
60
- allowAutoexposed: true, // TODO: think about Info/Warning
61
- noMessageForLocalized: true, // TODO: should we issue a Debug message for code completion?
62
- },
227
+ annotate: {},
228
+ _extensions: {},
63
229
  type: { // TODO: more detailed later (e.g. for enum base type?)
64
- envFn: artifactsEnv,
65
230
  check: checkTypeRef,
66
- expectedMsgId: 'expected-type',
231
+ expectedMsgId: 'ref-expecting-type',
67
232
  sloppyMsgId: 'ref-sloppy-type',
68
- deprecateSmart: true,
69
233
  },
70
234
  actionParamType: {
71
- envFn: artifactsEnv,
72
235
  check: checkActionParamTypeRef,
73
- expectedMsgId: 'expected-actionparam-type',
236
+ expectedMsgId: 'ref-expecting-action-param-type',
74
237
  sloppyMsgId: 'ref-sloppy-actionparam-type',
75
- deprecateSmart: true,
76
238
  },
77
239
  eventType: {
78
- envFn: artifactsEnv,
79
240
  check: checkEventTypeRef,
80
- expectedMsgId: 'expected-event-type',
241
+ expectedMsgId: 'ref-expecting-event-type',
81
242
  sloppyMsgId: 'ref-sloppy-event-type',
82
- deprecateSmart: true,
83
243
  },
84
244
  include: {
85
245
  check: checkIncludesRef,
86
- expectedMsgId: 'expected-struct',
87
- envFn: artifactsEnv,
246
+ expectedMsgId: 'ref-expecting-struct',
88
247
  },
89
248
  viewInclude: {
90
249
  check: checkViewIncludesRef,
91
250
  expectedMsgId: 'ref-expecting-bare-aspect',
92
- envFn: artifactsEnv,
93
251
  },
94
252
  target: {
95
253
  check: checkEntityRef,
96
- expectedMsgId: 'expected-entity',
254
+ expectedMsgId: 'ref-expecting-entity',
97
255
  noDep: true,
98
- envFn: artifactsEnv,
99
256
  },
100
- compositionTarget: {
257
+ targetAspect: {
101
258
  check: checkTargetRef,
102
- expectedMsgId: 'expected-target',
259
+ expectedMsgId: 'ref-expecting-target',
103
260
  sloppyMsgId: 'ref-sloppy-target',
104
261
  noDep: 'only-entity',
105
- envFn: artifactsEnv,
106
262
  },
107
263
  from: {
108
- envFn: artifactsEnv,
109
264
  check: checkSourceRef,
110
- expectedMsgId: 'expected-source',
265
+ expectedMsgId: 'ref-expecting-source',
111
266
  assoc: 'from',
112
267
  argsSpec: 'expr',
113
- deprecateSmart: true,
114
268
  },
115
269
  // element references ----------------------------------------------------
116
- // if we want to disallow assoc nav for TYPE, do not do it here
117
- typeOf: { next: '_$next', dollar: true }, // TODO: disallow in var
118
270
  // TODO: dep for (explicit+implicit!) foreign keys
119
- targetElement: { next: '__none_', assoc: false, dollar: false },
271
+ // TODO: also check that we do not follow associations in foreign key? no args, no filter
272
+ targetElement: { assoc: false },
120
273
  filter: {
121
- next: '_$next', lexical: 'main', dollar: 'none', escape: 'param',
274
+ escape: 'param',
275
+ },
276
+ 'calc-filter': {
277
+ escape: 'param',
122
278
  },
123
279
  default: {
124
- next: '_$next',
125
- dollar: true,
126
280
  check: checkConstRef,
127
- expectedMsgId: 'expected-const',
281
+ expectedMsgId: 'ref-expecting-const',
128
282
  },
129
- expr: { // in: from-on,
130
- next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
283
+ expr: {
284
+ escape: 'param', assoc: 'nav',
285
+ },
286
+ 'param-only': {
287
+ escape: 'param',
131
288
  },
132
- exists: { // same as expr
133
- next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
289
+ calc: {
290
+ assoc: 'nav',
134
291
  },
135
- 'approved-exists': { // same as expr
136
- next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
292
+ joinOn: { // ON condition for JOIN: should be different to 'expr'!
293
+ escape: 'param', assoc: 'nav',
137
294
  },
138
295
  on: { // TODO: there will also be a 'from-on' (see 'expr')
139
- noAliasOrMixin: true, // TODO: some headReject or similar
140
- next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
141
- dollar: true,
142
- rootEnv: 'elements', // the final environment for the path root
143
296
  noDep: true, // do not set dependency for circular-check
144
297
  allowSelf: true,
145
298
  }, // TODO: special assoc for only on user
146
299
  'mixin-on': {
147
300
  escape: 'param', // TODO: extra check that assocs containing param in ON is not published
148
- next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
149
- dollar: true,
150
301
  noDep: true, // do not set dependency for circular-check
151
302
  allowSelf: true,
152
303
  }, // TODO: special assoc for only on user
153
- rewrite: {
304
+ // ---------marker for getPathRoot replaced-----------
305
+ 'order-by-ref': {
154
306
  next: '_$next',
155
307
  dollar: true,
156
308
  escape: 'param',
157
- noDep: true,
158
- allowSelf: true,
159
- rewrite: true,
160
- }, // TODO: assertion that there is no next/escape used
161
- 'order-by': {
309
+ assoc: 'nav',
310
+ dynamic: 'query',
311
+ deprecatedSourceRefs: true,
312
+ },
313
+ 'order-by-expr': {
162
314
  next: '_$next',
163
315
  dollar: true,
164
316
  escape: 'param',
165
317
  assoc: 'nav',
166
- deprecatedSourceRefs: true,
167
318
  },
168
- 'order-by-union': {
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': {
169
328
  next: '_$next',
170
329
  dollar: true,
171
330
  escape: 'param',
172
331
  noDep: true,
173
- noExt: true,
332
+ dynamic: false,
333
+ lexical: 'next',
174
334
  },
175
335
  // expr TODO: better - on condition for assoc, other on
176
336
  // expr TODO: write dependency, but care for $self
177
337
  param: {
178
338
  check: checkConstRef,
179
- expectedMsgId: 'expected-const',
339
+ expectedMsgId: 'ref-expecting-const',
180
340
  },
181
341
  };
182
342
 
183
- const VolatileFns = model.$volatileFunctions;
184
343
  Object.assign( model.$functions, {
185
344
  resolveUncheckedPath,
186
345
  resolveTypeArgumentsUnchecked,
@@ -267,174 +426,63 @@ function fns( model ) {
267
426
  // the return value. Otherwise, complain if `unchecked` is false, and set
268
427
  // `ref.absolute` to the path name of `ref`.
269
428
  // Used for collecting artifact extension, and annotation assignments.
270
- function resolveUncheckedPath( ref, expected, user ) {
271
- if (!ref.path || ref.path.broken) // incomplete type AST
429
+ //
430
+ // Return '' if the ref is good, but points to an element.
431
+ function resolveUncheckedPath( ref, refCtx, user ) {
432
+ const { path } = ref;
433
+ if (!path || path.broken) // incomplete type AST
272
434
  return undefined;
273
- if (ref._artifact)
274
- return ref._artifact.name.absolute;
275
- const spec = specExpected[expected];
276
- let art = (ref.scope === 'global' || spec.global)
277
- ? getPathRoot( ref.path, spec, user, {}, model[spec.global || 'definitions'] )
278
- : getPathRoot( ref.path, spec, user, user._block, null, true );
279
- if (art === false) // redefinitions
280
- art = ref.path[0]._artifact[0]; // array stored in head's _artifact
281
- else if (!art)
282
- return (spec.useDefinitions) ? pathName( ref.path ) : null;
283
- // art can be using proxy...
284
- if (ref.path.length > 1)
285
- return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
286
- return art.name.absolute;
287
- }
288
-
289
- function userQuery( user ) {
290
- // TODO: we need _query links set by the definer
291
- while (user._main) {
292
- if (user.kind === 'select' || user.kind === '$join')
293
- return user;
294
- user = user._parent;
295
- }
296
- return null;
435
+
436
+ const semantics = referenceSemantics[refCtx];
437
+ let art = getPathRoot( ref, semantics, user );
438
+ if (ref.scope && ref.scope !== 'global')
439
+ return ''; // TYPE OF, Main:elem
440
+
441
+ if (Array.isArray( art ))
442
+ art = art[0];
443
+ if (!art)
444
+ return (semantics.notFound) ? art : pathName( path );
445
+ if (path.length === 1)
446
+ return art.name.absolute; // TODO: name.id
447
+ return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
297
448
  }
298
449
 
299
450
  // Return artifact or element referred by the path in `ref`. The first
300
451
  // environment we search in is `env`. If no such artifact or element exist,
301
452
  // complain with message and return `undefined`. Record a dependency from
302
453
  // `user` to the found artifact if `user` is provided.
303
- function resolvePath( ref, expected, user, extDict, msgArt ) {
454
+ function resolvePath( ref, expected, user ) {
455
+ const origUser = user;
456
+ user = user._user || user;
304
457
  if (ref == null) // no references -> nothing to do
305
458
  return undefined;
306
459
  if (ref._artifact !== undefined)
307
460
  return ref._artifact;
308
- if (!ref.path || ref.path.broken || !ref.path.length) {
461
+
462
+ const { path } = ref;
463
+ if (!path || path.broken || !path.length) {
309
464
  // incomplete type AST or empty env (already reported)
310
465
  return setArtifactLink( ref, undefined );
311
466
  }
312
467
 
313
- let spec = specExpected[expected];
314
- const { path } = ref;
315
- const head = path[0];
316
- // message(null,head.location,{art:user,expected, id: head.id},
317
- // 'Info','User $(ART), $(EXPECTED) $(ID)')
318
- let env = user._block; // artifact references: block
468
+ const s = referenceSemantics[expected]; // TODO: temp indirection
469
+ const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
319
470
 
320
- if (ref.scope === 'param') {
321
- if (!spec.escape) {
322
- error( 'ref-unexpected-scope', [ ref.location, user ], { '#': 'std' } );
323
- return setArtifactLink( ref, null );
324
- }
325
- if (user.$syntax === 'calc')
326
- error('ref-unexpected-scope', [ ref.location, user ], { '#': 'calc' } );
471
+ const r = getPathRoot( ref, semantics, origUser );
472
+ const root = r && acceptPathRoot( r, ref, semantics, user );
473
+ if (!root)
474
+ return setArtifactLink( ref, root );
327
475
 
476
+ let spec = specExpected[expected];
477
+ if (ref.scope === 'param') {
478
+ if (!spec.escape)
479
+ throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
328
480
  spec = specExpected[spec.escape];
329
- // In queries and query entities, the first lexical search environment
330
- // are the parameters, otherwise the block. It is currently ensured that
331
- // _block in queries is the same as _block of the query entity:
332
- const lexical = (user._main || user).$tableAliases; // queries (but also query entities)
333
- env = lexical && lexical.$parameters || user._block;
334
- extDict = null; // let getPathRoot() choose it
335
- }
336
- else if (spec.next === '__none_') {
337
- env = {};
338
- }
339
- else if (spec.next) { // TODO: combine spec.next / spec.lexical to spec.lexical
340
- // TODO: SIMPLIFY this function
341
- const query = (spec.lexical === 'main') ? user._main : userQuery( user );
342
- // in path filter, just $magic (and $parameters)
343
- env = (spec.lexical === 'from') ? query._parent : query || user._main || user;
344
- // queries: first table aliases, then $magic - value refs: first $self, then $magic
345
- if (!extDict && !spec.noExt) {
346
- // TODO: change to name restriction for $joins, not own environments
347
- extDict = query && spec.rootEnv !== 'elements' &&
348
- // first step: only use _combined of real query - TODO:
349
- // reject if not visible, but not allow more (!)
350
- (query._combined || query._parent._combined) ||
351
- VolatileFns.environment( user._main ? user._parent : user );
352
- }
353
- }
354
-
355
- // 'global' for CSN later in value paths, CDL for Association/Composition:
356
- let art = (ref.scope === 'global' || spec.global)
357
- ? getPathRoot( path, spec, user, {}, model[spec.global || 'definitions'] )
358
- : getPathRoot( path, spec, user, env, extDict, msgArt || 0 );
359
- if (!art) {
360
- return setArtifactLink( ref, art );
361
- }
362
- else if (!spec.envFn && user._pathHead) {
363
- if (art.kind === '$self') {
364
- const headEnv = VolatileFns.environment( user._pathHead ) &&
365
- user._pathHead._origin &&
366
- VolatileFns.environment( user._pathHead._origin );
367
- rejectBareSelf( spec, path, user, headEnv );
368
- }
369
- }
370
- else if (art.kind === 'using') {
371
- const def = model.definitions[art.name.absolute];
372
- if (!def) {
373
- // It could be that the artifact was removed and that the using-proxy needs to be reported.
374
- // The check for $inferred is required to avoid consequential errors for cases such as:
375
- // using unknown.abc;
376
- // entity P as projection on abc; // <-- no consequential error here
377
- if (art.$inferred === 'path-prefix') {
378
- // head._artifact referred to the `using`. Remove the reference,
379
- // so that getPathItem() below emits an error.
380
- setArtifactLink( head, false );
381
- setArtifactLink( ref, false );
382
- }
383
- else {
384
- return setArtifactLink( ref, false );
385
- }
386
- }
387
- else if (def.$duplicates) { // redefined art referenced by using proxy
388
- return setArtifactLink( ref, false );
389
- }
390
- else {
391
- setArtifactLink( head, def ); // we do not want to see the using
392
- }
393
- }
394
- else if (art.kind === 'mixin') {
395
- if (spec.noAliasOrMixin) {
396
- // TODO: good enough for now - change later to not search for table aliases at all
397
- signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
398
- { '#': 'mixin', id: head.id } );
399
- // also set link on head?
400
- return setArtifactLink( ref, false );
401
- }
402
- // console.log(message( null, art.location, art, {}, 'Info','MIX').toString())
403
- setLink( head, '_navigation', art );
404
- }
405
- else if (art.kind === '$navElement') {
406
- setLink( head, '_navigation', art );
407
- setArtifactLink( head, art._origin );
408
- // TODO: set art?
409
- }
410
- else if (art.kind === '$tableAlias' || art.kind === '$self') {
411
- if (art.kind === '$self') {
412
- rejectBareSelf( spec, path, user, extDict );
413
- }
414
- else if (spec.noAliasOrMixin) {
415
- // TODO: good enough for now - change later to not search for table aliases at all
416
- signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
417
- { '#': 'alias', id: head.id } );
418
- // also set link on head?
419
- return setArtifactLink( ref, false );
420
- }
421
- setLink( head, '_navigation', art );
422
- setArtifactLink( head, art._origin ); // query source or leading query in FROM
423
- // require('../model/revealInternalProperties').log(model, 'foo.bar.S.V1a')
424
- if (!art._origin)
425
- return setArtifactLink( ref, art._origin );
426
- // if just table alias (with expand), mark `user` with `$noOrigin` to indicate
427
- // that the corresponding entity should not be put as $origin into the CSN
428
- if (path.length === 1 && user && art.kind === '$tableAlias')
429
- user.$noOrigin = true;
430
481
  }
431
482
 
432
483
  // how many path items are for artifacts (rest: elements)
433
- const artItemsCount = (typeof ref.scope === 'number')
434
- ? ref.scope || Number.MAX_SAFE_INTEGER
435
- : spec.artItemsCount || 1;
436
484
  // console.log(expected, ref.path.map(a=>a.id),artItemsCount)
437
- art = getPathItem( path, spec, user, artItemsCount, !spec.envFn && user._pathHead && art);
485
+ let art = getPathItem( ref, semantics, spec, user );
438
486
  if (!art)
439
487
  return setArtifactLink( ref, art );
440
488
 
@@ -445,7 +493,7 @@ function fns( model ) {
445
493
  setArtifactLink( step, art );
446
494
  path.push( step );
447
495
  }
448
- if (spec.check) {
496
+ if (spec.check && !ref.$expected) { // do not check with exists path (is already error)
449
497
  const fail = spec.check( art, path );
450
498
  if (fail === true) {
451
499
  signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
@@ -467,7 +515,7 @@ function fns( model ) {
467
515
  // TODO: location of last path item if not main artifact
468
516
  if (spec.assoc === 'from' && art._main) {
469
517
  dependsOn( user, art._main, location );
470
- VolatileFns.environment( art, location, user );
518
+ environment( art, location, user );
471
519
  }
472
520
  else if (art.kind !== 'select') { // no real dependency to bare $self
473
521
  dependsOn( user, art, location );
@@ -476,45 +524,10 @@ function fns( model ) {
476
524
  // element elem: type of elem.x;
477
525
  }
478
526
  }
479
- // Warning for CDL TYPE OF references without ':' or shifted ':'
480
- if (spec.deprecateSmart && typeof ref.scope === 'number' &&
481
- !(env.$frontend && env.$frontend !== 'cdl'))
482
- deprecateSmart( ref, art, user );
483
527
  // TODO: follow FROM here, see csnRef - fromRef
484
528
  return setArtifactLink( ref, art );
485
529
  }
486
530
 
487
- function rejectBareSelf( spec, path, user, extDict ) {
488
- if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
489
- const head = path[0];
490
- // TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
491
- signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
492
- { id: head.id } );
493
- }
494
- }
495
-
496
- // Issue errors for "smart" element-in-artifact references
497
- // without a colon; and errors for misplaced colons in references.
498
- // This function likely disappears again in cds-compiler v2.x.
499
- function deprecateSmart( ref, art, user ) {
500
- const { path } = ref;
501
- const scope = path.findIndex( i => i._artifact._main );
502
- if (ref.scope) { // provided a ':' in the ref path
503
- if (scope === ref.scope) // correctly between main artifact and element
504
- return;
505
- const item = path[ref.scope];
506
- error( 'ref-unexpected-colon', [ item.location, user ], { id: item.id },
507
- 'Replace the colon before $(ID) by a dot' );
508
- ref.scope = 0; // correct (otherwise CSN refs are wrong)
509
- }
510
- if (scope >= 0) { // we have a element-in-artifact reference
511
- const item = path[scope];
512
- error( 'ref-missing-colon', [ item.location, user ], { id: item.id },
513
- 'Replace the dot before $(ID) by a colon' );
514
- ref.scope = scope; // no need to recalculate in to-csn.js
515
- }
516
- }
517
-
518
531
  /**
519
532
  * Resolve the type arguments of `artifact` according to the type `typeArtifact`.
520
533
  * User is used for semantic message location.
@@ -527,13 +540,15 @@ function fns( model ) {
527
540
  *
528
541
  * Left-over arguments are errors for non-builtins and warnings for builtins.
529
542
  *
543
+ * TODO: move to define.js (and probably rename), rewrite (consider syntax-unexpected-argument)
544
+ *
530
545
  * @param {object} artifact
531
546
  * @param {object} typeArtifact
532
547
  * @param {CSN.Artifact} user
533
548
  */
534
549
  function resolveTypeArgumentsUnchecked( artifact, typeArtifact, user ) {
535
550
  let args = artifact.$typeArgs || [];
536
- const parameters = typeArtifact.parameters || [];
551
+ const parameters = typeArtifact?.parameters || [];
537
552
 
538
553
  if (parameters.length > 0) {
539
554
  // For Builtins
@@ -546,7 +561,7 @@ function fns( model ) {
546
561
  }
547
562
  args = args.slice(parameters.length);
548
563
  }
549
- else if (args.length > 0 && !typeArtifact.builtin) {
564
+ else if (args.length > 0 && !typeArtifact?.builtin) {
550
565
  // One or two arguments are interpreted as either length or precision/scale.
551
566
  // For builtins, we know what arguments are expected, and we do not need this mapping.
552
567
  // Also, we expect non-structured types.
@@ -566,188 +581,77 @@ function fns( model ) {
566
581
 
567
582
  // Warn about left-over arguments.
568
583
  if (args.length > 0) {
569
- const loc = [ args[args.length - 1].location, user ];
570
- if (typeArtifact.builtin)
584
+ const loc = [ args[0].location, user ];
585
+ if (typeArtifact?.builtin) {
571
586
  message( 'type-ignoring-argument', loc, { art: typeArtifact } );
572
- else
573
- error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
587
+ }
588
+ else if (options.testMode) {
589
+ // Ensure: parser reports error and sets $typeArgs to undefined if more than 2 parameter
590
+ throw new CompilerAssertion( 'More than 2 type arguments set by parser' );
591
+ }
574
592
  }
575
593
  artifact.$typeArgs = undefined;
576
594
  }
577
595
 
578
- /**
579
- * Return artifact or element referred by name `head`. The first environment
580
- * we search in is `env`. If `unchecked` is equal to `true`, do not report an error
581
- * if the artifact does not exist. Return a "fresh" artifact for
582
- * non-existing external using references if `unchecked` is truthy.
583
- */
584
- function getPathRoot( path, spec, user, env, extDict, msgArt ) {
596
+ function getPathRoot( { path, scope, location }, semantics, user ) {
597
+ // TODO: use string value of isMainRef?
585
598
  const head = path[0];
586
- if (!head || !head.id || !env)
599
+ if (!head || !head.id)
587
600
  return undefined; // parse error
588
- if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
589
- if (spec.rootEnv === 'elements') { // ON condition in expand/inline
590
- let root = user._pathHead;
591
- while (root.kind === '$inline')
592
- root = root._parent;
593
- return root;
594
- }
595
- VolatileFns.environment( user._pathHead ); // make sure _origin is set
596
- return user._pathHead._origin;
597
- // const { _origin } = user._pathHead;
598
- // return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
599
- }
600
- // if (head.id === 'k') {console.log(Object.keys(user));
601
- // throw new CompilerAssertion(JSON.stringify(user.name))}
602
- // if head._artifact is set or is null then it was already computed once
603
601
  if (head._artifact !== undefined)
604
- return Array.isArray(head._artifact) ? false : head._artifact;
605
- // console.log(pathName(path), !spec.next && !extDict &&
606
- // (spec.useDefinitions || env.$frontend === 'json' || env))
607
- if (!spec.next && !extDict) {
608
- // CSN artifact paths are always fully qualified so we use
609
- // model.definitions for the JSON frontend.
610
- extDict = (spec.useDefinitions || env.$frontend && env.$frontend !== 'cdl')
611
- ? model.definitions
612
- : model.$builtins;
613
- }
614
- const nodollar = !spec.dollar && spec.next;
615
- const nextProp = spec.next || '_block';
616
- for (let art = env; art; art = art[nextProp]) {
617
- if (nodollar && !art._main) // $self stored in main.$tableAliases
618
- break; // TODO: probably remove _$next link
619
- const e = art.artifacts || art.$tableAliases || Object.create(null);
620
- const r = e[head.id];
621
- if (r && r.$inferred !== '$internal') {
622
- if (Array.isArray(r)) { // redefinitions
623
- setArtifactLink( head, r );
624
- return false;
625
- }
626
- // if (head.$delimited && r.kind !== '$tableAlias' && r.kind !== 'mixin')
627
- // TODO: warning for delimited special - or directly in parser
628
- if (r.kind === '$parameters') {
629
- if (!head.$delimited && path.length > 1) {
630
- message( 'ref-obsolete-parameters', [ head.location, user ],
631
- { code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
632
- 'Obsolete $(CODE) - replace by $(NEWCODE)' );
633
- // TODO: replace it in to-csn correspondingly !!!
634
- return setArtifactLink( head, r );
635
- }
636
- }
637
- else if (r.kind === '$self') {
638
- // TODO: handle $delimited differently
639
- // TODO: $projection only if not delimited _and_ length > 1
640
- return setArtifactLink( head, r );
641
- }
642
- else if (r.kind !== '$tableAlias' || path.length > 1 || user.expand || user.inline) {
643
- // except "real" table aliases (not $self) with path len 1
644
- // TODO: $projection only if not delimited _and_ length > 1
645
- return setArtifactLink( head, r );
646
- }
647
- }
648
- }
649
- if (extDict && (!spec.dollar || head.id[0] !== '$')) {
650
- const r = extDict[head.id];
651
- if (Array.isArray(r)) {
652
- if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
653
- // only complain about ambiguous source elements if we do not have
654
- // duplicate table aliases, only mention non-ambiguous source elems
655
- const uniqueNames = r.filter( e => !e.$duplicates);
656
- if (uniqueNames.length) {
657
- const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
658
- .map( e => `${ e.name.alias }.${ e.name.element }` );
659
- let variant = names.length === uniqueNames.length ? 'std' : 'few';
660
- if (names.length === 0)
661
- variant = 'none';
662
- error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
663
- }
664
- }
665
- setArtifactLink( head, r );
666
- return false;
667
- }
668
- else if (r) {
669
- return setArtifactLink( head, r );
670
- }
671
- else if (spec.deprecatedSourceRefs && env._combined &&
672
- isDeprecatedEnabled( options, 'autoCorrectOrderBySourceRefs' )) {
673
- // User has provided a source element without table alias where a query
674
- // element is expected. Possible on many DBs (and compiler v1), in CAP
675
- // only with table alias. Auto-correct it if no duplicate.
676
- // TODO: we could use that info also in messages when the deprecated flag is not set
677
- const s = env._combined[head.id];
678
- if (s && !Array.isArray(s)) {
679
- path.$prefix = s.name.alias; // pushing it to path directly could be problematic
680
- warning( null, [ head.location, user ],
681
- { id: head.id, newcode: `${ s.name.alias }.${ head.id }` },
682
- 'Replace source element reference $(ID) by $(NEWCODE); auto-corrected' );
683
- return setArtifactLink( head, s );
684
- }
685
- }
686
- }
687
- if (spec.noMessage || msgArt === true && extDict === model.definitions)
688
- return null;
602
+ return head._artifact;
603
+ const ruser = user._user || user; // TODO: nicer name if we keep this
689
604
 
690
- const valid = [];
691
- for (let art = env; art; art = art[nextProp]) {
692
- const e = art.artifacts || art.$tableAliases || Object.create(null);
693
- valid.push( e );
694
- }
695
- if (extDict) {
696
- const e = Object.create(null);
697
- // the names of the external dictionary are valid, too, except duplicate
698
- // navigation elements (for which you should use a table alias)
699
- if (extDict !== model.definitions) {
700
- for (const name in extDict) {
701
- if (!spec.dollar || name[0] !== '$')
702
- e[name] = extDict[name];
703
- }
704
- }
705
- else {
706
- for (const name in extDict) {
707
- if (!name.includes('.') && (!spec.dollar || name[0] !== '$'))
708
- e[name] = extDict[name];
709
- }
605
+ // Handle expand/inline, `type of`, :param, global (internally for CDL):
606
+ if (user._pathHead && !semantics.isMainRef) // in expand/inline
607
+ semantics = semantics.nestedColumn();
608
+ if (typeof scope === 'string') { // typeOf, param, global
609
+ semantics = semantics?.[scope] && semantics[scope]( ruser, path, location );
610
+ if (!semantics) {
611
+ if (semantics == null)
612
+ throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
613
+ return setArtifactLink( head, null );
710
614
  }
711
- valid.push( e );
712
615
  }
616
+ const valid = [];
713
617
 
714
- if (spec.next) { // value ref
715
- // TODO: if not in query, specify where we search for elements and delete env.$msg
716
- // TODO: also something special if it starts with '$'
717
- if (msgArt) {
718
- // TODO: we might mention both the "direct" and the "effective" type and
719
- // always just mentioned one identifier as not found
720
- signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
721
- { art: searchName( msgArt, head.id, 'element' ) } );
722
- }
723
- else if (head.id[0] === '$') {
724
- const tableAlias = extDict[head.id]?._parent;
725
- const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
726
- signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, {
727
- '#': alias ? 'alias' : 'std',
728
- alias,
729
- id: head.id,
730
- } );
731
- }
732
- else {
733
- const isVirtual = (user.name?.id === head.id && user.virtual?.val);
734
- const code = isVirtual ? 'virtual null as ‹name›' : '';
735
- signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
736
- { art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
737
- }
738
- }
739
- else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
740
- // IDE can inspect <model>.definitions - provide null for valid
741
- if (!spec.noMessageForLocalized || !head.id.startsWith( 'localized.' )) {
742
- signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ head.location, user ],
743
- valid, { art: head.id } );
618
+ // Search in lexical environments, including $self/$projection:
619
+ const { isMainRef } = semantics;
620
+ const lexical = semantics.lexical?.( ruser ); // TODO: _pathHead?
621
+ if (lexical) {
622
+ const [ nextProp, dictProp ] = (isMainRef)
623
+ ? [ '_block', 'artifacts' ]
624
+ : [ '_$next', '$tableAliases' ];
625
+ // let notApplicable = ...; // for table aliases in JOIN-ON and UNION order-by
626
+ for (let env = lexical; env; env = env[nextProp]) {
627
+ const dict = env[dictProp] || Object.create(null);
628
+ const r = dict[head.id];
629
+ if (acceptLexical( r, path, semantics, user ))
630
+ return setArtifactLink( head, r );
631
+ valid.push( dict );
744
632
  }
745
633
  }
746
- else if (!spec.noMessageForLocalized || head.id !== 'localized') {
747
- signalNotFound( spec.undefinedArt || 'ref-undefined-art', [ head.location, user ],
748
- valid, { name: head.id } );
749
- }
750
- return setArtifactLink( head, null );
634
+
635
+ // Search in $special (ex $self/$projection) and dynamic environment:
636
+ const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
637
+ if (!dynamicDict) // avoid consequential errors
638
+ return setArtifactLink( head, null );
639
+ const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
640
+ const dict = (isVar) ? model.$magicVariables.artifacts : dynamicDict;
641
+ const r = dict[head.id];
642
+ if (r)
643
+ return setArtifactLink( head, r );
644
+
645
+ if (!semantics.dollar)
646
+ valid.push( dynamicDict );
647
+ else
648
+ valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
649
+ if (semantics.notFound === false)
650
+ return setArtifactLink( head, null );
651
+ // TODO: streamline function arguments (probably: user, path, semantics )
652
+ const undef = semantics.notFound( ruser, head, valid, dynamicDict,
653
+ !isMainRef && user._user && user._artifact, path );
654
+ return setArtifactLink( head, undef || null );
751
655
  }
752
656
 
753
657
  // Return artifact or element referred by path (array of ids) `tail`. The
@@ -757,84 +661,105 @@ function fns( model ) {
757
661
  // TODO - think about setting _navigation for all $navElement – the
758
662
  // "ref: ['tabAlias']: inline: […]" handling might be easier
759
663
  // (no _pathHead consultation for key prop and renaming support)
760
- function getPathItem( path, spec, user, artItemsCount, headArt ) {
664
+ function getPathItem( ref, semantics, spec, user ) {
761
665
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
762
- let art = headArt;
666
+ const { path } = ref;
667
+ let artItemsCount = 0;
668
+ const { isMainRef } = semantics;
669
+ if (isMainRef) {
670
+ artItemsCount = (typeof ref.scope === 'number' && ref.scope) ||
671
+ (ref.scope ? 1 : path.length);
672
+ }
673
+ let art = null;
763
674
  let nav = spec.assoc !== '$keys' && null; // false for '$keys'
764
675
  const last = path[path.length - 1];
676
+ // TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
677
+ const elementsEnv = semantics.navigation || environment;
765
678
  for (const item of path) {
766
679
  --artItemsCount;
767
- if (!item || !item.id) // incomplete AST due to parse error
680
+ if (!item?.id) // incomplete AST due to parse error
768
681
  return undefined;
769
- if (item._artifact) { // should be there on first path element (except with expand)
682
+ if (item._artifact) { // should be there on first path element
770
683
  art = item._artifact;
771
- if (Array.isArray(art))
772
- return false;
773
- if (art.$requireElementAccess && path.length === 1)
774
- // Path with only one item, but we expect an element, e.g. `$at.from`.
775
- signalMissingElementAccess(art, [ item.location, user ]);
776
684
  continue;
777
685
  }
778
686
 
779
- const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
780
- const env = fn( art, item.location, user, spec.assoc );
781
- const sub = setArtifactLink( item, env?.[item.id] );
687
+ const prev = art;
688
+ const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
689
+ const env = envFn( art, item.location, user, spec.assoc );
690
+ art = setArtifactLink( item, env?.[item.id] );
782
691
 
783
- if (!sub) {
692
+ if (!art) {
784
693
  // element was not found in environment
785
694
 
786
695
  // TODO (done?): if `env` was 0, we might set a dependency to induce an
787
696
  // illegal-cycle error instead of reporting via `errorNotFound`.
788
- if (art.$uncheckedElements) { // magic variable / replacement variable
697
+ if (prev.$uncheckedElements) { // magic variable / replacement variable
789
698
  signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
790
699
  { id: pathName( path ) } );
791
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
+ }
792
707
  else {
793
- errorNotFound( item, env );
708
+ errorNotFound( item, env, prev, spec, user );
794
709
  }
795
710
  return null;
796
711
  }
797
- else if (Array.isArray(sub)) { // redefinitions
798
- return false;
799
- }
800
-
801
- if (nav) { // we have already "pseudo-followed" a managed association
802
- // We currently rely on the check that targetElement references do
803
- // not (pseudo-) follow associations, otherwise potential redirection
804
- // there had to be considered, too. Also, fk refs to sub elements in
805
- // combinations with redirections of the target which directly access
806
- // the potentially renamed sub elements would be really complex.
807
- // With our restriction, no renaming must be considered for item.id.
808
- setTargetReferenceKey( item.id, item );
809
- }
810
- // Now set an _navigation link for managed assocs in ON condition etc
811
- else if (art && art.target && nav != null) {
812
- // Find the original ref for sub and the original foreign key
813
- // definition. This way, we do not need the foreign keys with
814
- // rewritten target element path, which might not be available at
815
- // this point (rewriteKeys in Resolver Phase 5). If we want to
816
- // follow associations in foreign key definitions, rewriteKeys must
817
- // be moved to the on-demand Resolver Phase 2.
818
- let orig; // for the original target element
819
- for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
820
- orig = o;
821
- nav = (orig._effectiveType || orig).$keysNavigation;
822
- setTargetReferenceKey( orig.name.id, item );
823
- }
824
- art = sub;
825
- if (spec.envFn && !spec.allowAutoexposed && (!artItemsCount || item === last) &&
826
- art && art.$inferred === 'autoexposed' && !user.$inferred) {
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
+ // need to do that here, because we also need to disallow Service.AutoExposed:elem
716
+ if (isMainRef !== 'all' && artItemsCount === 0 &&
717
+ art.$inferred === 'autoexposed' && !user.$inferred) {
827
718
  // Depending on the processing sequence, the following could be a
828
719
  // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
829
720
  // could "change" to this message at the end of compile():
830
- message( 'ref-autoexposed', [ item.location, user ], { art },
831
- // eslint-disable-next-line max-len
832
- 'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
721
+ error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
722
+ // eslint-disable-next-line max-len
723
+ 'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
724
+ // return null;
833
725
  }
834
726
  }
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
+ }
835
732
  return art;
733
+ }
836
734
 
837
- function setTargetReferenceKey( id, item ) {
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 ) {
838
763
  const node = nav && nav[id];
839
764
  nav = null;
840
765
  if (node) {
@@ -854,40 +779,373 @@ function fns( model ) {
854
779
  // eslint-disable-next-line max-len
855
780
  'You can\'t follow associations other than to elements referred to in a managed association\'s key' );
856
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
+ // Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
811
+
812
+ function acceptLexical( art, path, semantics, user ) {
813
+ if (semantics.isMainRef || !art)
814
+ return !!art;
815
+ // Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
816
+ // Do not accept a lonely table alias and `$projection`
817
+ // 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';
826
+ }
857
827
 
858
- function errorNotFound( item, env ) {
859
- if (!spec.next && artItemsCount >= 0) { // artifact ref
860
- // TODO: better for FROM e.Assoc (even disallow for other refs)
861
- const a = searchName( art, item.id, (spec.envFn || art._subArtifacts) && 'absolute' );
862
- signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
863
- [ env ], { art: a } );
828
+ function acceptPathRoot( art, ref, semantics, user ) {
829
+ const { path } = ref;
830
+ const [ head ] = path;
831
+ if (Array.isArray( art ))
832
+ return getAmbiguousRefLink( art, head, user );
833
+ switch (art.kind) {
834
+ case 'using': {
835
+ const def = model.definitions[art.name.absolute];
836
+ if (!def)
837
+ return def;
838
+ if (def.$duplicates)
839
+ return false;
840
+ return setArtifactLink( head, def ); // we do not want to see the using
864
841
  }
865
- else if (art.name && art.name.select && art.name.select > 1) {
866
- // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
867
- // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
868
- // TODO: probably not extra messageId, but text variant
869
- // TODO: views elements are proxies to query-0 elements, not the same
870
- // TODO: better message text
871
- signalNotFound( 'query-undefined-element', [ item.location, user ],
872
- [ env ], { id: item.id } );
842
+ case 'mixin': {
843
+ return setLink( head, '_navigation', art );
873
844
  }
874
- else if (art.kind === '$parameters') {
875
- signalNotFound( 'ref-undefined-param', [ item.location, user ],
876
- [ env ], { art: art._main, id: item.id } );
845
+ case '$navElement': {
846
+ if (head.id === user.$extended)
847
+ path.$prefix = user.$extended;
848
+ setLink( head, '_navigation', art );
849
+ return setArtifactLink( head, art._origin );
877
850
  }
878
- else {
879
- const variant = art.kind === 'aspect' && !art.name && 'aspect';
880
- signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
881
- [ env ], {
882
- '#': variant,
883
- art: (variant ? '' : searchName( art, item.id, 'element' )),
884
- id: item.id,
885
- } );
851
+ case '$self': // TODO: remove $projection from CC
852
+ case '$tableAlias': {
853
+ setLink( head, '_navigation', art );
854
+ setArtifactLink( head, art._origin ); // query source or leading query in FROM
855
+ if (!art._origin)
856
+ return art._origin;
857
+ // if just table alias (with expand), mark `user` with `$noOrigin` to indicate
858
+ // that the corresponding entity should not be put as $origin into the CSN.
859
+ // TODO: remove again, should be easy enough in to-csn without.
860
+ 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
+ }
868
+ return art;
869
+ }
870
+ case '$parameters': { // TODO: remove from CC
871
+ // TODO: if ref.scope='param' is handled, test that here, too ?
872
+ const { id } = path[1];
873
+ message( 'ref-obsolete-parameters', [ head.location, user ],
874
+ { code: `$parameters.${ id }`, newcode: `:${ id }` },
875
+ 'Obsolete $(CODE) - replace by $(NEWCODE)' );
876
+ // TODO: replace it in to-csn correspondingly, probably v5 or later in v4 ?
877
+ return art;
886
878
  }
887
- return null;
879
+ default:
880
+ return art;
881
+ }
882
+ }
883
+
884
+ function getAmbiguousRefLink( arr, head, user ) {
885
+ if (arr[0].kind !== '$navElement' || arr.some( e => e._parent.$duplicates ))
886
+ return false;
887
+ // only complain about ambiguous source elements if we do not have
888
+ // duplicate table aliases, only mention non-ambiguous source elems
889
+ const uniqueNames = arr.filter( e => !e.$duplicates);
890
+ if (uniqueNames.length) {
891
+ const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
892
+ .map( e => `${ e.name.alias }.${ e.name.element }` );
893
+ let variant = names.length === uniqueNames.length ? 'std' : 'few';
894
+ if (names.length === 0)
895
+ variant = 'none';
896
+ error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
888
897
  }
898
+ return false;
899
+ }
900
+
901
+ // Functions for the secondary reference semantics ----------------------------
902
+
903
+ function typeOfSemantics( user, [ head ] ) {
904
+ // `type of` is only allowed for (sub) elements of main artifacts
905
+ let struct = user;
906
+ while (struct.kind === 'element')
907
+ struct = struct._parent;
908
+ if (struct === user._main && struct.kind !== 'annotation')
909
+ return { dynamic: typeOfParentDict };
910
+ error( 'type-unexpected-typeof', [ head.location, user ],
911
+ { keyword: 'type of', '#': struct.kind } );
912
+ return false;
913
+ }
914
+
915
+ function paramSemantics() {
916
+ return { dynamic: artifactParams, notFound: undefinedParam };
917
+ }
918
+ function paramUnsupported( user, _path, location ) {
919
+ error( 'ref-unexpected-scope', [ location, user ],
920
+ // why an extra text for calculated elements? or separate for all?
921
+ { '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
922
+ return false;
889
923
  }
890
924
 
925
+ // Functions for semantics.lexical: -------------------------------------------
926
+
927
+ function userBlock( user ) {
928
+ return definedViaCdl( user ) && user._block;
929
+ }
930
+ function justDollarSelf( user ) {
931
+ const query = userQuery( user );
932
+ if (!query)
933
+ return user._main || user;
934
+ // query.$tableAliases contains both aliases and $self/$projection
935
+ const aliases = query.$tableAliases;
936
+ const r = Object.create(null);
937
+ if (aliases.$self.kind === '$self')
938
+ r.$self = aliases.$self;
939
+ // TODO: disallow $projection for ON conditions all together
940
+ if (aliases.$projection?.kind === '$self')
941
+ r.$projection = aliases.$projection;
942
+ const { $parameters } = user._main.$tableAliases;
943
+ if ($parameters) // no need to test `kind`, just compiler-set “aliases”
944
+ r.$parameters = $parameters;
945
+ return { $tableAliases: r };
946
+ }
947
+ function tableAliasesAndSelf( user ) {
948
+ return userQuery( user ) || user._main || user;
949
+ }
950
+ function tableAliasesIfNotExtendAndSelf( user ) {
951
+ if (!user.$extended)
952
+ return tableAliasesAndSelf( user );
953
+ if (typeof user.$extended !== 'string') {
954
+ const aliases = userQuery( user ).$tableAliases;
955
+ user.$extended = Object.keys( aliases )[0];
956
+ }
957
+ return justDollarSelf( user );
958
+ }
959
+
960
+ // Functions called via semantics.dynamic: ------------------------------------
961
+
962
+ function modelDefinitions() {
963
+ return model.definitions;
964
+ }
965
+ function modelBuiltinsOrDefinitions( user ) {
966
+ return definedViaCdl( user ) ? model.$builtins : model.definitions;
967
+ }
968
+
969
+ function artifactParams( user ) {
970
+ const lexical = (user._main || user).$tableAliases;
971
+ // TODO: already report error here if no parameters?
972
+ return lexical?.$parameters?.elements || Object.create( null );
973
+ }
974
+
975
+ function typeOfParentDict( user ) {
976
+ // CDL produces the following XSN representation for `type of elem`:
977
+ // { path: [{ id: 'type of'}, { id: 'elem'}], scope: 'typeOf' }
978
+ return { 'type of': user._parent };
979
+ }
980
+
981
+ 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 );
985
+ }
986
+
987
+ function combinedSourcesOrParentElements( user ) {
988
+ const query = userQuery( user );
989
+ if (!query)
990
+ return environment( user._main ? user._parent : user );
991
+ return query._combined; // TODO: do we need query._parent._combined ?
992
+ }
993
+ function parentElements( user ) {
994
+ return environment( user._main && user.kind !== 'select' ? user._parent : user );
995
+ }
996
+
997
+ function queryElements( user ) {
998
+ return environment( user );
999
+ }
1000
+
1001
+ function nestedElements( user ) {
1002
+ Functions.navigationEnv( user._pathHead ); // set _origin
1003
+ return environment( user._pathHead._origin );
1004
+ }
1005
+
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;
1010
+ if (env === 0)
1011
+ return 0;
1012
+ const target = env?.targetAspect;
1013
+ if (target) {
1014
+ if (target.elements)
1015
+ return target.elements;
1016
+ env = resolvePath( env.targetAspect, 'targetAspect', env );
1017
+ }
1018
+ return env?.elements || Object.create(null);
1019
+ }
1020
+
1021
+ function artifactsEnv( art ) {
1022
+ return art._subArtifacts || Object.create(null);
1023
+ }
1024
+
1025
+ // Return effective search environment provided by artifact `art`, i.e. the
1026
+ // `artifacts` or `elements` dictionary. For the latter, follow the `type`
1027
+ // chain and resolve the association `target`. View elements are calculated
1028
+ // on demand.
1029
+ // TODO: what about location/user when called from getPath ?
1030
+ // TODO: think of always acting as if falsyIfNone would be true
1031
+ // (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 );
1034
+ if (env === 0)
1035
+ return 0;
1036
+ return env?.elements || !falsyIfNone && Object.create(null);
1037
+ }
1038
+
1039
+ // Functions called via semantics.notFound: -----------------------------------
1040
+
1041
+ function undefinedDefinition( user, item, valid, _dict, prev ) {
1042
+ // in a CSN source or for `using`, only one env was tested (valid.length 1) :
1043
+ const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
1044
+ signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
1045
+ [ item.location, user ], valid, { art } );
1046
+ // TODO: improve text, use text variant for: "or builtin" or "definitions" or none
1047
+ }
1048
+
1049
+ function undefinedForAnnotate( user, item, valid, _dict, prev ) {
1050
+ // in a CSN source, only one env was tested (valid.length 1):
1051
+ const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
1052
+ signalNotFound( (valid.length > 1 ? 'anno-undefined-art' : 'anno-undefined-def'),
1053
+ // TODO: ext-undefined-xyz
1054
+ [ item.location, user ], valid, { art } );
1055
+ }
1056
+
1057
+ function undefinedParam( user, head, valid ) {
1058
+ // TODO: text variant if there are no parameters, or in artifactParameters()?
1059
+ signalNotFound( 'ref-undefined-param', [ head.location, user ], valid,
1060
+ { art: user._main || user, id: head.id } );
1061
+ }
1062
+
1063
+ function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
1064
+ // is only called if there is a target, targetElements() returns falsy otherwise
1065
+ const { target } = pathItemArtifact?._effectiveType || user._parent;
1066
+ // TODO: better with $refs in filter conditions
1067
+ signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
1068
+ { '#': 'target', art: target, id: head.id } );
1069
+ }
1070
+
1071
+ function undefinedVariable( user, head, valid ) {
1072
+ // TODO: avoid message if we have already complained about `(exists …)`?
1073
+ const { id } = head;
1074
+ const isVar = id.charAt( 0 ) === '$' && id !== '$self';
1075
+ signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
1076
+ [ head.location, user ],
1077
+ valid, { '#': 'std', id } );
1078
+ // TODO: use s/th better than 'ref-expecting-const'?
1079
+ }
1080
+
1081
+ function undefinedSourceElement( user, head, valid, dynamicDict ) {
1082
+ // TODO: we might mention both the "direct" and the "effective" type and
1083
+ // always just mentioned one identifier as not found
1084
+ const { id } = head;
1085
+ if (id.charAt( 0 ) === '$') {
1086
+ const tableAlias = dynamicDict[id]?._parent;
1087
+ // TODO: probably better to pass param `semantics` and calculate dynamic dict explicitly
1088
+ const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
1089
+ // TODO: mention $self without query
1090
+ signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
1091
+ { '#': (alias ? 'alias' : 'std'), alias, id } );
1092
+ }
1093
+ else {
1094
+ const isVirtual = (user.name?.id === id && user.virtual?.val);
1095
+ const code = 'virtual null as ‹name›';
1096
+ signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
1097
+ { art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
1098
+ }
1099
+ }
1100
+
1101
+ function undefinedParentElement( user, head, valid, dynamicDict ) {
1102
+ // TODO: we might mention both the "direct" and the "effective" type and
1103
+ // always just mentioned one identifier as not found
1104
+ const { id } = head;
1105
+ if (id.charAt( 0 ) === '$') {
1106
+ const queryOrMain = dynamicDict[id]?._parent;
1107
+ const withSelf = queryOrMain && (!queryOrMain._main || queryOrMain?.kind === 'select');
1108
+ signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
1109
+ { '#': (withSelf ? 'self' : 'std'), alias: '$self', id } );
1110
+ }
1111
+ else {
1112
+ // TODO: extra msg like ref-rejected-on if elem found in source elements?
1113
+ // also whether users wronly tried to refer to aliases/mixins?
1114
+ const msgVar = userQuery( user ) ? 'query' : null;
1115
+ // TODO: better with ON in expand if that is supported
1116
+ signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
1117
+ { '#': msgVar, art: head.id } );
1118
+ }
1119
+ }
1120
+
1121
+ function undefinedOrderByElement( user, head, valid, dynamicDict, _art, path ) {
1122
+ const { id } = head;
1123
+ const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
1124
+ if (src && !Array.isArray(src)) {
1125
+ path.$prefix = src.name.alias; // pushing it to path directly could be problematic
1126
+ // configurable error:
1127
+ signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
1128
+ { id: head.id, newcode: `${ src.name.alias }.${ head.id }` } );
1129
+ return src;
1130
+ }
1131
+ undefinedParentElement( user, head, valid, dynamicDict );
1132
+ return null;
1133
+ }
1134
+
1135
+ function undefinedNestedElement( user, head, valid ) {
1136
+ // environment( user._pathHead ); // set _origin
1137
+ const art = user._pathHead._origin;
1138
+ // if (!art) console.log('UNE:',user,user._pathHead)
1139
+ 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() ?
1145
+ }
1146
+
1147
+ // Low-level functions --------------------------------------------------------
1148
+
891
1149
  /**
892
1150
  * Make a "not found" error and optionally attach valid names.
893
1151
  *
@@ -903,8 +1161,10 @@ function fns( model ) {
903
1161
  /** @type {object} */
904
1162
  const err = message( msgId, location, textParams );
905
1163
  if (valid) {
1164
+ const user = Array.isArray( location ) && location[1];
1165
+ err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
906
1166
  valid.reverse();
907
- attachAndEmitValidNames(err, ...valid);
1167
+ attachAndEmitValidNames( err, ...valid );
908
1168
  }
909
1169
  }
910
1170
 
@@ -924,7 +1184,7 @@ function fns( model ) {
924
1184
  prev[`${ art.name.id }.${ curr }`] = true;
925
1185
  return prev;
926
1186
  }, Object.create(null));
927
- attachAndEmitValidNames(err, valid);
1187
+ attachAndEmitValidNames( err, valid );
928
1188
  }
929
1189
 
930
1190
  /**
@@ -935,29 +1195,46 @@ function fns( model ) {
935
1195
  * @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
936
1196
  */
937
1197
  function attachAndEmitValidNames( msg, ...validDicts ) {
1198
+ const viaCdl = msg.validNames; // TODO: move to argument list
938
1199
  if (!options.testMode && !options.attachValidNames)
939
1200
  return;
940
1201
 
941
1202
  const valid = Object.assign( Object.create( null ), ...validDicts );
942
1203
  msg.validNames = Object.create( null );
943
1204
  for (const name of Object.keys( valid )) {
944
- // ignore internal types such as cds.Association
945
- if (valid[name].internal || valid[name].deprecated || valid[name].$inferred === '$internal')
946
- continue;
947
- msg.validNames[name] = valid[name];
1205
+ const art = valid[name];
1206
+ // ignore internal types such as cds.Association, ignore names with dot for
1207
+ // CDL references to main artifacts:
1208
+ if (!art.internal && !art.deprecated && art.$inferred !== '$internal' &&
1209
+ (viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
1210
+ msg.validNames[name] = art;
948
1211
  }
949
1212
 
950
1213
  if (options.testMode && !options.$recompile) {
951
1214
  // no semantic location => either first of [loc, semantic loc] pair or just location.
952
- const loc = msg.location[0] || msg.location;
1215
+ const loc = msg.$location[0] || msg.$location;
953
1216
  const names = Object.keys(msg.validNames);
954
- info( null, loc,
1217
+ names.sort();
1218
+ if (names.length > 22) {
1219
+ names.length = 20;
1220
+ names[20] = '…';
1221
+ }
1222
+ info( null, [ loc, null ],
955
1223
  { '#': !names.length ? 'zero' : 'std' },
956
- { std: `Valid: ${ names.sort().join(', ') }`, zero: 'No valid names' });
1224
+ { std: `Valid: ${ names.join(', ') }`, zero: 'No valid names' });
957
1225
  }
958
1226
  }
959
1227
  }
960
1228
 
1229
+ function removeDollarNames( dict ) {
1230
+ const r = Object.create( null );
1231
+ for (const name in dict) {
1232
+ if (name.charAt( 0 ) !== '$')
1233
+ r[name] = dict[name];
1234
+ }
1235
+ return r;
1236
+ }
1237
+
961
1238
  module.exports = {
962
1239
  fns,
963
1240
  };