@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,1151 @@
1
+ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN
2
+
3
+ // AST-like CSN looks as follows:
4
+ // { kind: 'source', env: <dictionary of artifact defs>, namespace: {}, ... }
5
+ //
6
+ // The property `artifacts` of a source contains the top-level definitions.
7
+ // Definitions inside a context are not listed here (as opposed to
8
+ // `definitions`, see below), but inside the property `artifacts` of that context.
9
+
10
+ // The define phase (function 'define' below) enriches a dictionary of
11
+ // (file names to) AST-like CSNs and restructure them a little bit, the result
12
+ // is called "augmented CSN":
13
+ // { sources: <dictionary of ASTs>, definitions: <dictionary of artifact defs> }
14
+ //
15
+ // The property `sources` is the input argument (dictionary of source ASTs).
16
+ //
17
+ // The property `definitions` is set by this compiler phase. It contains the
18
+ // definitions of all main artifacts (i.e. not elements) from all sources, the
19
+ // key is the absolute name of that artifact. These definitions are the same
20
+ // objects as the definitions accessible via `sources` and `artifacts` of the
21
+ // corresponding source/context.
22
+ //
23
+ // Because different sources could define artifacts with the same absolute
24
+ // name, this compiler phase also put a property `messages` to the resulting
25
+ // model, which is a vector of messages for the redefinitions. (Using the same
26
+ // name for different definitions in one source is already recognized during
27
+ // parsing.)
28
+ //
29
+ // You get the compact "official" CSN format by applying the function exported
30
+ // by "../json/to-csn.js" to the augmented CSN.
31
+
32
+ // Example 'file.cds':
33
+ // namespace A;
34
+ // context B {
35
+ // type C { elem: String(4); }
36
+ // }
37
+ // Check the augmented CSN by compiling it with
38
+ // cdsc --raw-output + file.cds
39
+ //
40
+ // ┌───────────────┐ ┌───────────────────────────────────────────┐
41
+ // │ sources │ │ definitions │
42
+ // └──┬────────────┘ └──┬────────────────────────────┬───────────┘
43
+ // │ │ │
44
+ // │ ['file.cds'] │ ['A.B'] │ ['A.B.C']
45
+ // ↓ ↓ ↓
46
+ // ┌───────────────┐ _parent ┌────────────────┐ _parent ┌──────────────┐
47
+ // │ kind:'source' │←──────────┤ kind:'context' │←──────────┤ kind: 'type' │
48
+ // │ artifacts: ───┼──────────→│ artifacts: ────┼──────────→│ ... │
49
+ // └───────────────┘ ['B'] └────────────────┘ ['C'] └──────────────┘
50
+ //
51
+ // The _parent properties are not shown in the JSON - they are used for name
52
+ // resolution, see file './resolver.js'.
53
+
54
+ // An artifact definition looks as follows (example: context "A.B" above):
55
+ // {
56
+ // kind: 'context',
57
+ // name: { id: 'B', absolute: 'A.B', location: { <for the id "B"> } },
58
+ // artifacts: <for contexts, a dictionary of artifacts defined within>,
59
+ // location: { <of the complete artifact definition> } },
60
+ // _parent: <the parent artifact, here the source 'file.cds'>
61
+ // }
62
+ // The properties `name.absolute`, `name.component` and `_parent` are set
63
+ // during this compiler phase.
64
+
65
+ // The definition of an entity or a structured type would contain an `elements`
66
+ // property instead of an `artifacts` property.
67
+
68
+ // An element definition looks as follows (example: "elem" above):
69
+ // {
70
+ // kind: 'element',
71
+ // name: { id: 'elem', component: 'elem', location: { <for the id "elem"> } }
72
+ // type: { path: [ { id: 'String', location: ... } ] },
73
+ // $typeArgs: [ { number: '4', location: ... } ]
74
+ // location: { <of the complete element definition> } },
75
+ // _parent: <the parent artifact, here the type "A.B.C">
76
+ // }
77
+ // References are resolved in the "resolve" phase of the compiler, see
78
+ // './resolver.js'. We then get the properties `type.absolute` and `length`.
79
+
80
+ // Sub phase 1 (addXYZ) - only for main artifacts
81
+ // - set _block links
82
+ // - store definitions (including context extensions), NO duplicate check
83
+ // - artifact name check
84
+ // - Note: the only allow name resolving is resolveUncheckedPath(),
85
+ // TODO: make sure that _no_ _artifact link is set
86
+ // - POST: all user-written definitions are in model.definitions
87
+
88
+ // Sub Phase 2 (initXYZ)
89
+ // - set _parent, _main (later: _service?) links, and _block links of members
90
+ // - add _subArtifacts dictionary and "namespace artifacts" for name resolution
91
+ // - duplicate checks
92
+ // - structure checks ?
93
+ // - annotation assignments
94
+ // - POST: resolvePath() can be called for artifact references (if complete model)
95
+
96
+ // More sub phases...
97
+
98
+ // The main difficulty is the correct behavior concerning duplicate definitions
99
+ // - We need a unique object for the _subArtifacts dictionary.
100
+ // - We must have a property at the artifact whether there are duplicates in order
101
+ // to avoid consequential or repeated errors.
102
+ // - But: The same artifact is added to multiple dictionaries.
103
+ // - Solution part 1: $duplicates as property of the artifact or member
104
+ // for `definitions`, `_artifacts`, member dictionaries, `vocabulary`
105
+ // dictionary of the whole model, `$tableAliases` dictionary of queries.
106
+ // - Solution part 2: array value in dictionary for duplicates in CDL `artifacts`
107
+ // dictionary, `_combined` dictionary for query search, `$tableAliases`
108
+ // of JOIN restrictions, `vocabulary` dictionary of a CDL input source.
109
+
110
+ 'use strict';
111
+
112
+ const { isDeprecatedEnabled, forEachGeneric, forEachInOrder } = require('../base/model');
113
+ const {
114
+ dictAdd, dictAddArray, dictForEach, pushToDict,
115
+ } = require('../base/dictionaries');
116
+ const { kindProperties, dictKinds } = require('./base');
117
+ const {
118
+ setLink,
119
+ setMemberParent,
120
+ storeExtension,
121
+ dependsOnSilent,
122
+ pathName,
123
+ splitIntoPath,
124
+ } = require('./utils');
125
+ const { compareLayer } = require('./moduleLayers');
126
+ const { initBuiltins, isInReservedNamespace } = require('./builtins');
127
+
128
+ const $location = Symbol.for('cds.$location');
129
+
130
+ /**
131
+ * Export function of this file. Transform argument `sources` = dictionary of
132
+ * AST-like CSNs into augmented CSN. If a vector is provided for argument
133
+ * `messages` (usually the combined messages from `parse` for all sources), do
134
+ * not throw an exception in case of an error, but push the corresponding error
135
+ * object to that vector. If at least one AST does not exist due to a parse
136
+ * error, set property `lintMode` of `options` to `true`. Then, the resolver
137
+ * does not report errors for using directives pointing to non-existing
138
+ * artifacts.
139
+ *
140
+ * @param {XSN.Model} model Model with `sources` property that contain AST-like CSNs.
141
+ */
142
+ function define( model ) {
143
+ const { options } = model;
144
+ // Get simplified "resolve" functionality and the message function:
145
+ const {
146
+ error, warning, info, messages,
147
+ } = model.$messageFunctions;
148
+ const {
149
+ resolveUncheckedPath,
150
+ defineAnnotations,
151
+ } = model.$functions;
152
+
153
+ const extensionsDict = Object.create(null);
154
+ Object.assign( model.$functions, {
155
+ initArtifact,
156
+ initMembers,
157
+ extensionsDict, // a dictionary - TODO: remove
158
+ checkDefinitions,
159
+ } );
160
+ // During the definer, we can only resolve artifact references, i.e,
161
+ // after a `.`, we only search in the `_subArtifacts` dictionary:
162
+ model.$volatileFunctions.environment = function artifactsEnv( art ) {
163
+ return art._subArtifacts || Object.create(null);
164
+ };
165
+
166
+ return doDefine();
167
+
168
+ /**
169
+ * Main function of the definer.
170
+ */
171
+ function doDefine() {
172
+ if (options.deprecated &&
173
+ messages.every( m => m.messageId !== 'api-deprecated-option' )) {
174
+ warning( 'api-deprecated-option', {},
175
+ { prop: 'deprecated', '#': (options.beta ? 'beta' : 'std') }, {
176
+ // TODO: make the text scarier in future versions
177
+ std: 'With option $(PROP), many newer features are disabled',
178
+ // eslint-disable-next-line max-len
179
+ beta: 'With option $(PROP), beta features and many other newer features are disabled',
180
+ } );
181
+ }
182
+ model.definitions = Object.create(null);
183
+ setLink( model, '_entities', [] ); // for entities with includes
184
+ model.$entity = 0;
185
+ model.$compositionTargets = Object.create(null);
186
+ model.$lateExtensions = Object.create(null); // for generated artifacts
187
+
188
+ initBuiltins( model );
189
+ for (const name in model.sources)
190
+ addSource( model.sources[name] );
191
+ for (const name in model.sources)
192
+ initNamespaceAndUsing( model.sources[name] );
193
+ dictForEach( model.definitions, initArtifact );
194
+ dictForEach( model.vocabularies, initVocabulary );
195
+
196
+ mergeI18nBlocks( model );
197
+ }
198
+
199
+ // Phase 1: ----------------------------------------------------------------
200
+
201
+ /**
202
+ * Add definitions of the given source AST, both CDL and CSN
203
+ *
204
+ * @param {XSN.AST} src
205
+ */
206
+ function addSource( src ) {
207
+ // handle sub model from parser
208
+ if (!src.kind)
209
+ src.kind = 'source';
210
+
211
+ let namespace = src.namespace && src.namespace.path;
212
+ let prefix = namespace ? `${ pathName( namespace ) }.` : '';
213
+ if (isInReservedNamespace(prefix)) {
214
+ error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], {},
215
+ // TODO: use $(NAME)
216
+ 'The namespace "cds" is reserved for CDS builtins' );
217
+ namespace = null;
218
+ }
219
+ if (src.$frontend !== 'json') { // CDL input
220
+ // TODO: set _block to builtin
221
+ if (src.artifacts)
222
+ addPathPrefixes( src.artifacts, prefix ); // before addUsing
223
+ else if (src.usings || src.namespace)
224
+ src.artifacts = Object.create(null);
225
+ if (src.usings)
226
+ src.usings.forEach( u => addUsing( u, src ) );
227
+ if (namespace)
228
+ addNamespace( namespace, src );
229
+ if (src.artifacts) // addArtifact needs usings for context extensions
230
+ dictForEach( src.artifacts, a => addArtifact( a, src, prefix ) );
231
+ }
232
+ else if (src.definitions) { // CSN input
233
+ prefix = '';
234
+ dictForEach( src.definitions, v => addDefinition( v, src ) );
235
+ }
236
+ if (src.vocabularies) {
237
+ if (!model.vocabularies)
238
+ model.vocabularies = Object.create(null);
239
+ dictForEach( src.vocabularies, v => addVocabulary( v, src, prefix ) );
240
+ }
241
+ if (src.extensions) { // requires using to be known!
242
+ src.extensions.forEach( e => addExtension( e, src ) );
243
+ }
244
+ }
245
+
246
+ function addDefinition( art, block ) {
247
+ const { absolute } = art.name;
248
+ // TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
249
+ if (absolute === 'cds' || isInReservedNamespace(absolute)) {
250
+ error( 'reserved-namespace-cds', [ art.name.location, art ], {},
251
+ // TODO: use $(NAME)
252
+ 'The namespace "cds" is reserved for CDS builtins' );
253
+ const builtin = model.definitions[absolute];
254
+ if (builtin && builtin.builtin) // if already a builtin...
255
+ return;
256
+ // otherwise we just define it...
257
+ }
258
+ else if (art.query && (absolute === 'localized' || absolute.startsWith( 'localized.' ))) {
259
+ // Due to recompilation, we don't emit this info message for JSON frontend.
260
+ // TODO: generalize this for $generated (definitions starting with
261
+ // "localized" just have `$generated: true` as default)
262
+ if (block.$frontend !== 'json') {
263
+ info( 'ignored-localized-definition', [ art.name.location, art ], {},
264
+ 'This definition in the namespace "localized" is ignored' );
265
+ }
266
+ return;
267
+ }
268
+ setLink( art, '_block', block );
269
+ // dictAdd might set $duplicates to true
270
+ dictAdd( model.definitions, absolute, art );
271
+ }
272
+
273
+ // If 'A.B.C' is in 'artifacts', also add 'A' for name resolution
274
+ function addPathPrefixes( artifacts, prefix ) {
275
+ for (const name in artifacts) {
276
+ const d = artifacts[name];
277
+ const a = Array.isArray(d) ? d[0] : d;
278
+ if (!a.name.absolute)
279
+ a.name.absolute = prefix + name;
280
+ const index = name.indexOf( '.' );
281
+ if (index < 0)
282
+ continue; // also for newly added (i.e. does not matter whether visited or not)
283
+ const id = name.substring( 0, index );
284
+ if (artifacts[id])
285
+ continue;
286
+ // TODO: enable optional locations
287
+ const location = a.name.path && a.name.path[0].location || a.location;
288
+ const absolute = prefix + id;
289
+ artifacts[id] = {
290
+ kind: 'using', // !, not namespace - we do not know artifact yet
291
+ name: {
292
+ id, absolute, location, $inferred: 'as',
293
+ },
294
+ // TODO: use global ref (in general - all uses of splitIntoPath)
295
+ extern: { path: splitIntoPath( location, absolute ), location },
296
+ location,
297
+ $inferred: 'path-prefix',
298
+ };
299
+ }
300
+ }
301
+
302
+
303
+ /**
304
+ * Add the names of a USING declaration to the top-level search environment
305
+ * of the source, and set the absolute name referred by the USING
306
+ * declaration.
307
+ *
308
+ * @param {XSN.Using} decl Node to be expanded and added to `src`
309
+ * @param {XSN.AST} src
310
+ */
311
+ function addUsing( decl, src ) {
312
+ if (decl.usings) {
313
+ // e.g. `using {a,b} from 'file.cds'` -> recursive
314
+ decl.usings.forEach( u => addUsing( u, src ) );
315
+ return;
316
+ }
317
+ const { path } = decl.extern;
318
+ if (path.broken || !path[0]) // syntax error
319
+ return;
320
+ if (!decl.name)
321
+ decl.name = { ...path[path.length - 1], $inferred: 'as' };
322
+ decl.name.absolute = pathName( path );
323
+ const name = decl.name.id;
324
+ // TODO: check name: no "."
325
+ const found = src.artifacts[name];
326
+ if (found && found.$inferred === 'path-prefix' &&
327
+ found.name.absolute === decl.name.absolute)
328
+ src.artifacts[name] = decl;
329
+ else
330
+ dictAddArray( src.artifacts, name, decl );
331
+ }
332
+
333
+ function addNamespace( path, src ) {
334
+ const absolute = pathName( path );
335
+ if (path.broken) // parsing may have failed
336
+ return;
337
+ // create using for own namespace:
338
+ const last = path[path.length - 1];
339
+ const { id } = last;
340
+ if (src.artifacts[id] || last.id.includes('.'))
341
+ // not used as we have a definition/using with that name, or dotted last path id
342
+ return;
343
+ src.artifacts[id] = {
344
+ kind: 'using',
345
+ name: {
346
+ id, absolute, location: last.location, $inferred: 'as',
347
+ },
348
+ extern: src.namespace,
349
+ location: src.namespace.location,
350
+ $inferred: 'namespace',
351
+ };
352
+ }
353
+ function addArtifact( art, block, prefix ) {
354
+ if (art.kind === 'using')
355
+ return;
356
+ art.name.absolute = prefix + pathName( art.name.path );
357
+ addDefinition( art, block );
358
+ if (art.artifacts) {
359
+ const p = `${ art.name.absolute }.`;
360
+ // path prefixes (usings) must be added before extensions in artifacts:
361
+ addPathPrefixes( art.artifacts, p );
362
+ dictForEach( art.artifacts, a => addArtifact( a, art, p ) );
363
+ }
364
+ if (art.extensions) { // requires using to be known!
365
+ art.extensions.forEach( e => e.name && addExtension( e, art ) );
366
+ }
367
+ }
368
+
369
+ function addExtension( ext, block ) {
370
+ setLink( ext, '_block', block );
371
+ const absolute = ext.name && resolveUncheckedPath( ext.name, 'extend', ext );
372
+ if (!absolute) // broken path
373
+ return;
374
+ delete ext.name.path[0]._artifact; // might point to wrong JS object in phase 1
375
+ ext.name.absolute = absolute; // definition might not be there yet, no _artifact link
376
+ pushToDict( extensionsDict, absolute, ext );
377
+ if (!ext.artifacts)
378
+ return;
379
+ // Directly add the artifacts of context and service extension:
380
+ if (!model.$blocks)
381
+ model.$blocks = Object.create( null );
382
+ // Set block number for debugging (--raw-output):
383
+ // eslint-disable-next-line no-multi-assign
384
+ ext.name.select = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
385
+ const prefix = `${ absolute }.`;
386
+ dictForEach( ext.artifacts, a => addArtifact( a, ext, prefix ) );
387
+ }
388
+
389
+ function addVocabulary( vocab, block, prefix ) {
390
+ setLink( vocab, '_block', block );
391
+ const { name } = vocab;
392
+ if (!name.absolute)
393
+ name.absolute = prefix + name.path.map( id => id.id ).join('.');
394
+ dictAdd( model.vocabularies, name.absolute, vocab );
395
+ }
396
+
397
+ // Phase 2 ("init") --------------------------------------------------------
398
+
399
+ function checkRedefinition( art ) {
400
+ if (!art.$duplicates)
401
+ return;
402
+ if (art._main) {
403
+ error( 'duplicate-definition', [ art.name.location, art ], {
404
+ name: art.name.id,
405
+ '#': kindProperties[art.kind].normalized || art.kind,
406
+ } );
407
+ }
408
+ else {
409
+ error( 'duplicate-definition', [ art.name.location, art ], {
410
+ name: art.name.absolute,
411
+ '#': (art.kind === 'annotation' ? 'annotation' : 'absolute' ),
412
+ } );
413
+ }
414
+ }
415
+
416
+ function initNamespaceAndUsing( src ) {
417
+ if (src.namespace) {
418
+ const decl = src.namespace;
419
+ const { path } = decl;
420
+ if (path.broken) // parsing may have failed
421
+ return;
422
+ const { id } = path[path.length - 1];
423
+ const absolute = pathName( path );
424
+ if (!model.definitions[absolute]) {
425
+ // TODO: do we really need this namespace entry - try without (msg change)
426
+ const location = path.location || decl.location;
427
+ // TODO: make it possible to have no location
428
+ const ns = { kind: 'namespace', name: { absolute, location }, location };
429
+ model.definitions[absolute] = ns;
430
+ initParentLink( ns, model.definitions );
431
+ }
432
+ const builtin = model.$builtins[id];
433
+ if (builtin && !builtin.internal &&
434
+ src.artifacts[id] && src.artifacts[id].extern === decl) {
435
+ warning( 'ref-shadowed-builtin', [ decl.location, null ], // no home artifact
436
+ { id, art: absolute, code: `using ${ builtin.name.absolute };` },
437
+ '$(ID) now refers to $(ART) - consider $(CODE)' );
438
+ }
439
+ // setArtifactLink( decl, model.definitions[absolute] ); // TODO: necessary?
440
+ }
441
+ if (!src.usings)
442
+ return;
443
+ for (const name in src.artifacts) {
444
+ const entry = src.artifacts[name];
445
+ if (!Array.isArray(entry)) // no local name duplicate
446
+ continue;
447
+ for (const decl of entry) {
448
+ if (!decl.$duplicates) { // do not have two duplicate messages
449
+ error( 'duplicate-using', [ decl.name.location, null ], { name }, // TODO: semantic
450
+ 'Duplicate definition of top-level name $(NAME)' );
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ function initArtifact( art, reInit = false ) {
457
+ if (!reInit)
458
+ initParentLink( art, model.definitions );
459
+ const block = art._block;
460
+ checkRedefinition( art );
461
+ defineAnnotations( art, art, block );
462
+ initMembers( art, art, block );
463
+ initDollarSelf( art ); // $self
464
+ if (art.params)
465
+ initParams( art ); // $parameters
466
+ if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
467
+ extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"
468
+
469
+ if (!art.query)
470
+ return;
471
+ art.$queries = [];
472
+ setLink( art, '_from', [] ); // for sequence of resolve steps
473
+ if (!setLink( art, '_leadingQuery', initQueryExpression( art.query, art ) ) )
474
+ return; // null or undefined in case of parse error
475
+ setLink( art._leadingQuery, '_$next', art );
476
+ // the following we be removed soon if we have:
477
+ // view elements as proxies to elements of leading query
478
+ if (art.elements) { // specified element via compilation of client-style CSN
479
+ setLink( art, 'elements$', art.elements );
480
+ delete art.elements;
481
+ }
482
+ }
483
+
484
+ function initVocabulary( art ) {
485
+ initParentLink( art, model.vocabularies );
486
+ checkRedefinition( art );
487
+ const block = art._block;
488
+ defineAnnotations( art, art, block );
489
+ initMembers( art, art, block );
490
+ }
491
+
492
+ function initParentLink( art, definitions ) {
493
+ setLink( art, '_parent', null );
494
+ const { absolute } = art.name;
495
+ const dot = absolute.lastIndexOf('.');
496
+ if (dot < 0)
497
+ return;
498
+ art.name.id = absolute.substring( dot + 1 ); // XSN TODO: remove name.id for artifacts
499
+ const prefix = absolute.substring( 0, dot );
500
+ let parent = definitions[prefix];
501
+ if (!parent) {
502
+ const { location } = art.name; // TODO: make it possible to have no location
503
+ parent = { kind: 'namespace', name: { absolute: prefix, location }, location };
504
+ definitions[prefix] = parent;
505
+ initParentLink( parent, definitions );
506
+ }
507
+ if (art.kind !== 'namespace' &&
508
+ isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
509
+ let p = parent;
510
+ while (p && kindProperties[p.kind].artifacts)
511
+ p = p._parent;
512
+ if (p) {
513
+ error( 'subartifacts-not-supported', [ art.name.location, art ],
514
+ { art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
515
+ // eslint-disable-next-line max-len
516
+ 'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
517
+ }
518
+ }
519
+ setLink( art, '_parent', parent );
520
+ if (!parent._subArtifacts)
521
+ setLink( parent, '_subArtifacts', Object.create(null) );
522
+ if (art.$duplicates !== true) // no redef or "first def"
523
+ parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
524
+ }
525
+
526
+ // From here til EOF, reexamine code ---------------------------------------
527
+ // See populate:
528
+ // - userQuery() or _query property?
529
+ // - initFromColumns()
530
+ // - ensureColumnName()
531
+
532
+ function initDollarSelf( art ) {
533
+ const selfname = '$self';
534
+ // TODO: use setMemberParent() ?
535
+ const name = art.name || art._outer.name;
536
+ const self = {
537
+ name: { id: selfname, alias: selfname, absolute: name.absolute },
538
+ kind: '$self',
539
+ location: art.location,
540
+ };
541
+ if (name.element) // $self for anonymous aspect
542
+ self.name.element = name.element;
543
+ setLink( self, '_parent', art );
544
+ setLink( self, '_main', art ); // used on main artifact
545
+ setLink( self, '_origin', art );
546
+ art.$tableAliases = Object.create(null);
547
+ art.$tableAliases[selfname] = self;
548
+ setLink( art, '_$next', model.$magicVariables );
549
+ }
550
+
551
+ function initParams( art ) {
552
+ // TODO: use setMemberParent() ?
553
+ const parameters = {
554
+ name: { id: '$parameters', param: '$parameters', absolute: art.name.absolute },
555
+ kind: '$parameters',
556
+ location: art.location,
557
+ };
558
+ setLink( parameters, '_parent', art );
559
+ setLink( parameters, '_main', art );
560
+ // Search for :const after :param. If there will be a possibility in the
561
+ // future that we can extend <query>.columns, we must be sure to use
562
+ // _block of that new column after :param (or just allow $parameters there).
563
+ setLink( parameters, '_block', art._block );
564
+ if (art.params) {
565
+ parameters.elements = art.params;
566
+ parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
567
+ }
568
+ art.$tableAliases.$parameters = parameters;
569
+ }
570
+
571
+ function initSubQuery( query ) {
572
+ if (query.on)
573
+ initExprForQuery( query.on, query );
574
+ // TODO: MIXIN with name = ...subquery (not yet supported anyway)
575
+ initSelectItems( query, query.columns, query );
576
+ if (query.where)
577
+ initExprForQuery( query.where, query );
578
+ if (query.having)
579
+ initExprForQuery( query.having, query );
580
+ initMembers( query, query, query._block );
581
+ }
582
+
583
+ function initSelectItems( parent, columns, user ) {
584
+ // TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
585
+ let wildcard = null;
586
+ let hasItems = false;
587
+ for (const col of columns || parent.expand || parent.inline || []) {
588
+ if (!col) // parse error
589
+ continue;
590
+ hasItems = true;
591
+ if (!columns) {
592
+ if (parent.value)
593
+ setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline
594
+ else if (parent._pathHead)
595
+ setLink( col, '_pathHead', parent._pathHead );
596
+ }
597
+ if (col.val === '*') {
598
+ if (!wildcard) {
599
+ wildcard = col;
600
+ }
601
+ else {
602
+ // a late syntax error (this code also runs with parse-cdl), i.e.
603
+ // no semantic loc (wouldn't be available for expand/inline anyway)
604
+ error( 'syntax-duplicate-clause', [ col.location, null ],
605
+ { prop: '*', line: wildcard.location.line, col: wildcard.location.col },
606
+ 'You have provided a $(PROP) already at line $(LINE), column $(COL)' );
607
+ // TODO: extra text variants for expand/inline? - probably not
608
+ col.val = null; // do not consider it for expandWildcard()
609
+ }
610
+ }
611
+ // Either expression (value), expand or new association (target && type)
612
+ else if (col.value || col.expand || (col.target && col.type)) {
613
+ setLink( col, '_block', parent._block );
614
+ defineAnnotations( col, col, parent._block ); // TODO: complain with inline
615
+ // TODO: allow sub queries? at least in top-level expand without parallel ref
616
+ if (columns)
617
+ initExprForQuery( col.value, parent );
618
+ initSelectItems( col, null, user );
619
+ }
620
+ }
621
+
622
+ if (hasItems && !wildcard && parent.excludingDict) {
623
+ // TODO: Better way to get source file?
624
+ let block = parent;
625
+ while (block._block)
626
+ block = block._block;
627
+
628
+ if (block.$frontend === 'cdl') {
629
+ warning('query-ignoring-exclude', [ parent.excludingDict[$location], user ],
630
+ { prop: '*' },
631
+ 'Excluding elements without wildcard $(PROP) has no effect');
632
+ }
633
+ }
634
+ }
635
+
636
+ function initExprForQuery( expr, query ) {
637
+ if (Array.isArray(expr)) { // TODO: old-style $parens ?
638
+ expr.forEach( e => initExprForQuery( e, query ) );
639
+ }
640
+ else if (!expr) {
641
+ return;
642
+ }
643
+ else if (expr.query) {
644
+ initQueryExpression( expr.query, query );
645
+ }
646
+ else if (expr.args) {
647
+ const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
648
+ args.forEach( e => initExprForQuery( e, query ) );
649
+ }
650
+ else if (expr.path && expr.$expected === 'exists') {
651
+ expr.$expected = 'approved-exists';
652
+ approveExistsInChildren(expr);
653
+ }
654
+ }
655
+
656
+ /**
657
+ * If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
658
+ * since we will have a top-level subquery after exists-processing in the forHanaNew.
659
+ *
660
+ * Recursively drill down into:
661
+ * - the .path
662
+ * - the .args
663
+ * - the .where.args
664
+ *
665
+ * Any $expected === 'exists' encountered along the way are turned into 'approved-exists'
666
+ *
667
+ * working: exists toE[exists toE] -> select from E where exists toE
668
+ * not working: toE[exists toE] -> we don't support subqueries in filters
669
+ *
670
+ * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
671
+ */
672
+ function approveExistsInChildren(exprOrPathElement) {
673
+ if (exprOrPathElement.$expected === 'exists')
674
+ exprOrPathElement.$expected = 'approved-exists';
675
+ // Drill down
676
+ if (exprOrPathElement.args)
677
+ exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
678
+ else if (exprOrPathElement.where && exprOrPathElement.where.args)
679
+ exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
680
+ else if (exprOrPathElement.path)
681
+ exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
682
+ }
683
+
684
+ // table is table expression in FROM, becomes an alias
685
+ function initTableExpression( table, query, joinParents ) {
686
+ if (!table) // parse error
687
+ return;
688
+ if (table.path) { // path in FROM
689
+ if (!table.path.length || table.path.broken)
690
+ // parse error (e.g. final ',' in FROM), projection on <eof>
691
+ return;
692
+ if (!table.name) {
693
+ const last = table.path[table.path.length - 1];
694
+ const dot = last.id.lastIndexOf('.');
695
+ const id = (dot < 0) ? last.id : last.id.substring( dot + 1 );
696
+ // TODO: if we have too much time, we can calculate the real location with '.'
697
+ table.name = { $inferred: 'as', id, location: last.location };
698
+ }
699
+ addAsAlias();
700
+ // _origin is set when we resolve the ref
701
+ if (query._parent.kind !== 'select')
702
+ query._main._from.push( table ); // store tabref if outside "real" subquery
703
+ }
704
+ else if (table.query) {
705
+ if (!table.name || !table.name.id) {
706
+ error( 'query-req-alias', [ table.location, query ], {}, // TODO: not subquery.location ?
707
+ 'Table alias is required for this subquery' );
708
+ return;
709
+ }
710
+ addAsAlias();
711
+ setLink( table, '_effectiveType', table.query ); // TODO: remove!
712
+ // Store _origin to leading query of table.query for name resolution
713
+ setLink( table, '_origin', initQueryExpression( table.query, table ) );
714
+ }
715
+ else if (table.join) {
716
+ if (table.on) {
717
+ setLink( table, '_$next', query ); // or query._$next?
718
+ setLink( table, '_block', query._block );
719
+ table.kind = '$join';
720
+ table.name = { location: query.location }; // param comes later
721
+ table.$tableAliases = Object.create( null ); // table aliases and mixin definitions
722
+ joinParents = [ ...joinParents, table ];
723
+ }
724
+ if (table.args) {
725
+ table.args.forEach( (tab, index) => {
726
+ // set for A2J such that for every table alias `ta`:
727
+ // ta === (ta._joinParent
728
+ // ? ta._joinParent.args[ta.$joinArgsIndex] // in JOIN
729
+ // : ta._parent.from ) // directly in FROM
730
+ // Note for --raw-output: _joinParent pointing to CROSS JOIN node has not name
731
+ if (!tab) // parse error; time for #6241
732
+ return; // (parser method to only add non-null to array)
733
+ setLink( tab, '_joinParent', table );
734
+ tab.$joinArgsIndex = index;
735
+ initTableExpression( tab, query, joinParents );
736
+ } );
737
+ }
738
+ if (table.on) { // after processing args to get the $tableAliases
739
+ setMemberParent( table, query.name.select, query ); // sets _parent,_main
740
+ initSubQuery( table ); // init sub queries in ON
741
+ const aliases = Object.keys( table.$tableAliases || {} );
742
+ // Use first tabalias name on the right side of the join to name the
743
+ // (internal) query, should only be relevant for --raw-output, not for
744
+ // user messages or references - TODO: correct if join on left?
745
+ table.name.param = aliases[1] || aliases[0] || '<unknown>';
746
+ setLink( table, '_$next', query._$next );
747
+ // TODO: probably set this to query if we switch to name restriction in JOIN
748
+ }
749
+ }
750
+ return;
751
+
752
+ function addAsAlias() {
753
+ table.kind = '$tableAlias';
754
+ setMemberParent( table, table.name.id, query );
755
+ setLink( table, '_block', query._block );
756
+ dictAdd( query.$tableAliases, table.name.id, table, ( name, loc ) => {
757
+ error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
758
+ } );
759
+ // also add to JOIN nodes for name restrictions:
760
+ for (const p of joinParents) {
761
+ // for JOIN alias restriction, we cannot use $duplicates, as it is
762
+ // already used for duplicate aliases of queries:
763
+ dictAddArray( p.$tableAliases, table.name.id, table );
764
+ }
765
+ if (table.name.id[0] === '$') {
766
+ warning( 'syntax-dollar-ident', [ table.name.location, table ], {
767
+ '#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
768
+ name: '$',
769
+ keyword: 'as',
770
+ } );
771
+ }
772
+ }
773
+ }
774
+
775
+ // art is:
776
+ // - entity for top-level queries (including UNION args)
777
+ // - $tableAlias for sub query in FROM - TODO: what about UNION there?
778
+ // - $query for real sub query (in columns, WHERE, ...), again: what about UNION there?
779
+ function initQueryExpression( query, art ) {
780
+ if (!query) // parse error
781
+ return query;
782
+ if (query.from) { // select
783
+ initQuery();
784
+ initTableExpression( query.from, query, [] );
785
+ if (query.mixin)
786
+ addMixin();
787
+ if (!query.$tableAliases.$self) { // same as $projection
788
+ const self = {
789
+ name: { alias: '$self', query: query.name.select, absolute: art.name.absolute },
790
+ kind: '$self',
791
+ location: query.location,
792
+ };
793
+ setLink( self, '_origin', query );
794
+ setLink( self, '_parent', query );
795
+ setLink( self, '_main', query._main );
796
+ setLink( self, '_effectiveType', query ); // TODO: remove
797
+ query.$tableAliases.$self = self;
798
+ query.$tableAliases.$projection = self;
799
+ }
800
+ initSubQuery( query ); // check for SELECT clauses after from / mixin
801
+ }
802
+ else if (query.args) { // UNION, INTERSECT, ..., query in parens
803
+ const leading = initQueryExpression( query.args[0], art );
804
+ for (const q of query.args.slice(1))
805
+ initQueryExpression( q, art );
806
+ setLink( query, '_leadingQuery', leading );
807
+ if (leading && query.orderBy) {
808
+ if (leading.$orderBy)
809
+ leading.$orderBy.push( ...query.orderBy );
810
+ else
811
+ leading.$orderBy = [ ...query.orderBy ];
812
+ }
813
+ // ORDER BY to be evaluated in leading query (LIMIT is literal)
814
+ }
815
+ else { // with parse error (`select from <EOF>`, `select from E { *, ( select }`)
816
+ return undefined;
817
+ }
818
+ return query._leadingQuery || query;
819
+
820
+ function initQuery() {
821
+ const main = art._main || art;
822
+ setLink( query, '_$next',
823
+ // if art is $tableAlias, set to embedding query
824
+ (!art._main || art.kind === 'select' || art.kind === '$join')
825
+ ? art : art._parent ); // TODO: check with name resolution change
826
+ setLink( query, '_block', art._block );
827
+ query.kind = 'select';
828
+ query.name = { location: query.location };
829
+ setMemberParent( query, main.$queries.length + 1, main );
830
+ // console.log(art.kind,art.name,query.name,query._$next.name)
831
+ // if (query.name.query === 1 && query.name.absolute === 'S') throw Error();
832
+ main.$queries.push( query );
833
+ setLink( query, '_parent', art ); // _parent should point to alias/main/query
834
+ query.$tableAliases = Object.create( null ); // table aliases and mixin definitions
835
+ dependsOnSilent( main, query );
836
+ }
837
+
838
+ function addMixin() {
839
+ // TODO: re-check if mixins have already duplicates
840
+ for (const name in query.mixin) {
841
+ const mixin = query.mixin[name];
842
+ if (!(mixin.$duplicates)) {
843
+ setMemberParent( mixin, name, query );
844
+ mixin.name.alias = mixin.name.id;
845
+ setLink( mixin, '_block', art._block );
846
+ // TODO: do some initMembers() ? If people had annotation
847
+ // assignments on the mixin... (also for future mixin definitions
848
+ // with generated values)
849
+ dictAdd( query.$tableAliases, name, query.mixin[name], ( dupName, loc ) => {
850
+ error( 'duplicate-definition', [ loc, query ], { name: dupName, '#': 'alias' } );
851
+ } );
852
+ if (mixin.name.id[0] === '$') {
853
+ warning( 'syntax-dollar-ident', [ mixin.name.location, mixin ],
854
+ { '#': 'mixin', name: '$' } );
855
+ }
856
+ }
857
+ }
858
+ }
859
+ }
860
+
861
+ function isDirectComposition( art ) {
862
+ const type = art.type && art.type.path;
863
+ return type && type[0] && type[0].id === 'cds.Composition';
864
+ }
865
+
866
+ // Return whether the `target` is actually a `targetAspect`
867
+ // TODO: really do that here and not in kick-start.js?
868
+ function targetIsTargetAspect( elem ) {
869
+ const { target } = elem;
870
+ if (target.elements) {
871
+ // TODO: error if CSN has both target.elements and targetAspect.elements -> delete target
872
+ return true;
873
+ }
874
+ if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
875
+ return false;
876
+ const name = resolveUncheckedPath( target, 'compositionTarget', elem );
877
+ const aspect = name && model.definitions[name];
878
+ return aspect && (aspect.kind === 'aspect' || aspect.kind === 'type'); // type is sloppy
879
+ }
880
+
881
+ /**
882
+ * Set property `_parent` for all elements in `parent` to `parent` and do so
883
+ * recursively for all sub elements. Also set the property
884
+ * `name.component` of the element with the help of argument `prefix`
885
+ * (which is basically the component name of the `parent` element plus a dot).
886
+ */
887
+ // If not for extensions: construct === parent
888
+ function initMembers( construct, parent, block, initExtensions = false ) {
889
+ // TODO: split extend from init
890
+ const isQueryExtension = kindProperties[construct.kind].isExtension &&
891
+ (parent._main || parent).query;
892
+ let obj = construct;
893
+ if (obj.items) { // TODO: while
894
+ setLink( obj.items, '_outer', obj );
895
+ obj = obj.items; // TODO: probably set parent = obj (is so in csnRefs)
896
+ setLink( obj, '_block', block );
897
+ }
898
+ if (obj.target && targetIsTargetAspect( obj )) {
899
+ obj.targetAspect = obj.target;
900
+ delete obj.target;
901
+ }
902
+ const { targetAspect } = obj;
903
+ if (targetAspect) {
904
+ if (obj.foreignKeys) {
905
+ error( 'unexpected-keys-for-composition', [ obj.foreignKeys[$location], construct ],
906
+ {},
907
+ 'A managed aspect composition can\'t have a foreign keys specification' );
908
+ delete obj.foreignKeys; // continuation semantics: not specified
909
+ }
910
+ if (obj.on && !obj.target) {
911
+ error( 'unexpected-on-for-composition', [ obj.on.location, construct ],
912
+ {},
913
+ 'A managed aspect composition can\'t have a specified ON condition' );
914
+ delete obj.on; // continuation semantics: not specified
915
+ }
916
+ if (targetAspect.elements) {
917
+ const inEntity = parent._main && parent._main.kind === 'entity';
918
+ // TODO: also allow indirectly (component in component in entity)?
919
+ setLink( targetAspect, '_outer', obj );
920
+ setLink( targetAspect, '_parent', parent._parent );
921
+ setLink( targetAspect, '_main', null ); // for name resolution
922
+
923
+ parent = targetAspect;
924
+ construct = parent; // avoid extension behavior
925
+ targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
926
+ setLink( targetAspect, '_block', block );
927
+ initDollarSelf( targetAspect );
928
+ // allow ref of up_ in anonymous aspect inside entity
929
+ // (TODO: complain if used and the managed composition is included into
930
+ // another entity - might induce auto-redirection):
931
+ if (inEntity && !targetAspect.elements.up_) {
932
+ const up = {
933
+ name: {
934
+ id: 'up_',
935
+ alias: 'up_',
936
+ element: obj.name.element,
937
+ absolute: obj.name.absolute,
938
+ },
939
+ kind: '$navElement',
940
+ location: obj.location,
941
+ };
942
+ setLink( up, '_parent', targetAspect );
943
+ setLink( up, '_main', targetAspect ); // used on main artifact
944
+ // recompilation case: both target and targetAspect → allow up_ in that case, too:
945
+ const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
946
+ const entity = name && model.definitions[name];
947
+ if (entity && entity.elements)
948
+ setLink( up, '_origin', entity.elements.up_ );
949
+ // processAspectComposition/expand() sets _origin to element of
950
+ // generated target entity
951
+ targetAspect.$tableAliases.up_ = up;
952
+ }
953
+ obj = targetAspect;
954
+ }
955
+ }
956
+ if (obj !== parent && obj.elements && parent.enum) {
957
+ // in extensions, extended enums are represented as elements
958
+ for (const n in obj.elements) {
959
+ const e = obj.elements[n];
960
+ if (e.kind === 'element')
961
+ e.kind = 'enum';
962
+ }
963
+ // obj = Object.assign( { enum: obj.elements}, obj );
964
+ // delete obj.elements; // No extra syntax for EXTEND enum
965
+ forEachGeneric( { enum: obj.elements }, 'enum', init );
966
+ }
967
+ else {
968
+ if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
969
+ forEachInOrder( obj, 'elements', init );
970
+ if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
971
+ forEachGeneric( obj, 'enum', init );
972
+ }
973
+
974
+ if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?
975
+ forEachInOrder( obj, 'foreignKeys', init );
976
+ if (checkDefinitions( construct, parent, 'actions' ))
977
+ forEachGeneric( construct, 'actions', init );
978
+ if (checkDefinitions( construct, parent, 'params' ))
979
+ forEachInOrder( construct, 'params', init );
980
+ const { returns } = construct;
981
+ if (returns) {
982
+ returns.kind = (kindProperties[construct.kind].isExtension) ? construct.kind : 'param';
983
+ init( returns, '' ); // '' is special name for returns parameter
984
+ }
985
+ return;
986
+
987
+ function init( elem, name, prop ) {
988
+ if (!elem.kind) // wrong CSN input
989
+ elem.kind = dictKinds[prop];
990
+ if (!elem.name && !elem._outer) {
991
+ const ref = elem.targetElement || elem.kind === 'element' && elem.value;
992
+ if (ref && ref.path) {
993
+ elem.name = Object.assign( { $inferred: 'as' },
994
+ ref.path[ref.path.length - 1] );
995
+ }
996
+ else { // if JSON parser misses to set name
997
+ elem.name = { id: name, location: elem.location };
998
+ }
999
+ }
1000
+ // if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
1001
+ if (kindProperties[elem.kind].isExtension && !initExtensions) {
1002
+ storeExtension( elem, name, prop, parent, block );
1003
+ return;
1004
+ }
1005
+ if (isQueryExtension && elem.kind === 'element') {
1006
+ error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
1007
+ { code: 'extend projection' },
1008
+ 'Use $(CODE) to add select items to the query entity' );
1009
+ return;
1010
+ }
1011
+
1012
+ const bl = elem._block || block;
1013
+ setLink( elem, '_block', bl );
1014
+ setMemberParent( elem, name, parent, construct !== parent && prop );
1015
+ // console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
1016
+ checkRedefinition( elem );
1017
+ defineAnnotations( elem, elem, bl );
1018
+ initMembers( elem, elem, bl, initExtensions );
1019
+
1020
+ // for a correct home path, setMemberParent needed to be called
1021
+
1022
+ if (elem.value && elem.kind === 'element' ) {
1023
+ // For enums in extensions, `elem.kind` is only changed to `enum` for non-parseCdl
1024
+ // mode *and* if the referenced artifact is found.
1025
+ // This means that for non-applicable extensions and parseCdl mode, this check
1026
+ // is not used.
1027
+ //
1028
+ // `parent` is the extended entity/type/enum/... and *not* the "extend: ..."
1029
+ // itself (which is `construct`).
1030
+ if ( parent.kind !== 'select' && parent.kind !== 'extend' ) {
1031
+ error( 'unexpected-val', [ elem.value.location, elem ],
1032
+ { '#': construct.kind },
1033
+ {
1034
+ std: 'Elements can\'t have a value',
1035
+ entity: 'Entity elements can\'t have a value',
1036
+ type: 'Type elements can\'t have a value',
1037
+ extend: 'Cannot extend type/entity elements with values',
1038
+ });
1039
+ return;
1040
+ }
1041
+ }
1042
+
1043
+ if (parent.enum && elem.type) {
1044
+ // already rejected by from-csn, can only happen in extensions
1045
+ error( 'unexpected-type', [ elem.type.location, elem ], {},
1046
+ 'Enum values can\'t have a custom type' );
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
1052
+ // TODO: do differently, see also annotateMembers() in resolver
1053
+ // To have been checked by parsers:
1054
+ // - artifacts (CDL-only anyway) only inside [extend] context|service
1055
+ if (!dict)
1056
+ return false;
1057
+ const names = Object.keys( dict );
1058
+ if (!names.length) // TODO: re-check - really allow empty dict if no other?
1059
+ return false;
1060
+ const feature = kindProperties[parent.kind][prop];
1061
+ if (feature &&
1062
+ (feature === true || construct.kind !== 'extend' || feature( prop, parent )))
1063
+ return true;
1064
+ const location = dict[$location];
1065
+ if (prop === 'actions') {
1066
+ error( 'unexpected-actions', [ location, construct ], {},
1067
+ 'Actions and functions only exist top-level and for entities' );
1068
+ }
1069
+ else if (parent.kind === 'action' || parent.kind === 'function') {
1070
+ error( 'extend-action', [ construct.location, construct ], {},
1071
+ 'Actions and functions can\'t be extended, only annotated' );
1072
+ }
1073
+ else if (prop === 'params') {
1074
+ if (!feature) {
1075
+ // Note: This error can't be triggered at the moment. But as we likely want to
1076
+ // allow extensions with params in the future, we keep the code.
1077
+ error( 'unexpected-params', [ location, construct ], {},
1078
+ 'Parameters only exist for entities, actions or functions' );
1079
+ }
1080
+ else {
1081
+ // remark: we could allow this
1082
+ error( 'extend-with-params', [ location, construct ], {},
1083
+ 'Extending artifacts with parameters is not supported' );
1084
+ }
1085
+ }
1086
+ else if (feature) { // allowed in principle, but not with extend
1087
+ error( 'extend-type', [ location, construct ], {},
1088
+ 'Only structures or enum types can be extended with elements/enums' );
1089
+ }
1090
+ else if (prop === 'elements') {
1091
+ error( 'unexpected-elements', [ location, construct ], {},
1092
+ 'Elements only exist in entities, types or typed constructs' );
1093
+ }
1094
+ else if (prop === 'columns') {
1095
+ error( 'extend-columns', [ location, construct ], { art: construct } );
1096
+ }
1097
+ else { // if (prop === 'enum') {
1098
+ error( 'unexpected-enum', [ location, construct ], {},
1099
+ 'Enum symbols can only be defined for types or typed constructs' );
1100
+ }
1101
+ return construct === parent;
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * Merge (optional) translations into the XSN model.
1107
+ *
1108
+ * @param {XSN.Model} model
1109
+ */
1110
+ function mergeI18nBlocks( model ) {
1111
+ const sortedSources = Object.keys(model.sources)
1112
+ .filter(name => !!model.sources[name].i18n)
1113
+ .sort( (a, b) => compareLayer( model.sources[a], model.sources[b] ) );
1114
+
1115
+ if (sortedSources.length === 0)
1116
+ return;
1117
+
1118
+ if (!model.i18n)
1119
+ model.i18n = Object.create( null );
1120
+
1121
+ for (const name of sortedSources)
1122
+ initI18nFromSource( model.sources[name] );
1123
+
1124
+ /**
1125
+ * Add the source's translations to the model. Warns if the sources translations
1126
+ * do not match the ones from previous sources.
1127
+ *
1128
+ * @param {XSN.AST} src
1129
+ */
1130
+ function initI18nFromSource( src ) {
1131
+ for (const langKey of Object.keys( src.i18n )) {
1132
+ if (!model.i18n[langKey])
1133
+ model.i18n[langKey] = Object.create( null );
1134
+
1135
+ for (const textKey of Object.keys( src.i18n[langKey] )) {
1136
+ const sourceVal = src.i18n[langKey][textKey];
1137
+ const modelVal = model.i18n[langKey][textKey];
1138
+ if (!modelVal) {
1139
+ model.i18n[langKey][textKey] = sourceVal;
1140
+ }
1141
+ else if (modelVal.val !== sourceVal.val) {
1142
+ // TODO: put mergeI18nBlocks() into main function instead
1143
+ model.$messageFunctions.warning( 'i18n-different-value', sourceVal.location,
1144
+ { prop: textKey, otherprop: langKey } );
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+ }
1150
+
1151
+ module.exports = define;