@sap/cds-compiler 2.11.4 → 2.13.8
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.
- package/CHANGELOG.md +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
package/lib/compiler/definer.js
DELETED
|
@@ -1,2340 +0,0 @@
|
|
|
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 artifats
|
|
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 { searchName, weakLocation } = require('../base/messages');
|
|
113
|
-
const {
|
|
114
|
-
isDeprecatedEnabled, isBetaEnabled,
|
|
115
|
-
setProp, forEachGeneric, forEachInOrder,
|
|
116
|
-
forEachDefinition,
|
|
117
|
-
forEachMemberRecursivelyWithQuery,
|
|
118
|
-
} = require('../base/model');
|
|
119
|
-
const {
|
|
120
|
-
dictAdd, dictAddArray, dictForEach, pushToDict,
|
|
121
|
-
} = require('../base/dictionaries');
|
|
122
|
-
const {
|
|
123
|
-
dictLocation,
|
|
124
|
-
} = require('../base/location');
|
|
125
|
-
const { kindProperties, dictKinds } = require('./base');
|
|
126
|
-
const {
|
|
127
|
-
annotationVal,
|
|
128
|
-
annotationIsFalse,
|
|
129
|
-
annotateWith,
|
|
130
|
-
linkToOrigin,
|
|
131
|
-
setMemberParent,
|
|
132
|
-
storeExtension,
|
|
133
|
-
dependsOnSilent,
|
|
134
|
-
pathName,
|
|
135
|
-
augmentPath,
|
|
136
|
-
splitIntoPath,
|
|
137
|
-
} = require('./utils');
|
|
138
|
-
const { compareLayer, layer } = require('./moduleLayers');
|
|
139
|
-
const { initBuiltins, isInReservedNamespace } = require('./builtins');
|
|
140
|
-
const setLink = setProp;
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Export function of this file. Transform argument `sources` = dictionary of
|
|
144
|
-
* AST-like CSNs into augmented CSN. If a vector is provided for argument
|
|
145
|
-
* `messages` (usually the combined messages from `parse` for all sources), do
|
|
146
|
-
* not throw an exception in case of an error, but push the corresponding error
|
|
147
|
-
* object to that vector. If at least one AST does not exist due to a parse
|
|
148
|
-
* error, set property `lintMode` of `options` to `true`. Then, the resolver
|
|
149
|
-
* does not report errors for using directives pointing to non-existing
|
|
150
|
-
* artifacts.
|
|
151
|
-
*
|
|
152
|
-
* @param {XSN.Model} model Model with `sources` property that contain AST-like CSNs.
|
|
153
|
-
*/
|
|
154
|
-
function define( model ) {
|
|
155
|
-
const { options } = model;
|
|
156
|
-
// Get simplified "resolve" functionality and the message function:
|
|
157
|
-
const {
|
|
158
|
-
message, error, warning, info, messages,
|
|
159
|
-
} = model.$messageFunctions;
|
|
160
|
-
const {
|
|
161
|
-
resolveUncheckedPath,
|
|
162
|
-
resolvePath,
|
|
163
|
-
resolveTypeArguments,
|
|
164
|
-
defineAnnotations,
|
|
165
|
-
attachAndEmitValidNames,
|
|
166
|
-
} = model.$functions;
|
|
167
|
-
Object.assign( model.$functions, {
|
|
168
|
-
initArtifact,
|
|
169
|
-
lateExtensions,
|
|
170
|
-
projectionAncestor,
|
|
171
|
-
hasTruthyProp,
|
|
172
|
-
} );
|
|
173
|
-
// During the definer, we can only resolve artifact references, i.e,
|
|
174
|
-
// after a `.`, we only search in the `_subArtifacts` dictionary:
|
|
175
|
-
model.$volatileFunctions.environment = function artifactsEnv( art ) {
|
|
176
|
-
return art._subArtifacts || Object.create(null);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const extensionsDict = Object.create(null);
|
|
180
|
-
let addTextsLanguageAssoc = false;
|
|
181
|
-
return doDefine();
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Main function of the definer.
|
|
185
|
-
*
|
|
186
|
-
* @returns {XSN.Model}
|
|
187
|
-
*/
|
|
188
|
-
function doDefine() {
|
|
189
|
-
if (options.deprecated &&
|
|
190
|
-
messages.every( m => m.messageId !== 'api-deprecated-option' )) {
|
|
191
|
-
warning( 'api-deprecated-option', {},
|
|
192
|
-
{ prop: 'deprecated', '#': (options.beta ? 'beta' : 'std') }, {
|
|
193
|
-
// TODO: make the text scarier in future versions
|
|
194
|
-
std: 'With option $(PROP), many newer features are disabled',
|
|
195
|
-
// eslint-disable-next-line max-len
|
|
196
|
-
beta: 'With option $(PROP), beta features and many other newer features are disabled',
|
|
197
|
-
} );
|
|
198
|
-
}
|
|
199
|
-
model.definitions = Object.create(null);
|
|
200
|
-
setProp( model, '_entities', [] ); // for entities with includes
|
|
201
|
-
model.$entity = 0;
|
|
202
|
-
model.$compositionTargets = Object.create(null);
|
|
203
|
-
model.$lateExtensions = Object.create(null); // for generated artifacts
|
|
204
|
-
|
|
205
|
-
initBuiltins( model );
|
|
206
|
-
for (const name in model.sources)
|
|
207
|
-
addSource( model.sources[name] );
|
|
208
|
-
for (const name in model.sources)
|
|
209
|
-
initNamespaceAndUsing( model.sources[name] );
|
|
210
|
-
dictForEach( model.definitions, initArtifact );
|
|
211
|
-
dictForEach( model.vocabularies, initVocabulary );
|
|
212
|
-
|
|
213
|
-
mergeI18nBlocks( model );
|
|
214
|
-
|
|
215
|
-
if (options.parseCdl) {
|
|
216
|
-
initExtensionsWithoutApplying();
|
|
217
|
-
// If no extensions shall be applied then we can skip further
|
|
218
|
-
// artifact processing and return the model with an `extensions` property.
|
|
219
|
-
return model;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
applyExtensions();
|
|
223
|
-
|
|
224
|
-
const commonLanguagesEntity // TODO: remove beta after a grace period
|
|
225
|
-
= (options.addTextsLanguageAssoc || isBetaEnabled( options, 'addTextsLanguageAssoc' )) &&
|
|
226
|
-
model.definitions['sap.common.Languages'];
|
|
227
|
-
addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
|
|
228
|
-
commonLanguagesEntity.elements.code);
|
|
229
|
-
Object.keys( model.definitions ).forEach( processArtifact );
|
|
230
|
-
lateExtensions( false );
|
|
231
|
-
// Set _service link (sorted to set it on parent first). Could be set
|
|
232
|
-
// directly, but beware a namespace becoming a service later.
|
|
233
|
-
Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
|
|
234
|
-
forEachGeneric( model, 'definitions', postProcessArtifact );
|
|
235
|
-
return model;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Phase 1: ----------------------------------------------------------------
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Add definitions of the given source AST, both CDL and CSN
|
|
242
|
-
*
|
|
243
|
-
* @param {XSN.AST} src
|
|
244
|
-
*/
|
|
245
|
-
function addSource( src ) {
|
|
246
|
-
// handle sub model from parser
|
|
247
|
-
if (!src.kind)
|
|
248
|
-
src.kind = 'source';
|
|
249
|
-
|
|
250
|
-
let namespace = src.namespace && src.namespace.path;
|
|
251
|
-
let prefix = namespace ? `${ pathName( namespace ) }.` : '';
|
|
252
|
-
if (isInReservedNamespace(prefix)) {
|
|
253
|
-
error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], {},
|
|
254
|
-
// TODO: use $(NAME)
|
|
255
|
-
'The namespace "cds" is reserved for CDS builtins' );
|
|
256
|
-
namespace = null;
|
|
257
|
-
}
|
|
258
|
-
if (src.$frontend !== 'json') { // CDL input
|
|
259
|
-
// TODO: set _block to builtin
|
|
260
|
-
if (src.artifacts)
|
|
261
|
-
addPathPrefixes( src.artifacts, prefix ); // before addUsing
|
|
262
|
-
else if (src.usings || src.namespace)
|
|
263
|
-
src.artifacts = Object.create(null);
|
|
264
|
-
if (src.usings)
|
|
265
|
-
src.usings.forEach( u => addUsing( u, src ) );
|
|
266
|
-
if (namespace)
|
|
267
|
-
addNamespace( namespace, src );
|
|
268
|
-
if (src.artifacts) // addArtifact needs usings for context extensions
|
|
269
|
-
dictForEach( src.artifacts, a => addArtifact( a, src, prefix ) );
|
|
270
|
-
}
|
|
271
|
-
else if (src.definitions) { // CSN input
|
|
272
|
-
prefix = '';
|
|
273
|
-
dictForEach( src.definitions, v => addDefinition( v, src ) );
|
|
274
|
-
}
|
|
275
|
-
if (src.vocabularies) {
|
|
276
|
-
if (!model.vocabularies)
|
|
277
|
-
model.vocabularies = Object.create(null);
|
|
278
|
-
dictForEach( src.vocabularies, v => addVocabulary( v, src, prefix ) );
|
|
279
|
-
}
|
|
280
|
-
if (src.extensions) { // requires using to be known!
|
|
281
|
-
src.extensions.forEach( e => addExtension( e, src ) );
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function addDefinition( art, block ) {
|
|
286
|
-
const { absolute } = art.name;
|
|
287
|
-
// TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
|
|
288
|
-
if (absolute === 'cds' || isInReservedNamespace(absolute)) {
|
|
289
|
-
error( 'reserved-namespace-cds', [ art.name.location, art ], {},
|
|
290
|
-
// TODO: use $(NAME)
|
|
291
|
-
'The namespace "cds" is reserved for CDS builtins' );
|
|
292
|
-
}
|
|
293
|
-
else if (absolute === 'localized' || absolute.startsWith( 'localized.' )) {
|
|
294
|
-
if (!art.query && art.kind !== 'context') { // context for recompilation (TODO: necessary?)
|
|
295
|
-
error( 'reserved-namespace-localized', [ art.name.location, art ], {},
|
|
296
|
-
'The namespace "localized" is reserved for localization views' );
|
|
297
|
-
}
|
|
298
|
-
else if (block.$frontend !== 'json') {
|
|
299
|
-
info( 'ignored-localized-definition', [ art.name.location, art ], {},
|
|
300
|
-
'This definition in the namespace "localized" is ignored' );
|
|
301
|
-
}
|
|
302
|
-
else if (!block.$withLocalized && !options.$recompile) { // block = src
|
|
303
|
-
block.$withLocalized = true;
|
|
304
|
-
info( 'recalculated-localized', [ art.name.location, null ], {},
|
|
305
|
-
'Input CSN contains localization view definitions which are re-calculated' );
|
|
306
|
-
}
|
|
307
|
-
art.$inferred = 'LOCALIZED-IGNORED';
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
setLink( art, '_block', block );
|
|
312
|
-
// dictAdd might set $duplicates to true
|
|
313
|
-
dictAdd( model.definitions, absolute, art );
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// If 'A.B.C' is in 'artifacts', also add 'A' for name resolution
|
|
320
|
-
function addPathPrefixes( artifacts, prefix ) {
|
|
321
|
-
for (const name in artifacts) {
|
|
322
|
-
const d = artifacts[name];
|
|
323
|
-
const a = Array.isArray(d) ? d[0] : d;
|
|
324
|
-
if (!a.name.absolute)
|
|
325
|
-
a.name.absolute = prefix + name;
|
|
326
|
-
const index = name.indexOf( '.' );
|
|
327
|
-
if (index < 0)
|
|
328
|
-
continue; // also for newly added (i.e. does not matter whether visited or not)
|
|
329
|
-
const id = name.substring( 0, index );
|
|
330
|
-
if (artifacts[id])
|
|
331
|
-
continue;
|
|
332
|
-
// TODO: enable optional locations
|
|
333
|
-
const location = a.name.path && a.name.path[0].location || a.location;
|
|
334
|
-
const absolute = prefix + id;
|
|
335
|
-
artifacts[id] = {
|
|
336
|
-
kind: 'using', // !, not namespace - we do not know artifact yet
|
|
337
|
-
name: {
|
|
338
|
-
id, absolute, location, $inferred: 'as',
|
|
339
|
-
},
|
|
340
|
-
// TODO: use global ref (in general - all uses of splitIntoPath)
|
|
341
|
-
extern: { path: splitIntoPath( location, absolute ), location },
|
|
342
|
-
location,
|
|
343
|
-
$inferred: 'path-prefix',
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Add the names of a USING declaration to the top-level search environment
|
|
351
|
-
* of the source, and set the absolute name referred by the USING
|
|
352
|
-
* declaration.
|
|
353
|
-
*
|
|
354
|
-
* @param {XSN.Using} decl Node to be expanded and added to `src`
|
|
355
|
-
* @param {XSN.AST} src
|
|
356
|
-
*/
|
|
357
|
-
function addUsing( decl, src ) {
|
|
358
|
-
if (decl.usings) {
|
|
359
|
-
// e.g. `using {a,b} from 'file.cds'` -> recursive
|
|
360
|
-
decl.usings.forEach( u => addUsing( u, src ) );
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
const { path } = decl.extern;
|
|
364
|
-
if (path.broken || !path[0]) // syntax error
|
|
365
|
-
return;
|
|
366
|
-
if (!decl.name)
|
|
367
|
-
decl.name = { ...path[path.length - 1], $inferred: 'as' };
|
|
368
|
-
decl.name.absolute = pathName( path );
|
|
369
|
-
const name = decl.name.id;
|
|
370
|
-
// TODO: check name: no "."
|
|
371
|
-
if (path[0].id === 'localized' || path[0].id.startsWith( 'localized.' )) {
|
|
372
|
-
decl.$inferred = 'LOCALIZED-IGNORED';
|
|
373
|
-
warning( 'using-localized-view', [ path.location, decl ], {},
|
|
374
|
-
'Localization views can\'t be referred to - ignored USING' );
|
|
375
|
-
// actually not ignored anymore
|
|
376
|
-
}
|
|
377
|
-
const found = src.artifacts[name];
|
|
378
|
-
if (found && found.$inferred === 'path-prefix' &&
|
|
379
|
-
found.name.absolute === decl.name.absolute)
|
|
380
|
-
src.artifacts[name] = decl;
|
|
381
|
-
else
|
|
382
|
-
dictAddArray( src.artifacts, name, decl );
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function addNamespace( path, src ) {
|
|
386
|
-
const absolute = pathName( path );
|
|
387
|
-
if (path.broken) // parsing may have failed
|
|
388
|
-
return;
|
|
389
|
-
// create using for own namespace:
|
|
390
|
-
const last = path[path.length - 1];
|
|
391
|
-
const { id } = last;
|
|
392
|
-
if (src.artifacts[id] || last.id.includes('.'))
|
|
393
|
-
// not used as we have a definition/using with that name, or dotted last path id
|
|
394
|
-
return;
|
|
395
|
-
src.artifacts[id] = {
|
|
396
|
-
kind: 'using',
|
|
397
|
-
name: {
|
|
398
|
-
id, absolute, location: last.location, $inferred: 'as',
|
|
399
|
-
},
|
|
400
|
-
extern: src.namespace,
|
|
401
|
-
location: src.namespace.location,
|
|
402
|
-
$inferred: 'namespace',
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
function addArtifact( art, block, prefix ) {
|
|
406
|
-
if (art.kind === 'using')
|
|
407
|
-
return;
|
|
408
|
-
art.name.absolute = prefix + pathName( art.name.path );
|
|
409
|
-
addDefinition( art, block );
|
|
410
|
-
if (art.artifacts) {
|
|
411
|
-
const p = `${ art.name.absolute }.`;
|
|
412
|
-
// path prefixes (usings) must be added before extensions in artifacts:
|
|
413
|
-
addPathPrefixes( art.artifacts, p );
|
|
414
|
-
dictForEach( art.artifacts, a => addArtifact( a, art, p ) );
|
|
415
|
-
}
|
|
416
|
-
if (art.extensions) { // requires using to be known!
|
|
417
|
-
art.extensions.forEach( e => e.name && addExtension( e, art ) );
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function addExtension( ext, block ) {
|
|
422
|
-
setLink( ext, '_block', block );
|
|
423
|
-
const absolute = ext.name && resolveUncheckedPath( ext.name, 'extend', ext );
|
|
424
|
-
if (!absolute) // broken path
|
|
425
|
-
return;
|
|
426
|
-
delete ext.name.path[0]._artifact; // might point to wrong JS object in phase 1
|
|
427
|
-
ext.name.absolute = absolute; // definition might not be there yet, no _artifact link
|
|
428
|
-
pushToDict( extensionsDict, absolute, ext );
|
|
429
|
-
if (!ext.artifacts)
|
|
430
|
-
return;
|
|
431
|
-
// Directly add the artifacts of context and service extension:
|
|
432
|
-
if (!model.$blocks)
|
|
433
|
-
model.$blocks = Object.create( null );
|
|
434
|
-
// Set block number for debugging (--raw-output):
|
|
435
|
-
// eslint-disable-next-line no-multi-assign
|
|
436
|
-
ext.name.select = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
|
|
437
|
-
const prefix = `${ absolute }.`;
|
|
438
|
-
dictForEach( ext.artifacts, a => addArtifact( a, ext, prefix ) );
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function addVocabulary( vocab, block, prefix ) {
|
|
442
|
-
setLink( vocab, '_block', block );
|
|
443
|
-
const { name } = vocab;
|
|
444
|
-
if (!name.absolute)
|
|
445
|
-
name.absolute = prefix + name.path.map( id => id.id ).join('.');
|
|
446
|
-
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Phase 2 ("init") --------------------------------------------------------
|
|
450
|
-
|
|
451
|
-
function checkRedefinition( art ) {
|
|
452
|
-
if (!art.$duplicates)
|
|
453
|
-
return;
|
|
454
|
-
if (art._main) {
|
|
455
|
-
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
456
|
-
name: art.name.id,
|
|
457
|
-
'#': kindProperties[art.kind].normalized || art.kind,
|
|
458
|
-
} );
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
462
|
-
name: art.name.absolute,
|
|
463
|
-
'#': (art.kind === 'annotation' ? 'annotation' : 'absolute' ),
|
|
464
|
-
} );
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function initNamespaceAndUsing( src ) {
|
|
469
|
-
if (src.namespace) {
|
|
470
|
-
const decl = src.namespace;
|
|
471
|
-
const { path } = decl;
|
|
472
|
-
if (path.broken) // parsing may have failed
|
|
473
|
-
return;
|
|
474
|
-
const { id } = path[path.length - 1];
|
|
475
|
-
const absolute = pathName( path );
|
|
476
|
-
if (!model.definitions[absolute]) {
|
|
477
|
-
// TODO: do we really need this namespace entry - try without (msg change)
|
|
478
|
-
const location = path.location || decl.location;
|
|
479
|
-
// TODO: make it possible to have no location
|
|
480
|
-
const ns = { kind: 'namespace', name: { absolute, location }, location };
|
|
481
|
-
model.definitions[absolute] = ns;
|
|
482
|
-
initParentLink( ns, model.definitions );
|
|
483
|
-
}
|
|
484
|
-
const builtin = model.$builtins[id];
|
|
485
|
-
if (builtin && !builtin.internal &&
|
|
486
|
-
src.artifacts[id] && src.artifacts[id].extern === decl) {
|
|
487
|
-
warning( 'ref-shadowed-builtin', [ decl.location, null ], // no home artifact
|
|
488
|
-
{ id, art: absolute, code: `using ${ builtin.name.absolute };` },
|
|
489
|
-
'$(ID) now refers to $(ART) - consider $(CODE)' );
|
|
490
|
-
}
|
|
491
|
-
// setLink( decl, '_artifact', model.definitions[absolute] ); // TODO: necessary?
|
|
492
|
-
}
|
|
493
|
-
if (!src.usings)
|
|
494
|
-
return;
|
|
495
|
-
for (const name in src.artifacts) {
|
|
496
|
-
const entry = src.artifacts[name];
|
|
497
|
-
if (!Array.isArray(entry)) // no local name duplicate
|
|
498
|
-
continue;
|
|
499
|
-
for (const decl of entry) {
|
|
500
|
-
if (!decl.$duplicates) { // do not have two duplicate messages
|
|
501
|
-
error( 'duplicate-using', [ decl.name.location, null ], { name }, // TODO: semantic
|
|
502
|
-
'Duplicate definition of top-level name $(NAME)' );
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function initArtifact( art, reInit = false ) {
|
|
509
|
-
if (!reInit)
|
|
510
|
-
initParentLink( art, model.definitions );
|
|
511
|
-
const block = art._block;
|
|
512
|
-
checkRedefinition( art );
|
|
513
|
-
defineAnnotations( art, art, block );
|
|
514
|
-
initMembers( art, art, block );
|
|
515
|
-
initDollarSelf( art ); // $self
|
|
516
|
-
if (art.params)
|
|
517
|
-
initParams( art ); // $parameters
|
|
518
|
-
if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
|
|
519
|
-
extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"
|
|
520
|
-
|
|
521
|
-
if (!art.query)
|
|
522
|
-
return;
|
|
523
|
-
art.$queries = [];
|
|
524
|
-
setLink( art, '_from', [] ); // for sequence of resolve steps
|
|
525
|
-
if (!setLink( art, '_leadingQuery', initQueryExpression( art.query, art ) ) )
|
|
526
|
-
return; // null or undefined in case of parse error
|
|
527
|
-
setProp( art._leadingQuery, '_$next', art );
|
|
528
|
-
// the following we be removed soon if we have:
|
|
529
|
-
// view elements as proxies to elements of leading query
|
|
530
|
-
if (art.elements) { // specified element via compilation of client-style CSN
|
|
531
|
-
setProp( art, 'elements$', art.elements );
|
|
532
|
-
delete art.elements;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function initVocabulary( art ) {
|
|
537
|
-
initParentLink( art, model.vocabularies );
|
|
538
|
-
checkRedefinition( art );
|
|
539
|
-
const block = art._block;
|
|
540
|
-
defineAnnotations( art, art, block );
|
|
541
|
-
initMembers( art, art, block );
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function initParentLink( art, definitions ) {
|
|
545
|
-
setLink( art, '_parent', null );
|
|
546
|
-
const { absolute } = art.name;
|
|
547
|
-
const dot = absolute.lastIndexOf('.');
|
|
548
|
-
if (dot < 0)
|
|
549
|
-
return;
|
|
550
|
-
art.name.id = absolute.substring( dot + 1 ); // XSN TODO: remove name.id for artifacts
|
|
551
|
-
const prefix = absolute.substring( 0, dot );
|
|
552
|
-
let parent = definitions[prefix];
|
|
553
|
-
if (!parent) {
|
|
554
|
-
const { location } = art.name; // TODO: make it possible to have no location
|
|
555
|
-
parent = { kind: 'namespace', name: { absolute: prefix, location }, location };
|
|
556
|
-
definitions[prefix] = parent;
|
|
557
|
-
initParentLink( parent, definitions );
|
|
558
|
-
}
|
|
559
|
-
if (art.kind !== 'namespace' &&
|
|
560
|
-
isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
|
|
561
|
-
let p = parent;
|
|
562
|
-
while (p && kindProperties[p.kind].artifacts)
|
|
563
|
-
p = p._parent;
|
|
564
|
-
if (p) {
|
|
565
|
-
error( 'subartifacts-not-supported', [ art.name.location, art ],
|
|
566
|
-
{ art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
|
|
567
|
-
// eslint-disable-next-line max-len
|
|
568
|
-
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
setLink( art, '_parent', parent );
|
|
572
|
-
if (!parent._subArtifacts)
|
|
573
|
-
setLink( parent, '_subArtifacts', Object.create(null) );
|
|
574
|
-
if (art.$duplicates !== true) // no redef or "first def"
|
|
575
|
-
parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// From here til EOF, reexamine code ---------------------------------------
|
|
579
|
-
|
|
580
|
-
function initDollarSelf( art ) {
|
|
581
|
-
const selfname = '$self';
|
|
582
|
-
// TODO: use setMemberParent() ?
|
|
583
|
-
const self = {
|
|
584
|
-
name: { id: selfname, alias: selfname, absolute: art.name.absolute },
|
|
585
|
-
kind: '$self',
|
|
586
|
-
location: art.location,
|
|
587
|
-
};
|
|
588
|
-
setProp( self, '_parent', art );
|
|
589
|
-
setProp( self, '_main', art ); // used on main artifact
|
|
590
|
-
setProp( self, '_origin', art );
|
|
591
|
-
art.$tableAliases = Object.create(null);
|
|
592
|
-
art.$tableAliases[selfname] = self;
|
|
593
|
-
setProp( art, '_$next', model.$magicVariables );
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function initParams( art ) {
|
|
597
|
-
// TODO: use setMemberParent() ?
|
|
598
|
-
const parameters = {
|
|
599
|
-
name: { id: '$parameters', param: '$parameters', absolute: art.name.absolute },
|
|
600
|
-
kind: '$parameters',
|
|
601
|
-
location: art.location,
|
|
602
|
-
};
|
|
603
|
-
setProp( parameters, '_parent', art );
|
|
604
|
-
setProp( parameters, '_main', art );
|
|
605
|
-
// Search for :const after :param. If there will be a possibility in the
|
|
606
|
-
// future that we can extend <query>.columns, we must be sure to use
|
|
607
|
-
// _block of that new column after :param (or just allow $parameters there).
|
|
608
|
-
setProp( parameters, '_block', art._block );
|
|
609
|
-
if (art.params) {
|
|
610
|
-
parameters.elements = art.params;
|
|
611
|
-
parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
|
|
612
|
-
}
|
|
613
|
-
art.$tableAliases.$parameters = parameters;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function initSubQuery( query ) {
|
|
617
|
-
if (query.on)
|
|
618
|
-
initExprForQuery( query.on, query );
|
|
619
|
-
// TODO: MIXIN with name = ...subquery (not yet supported anyway)
|
|
620
|
-
initSelectItems( query, query.columns );
|
|
621
|
-
if (query.where)
|
|
622
|
-
initExprForQuery( query.where, query );
|
|
623
|
-
if (query.having)
|
|
624
|
-
initExprForQuery( query.having, query );
|
|
625
|
-
initMembers( query, query, query._block );
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
function initSelectItems( parent, columns ) {
|
|
629
|
-
// TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
|
|
630
|
-
let wildcard = null;
|
|
631
|
-
for (const col of columns || parent.expand || parent.inline || []) {
|
|
632
|
-
if (!col) // parse error
|
|
633
|
-
continue;
|
|
634
|
-
if (!columns) {
|
|
635
|
-
if (parent.value)
|
|
636
|
-
setProp( col, '_pathHead', parent ); // also set for '*' in expand/inline
|
|
637
|
-
else if (parent._pathHead)
|
|
638
|
-
setProp( col, '_pathHead', parent._pathHead );
|
|
639
|
-
}
|
|
640
|
-
if (col.val === '*') {
|
|
641
|
-
if (!wildcard) {
|
|
642
|
-
wildcard = col;
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
// a late syntax error (this code also runs with parse-cdl), i.e.
|
|
646
|
-
// no semantic loc (wouldn't be available for expand/inline anyway)
|
|
647
|
-
error( 'syntax-duplicate-clause', [ col.location, null ],
|
|
648
|
-
{ prop: '*', line: wildcard.location.line, col: wildcard.location.col },
|
|
649
|
-
'You have provided a $(PROP) already at line $(LINE), column $(COL)' );
|
|
650
|
-
// TODO: extra text variants for expand/inline? - probably not
|
|
651
|
-
col.val = null; // do not consider it for expandWildcard()
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
else if (col.value || col.expand) {
|
|
655
|
-
setProp( col, '_block', parent._block );
|
|
656
|
-
defineAnnotations( col, col, parent._block ); // TODO: complain with inline
|
|
657
|
-
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
658
|
-
if (columns)
|
|
659
|
-
initExprForQuery( col.value, parent );
|
|
660
|
-
initSelectItems( col );
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function initExprForQuery( expr, query ) {
|
|
666
|
-
if (Array.isArray(expr)) { // TODO: old-style $parens ?
|
|
667
|
-
expr.forEach( e => initExprForQuery( e, query ) );
|
|
668
|
-
}
|
|
669
|
-
else if (!expr) {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
else if (expr.query) {
|
|
673
|
-
initQueryExpression( expr.query, query );
|
|
674
|
-
}
|
|
675
|
-
else if (expr.args) {
|
|
676
|
-
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
677
|
-
args.forEach( e => initExprForQuery( e, query ) );
|
|
678
|
-
}
|
|
679
|
-
else if (expr.path && expr.$expected === 'exists') {
|
|
680
|
-
expr.$expected = 'approved-exists';
|
|
681
|
-
approveExistsInChildren(expr);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
|
|
687
|
-
* since we will have a top-level subquery after exists-processing in the forHanaNew.
|
|
688
|
-
*
|
|
689
|
-
* Recursively drill down into:
|
|
690
|
-
* - the .path
|
|
691
|
-
* - the .args
|
|
692
|
-
* - the .where.args
|
|
693
|
-
*
|
|
694
|
-
* Any $expected === 'exists' encountered along the way are turned into 'approved-exists'
|
|
695
|
-
*
|
|
696
|
-
* working: exists toE[exists toE] -> select from E where exists toE
|
|
697
|
-
* not working: toE[exists toE] -> we don't support subqueries in filters
|
|
698
|
-
*
|
|
699
|
-
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
700
|
-
*/
|
|
701
|
-
function approveExistsInChildren(exprOrPathElement) {
|
|
702
|
-
if (exprOrPathElement.$expected === 'exists')
|
|
703
|
-
exprOrPathElement.$expected = 'approved-exists';
|
|
704
|
-
// Drill down
|
|
705
|
-
if (exprOrPathElement.args)
|
|
706
|
-
exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
|
|
707
|
-
else if (exprOrPathElement.where && exprOrPathElement.where.args)
|
|
708
|
-
exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
|
|
709
|
-
else if (exprOrPathElement.path)
|
|
710
|
-
exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// table is table expression in FROM, becomes an alias
|
|
714
|
-
function initTableExpression( table, query, joinParents ) {
|
|
715
|
-
if (!table) // parse error
|
|
716
|
-
return;
|
|
717
|
-
if (table.path) { // path in FROM
|
|
718
|
-
if (!table.path.length || table.path.broken)
|
|
719
|
-
// parse error (e.g. final ',' in FROM), projection on <eof>
|
|
720
|
-
return;
|
|
721
|
-
if (!table.name) {
|
|
722
|
-
const last = table.path[table.path.length - 1];
|
|
723
|
-
const dot = last.id.lastIndexOf('.');
|
|
724
|
-
const id = (dot < 0) ? last.id : last.id.substring( dot + 1 );
|
|
725
|
-
// TODO: if we have too much time, we can calculate the real location with '.'
|
|
726
|
-
table.name = { $inferred: 'as', id, location: last.location };
|
|
727
|
-
}
|
|
728
|
-
addAsAlias();
|
|
729
|
-
// _origin is set when we resolve the ref
|
|
730
|
-
if (query._parent.kind !== 'select')
|
|
731
|
-
query._main._from.push( table ); // store tabref if outside "real" subquery
|
|
732
|
-
}
|
|
733
|
-
else if (table.query) {
|
|
734
|
-
if (!table.name || !table.name.id) {
|
|
735
|
-
error( 'query-req-alias', [ table.location, query ], {}, // TODO: not subquery.location ?
|
|
736
|
-
'Table alias is required for this subquery' );
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
addAsAlias();
|
|
740
|
-
setProp( table, '_effectiveType', table.query ); // TODO: remove!
|
|
741
|
-
// Store _origin to leading query of table.query for name resolution
|
|
742
|
-
setProp( table, '_origin', initQueryExpression( table.query, table ) );
|
|
743
|
-
}
|
|
744
|
-
else if (table.join) {
|
|
745
|
-
if (table.on) {
|
|
746
|
-
setProp( table, '_$next', query ); // or query._$next?
|
|
747
|
-
setProp( table, '_block', query._block );
|
|
748
|
-
table.kind = '$join';
|
|
749
|
-
table.name = { location: query.location }; // param comes later
|
|
750
|
-
table.$tableAliases = Object.create( null ); // table aliases and mixin definitions
|
|
751
|
-
joinParents = [ ...joinParents, table ];
|
|
752
|
-
}
|
|
753
|
-
if (table.args) {
|
|
754
|
-
table.args.forEach( (tab, index) => {
|
|
755
|
-
// set for A2J such that for every table alias `ta`:
|
|
756
|
-
// ta === (ta._joinParent
|
|
757
|
-
// ? ta._joinParent.args[ta.$joinArgsIndex] // in JOIN
|
|
758
|
-
// : ta._parent.from ) // directly in FROM
|
|
759
|
-
// Note for --raw-output: _joinParent pointing to CROSS JOIN node has not name
|
|
760
|
-
if (!tab) // parse error; time for #6241
|
|
761
|
-
return; // (parser method to only add non-null to array)
|
|
762
|
-
setProp( tab, '_joinParent', table );
|
|
763
|
-
tab.$joinArgsIndex = index;
|
|
764
|
-
initTableExpression( tab, query, joinParents );
|
|
765
|
-
} );
|
|
766
|
-
}
|
|
767
|
-
if (table.on) { // after processing args to get the $tableAliases
|
|
768
|
-
setMemberParent( table, query.name.select, query ); // sets _parent,_main
|
|
769
|
-
initSubQuery( table ); // init sub queries in ON
|
|
770
|
-
const aliases = Object.keys( table.$tableAliases || {} );
|
|
771
|
-
// Use first tabalias name on the right side of the join to name the
|
|
772
|
-
// (internal) query, should only be relevant for --raw-output, not for
|
|
773
|
-
// user messages or references - TODO: correct if join on left?
|
|
774
|
-
table.name.param = aliases[1] || aliases[0] || '<unknown>';
|
|
775
|
-
setProp( table, '_$next', query._$next );
|
|
776
|
-
// TODO: probably set this to query if we switch to name restriction in JOIN
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return;
|
|
780
|
-
|
|
781
|
-
function addAsAlias() {
|
|
782
|
-
table.kind = '$tableAlias';
|
|
783
|
-
setMemberParent( table, table.name.id, query );
|
|
784
|
-
setProp( table, '_block', query._block );
|
|
785
|
-
dictAdd( query.$tableAliases, table.name.id, table, ( name, loc ) => {
|
|
786
|
-
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
|
|
787
|
-
} );
|
|
788
|
-
// also add to JOIN nodes for name restrictions:
|
|
789
|
-
for (const p of joinParents) {
|
|
790
|
-
// for JOIN alias restriction, we cannot use $duplicates, as it is
|
|
791
|
-
// already used for duplicate aliases of queries:
|
|
792
|
-
dictAddArray( p.$tableAliases, table.name.id, table );
|
|
793
|
-
}
|
|
794
|
-
if (table.name.id[0] === '$') {
|
|
795
|
-
warning( 'syntax-dollar-ident', [ table.name.location, table ], {
|
|
796
|
-
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
|
|
797
|
-
name: '$',
|
|
798
|
-
keyword: 'as',
|
|
799
|
-
} );
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// art is:
|
|
805
|
-
// - entity for top-level queries (including UNION args)
|
|
806
|
-
// - $tableAlias for sub query in FROM - TODO: what about UNION there?
|
|
807
|
-
// - $query for real sub query (in columns, WHERE, ...), again: what about UNION there?
|
|
808
|
-
function initQueryExpression( query, art ) {
|
|
809
|
-
if (!query) // parse error
|
|
810
|
-
return query;
|
|
811
|
-
if (query.from) { // select
|
|
812
|
-
initQuery();
|
|
813
|
-
initTableExpression( query.from, query, [] );
|
|
814
|
-
if (query.mixin)
|
|
815
|
-
addMixin();
|
|
816
|
-
if (!query.$tableAliases.$self) { // same as $projection
|
|
817
|
-
const self = {
|
|
818
|
-
name: { alias: '$self', query: query.name.select, absolute: art.name.absolute },
|
|
819
|
-
kind: '$self',
|
|
820
|
-
location: query.location,
|
|
821
|
-
};
|
|
822
|
-
setProp( self, '_origin', query );
|
|
823
|
-
setProp( self, '_parent', query );
|
|
824
|
-
setProp( self, '_main', query._main );
|
|
825
|
-
setProp( self, '_effectiveType', query ); // TODO: remove
|
|
826
|
-
query.$tableAliases.$self = self;
|
|
827
|
-
query.$tableAliases.$projection = self;
|
|
828
|
-
}
|
|
829
|
-
initSubQuery( query ); // check for SELECT clauses after from / mixin
|
|
830
|
-
}
|
|
831
|
-
else if (query.args) { // UNION, INTERSECT, ..., query in parens
|
|
832
|
-
const leading = initQueryExpression( query.args[0], art );
|
|
833
|
-
for (const q of query.args.slice(1))
|
|
834
|
-
initQueryExpression( q, art );
|
|
835
|
-
setProp( query, '_leadingQuery', leading );
|
|
836
|
-
if (leading && query.orderBy) {
|
|
837
|
-
if (leading.$orderBy)
|
|
838
|
-
leading.$orderBy.push( ...query.orderBy );
|
|
839
|
-
else
|
|
840
|
-
leading.$orderBy = [ ...query.orderBy ];
|
|
841
|
-
}
|
|
842
|
-
// ORDER BY to be evaluated in leading query (LIMIT is literal)
|
|
843
|
-
}
|
|
844
|
-
else { // with parse error (`select from <EOF>`, `select from E { *, ( select }`)
|
|
845
|
-
return undefined;
|
|
846
|
-
}
|
|
847
|
-
return query._leadingQuery || query;
|
|
848
|
-
|
|
849
|
-
function initQuery() {
|
|
850
|
-
const main = art._main || art;
|
|
851
|
-
setProp( query, '_$next',
|
|
852
|
-
// if art is $tableAlias, set to embedding query
|
|
853
|
-
(!art._main || art.kind === 'select' || art.kind === '$join')
|
|
854
|
-
? art : art._parent ); // TODO: check with name resolution change
|
|
855
|
-
setProp( query, '_block', art._block );
|
|
856
|
-
query.kind = 'select';
|
|
857
|
-
query.name = { location: query.location };
|
|
858
|
-
setMemberParent( query, main.$queries.length + 1, main );
|
|
859
|
-
// console.log(art.kind,art.name,query.name,query._$next.name)
|
|
860
|
-
// if (query.name.query === 1 && query.name.absolute === 'S') throw Error();
|
|
861
|
-
main.$queries.push( query );
|
|
862
|
-
setProp( query, '_parent', art ); // _parent should point to alias/main/query
|
|
863
|
-
query.$tableAliases = Object.create( null ); // table aliases and mixin definitions
|
|
864
|
-
dependsOnSilent( main, query );
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function addMixin() {
|
|
868
|
-
// TODO: re-check if mixins have already duplicates
|
|
869
|
-
for (const name in query.mixin) {
|
|
870
|
-
const mixin = query.mixin[name];
|
|
871
|
-
if (!(mixin.$duplicates)) {
|
|
872
|
-
setMemberParent( mixin, name, query );
|
|
873
|
-
mixin.name.alias = mixin.name.id;
|
|
874
|
-
setProp( mixin, '_block', art._block );
|
|
875
|
-
// TODO: do some initMembers() ? If people had annotation
|
|
876
|
-
// assignments on the mixin... (also for future mixin definitions
|
|
877
|
-
// with generated values)
|
|
878
|
-
dictAdd( query.$tableAliases, name, query.mixin[name], ( dupName, loc ) => {
|
|
879
|
-
error( 'duplicate-definition', [ loc, query ], { name: dupName, '#': 'alias' } );
|
|
880
|
-
} );
|
|
881
|
-
if (mixin.name.id[0] === '$') {
|
|
882
|
-
warning( 'syntax-dollar-ident', [ mixin.name.location, mixin ],
|
|
883
|
-
{ '#': 'mixin', name: '$' } );
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
function isDirectComposition( art ) {
|
|
891
|
-
const type = art.type && art.type.path;
|
|
892
|
-
return type && type[0] && type[0].id === 'cds.Composition';
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// Return whether the `target` is actually a `targetAspect`
|
|
896
|
-
function targetIsTargetAspect( elem ) {
|
|
897
|
-
const { target } = elem;
|
|
898
|
-
if (target.elements) {
|
|
899
|
-
// TODO: error if CSN has both target.elements and targetAspect.elements -> delete target
|
|
900
|
-
return true;
|
|
901
|
-
}
|
|
902
|
-
if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
|
|
903
|
-
return false;
|
|
904
|
-
const name = resolveUncheckedPath( target, 'compositionTarget', elem );
|
|
905
|
-
const aspect = name && model.definitions[name];
|
|
906
|
-
return aspect && (aspect.kind === 'aspect' || aspect.kind === 'type'); // type is sloppy
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
911
|
-
* recursively for all sub elements. Also set the property
|
|
912
|
-
* `name.component` of the element with the help of argument `prefix`
|
|
913
|
-
* (which is basically the component name of the `parent` element plus a dot).
|
|
914
|
-
*/
|
|
915
|
-
function initMembers( construct, parent, block, initExtensions = false ) {
|
|
916
|
-
// TODO: split extend from init
|
|
917
|
-
const isQueryExtension = kindProperties[construct.kind].isExtension &&
|
|
918
|
-
(parent._main || parent).query;
|
|
919
|
-
let obj = construct;
|
|
920
|
-
if (obj.items) {
|
|
921
|
-
obj = obj.items;
|
|
922
|
-
setProp( obj, '_outer', construct );
|
|
923
|
-
setProp( obj, '_block', block );
|
|
924
|
-
}
|
|
925
|
-
if (obj.target && targetIsTargetAspect( obj )) {
|
|
926
|
-
obj.targetAspect = obj.target;
|
|
927
|
-
delete obj.target;
|
|
928
|
-
}
|
|
929
|
-
if (obj.targetAspect) {
|
|
930
|
-
if (obj.foreignKeys) {
|
|
931
|
-
error( 'unexpected-keys-for-composition', [ dictLocation( obj.foreignKeys ), construct ],
|
|
932
|
-
{},
|
|
933
|
-
'A managed aspect composition can\'t have a foreign keys specification' );
|
|
934
|
-
delete obj.foreignKeys; // continuation semantics: not specified
|
|
935
|
-
}
|
|
936
|
-
if (obj.on && !obj.target) {
|
|
937
|
-
error( 'unexpected-on-for-composition', [ dictLocation( obj.foreignKeys ), construct ],
|
|
938
|
-
{},
|
|
939
|
-
'A managed aspect composition can\'t have a specified ON condition' );
|
|
940
|
-
delete obj.on; // continuation semantics: not specified
|
|
941
|
-
}
|
|
942
|
-
if (obj.targetAspect.elements) {
|
|
943
|
-
obj = obj.targetAspect;
|
|
944
|
-
setProp( obj, '_outer', construct );
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
if (obj !== parent && obj.elements && parent.enum) {
|
|
948
|
-
// in extensions, extended enums are represented as elements
|
|
949
|
-
for (const n in obj.elements) {
|
|
950
|
-
const e = obj.elements[n];
|
|
951
|
-
if (e.kind === 'element')
|
|
952
|
-
e.kind = 'enum';
|
|
953
|
-
}
|
|
954
|
-
// obj = Object.assign( { enum: obj.elements}, obj );
|
|
955
|
-
// delete obj.elements; // No extra syntax for EXTEND enum
|
|
956
|
-
forEachGeneric( { enum: obj.elements }, 'enum', init );
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
|
|
960
|
-
forEachInOrder( obj, 'elements', init );
|
|
961
|
-
if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
|
|
962
|
-
forEachGeneric( obj, 'enum', init );
|
|
963
|
-
}
|
|
964
|
-
if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?
|
|
965
|
-
forEachInOrder( obj, 'foreignKeys', init );
|
|
966
|
-
if (checkDefinitions( construct, parent, 'actions' ))
|
|
967
|
-
forEachGeneric( construct, 'actions', init );
|
|
968
|
-
if (checkDefinitions( construct, parent, 'params' ))
|
|
969
|
-
forEachInOrder( construct, 'params', init );
|
|
970
|
-
const { returns } = construct;
|
|
971
|
-
if (returns) {
|
|
972
|
-
returns.kind = (kindProperties[construct.kind].isExtension) ? construct.kind : 'param';
|
|
973
|
-
init( returns, '' ); // '' is special name for returns parameter
|
|
974
|
-
}
|
|
975
|
-
return;
|
|
976
|
-
|
|
977
|
-
function init( elem, name, prop ) {
|
|
978
|
-
if (!elem.kind) // wrong CSN input
|
|
979
|
-
elem.kind = dictKinds[prop];
|
|
980
|
-
if (!elem.name) {
|
|
981
|
-
const ref = elem.targetElement || elem.kind === 'element' && elem.value;
|
|
982
|
-
if (ref && ref.path) {
|
|
983
|
-
elem.name = Object.assign( { $inferred: 'as' },
|
|
984
|
-
ref.path[ref.path.length - 1] );
|
|
985
|
-
}
|
|
986
|
-
else { // if JSON parser misses to set name
|
|
987
|
-
elem.name = { id: name, location: elem.location };
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
// if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
|
|
991
|
-
if (kindProperties[elem.kind].isExtension && !initExtensions) {
|
|
992
|
-
storeExtension( elem, name, prop, parent, block );
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
if (isQueryExtension && elem.kind === 'element') {
|
|
996
|
-
error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
|
|
997
|
-
{ art: parent._main || parent },
|
|
998
|
-
'Query entity $(ART) can only be extended with actions' );
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
const bl = elem._block || block;
|
|
1003
|
-
setProp( elem, '_block', bl );
|
|
1004
|
-
setMemberParent( elem, name, parent, construct !== parent && prop );
|
|
1005
|
-
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1006
|
-
checkRedefinition( elem );
|
|
1007
|
-
defineAnnotations( elem, elem, bl );
|
|
1008
|
-
initMembers( elem, elem, bl, initExtensions );
|
|
1009
|
-
|
|
1010
|
-
// for a correct home path, setMemberParent needed to be called
|
|
1011
|
-
|
|
1012
|
-
if (elem.value && elem.kind === 'element' ) {
|
|
1013
|
-
// For enums in extensions, `elem.kind` is only changed to `enum` for non-parseCdl
|
|
1014
|
-
// mode *and* if the referenced artifact is found.
|
|
1015
|
-
// This means that for non-applicable extensions and parseCdl mode, this check
|
|
1016
|
-
// is not used.
|
|
1017
|
-
//
|
|
1018
|
-
// `parent` is the extended entity/type/enum/... and *not* the "extend: ..."
|
|
1019
|
-
// itself (which is `construct`).
|
|
1020
|
-
if ( ![ 'select', 'extend' ].includes(parent.kind) ) {
|
|
1021
|
-
error( 'unexpected-val', [ elem.value.location, elem ],
|
|
1022
|
-
{ '#': construct.kind },
|
|
1023
|
-
{
|
|
1024
|
-
std: 'Elements can\'t have a value',
|
|
1025
|
-
entity: 'Entity elements can\'t have a value',
|
|
1026
|
-
type: 'Type elements can\'t have a value',
|
|
1027
|
-
extend: 'Cannot extend type/entity elements with values',
|
|
1028
|
-
});
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
if (parent.enum && elem.type) {
|
|
1034
|
-
// already rejected by from-csn, can only happen in extensions
|
|
1035
|
-
error( 'unexpected-type', [ elem.type.location, elem ], {},
|
|
1036
|
-
'Enum values can\'t have a custom type' );
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
|
|
1042
|
-
// TODO: do differently, see also annotateMembers() in resolver
|
|
1043
|
-
// To have been checked by parsers:
|
|
1044
|
-
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1045
|
-
if (!dict)
|
|
1046
|
-
return false;
|
|
1047
|
-
const names = Object.getOwnPropertyNames( dict );
|
|
1048
|
-
if (!names.length)
|
|
1049
|
-
return false;
|
|
1050
|
-
const feature = kindProperties[parent.kind][prop];
|
|
1051
|
-
if (feature &&
|
|
1052
|
-
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1053
|
-
return true;
|
|
1054
|
-
const location = dictLocation( names.map( name => dict[name] ) );
|
|
1055
|
-
if (prop === 'actions') {
|
|
1056
|
-
error( 'unexpected-actions', [ location, construct ], {},
|
|
1057
|
-
'Actions and functions only exist top-level and for entities' );
|
|
1058
|
-
}
|
|
1059
|
-
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1060
|
-
error( 'extend-action', [ construct.location, construct ], {},
|
|
1061
|
-
'Actions and functions can\'t be extended, only annotated' );
|
|
1062
|
-
}
|
|
1063
|
-
else if (prop === 'params') {
|
|
1064
|
-
if (!feature) {
|
|
1065
|
-
// Note: This error can't be triggered at the moment. But as we likely want to
|
|
1066
|
-
// allow extensions with params in the future, we keep the code.
|
|
1067
|
-
error( 'unexpected-params', [ location, construct ], {},
|
|
1068
|
-
'Parameters only exist for entities, actions or functions' );
|
|
1069
|
-
}
|
|
1070
|
-
else {
|
|
1071
|
-
// remark: we could allow this
|
|
1072
|
-
error( 'extend-with-params', [ location, construct ], {},
|
|
1073
|
-
'Extending artifacts with parameters is not supported' );
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
else if (feature) { // allowed in principle, but not with extend
|
|
1077
|
-
error( 'extend-type', [ location, construct ], {},
|
|
1078
|
-
'Only structures or enum types can be extended with elements/enums' );
|
|
1079
|
-
}
|
|
1080
|
-
else if (prop === 'elements') {
|
|
1081
|
-
error( 'unexpected-elements', [ location, construct ], {},
|
|
1082
|
-
'Elements only exist in entities, types or typed constructs' );
|
|
1083
|
-
}
|
|
1084
|
-
else { // if (prop === 'enum') {
|
|
1085
|
-
error( 'unexpected-enum', [ location, construct ], {},
|
|
1086
|
-
'Enum symbols can only be defined for types or typed constructs' );
|
|
1087
|
-
}
|
|
1088
|
-
return construct === parent;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* Set projection ancestors, and _service link for artifact with absolute name 'name':
|
|
1093
|
-
* - not set: internal artifact
|
|
1094
|
-
* - null: not within service
|
|
1095
|
-
* - service: the artifact of the embedding service
|
|
1096
|
-
* This function must be called ordered: parent first
|
|
1097
|
-
*
|
|
1098
|
-
* @param {string} name Artifact name
|
|
1099
|
-
*/
|
|
1100
|
-
function setAncestorsAndService( name ) {
|
|
1101
|
-
const art = model.definitions[name];
|
|
1102
|
-
if (!('_parent' in art))
|
|
1103
|
-
return; // nothing to do for builtins and redefinitions
|
|
1104
|
-
if (art._from && !('_ancestors' in art))
|
|
1105
|
-
setProjectionAncestors( art );
|
|
1106
|
-
|
|
1107
|
-
let parent = art._parent;
|
|
1108
|
-
if (parent === model.definitions.localized)
|
|
1109
|
-
parent = model.definitions[name.substring( 'localized.'.length )];
|
|
1110
|
-
const service = parent && (parent._service || parent.kind === 'service' && parent);
|
|
1111
|
-
setProp( art, '_service', service );
|
|
1112
|
-
if (!parent || !service)
|
|
1113
|
-
return;
|
|
1114
|
-
// To be removed when nested services are allowed
|
|
1115
|
-
if (!isBetaEnabled(options, 'nestedServices') && art.kind === 'service') {
|
|
1116
|
-
while (parent.kind !== 'service')
|
|
1117
|
-
parent = parent._parent;
|
|
1118
|
-
message( 'service-nested-service', [ art.name.location, art ], { art: parent },
|
|
1119
|
-
'A service can\'t be nested within a service $(ART)' );
|
|
1120
|
-
}
|
|
1121
|
-
else if (art.kind === 'context') {
|
|
1122
|
-
while (parent.kind !== 'service')
|
|
1123
|
-
parent = parent._parent;
|
|
1124
|
-
// TODO: remove this error
|
|
1125
|
-
message( 'service-nested-context', [ art.name.location, art ], { art: parent },
|
|
1126
|
-
'A context can\'t be nested within a service $(ART)' );
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function setProjectionAncestors( art ) {
|
|
1131
|
-
// Must be run after processLocalizedData() as we could have a projection
|
|
1132
|
-
// on a generated entity.
|
|
1133
|
-
|
|
1134
|
-
// TODO: do not do implicit redirection across services, i.e. Service2.E is
|
|
1135
|
-
// no redirection target for E if Service2.E = projection on Service1.E and
|
|
1136
|
-
// Service1.E = projection on E
|
|
1137
|
-
const chain = [];
|
|
1138
|
-
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
1139
|
-
const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
|
|
1140
|
-
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
1141
|
-
// use the projection having @cds.redirection.target anyhow instead of
|
|
1142
|
-
// `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
|
|
1143
|
-
while (art && !('_ancestors' in art) &&
|
|
1144
|
-
art._from && art._from.length === 1 &&
|
|
1145
|
-
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) ) &&
|
|
1146
|
-
art.query.op && art.query.op.val === 'SELECT') {
|
|
1147
|
-
chain.push( art );
|
|
1148
|
-
setProp( art, '_ancestors', null ); // avoid infloop with cyclic from
|
|
1149
|
-
const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
|
|
1150
|
-
art = name && projectionAncestor( model.definitions[name], art.params );
|
|
1151
|
-
if (autoexposed)
|
|
1152
|
-
break; // only direct projection for auto-exposed
|
|
1153
|
-
}
|
|
1154
|
-
let ancestors = art && (!autoexposed && art._ancestors || []);
|
|
1155
|
-
for (const a of chain.reverse()) {
|
|
1156
|
-
ancestors = (ancestors ? [ ...ancestors, art ] : []);
|
|
1157
|
-
setProp( a, '_ancestors', ancestors );
|
|
1158
|
-
art = a;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Return argument `source` if entity `source` has parameters like `params`
|
|
1163
|
-
// - same parameters, although `params` can contain a new optional one (with DEFAULT)
|
|
1164
|
-
// - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
|
|
1165
|
-
// - exactly the same types (type argument do not matter)
|
|
1166
|
-
function projectionAncestor( source, params ) {
|
|
1167
|
-
if (!source)
|
|
1168
|
-
return source;
|
|
1169
|
-
if (!params) // proj has no params => ok if source has no params
|
|
1170
|
-
return !source.params && source;
|
|
1171
|
-
const sourceParams = source.params || Object.create(null);
|
|
1172
|
-
for (const n in sourceParams) {
|
|
1173
|
-
if (!(n in params)) // source param is not projection param
|
|
1174
|
-
return null; // -> can't be used as implicit redirection target
|
|
1175
|
-
}
|
|
1176
|
-
for (const n in params) {
|
|
1177
|
-
const pp = params[n];
|
|
1178
|
-
const sp = sourceParams[n];
|
|
1179
|
-
if (sp) {
|
|
1180
|
-
if (sp.default && !pp.default) // param DEFAULT clause not supported yet
|
|
1181
|
-
return null; // param is not optional anymore
|
|
1182
|
-
const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
|
|
1183
|
-
const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
|
|
1184
|
-
if ((pt || null) !== (st || null))
|
|
1185
|
-
return null; // params have different type
|
|
1186
|
-
}
|
|
1187
|
-
else if (!pp.default) {
|
|
1188
|
-
return null;
|
|
1189
|
-
} // non-optional param in projection, but not source
|
|
1190
|
-
}
|
|
1191
|
-
return source;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
function postProcessArtifact( art ) {
|
|
1195
|
-
tagCompositionTargets( art );
|
|
1196
|
-
if (art.$queries) {
|
|
1197
|
-
for (const query of art.$queries) {
|
|
1198
|
-
if (query.mixin)
|
|
1199
|
-
forEachGeneric( query, 'mixin', tagCompositionTargets );
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
if (!art._ancestors || art.kind !== 'entity')
|
|
1203
|
-
return; // redirections only to entities
|
|
1204
|
-
const service = art._service;
|
|
1205
|
-
if (!service)
|
|
1206
|
-
return;
|
|
1207
|
-
const sname = service.name.absolute;
|
|
1208
|
-
art._ancestors.forEach( expose );
|
|
1209
|
-
return;
|
|
1210
|
-
|
|
1211
|
-
function expose( ancestor ) {
|
|
1212
|
-
if (ancestor._service === service)
|
|
1213
|
-
return;
|
|
1214
|
-
const desc = ancestor._descendants ||
|
|
1215
|
-
setLink( ancestor, '_descendants', Object.create(null) );
|
|
1216
|
-
if (!desc[sname])
|
|
1217
|
-
desc[sname] = [ art ];
|
|
1218
|
-
else
|
|
1219
|
-
desc[sname].push( art );
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
function processAspectComposition( base ) {
|
|
1224
|
-
// TODO: we need to forbid COMPOSITION of entity w/o keys and ON anyway
|
|
1225
|
-
// TODO: consider entity includes
|
|
1226
|
-
// TODO: nested containment
|
|
1227
|
-
// TODO: better do circular checks in the aspect!
|
|
1228
|
-
if (base.kind !== 'entity' || base.query)
|
|
1229
|
-
return;
|
|
1230
|
-
const keys = baseKeys();
|
|
1231
|
-
if (keys)
|
|
1232
|
-
forEachGeneric( base, 'elements', expand ); // TODO: recursively here?
|
|
1233
|
-
return;
|
|
1234
|
-
|
|
1235
|
-
function baseKeys() {
|
|
1236
|
-
const k = Object.create(null);
|
|
1237
|
-
for (const name in base.elements) {
|
|
1238
|
-
const elem = base.elements[name];
|
|
1239
|
-
if (elem.$duplicates)
|
|
1240
|
-
return false; // no composition-of-type unfold with redefined elems
|
|
1241
|
-
if (elem.key && elem.key.val)
|
|
1242
|
-
k[name] = elem;
|
|
1243
|
-
}
|
|
1244
|
-
return k;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
function expand( elem ) {
|
|
1248
|
-
if (elem.target)
|
|
1249
|
-
return;
|
|
1250
|
-
let origin = elem;
|
|
1251
|
-
// included element do not have target aspect directly
|
|
1252
|
-
while (origin && !origin.targetAspect && origin._origin)
|
|
1253
|
-
origin = origin._origin;
|
|
1254
|
-
let target = origin.targetAspect;
|
|
1255
|
-
if (target && target.path)
|
|
1256
|
-
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
1257
|
-
if (!target || !target.elements)
|
|
1258
|
-
return;
|
|
1259
|
-
const entityName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
1260
|
-
? `${ base.name.absolute }_${ elem.name.id }`
|
|
1261
|
-
: `${ base.name.absolute }.${ elem.name.id }`;
|
|
1262
|
-
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
1263
|
-
createTargetEntity( target, elem, keys, entityName, base );
|
|
1264
|
-
elem.target = {
|
|
1265
|
-
location: (elem.targetAspect || elem).location,
|
|
1266
|
-
$inferred: 'aspect-composition',
|
|
1267
|
-
};
|
|
1268
|
-
setLink( elem.target, '_artifact', entity );
|
|
1269
|
-
if (entity) {
|
|
1270
|
-
model.$compositionTargets[entity.name.absolute] = true;
|
|
1271
|
-
processAspectComposition( entity );
|
|
1272
|
-
processLocalizedData( entity );
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
function allowAspectComposition( target, elem, keys, entityName ) {
|
|
1278
|
-
if (!target.elements || Object.values( target.elements ).some( e => e.$duplicates ))
|
|
1279
|
-
return false; // no elements or with redefinitions
|
|
1280
|
-
const location = elem.target && elem.target.location || elem.location;
|
|
1281
|
-
if ((elem._main._upperAspects || []).includes( target ))
|
|
1282
|
-
return 0; // circular containment of the same aspect
|
|
1283
|
-
|
|
1284
|
-
const keyNames = Object.keys( keys );
|
|
1285
|
-
if (!keyNames.length) {
|
|
1286
|
-
// TODO: for "inner aspect-compositions", signal already in type
|
|
1287
|
-
error( null, [ location, elem ], { target },
|
|
1288
|
-
'An aspect $(TARGET) can\'t be used as target in an entity without keys' );
|
|
1289
|
-
return false;
|
|
1290
|
-
}
|
|
1291
|
-
// if (keys.up_) { // only to be tested if we allow to provide a prefix, which could be ''
|
|
1292
|
-
// // Cannot be in an "inner aspect-compositions" as it would already be wrong before
|
|
1293
|
-
// // TODO: if anonymous type, use location of "up_" element
|
|
1294
|
-
// // FUTURE: add sub info with location of "up_" element
|
|
1295
|
-
// message( 'id', [location, elem], { target, name: 'up_' }, 'Error',
|
|
1296
|
-
// 'An aspect $(TARGET) can't be used as target in an entity with a key named $(NAME)' );
|
|
1297
|
-
// return false;
|
|
1298
|
-
// }
|
|
1299
|
-
if (target.elements.up_) {
|
|
1300
|
-
// TODO: for "inner aspect-compositions", signal already in type
|
|
1301
|
-
// TODO: if anonymous type, use location of "up_" element
|
|
1302
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
1303
|
-
error( null, [ location, elem ], { target, name: 'up_' },
|
|
1304
|
-
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
1305
|
-
return false;
|
|
1306
|
-
}
|
|
1307
|
-
if (model.definitions[entityName]) {
|
|
1308
|
-
error( null, [ location, elem ], { art: entityName },
|
|
1309
|
-
// eslint-disable-next-line max-len
|
|
1310
|
-
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
1313
|
-
const names = Object.keys( target.elements )
|
|
1314
|
-
.filter( n => n.startsWith('up__') && keyNames.includes( n.substring(4) ) );
|
|
1315
|
-
if (names.length) {
|
|
1316
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
1317
|
-
error( null, [ location, elem ], { target: entityName, names }, {
|
|
1318
|
-
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
1319
|
-
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
1320
|
-
});
|
|
1321
|
-
return false;
|
|
1322
|
-
}
|
|
1323
|
-
return true;
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
function createTargetEntity( target, elem, keys, entityName, base ) {
|
|
1327
|
-
const { location } = elem.targetAspect || elem.target || elem;
|
|
1328
|
-
elem.on = {
|
|
1329
|
-
location,
|
|
1330
|
-
op: { val: '=', location },
|
|
1331
|
-
args: [
|
|
1332
|
-
augmentPath( location, elem.name.id, 'up_' ),
|
|
1333
|
-
augmentPath( location, '$self' ),
|
|
1334
|
-
],
|
|
1335
|
-
$inferred: 'aspect-composition',
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
const elements = Object.create(null);
|
|
1339
|
-
const art = {
|
|
1340
|
-
kind: 'entity',
|
|
1341
|
-
name: { path: splitIntoPath( location, entityName ), absolute: entityName, location },
|
|
1342
|
-
location,
|
|
1343
|
-
elements,
|
|
1344
|
-
$inferred: 'composition-entity',
|
|
1345
|
-
};
|
|
1346
|
-
if (target.name) { // named target aspect
|
|
1347
|
-
setLink( art, '_origin', target );
|
|
1348
|
-
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
1349
|
-
}
|
|
1350
|
-
else {
|
|
1351
|
-
// TODO: do we need to give the anonymous target aspect a kind and name?
|
|
1352
|
-
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
const up = { // elements.up_ = ...
|
|
1356
|
-
name: { location, id: 'up_' },
|
|
1357
|
-
kind: 'element',
|
|
1358
|
-
location,
|
|
1359
|
-
$inferred: 'aspect-composition',
|
|
1360
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
1361
|
-
target: augmentPath( location, base.name.absolute ),
|
|
1362
|
-
cardinality: {
|
|
1363
|
-
targetMin: { val: 1, literal: 'number', location },
|
|
1364
|
-
targetMax: { val: 1, literal: 'number', location },
|
|
1365
|
-
location,
|
|
1366
|
-
},
|
|
1367
|
-
};
|
|
1368
|
-
// By default, 'up_' is a managed primary key association.
|
|
1369
|
-
// If 'up_' shall be rendered unmanaged, infer the parent
|
|
1370
|
-
// primary keys and add the ON condition
|
|
1371
|
-
if (isDeprecatedEnabled( options, 'unmanagedUpInComponent' )) {
|
|
1372
|
-
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
1373
|
-
'up__', '@odata.containment.ignore' );
|
|
1374
|
-
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
1375
|
-
}
|
|
1376
|
-
else {
|
|
1377
|
-
up.key = { location, val: true };
|
|
1378
|
-
// managed associations must be explicitly set to not null
|
|
1379
|
-
// even if target cardinality is 1..1
|
|
1380
|
-
up.notNull = { location, val: true };
|
|
1381
|
-
}
|
|
1382
|
-
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
1383
|
-
setLink( art, '_base', base._base || base );
|
|
1384
|
-
|
|
1385
|
-
dictAdd( art.elements, 'up_', up);
|
|
1386
|
-
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
1387
|
-
|
|
1388
|
-
setLink( art, '_block', model.$internal );
|
|
1389
|
-
model.definitions[entityName] = art;
|
|
1390
|
-
initArtifact( art );
|
|
1391
|
-
return art;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
|
|
1395
|
-
// TODO: also use for includeMembers()?
|
|
1396
|
-
for (const name in elements) {
|
|
1397
|
-
const pname = `${ prefix }${ name }`;
|
|
1398
|
-
const origin = elements[name];
|
|
1399
|
-
const proxy = linkToOrigin( origin, pname, null, null, location || origin.location );
|
|
1400
|
-
proxy.$inferred = inferred;
|
|
1401
|
-
if (origin.masked)
|
|
1402
|
-
proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1403
|
-
if (origin.key)
|
|
1404
|
-
proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1405
|
-
if (anno)
|
|
1406
|
-
annotateWith( proxy, anno );
|
|
1407
|
-
dictAdd( proxyDict.elements, pname, proxy );
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
function tagCompositionTargets( elem ) {
|
|
1412
|
-
const type = elem.type && elem.type.path;
|
|
1413
|
-
if (elem.target && type && type[0] && type[0].id === 'cds.Composition') {
|
|
1414
|
-
// Currently not as sub element
|
|
1415
|
-
// Composition always via [{id:'cds.Composition'}] in both CSN and CDL
|
|
1416
|
-
const expected = elem.key && elem.key.val ? 'target' : 'compositionTarget';
|
|
1417
|
-
const target = resolvePath( elem.target, expected, elem );
|
|
1418
|
-
if (target)
|
|
1419
|
-
model.$compositionTargets[target.name.absolute] = true;
|
|
1420
|
-
}
|
|
1421
|
-
forEachGeneric( elem, 'elements', tagCompositionTargets );
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* @param {Function|false} [veryLate]
|
|
1426
|
-
*/
|
|
1427
|
-
function lateExtensions( veryLate ) {
|
|
1428
|
-
for (const name in model.$lateExtensions) {
|
|
1429
|
-
const art = model.definitions[name];
|
|
1430
|
-
const exts = model.$lateExtensions[name];
|
|
1431
|
-
if (art && art.kind !== 'namespace') {
|
|
1432
|
-
if (art.builtin) {
|
|
1433
|
-
for (const ext of exts)
|
|
1434
|
-
info( 'anno-builtin', [ ext.name.location, ext ] );
|
|
1435
|
-
}
|
|
1436
|
-
// created texts entity, autoexposed entity
|
|
1437
|
-
if (exts) {
|
|
1438
|
-
extendArtifact( exts, art, 'gen' );
|
|
1439
|
-
if (veryLate)
|
|
1440
|
-
veryLate( art );
|
|
1441
|
-
model.$lateExtensions[name] = null; // done
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
else if (veryLate) {
|
|
1445
|
-
// Complain about unused extensions, i.e. those
|
|
1446
|
-
// which do not point to a valid artifact
|
|
1447
|
-
for (const ext of exts) {
|
|
1448
|
-
delete ext.name.path[0]._artifact; // get message for root
|
|
1449
|
-
// TODO: make resolvePath('extend'/'annotate') ignore namespaces
|
|
1450
|
-
if (resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
1451
|
-
// should issue error for cds extensions (annotate ok)
|
|
1452
|
-
if (art.kind === 'namespace') {
|
|
1453
|
-
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
|
1454
|
-
'Namespaces can\'t be annotated' );
|
|
1455
|
-
}
|
|
1456
|
-
// Builtin annotations would be represented as annotations in to-csn.js
|
|
1457
|
-
else if (art.builtin) {
|
|
1458
|
-
info( 'anno-builtin', [ ext.name.location, ext ] );
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
// TODO: warning for context/service extension on non-correct
|
|
1462
|
-
if (ext.kind === 'annotate')
|
|
1463
|
-
delete ext.name._artifact; // make it be considered by extendArtifact()
|
|
1464
|
-
}
|
|
1465
|
-
// create "super" ANNOTATE containing all non-applied ones
|
|
1466
|
-
const first = exts[0];
|
|
1467
|
-
const { location } = first.name;
|
|
1468
|
-
|
|
1469
|
-
/** @type {XSN.Definition} */
|
|
1470
|
-
const annotationArtifact = {
|
|
1471
|
-
kind: 'annotate',
|
|
1472
|
-
name: { path: [ { id: name, location } ], absolute: name, location },
|
|
1473
|
-
location: first.location,
|
|
1474
|
-
};
|
|
1475
|
-
|
|
1476
|
-
if (!model.extensions)
|
|
1477
|
-
model.extensions = [];
|
|
1478
|
-
|
|
1479
|
-
model.extensions.push(annotationArtifact);
|
|
1480
|
-
extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
|
|
1481
|
-
// if one of the annotate statement mentions 'returns', assume it
|
|
1482
|
-
// TODO: with warning/info?
|
|
1483
|
-
for (const ext of exts) {
|
|
1484
|
-
if (ext.$syntax === 'returns')
|
|
1485
|
-
annotationArtifact.$syntax = 'returns';
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function initExtensionsWithoutApplying() {
|
|
1492
|
-
// TODO: think of a better function name
|
|
1493
|
-
if (!model.extensions)
|
|
1494
|
-
model.extensions = [];
|
|
1495
|
-
|
|
1496
|
-
for (const name in extensionsDict) {
|
|
1497
|
-
const extensions = extensionsDict[name];
|
|
1498
|
-
|
|
1499
|
-
for (const ext of extensions) {
|
|
1500
|
-
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
1501
|
-
// Define annotations of this top-level extension
|
|
1502
|
-
defineAnnotations( ext, ext, ext._block );
|
|
1503
|
-
// Initialize members and define annotations in sub-elements.
|
|
1504
|
-
initMembers( ext, ext, ext._block, true);
|
|
1505
|
-
resolveAllArtifactPathsUnchecked( ext );
|
|
1506
|
-
|
|
1507
|
-
model.extensions.push(ext);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// `model.definitions` also contains e.g. "includes" that need to be resolved
|
|
1512
|
-
forEachDefinition(model, definition => resolveAllArtifactPathsUnchecked(definition));
|
|
1513
|
-
forEachGeneric(model, 'vocabularies', resolveAllArtifactPathsUnchecked );
|
|
1514
|
-
|
|
1515
|
-
function resolveAllArtifactPathsUnchecked( art ) {
|
|
1516
|
-
if (!art)
|
|
1517
|
-
return;
|
|
1518
|
-
|
|
1519
|
-
checkArtifact(art);
|
|
1520
|
-
forEachMemberRecursivelyWithQuery(art, checkArtifact);
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
// Function for parse.cdl
|
|
1524
|
-
/** @param {XSN.Artifact} artifact */
|
|
1525
|
-
function checkArtifact( artifact ) {
|
|
1526
|
-
// columns are initialized (and made to elements) in the resolver - do init here
|
|
1527
|
-
for (const col of artifact.columns || []) {
|
|
1528
|
-
if (!col.name)
|
|
1529
|
-
col.name = {};
|
|
1530
|
-
setMemberParent( col, null, artifact );
|
|
1531
|
-
if (col.value)
|
|
1532
|
-
recursivelyResolveExpressionCastTypes(col.value, artifact);
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
resolveTypeUnchecked(artifact, artifact);
|
|
1536
|
-
|
|
1537
|
-
if (artifact.items)
|
|
1538
|
-
resolveTypeUnchecked(artifact.items, artifact);
|
|
1539
|
-
|
|
1540
|
-
for (const include of (artifact.includes || []))
|
|
1541
|
-
resolveUncheckedPath(include, 'include', artifact);
|
|
1542
|
-
|
|
1543
|
-
if (artifact.returns) {
|
|
1544
|
-
const returnType = (artifact.returns.items || artifact.returns);
|
|
1545
|
-
if (returnType.type)
|
|
1546
|
-
resolveTypeUnchecked(returnType, artifact);
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
if (artifact.targetAspect) {
|
|
1550
|
-
if (artifact.targetAspect.path)
|
|
1551
|
-
resolveUncheckedPath(artifact.targetAspect, 'target', artifact);
|
|
1552
|
-
else if (artifact.targetAspect.elements) // ad-hoc composition
|
|
1553
|
-
forEachMemberRecursivelyWithQuery(artifact.targetAspect, checkArtifact);
|
|
1554
|
-
}
|
|
1555
|
-
if (artifact.target) {
|
|
1556
|
-
if (artifact.target.path)
|
|
1557
|
-
resolveUncheckedPath(artifact.target, 'target', artifact);
|
|
1558
|
-
else if (artifact.target.elements) // ad-hoc composition
|
|
1559
|
-
forEachMemberRecursivelyWithQuery(artifact.target, checkArtifact);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
// User provided 'from'
|
|
1563
|
-
if (artifact.from) // may be `null` due to EOF
|
|
1564
|
-
resolveUncheckedPath(artifact.from, 'from', artifact);
|
|
1565
|
-
|
|
1566
|
-
// Calculated 'from'. TODO: for parse-cdl?
|
|
1567
|
-
for (const from of (artifact._from || []))
|
|
1568
|
-
resolveUncheckedPath(from, 'from', artifact);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
/**
|
|
1572
|
-
* Recursively resolve `type`s in expressions.
|
|
1573
|
-
* This happens e.g. in `cast()` calls.
|
|
1574
|
-
*
|
|
1575
|
-
* @param {object} expr Expression to check for `type`s.
|
|
1576
|
-
* @param {XSN.Artifact} artifact Surrounding artifact, used for error reporting.
|
|
1577
|
-
*/
|
|
1578
|
-
function recursivelyResolveExpressionCastTypes( expr, artifact ) {
|
|
1579
|
-
// TODO: named argument, cast in query clauses, cast in filters, ...
|
|
1580
|
-
while (Array.isArray(expr)) // old-style XSN paren representation
|
|
1581
|
-
expr = expr[0];
|
|
1582
|
-
if (expr.args) {
|
|
1583
|
-
for (const arg of Array.isArray(expr.args) ? expr.args : Object.values( expr.args ))
|
|
1584
|
-
recursivelyResolveExpressionCastTypes(arg, artifact);
|
|
1585
|
-
}
|
|
1586
|
-
if (expr.type) {
|
|
1587
|
-
const name = resolveUncheckedPath( expr.type, 'type', artifact );
|
|
1588
|
-
const def = name && model.definitions[name];
|
|
1589
|
-
if (def)
|
|
1590
|
-
resolveTypeArguments( expr, def, artifact );
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
/**
|
|
1595
|
-
* Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
|
|
1596
|
-
*
|
|
1597
|
-
* @param {object} artWithType
|
|
1598
|
-
* @param {XSN.Artifact} artifact
|
|
1599
|
-
*/
|
|
1600
|
-
function resolveTypeUnchecked(artWithType, artifact) {
|
|
1601
|
-
if (!artWithType.type)
|
|
1602
|
-
return;
|
|
1603
|
-
const root = artWithType.type.path && artWithType.type.path[0];
|
|
1604
|
-
if (!root) // parse error
|
|
1605
|
-
return;
|
|
1606
|
-
// `scope` is only `typeOf` for `type of element` and not
|
|
1607
|
-
// `type of Entity:element`. For the latter we can resolve the path
|
|
1608
|
-
// without special treatment.
|
|
1609
|
-
if (artWithType.type.scope !== 'typeOf') {
|
|
1610
|
-
// elem: Type or elem: type of Artifact:elem
|
|
1611
|
-
const name = resolveUncheckedPath(artWithType.type, 'type', artifact);
|
|
1612
|
-
const def = name && model.definitions[name];
|
|
1613
|
-
if (def)
|
|
1614
|
-
resolveTypeArguments( artWithType, def, artifact );
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
else if (!artifact._main) {
|
|
1618
|
-
error( 'ref-undefined-typeof', [ artWithType.type.location, artifact ], {},
|
|
1619
|
-
'Current artifact has no element to refer to as type' );
|
|
1620
|
-
return;
|
|
1621
|
-
}
|
|
1622
|
-
else if (root.id === '$self' || root.id === '$projection') {
|
|
1623
|
-
setLink( root, '_artifact', artifact._main );
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
const fake = { name: { absolute: artifact.name.absolute } };
|
|
1627
|
-
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
|
|
1628
|
-
setLink( fake, '_parent', artifact._parent );
|
|
1629
|
-
setLink( fake, '_main', artifact._main ); // value does not matter...
|
|
1630
|
-
setLink( root, '_artifact', fake );
|
|
1631
|
-
}
|
|
1632
|
-
resolveTypeArguments( artifact, {}, artifact ); // issue error for type args
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
/**
|
|
1637
|
-
* Apply the extensions inside the extensionsDict on the model.
|
|
1638
|
-
*
|
|
1639
|
-
* Phase 1: context extends, 2: extends with structure includes, 3: extends
|
|
1640
|
-
* without structure includes (in the case of cyclic includes)
|
|
1641
|
-
*
|
|
1642
|
-
* Before phase 1: all artifact extensions have been collected (even those
|
|
1643
|
-
* inside extend context), only "empty" ones from structure includes are still unknown.
|
|
1644
|
-
* After phase 1, all main artifacts are known, also "empty" extensions are known.
|
|
1645
|
-
*/
|
|
1646
|
-
function applyExtensions() {
|
|
1647
|
-
let phase = 1; // TODO: basically remove phase 1
|
|
1648
|
-
let extNames = Object.keys( extensionsDict ).sort();
|
|
1649
|
-
// Remark: The sort() makes sure that an extend for artifact C.E is applied
|
|
1650
|
-
// after the extend for C has been applied (which could have defined C.E).
|
|
1651
|
-
// Looping over model.definitions in Phase 1 would miss the `extend
|
|
1652
|
-
// context` for a context C.C defined in an `extend context C`.
|
|
1653
|
-
//
|
|
1654
|
-
// TODO: no need to sort anymore
|
|
1655
|
-
while (extNames.length) {
|
|
1656
|
-
const { length } = extNames;
|
|
1657
|
-
for (const name of extNames) {
|
|
1658
|
-
const art = model.definitions[name];
|
|
1659
|
-
if (!art || art.kind === 'namespace') {
|
|
1660
|
-
model.$lateExtensions[name] = extensionsDict[name];
|
|
1661
|
-
delete extensionsDict[name];
|
|
1662
|
-
}
|
|
1663
|
-
else if (art.$duplicates) { // cannot extend redefinitions
|
|
1664
|
-
delete extensionsDict[name];
|
|
1665
|
-
}
|
|
1666
|
-
else if (phase === 1
|
|
1667
|
-
? extendContext( name, art )
|
|
1668
|
-
: extendArtifact( extensionsDict[name], art, phase > 2 )) { // >2: no self-include
|
|
1669
|
-
delete extensionsDict[name];
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
1673
|
-
if (phase === 1)
|
|
1674
|
-
phase = 2;
|
|
1675
|
-
else if (extNames.length >= length)
|
|
1676
|
-
phase = 3;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
function extendContext( name, art ) {
|
|
1681
|
-
// (ext.expectedKind == art.kind) already checked by parser except for context/service
|
|
1682
|
-
if (!kindProperties[art.kind].artifacts) {
|
|
1683
|
-
// no context or service => warn about context extensions
|
|
1684
|
-
for (const ext of extensionsDict[name]) {
|
|
1685
|
-
if ([ 'context', 'service' ].includes( ext.expectedKind )) {
|
|
1686
|
-
const loc = ext.name.location;
|
|
1687
|
-
// TODO: warning is enough
|
|
1688
|
-
error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind }, {
|
|
1689
|
-
std: 'Cannot extend non-context / non-service $(NAME) with artifacts',
|
|
1690
|
-
service: 'Cannot extend non-service $(NAME) with artifacts',
|
|
1691
|
-
context: 'Cannot extend non-context $(NAME) with artifacts',
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
return false;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
for (const ext of extensionsDict[name]) {
|
|
1699
|
-
setProp( ext.name, '_artifact', art );
|
|
1700
|
-
checkDefinitions( ext, art, 'elements'); // error for elements etc
|
|
1701
|
-
checkDefinitions( ext, art, 'enum');
|
|
1702
|
-
checkDefinitions( ext, art, 'actions');
|
|
1703
|
-
checkDefinitions( ext, art, 'params');
|
|
1704
|
-
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
1705
|
-
}
|
|
1706
|
-
return true;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
/**
|
|
1710
|
-
* Extend artifact `art` by `extensions`. `noIncludes` can have values:
|
|
1711
|
-
* - false: includes are applied, extend and annotate is performed
|
|
1712
|
-
* - true: includes are not applied, extend and annotate is performed
|
|
1713
|
-
* - 'gen': no includes and no extensions allowed, annotate is performed
|
|
1714
|
-
*
|
|
1715
|
-
* @param {XSN.Extension[]} extensions
|
|
1716
|
-
* @param {XSN.Definition} art
|
|
1717
|
-
* @param {boolean|'gen'} [noIncludes=false]
|
|
1718
|
-
*/
|
|
1719
|
-
function extendArtifact( extensions, art, noIncludes = false) {
|
|
1720
|
-
if (!noIncludes && !(canApplyIncludes( art ) && extensions.every( canApplyIncludes )))
|
|
1721
|
-
return false;
|
|
1722
|
-
if (!art.query) {
|
|
1723
|
-
model._entities.push( art ); // add structure with includes in dep order
|
|
1724
|
-
art.$entity = ++model.$entity;
|
|
1725
|
-
}
|
|
1726
|
-
if (!noIncludes && art.includes)
|
|
1727
|
-
applyIncludes( art, art );
|
|
1728
|
-
extendMembers( extensions, art, noIncludes === 'gen' );
|
|
1729
|
-
if (!noIncludes && art.includes) {
|
|
1730
|
-
// early propagation of specific annotation assignments
|
|
1731
|
-
propagateEarly( art, '@cds.autoexpose' );
|
|
1732
|
-
propagateEarly( art, '@fiori.draft.enabled' );
|
|
1733
|
-
}
|
|
1734
|
-
// TODO: complain about element extensions inside projection
|
|
1735
|
-
return true;
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* @param {XSN.Definition} art
|
|
1740
|
-
* @param {string} prop
|
|
1741
|
-
*/
|
|
1742
|
-
function propagateEarly( art, prop ) {
|
|
1743
|
-
if (art[prop])
|
|
1744
|
-
return;
|
|
1745
|
-
for (const ref of art.includes) {
|
|
1746
|
-
const aspect = ref._artifact;
|
|
1747
|
-
if (aspect) {
|
|
1748
|
-
const anno = aspect[prop];
|
|
1749
|
-
if (anno && (anno.val !== null || !art[prop]))
|
|
1750
|
-
art[prop] = Object.assign( { $inferred: 'include' }, anno );
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
/**
|
|
1756
|
-
* @param {XSN.Definition} art
|
|
1757
|
-
*/
|
|
1758
|
-
function canApplyIncludes( art ) {
|
|
1759
|
-
if (art.includes) {
|
|
1760
|
-
for (const ref of art.includes) {
|
|
1761
|
-
const template = resolvePath( ref, 'include', art );
|
|
1762
|
-
if (template && template.name.absolute in extensionsDict)
|
|
1763
|
-
return false;
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
return true;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
function extendMembers( extensions, art, noExtend ) {
|
|
1770
|
-
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
1771
|
-
const elemExtensions = [];
|
|
1772
|
-
extensions.sort( compareLayer );
|
|
1773
|
-
for (const ext of extensions) {
|
|
1774
|
-
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
1775
|
-
// 'Info', 'EXT').toString())
|
|
1776
|
-
if (!('_artifact' in ext.name)) { // not already applied
|
|
1777
|
-
setProp( ext.name, '_artifact', art );
|
|
1778
|
-
if (noExtend && ext.kind === 'extend') {
|
|
1779
|
-
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
1780
|
-
'You can\'t use EXTEND on the generated $(ART)' );
|
|
1781
|
-
continue;
|
|
1782
|
-
}
|
|
1783
|
-
if (ext.includes) {
|
|
1784
|
-
// TODO: currently, re-compiling from gensrc does not give the exact
|
|
1785
|
-
// element sequence - we need something like
|
|
1786
|
-
// includes = ['Base1',3,'Base2']
|
|
1787
|
-
// where 3 means adding the next 3 elements before applying include 'Base2'
|
|
1788
|
-
if (art.includes)
|
|
1789
|
-
art.includes.push(...ext.includes);
|
|
1790
|
-
else
|
|
1791
|
-
art.includes = [ ...ext.includes ];
|
|
1792
|
-
applyIncludes( ext, art );
|
|
1793
|
-
}
|
|
1794
|
-
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
1795
|
-
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
1796
|
-
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
1797
|
-
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
1798
|
-
}
|
|
1799
|
-
for (const name in ext.elements) {
|
|
1800
|
-
const elem = ext.elements[name];
|
|
1801
|
-
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
1802
|
-
elemExtensions.push( elem );
|
|
1803
|
-
break;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
if (ext.columns) // extend projection
|
|
1808
|
-
extendColumns( ext, art );
|
|
1809
|
-
}
|
|
1810
|
-
if (elemExtensions.length > 1)
|
|
1811
|
-
reportUnstableExtensions( elemExtensions );
|
|
1812
|
-
|
|
1813
|
-
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
1814
|
-
const dict = art._extend && art._extend[prop];
|
|
1815
|
-
for (const name in dict) {
|
|
1816
|
-
let obj = art;
|
|
1817
|
-
if (obj.targetAspect)
|
|
1818
|
-
obj = obj.targetAspect;
|
|
1819
|
-
while (obj.items)
|
|
1820
|
-
obj = obj.items;
|
|
1821
|
-
const validDict = obj[prop] || prop === 'elements' && obj.enum;
|
|
1822
|
-
const member = validDict[name];
|
|
1823
|
-
if (!member)
|
|
1824
|
-
extendNothing( dict[name], prop, name, art, validDict );
|
|
1825
|
-
else if (!(member.$duplicates))
|
|
1826
|
-
extendMembers( dict[name], member );
|
|
1827
|
-
}
|
|
1828
|
-
});
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* Copy columns for EXTEND PROJECTION
|
|
1833
|
-
*
|
|
1834
|
-
* @param {XSN.Extension} ext
|
|
1835
|
-
* @param {XSN.Artifact} art
|
|
1836
|
-
*/
|
|
1837
|
-
function extendColumns( ext, art ) {
|
|
1838
|
-
// TODO: consider reportUnstableExtensions
|
|
1839
|
-
const { location } = ext.name;
|
|
1840
|
-
const { query } = art;
|
|
1841
|
-
if (!query) {
|
|
1842
|
-
if (art.kind !== 'annotate')
|
|
1843
|
-
error( 'extend-columns', [ location, ext ], { art } );
|
|
1844
|
-
return;
|
|
1845
|
-
}
|
|
1846
|
-
if (!query.from || !query.from.path) {
|
|
1847
|
-
error( 'extend-columns', [ location, ext ], { art } );
|
|
1848
|
-
}
|
|
1849
|
-
else {
|
|
1850
|
-
if (!query.columns)
|
|
1851
|
-
query.columns = [ { location, val: '*' } ];
|
|
1852
|
-
|
|
1853
|
-
for (const column of ext.columns) {
|
|
1854
|
-
setProp( column, '_block', ext._block );
|
|
1855
|
-
query.columns.push(column);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
function reportUnstableExtensions( extensions ) {
|
|
1861
|
-
// Report 'Warning: Unstable element order due to repeated extensions'.
|
|
1862
|
-
// Similar to chooseAssignment(), TODO there: also extra intralayer message
|
|
1863
|
-
// as this is a modeling error
|
|
1864
|
-
let lastExt = null;
|
|
1865
|
-
let open = []; // the "highest" layers
|
|
1866
|
-
for (const ext of extensions) {
|
|
1867
|
-
const extLayer = layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
1868
|
-
if (!open.length) {
|
|
1869
|
-
lastExt = ext;
|
|
1870
|
-
open = [ extLayer.realname ];
|
|
1871
|
-
}
|
|
1872
|
-
else if (extLayer.realname === open[open.length - 1]) { // in same layer
|
|
1873
|
-
if (lastExt) {
|
|
1874
|
-
message( 'extend-repeated-intralayer', [ lastExt.location, lastExt ] );
|
|
1875
|
-
lastExt = null;
|
|
1876
|
-
}
|
|
1877
|
-
message( 'extend-repeated-intralayer', [ ext.location, ext ] );
|
|
1878
|
-
}
|
|
1879
|
-
else {
|
|
1880
|
-
if (lastExt && (open.length > 1 || !extLayer._layerExtends[open[0]])) {
|
|
1881
|
-
// report for lastExt if that is unrelated to other open exts or current ext
|
|
1882
|
-
message( 'extend-unrelated-layer', [ lastExt.location, lastExt ], {},
|
|
1883
|
-
'Unstable element order due to other extension in unrelated layer' );
|
|
1884
|
-
}
|
|
1885
|
-
lastExt = ext;
|
|
1886
|
-
open = open.filter( name => !extLayer._layerExtends[name] );
|
|
1887
|
-
open.push( extLayer.realname );
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
/**
|
|
1892
|
-
* @param {XSN.Extension[]} extensions
|
|
1893
|
-
* @param {string} prop
|
|
1894
|
-
* @param {string} name
|
|
1895
|
-
* @param {XSN.Artifact} art
|
|
1896
|
-
* @param {object} validDict
|
|
1897
|
-
*/
|
|
1898
|
-
function extendNothing( extensions, prop, name, art, validDict ) {
|
|
1899
|
-
for (const ext of extensions) {
|
|
1900
|
-
// TODO: use shared functionality with notFound in resolver.js
|
|
1901
|
-
const { location } = ext.name;
|
|
1902
|
-
const msg
|
|
1903
|
-
= error( 'extend-undefined', [ location, ext ],
|
|
1904
|
-
{ art: searchName( art, name, dictKinds[prop] ) },
|
|
1905
|
-
{
|
|
1906
|
-
std: 'Unknown $(ART) - nothing to extend',
|
|
1907
|
-
// eslint-disable-next-line max-len
|
|
1908
|
-
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
1909
|
-
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
1910
|
-
} );
|
|
1911
|
-
attachAndEmitValidNames(msg, validDict);
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
/**
|
|
1916
|
-
* @param {XSN.Extension} ext
|
|
1917
|
-
* @param {XSN.Artifact} art
|
|
1918
|
-
*/
|
|
1919
|
-
function applyIncludes( ext, art ) {
|
|
1920
|
-
if (!art._ancestors)
|
|
1921
|
-
setProp( art, '_ancestors', [] ); // recursive array of includes
|
|
1922
|
-
for (const ref of ext.includes) {
|
|
1923
|
-
const template = ref._artifact; // already resolved
|
|
1924
|
-
if (template) {
|
|
1925
|
-
if (template._ancestors)
|
|
1926
|
-
art._ancestors.push( ...template._ancestors );
|
|
1927
|
-
art._ancestors.push( template );
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
includeMembers( ext, 'elements', forEachInOrder, ext === art && art );
|
|
1931
|
-
includeMembers( ext, 'actions', forEachGeneric, ext === art && art );
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
/**
|
|
1935
|
-
* @param {XSN.Extension} ext
|
|
1936
|
-
* @param {string} prop
|
|
1937
|
-
* @param {function} forEach
|
|
1938
|
-
* @param {XSN.Artifact} parent
|
|
1939
|
-
*/
|
|
1940
|
-
function includeMembers( ext, prop, forEach, parent ) {
|
|
1941
|
-
// TODO two kind of messages:
|
|
1942
|
-
// Error 'More than one include defines element "A"' (at include ref)
|
|
1943
|
-
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
1944
|
-
const members = ext[prop];
|
|
1945
|
-
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
1946
|
-
for (const ref of ext.includes) {
|
|
1947
|
-
const template = ref._artifact; // already resolved
|
|
1948
|
-
if (template) { // be robust
|
|
1949
|
-
forEach( template, prop, ( origin, name ) => {
|
|
1950
|
-
if (members && name in members)
|
|
1951
|
-
return; // TODO: warning for overwritten element
|
|
1952
|
-
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
1953
|
-
if (!parent) // not yet set for EXTEND foo WITH bar
|
|
1954
|
-
dictAdd( ext[prop], name, elem );
|
|
1955
|
-
elem.$inferred = 'include';
|
|
1956
|
-
if (origin.masked)
|
|
1957
|
-
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1958
|
-
if (origin.key)
|
|
1959
|
-
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1960
|
-
// TODO: also complain if elem is just defined in art
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
// TODO: expand elements having direct elements (if needed)
|
|
1965
|
-
if (members) {
|
|
1966
|
-
forEach( { [prop]: members }, prop, ( elem, name ) => {
|
|
1967
|
-
dictAdd( ext[prop], name, elem );
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
/**
|
|
1973
|
-
* Process "composition of" artifacts.
|
|
1974
|
-
*
|
|
1975
|
-
* @param {string} name
|
|
1976
|
-
*/
|
|
1977
|
-
function processArtifact( name ) {
|
|
1978
|
-
const art = model.definitions[name];
|
|
1979
|
-
if (!(art.$duplicates)) {
|
|
1980
|
-
processAspectComposition( art );
|
|
1981
|
-
if (art.kind === 'entity' && !art.query && art.elements)
|
|
1982
|
-
// check potential entity parse error
|
|
1983
|
-
processLocalizedData( art );
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
/**
|
|
1988
|
-
* @param {XSN.Artifact} art
|
|
1989
|
-
*/
|
|
1990
|
-
function processLocalizedData( art ) {
|
|
1991
|
-
const fioriAnno = art['@fiori.draft.enabled'];
|
|
1992
|
-
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
1993
|
-
|
|
1994
|
-
const textsName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
1995
|
-
? `${ art.name.absolute }_texts`
|
|
1996
|
-
: `${ art.name.absolute }.texts`;
|
|
1997
|
-
const textsEntity = model.definitions[textsName];
|
|
1998
|
-
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
1999
|
-
if (!localized)
|
|
2000
|
-
return;
|
|
2001
|
-
if (textsEntity) // expanded localized data in source
|
|
2002
|
-
return; // -> make it idempotent
|
|
2003
|
-
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
2004
|
-
addTextsAssociations( art, textsName, localized );
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
/**
|
|
2008
|
-
* @param {XSN.Artifact} art
|
|
2009
|
-
* @param {XSN.Artifact|undefined} textsEntity
|
|
2010
|
-
* @param {boolean} fioriEnabled
|
|
2011
|
-
*/
|
|
2012
|
-
function localizedData( art, textsEntity, fioriEnabled ) {
|
|
2013
|
-
let keys = 0;
|
|
2014
|
-
const textElems = [];
|
|
2015
|
-
const conflictingElements = [];
|
|
2016
|
-
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
2017
|
-
if (fioriEnabled)
|
|
2018
|
-
protectedElements.push('ID_texts');
|
|
2019
|
-
if (addTextsLanguageAssoc)
|
|
2020
|
-
protectedElements.push('language');
|
|
2021
|
-
|
|
2022
|
-
for (const name in art.elements) {
|
|
2023
|
-
const elem = art.elements[name];
|
|
2024
|
-
if (elem.$duplicates)
|
|
2025
|
-
return false; // no localized-data unfold with redefined elems
|
|
2026
|
-
if (protectedElements.includes( name ))
|
|
2027
|
-
conflictingElements.push( elem );
|
|
2028
|
-
|
|
2029
|
-
const isKey = elem.key && elem.key.val;
|
|
2030
|
-
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
2031
|
-
|
|
2032
|
-
if (isKey) {
|
|
2033
|
-
keys += 1;
|
|
2034
|
-
textElems.push( elem );
|
|
2035
|
-
}
|
|
2036
|
-
else if (isLocalized) {
|
|
2037
|
-
textElems.push( elem );
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
if (isKey && isLocalized) { // key with localized is wrong - ignore localized
|
|
2041
|
-
const errpos = elem.localized || elem.type || elem.name;
|
|
2042
|
-
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
2043
|
-
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
if (textElems.length <= keys)
|
|
2047
|
-
return false;
|
|
2048
|
-
|
|
2049
|
-
if (!keys) {
|
|
2050
|
-
warning( null, [ art.name.location, art ], {},
|
|
2051
|
-
'No texts entity can be created when no key element exists' );
|
|
2052
|
-
return false;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
if (textsEntity) {
|
|
2056
|
-
if (textsEntity.$duplicates)
|
|
2057
|
-
return false;
|
|
2058
|
-
if (textsEntity.kind !== 'entity' || textsEntity.query ||
|
|
2059
|
-
// already have elements "texts" and "localized" (and optionally ID_texts)
|
|
2060
|
-
conflictingElements.length !== 2 || art.elements.locale ||
|
|
2061
|
-
(fioriEnabled && art.elements.ID_texts)) {
|
|
2062
|
-
// TODO if we have too much time: check all elements of texts entity for safety
|
|
2063
|
-
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
2064
|
-
// eslint-disable-next-line max-len
|
|
2065
|
-
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
2066
|
-
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
2067
|
-
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
2068
|
-
}
|
|
2069
|
-
else if (!art._block || art._block.$frontend !== 'json') {
|
|
2070
|
-
info( null, [ art.name.location, art ], {},
|
|
2071
|
-
'Localized data expansions has already been done' );
|
|
2072
|
-
return textElems; // make double-compilation even with after toHana
|
|
2073
|
-
}
|
|
2074
|
-
else if (!art._block.$withLocalized && !options.$recompile) {
|
|
2075
|
-
art._block.$withLocalized = true;
|
|
2076
|
-
info( 'recalculated-text-entities', [ art.name.location, null ], {},
|
|
2077
|
-
'Input CSN contains expansions for localized data' );
|
|
2078
|
-
return textElems; // make compilation idempotent
|
|
2079
|
-
}
|
|
2080
|
-
else {
|
|
2081
|
-
return textElems;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
for (const elem of conflictingElements) {
|
|
2085
|
-
warning( null, [ elem.name.location, art ], { name: elem.name.id },
|
|
2086
|
-
'No texts entity can be created when element $(NAME) exists' );
|
|
2087
|
-
}
|
|
2088
|
-
return !textsEntity && !conflictingElements.length && textElems;
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
/**
|
|
2092
|
-
* TODO: set _parent also for main artifacts!
|
|
2093
|
-
*
|
|
2094
|
-
* @param {XSN.Artifact} base
|
|
2095
|
-
* @param {string} absolute
|
|
2096
|
-
* @param {XSN.Element[]} textElems
|
|
2097
|
-
* @param {boolean} fioriEnabled
|
|
2098
|
-
*/
|
|
2099
|
-
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
2100
|
-
const elements = Object.create(null);
|
|
2101
|
-
const { location } = base.name;
|
|
2102
|
-
const art = {
|
|
2103
|
-
kind: 'entity',
|
|
2104
|
-
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
2105
|
-
location: base.location,
|
|
2106
|
-
elements,
|
|
2107
|
-
$inferred: 'localized-entity',
|
|
2108
|
-
};
|
|
2109
|
-
const locale = {
|
|
2110
|
-
name: { location, id: 'locale' },
|
|
2111
|
-
kind: 'element',
|
|
2112
|
-
type: augmentPath( location, 'cds.String' ),
|
|
2113
|
-
length: { literal: 'number', val: 14, location },
|
|
2114
|
-
location,
|
|
2115
|
-
};
|
|
2116
|
-
|
|
2117
|
-
if (!fioriEnabled) {
|
|
2118
|
-
locale.key = { val: true, location };
|
|
2119
|
-
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
2120
|
-
// TODO (next major version): remove?
|
|
2121
|
-
annotateWith( art, '@odata.draft.enabled', art.location, false );
|
|
2122
|
-
}
|
|
2123
|
-
else {
|
|
2124
|
-
const textId = {
|
|
2125
|
-
name: { location, id: 'ID_texts' },
|
|
2126
|
-
kind: 'element',
|
|
2127
|
-
key: { val: true, location },
|
|
2128
|
-
type: augmentPath( location, 'cds.UUID' ),
|
|
2129
|
-
location,
|
|
2130
|
-
};
|
|
2131
|
-
dictAdd( art.elements, 'ID_texts', textId );
|
|
2132
|
-
}
|
|
2133
|
-
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
|
|
2134
|
-
setProp( art, '_base', base );
|
|
2135
|
-
|
|
2136
|
-
dictAdd( art.elements, 'locale', locale );
|
|
2137
|
-
if (addTextsLanguageAssoc) {
|
|
2138
|
-
const language = {
|
|
2139
|
-
name: { location, id: 'language' },
|
|
2140
|
-
kind: 'element',
|
|
2141
|
-
location,
|
|
2142
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
2143
|
-
target: augmentPath( location, 'sap.common.Languages' ),
|
|
2144
|
-
on: {
|
|
2145
|
-
op: { val: '=', location },
|
|
2146
|
-
args: [
|
|
2147
|
-
{ path: [ { id: 'language', location }, { id: 'code', location } ], location },
|
|
2148
|
-
{ path: [ { id: 'locale', location } ], location },
|
|
2149
|
-
],
|
|
2150
|
-
location,
|
|
2151
|
-
},
|
|
2152
|
-
};
|
|
2153
|
-
setProp( language, '_block', model.$internal );
|
|
2154
|
-
dictAdd( art.elements, 'language', language );
|
|
2155
|
-
}
|
|
2156
|
-
setLink( art, '_block', model.$internal );
|
|
2157
|
-
model.definitions[absolute] = art;
|
|
2158
|
-
initArtifact( art );
|
|
2159
|
-
|
|
2160
|
-
// assertUnique array value, first entry is 'locale'
|
|
2161
|
-
const assertUniqueValue = [ {
|
|
2162
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
2163
|
-
location: locale.location,
|
|
2164
|
-
} ];
|
|
2165
|
-
|
|
2166
|
-
for (const orig of textElems) {
|
|
2167
|
-
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
2168
|
-
if (orig.key && orig.key.val) {
|
|
2169
|
-
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
2170
|
-
// TODO: the previous would be better, but currently not supported in toCDL
|
|
2171
|
-
if (!fioriEnabled) {
|
|
2172
|
-
elem.key = { val: true, $inferred: 'localized', location };
|
|
2173
|
-
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
2174
|
-
// they should be omitted from OData containment EDM
|
|
2175
|
-
annotateWith( elem, '@odata.containment.ignore', location );
|
|
2176
|
-
}
|
|
2177
|
-
else {
|
|
2178
|
-
// add the former key paths to the unique constraint
|
|
2179
|
-
assertUniqueValue.push({
|
|
2180
|
-
path: [ { id: orig.name.id, location: orig.location } ],
|
|
2181
|
-
location: orig.location,
|
|
2182
|
-
});
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
2186
|
-
const localized = orig.localized || orig.type || orig.name;
|
|
2187
|
-
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
if (fioriEnabled)
|
|
2191
|
-
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
/**
|
|
2195
|
-
* @param {XSN.Artifact} art
|
|
2196
|
-
* @param {string} textsName
|
|
2197
|
-
* @param {XSN.Element[]} textElems
|
|
2198
|
-
*/
|
|
2199
|
-
function addTextsAssociations( art, textsName, textElems ) {
|
|
2200
|
-
// texts : Composition of many Books.texts on texts.ID=ID;
|
|
2201
|
-
/** @type {array} */
|
|
2202
|
-
const keys = textElems.filter( e => e.key && e.key.val );
|
|
2203
|
-
const { location } = art.name;
|
|
2204
|
-
const texts = {
|
|
2205
|
-
name: { location, id: 'texts' },
|
|
2206
|
-
kind: 'element',
|
|
2207
|
-
location,
|
|
2208
|
-
$inferred: 'localized',
|
|
2209
|
-
type: augmentPath( location, 'cds.Composition' ),
|
|
2210
|
-
cardinality: { targetMax: { literal: 'string', val: '*', location }, location },
|
|
2211
|
-
target: augmentPath( location, textsName ),
|
|
2212
|
-
on: augmentEqual( location, 'texts', keys ),
|
|
2213
|
-
};
|
|
2214
|
-
setMemberParent( texts, 'texts', art, 'elements' );
|
|
2215
|
-
setProp( texts, '_block', model.$internal );
|
|
2216
|
-
// localized : Association to Books.texts on
|
|
2217
|
-
// localized.ID=ID and localized.locale = $user.locale;
|
|
2218
|
-
keys.push( [ 'localized.locale', '$user.locale' ] );
|
|
2219
|
-
const localized = {
|
|
2220
|
-
name: { location, id: 'localized' },
|
|
2221
|
-
kind: 'element',
|
|
2222
|
-
location,
|
|
2223
|
-
$inferred: 'localized',
|
|
2224
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
2225
|
-
target: augmentPath( location, textsName ),
|
|
2226
|
-
on: augmentEqual( location, 'localized', keys ),
|
|
2227
|
-
};
|
|
2228
|
-
setMemberParent( localized, 'localized', art, 'elements' );
|
|
2229
|
-
setProp( localized, '_block', model.$internal );
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
/**
|
|
2233
|
-
* @param {XSN.Artifact} art
|
|
2234
|
-
* @param {string} prop
|
|
2235
|
-
*/
|
|
2236
|
-
function hasTruthyProp( art, prop ) {
|
|
2237
|
-
// Returns whether art directly or indirectly has the property 'prop',
|
|
2238
|
-
// following the 'origin' and the 'type' (not involving elements).
|
|
2239
|
-
//
|
|
2240
|
-
// TODO: we should issue a warning if we get localized via TYPE OF
|
|
2241
|
-
// TODO XSN: for anno short form, use { val: true, location, <no literal prop> }
|
|
2242
|
-
// ...then this function also works with annotations
|
|
2243
|
-
const processed = Object.create(null); // avoid infloops with circular refs
|
|
2244
|
-
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
2245
|
-
while (art && !processed[name]) {
|
|
2246
|
-
if (art[prop])
|
|
2247
|
-
return art[prop].val;
|
|
2248
|
-
processed[name] = art;
|
|
2249
|
-
if (art._origin) {
|
|
2250
|
-
art = art._origin;
|
|
2251
|
-
name = art && art.name.absolute;
|
|
2252
|
-
}
|
|
2253
|
-
else if (art.type && art._block && art.type.scope !== 'typeOf') {
|
|
2254
|
-
// TODO: also do something special for TYPE OF inside `art`s own elements
|
|
2255
|
-
name = resolveUncheckedPath( art.type, 'type', art );
|
|
2256
|
-
art = name && model.definitions[name];
|
|
2257
|
-
}
|
|
2258
|
-
else {
|
|
2259
|
-
return false;
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
return false;
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
/**
|
|
2267
|
-
* Merge (optional) translations into the XSN model.
|
|
2268
|
-
*
|
|
2269
|
-
* @param {XSN.Model} model
|
|
2270
|
-
*/
|
|
2271
|
-
function mergeI18nBlocks( model ) {
|
|
2272
|
-
const sortedSources = Object.keys(model.sources)
|
|
2273
|
-
.filter(name => !!model.sources[name].i18n)
|
|
2274
|
-
.sort( (a, b) => compareLayer( model.sources[a], model.sources[b] ) );
|
|
2275
|
-
|
|
2276
|
-
if (sortedSources.length === 0)
|
|
2277
|
-
return;
|
|
2278
|
-
|
|
2279
|
-
if (!model.i18n)
|
|
2280
|
-
model.i18n = Object.create( null );
|
|
2281
|
-
|
|
2282
|
-
for (const name of sortedSources)
|
|
2283
|
-
initI18nFromSource( model.sources[name] );
|
|
2284
|
-
|
|
2285
|
-
/**
|
|
2286
|
-
* Add the source's translations to the model. Warns if the sources translations
|
|
2287
|
-
* do not match the ones from previous sources.
|
|
2288
|
-
*
|
|
2289
|
-
* @param {XSN.AST} src
|
|
2290
|
-
*/
|
|
2291
|
-
function initI18nFromSource( src ) {
|
|
2292
|
-
for (const langKey of Object.keys( src.i18n )) {
|
|
2293
|
-
if (!model.i18n[langKey])
|
|
2294
|
-
model.i18n[langKey] = Object.create( null );
|
|
2295
|
-
|
|
2296
|
-
for (const textKey of Object.keys( src.i18n[langKey] )) {
|
|
2297
|
-
const sourceVal = src.i18n[langKey][textKey];
|
|
2298
|
-
const modelVal = model.i18n[langKey][textKey];
|
|
2299
|
-
if (!modelVal) {
|
|
2300
|
-
model.i18n[langKey][textKey] = sourceVal;
|
|
2301
|
-
}
|
|
2302
|
-
else if (modelVal.val !== sourceVal.val) {
|
|
2303
|
-
// TODO: put mergeI18nBlocks() into main function instead
|
|
2304
|
-
model.$messageFunctions.warning( 'i18n-different-value', sourceVal.location,
|
|
2305
|
-
{ prop: textKey, otherprop: langKey } );
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
|
-
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
2313
|
-
const args = relations.map( eq );
|
|
2314
|
-
return (args.length === 1)
|
|
2315
|
-
? args[0]
|
|
2316
|
-
: { op: { val: 'and', location }, args, location };
|
|
2317
|
-
|
|
2318
|
-
function eq( refs ) {
|
|
2319
|
-
if (Array.isArray(refs))
|
|
2320
|
-
return { op: { val: '=', location }, args: refs.map( ref ), location };
|
|
2321
|
-
|
|
2322
|
-
const { id } = refs.name;
|
|
2323
|
-
return {
|
|
2324
|
-
op: { val: '=', location },
|
|
2325
|
-
args: [
|
|
2326
|
-
{ path: [ { id: assocname, location }, { id, location } ], location },
|
|
2327
|
-
{ path: [ { id: `${ prefix }${ id }`, location } ], location },
|
|
2328
|
-
],
|
|
2329
|
-
location,
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
function ref( path ) {
|
|
2333
|
-
return { path: path.split('.').map( id => ({ id, location }) ), location };
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// these function could be used to a future lib/compiler/utils.js, but DO NOT
|
|
2338
|
-
// SHARE with utility functions for CSN processors
|
|
2339
|
-
|
|
2340
|
-
module.exports = { define };
|