@sap/cds-compiler 2.12.0 → 2.15.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 (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -0,0 +1,1521 @@
1
+ // Compiler phase "resolve": resolve all references
2
+
3
+ // The resolve phase tries to find the artifacts (and elements) for all
4
+ // references in the augmented CSN. If there are unresolved references, this
5
+ // compiler phase fails with an error containing a vector of corresponding
6
+ // messages (alternatively, we could just store this vector in the CSN).
7
+
8
+ // References are resolved according to the scoping rules of CDS specification.
9
+ // That means, the first name of a reference path is not only searched in the
10
+ // current environments, but also in the parent environments, with the source
11
+ // as second-last, and the environment for builtins as the last search
12
+ // environment.
13
+
14
+ // For all type references, we set the property `type._artifact`, the latter is
15
+ // the actual type definition.
16
+
17
+ // If the referred type definition has a `parameters` property, we use it to
18
+ // transform the `$typeArgs` property (sibling to the `type` property`) to
19
+ // named properties. See function `resolveTypeExpr` below for details.
20
+
21
+ // Example 'file.cds' (see './define.js' for the CSN before "resolve"):
22
+ // type C { elem: String(4); }
23
+ //
24
+ // The corresponding definition of element "elem" looks as follows:
25
+ // {
26
+ // kind: 'element',
27
+ // name: { id: 'elem', component: 'elem', location: ... }
28
+ // type: { absolute: 'cds.String', _artifact: {...}, path: ...},
29
+ // length: { val: 4, location: <of the number literal> },
30
+ // location: ..., _parent: ...
31
+ // }
32
+
33
+ // Potential file names:
34
+ // lookup-refs / memorize: main refs loop (phase 2)
35
+ // monitor-refs: resolve-refs (not leading to new defs/elems)
36
+ // repair-props: rewrite, late extensions
37
+ // test-model: cycle detection, late tests (currently in checks)
38
+
39
+ 'use strict';
40
+
41
+ const {
42
+ isDeprecatedEnabled,
43
+ forEachDefinition,
44
+ forEachMember,
45
+ forEachGeneric,
46
+ forEachInOrder,
47
+ } = require('../base/model');
48
+ const { dictAdd } = require('../base/dictionaries');
49
+ const { dictLocation } = require('../base/location');
50
+ const { searchName, weakLocation } = require('../base/messages');
51
+ const { combinedLocation } = require('../base/location');
52
+ const { forEachValue } = require('../utils/objectUtils');
53
+ const { typeParameters } = require('./builtins');
54
+
55
+ const { kindProperties } = require('./base');
56
+ const {
57
+ setLink,
58
+ setArtifactLink,
59
+ pathName,
60
+ linkToOrigin,
61
+ setMemberParent,
62
+ withAssociation,
63
+ storeExtension,
64
+ dependsOn,
65
+ dependsOnSilent,
66
+ testExpr,
67
+ targetMaxNotOne,
68
+ traverseQueryPost,
69
+ } = require('./utils');
70
+
71
+ const detectCycles = require('./cycle-detector');
72
+ const layers = require('./moduleLayers');
73
+
74
+ const $location = Symbol.for('cds.$location');
75
+
76
+ const annotationPriorities = {
77
+ define: 1, extend: 2, annotate: 2, edmx: 3,
78
+ };
79
+ const $inferred = Symbol.for('cds.$inferred');
80
+
81
+ // Export function of this file. Resolve type references in augmented CSN
82
+ // `model`. If the model has a property argument `messages`, do not throw
83
+ // exception in case of an error, but push the corresponding error object to
84
+ // that property (should be a vector).
85
+ function resolve( model ) {
86
+ const { options } = model;
87
+ // Get shared functionality and the message function:
88
+ const {
89
+ info, warning, error, message,
90
+ } = model.$messageFunctions;
91
+ const {
92
+ resolvePath,
93
+ defineAnnotations,
94
+ attachAndEmitValidNames,
95
+ lateExtensions,
96
+ effectiveType,
97
+ directType,
98
+ resolveType,
99
+ resolveTypeArgumentsUnchecked,
100
+ populateQuery,
101
+ } = model.$functions;
102
+ const { environment } = model.$volatileFunctions;
103
+ Object.assign( model.$functions, {
104
+ resolveExpr,
105
+ } );
106
+
107
+ /** @type {any} may also be a boolean */
108
+
109
+ // behavior depending on option `deprecated`:
110
+ const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
111
+ // TODO: we should get rid of noElementsExpansion soon; both
112
+ // beta.nestedProjections and beta.universalCsn do not work with it.
113
+
114
+ return doResolve();
115
+
116
+ function doResolve() {
117
+ // Phase 1: check paths in usings has been moved to kick-start.js Phase 2:
118
+ // calculate/init view elements & collect views in order:
119
+ // TODO: It might be that we need to call propagateKeyProps() and
120
+ // addImplicitForeignKeys() in populate.js, as we might need to know the
121
+ // foreign keys in populate.js (foreign key access w/o JOINs).
122
+
123
+ // Phase 3: calculate keys along simple queries in collected views:
124
+ model._entities.forEach( propagateKeyProps );
125
+ // While most dependencies leading have been added at this point, new
126
+ // cycles could be added later (e.g. via assocs in where conditions),
127
+ // i.e. keep cycle detection with messages at the end (or after phase 4).
128
+
129
+ // Phase 4: resolve all artifacts:
130
+ forEachDefinition( model, resolveRefs );
131
+ forEachGeneric( model, 'vocabularies', resolveRefs );
132
+ // for builtin types
133
+ forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
134
+ forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
135
+ // Phase 6: apply ANNOTATE on autoexposed entities and unknown artifacts:
136
+ lateExtensions( annotateMembers );
137
+ if (model.extensions)
138
+ model.extensions.map( annotateUnknown );
139
+ // Phase 7: report cyclic dependencies:
140
+ detectCycles( model.definitions, ( user, art, location ) => {
141
+ if (location) {
142
+ error( 'ref-cyclic', [ location, user ], { art }, {
143
+ std: 'Illegal circular reference to $(ART)',
144
+ element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
145
+ });
146
+ }
147
+ });
148
+ return model;
149
+ }
150
+
151
+ //--------------------------------------------------------------------------
152
+ // Phase 3: calculate propagated KEYs
153
+ //--------------------------------------------------------------------------
154
+
155
+ function propagateKeyProps( view ) {
156
+ // Second argument true ensure that `key` is only propagated along simple
157
+ // view, i.e. ref or subquery in FROM, not UNION or JOIN.
158
+ traverseQueryPost( view.query, true, ( query ) => {
159
+ if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
160
+ withKeyPropagation( query )) // now the part with messages
161
+ inheritKeyProp( query, true );
162
+ } );
163
+ }
164
+
165
+ function withExplicitKeys( query ) {
166
+ for (const name in query.elements) {
167
+ const elem = query.elements[name];
168
+ if (elem.key && !elem.$duplicates) // also those from includes
169
+ return true;
170
+ }
171
+ return false;
172
+ }
173
+
174
+ function inheritKeyProp( query, doIt ) {
175
+ for (const name in query.elements) {
176
+ const elem = query.elements[name];
177
+ // no key prop for duplicate elements or additional specified elements:
178
+ if (elem.$duplicates || !elem.value)
179
+ continue;
180
+ const nav = pathNavigation( elem.value );
181
+ if (!nav.navigation)
182
+ continue; // undefined, expr, $magic, :const, $self (!), $self.elem
183
+ const { item } = nav;
184
+ if (item !== elem.value.path[elem.value.path.length - 1])
185
+ continue; // having selected a sub elem / navigated along assoc
186
+ const { key } = item._artifact;
187
+ if (key) {
188
+ if (!doIt)
189
+ return true;
190
+ elem.key = { location: elem.value.location, val: key.val, $inferred: 'query' };
191
+ }
192
+ }
193
+ return false;
194
+ }
195
+
196
+ function primarySourceNavigation( aliases ) {
197
+ for (const name in aliases)
198
+ return aliases[name].elements;
199
+ return undefined;
200
+ }
201
+
202
+ function withKeyPropagation( query ) {
203
+ const { from } = query;
204
+ if (!from) // parse error SELECT FROM <EOF>
205
+ return false;
206
+
207
+ let propagateKeys = true; // used instead early RETURN to get more messages
208
+ const toMany = withAssociation( from, targetMaxNotOne, true );
209
+ if (toMany) {
210
+ propagateKeys = false;
211
+ info( 'query-from-many', [ toMany.location, query ], { art: toMany },
212
+ {
213
+ // eslint-disable-next-line max-len
214
+ std: 'Selecting from to-many association $(ART) - key properties are not propagated',
215
+ // eslint-disable-next-line max-len
216
+ element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
217
+ } );
218
+ }
219
+ // Check that all keys from the source are projected:
220
+ const notProjected = []; // we actually push to the array
221
+ const navElems = primarySourceNavigation( query.$tableAliases );
222
+ for (const name in navElems) {
223
+ const nav = navElems[name];
224
+ if (nav.$duplicates)
225
+ continue;
226
+ const { key } = nav._origin;
227
+ if (key && key.val && !(nav._projections && nav._projections.length))
228
+ notProjected.push( nav.name.id );
229
+ }
230
+ if (notProjected.length) {
231
+ propagateKeys = false;
232
+ info( 'query-missing-keys', [ from.location, query ], { names: notProjected },
233
+ {
234
+ std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
235
+ one: 'Key $(NAMES) has not been projected - key properties are not propagated',
236
+ } );
237
+ }
238
+ // Check that there is no to-many assoc used in select item:
239
+ for (const name in query.elements) {
240
+ const elem = query.elements[name];
241
+ if (!elem.$inferred && elem.value &&
242
+ testExpr( elem.value, selectTest, () => false ))
243
+ propagateKeys = false;
244
+ }
245
+ return propagateKeys;
246
+
247
+ function selectTest( expr ) {
248
+ const art = withAssociation( expr, targetMaxNotOne );
249
+ if (art) {
250
+ info( 'query-navigate-many', [ art.location, query ], { art },
251
+ {
252
+ // eslint-disable-next-line max-len
253
+ std: 'Navigating along to-many association $(ART) - key properties are not propagated',
254
+ // eslint-disable-next-line max-len
255
+ element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
256
+ // eslint-disable-next-line max-len
257
+ alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
258
+ } );
259
+ }
260
+ return art;
261
+ }
262
+ }
263
+
264
+ //--------------------------------------------------------------------------
265
+ // Phase 4:
266
+ //--------------------------------------------------------------------------
267
+
268
+ function adHocOrMainKind( elem ) {
269
+ const main = elem._main;
270
+ if (main) {
271
+ do {
272
+ elem = elem._parent;
273
+ if (elem.targetAspect)
274
+ return 'aspect'; // ad-hoc composition target aspect
275
+ } while (elem !== main);
276
+ }
277
+ return elem.kind;
278
+ }
279
+ // TODO: have $applied/$extension/$status on extension with the following values
280
+ // - 'unknown': artifact to extend/annotate is not defined or contains unknown member
281
+ // - 'referred': contains annotation for element of referred type (not yet supported)
282
+ // - 'inferred': only contains extension for known member, but some inferred ones
283
+ // (inferred = elements from structure includes, query elements)
284
+ // - 'original': only contains extensions on non-inferred members
285
+
286
+ // Resolve all references in artifact or element `art`. Do so recursively in
287
+ // all sub elements.
288
+ // TODO: make this function smaller
289
+ function resolveRefs( art ) {
290
+ // console.log(message( null, art.location, art, {}, 'Info','REFS').toString())
291
+ // console.log(message( null, art.location, art, {target:art.target}, 'Info','RR').toString())
292
+ const parent = art._parent;
293
+ const allowedInMain = [ 'entity', 'aspect' ].includes( adHocOrMainKind( art ) );
294
+ const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
295
+ if (art.key && art.key.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
296
+ warning( 'unexpected-key', [ art.key.location, art ],
297
+ { '#': allowedInMain ? 'sub' : 'std' }, {
298
+ std: 'KEY is only supported for elements in an entity or an aspect',
299
+ sub: 'KEY is only supported for top-level elements',
300
+ });
301
+ }
302
+ if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
303
+ message( 'type-managed-composition', [ art.targetAspect.location, art ],
304
+ { '#': allowedInMain ? 'sub' : 'std' } );
305
+ }
306
+ if (art.includes && !allowedInMain) {
307
+ for (const include of art.includes) {
308
+ const struct = include._artifact;
309
+ if (struct && struct.kind !== 'type' && struct.elements &&
310
+ Object.values( struct.elements ).some( e => e.targetAspect)) {
311
+ message( 'type-managed-composition', [ include.location, art ],
312
+ { '#': struct.kind, art: struct } );
313
+ }
314
+ }
315
+ }
316
+ let obj = art;
317
+ if (obj.type) // TODO: && !obj.type.$inferred ?
318
+ resolveTypeExpr( obj, art );
319
+ const type = effectiveType( obj ); // make sure implicitly redirected target exists
320
+ if (!obj.items && type && type.items && enableExpandElements) {
321
+ // TODO: shouldn't be this part of populate.js ?
322
+ const items = {
323
+ location: weakLocation( (obj.type || obj).location ),
324
+ $inferred: 'expand-items',
325
+ };
326
+ setLink( items, '_outer', obj );
327
+ setLink( items, '_origin', type.items );
328
+ obj.items = items;
329
+ obj.$expand = 'origin';
330
+ }
331
+ if (obj.items) { // TODO: make this a while in v2 (also items proxy)
332
+ obj = obj.items || obj; // the object which has type properties
333
+ if (enableExpandElements)
334
+ effectiveType(obj);
335
+ }
336
+ if (obj.type) { // TODO: && !obj.type.$inferred ?
337
+ if (obj !== (art.returns || art)) // not already checked
338
+ resolveTypeExpr( obj, art );
339
+ // typeOf unmanaged assoc? TODO: is this the right place to check this?
340
+ // (probably better in rewriteAssociations)
341
+ const elemtype = obj.type._artifact;
342
+ if (elemtype && effectiveType( elemtype )) {
343
+ const assocType = getAssocSpec( elemtype ) || {};
344
+ if (assocType.on && !obj.on)
345
+ obj.on = { $inferred: 'rewrite' };
346
+ if (assocType.targetAspect) {
347
+ error( 'composition-as-type-of', [ obj.type.location, art ], {},
348
+ 'A managed aspect composition element can\'t be used as type' );
349
+ return;
350
+ }
351
+ else if (assocType.on) {
352
+ error( 'assoc-as-type-of', [ obj.type.location, art ], {},
353
+ 'An unmanaged association can\'t be used as type' );
354
+ return;
355
+ }
356
+
357
+ // Check if relational type is missing its target or if it's used directly.
358
+ if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
359
+ !obj.target && !obj.targetAspect) {
360
+ const isCsn = (obj._block && obj._block.$frontend === 'json');
361
+ error('type-missing-target', [ obj.type.location, obj ],
362
+ { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
363
+ // We don't say "use 'association to <target>" because the type could be used
364
+ // in action parameters, etc. as well.
365
+ std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
366
+ csn: 'Type $(TYPE) is missing a target',
367
+ });
368
+ }
369
+ }
370
+ }
371
+ if (obj.target) {
372
+ // console.log(obj.name,obj._origin.name)
373
+ if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
374
+ resolveTarget( art, obj._origin );
375
+ // console.log(error( 'test-target', [ obj.location, obj ],
376
+ // { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
377
+ if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
378
+ resolveTarget( art, obj );
379
+ else
380
+ // TODO: better write when inferred target must be redirected
381
+ resolveRedirected( obj, obj.target._artifact );
382
+ }
383
+ else if (obj.kind === 'mixin') {
384
+ error( 'non-assoc-in-mixin', [ (obj.type || obj.name).location, art ], {},
385
+ 'Only unmanaged associations are allowed in mixin clauses' );
386
+ }
387
+ if (art.targetElement) { // in foreign keys
388
+ const target = parent && parent.target;
389
+ if (target && target._artifact) {
390
+ // we just look in target for the path
391
+ // TODO: also check that we do not follow associations? no args, no filter
392
+ resolvePath( art.targetElement, 'targetElement', art,
393
+ environment( target._artifact ), target._artifact );
394
+ }
395
+ }
396
+ // Resolve projections/views
397
+ // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
398
+
399
+ if (art.$queries)
400
+ art.$queries.forEach( resolveQuery );
401
+
402
+ if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
403
+ effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
404
+
405
+ if (obj.elements) { // silent dependencies
406
+ forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
407
+ }
408
+ else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
409
+ forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
410
+ }
411
+ if (obj.foreignKeys) { // silent dependencies
412
+ forEachGeneric( obj, 'foreignKeys', (elem) => {
413
+ dependsOnSilent( art, elem );
414
+ } );
415
+ addForeignKeyNavigations( art );
416
+ }
417
+
418
+ resolveExpr( art.default, 'default', art );
419
+ resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
420
+ if (art.value && !art.type && !art.target && !art.elements)
421
+ inferTypeFromCast( art );
422
+
423
+ if (art.kind === 'element' || art.kind === 'mixin')
424
+ effectiveType( art );
425
+
426
+ annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
427
+ chooseAnnotationsInArtifact( art );
428
+
429
+ forEachMember( art, resolveRefs, art.targetAspect );
430
+
431
+ // Set '@Core.Computed' in the Core Compiler to have it propagated...
432
+ if (art.kind !== 'element' || art['@Core.Computed'])
433
+ return;
434
+ if (art.virtual && art.virtual.val ||
435
+ art.value &&
436
+ (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
437
+ art.value._artifact.kind === 'builtin' || art.value._artifact.kind === 'param' )) {
438
+ art['@Core.Computed'] = {
439
+ name: {
440
+ path: [ { id: 'Core.Computed', location: art.location } ],
441
+ location: art.location,
442
+ },
443
+ $inferred: '$generated',
444
+ };
445
+ }
446
+ }
447
+
448
+ // Return type containing the assoc spec (keys, on); note that no
449
+ // propagation/rewrite has been done yet, cyclic dependency must have been
450
+ // checked before!
451
+ function getAssocSpec( type ) {
452
+ const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
453
+ // only to be called without cycles
454
+ while (type && !cyclic.has(type)) {
455
+ cyclic.add(type);
456
+ if (type.on || type.foreignKeys || type.targetAspect)
457
+ return type;
458
+ type = directType( type );
459
+ }
460
+ return null;
461
+ }
462
+
463
+ function inferTypeFromCast( elem ) {
464
+ // TODO: think about CAST checks in checks.js
465
+ const { op, type } = elem.value;
466
+ if (op && op.val === 'cast' && type && type._artifact) {
467
+ // op.val is also correctly set with CSN input
468
+ elem.type = { ...type, $inferred: 'cast' };
469
+ setArtifactLink( elem.type, type._artifact );
470
+ for (const prop of typeParameters.list) {
471
+ if (elem.value[prop])
472
+ elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
473
+ }
474
+ }
475
+ }
476
+
477
+ // Phase 4 - annotations ---------------------------------------------------
478
+
479
+ function annotateUnknown( ext ) {
480
+ // extensions may have annotations for elements/actions/... which may
481
+ // themselves may be unknown
482
+ forEachMember(ext, annotateUnknown);
483
+
484
+ if (ext.$extension) // extension for known artifact -> already applied
485
+ return;
486
+ annotateMembers( ext );
487
+ for (const prop in ext) {
488
+ if (prop.charAt(0) === '@')
489
+ chooseAssignment( prop, ext );
490
+ }
491
+ }
492
+
493
+ /**
494
+ * @param {XSN.Artifact} art
495
+ * @param {XSN.Extension[]} [extensions]
496
+ * @param {string} [prop]
497
+ * @param {string} [name]
498
+ * @param {object} [parent]
499
+ * @param {string} [kind]
500
+ */
501
+ function annotateMembers( art, extensions, prop, name, parent, kind ) {
502
+ const showMsg = !art && parent && parent.kind !== 'annotate';
503
+ if (!art && extensions && extensions.length) {
504
+ if (Array.isArray( parent ))
505
+ return;
506
+ const parentExt = extensionFor(parent);
507
+ art = parentExt[prop] && parentExt[prop][name];
508
+ if (!art) {
509
+ art = {
510
+ kind, // for setMemberParent()
511
+ name: { id: name, location: extensions[0].name.location },
512
+ location: extensions[0].location,
513
+ };
514
+ setMemberParent( art, name, parentExt, prop );
515
+ art.kind = 'annotate'; // after setMemberParent()!
516
+ }
517
+ }
518
+
519
+ for (const ext of extensions || []) {
520
+ if ('_artifact' in ext.name) // already applied
521
+ continue;
522
+ setArtifactLink( ext.name, art );
523
+
524
+ if (art) {
525
+ defineAnnotations( ext, art, ext._block, ext.kind );
526
+ // eslint-disable-next-line no-shadow
527
+ forEachMember( ext, ( elem, name, prop ) => {
528
+ storeExtension( elem, name, prop, art, ext._block );
529
+ });
530
+ }
531
+ if (showMsg) {
532
+ // somehow similar to checkDefinitions():
533
+ const feature = kindProperties[parent.kind][prop];
534
+ if (prop === 'elements' || prop === 'enum') {
535
+ if (!feature) {
536
+ warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
537
+ 'Elements only exist in entities, types or typed constructs' );
538
+ }
539
+ else {
540
+ notFound( 'anno-undefined-element', ext.name.location, art,
541
+ { art: searchName( parent, name, parent.enum ? 'enum' : 'element' ) },
542
+ parent.elements || parent.enum );
543
+ }
544
+ }
545
+ else if (prop === 'actions') {
546
+ if (!feature) {
547
+ warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
548
+ 'Actions and functions only exist top-level and for entities' );
549
+ }
550
+ else {
551
+ notFound( 'anno-undefined-action', ext.name.location, art,
552
+ { art: searchName( parent, name, 'action' ) },
553
+ parent.actions );
554
+ }
555
+ }
556
+ else if (!feature) {
557
+ warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
558
+ 'Parameters only exist for actions or functions' );
559
+ } // TODO: entities betaMod
560
+ else {
561
+ notFound( 'anno-undefined-param', ext.name.location, art,
562
+ { art: searchName( parent, name, 'param' ) },
563
+ parent.params );
564
+ }
565
+ }
566
+ }
567
+ if (art && art._annotate) {
568
+ if (art.kind === 'action' || art.kind === 'function') {
569
+ expandParameters( art );
570
+ if (art.returns)
571
+ effectiveType( art.returns );
572
+ }
573
+ const aor = art.returns || art;
574
+ const obj = aor.items || aor.targetAspect || aor;
575
+ // Currently(?), effectiveType() does not calculate the effective type of
576
+ // its line item:
577
+ effectiveType( obj );
578
+ if (art._annotate.elements)
579
+ setExpandStatusAnnotate( aor, 'annotate' );
580
+ annotate( obj, 'element', 'elements', 'enum', art );
581
+ annotate( art, 'action', 'actions' );
582
+ annotate( art, 'param', 'params' );
583
+ // const { returns } = art._annotate;
584
+ // if (returns) {
585
+ // const dict = returns.elements;
586
+ // const env = obj.returns && obj.returns.elements || null;
587
+ // for (const n in dict)
588
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
589
+ // }
590
+ }
591
+ return;
592
+
593
+ function notFound( msgId, location, address, args, validDict ) {
594
+ // TODO: probably move this to shared.js and use for EXTEND, too
595
+ const msg = message( msgId, [ location, address ], args );
596
+ attachAndEmitValidNames(msg, validDict);
597
+ }
598
+
599
+ // eslint-disable-next-line no-shadow
600
+ function annotate( obj, kind, prop, altProp, parent = obj ) {
601
+ const dict = art._annotate[prop];
602
+ const env = obj[prop] || altProp && obj[altProp] || null;
603
+ for (const n in dict)
604
+ annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
605
+ }
606
+ }
607
+
608
+ function setExpandStatusAnnotate( elem, status ) {
609
+ for (;;) {
610
+ if (elem.$expand === status)
611
+ return; // already set
612
+ elem.$expand = status; // meaning: expanded, containing annos
613
+ for (let line = elem.items; line; line = line.items)
614
+ line.$expand = status; // to-csn just uses the innermost $expand
615
+ if (!elem._main)
616
+ return;
617
+ elem = elem._parent;
618
+ }
619
+ }
620
+
621
+
622
+ function expandParameters( action ) {
623
+ // see also expandElements()
624
+ if (!enableExpandElements || !effectiveType( action ))
625
+ return;
626
+ const chain = [];
627
+ // Should we be able to consider params and returns separately?
628
+ // Probably not, let to-csn omit unchanged params/returns.
629
+ while (action._origin && !action.params) {
630
+ chain.push( action );
631
+ action = action._origin;
632
+ }
633
+ chain.reverse();
634
+ for (const art of chain) {
635
+ const origin = art._origin;
636
+ if (!art.params && origin.params) {
637
+ for (const name in origin.params) {
638
+ // TODO: we could check _annotate here to decide whether we really
639
+ // not to create proxies
640
+ const orig = origin.params[name];
641
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
642
+ .$inferred = 'expand-param';
643
+ }
644
+ }
645
+ if (!art.returns && origin.returns) {
646
+ // TODO: make linkToOrigin() work for returns, kind/name?
647
+ const location = weakLocation( origin.returns.location );
648
+ art.returns = {
649
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
650
+ kind: 'param',
651
+ location,
652
+ $inferred: 'expand-param',
653
+ };
654
+ setLink( art.returns, '_parent', art );
655
+ setLink( art.returns, '_main', art._main || art );
656
+ setLink( art.returns, '_origin', origin.returns );
657
+ }
658
+ }
659
+ }
660
+
661
+ function extensionFor( art ) {
662
+ if (art.kind === 'annotate')
663
+ return art;
664
+ if (art._extension)
665
+ return art._extension;
666
+
667
+ // $extension means: already applied
668
+ const ext = {
669
+ kind: art.kind, // set kind for setMemberParent()
670
+ $extension: 'exists',
671
+ location: art.location, // location( extension to existing art ) = location(art)
672
+ };
673
+ const { location } = art.name;
674
+ if (!art._main) {
675
+ ext.name = {
676
+ path: [ { id: art.name.absolute, location } ],
677
+ location,
678
+ absolute: art.name.absolute,
679
+ };
680
+ if (model.extensions)
681
+ model.extensions.push(ext);
682
+ else
683
+ model.extensions = [ ext ];
684
+ }
685
+ else {
686
+ ext.name = { id: art.name.id, location };
687
+ const parent = extensionFor( art._parent );
688
+ const kind = kindProperties[art.kind].normalized || art.kind;
689
+ // enums would be first in elements
690
+ if ( parent[kindProperties[kind].dict] &&
691
+ parent[kindProperties[kind].dict][art.name.id] )
692
+ throw new Error(art.name.id);
693
+ setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
694
+ }
695
+ ext.kind = 'annotate'; // after setMemberParent()!
696
+ setLink( art, '_extension', ext );
697
+ setArtifactLink( ext.name, art );
698
+ if (art.returns)
699
+ ext.$syntax = 'returns';
700
+ return ext;
701
+ }
702
+
703
+ /**
704
+ * Goes through all (applied) annotations in the given artifact and chooses one
705
+ * if multiple exist according to the module layer.
706
+ *
707
+ * @param {XSN.Artifact} art
708
+ */
709
+ function chooseAnnotationsInArtifact( art ) {
710
+ for (const prop in art) {
711
+ if (prop.charAt(0) === '@')
712
+ chooseAssignment( prop, art );
713
+ }
714
+ }
715
+
716
+ function chooseAssignment( annoName, art ) {
717
+ // TODO: getPath an all names
718
+ const anno = art[annoName];
719
+ if (!Array.isArray(anno)) { // just one assignment -> use it
720
+ if (removeEllipsis( anno )) {
721
+ error( 'anno-unexpected-ellipsis',
722
+ [ anno.name.location, art ], { code: '...' } );
723
+ }
724
+ return;
725
+ }
726
+ // sort assignment according to layer
727
+ const layerAnnos = Object.create(null);
728
+ for (const a of anno) {
729
+ const layer = layers.layer( a._block );
730
+ const name = (layer) ? layer.realname : '';
731
+ const done = layerAnnos[name];
732
+ if (done)
733
+ done.annos.push( a );
734
+ else
735
+ layerAnnos[name] = { layer, annos: [ a ] };
736
+ }
737
+ mergeArrayInSCCs();
738
+ art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
739
+ return;
740
+
741
+ // Merge annotations in each layer, i.e. multiple annotations in the same layer are
742
+ // stored in an array and need to be merged before different layers can be merged.
743
+ function mergeArrayInSCCs( ) {
744
+ let pos = 0;
745
+ forEachValue(layerAnnos, (layer) => {
746
+ const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
747
+ annotationPriorities[v.$priority] === annotationPriorities.define));
748
+ if (mergeSource) {
749
+ // If the source annotation (at 'define' level) contains an ellipsis,
750
+ // there is no base to apply to.
751
+ if (removeEllipsis( mergeSource )) {
752
+ error( 'anno-unexpected-ellipsis',
753
+ [ mergeSource.name.location, art ], { code: '...' } );
754
+ }
755
+ // merge source into ellipsis array annotates
756
+ layer.annos.forEach( (mergeTarget) => {
757
+ if (mergeTarget.$priority &&
758
+ annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
759
+ pos = findEllipsis( mergeTarget );
760
+ if (pos > -1) {
761
+ if (mergeSource.literal !== 'array') {
762
+ error( 'anno-mismatched-ellipsis',
763
+ [ mergeSource.name.location, art ], { code: '...' } );
764
+ return;
765
+ }
766
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
767
+ }
768
+ }
769
+ });
770
+ }
771
+ });
772
+ }
773
+
774
+ function mergeLayeredArrays( mergeTarget ) {
775
+ if (mergeTarget.literal === 'array') {
776
+ let layer = layers.layer( mergeTarget._block );
777
+ delete layerAnnos[(layer) ? layer.realname : ''];
778
+ let pos = findEllipsis( mergeTarget );
779
+ let hasRun = false;
780
+ while (pos > -1 && Object.keys( layerAnnos ).length ) {
781
+ hasRun = true;
782
+ const mergeSource = findLayerCandidate();
783
+ if (mergeSource.literal !== 'array') {
784
+ error( 'anno-mismatched-ellipsis',
785
+ [ mergeSource.name.location, art ], { code: '...' } );
786
+ return mergeTarget;
787
+ }
788
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
789
+ layer = layers.layer( mergeSource._block );
790
+ delete layerAnnos[(layer) ? layer.realname : ''];
791
+ pos = findEllipsis( mergeTarget );
792
+ }
793
+ // All layers were processed. Remove excess ellipsis.
794
+ if (removeEllipsis( mergeTarget, pos ) && hasRun) {
795
+ // There shouldn't be any ellipsis or we don't have a base annotation.
796
+ // But only if the loop above has run. Otherwise the in-layer merge
797
+ // already warned about this case.
798
+ message( 'anno-unexpected-ellipsis-layers',
799
+ [ mergeTarget.name.location, art ], { code: '...' } );
800
+ }
801
+ }
802
+ return mergeTarget;
803
+ }
804
+
805
+ function mergeArrayValues( previousValue, arraySpec ) {
806
+ let prevPos = 0;
807
+ const result = [];
808
+ for (const item of arraySpec) {
809
+ const ell = item && item.literal === 'token' && item.val === '...';
810
+ if (!ell) {
811
+ result.push( item );
812
+ }
813
+ else {
814
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
815
+ while (prevPos < previousValue.length) {
816
+ const prevItem = previousValue[prevPos++];
817
+ result.push( prevItem );
818
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
819
+ upToSpec = false;
820
+ break;
821
+ }
822
+ }
823
+ if (upToSpec) { // non-matched UP TO
824
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
825
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
826
+ }
827
+ }
828
+ }
829
+ return result;
830
+ }
831
+
832
+ function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
833
+ const { literal } = upToSpec;
834
+ if (trueIfFullUpTo !== true) { // inside struct of UP TO
835
+ if (literal !== 'struct' && literal !== 'array' )
836
+ return true;
837
+ }
838
+ else if (literal === 'struct') {
839
+ return Object.values( upToSpec.struct ).every( checkUpToSpec );
840
+ }
841
+ else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
842
+ return true;
843
+ }
844
+ error( null, [ upToSpec.location, art ],
845
+ { anno: annoName, code: '... up to', '#': literal },
846
+ {
847
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
848
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
849
+ // eslint-disable-next-line max-len
850
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
851
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
852
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
853
+ } );
854
+ return false;
855
+ }
856
+
857
+ function equalUpTo( previousItem, upToSpec ) {
858
+ if (!previousItem)
859
+ return false;
860
+ if ('val' in upToSpec) {
861
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
862
+ return true;
863
+ const typeUpTo = typeof upToSpec.val;
864
+ const typePrev = typeof previousItem.val;
865
+ if (typeUpTo === 'number')
866
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
867
+ if (typePrev === 'number')
868
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
869
+ }
870
+ else if (upToSpec.path) {
871
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
872
+ }
873
+ else if (upToSpec.sym) {
874
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
875
+ }
876
+ else if (upToSpec.struct && previousItem.struct) {
877
+ return Object.entries( upToSpec.struct )
878
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
879
+ }
880
+ return false;
881
+ }
882
+
883
+ function normalizeRef( node ) { // see to-csn.js
884
+ const ref = pathName( node.path );
885
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
886
+ }
887
+
888
+ function removeEllipsis(a, pos = findEllipsis( a )) {
889
+ let count = 0;
890
+ while (a.literal === 'array' && pos > -1) {
891
+ count++;
892
+ a.val.splice(pos, 1);
893
+ pos = findEllipsis( a );
894
+ }
895
+ return count;
896
+ }
897
+
898
+ function findEllipsis(a) {
899
+ return (a.literal === 'array' && a.val)
900
+ ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
901
+ }
902
+
903
+ function findLayerCandidate() {
904
+ // collect assignments of upper layers (are in no _layerExtends)
905
+ const exts = Object.keys( layerAnnos ).map( layerExtends );
906
+ const allExtends = Object.assign( Object.create(null), ...exts );
907
+ const collected = [];
908
+ for (const name in layerAnnos) {
909
+ if (!(name in allExtends))
910
+ collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
911
+ }
912
+ // inspect collected assignments - choose the one or signal error
913
+ const justOnePerLayer = collected.every( annos => annos.length === 1);
914
+ if (!justOnePerLayer || collected.length > 1) {
915
+ for (const annos of collected) {
916
+ for (const a of annos ) {
917
+ // Only the message ID is different.
918
+ if (justOnePerLayer) {
919
+ message( 'anno-duplicate-unrelated-layer',
920
+ [ a.name.location, art ], { anno: annoName },
921
+ 'Duplicate assignment with $(ANNO)' );
922
+ }
923
+ else {
924
+ message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
925
+ }
926
+ }
927
+ }
928
+ }
929
+ return collected[0][0]; // just choose any one with error
930
+ }
931
+
932
+ function layerExtends( name ) {
933
+ const { layer } = layerAnnos[name];
934
+ return layer && layer._layerExtends;
935
+ }
936
+ }
937
+
938
+ function prioritizedAnnos( annos ) {
939
+ let prio = 0;
940
+ let r = [];
941
+ for (const a of annos) {
942
+ const p = annotationPriorities[a.$priority] || annotationPriorities.define;
943
+ if (p === prio) {
944
+ r.push(a);
945
+ }
946
+ else if (p > prio) {
947
+ r = [ a ];
948
+ prio = p;
949
+ }
950
+ }
951
+ return r;
952
+ }
953
+
954
+ // Phase 4 - queries and associations --------------------------------------
955
+
956
+ function resolveQuery( query ) {
957
+ if (!query._main) // parse error
958
+ return;
959
+ traverseQueryPost( query, null, populateQuery ); // TODO: still necessary?
960
+ forEachGeneric( query, '$tableAliases', ( alias ) => {
961
+ // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
962
+ if (alias.kind === 'mixin')
963
+ resolveRefs( alias ); // mixin element
964
+ else if (alias.kind !== '$self')
965
+ // pure path has been resolved, resolve args and filter now:
966
+ resolveExpr( alias, 'from', query._parent );
967
+ } );
968
+ for (const col of query.$inlines)
969
+ resolveExpr( col.value, 'expr', col, undefined, true );
970
+ // for (const col of query.$inlines)
971
+ // if (!col.value.path) throw Error(col.name.element)
972
+ if (query !== query._main._leadingQuery) // will be done later
973
+ forEachGeneric( query, 'elements', resolveRefs );
974
+ if (query.from)
975
+ resolveJoinOn( query.from );
976
+ if (query.where)
977
+ resolveExpr( query.where, 'expr', query, query._combined );
978
+ if (query.groupBy)
979
+ resolveBy( query.groupBy, 'expr' );
980
+ resolveExpr( query.having, 'expr', query, query._combined );
981
+ if (query.$orderBy) // ORDER BY from UNION:
982
+ // TODO clarify: can I access the tab alias of outer queries? If not:
983
+ // 4th arg query._main instead query._parent.
984
+ resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
985
+ if (query.orderBy) { // ORDER BY
986
+ // search in `query.elements` after having checked table aliases of the current query
987
+ resolveBy( query.orderBy, 'expr', query.elements );
988
+ // TODO: disallow resulting element ref if in expression!
989
+ // Necessary to check it in the compiler as it might work with other semantics on DB!
990
+ // (we could downgrade it to a warning if name is equal to unique source element name)
991
+ // TODO: Some helping text mentioning an alias name would be useful
992
+ }
993
+ return;
994
+
995
+ function resolveJoinOn( join ) {
996
+ if (join && join.args) { // JOIN
997
+ for (const j of join.args)
998
+ resolveJoinOn( j );
999
+ if (join.on)
1000
+ resolveExpr( join.on, 'expr', query, query._combined );
1001
+ // TODO: check restrictions according to join "query"
1002
+ }
1003
+ }
1004
+
1005
+ // Note the strange name resolution (dynamic part) for ORDER BY: the same
1006
+ // as for select items if it is an expression, but first look at select
1007
+ // item alias (i.e. like `$projection.NAME` if it is a path. If it is an
1008
+ // ORDER BY of an UNION, do not allow any dynamic path in an expression,
1009
+ // and only allow the elements of the leading query if it is a path.
1010
+ //
1011
+ // This seem to be similar, but different in SQLite 3.22.0: ORDER BY seems
1012
+ // to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
1013
+ // resolution seems to use select item aliases from all SELECTs of the
1014
+ // UNION (see <SQLite>/test/tkt2822.test).
1015
+ function resolveBy( array, mode, pathDict, q ) {
1016
+ for (const value of array ) {
1017
+ if (value)
1018
+ resolveExpr( value, mode, q || query, value.path && pathDict );
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ function resolveTarget( art, obj ) {
1024
+ if (art !== obj && obj.on && obj.$inferred !== 'REDIRECTED') {
1025
+ message( 'assoc-in-array', [ obj.on.location, art ], {},
1026
+ // TODO: also check parameter parent, two messages?
1027
+ 'An association can\'t be used for arrays or parameters' );
1028
+ setArtifactLink( obj.target, undefined );
1029
+ return;
1030
+ }
1031
+ const target = resolvePath( obj.target, 'target', art );
1032
+
1033
+ if (obj._pathHead && obj.type && !obj.type.$inferred && art._main && art._main.query) {
1034
+ // New association inside expand/inline: The on-condition can't be properly checked,
1035
+ // so abort early. See #8797
1036
+ error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
1037
+ 'Unexpected new association in expand/inline' );
1038
+ return; // avoid subsequent errors
1039
+ }
1040
+
1041
+ if (obj.on) {
1042
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1043
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1044
+ // use of unmanaged assocs in MANY, at least with $self
1045
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1046
+ const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1047
+ obj.type.path[0].id === 'cds.Composition';
1048
+ message( 'assoc-as-type', [ obj.on.location, art ],
1049
+ { '#': isComposition ? 'comp' : 'std' }, {
1050
+ std: 'An unmanaged association can\'t be defined as type',
1051
+ comp: 'An unmanaged composition can\'t be defined as type',
1052
+ });
1053
+ // TODO: also warning if inside structure
1054
+ }
1055
+ else if (obj.$inferred !== 'REDIRECTED') {
1056
+ // TODO: extra with $inferred (to avoid messages)?
1057
+ // TODO: in the ON condition of an explicitly provided model entity
1058
+ // which is going to be implicitly redirected, we can never navigate
1059
+ // along associations, even not to the foreign keys (at least if they
1060
+ // are renamed) - introduce extra 'expected' which inspects REDIRECTED
1061
+ resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
1062
+ }
1063
+ else {
1064
+ const elements = Object.create( art._parent.elements );
1065
+ elements[art.name.id] = obj;
1066
+ resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art, elements );
1067
+ }
1068
+ }
1069
+ else if (art.kind === 'mixin') {
1070
+ error( 'assoc-in-mixin', [ obj.target.location, art ], {},
1071
+ 'Managed associations are not allowed for MIXIN elements' );
1072
+ return; // avoid subsequent errors
1073
+ }
1074
+ else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
1075
+ // New association in views, i.e. parent is a query.
1076
+ error( 'query-expected-on-condition', [ obj.target.location, art ], {},
1077
+ 'Expected on-condition for published association' );
1078
+ return; // avoid subsequent errors
1079
+ }
1080
+ else if (target && !obj.foreignKeys && target.kind === 'entity') {
1081
+ if (obj.$inferred === 'REDIRECTED') {
1082
+ addImplicitForeignKeys( art, obj, target );
1083
+ }
1084
+ else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
1085
+ // cds.Association, ...
1086
+ addImplicitForeignKeys( art, obj, target );
1087
+ }
1088
+ }
1089
+
1090
+ if (target && !target.$inferred) {
1091
+ if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
1092
+ resolveRedirected( art, target );
1093
+ }
1094
+ }
1095
+ }
1096
+
1097
+ function addImplicitForeignKeys( art, obj, target ) {
1098
+ obj.foreignKeys = Object.create(null);
1099
+ forEachInOrder( target, 'elements', ( elem, name ) => {
1100
+ if (elem.key && elem.key.val) {
1101
+ const { location } = art.target;
1102
+ const key = {
1103
+ name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()
1104
+ kind: 'key',
1105
+ targetElement: { path: [ { id: elem.name.id, location } ], location },
1106
+ location,
1107
+ $inferred: 'keys',
1108
+ };
1109
+ setMemberParent( key, name, art );
1110
+ dictAdd( obj.foreignKeys, name, key );
1111
+ setArtifactLink( key.targetElement, elem );
1112
+ setArtifactLink( key.targetElement.path[0], elem );
1113
+ setLink( key, '_effectiveType', effectiveType(elem) );
1114
+ dependsOn(key, elem, location);
1115
+ dependsOnSilent(art, key);
1116
+ }
1117
+ });
1118
+ obj.foreignKeys[$inferred] = 'keys';
1119
+ }
1120
+
1121
+ function addForeignKeyNavigations( art ) {
1122
+ art.$keysNavigation = Object.create(null);
1123
+ forEachGeneric( art, 'foreignKeys', ( key ) => {
1124
+ if (!key.targetElement || !key.targetElement.path)
1125
+ return;
1126
+ let dict = art.$keysNavigation;
1127
+ const last = key.targetElement.path[key.targetElement.path.length - 1];
1128
+ for (const item of key.targetElement.path) {
1129
+ let nav = dict[item.id];
1130
+ if (!nav) {
1131
+ nav = {};
1132
+ dict[item.id] = nav;
1133
+ if (item === last)
1134
+ setArtifactLink( nav, key );
1135
+ else
1136
+ nav.$keysNavigation = Object.create(null);
1137
+ }
1138
+ else if (item === last || nav._artifact) {
1139
+ error( 'duplicate-key-ref', [ item.location, key ], {},
1140
+ 'The same target reference has already been used in a key definition' );
1141
+ return;
1142
+ }
1143
+ dict = nav.$keysNavigation;
1144
+ }
1145
+ } );
1146
+ }
1147
+
1148
+ // TODO: add this somehow to tweak-assocs.js ?
1149
+ function resolveRedirected( elem, target ) {
1150
+ setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
1151
+ const assoc = directType( elem );
1152
+ const origType = assoc && effectiveType( assoc );
1153
+ if (!origType || !origType.target) {
1154
+ const path = (elem.value && elem.value.path);
1155
+ const loc = (path && path[path.length - 1] || elem.value || elem).location;
1156
+ error( 'redirected-no-assoc', [ loc, elem ], {},
1157
+ 'Only an association can be redirected' );
1158
+ return;
1159
+ }
1160
+ // console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
1161
+ // .toString(), elem.value)
1162
+ const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
1163
+ if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
1164
+ if (!elem.on && origType.on) {
1165
+ error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1166
+ // TODO: Better text ?
1167
+ 'The ON condition is not rewritten here - provide an explicit ON condition' );
1168
+ return;
1169
+ }
1170
+ }
1171
+ const origTarget = origType.target._artifact;
1172
+ if (!origTarget || !target)
1173
+ return;
1174
+
1175
+ const chain = [];
1176
+ if (target === origTarget) {
1177
+ if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1178
+ // Only a managed redirection gets this info message. Because otherwise
1179
+ // we'd have to check whether on-condition/foreignKeys are the same.
1180
+ info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1181
+ 'The redirected target is the original $(ART)' );
1182
+ }
1183
+ setLink( elem, '_redirected', chain ); // store the chain
1184
+ return;
1185
+ }
1186
+ if (elem.foreignKeys || elem.on)
1187
+ return; // TODO: or should we still bring an msg if nothing in common?
1188
+ // now check whether target and origTarget are "related"
1189
+ while (target.query) {
1190
+ const from = target.query.args ? {} : target.query.from;
1191
+ if (!from)
1192
+ return; // parse error - TODO: or UNION?
1193
+ if (!from.path) {
1194
+ warning( 'redirected-to-complex', [ elem.target.location, elem ],
1195
+ { art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
1196
+ {
1197
+ std: 'Redirection involves the complex view $(ART)',
1198
+ target: 'The redirected target $(ART) is a complex view',
1199
+ });
1200
+ break;
1201
+ }
1202
+ target = from._artifact;
1203
+ if (!target)
1204
+ return;
1205
+ chain.push( from );
1206
+ if (target === origTarget) {
1207
+ chain.reverse();
1208
+ setLink( elem, '_redirected', chain );
1209
+ return;
1210
+ }
1211
+ }
1212
+ let redirected = null;
1213
+ let news = [ { chain: chain.reverse(), sources: [ target ] } ];
1214
+ const dict = Object.create(null);
1215
+ while (news.length) {
1216
+ const outer = news;
1217
+ news = [];
1218
+ for (const o of outer) {
1219
+ for (const s of o.sources) {
1220
+ const art = (s.kind === '$tableAlias') ? s._origin : s;
1221
+ if (art !== origTarget) {
1222
+ if (findOrig( o.chain, s, art ) && !redirected) // adds to news []
1223
+ redirected = false; // do not report further error
1224
+ }
1225
+ else if (!redirected) {
1226
+ redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
1227
+ }
1228
+ else {
1229
+ error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
1230
+ 'The redirected target originates more than once from $(ART)' );
1231
+ return;
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+ if (redirected) {
1237
+ setLink( elem, '_redirected', redirected );
1238
+ }
1239
+ else if (redirected == null) {
1240
+ error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
1241
+ 'The redirected target does not originate from $(ART)' );
1242
+ }
1243
+ return;
1244
+
1245
+ // B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
1246
+ // what does a: redirected to C means?
1247
+ // -> collect all elements Qi used in ON (corr: foreign keys)
1248
+ // -> only use an tableAlias which has propagation for all elements
1249
+ // no - error if the original target can be reached twice
1250
+ // even better: disallow complex view (try as error first)
1251
+
1252
+ // eslint-disable-next-line no-shadow
1253
+ function findOrig( chain, alias, art ) {
1254
+ if (!art || dict[art.name.absolute])
1255
+ // some include ref or query source cannot be found, or cyclic ref
1256
+ return true;
1257
+ dict[art.name.absolute] = true;
1258
+
1259
+ if (art.includes) {
1260
+ news.push( {
1261
+ chain: [ art, ...chain ],
1262
+ sources: art.includes
1263
+ .map( r => r._artifact )
1264
+ .filter( i => i ), // _artifact may be `null` if the include cannot be found
1265
+ } );
1266
+ }
1267
+ const query = art._leadingQuery;
1268
+ if (!query)
1269
+ return false; // non-query entity
1270
+ if (!query.$tableAliases) // previous error in query definition
1271
+ return true;
1272
+ const sources = [];
1273
+ for (const n in query.$tableAliases) {
1274
+ const a = query.$tableAliases[n];
1275
+ if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
1276
+ sources.push( a );
1277
+ }
1278
+ if (alias.kind === '$tablealias')
1279
+ news.push( { chain: [ alias, ...chain ], sources } );
1280
+ else
1281
+ news.push( { chain, sources } );
1282
+ return false;
1283
+ }
1284
+ }
1285
+
1286
+ //--------------------------------------------------------------------------
1287
+ // General resolver functions
1288
+ //--------------------------------------------------------------------------
1289
+
1290
+ // Resolve the type and its arguments if applicable.
1291
+ function resolveTypeExpr( art, user ) {
1292
+ const typeArt = resolveType( art.type, user );
1293
+ if (typeArt) {
1294
+ resolveTypeArgumentsUnchecked( art, typeArt, user );
1295
+ checkTypeArguments( art );
1296
+ }
1297
+ }
1298
+
1299
+ /**
1300
+ * Check the type arguments on `artWithType`.
1301
+ * If the effective type is an array or structured type, an error is emitted.
1302
+ */
1303
+ function checkTypeArguments( artWithType ) {
1304
+ // Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
1305
+ // Also: For enums, it points to the enum type, which is why this trick is needed.
1306
+ // TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
1307
+ // trick may be removed if effectiveType() does not stop at enums.
1308
+ const cyclic = new Set();
1309
+ let effectiveTypeArt = effectiveType( artWithType );
1310
+ while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
1311
+ cyclic.add(effectiveTypeArt);
1312
+ const underlyingEnumType = directType(effectiveTypeArt);
1313
+ if (underlyingEnumType)
1314
+ effectiveTypeArt = effectiveType(underlyingEnumType);
1315
+ else
1316
+ break;
1317
+ }
1318
+
1319
+ if (!effectiveTypeArt)
1320
+ return; // e.g. illegal definition references
1321
+
1322
+ const params = effectiveTypeArt.parameters &&
1323
+ effectiveTypeArt.parameters.map(p => p.name || p) || [];
1324
+
1325
+ for (const param of typeParameters.list) {
1326
+ if (artWithType[param] !== undefined) {
1327
+ if (!params.includes(param)) {
1328
+ // Whether the type ref itself is a builtin or a custom type with a builtin as base.
1329
+ const type = directType(artWithType);
1330
+
1331
+ let variant;
1332
+ if (type.builtin)
1333
+ // `.type` is already a builtin: use a nicer message.
1334
+ variant = 'builtin';
1335
+ else if (effectiveTypeArt.builtin)
1336
+ // base type is a builtin, i.e. a scalar
1337
+ variant = 'type';
1338
+ else
1339
+ // effectiveType is not a builtin -> array or structured
1340
+ variant = 'non-scalar';
1341
+
1342
+ error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1343
+ '#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
1344
+ });
1345
+ break; // Avoid spam: Only emit the first error.
1346
+ }
1347
+ else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
1348
+ error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
1349
+ '#': 'incorrect-type',
1350
+ prop: param,
1351
+ code: artWithType[param].literal,
1352
+ names: typeParameters.expectedLiteralsFor[param],
1353
+ });
1354
+ break; // Avoid spam: Only emit the first error.
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ function resolveExpr( expr, expected, user, extDict, expandOrInline) {
1361
+ // TODO: when we have rewritten the resolvePath functions,
1362
+ // define a traverseExpr() in ./utils.js
1363
+ // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
1364
+ if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
1365
+ return;
1366
+ if (Array.isArray(expr)) {
1367
+ expr.forEach( e => resolveExpr( e, expected, user, extDict ) );
1368
+ return;
1369
+ }
1370
+
1371
+ if (expr.type) // e.g. cast( a as Integer )
1372
+ resolveTypeExpr( expr, user );
1373
+
1374
+ if (expr.path) {
1375
+ if (expr.$expected === 'exists') {
1376
+ error( 'expr-unexpected-exists', [ expr.location, user ], {},
1377
+ 'An EXISTS predicate is not expected here' );
1378
+ // We complain about the EXISTS before, as EXISTS subquery is also not
1379
+ // supported (avoid that word if you do not want to get tickets when it
1380
+ // will be supported), TODO: location of EXISTS
1381
+ expr.$expected = 'approved-exists'; // only complain once
1382
+ }
1383
+ if (expected instanceof Function) {
1384
+ expected( expr, user, extDict );
1385
+ return;
1386
+ }
1387
+ resolvePath( expr, expected, user, extDict );
1388
+
1389
+ const last = !expandOrInline && expr.path[expr.path.length - 1];
1390
+ for (const step of expr.path) {
1391
+ if (step && (step.args || step.where || step.cardinality) &&
1392
+ step._artifact && !Array.isArray( step._artifact ) )
1393
+ resolveParamsAndWhere( step, expected, user, extDict, step === last );
1394
+ }
1395
+ }
1396
+ else if (expr.query) {
1397
+ const { query } = expr;
1398
+ if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
1399
+ // traverseQueryPost( query, false, resolveQuery );
1400
+ }
1401
+ else {
1402
+ error( 'expr-no-subquery', [ expr.location, user ], {},
1403
+ 'Subqueries are not supported here' );
1404
+ }
1405
+ }
1406
+ else if (expr.op && expr.args) {
1407
+ const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
1408
+ args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
1409
+ }
1410
+ if (expr.suffix && isDeprecatedEnabled( options )) {
1411
+ const { location } = expr.suffix[0] || expr;
1412
+ error( null, [ location, user ], { prop: 'deprecated' },
1413
+ 'Window functions are not supported if $(PROP) options are set' );
1414
+ }
1415
+ if (expr.suffix)
1416
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
1417
+ }
1418
+
1419
+ function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
1420
+ const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
1421
+ const type = alias || effectiveType( step._artifact );
1422
+ const art = (type && type.target) ? type.target._artifact : type;
1423
+ if (!art)
1424
+ return;
1425
+ const entity = (art.kind === 'entity') &&
1426
+ (!isLast || [ 'from', 'exists', 'approved-exists' ].includes( expected )) && art;
1427
+ if (step.args)
1428
+ resolveParams( step.args, art, entity, expected, user, extDict, step.location );
1429
+ if (entity) {
1430
+ if (step.where)
1431
+ resolveExpr( step.where, 'filter', user, environment( type ) );
1432
+ }
1433
+ else if (step.where && step.where.location || step.cardinality ) {
1434
+ const location = combinedLocation( step.where, step.cardinality );
1435
+ // XSN TODO: filter$location including […]
1436
+ message( 'expr-no-filter', [ location, user ], { '#': expected },
1437
+ {
1438
+ std: 'A filter can only be provided when navigating along associations',
1439
+ from: 'A filter can only be provided for the source entity or associations',
1440
+ } );
1441
+ }
1442
+ }
1443
+
1444
+ function resolveParams( dict, art, entity, expected, user, extDict, stepLocation ) {
1445
+ if (!entity || !entity.params) {
1446
+ let first = dict[Object.keys(dict)[0]];
1447
+ if (Array.isArray(first))
1448
+ first = first[0];
1449
+ message( 'args-no-params',
1450
+ [ dict[$location] ||
1451
+ dictLocation( dict, first && first.name && first.name.location || stepLocation),
1452
+ user ],
1453
+ { art, '#': (entity ? 'entity' : expected ) },
1454
+ {
1455
+ std: 'Parameters can only be provided when navigating along associations',
1456
+ from: 'Parameters can only be provided for the source entity or associations',
1457
+ // or extra message id for entity?
1458
+ entity: 'Entity $(ART) has no parameters',
1459
+ } );
1460
+ return;
1461
+ }
1462
+ const exp = (expected === 'from') ? 'expr' : expected;
1463
+ if (Array.isArray(dict)) {
1464
+ message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
1465
+ 'Named parameters must be provided for the entity' );
1466
+ for (const a of dict)
1467
+ resolveExpr( a, exp, user, extDict );
1468
+ return;
1469
+ }
1470
+ // TODO: allow to specify expected for arguments in in specExpected
1471
+ for (const name in dict) {
1472
+ const param = art.params[name];
1473
+ const arg = dict[name];
1474
+ for (const a of Array.isArray(arg) ? arg : [ arg ]) {
1475
+ setArtifactLink( a.name, param );
1476
+ if (!param) {
1477
+ message( 'args-undefined-param', [ a.name.location, user ], { art, id: name },
1478
+ 'Entity $(ART) has no parameter $(ID)' );
1479
+ }
1480
+ resolveExpr( a, exp, user, extDict );
1481
+ }
1482
+ }
1483
+ }
1484
+ }
1485
+
1486
+ // Return condensed info about reference in select item
1487
+ // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1488
+ // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1489
+ // - mixinElem -> { navigation: mixinElement, item: path[0] }
1490
+ // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1491
+ // - $self -> { item: undefined, tableAlias: $self }
1492
+ // - $parameters.P, :P -> {}
1493
+ // - $now, current_date -> {}
1494
+ // - undef, redef -> {}
1495
+ // With 'navigation': store that navigation._artifact is projected
1496
+ // With 'navigation': rewrite its ON condition
1497
+ // With navigation: Do KEY propagation
1498
+ //
1499
+ // TODO: re-think this function, copied in populate.js and tweak-assocs.js
1500
+ function pathNavigation( ref ) {
1501
+ // currently, indirectly projectable elements are not included - we might
1502
+ // keep it this way! If we want them to be included - be aware: cycles
1503
+ if (!ref._artifact)
1504
+ return {};
1505
+ let item = ref.path && ref.path[0];
1506
+ const root = item && item._navigation;
1507
+ if (!root)
1508
+ return {};
1509
+ if (root.kind === '$navElement')
1510
+ return { navigation: root, item, tableAlias: root._parent };
1511
+ if (root.kind === 'mixin')
1512
+ return { navigation: root, item };
1513
+ item = ref.path[1];
1514
+ if (root.kind === '$self')
1515
+ return { item, tableAlias: root };
1516
+ if (root.kind !== '$tableAlias' || ref.path.length < 2)
1517
+ return {}; // should not happen
1518
+ return { navigation: root.elements[item.id], item, tableAlias: root };
1519
+ }
1520
+
1521
+ module.exports = resolve;