@sap/cds-compiler 3.8.0 → 3.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +26 -5
- package/lib/api/.eslintrc.json +3 -2
- package/lib/api/options.js +3 -1
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +27 -18
- package/lib/base/messages.js +6 -1
- package/lib/base/model.js +2 -2
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +6 -6
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +28 -17
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +11 -6
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +3 -2
- package/lib/compiler/assert-consistency.js +8 -3
- package/lib/compiler/base.js +19 -13
- package/lib/compiler/builtins.js +7 -0
- package/lib/compiler/checks.js +73 -6
- package/lib/compiler/define.js +10 -5
- package/lib/compiler/extend.js +924 -1709
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +838 -0
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +20 -8
- package/lib/compiler/resolve.js +3 -3
- package/lib/compiler/shared.js +11 -6
- package/lib/edm/annotations/genericTranslation.js +6 -6
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +25 -11
- package/lib/edm/edmPreprocessor.js +47 -23
- package/lib/edm/edmUtils.js +37 -9
- package/lib/gen/Dictionary.json +5 -7
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +24 -23
- package/lib/gen/languageLexer.interp +4 -1
- package/lib/gen/languageLexer.js +792 -784
- package/lib/gen/languageLexer.tokens +12 -11
- package/lib/gen/languageParser.js +3944 -3865
- package/lib/json/from-csn.js +27 -6
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +4 -2
- package/lib/language/language.g4 +32 -24
- package/lib/model/csnRefs.js +15 -7
- package/lib/model/csnUtils.js +41 -76
- package/lib/modelCompare/utils/.eslintrc.json +1 -1
- package/lib/optionProcessor.js +7 -4
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/toCdl.js +244 -168
- package/lib/render/toHdbcds.js +18 -10
- package/lib/render/toSql.js +24 -2
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +11 -6
- package/lib/transform/db/flattening.js +22 -15
- package/lib/transform/db/rewriteCalculatedElements.js +50 -29
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/views.js +1 -1
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +3 -4
- package/lib/transform/forOdataNew.js +5 -6
- package/lib/transform/forRelationalDB.js +7 -7
- package/lib/transform/odata/toFinalBaseType.js +6 -6
- package/lib/transform/odata/typesExposure.js +12 -3
- package/lib/transform/odata/utils.js +3 -0
- package/lib/transform/transformUtilsNew.js +11 -26
- package/lib/transform/translateAssocsToJoins.js +9 -9
- package/lib/transform/universalCsn/.eslintrc.json +3 -2
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
- package/package.json +1 -1
package/lib/compiler/extend.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
// Extend
|
|
2
|
-
|
|
3
|
-
// Is handled together in this file because people want to extend the generated
|
|
4
|
-
// definitions in the future.
|
|
1
|
+
// Extend
|
|
5
2
|
|
|
6
3
|
'use strict';
|
|
7
4
|
|
|
8
5
|
const { searchName, weakLocation } = require('../base/messages');
|
|
9
6
|
const {
|
|
10
|
-
|
|
11
|
-
forEachGeneric, forEachInOrder, forEachDefinition,
|
|
7
|
+
forEachInOrder, forEachDefinition,
|
|
12
8
|
forEachMember,
|
|
13
9
|
isBetaEnabled,
|
|
14
10
|
} = require('../base/model');
|
|
@@ -18,15 +14,10 @@ const {
|
|
|
18
14
|
setLink,
|
|
19
15
|
setArtifactLink,
|
|
20
16
|
copyExpr,
|
|
21
|
-
setAnnotation,
|
|
22
17
|
setExpandStatusAnnotate,
|
|
23
18
|
linkToOrigin,
|
|
24
|
-
setMemberParent,
|
|
25
19
|
dependsOnSilent,
|
|
26
|
-
augmentPath,
|
|
27
20
|
pathName,
|
|
28
|
-
splitIntoPath,
|
|
29
|
-
isDirectComposition,
|
|
30
21
|
annotationHasEllipsis,
|
|
31
22
|
} = require('./utils');
|
|
32
23
|
const layers = require('./moduleLayers');
|
|
@@ -35,6 +26,8 @@ const $location = Symbol.for('cds.$location');
|
|
|
35
26
|
|
|
36
27
|
const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
|
|
37
28
|
|
|
29
|
+
// Array.prototype.spread = 42; // prototype-polluted JS classes
|
|
30
|
+
|
|
38
31
|
function extend( model ) {
|
|
39
32
|
const { options } = model;
|
|
40
33
|
// Get simplified "resolve" functionality and the message function:
|
|
@@ -46,84 +39,24 @@ function extend( model ) {
|
|
|
46
39
|
resolveUncheckedPath,
|
|
47
40
|
resolveTypeArgumentsUnchecked,
|
|
48
41
|
attachAndEmitValidNames,
|
|
49
|
-
initArtifact,
|
|
50
42
|
initMembers,
|
|
51
43
|
} = model.$functions;
|
|
52
44
|
|
|
53
45
|
Object.assign( model.$functions, {
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
createRemainingAnnotateStatements,
|
|
47
|
+
extendArtifactBefore,
|
|
56
48
|
extendArtifactAfter,
|
|
49
|
+
applyIncludes, // TODO: re-check
|
|
57
50
|
} );
|
|
58
51
|
|
|
59
|
-
|
|
52
|
+
sortModelSources();
|
|
53
|
+
const extensionsDict = Object.create(null); // TODO TMP
|
|
60
54
|
forEachDefinition( model, tagIncludes ); // TODO TMP
|
|
61
55
|
|
|
62
|
-
forEachDefinition( model,
|
|
63
|
-
applyExtensions();
|
|
64
|
-
|
|
65
|
-
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
66
|
-
const useTextsAspect = checkTextsAspect();
|
|
67
|
-
|
|
68
|
-
Object.keys( model.definitions ).forEach( processArtifact );
|
|
69
|
-
|
|
70
|
-
compositionChildPersistence();
|
|
56
|
+
forEachDefinition( model, extendArtifactBefore );
|
|
57
|
+
applyExtensions(); // old-style
|
|
71
58
|
return;
|
|
72
59
|
|
|
73
|
-
/**
|
|
74
|
-
* Process "composition of" artifacts.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} name
|
|
77
|
-
*/
|
|
78
|
-
function processArtifact( name ) {
|
|
79
|
-
const art = model.definitions[name];
|
|
80
|
-
if (!(art.$duplicates)) {
|
|
81
|
-
processAspectComposition( art );
|
|
82
|
-
if (art.kind === 'entity' && !art.query && art.elements)
|
|
83
|
-
// check potential entity parse error
|
|
84
|
-
processLocalizedData( art );
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
90
|
-
* if they have the property.
|
|
91
|
-
*
|
|
92
|
-
* @param {XSN.Definition} art
|
|
93
|
-
* @param {string} prop
|
|
94
|
-
*/
|
|
95
|
-
function propagateEarly( art, prop ) {
|
|
96
|
-
if (art[prop])
|
|
97
|
-
return;
|
|
98
|
-
for (const ref of art.includes) {
|
|
99
|
-
const aspect = ref._artifact;
|
|
100
|
-
if (aspect) {
|
|
101
|
-
const anno = aspect[prop];
|
|
102
|
-
if (anno && (anno.val !== null || !art[prop]))
|
|
103
|
-
art[prop] = Object.assign( { $inferred: 'include' }, anno );
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
|
|
110
|
-
* for managed compositions. This needs to be done after extensions, i.e. annotations,
|
|
111
|
-
* have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
|
|
112
|
-
*/
|
|
113
|
-
function compositionChildPersistence() {
|
|
114
|
-
const processed = new WeakSet();
|
|
115
|
-
forEachDefinition(model, processCompositionPersistence);
|
|
116
|
-
|
|
117
|
-
function processCompositionPersistence( def ) {
|
|
118
|
-
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
119
|
-
if (def._parent)
|
|
120
|
-
processCompositionPersistence(def._parent);
|
|
121
|
-
copyPersistenceAnnotations( def, def._parent );
|
|
122
|
-
processed.add(def);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
60
|
// TMP:
|
|
128
61
|
function tagIncludes( art ) {
|
|
129
62
|
if (art.includes)
|
|
@@ -135,6 +68,43 @@ function extend( model ) {
|
|
|
135
68
|
//-----------------------------------------------------------------------------
|
|
136
69
|
// extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements
|
|
137
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
73
|
+
* if multiple exist according to the module layer.
|
|
74
|
+
* TODO: update comment if extension algorithm is finished
|
|
75
|
+
*
|
|
76
|
+
* @param {XSN.Artifact} art
|
|
77
|
+
*/
|
|
78
|
+
function extendArtifactBefore( art ) {
|
|
79
|
+
// for main artifacts, move extensions from `$collectedExtensions` model dictionary:
|
|
80
|
+
if (!art._main && !art._outer && art._extensions === undefined &&
|
|
81
|
+
art.kind !== 'namespace') {
|
|
82
|
+
// if (!art.name) console.log(art)
|
|
83
|
+
const { absolute } = art.name;
|
|
84
|
+
setLink( art, '_extensions', model.$collectedExtensions[absolute]?._extensions || null );
|
|
85
|
+
if (art._extensions && !art.builtin) { // keep extensions for builtin in $collectedExtensions
|
|
86
|
+
delete model.$collectedExtensions[absolute];
|
|
87
|
+
// TODO: if the extension mechanism has been completed, we could uncomment:
|
|
88
|
+
// art._extensions.forEach( ext => resolvePath( ext.name, ext.kind, ext )); // for LSP
|
|
89
|
+
// for now, we do that at the end of createRemainingAnnotateStatements()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (art._extensions) {
|
|
93
|
+
// TODO: the following function can now be simplified
|
|
94
|
+
// if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
|
|
95
|
+
// With extensions, member appears in CSN, affects directly the rendering of
|
|
96
|
+
// elements etc. TODO: do that more specifically on the dicts (via symbol)
|
|
97
|
+
// Probably better: we could use the _extensions dict prop directly in to-csn
|
|
98
|
+
if (art.$inferred)
|
|
99
|
+
setExpandStatusAnnotate( art, 'annotate' );
|
|
100
|
+
if (Array.isArray( art._extensions )) {
|
|
101
|
+
checkExtensionsKind( art._extensions, art ); // TODO: check with builtins
|
|
102
|
+
transformArtifactExtensions( art );
|
|
103
|
+
}
|
|
104
|
+
applyAllExtensions( art );
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
138
108
|
// TODO: assert that we have not yet transformed/used _extensions on sub elements
|
|
139
109
|
// TODO necessary(?): transformArtifactExtensions must ensure that each annotate
|
|
140
110
|
// is in either returns,items,elements,enum
|
|
@@ -184,1710 +154,993 @@ function extend( model ) {
|
|
|
184
154
|
/**
|
|
185
155
|
* Create super annotate statements for remaining extensions
|
|
186
156
|
*/
|
|
187
|
-
function
|
|
188
|
-
model.extensions = Object.values( model.$
|
|
157
|
+
function createRemainingAnnotateStatements() {
|
|
158
|
+
model.extensions = Object.values( model.$collectedExtensions );
|
|
189
159
|
// TODO: testMode sort?
|
|
190
160
|
model.extensions.forEach( createSuperAnnotate );
|
|
161
|
+
// set _artifact links for “main extensions” late as it would disturb the
|
|
162
|
+
// still existing old extend mechanism, see extendArtifactBefore(),
|
|
163
|
+
// needed for LSP and friends:
|
|
164
|
+
Object.values( model.sources ).forEach( setArtifactLinkForExtensions );
|
|
165
|
+
Object.values( model.definitions ).forEach( setArtifactLinkForExtensions );
|
|
191
166
|
}
|
|
192
167
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// We then report (in the future), use the first message of:
|
|
202
|
-
// - the usual messages if a type argument is wrong, independently from `extend`
|
|
203
|
-
// - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
|
|
204
|
-
// - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
|
|
205
|
-
//
|
|
206
|
-
// TODO v4: do not allow `extend … with (precision: …)` alone if original def also has `scale`
|
|
207
|
-
function applyTypeExtensions( art, ext, prop, scaleDiff ) {
|
|
208
|
-
// console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
|
|
209
|
-
if (!ext?.[prop])
|
|
210
|
-
return 0;
|
|
211
|
-
if (!art[prop]) {
|
|
212
|
-
const isBuiltin = art._effectiveType?.builtin;
|
|
213
|
-
if (isBuiltin && !allowsTypeArgument( art, prop )) {
|
|
214
|
-
// Let checkTypeArguments() in resolve.js report a message, is incomplete
|
|
215
|
-
// though, i.e. can only safely be used for scalars at the moment. But we
|
|
216
|
-
// will improve that function and not try to do extra things here.
|
|
217
|
-
art[prop] = ext[prop]; // enable checkTypeArguments() doing its job
|
|
218
|
-
return 0;
|
|
219
|
-
}
|
|
220
|
-
// TODO: think about 'ext-unexpected-type-argument'
|
|
221
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
222
|
-
{ '#': (isBuiltin ? 'indirect' : 'new-prop'), prop } );
|
|
223
|
-
return 0;
|
|
224
|
-
}
|
|
225
|
-
const artVal = art[prop].val;
|
|
226
|
-
const extVal = ext[prop].val;
|
|
227
|
-
if (prop === 'srid') {
|
|
228
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'prop', prop } );
|
|
229
|
-
}
|
|
230
|
-
else if (typeof artVal !== 'number' || typeof extVal !== 'number' ) {
|
|
231
|
-
// Users can't change from/to string value for property,
|
|
232
|
-
// e.g. `variable`/`floating` for Decimal
|
|
233
|
-
// TODO: Shouldn't the text distinguish between orig string and extension string?
|
|
234
|
-
// Not sure whether to talk about strings if we have a keyword in CDL
|
|
235
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'string', prop } );
|
|
236
|
-
}
|
|
237
|
-
else if (extVal < artVal + (scaleDiff || 0)) {
|
|
238
|
-
const number = artVal + (scaleDiff || 0);
|
|
239
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
240
|
-
// eslint-disable-next-line object-curly-newline
|
|
241
|
-
{ '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale' } );
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
art[prop] = ext[prop];
|
|
245
|
-
return extVal - artVal;
|
|
168
|
+
// TODO: delete again
|
|
169
|
+
function setArtifactLinkForExtensions( source ) {
|
|
170
|
+
if (!source.extensions)
|
|
171
|
+
return;
|
|
172
|
+
for (const ext of source.extensions ) {
|
|
173
|
+
const { name } = ext;
|
|
174
|
+
if (name?.absolute && name._artifact === undefined) // no link set yet
|
|
175
|
+
resolvePath( name, ext.kind, ext ); // for LSP
|
|
246
176
|
}
|
|
247
|
-
return 0;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function allowsTypeArgument( art, prop ) {
|
|
251
|
-
const { parameters } = art._effectiveType;
|
|
252
|
-
if (!parameters)
|
|
253
|
-
return false;
|
|
254
|
-
return parameters.includes( prop ) || parameters[0]?.name === prop;
|
|
255
177
|
}
|
|
256
178
|
|
|
257
|
-
|
|
258
|
-
// TODO: setExpandStatusAnnotate
|
|
259
|
-
const extensions = extensionsMap[extProp];
|
|
260
|
-
if (!extensions)
|
|
261
|
-
return;
|
|
262
|
-
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
179
|
+
// For extendArtifactBefore(): ------------------------------------------------
|
|
263
180
|
|
|
181
|
+
function checkExtensionsKind( extensions, art ) {
|
|
264
182
|
for (const ext of extensions) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
183
|
+
const kind = ext.expectedKind?.val;
|
|
184
|
+
if (kind && kind !== art.kind) {
|
|
185
|
+
const loc = ext.expectedKind.location;
|
|
186
|
+
if (kind === 'context' || kind === 'service') {
|
|
187
|
+
// We have no real artifact during the construction of a super-annotate statement:
|
|
188
|
+
const msgArgs = {
|
|
189
|
+
'#': (art.kind === 'service' || art.kind === 'annotate') ? art.kind : 'std',
|
|
190
|
+
art,
|
|
191
|
+
kind,
|
|
192
|
+
code: 'extend … with definitions',
|
|
193
|
+
keyword: 'extend service',
|
|
194
|
+
};
|
|
195
|
+
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
196
|
+
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
197
|
+
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
198
|
+
// do not mention 'extend context', that is not in CAPire
|
|
199
|
+
service: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) or $(KEYWORD) instead',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
|
|
275
203
|
}
|
|
276
204
|
}
|
|
277
205
|
}
|
|
278
206
|
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
207
|
+
// TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
|
|
208
|
+
function transformArtifactExtensions( art ) {
|
|
209
|
+
const hasOnlySubExtensions = art._outer; // items, anonymous aspects
|
|
210
|
+
const dict = Object.create(null);
|
|
211
|
+
for (const ext of art._extensions) {
|
|
212
|
+
for (const prop in ext) {
|
|
213
|
+
if (ext[prop] === undefined) // deleted propery
|
|
214
|
+
continue;
|
|
215
|
+
// TODO: do this check nicer (after complete move to new extensions mechanism)
|
|
216
|
+
if (prop.charAt(0) === '@' || prop === 'doc' ||
|
|
217
|
+
prop === 'includes' || prop === 'columns' ||
|
|
218
|
+
prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
|
|
219
|
+
if (!hasOnlySubExtensions)
|
|
220
|
+
pushToDict( dict, prop, ext );
|
|
221
|
+
}
|
|
222
|
+
else if (prop === 'elements' || prop === 'enum' || prop === 'actions' ||
|
|
223
|
+
prop === 'params' || prop === 'returns') {
|
|
224
|
+
if (ext.kind === 'extend')
|
|
225
|
+
pushToDict( dict, 'includes', ext );
|
|
226
|
+
pushToDict( dict, prop, ext );
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
art._extensions = dict;
|
|
231
|
+
}
|
|
286
232
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Sort sources according to the reversed layered extension order without
|
|
235
|
+
* reporting any messages.
|
|
236
|
+
*
|
|
237
|
+
* The order of the CSN property `$sources` (from XSN `_sortedSources`) is
|
|
238
|
+
* defined as follows: for _any_ model
|
|
239
|
+
*
|
|
240
|
+
* - add `type $Sources: String @(Names: []);` to one of the source files
|
|
241
|
+
* - add `annotate $Sources with @Names: [..., ‹sourceName›]` to each source
|
|
242
|
+
* file where ‹sourceName› is the file name of the source
|
|
243
|
+
* - then the array value of `‹csn›.$sources` is the reverse of the array value
|
|
244
|
+
* of `‹csn›.definitions.$Sources.@Names`
|
|
245
|
+
*/
|
|
246
|
+
function sortModelSources() {
|
|
247
|
+
const scheduled = [];
|
|
248
|
+
const layered = layeredExtensions( Object.values( model.sources ) );
|
|
249
|
+
for (;;) {
|
|
250
|
+
const { highest } = extensionsOfHighestLayers( layered );
|
|
251
|
+
if (!highest.length)
|
|
252
|
+
break;
|
|
253
|
+
highest.reverse();
|
|
254
|
+
scheduled.push( ...highest );
|
|
255
|
+
}
|
|
256
|
+
setLink( model, '_sortedSources', scheduled );
|
|
295
257
|
}
|
|
296
258
|
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
259
|
+
function applyAllExtensions( art ) {
|
|
260
|
+
const extensions = art._extensions;
|
|
261
|
+
for (const prop in extensions) {
|
|
262
|
+
// TODO: do the following `if` in a nicer way
|
|
263
|
+
if ([ 'elements', 'enum', 'actions', 'params', 'returns' ].includes( prop ))
|
|
264
|
+
continue; // currently just annotates on sub elements - TODO: error here
|
|
265
|
+
// annotations, `doc`, `includes`, `columns`, `length`, ...
|
|
266
|
+
const scheduled = [];
|
|
267
|
+
// sort extensions according to layer (specified elements are bottom layer):
|
|
268
|
+
const layered = layeredExtensions( extensions[prop] );
|
|
304
269
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
270
|
+
let cont = true;
|
|
271
|
+
while (cont) {
|
|
272
|
+
const { highest, issue } = extensionsOfHighestLayers( layered );
|
|
273
|
+
// console.log( 'CA:', annoName, issue, extensions)
|
|
274
|
+
let index = highest.length;
|
|
275
|
+
cont = !!index; // safety
|
|
276
|
+
while (--index >= 0) {
|
|
277
|
+
const ext = highest[index];
|
|
278
|
+
scheduled.push( ext );
|
|
279
|
+
if (extensionOverwrites( ext, prop )) {
|
|
280
|
+
cont = false;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (issue || index > 0)
|
|
285
|
+
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
286
|
+
}
|
|
287
|
+
// Now apply the relevant extensions
|
|
288
|
+
scheduled.reverse();
|
|
289
|
+
for (const ext of scheduled)
|
|
290
|
+
applySingleExtension( art, ext, prop );
|
|
291
|
+
delete extensions[prop];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
310
294
|
|
|
311
|
-
|
|
312
|
-
return
|
|
295
|
+
function extensionOverwrites( ext, prop ) {
|
|
296
|
+
return (prop.charAt(0) !== '@')
|
|
297
|
+
? [ 'doc', 'length', 'precision', 'scale', 'srid' ].includes( prop )
|
|
298
|
+
: !annotationHasEllipsis( ext[prop] );
|
|
313
299
|
}
|
|
314
300
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
for (const ext of extensions || []) {
|
|
335
|
-
if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
|
|
336
|
-
ext.$syntax = '$inside-returns';
|
|
337
|
-
delete ext.params;
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
warning( 'ext-expected-returns', [ ext.name.location, ext ], {
|
|
341
|
-
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
342
|
-
}, {
|
|
343
|
-
std: 'Expected $(CODE)', // unused variant
|
|
344
|
-
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
345
|
-
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
346
|
-
} );
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// const unexpected_props = {
|
|
352
|
-
// elements: 'anno-unexpected-elements',
|
|
353
|
-
// enum: 'anno-unexpected-elements', // TODO
|
|
354
|
-
// params: 'anno-unexpected-params',
|
|
355
|
-
// actions: 'anno-unexpected-actions',
|
|
356
|
-
// };
|
|
357
|
-
// const undefined_props = {
|
|
358
|
-
// elements: 'anno-undefined-element',
|
|
359
|
-
// enum: 'anno-undefined-element', // TODO
|
|
360
|
-
// params: 'anno-undefined-param',
|
|
361
|
-
// actions: 'anno-undefined-action',
|
|
362
|
-
// };
|
|
363
|
-
|
|
364
|
-
function checkRemainingMemberExtensions( parent, ext, prop, name ) {
|
|
365
|
-
// console.log('CRME:',prop,name,parent,ext)
|
|
366
|
-
const dict = parent[prop];
|
|
367
|
-
if (!dict) {
|
|
368
|
-
// TODO: check - for each name? - better locations
|
|
369
|
-
const location = ext._parent[prop][$location] || ext.name.location;
|
|
370
|
-
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
371
|
-
switch (prop) {
|
|
372
|
-
// TODO: change texts, somehow similar to checkDefinitions() ?
|
|
373
|
-
case 'elements':
|
|
374
|
-
case 'enum': // TODO: extra?
|
|
375
|
-
warning( 'anno-unexpected-elements', [ location, ext._parent ],
|
|
376
|
-
{ '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
|
|
377
|
-
std: 'Elements only exist in entities, types or typed constructs',
|
|
378
|
-
entity: 'Elements of entity types can\'t be annotated',
|
|
379
|
-
});
|
|
380
|
-
break;
|
|
381
|
-
case 'params':
|
|
382
|
-
warning( 'anno-unexpected-params', [ location, ext._parent ], {},
|
|
383
|
-
'Parameters only exist for actions or functions' );
|
|
384
|
-
break;
|
|
385
|
-
case 'actions':
|
|
386
|
-
warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
|
|
387
|
-
'Actions and functions only exist top-level and for entities' );
|
|
388
|
-
break;
|
|
389
|
-
default:
|
|
390
|
-
// assert
|
|
391
|
-
}
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
else if (!dict[name]) {
|
|
395
|
-
// TODO: make variant `returns` an auto-variant for ($ART) ?
|
|
396
|
-
const inReturns = parent._parent?.returns && parent._parent;
|
|
397
|
-
const art = inReturns || parent;
|
|
398
|
-
switch (prop) {
|
|
399
|
-
case 'elements':
|
|
400
|
-
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
401
|
-
{ '#': (inReturns ? 'returns' : 'element'), art, name },
|
|
402
|
-
parent.elements );
|
|
403
|
-
break;
|
|
404
|
-
case 'enum': // TODO: extra msg id?
|
|
405
|
-
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
406
|
-
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
407
|
-
parent.enum );
|
|
408
|
-
break;
|
|
409
|
-
case 'params':
|
|
410
|
-
notFound( 'anno-undefined-param', ext.name.location, ext,
|
|
411
|
-
{ '#': 'param', art: parent, name },
|
|
412
|
-
parent.params );
|
|
413
|
-
break;
|
|
414
|
-
case 'actions':
|
|
415
|
-
notFound( 'anno-undefined-action', ext.name.location, ext,
|
|
416
|
-
{ '#': 'action', art: parent, name },
|
|
417
|
-
parent.actions );
|
|
418
|
-
break;
|
|
419
|
-
default:
|
|
420
|
-
// assert
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function notFound( msgId, location, address, args, validDict ) {
|
|
427
|
-
const msg = message( msgId, [ location, address ], args );
|
|
428
|
-
attachAndEmitValidNames( msg, validDict );
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// For createRemainingAnnotateStatements(): -----------------------------------
|
|
432
|
-
|
|
433
|
-
function createSuperAnnotate( annotate ) {
|
|
434
|
-
const extensions = annotate._extensions;
|
|
435
|
-
if (extensions && !annotate._main) {
|
|
436
|
-
const { absolute } = annotate.name;
|
|
437
|
-
const isLocalized = absolute.startsWith( 'localized.' ); // TODO: && anno
|
|
438
|
-
const art = model.definitions[absolute];
|
|
439
|
-
for (const ext of extensions)
|
|
440
|
-
checkRemainingMainExtensions( art, ext, isLocalized );
|
|
441
|
-
if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
|
|
442
|
-
setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
|
|
443
|
-
// direct annotations on builtins or on the builtins for propagation, and
|
|
444
|
-
// also shallow-copied to $collectedExtensions for to-csn
|
|
445
|
-
for (const prop in art) {
|
|
446
|
-
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
447
|
-
annotate[prop] = art[prop];
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
|
|
451
|
-
annotate.location = extensions[0].location;
|
|
452
|
-
annotate.name.location = extensions[0].name.location;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
chooseAnnotationsInArtifact( annotate );
|
|
456
|
-
extendArtifactAfter( annotate );
|
|
457
|
-
forEachMember( annotate, createSuperAnnotate );
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
461
|
-
if (localized) // TODO v4: ignore only for annotate
|
|
462
|
-
return;
|
|
463
|
-
if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
|
|
464
|
-
return;
|
|
465
|
-
// else if (ext.kind === 'extend') { // TODO v4 - add error
|
|
466
|
-
// }
|
|
467
|
-
if (art?.kind === 'namespace') {
|
|
468
|
-
// TODO: not at all different to having no definition
|
|
469
|
-
info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
470
|
-
'Namespaces can\'t be annotated' );
|
|
471
|
-
}
|
|
472
|
-
else if (art?.builtin) {
|
|
473
|
-
info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
474
|
-
'Builtin types should not be annotated. Use custom type instead' );
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Issue messages for annotations on namespaces and builtins
|
|
479
|
-
// (TODO: really here?, probably split main artifacts vs returns)
|
|
480
|
-
// see also lateExtensions() where similar messages are reported
|
|
481
|
-
function checkAnnotate( construct, art ) {
|
|
482
|
-
// TODO: Handle extend statements properly: Different message for empty extend?
|
|
483
|
-
|
|
484
|
-
// --> without art._block, art not found
|
|
485
|
-
if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
|
|
486
|
-
if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
|
|
487
|
-
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
|
|
488
|
-
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
489
|
-
// `art._block` ensures that `art` is a defined def.
|
|
490
|
-
return;
|
|
491
|
-
// warning('ext-unexpected-returns', [ construct.name.location, construct ],
|
|
492
|
-
// { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
|
|
493
|
-
}
|
|
494
|
-
else if (construct.$syntax !== 'returns' &&
|
|
495
|
-
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
|
|
496
|
-
warning('ext-expected-returns', [ construct.name.location, construct ], {
|
|
497
|
-
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
498
|
-
}, {
|
|
499
|
-
std: 'Expected $(CODE)', // unused variant
|
|
500
|
-
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
501
|
-
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// extend ------------------------------------------------------------------
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Apply the extensions inside the extensionsDict on the model.
|
|
511
|
-
*
|
|
512
|
-
* First try normally: extends with structure includes; with remaining cyclic
|
|
513
|
-
* includes, do so without includes.
|
|
514
|
-
*/
|
|
515
|
-
function applyExtensions() {
|
|
516
|
-
let noIncludes = false;
|
|
517
|
-
let extNames = Object.keys( extensionsDict ).sort();
|
|
518
|
-
|
|
519
|
-
while (extNames.length) {
|
|
520
|
-
const { length } = extNames;
|
|
521
|
-
for (const name of extNames) {
|
|
522
|
-
const art = model.definitions[name];
|
|
523
|
-
if (art && art.kind !== 'namespace' &&
|
|
524
|
-
extendArtifact( extensionsDict[name], art, noIncludes ))
|
|
525
|
-
delete extensionsDict[name];
|
|
526
|
-
}
|
|
527
|
-
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
528
|
-
if (extNames.length >= length)
|
|
529
|
-
noIncludes = Object.keys( extensionsDict ); // = no includes
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function checkExtensionsKind( extensions, art ) {
|
|
534
|
-
for (const ext of extensions) {
|
|
535
|
-
const kind = ext.expectedKind?.val;
|
|
536
|
-
if (kind && kind !== art.kind) {
|
|
537
|
-
const loc = ext.expectedKind.location;
|
|
538
|
-
if (kind === 'context' || kind === 'service') {
|
|
539
|
-
// We have no real artifact during the construction of a super-annotate statement:
|
|
540
|
-
const msgArgs = {
|
|
541
|
-
'#': (art.kind === 'service' || art.kind === 'annotate') ? art.kind : 'std',
|
|
542
|
-
art,
|
|
543
|
-
kind,
|
|
544
|
-
code: 'extend … with definitions',
|
|
545
|
-
keyword: 'extend service',
|
|
546
|
-
};
|
|
547
|
-
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
548
|
-
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
549
|
-
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
550
|
-
// do not mention 'extend context', that is not in CAPire
|
|
551
|
-
service: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) or $(KEYWORD) instead',
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
// TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Extend artifact `art` by `extensions`. `noIncludes` can have values:
|
|
561
|
-
* - false: includes are applied, extend and annotate is performed
|
|
562
|
-
* - true: includes are not applied, extend and annotate is performed
|
|
563
|
-
* - 'gen': no includes and no extensions allowed, annotate is performed
|
|
564
|
-
*
|
|
565
|
-
* @param {XSN.Extension[]} extensions
|
|
566
|
-
* @param {XSN.Definition} art
|
|
567
|
-
* @param {boolean|'gen'} [noIncludes=false]
|
|
568
|
-
*/
|
|
569
|
-
function extendArtifact( extensions, art, noIncludes = false ) {
|
|
570
|
-
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
571
|
-
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
572
|
-
return false;
|
|
573
|
-
if (Array.isArray( noIncludes )) {
|
|
574
|
-
canApplyIncludes( art, art, noIncludes );
|
|
575
|
-
extensions.forEach( ext => canApplyIncludes( ext, art, noIncludes ) );
|
|
576
|
-
}
|
|
577
|
-
else if (!noIncludes &&
|
|
578
|
-
!(canApplyIncludes( art, art ) &&
|
|
579
|
-
extensions.every( ext => canApplyIncludes( ext, art) ))) {
|
|
580
|
-
// console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
|
|
581
|
-
return false;
|
|
582
|
-
}
|
|
583
|
-
if (!art.query) {
|
|
584
|
-
model._entities.push( art ); // add structure with includes in dep order
|
|
585
|
-
art.$entity = ++model.$entity;
|
|
586
|
-
}
|
|
587
|
-
if (!noIncludes && art.includes)
|
|
588
|
-
applyIncludes( art, art );
|
|
589
|
-
// checkExtensionsKind( extensions, art );
|
|
590
|
-
extendMembers( extensions, art, noIncludes === 'gen' );
|
|
591
|
-
if (!noIncludes && art.includes) {
|
|
592
|
-
// early propagation of specific annotation assignments
|
|
593
|
-
propagateEarly( art, '@cds.autoexpose' );
|
|
594
|
-
propagateEarly( art, '@fiori.draft.enabled' );
|
|
595
|
-
}
|
|
596
|
-
// TODO: complain about element extensions inside projection
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function extendMembers( extensions, art, noExtend ) {
|
|
601
|
-
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
602
|
-
const elemExtensions = [];
|
|
603
|
-
if (art._main) // extensions already sorted for main artifacts
|
|
604
|
-
extensions.sort( layers.compareLayer );
|
|
605
|
-
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
606
|
-
// console.log('EM:',art.name,extensions,art._extensions)
|
|
607
|
-
for (const ext of extensions) { // those in extMap.includes
|
|
608
|
-
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
609
|
-
// 'Info', 'EXT').toString())
|
|
610
|
-
if (ext.name._artifact === undefined) { // not already applied
|
|
611
|
-
setArtifactLink( ext.name, art );
|
|
612
|
-
if (noExtend && ext.kind === 'extend') {
|
|
613
|
-
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
614
|
-
'You can\'t use EXTEND on the generated $(ART)' );
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
if (ext.includes) {
|
|
618
|
-
// TODO: currently, re-compiling from gensrc does not give the exact
|
|
619
|
-
// element sequence - we need something like
|
|
620
|
-
// includes = ['Base1',3,'Base2']
|
|
621
|
-
// where 3 means adding the next 3 elements before applying include 'Base2'
|
|
622
|
-
if (art.includes)
|
|
623
|
-
art.includes.push(...ext.includes);
|
|
624
|
-
else
|
|
625
|
-
art.includes = [ ...ext.includes ];
|
|
626
|
-
applyIncludes( ext, art );
|
|
627
|
-
}
|
|
628
|
-
// console.log(ext,art)
|
|
629
|
-
checkAnnotate( ext, art );
|
|
630
|
-
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
631
|
-
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
632
|
-
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
633
|
-
}
|
|
634
|
-
for (const name in ext.elements) {
|
|
635
|
-
const elem = ext.elements[name];
|
|
636
|
-
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
637
|
-
elemExtensions.push( elem );
|
|
638
|
-
break; // more than one elem in same EXTEND is fine
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (elemExtensions.length > 1)
|
|
643
|
-
reportUnstableExtensions( elemExtensions );
|
|
644
|
-
|
|
645
|
-
// This whole function will be removed with a next change - no need to have nice code here:
|
|
646
|
-
const extsTmp = { elements: Object.create(null), actions: Object.create(null) };
|
|
647
|
-
for (const e of extensions) {
|
|
648
|
-
for (const n in e.elements || []) {
|
|
649
|
-
if (e.elements[n].kind === 'extend')
|
|
650
|
-
pushToDict( extsTmp.elements, n, e.elements[n] );
|
|
651
|
-
}
|
|
652
|
-
for (const n in extensions.actions || []) {
|
|
653
|
-
if (e.actions[n].kind === 'extend')
|
|
654
|
-
pushToDict( extsTmp.actions, n, e.actions[n] );
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
658
|
-
const dict = extsTmp[prop];
|
|
659
|
-
for (const name in dict) {
|
|
660
|
-
let obj = art;
|
|
661
|
-
if (obj.targetAspect)
|
|
662
|
-
obj = obj.targetAspect;
|
|
663
|
-
while (obj.items)
|
|
664
|
-
obj = obj.items;
|
|
665
|
-
const validDict = obj[prop] || prop === 'elements' && obj.enum;
|
|
666
|
-
const member = validDict && validDict[name];
|
|
667
|
-
if (!member)
|
|
668
|
-
extendNothing( dict[name], prop, name, art, validDict );
|
|
669
|
-
else if (!(member.$duplicates))
|
|
670
|
-
extendMembers( dict[name], member );
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Check that special `sap.common.*` aspects for `.texts` entities are
|
|
677
|
-
* consistent with compiler expectations. Emits messages and returns
|
|
678
|
-
* false if the aspects are not valid.
|
|
679
|
-
*
|
|
680
|
-
* @return {boolean}
|
|
681
|
-
*/
|
|
682
|
-
function checkTextsAspect() {
|
|
683
|
-
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
684
|
-
if (!textsAspect)
|
|
685
|
-
return false;
|
|
686
|
-
|
|
687
|
-
const specialElements = { locale: { key: true } };
|
|
688
|
-
|
|
689
|
-
if (textsAspect.kind !== 'aspect' || !textsAspect.elements) {
|
|
690
|
-
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
691
|
-
{ '#': 'no-aspect', art: textsAspect });
|
|
692
|
-
return false;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
let hasError = false;
|
|
696
|
-
if (addTextsLanguageAssoc && textsAspect.elements.language) {
|
|
697
|
-
const lang = textsAspect.elements.language;
|
|
698
|
-
error('def-unexpected-element', [ lang.name.location, lang ],
|
|
699
|
-
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
700
|
-
// eslint-disable-next-line max-len
|
|
701
|
-
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
702
|
-
hasError = true;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
for (const name in specialElements) {
|
|
706
|
-
const expected = specialElements[name];
|
|
707
|
-
const elem = textsAspect.elements[name];
|
|
708
|
-
if (!elem) {
|
|
709
|
-
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
710
|
-
{ '#': 'missing', art: textsAspect, name });
|
|
711
|
-
hasError = true;
|
|
712
|
-
}
|
|
713
|
-
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
714
|
-
const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
|
|
715
|
-
error('def-invalid-texts-aspect', [ loc, elem ],
|
|
716
|
-
{ '#': expected.key ? 'key' : 'no-key', art: elem });
|
|
717
|
-
hasError = true;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if (hasError) // avoid subsequent errors, if the special elements are already wrong
|
|
722
|
-
return false;
|
|
723
|
-
|
|
724
|
-
for (const name in textsAspect.elements) {
|
|
725
|
-
const elem = textsAspect.elements[name];
|
|
726
|
-
const include = elem.$inferred === 'include';
|
|
727
|
-
if (!specialElements[name] && elem.key) {
|
|
728
|
-
const loc = include ? elem.location : elem.key.location;
|
|
729
|
-
error( 'def-unexpected-key', [ loc, elem ],
|
|
730
|
-
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
731
|
-
hasError = true;
|
|
732
|
-
}
|
|
733
|
-
else if (hasTruthyProp( elem, 'localized' )) {
|
|
734
|
-
// TODO: T:loc, i.e. "localized" from other type (needs resolver?)
|
|
735
|
-
// Not supported anyway, but important for recompilation (which fails correctly).
|
|
736
|
-
const loc = elem.localized?.location || elem.location;
|
|
737
|
-
error( 'def-unexpected-localized', [ loc, elem ],
|
|
738
|
-
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
739
|
-
hasError = true;
|
|
740
|
-
}
|
|
741
|
-
else if (elem.targetAspect) {
|
|
742
|
-
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
|
|
743
|
-
{ art: textsAspect },
|
|
744
|
-
'$(ART) can\'t have composition of aspects' );
|
|
745
|
-
hasError = true;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
return !hasError;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
754
|
-
* except if all extensions are in the same file.
|
|
755
|
-
*
|
|
756
|
-
* @param {XSN.Extension[]} extensions
|
|
757
|
-
*/
|
|
758
|
-
function reportUnstableExtensions( extensions ) {
|
|
759
|
-
// No message if all extensions are in the same file:
|
|
760
|
-
const file = layers.realname( extensions[0] );
|
|
761
|
-
if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
|
|
762
|
-
return;
|
|
763
|
-
// Similar to chooseAssignment(), TODO there: also extra intralayer message
|
|
764
|
-
// as this is a modeling error
|
|
765
|
-
let lastExt = null;
|
|
766
|
-
let open = []; // the "highest" layers
|
|
767
|
-
for (const ext of extensions) {
|
|
768
|
-
const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
769
|
-
if (!open.length) {
|
|
770
|
-
lastExt = ext;
|
|
771
|
-
open = [ extLayer.realname ];
|
|
772
|
-
}
|
|
773
|
-
else if (extLayer.realname === open[open.length - 1]) { // in same layer
|
|
774
|
-
if (lastExt) {
|
|
775
|
-
message( 'extend-repeated-intralayer', [ lastExt.location, lastExt ] );
|
|
776
|
-
lastExt = null;
|
|
777
|
-
}
|
|
778
|
-
message( 'extend-repeated-intralayer', [ ext.location, ext ] );
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
if (lastExt && (open.length > 1 || !extLayer._layerExtends[open[0]])) {
|
|
782
|
-
// report for lastExt if that is unrelated to other open exts or current ext
|
|
783
|
-
message( 'extend-unrelated-layer', [ lastExt.location, lastExt ], {},
|
|
784
|
-
'Unstable element order due to other extension in unrelated layer' );
|
|
785
|
-
}
|
|
786
|
-
lastExt = ext;
|
|
787
|
-
open = open.filter( name => !extLayer._layerExtends[name] );
|
|
788
|
-
open.push( extLayer.realname );
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* @param {XSN.Extension[]} extensions
|
|
796
|
-
* @param {string} prop
|
|
797
|
-
* @param {string} name
|
|
798
|
-
* @param {XSN.Artifact} art
|
|
799
|
-
* @param {object} validDict
|
|
800
|
-
*/
|
|
801
|
-
function extendNothing( extensions, prop, name, art, validDict ) {
|
|
802
|
-
const artName = searchName( art, name, dictKinds[prop] );
|
|
803
|
-
for (const ext of extensions) {
|
|
804
|
-
// TODO: use shared functionality with notFound in resolver.js
|
|
805
|
-
const { location } = ext.name;
|
|
806
|
-
const extName = { ...artName, kind: ext.kind };
|
|
807
|
-
const msg
|
|
808
|
-
= error( 'extend-undefined', [ location, extName ],
|
|
809
|
-
{ art: artName },
|
|
810
|
-
{
|
|
811
|
-
std: 'Unknown $(ART) - nothing to extend',
|
|
812
|
-
// eslint-disable-next-line max-len
|
|
813
|
-
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
814
|
-
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
815
|
-
} );
|
|
816
|
-
attachAndEmitValidNames(msg, validDict);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// includes ----------------------------------------------------------------
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Returns true, if `art.includes` can be applied on `target`.
|
|
824
|
-
* They can't be applied if any of the artifacts referenced in
|
|
825
|
-
* `art.includes` are yet to be extended.
|
|
826
|
-
* `art !== target` if `art` is an extension.
|
|
827
|
-
*
|
|
828
|
-
* @param {XSN.Definition} art
|
|
829
|
-
* @param {XSN.Artifact} target
|
|
830
|
-
* @returns {boolean}
|
|
831
|
-
*/
|
|
832
|
-
function canApplyIncludes( art, target, justResolveCyclic ) {
|
|
833
|
-
if (!art.includes)
|
|
834
|
-
return true;
|
|
835
|
-
const isView = !!target.query;
|
|
836
|
-
for (const ref of art.includes) {
|
|
837
|
-
const name = resolveUncheckedPath( ref, 'include', art );
|
|
838
|
-
// console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
|
|
839
|
-
if (justResolveCyclic) {
|
|
840
|
-
if (!justResolveCyclic.includes( name ))
|
|
841
|
-
continue;
|
|
842
|
-
delete ref._artifact;
|
|
843
|
-
}
|
|
844
|
-
else if (name && name in extensionsDict) {
|
|
845
|
-
// one of the includes has itself extensions that need to be applied first
|
|
846
|
-
return false;
|
|
847
|
-
}
|
|
848
|
-
else if (ref._artifact) {
|
|
849
|
-
delete ref._artifact;
|
|
850
|
-
}
|
|
851
|
-
resolvePath( ref, isView ? 'viewInclude' : 'include', art );
|
|
852
|
-
}
|
|
853
|
-
return true;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
|
|
858
|
-
* If `ext === art`, then includes of the artifact itself are applied.
|
|
859
|
-
* If `ext !== art`, applies includes on the extensions, not artifact.
|
|
860
|
-
* Sets `_ancestor` links on `art`.
|
|
861
|
-
*
|
|
862
|
-
* Examples:
|
|
863
|
-
* ext === art: `entity E : F {}` => add elements of F to E
|
|
864
|
-
* ext !== art: `extend E with F` => add elements of F to extension on E
|
|
865
|
-
*
|
|
866
|
-
* @param {XSN.Extension} ext
|
|
867
|
-
* @param {XSN.Artifact} art
|
|
868
|
-
*/
|
|
869
|
-
function applyIncludes( ext, art ) {
|
|
870
|
-
if (kindProperties[art.kind].include !== true) {
|
|
871
|
-
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
872
|
-
{ meta: art.kind });
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
if (!art.query) {
|
|
877
|
-
if (!art._ancestors)
|
|
878
|
-
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
879
|
-
for (const ref of ext.includes) {
|
|
880
|
-
const template = ref._artifact;
|
|
881
|
-
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
882
|
-
if (template) {
|
|
883
|
-
if (template._ancestors)
|
|
884
|
-
art._ancestors.push( ...template._ancestors );
|
|
885
|
-
art._ancestors.push( template );
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
if (!art.query) // do not set art.elements and art.enums with query entity!
|
|
890
|
-
includeMembers( ext, art, 'elements' );
|
|
891
|
-
includeMembers( ext, art, 'actions' );
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`.
|
|
896
|
-
* If `art` is `ext`, set the parent link accordingly.
|
|
897
|
-
*
|
|
898
|
-
* @param {XSN.Extension} ext
|
|
899
|
-
* @param {XSN.Artifact} art
|
|
900
|
-
* @param {string} prop: 'elements' or 'actions'
|
|
901
|
-
*/
|
|
902
|
-
function includeMembers( ext, art, prop ) {
|
|
903
|
-
// TODO two kind of messages:
|
|
904
|
-
// Error 'More than one include defines element "A"' (at include ref)
|
|
905
|
-
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
906
|
-
const parent = ext === art && art;
|
|
907
|
-
const members = ext[prop];
|
|
908
|
-
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
909
|
-
for (const ref of ext.includes) {
|
|
910
|
-
const template = ref._artifact; // already resolved
|
|
911
|
-
if (template) { // be robust
|
|
912
|
-
forEachInOrder( template, prop, ( origin, name ) => {
|
|
913
|
-
if (members && name in members)
|
|
914
|
-
return; // TODO: warning for overwritten element
|
|
915
|
-
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
916
|
-
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
917
|
-
dictAdd( ext[prop], name, elem );
|
|
918
|
-
elem.$inferred = 'include';
|
|
919
|
-
if (origin.masked)
|
|
920
|
-
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
921
|
-
if (origin.key)
|
|
922
|
-
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
923
|
-
if (origin.value && origin.$syntax === 'calc') {
|
|
924
|
-
// TODO: If paths become invalid in the new artifact, should we mark
|
|
925
|
-
// all usages in the expressions? Possibly just the first one?
|
|
926
|
-
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
927
|
-
elem.$syntax = 'calc';
|
|
928
|
-
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
929
|
-
}
|
|
930
|
-
// TODO: also complain if elem is just defined in art
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
checkRedefinitionThroughIncludes( parent, prop );
|
|
935
|
-
// TODO: expand elements having direct elements (if needed)
|
|
936
|
-
if (members) {
|
|
937
|
-
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
938
|
-
dictAdd( ext[prop], name, elem );
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
945
|
-
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
|
946
|
-
*
|
|
947
|
-
* TODO(v4): Make this a hard error; see checkRedefinition(); maybe combine both;
|
|
948
|
-
*/
|
|
949
|
-
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
950
|
-
if (!parent[prop])
|
|
951
|
-
return;
|
|
952
|
-
forEachInOrder(parent, prop, ( member, name ) => {
|
|
953
|
-
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
954
|
-
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
955
|
-
if (isBetaEnabled(options, 'v4preview')) {
|
|
956
|
-
error( 'duplicate-definition', [ parent.name.location, member ],
|
|
957
|
-
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
|
|
958
|
-
}
|
|
959
|
-
else {
|
|
960
|
-
// Error accidentally removed in v2/v3, therefore only a warning.
|
|
961
|
-
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
|
|
962
|
-
{ '#': prop, name, sorted_arts: includes } );
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// localized texts entities
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* Process localized data for `art`. This includes creating `.texts` entities
|
|
972
|
-
* and `locale` associations.
|
|
973
|
-
*
|
|
974
|
-
* @param {XSN.Artifact} art
|
|
975
|
-
*/
|
|
976
|
-
function processLocalizedData( art ) {
|
|
977
|
-
const fioriAnno = art['@fiori.draft.enabled'];
|
|
978
|
-
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
979
|
-
|
|
980
|
-
const textsName = `${ art.name.absolute }.texts`;
|
|
981
|
-
const textsEntity = model.definitions[textsName];
|
|
982
|
-
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
983
|
-
if (!localized)
|
|
984
|
-
return;
|
|
985
|
-
if (textsEntity) // expanded localized data in source
|
|
986
|
-
return; // -> make it idempotent
|
|
987
|
-
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
988
|
-
addTextsAssociations( art, textsName, localized );
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* Returns `false`, if there is no localized data or an array of elements
|
|
993
|
-
* that are required for `.texts` entities such as keys and localized elements.
|
|
994
|
-
*
|
|
995
|
-
* @param {XSN.Artifact} art
|
|
996
|
-
* @param {XSN.Artifact|undefined} textsEntity
|
|
997
|
-
* @param {boolean} fioriEnabled
|
|
998
|
-
* @returns {false|XSN.Element[]}
|
|
999
|
-
*/
|
|
1000
|
-
function localizedData( art, textsEntity, fioriEnabled ) {
|
|
1001
|
-
let keys = 0;
|
|
1002
|
-
const textElems = [];
|
|
1003
|
-
const conflictingElements = [];
|
|
1004
|
-
// These elements are required or the localized-mechanism does not work.
|
|
1005
|
-
// Other elements from sap.common.TextsAspect may be "overridden" as per
|
|
1006
|
-
// usual include-mechanism.
|
|
1007
|
-
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
1008
|
-
if (fioriEnabled)
|
|
1009
|
-
protectedElements.push('ID_texts');
|
|
1010
|
-
if (addTextsLanguageAssoc)
|
|
1011
|
-
protectedElements.push('language');
|
|
1012
|
-
|
|
1013
|
-
for (const name in art.elements) {
|
|
1014
|
-
const elem = art.elements[name];
|
|
1015
|
-
if (elem.$duplicates)
|
|
1016
|
-
return false; // no localized-data unfold with redefined elems
|
|
1017
|
-
if (protectedElements.includes( name ))
|
|
1018
|
-
conflictingElements.push( elem );
|
|
1019
|
-
|
|
1020
|
-
const isKey = elem.key && elem.key.val;
|
|
1021
|
-
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
1022
|
-
|
|
1023
|
-
if (isKey) {
|
|
1024
|
-
keys += 1;
|
|
1025
|
-
textElems.push( elem );
|
|
1026
|
-
}
|
|
1027
|
-
else if (isLocalized) {
|
|
1028
|
-
textElems.push( elem );
|
|
301
|
+
// TODO: still a bit annotation assignment specific
|
|
302
|
+
function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
|
|
303
|
+
// TODO: think about messages for these
|
|
304
|
+
if (prop === 'elements' || prop === 'enum' || prop === 'actions' || prop === 'columns' ||
|
|
305
|
+
prop === 'params' || prop === 'returns' || prop === 'includes' )
|
|
306
|
+
return; // extensions currently handled extra
|
|
307
|
+
if (issue) {
|
|
308
|
+
// eslint-disable-next-line no-nested-ternary
|
|
309
|
+
let msg = (index < 0)
|
|
310
|
+
? 'anno-unstable-array'
|
|
311
|
+
: (issue === true)
|
|
312
|
+
? 'anno-duplicate'
|
|
313
|
+
: 'anno-duplicate-unrelated-layer';
|
|
314
|
+
if (prop.charAt(0) !== '@' && prop !== 'doc') {
|
|
315
|
+
msg = (issue === true)
|
|
316
|
+
? 'ext-duplicate-extend-type'
|
|
317
|
+
: 'ext-duplicate-extend-type-unrelated-layer';
|
|
318
|
+
// not sure whether to repeat the extended artifact in the message (we
|
|
319
|
+
// have the semantic location, after all)
|
|
1029
320
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
321
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
322
|
+
for (const ext of extensions) {
|
|
323
|
+
const anno = ext[prop];
|
|
324
|
+
if (anno && !anno.$errorReported) {
|
|
325
|
+
message( msg, [ anno.name?.location || anno.location, ext ],
|
|
326
|
+
{ '#': variant, anno: prop, type: art } );
|
|
327
|
+
}
|
|
1035
328
|
}
|
|
1036
329
|
}
|
|
1037
|
-
if (
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
330
|
+
else if (index > 0) { // more than one set (not just ...)
|
|
331
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
332
|
+
const msgid = (prop.charAt(0) === '@' || prop === 'doc')
|
|
333
|
+
? 'anno-duplicate-same-file' // TODO: always ext-duplicate-…
|
|
334
|
+
: 'ext-duplicate-same-file';
|
|
335
|
+
while (index >= 0) { // do not report for trailing [...]
|
|
336
|
+
const ext = extensions[index--];
|
|
337
|
+
const anno = ext[prop];
|
|
338
|
+
warning( msgid, [ anno.name?.location || anno.location, ext ],
|
|
339
|
+
{ '#': variant, prop, anno: prop } );
|
|
340
|
+
}
|
|
1044
341
|
}
|
|
342
|
+
}
|
|
1045
343
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
conflictingElements.length !== 2 || art.elements.locale ||
|
|
1052
|
-
(fioriEnabled && art.elements.ID_texts)) {
|
|
1053
|
-
// TODO if we have too much time: check all elements of texts entity for safety
|
|
1054
|
-
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
1055
|
-
// eslint-disable-next-line max-len
|
|
1056
|
-
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
1057
|
-
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
1058
|
-
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
1059
|
-
}
|
|
1060
|
-
else if (!art._block || art._block.$frontend !== 'json') {
|
|
1061
|
-
info( null, [ art.name.location, art ], {},
|
|
1062
|
-
'Localized data expansions has already been done' );
|
|
1063
|
-
return textElems; // make double-compilation even with after toHana
|
|
1064
|
-
}
|
|
1065
|
-
else if (!art._block.$withLocalized && !options.$recompile) {
|
|
1066
|
-
art._block.$withLocalized = true;
|
|
1067
|
-
info( 'recalculated-text-entities', [ art.name.location, null ], {},
|
|
1068
|
-
'Input CSN contains expansions for localized data' );
|
|
1069
|
-
return textElems; // make compilation idempotent
|
|
344
|
+
function applySingleExtension( art, ext, prop ) {
|
|
345
|
+
if (prop === 'includes') {
|
|
346
|
+
if (ext.kind === 'extend' && art.$inferred) {
|
|
347
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
348
|
+
'You can\'t use EXTEND on the generated $(ART)' );
|
|
1070
349
|
}
|
|
1071
|
-
else {
|
|
1072
|
-
|
|
350
|
+
else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
|
|
351
|
+
const { absolute } = art.name;
|
|
352
|
+
const dict = extensionsDict[absolute] || (extensionsDict[absolute] = []);
|
|
353
|
+
dict.push( ext ); // TODO: change
|
|
354
|
+
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[absolute])
|
|
1073
355
|
}
|
|
356
|
+
// art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
|
|
357
|
+
}
|
|
358
|
+
else if (prop === 'columns') {
|
|
359
|
+
const { query } = art;
|
|
360
|
+
if (!query?.from?.path)
|
|
361
|
+
error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
|
|
362
|
+
else if (!query.columns)
|
|
363
|
+
query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
|
|
364
|
+
else
|
|
365
|
+
query.columns.push( ...ext.columns );
|
|
366
|
+
}
|
|
367
|
+
else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
|
|
368
|
+
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
369
|
+
typeExts[prop] = ext;
|
|
1074
370
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
371
|
+
else {
|
|
372
|
+
const result = applyAssignment( art[prop], ext[prop], ext, prop );
|
|
373
|
+
art[prop] = (result.name) ? result : Object.assign( {}, art[prop], result );
|
|
1078
374
|
}
|
|
1079
|
-
return !textsEntity && !conflictingElements.length && textElems;
|
|
1080
375
|
}
|
|
1081
376
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const art = useTextsAspect
|
|
1092
|
-
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
1093
|
-
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
1094
|
-
// both functions are rather similar...
|
|
1095
|
-
|
|
1096
|
-
const { location } = base.name;
|
|
1097
|
-
|
|
1098
|
-
if (addTextsLanguageAssoc) {
|
|
1099
|
-
const language = {
|
|
1100
|
-
name: { location, id: 'language' },
|
|
1101
|
-
kind: 'element',
|
|
1102
|
-
location,
|
|
1103
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
1104
|
-
target: augmentPath( location, 'sap.common.Languages' ),
|
|
1105
|
-
on: {
|
|
1106
|
-
op: { val: '=', location },
|
|
1107
|
-
args: [
|
|
1108
|
-
{ path: [ { id: 'language', location }, { id: 'code', location } ], location },
|
|
1109
|
-
{ path: [ { id: 'locale', location } ], location },
|
|
1110
|
-
],
|
|
1111
|
-
location,
|
|
1112
|
-
},
|
|
1113
|
-
};
|
|
1114
|
-
setLink( language, '_block', model.$internal );
|
|
1115
|
-
dictAdd( art.elements, 'language', language );
|
|
377
|
+
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
378
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
379
|
+
if (!firstEllipsis)
|
|
380
|
+
return anno;
|
|
381
|
+
const hasBase = previousAnno?.literal === 'array';
|
|
382
|
+
if (!previousAnno) {
|
|
383
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
384
|
+
message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
|
|
385
|
+
previousAnno = { val: [] };
|
|
1116
386
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
387
|
+
else if (previousAnno.literal !== 'array') {
|
|
388
|
+
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
389
|
+
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
390
|
+
previousAnno = { val: [] };
|
|
391
|
+
}
|
|
392
|
+
const previousValue = previousAnno.val;
|
|
393
|
+
let prevPos = 0;
|
|
394
|
+
const result = [];
|
|
395
|
+
for (const item of anno.val) {
|
|
396
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
397
|
+
if (!ell) {
|
|
398
|
+
result.push( item );
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
|
|
402
|
+
while (prevPos < previousValue.length) {
|
|
403
|
+
const prevItem = previousValue[prevPos++];
|
|
404
|
+
result.push( prevItem );
|
|
405
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
406
|
+
upToSpec = false;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
1131
409
|
}
|
|
1132
|
-
|
|
1133
|
-
//
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
location: orig.location,
|
|
1137
|
-
});
|
|
410
|
+
if (upToSpec && hasBase) {
|
|
411
|
+
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
412
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
413
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
1138
414
|
}
|
|
1139
415
|
}
|
|
1140
|
-
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
1141
|
-
const localized = orig.localized || orig.type || orig.name;
|
|
1142
|
-
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
1143
|
-
}
|
|
1144
416
|
}
|
|
417
|
+
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
418
|
+
return { val: result, literal: 'array' };
|
|
419
|
+
}
|
|
420
|
+
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
1145
421
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
422
|
+
function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
|
|
423
|
+
const { literal } = upToSpec;
|
|
424
|
+
if (!isFullUpTo) { // inside struct of UP TO
|
|
425
|
+
if (literal !== 'struct' && literal !== 'array' )
|
|
426
|
+
return true;
|
|
1150
427
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
// The includes mechanism puts TextsAspect's elements before .texts' elements.
|
|
1154
|
-
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
1155
|
-
// up. Fix it.
|
|
1156
|
-
const { elements } = art;
|
|
1157
|
-
art.elements = Object.create(null);
|
|
1158
|
-
const names = [ 'ID_texts', 'locale', ...Object.keys(elements) ];
|
|
1159
|
-
for (const name of names)
|
|
1160
|
-
art.elements[name] = elements[name];
|
|
1161
|
-
|
|
1162
|
-
const { locale } = art.elements;
|
|
1163
|
-
assertUniqueValue.unshift({
|
|
1164
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
1165
|
-
location: locale.location,
|
|
1166
|
-
});
|
|
1167
|
-
setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
428
|
+
else if (literal === 'struct') {
|
|
429
|
+
return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
|
|
1168
430
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
431
|
+
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
error( null, [ upToSpec.location, art ],
|
|
435
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
436
|
+
{
|
|
437
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
438
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
439
|
+
// eslint-disable-next-line max-len
|
|
440
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
441
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
442
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
443
|
+
} );
|
|
444
|
+
return false;
|
|
1172
445
|
}
|
|
1173
446
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const textsAspectName = 'sap.common.TextsAspect';
|
|
1187
|
-
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
1188
|
-
const elements = Object.create(null);
|
|
1189
|
-
const { location } = base.name;
|
|
1190
|
-
const art = {
|
|
1191
|
-
kind: 'entity',
|
|
1192
|
-
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
1193
|
-
includes: [ createInclude( textsAspectName, base.location ) ],
|
|
1194
|
-
location: base.location,
|
|
1195
|
-
elements,
|
|
1196
|
-
$inferred: 'localized-entity',
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
if (!fioriEnabled) {
|
|
1200
|
-
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1201
|
-
// TODO (next major version): remove?
|
|
1202
|
-
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
447
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
448
|
+
if (!previousItem)
|
|
449
|
+
return false;
|
|
450
|
+
if ('val' in upToSpec) {
|
|
451
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
452
|
+
return true;
|
|
453
|
+
const typeUpTo = typeof upToSpec.val;
|
|
454
|
+
const typePrev = typeof previousItem.val;
|
|
455
|
+
if (typeUpTo === 'number')
|
|
456
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
457
|
+
if (typePrev === 'number')
|
|
458
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
1203
459
|
}
|
|
1204
|
-
else {
|
|
1205
|
-
|
|
1206
|
-
// `locale` is copied from `sap.common.TextsAspect`, but without "key".
|
|
1207
|
-
const textId = {
|
|
1208
|
-
name: { location, id: 'ID_texts' },
|
|
1209
|
-
kind: 'element',
|
|
1210
|
-
key: { val: true, location },
|
|
1211
|
-
type: augmentPath( location, 'cds.UUID' ),
|
|
1212
|
-
location,
|
|
1213
|
-
};
|
|
1214
|
-
dictAdd( art.elements, 'ID_texts', textId );
|
|
1215
|
-
|
|
1216
|
-
// "Early" include; only for element `locale`, which has its `key` property
|
|
1217
|
-
// removed (or rather: it is not copied).
|
|
1218
|
-
linkToOrigin( textsAspect.elements.locale, 'locale', art, 'elements', location );
|
|
460
|
+
else if (upToSpec.path) {
|
|
461
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
1219
462
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
1223
|
-
// TODO: what is this necessary? We do not create a text entity in this case
|
|
1224
|
-
|
|
1225
|
-
setLink( art, '_block', model.$internal );
|
|
1226
|
-
model.definitions[absolute] = art;
|
|
1227
|
-
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1228
|
-
return art;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
/**
|
|
1232
|
-
* @param {XSN.Artifact} base
|
|
1233
|
-
* @param {string} absolute
|
|
1234
|
-
* @param {boolean} fioriEnabled
|
|
1235
|
-
*/
|
|
1236
|
-
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
1237
|
-
const elements = Object.create(null);
|
|
1238
|
-
const { location } = base.name;
|
|
1239
|
-
const art = {
|
|
1240
|
-
kind: 'entity',
|
|
1241
|
-
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
1242
|
-
location: base.location,
|
|
1243
|
-
elements,
|
|
1244
|
-
$inferred: 'localized-entity',
|
|
1245
|
-
};
|
|
1246
|
-
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
1247
|
-
// If not, use the default `cds.String` with a length of 14.
|
|
1248
|
-
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
1249
|
-
const locale = {
|
|
1250
|
-
name: { location, id: 'locale' },
|
|
1251
|
-
kind: 'element',
|
|
1252
|
-
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
1253
|
-
location,
|
|
1254
|
-
};
|
|
1255
|
-
if (!hasLocaleType)
|
|
1256
|
-
locale.length = { literal: 'number', val: 14, location };
|
|
1257
|
-
|
|
1258
|
-
if (!fioriEnabled) {
|
|
1259
|
-
locale.key = { val: true, location };
|
|
1260
|
-
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1261
|
-
// TODO (next major version): remove?
|
|
1262
|
-
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
463
|
+
else if (upToSpec.sym) {
|
|
464
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
1263
465
|
}
|
|
1264
|
-
else {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
kind: 'element',
|
|
1268
|
-
key: { val: true, location },
|
|
1269
|
-
type: augmentPath( location, 'cds.UUID' ),
|
|
1270
|
-
location,
|
|
1271
|
-
};
|
|
1272
|
-
dictAdd( art.elements, 'ID_texts', textId );
|
|
466
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
467
|
+
return Object.entries( upToSpec.struct )
|
|
468
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
1273
469
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
setLink( art, '_block', model.$internal );
|
|
1277
|
-
model.definitions[absolute] = art;
|
|
1278
|
-
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1279
|
-
return art;
|
|
470
|
+
return false;
|
|
1280
471
|
}
|
|
1281
472
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
* @param {XSN.Element[]} textElems
|
|
1286
|
-
*/
|
|
1287
|
-
function addTextsAssociations( art, textsName, textElems ) {
|
|
1288
|
-
// texts : Composition of many Books.texts on texts.ID=ID;
|
|
1289
|
-
/** @type {array} */
|
|
1290
|
-
const keys = textElems.filter( e => e.key && e.key.val );
|
|
1291
|
-
const { location } = art.name;
|
|
1292
|
-
const texts = {
|
|
1293
|
-
name: { location, id: 'texts' },
|
|
1294
|
-
kind: 'element',
|
|
1295
|
-
location,
|
|
1296
|
-
$inferred: 'localized',
|
|
1297
|
-
type: augmentPath( location, 'cds.Composition' ),
|
|
1298
|
-
cardinality: { targetMax: { literal: 'string', val: '*', location }, location },
|
|
1299
|
-
target: augmentPath( location, textsName ),
|
|
1300
|
-
on: augmentEqual( location, 'texts', keys ),
|
|
1301
|
-
};
|
|
1302
|
-
setMemberParent( texts, 'texts', art, 'elements' );
|
|
1303
|
-
setLink( texts, '_block', model.$internal );
|
|
1304
|
-
// localized : Association to Books.texts on
|
|
1305
|
-
// localized.ID=ID and localized.locale = $user.locale;
|
|
1306
|
-
keys.push( [ 'localized.locale', '$user.locale' ] );
|
|
1307
|
-
const localized = {
|
|
1308
|
-
name: { location, id: 'localized' },
|
|
1309
|
-
kind: 'element',
|
|
1310
|
-
location,
|
|
1311
|
-
$inferred: 'localized',
|
|
1312
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
1313
|
-
target: augmentPath( location, textsName ),
|
|
1314
|
-
on: augmentEqual( location, 'localized', keys ),
|
|
1315
|
-
};
|
|
1316
|
-
setMemberParent( localized, 'localized', art, 'elements' );
|
|
1317
|
-
setLink( localized, '_block', model.$internal );
|
|
473
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
474
|
+
const ref = pathName( node.path );
|
|
475
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1318
476
|
}
|
|
1319
477
|
|
|
1320
|
-
|
|
1321
|
-
* Create a structure that can be used as an item in `includes`.
|
|
1322
|
-
*
|
|
1323
|
-
* @param {string} name
|
|
1324
|
-
* @param {XSN.Location} location
|
|
1325
|
-
*/
|
|
1326
|
-
function createInclude( name, location ) {
|
|
1327
|
-
const include = {
|
|
1328
|
-
path: [ { id: name, location } ],
|
|
1329
|
-
location,
|
|
1330
|
-
};
|
|
1331
|
-
setArtifactLink( include.path[0], model.definitions[name] );
|
|
1332
|
-
setArtifactLink( include, model.definitions[name] );
|
|
1333
|
-
return include;
|
|
1334
|
-
}
|
|
478
|
+
// For extendArtifactAfter(): -------------------------------------------------
|
|
1335
479
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
if (art
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
art =
|
|
1359
|
-
|
|
1360
|
-
return false;
|
|
1361
|
-
name = art && art.name.absolute;
|
|
1362
|
-
}
|
|
1363
|
-
else if (art.type && art._block && art.type.scope !== 'typeOf') {
|
|
1364
|
-
// TODO: also do something special for TYPE OF inside `art`s own elements
|
|
1365
|
-
name = resolveUncheckedPath( art.type, 'type', art );
|
|
1366
|
-
art = name && model.definitions[name];
|
|
1367
|
-
}
|
|
1368
|
-
else {
|
|
1369
|
-
return false;
|
|
480
|
+
// Remarks on messages: we allow the type extensions only if the artifact
|
|
481
|
+
// originally had that property → any check of the kind “type prop can only be
|
|
482
|
+
// used with FooBar” is independent from `extend … with type`. Function
|
|
483
|
+
// checkTypeArguments() in resolve.js reports 'type-unexpected-argument', but
|
|
484
|
+
// that is currently incomplete.
|
|
485
|
+
//
|
|
486
|
+
// We then report (in the future), use the first message of:
|
|
487
|
+
// - the usual messages if a type argument is wrong, independently from `extend`
|
|
488
|
+
// - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
|
|
489
|
+
// - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
|
|
490
|
+
//
|
|
491
|
+
// TODO v4: do not allow `extend … with (precision: …)` alone if original def also has `scale`
|
|
492
|
+
function applyTypeExtensions( art, ext, prop, scaleDiff ) {
|
|
493
|
+
// console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
|
|
494
|
+
if (!ext?.[prop])
|
|
495
|
+
return 0;
|
|
496
|
+
if (!art[prop]) {
|
|
497
|
+
const isBuiltin = art._effectiveType?.builtin;
|
|
498
|
+
if (isBuiltin && !allowsTypeArgument( art, prop )) {
|
|
499
|
+
// Let checkTypeArguments() in resolve.js report a message, is incomplete
|
|
500
|
+
// though, i.e. can only safely be used for scalars at the moment. But we
|
|
501
|
+
// will improve that function and not try to do extra things here.
|
|
502
|
+
art[prop] = ext[prop]; // enable checkTypeArguments() doing its job
|
|
503
|
+
return 0;
|
|
1370
504
|
}
|
|
505
|
+
// TODO: think about 'ext-unexpected-type-argument'
|
|
506
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
507
|
+
{ '#': (isBuiltin ? 'indirect' : 'new-prop'), prop } );
|
|
508
|
+
return 0;
|
|
1371
509
|
}
|
|
1372
|
-
|
|
510
|
+
const artVal = art[prop].val;
|
|
511
|
+
const extVal = ext[prop].val;
|
|
512
|
+
if (prop === 'srid') {
|
|
513
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'prop', prop } );
|
|
514
|
+
}
|
|
515
|
+
else if (typeof artVal !== 'number' || typeof extVal !== 'number' ) {
|
|
516
|
+
// Users can't change from/to string value for property,
|
|
517
|
+
// e.g. `variable`/`floating` for Decimal
|
|
518
|
+
// TODO: Shouldn't the text distinguish between orig string and extension string?
|
|
519
|
+
// Not sure whether to talk about strings if we have a keyword in CDL
|
|
520
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'string', prop } );
|
|
521
|
+
}
|
|
522
|
+
else if (extVal < artVal + (scaleDiff || 0)) {
|
|
523
|
+
const number = artVal + (scaleDiff || 0);
|
|
524
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
525
|
+
// eslint-disable-next-line object-curly-newline
|
|
526
|
+
{ '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale' } );
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
art[prop] = ext[prop];
|
|
530
|
+
return extVal - artVal;
|
|
531
|
+
}
|
|
532
|
+
return 0;
|
|
1373
533
|
}
|
|
1374
534
|
|
|
1375
|
-
|
|
535
|
+
function allowsTypeArgument( art, prop ) {
|
|
536
|
+
const { parameters } = art._effectiveType;
|
|
537
|
+
if (!parameters)
|
|
538
|
+
return false;
|
|
539
|
+
return parameters.includes( prop ) || parameters[0]?.name === prop;
|
|
540
|
+
}
|
|
1376
541
|
|
|
1377
|
-
function
|
|
1378
|
-
// TODO:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
// TODO: better do circular checks in the aspect!
|
|
1382
|
-
if (base.kind !== 'entity' || base.query)
|
|
542
|
+
function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
|
|
543
|
+
// TODO: setExpandStatusAnnotate
|
|
544
|
+
const extensions = extensionsMap[extProp];
|
|
545
|
+
if (!extensions)
|
|
1383
546
|
return;
|
|
1384
|
-
const
|
|
1385
|
-
if (keys)
|
|
1386
|
-
forEachGeneric( base, 'elements', expand ); // TODO: recursively here?
|
|
1387
|
-
return;
|
|
1388
|
-
|
|
1389
|
-
function baseKeys() {
|
|
1390
|
-
const k = Object.create(null);
|
|
1391
|
-
for (const name in base.elements) {
|
|
1392
|
-
const elem = base.elements[name];
|
|
1393
|
-
if (elem.$duplicates)
|
|
1394
|
-
return false; // no composition-of-type unfold with redefined elems
|
|
1395
|
-
if (elem.key && elem.key.val)
|
|
1396
|
-
k[name] = elem;
|
|
1397
|
-
}
|
|
1398
|
-
return k;
|
|
1399
|
-
}
|
|
547
|
+
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
1400
548
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
return;
|
|
1413
|
-
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
1414
|
-
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
1415
|
-
createTargetEntity( target, elem, keys, entityName, base );
|
|
1416
|
-
elem.target = {
|
|
1417
|
-
location: (elem.targetAspect || elem).location,
|
|
1418
|
-
$inferred: 'aspect-composition',
|
|
1419
|
-
};
|
|
1420
|
-
setArtifactLink( elem.target, entity );
|
|
1421
|
-
if (entity) {
|
|
1422
|
-
// Support using the up_ element in the generated entity to be used
|
|
1423
|
-
// inside the anonymous aspect:
|
|
1424
|
-
const { up_ } = target.$tableAliases;
|
|
1425
|
-
// TODO: invalidate "up_" alias (at least further navigation) if it
|
|
1426
|
-
// already has an _origin (when the managed composition is included)
|
|
1427
|
-
if (up_)
|
|
1428
|
-
setLink( up_, '_origin', entity.elements.up_ );
|
|
1429
|
-
model.$compositionTargets[entity.name.absolute] = true;
|
|
1430
|
-
processAspectComposition( entity );
|
|
1431
|
-
processLocalizedData( entity );
|
|
549
|
+
for (const ext of extensions) {
|
|
550
|
+
const extDict = ext[extProp];
|
|
551
|
+
for (const name in extDict) {
|
|
552
|
+
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
553
|
+
const elemExt = extDict[name];
|
|
554
|
+
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
555
|
+
continue; // definitions inside extend, already handled
|
|
556
|
+
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
557
|
+
const elem = artDict[name] || annotateFor( art, extProp, name );
|
|
558
|
+
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
|
|
559
|
+
pushToDict( elem, '_extensions', elemExt );
|
|
1432
560
|
}
|
|
1433
561
|
}
|
|
1434
562
|
}
|
|
1435
563
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
if ((elem._main._upperAspects || []).includes( target ))
|
|
1444
|
-
return 0; // circular containment of the same aspect
|
|
1445
|
-
|
|
1446
|
-
const keyNames = Object.keys( keys );
|
|
1447
|
-
if (!keyNames.length) {
|
|
1448
|
-
// TODO: for "inner aspect-compositions", signal already in type
|
|
1449
|
-
error( null, [ location, elem ], { target },
|
|
1450
|
-
'An aspect $(TARGET) can\'t be used as target in an entity without keys' );
|
|
1451
|
-
return false;
|
|
1452
|
-
}
|
|
1453
|
-
// if (keys.up_) { // only to be tested if we allow to provide a prefix, which could be ''
|
|
1454
|
-
// // Cannot be in an "inner aspect-compositions" as it would already be wrong before
|
|
1455
|
-
// // TODO: if anonymous type, use location of "up_" element
|
|
1456
|
-
// // FUTURE: add sub info with location of "up_" element
|
|
1457
|
-
// message( 'id', [location, elem], { target, name: 'up_' }, 'Error',
|
|
1458
|
-
// 'An aspect $(TARGET) can't be used as target in an entity with a key named $(NAME)' );
|
|
1459
|
-
// return false;
|
|
1460
|
-
// }
|
|
1461
|
-
if (target.elements.up_) {
|
|
1462
|
-
// TODO: for "inner aspect-compositions", signal already in type
|
|
1463
|
-
// TODO: if anonymous type, use location of "up_" element
|
|
1464
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
1465
|
-
error( null, [ location, elem ], { target, name: 'up_' },
|
|
1466
|
-
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
if (model.definitions[entityName]) {
|
|
1470
|
-
error( null, [ location, elem ], { art: entityName },
|
|
1471
|
-
// eslint-disable-next-line max-len
|
|
1472
|
-
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
1473
|
-
return false;
|
|
1474
|
-
}
|
|
1475
|
-
const names = Object.keys( target.elements )
|
|
1476
|
-
.filter( n => n.startsWith('up__') && keyNames.includes( n.substring(4) ) );
|
|
1477
|
-
if (names.length) {
|
|
1478
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
1479
|
-
error( null, [ location, elem ], { target: entityName, names }, {
|
|
1480
|
-
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
1481
|
-
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
1482
|
-
});
|
|
1483
|
-
return false;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
if (elem.type && !isDirectComposition(elem)) {
|
|
1487
|
-
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
1488
|
-
// TODO: Make it configurable error; v4: error
|
|
1489
|
-
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
|
|
1490
|
-
{ prop: 'Composition of', otherprop: 'Association to' },
|
|
1491
|
-
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
|
|
1492
|
-
}
|
|
564
|
+
// function moveReturnsExtensions( art, extensionsMap ) {
|
|
565
|
+
// const artReturns = art.returns;
|
|
566
|
+
// const extensions = extensionsMap.returns;
|
|
567
|
+
// // TODO: artItem is null
|
|
568
|
+
// for (const ext of extensions)
|
|
569
|
+
// pushToDict( artReturns, '_extensions', ext.returns );
|
|
570
|
+
// }
|
|
1493
571
|
|
|
1494
|
-
|
|
572
|
+
function annotateFor( art, prop, name ) {
|
|
573
|
+
const base = annotateBase( art );
|
|
574
|
+
if (name === '' && prop === 'params')
|
|
575
|
+
return base.returns || annotateCreate( base, name, base, 'returns' );
|
|
576
|
+
const dict = base[prop] || (base[prop] = Object.create( null ));
|
|
577
|
+
if (name == null)
|
|
578
|
+
return dict;
|
|
579
|
+
return dict[name] || annotateCreate( dict, name, base );
|
|
1495
580
|
}
|
|
1496
581
|
|
|
1497
|
-
function
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
args: [
|
|
1503
|
-
augmentPath( location, elem.name.id, 'up_' ),
|
|
1504
|
-
augmentPath( location, '$self' ),
|
|
1505
|
-
],
|
|
1506
|
-
$inferred: 'aspect-composition',
|
|
1507
|
-
};
|
|
582
|
+
function annotateBase( art ) {
|
|
583
|
+
while (art._outer) // TOOD: think about anonymous target aspect
|
|
584
|
+
art = art._outer;
|
|
585
|
+
if (art.kind === 'annotate')
|
|
586
|
+
return art;
|
|
1508
587
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
setLink( art, '_origin', target );
|
|
1523
|
-
// TODO: do we need to give the anonymous target aspect a kind and name?
|
|
1524
|
-
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
1525
|
-
}
|
|
588
|
+
// TODO: more to do if annotate can have `returns` property
|
|
589
|
+
if (art.kind === 'select')
|
|
590
|
+
art = art._parent;
|
|
591
|
+
if (art._main)
|
|
592
|
+
return annotateFor( art._parent, kindProperties[art.kind].dict, art.name.id );
|
|
593
|
+
|
|
594
|
+
const { absolute } = art.name;
|
|
595
|
+
return model.$collectedExtensions[absolute] ||
|
|
596
|
+
annotateCreate( model.$collectedExtensions, absolute );
|
|
597
|
+
}
|
|
1526
598
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
kind: '
|
|
1530
|
-
location,
|
|
1531
|
-
$inferred: '
|
|
1532
|
-
|
|
1533
|
-
target: augmentPath( location, base.name.absolute ),
|
|
1534
|
-
cardinality: {
|
|
1535
|
-
targetMin: { val: 1, literal: 'number', location },
|
|
1536
|
-
targetMax: { val: 1, literal: 'number', location },
|
|
1537
|
-
location,
|
|
1538
|
-
},
|
|
599
|
+
function annotateCreate( dict, id, parent, prop ) {
|
|
600
|
+
const annotate = {
|
|
601
|
+
kind: 'annotate',
|
|
602
|
+
name: { id, location: genLocation },
|
|
603
|
+
$inferred: '',
|
|
604
|
+
location: genLocation,
|
|
1539
605
|
};
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
1544
|
-
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
1545
|
-
'up__', '@odata.containment.ignore' );
|
|
1546
|
-
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
606
|
+
if (parent) {
|
|
607
|
+
setLink( annotate, '_parent', parent );
|
|
608
|
+
setLink( annotate, '_main', parent._main || parent );
|
|
1547
609
|
}
|
|
1548
610
|
else {
|
|
1549
|
-
|
|
1550
|
-
// managed associations must be explicitly set to not null
|
|
1551
|
-
// even if target cardinality is 1..1
|
|
1552
|
-
up.notNull = { location, val: true };
|
|
611
|
+
annotate.name.absolute = id; // TODO later (if all names are sparse): delete absolute
|
|
1553
612
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
1557
|
-
|
|
1558
|
-
setLink( art, '_block', model.$internal );
|
|
1559
|
-
model.definitions[entityName] = art;
|
|
1560
|
-
initArtifact( art );
|
|
1561
|
-
|
|
1562
|
-
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1563
|
-
// Copy persistence annotations from aspect.
|
|
1564
|
-
copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
|
|
1565
|
-
return art;
|
|
613
|
+
dict[prop || id] = annotate;
|
|
614
|
+
return annotate;
|
|
1566
615
|
}
|
|
1567
616
|
|
|
1568
|
-
function
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
617
|
+
function extendHandleReturns( extensions, art ) {
|
|
618
|
+
for (const ext of extensions || []) {
|
|
619
|
+
if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
|
|
620
|
+
ext.$syntax = '$inside-returns';
|
|
621
|
+
delete ext.params;
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
warning( 'ext-expected-returns', [ ext.name.location, ext ], {
|
|
625
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
626
|
+
}, {
|
|
627
|
+
std: 'Expected $(CODE)', // unused variant
|
|
628
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
629
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
630
|
+
} );
|
|
631
|
+
}
|
|
1582
632
|
}
|
|
1583
633
|
}
|
|
1584
634
|
|
|
635
|
+
// const unexpected_props = {
|
|
636
|
+
// elements: 'anno-unexpected-elements',
|
|
637
|
+
// enum: 'anno-unexpected-elements', // TODO
|
|
638
|
+
// params: 'anno-unexpected-params',
|
|
639
|
+
// actions: 'anno-unexpected-actions',
|
|
640
|
+
// };
|
|
641
|
+
// const undefined_props = {
|
|
642
|
+
// elements: 'anno-undefined-element',
|
|
643
|
+
// enum: 'anno-undefined-element', // TODO
|
|
644
|
+
// params: 'anno-undefined-param',
|
|
645
|
+
// actions: 'anno-undefined-action',
|
|
646
|
+
// };
|
|
1585
647
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
648
|
+
function checkRemainingMemberExtensions( parent, ext, prop, name ) {
|
|
649
|
+
// console.log('CRME:',prop,name,parent,ext)
|
|
650
|
+
const dict = parent[prop];
|
|
651
|
+
if (!dict) {
|
|
652
|
+
// TODO: check - for each name? - better locations
|
|
653
|
+
const location = ext._parent[prop][$location] || ext.name.location;
|
|
654
|
+
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
655
|
+
switch (prop) {
|
|
656
|
+
// TODO: change texts, somehow similar to checkDefinitions() ?
|
|
657
|
+
case 'elements':
|
|
658
|
+
case 'enum': // TODO: extra?
|
|
659
|
+
warning( 'anno-unexpected-elements', [ location, ext._parent ],
|
|
660
|
+
{ '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
|
|
661
|
+
std: 'Elements only exist in entities, types or typed constructs',
|
|
662
|
+
entity: 'Elements of entity types can\'t be annotated',
|
|
663
|
+
});
|
|
664
|
+
break;
|
|
665
|
+
case 'params':
|
|
666
|
+
warning( 'anno-unexpected-params', [ location, ext._parent ], {},
|
|
667
|
+
'Parameters only exist for actions or functions' );
|
|
668
|
+
break;
|
|
669
|
+
case 'actions':
|
|
670
|
+
warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
|
|
671
|
+
'Actions and functions only exist top-level and for entities' );
|
|
672
|
+
break;
|
|
673
|
+
default:
|
|
674
|
+
// assert
|
|
1608
675
|
}
|
|
676
|
+
return false;
|
|
1609
677
|
}
|
|
1610
|
-
if (
|
|
1611
|
-
// TODO:
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
678
|
+
else if (!dict[name]) {
|
|
679
|
+
// TODO: make variant `returns` an auto-variant for ($ART) ?
|
|
680
|
+
const inReturns = parent._parent?.returns && parent._parent;
|
|
681
|
+
const art = inReturns || parent;
|
|
682
|
+
switch (prop) {
|
|
683
|
+
case 'elements':
|
|
684
|
+
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
685
|
+
{ '#': (inReturns ? 'returns' : 'element'), art, name },
|
|
686
|
+
parent.elements );
|
|
687
|
+
break;
|
|
688
|
+
case 'enum': // TODO: extra msg id?
|
|
689
|
+
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
690
|
+
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
691
|
+
parent.enum );
|
|
692
|
+
break;
|
|
693
|
+
case 'params':
|
|
694
|
+
notFound( 'anno-undefined-param', ext.name.location, ext,
|
|
695
|
+
{ '#': 'param', art: parent, name },
|
|
696
|
+
parent.params );
|
|
697
|
+
break;
|
|
698
|
+
case 'actions':
|
|
699
|
+
notFound( 'anno-undefined-action', ext.name.location, ext,
|
|
700
|
+
{ '#': 'action', art: parent, name },
|
|
701
|
+
parent.actions );
|
|
702
|
+
break;
|
|
703
|
+
default:
|
|
704
|
+
// assert
|
|
1621
705
|
}
|
|
1622
|
-
applyAllExtensions( art );
|
|
1623
706
|
}
|
|
707
|
+
return true;
|
|
1624
708
|
}
|
|
1625
709
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const dict = Object.create(null);
|
|
1630
|
-
for (const ext of art._extensions) {
|
|
1631
|
-
for (const prop in ext) {
|
|
1632
|
-
if (ext[prop] === undefined) // deleted propery
|
|
1633
|
-
continue;
|
|
1634
|
-
// TODO: do this check nicer (after complete move to new extensions mechanism)
|
|
1635
|
-
if (prop.charAt(0) === '@' || prop === 'doc' ||
|
|
1636
|
-
prop === 'includes' || prop === 'columns' ||
|
|
1637
|
-
prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
|
|
1638
|
-
if (!hasOnlySubExtensions)
|
|
1639
|
-
pushToDict( dict, prop, ext );
|
|
1640
|
-
}
|
|
1641
|
-
else if (prop === 'elements' || prop === 'enum' || prop === 'actions' ||
|
|
1642
|
-
prop === 'params' || prop === 'returns') {
|
|
1643
|
-
if (ext.kind === 'extend')
|
|
1644
|
-
pushToDict( dict, 'includes', ext );
|
|
1645
|
-
pushToDict( dict, prop, ext );
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
art._extensions = dict;
|
|
710
|
+
function notFound( msgId, location, address, args, validDict ) {
|
|
711
|
+
const msg = message( msgId, [ location, address ], args );
|
|
712
|
+
attachAndEmitValidNames( msg, validDict );
|
|
1650
713
|
}
|
|
1651
714
|
|
|
1652
|
-
|
|
1653
|
-
const extensions = art._extensions;
|
|
1654
|
-
for (const prop in extensions) {
|
|
1655
|
-
// TODO: do the following `if` in a nicer way
|
|
1656
|
-
if ([ 'elements', 'enum', 'actions', 'params', 'returns' ].includes( prop ))
|
|
1657
|
-
continue; // currently just annotates on sub elements - TODO: error here
|
|
1658
|
-
// annotations, `doc`, `includes`, `columns`, `length`, ...
|
|
1659
|
-
const scheduled = [];
|
|
1660
|
-
// sort extensions according to layer (specified elements are bottom layer):
|
|
1661
|
-
const layered = layeredExtensions( extensions[prop] );
|
|
715
|
+
// For createRemainingAnnotateStatements(): -----------------------------------
|
|
1662
716
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
717
|
+
function createSuperAnnotate( annotate ) {
|
|
718
|
+
const extensions = annotate._extensions;
|
|
719
|
+
if (extensions && !annotate._main) {
|
|
720
|
+
const { absolute } = annotate.name;
|
|
721
|
+
const isLocalized = absolute.startsWith( 'localized.' ); // TODO: && anno
|
|
722
|
+
const art = model.definitions[absolute];
|
|
723
|
+
for (const ext of extensions)
|
|
724
|
+
checkRemainingMainExtensions( art, ext, isLocalized );
|
|
725
|
+
if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
|
|
726
|
+
setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
|
|
727
|
+
// direct annotations on builtins or on the builtins for propagation, and
|
|
728
|
+
// also shallow-copied to $collectedExtensions for to-csn
|
|
729
|
+
for (const prop in art) {
|
|
730
|
+
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
731
|
+
annotate[prop] = art[prop];
|
|
1676
732
|
}
|
|
1677
|
-
if (issue || index > 0)
|
|
1678
|
-
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
1679
733
|
}
|
|
1680
|
-
//
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
delete extensions[prop];
|
|
734
|
+
if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
|
|
735
|
+
annotate.location = extensions[0].location;
|
|
736
|
+
annotate.name.location = extensions[0].name.location;
|
|
737
|
+
}
|
|
1685
738
|
}
|
|
739
|
+
extendArtifactBefore( annotate );
|
|
740
|
+
extendArtifactAfter( annotate );
|
|
741
|
+
forEachMember( annotate, createSuperAnnotate );
|
|
1686
742
|
}
|
|
1687
743
|
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
744
|
+
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
745
|
+
if (localized) // TODO v4: ignore only for annotate
|
|
746
|
+
return;
|
|
747
|
+
if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
|
|
748
|
+
return;
|
|
749
|
+
// else if (ext.kind === 'extend') { // TODO v4 - add error
|
|
750
|
+
// }
|
|
751
|
+
if (art?.kind === 'namespace') {
|
|
752
|
+
// TODO: not at all different to having no definition
|
|
753
|
+
info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
754
|
+
'Namespaces can\'t be annotated' );
|
|
755
|
+
}
|
|
756
|
+
else if (art?.builtin) {
|
|
757
|
+
info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
758
|
+
'Builtin types should not be annotated. Use custom type instead' );
|
|
759
|
+
}
|
|
1692
760
|
}
|
|
1693
761
|
|
|
1694
|
-
//
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
? 'ext-duplicate-extend-type'
|
|
1710
|
-
: 'ext-duplicate-extend-type-unrelated-layer';
|
|
1711
|
-
// not sure whether to repeat the extended artifact in the message (we
|
|
1712
|
-
// have the semantic location, after all)
|
|
762
|
+
// Issue messages for annotations on namespaces and builtins
|
|
763
|
+
// (TODO: really here?, probably split main artifacts vs returns)
|
|
764
|
+
// see also createRemainingAnnotateStatements() where similar messages are reported
|
|
765
|
+
function checkAnnotate( construct, art ) {
|
|
766
|
+
// TODO: Handle extend statements properly: Different message for empty extend?
|
|
767
|
+
|
|
768
|
+
// --> without art._block, art not found
|
|
769
|
+
if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
|
|
770
|
+
if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
|
|
771
|
+
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
|
|
772
|
+
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
773
|
+
// `art._block` ensures that `art` is a defined def.
|
|
774
|
+
return;
|
|
775
|
+
// warning('ext-unexpected-returns', [ construct.name.location, construct ],
|
|
776
|
+
// { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
|
|
1713
777
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
778
|
+
else if (construct.$syntax !== 'returns' &&
|
|
779
|
+
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
|
|
780
|
+
warning('ext-expected-returns', [ construct.name.location, construct ], {
|
|
781
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
782
|
+
}, {
|
|
783
|
+
std: 'Expected $(CODE)', // unused variant
|
|
784
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
785
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
786
|
+
});
|
|
1721
787
|
}
|
|
1722
788
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// extend, mainly old-style ---------------------------------------------------
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Apply the extensions inside the extensionsDict on the model.
|
|
795
|
+
*
|
|
796
|
+
* First try normally: extends with structure includes; with remaining cyclic
|
|
797
|
+
* includes, do so without includes.
|
|
798
|
+
*/
|
|
799
|
+
function applyExtensions() {
|
|
800
|
+
let noIncludes = false;
|
|
801
|
+
let extNames = Object.keys( extensionsDict ).sort();
|
|
802
|
+
|
|
803
|
+
while (extNames.length) {
|
|
804
|
+
const { length } = extNames;
|
|
805
|
+
for (const name of extNames) {
|
|
806
|
+
const art = model.definitions[name];
|
|
807
|
+
if (art && art.kind !== 'namespace' &&
|
|
808
|
+
extendArtifact( extensionsDict[name], art, noIncludes ))
|
|
809
|
+
delete extensionsDict[name];
|
|
1733
810
|
}
|
|
811
|
+
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
812
|
+
if (extNames.length >= length)
|
|
813
|
+
noIncludes = Object.keys( extensionsDict ); // = no includes
|
|
1734
814
|
}
|
|
1735
815
|
}
|
|
1736
816
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
817
|
+
/**
|
|
818
|
+
* Extend artifact `art` by `extensions`. `noIncludes` can have values:
|
|
819
|
+
* - false: includes are applied, extend and annotate is performed
|
|
820
|
+
* - true: includes are not applied, extend and annotate is performed
|
|
821
|
+
* - 'gen': no includes and no extensions allowed, annotate is performed
|
|
822
|
+
*
|
|
823
|
+
* @param {XSN.Extension[]} extensions
|
|
824
|
+
* @param {XSN.Definition} art
|
|
825
|
+
* @param {boolean|'gen'} [noIncludes=false]
|
|
826
|
+
*/
|
|
827
|
+
function extendArtifact( extensions, art, noIncludes = false ) {
|
|
828
|
+
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
829
|
+
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
830
|
+
return false;
|
|
831
|
+
if (Array.isArray( noIncludes )) {
|
|
832
|
+
canApplyIncludes( art, art, noIncludes );
|
|
833
|
+
extensions.forEach( ext => canApplyIncludes( ext, art, noIncludes ) );
|
|
1750
834
|
}
|
|
1751
|
-
else if (
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
|
|
1757
|
-
else
|
|
1758
|
-
query.columns.push( ...ext.columns );
|
|
835
|
+
else if (!noIncludes &&
|
|
836
|
+
!(canApplyIncludes( art, art ) &&
|
|
837
|
+
extensions.every( ext => canApplyIncludes( ext, art) ))) {
|
|
838
|
+
// console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
|
|
839
|
+
return false;
|
|
1759
840
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
841
|
+
if (!art.query) {
|
|
842
|
+
model._entities.push( art ); // add structure with includes in dep order
|
|
843
|
+
art.$entity = ++model.$entity;
|
|
1763
844
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
845
|
+
if (!noIncludes && art.includes)
|
|
846
|
+
applyIncludes( art, art );
|
|
847
|
+
// checkExtensionsKind( extensions, art );
|
|
848
|
+
extendMembers( extensions, art, noIncludes === 'gen' );
|
|
849
|
+
if (!noIncludes && art.includes) {
|
|
850
|
+
// early propagation of specific annotation assignments
|
|
851
|
+
propagateEarly( art, '@cds.autoexpose' );
|
|
852
|
+
propagateEarly( art, '@fiori.draft.enabled' );
|
|
1767
853
|
}
|
|
854
|
+
// TODO: complain about element extensions inside projection
|
|
855
|
+
return true;
|
|
1768
856
|
}
|
|
1769
857
|
|
|
1770
|
-
function
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
858
|
+
function extendMembers( extensions, art, noExtend ) {
|
|
859
|
+
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
860
|
+
const elemExtensions = [];
|
|
861
|
+
if (art._main) // extensions already sorted for main artifacts
|
|
862
|
+
extensions.sort( layers.compareLayer );
|
|
863
|
+
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
864
|
+
// console.log('EM:',art.name,extensions,art._extensions)
|
|
865
|
+
for (const ext of extensions) { // those in extMap.includes
|
|
866
|
+
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
867
|
+
// 'Info', 'EXT').toString())
|
|
868
|
+
if (ext.name._artifact === undefined) { // not already applied
|
|
869
|
+
setArtifactLink( ext.name, art );
|
|
870
|
+
if (noExtend && ext.kind === 'extend') {
|
|
871
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
872
|
+
'You can\'t use EXTEND on the generated $(ART)' );
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (ext.includes) {
|
|
876
|
+
// TODO: currently, re-compiling from gensrc does not give the exact
|
|
877
|
+
// element sequence - we need something like
|
|
878
|
+
// includes = ['Base1',3,'Base2']
|
|
879
|
+
// where 3 means adding the next 3 elements before applying include 'Base2'
|
|
880
|
+
if (art.includes)
|
|
881
|
+
art.includes.push(...ext.includes);
|
|
882
|
+
else
|
|
883
|
+
art.includes = [ ...ext.includes ];
|
|
884
|
+
applyIncludes( ext, art );
|
|
885
|
+
}
|
|
886
|
+
// console.log(ext,art)
|
|
887
|
+
checkAnnotate( ext, art );
|
|
888
|
+
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
889
|
+
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
890
|
+
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
891
|
+
}
|
|
892
|
+
for (const name in ext.elements) {
|
|
893
|
+
const elem = ext.elements[name];
|
|
894
|
+
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
895
|
+
elemExtensions.push( elem );
|
|
896
|
+
break; // more than one elem in same EXTEND is fine
|
|
897
|
+
}
|
|
898
|
+
}
|
|
1779
899
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
900
|
+
if (elemExtensions.length > 1)
|
|
901
|
+
reportUnstableExtensions( elemExtensions );
|
|
902
|
+
|
|
903
|
+
// This whole function will be removed with a next change - no need to have nice code here:
|
|
904
|
+
const dict = Object.create(null);
|
|
905
|
+
// actions cannot be extended anyway. TODO: there should be a message
|
|
906
|
+
// (possible with CSN input), but that was missing before this change, too.
|
|
907
|
+
for (const e of extensions) {
|
|
908
|
+
if (!e.elements)
|
|
909
|
+
continue;
|
|
910
|
+
for (const n in e.elements) {
|
|
911
|
+
if (e.elements[n].kind === 'extend')
|
|
912
|
+
pushToDict( dict, n, e.elements[n] );
|
|
913
|
+
}
|
|
1784
914
|
}
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
915
|
+
for (const name in dict) {
|
|
916
|
+
let obj = art;
|
|
917
|
+
if (obj.targetAspect)
|
|
918
|
+
obj = obj.targetAspect;
|
|
919
|
+
while (obj.items)
|
|
920
|
+
obj = obj.items;
|
|
921
|
+
const validDict = obj.elements || obj.enum;
|
|
922
|
+
const member = validDict && validDict[name];
|
|
923
|
+
if (!member)
|
|
924
|
+
extendNothing( dict[name], 'elements', name, art, validDict );
|
|
925
|
+
else if (!(member.$duplicates))
|
|
926
|
+
extendMembers( dict[name], member );
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
932
|
+
* except if all extensions are in the same file.
|
|
933
|
+
*
|
|
934
|
+
* @param {XSN.Extension[]} extensions
|
|
935
|
+
*/
|
|
936
|
+
function reportUnstableExtensions( extensions ) {
|
|
937
|
+
// No message if all extensions are in the same file:
|
|
938
|
+
const file = layers.realname( extensions[0] );
|
|
939
|
+
if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
|
|
940
|
+
return;
|
|
941
|
+
// Similar to chooseAssignment(), TODO there: also extra intralayer message
|
|
942
|
+
// as this is a modeling error
|
|
943
|
+
let lastExt = null;
|
|
944
|
+
let open = []; // the "highest" layers
|
|
945
|
+
for (const ext of extensions) {
|
|
946
|
+
const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
947
|
+
if (!open.length) {
|
|
948
|
+
lastExt = ext;
|
|
949
|
+
open = [ extLayer.realname ];
|
|
1792
950
|
}
|
|
1793
|
-
else {
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
result.push( prevItem );
|
|
1798
|
-
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
1799
|
-
upToSpec = false;
|
|
1800
|
-
break;
|
|
1801
|
-
}
|
|
951
|
+
else if (extLayer.realname === open[open.length - 1]) { // in same layer
|
|
952
|
+
if (lastExt) {
|
|
953
|
+
message( 'extend-repeated-intralayer', [ lastExt.location, lastExt ] );
|
|
954
|
+
lastExt = null;
|
|
1802
955
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
956
|
+
message( 'extend-repeated-intralayer', [ ext.location, ext ] );
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
if (lastExt && (open.length > 1 || !extLayer._layerExtends[open[0]])) {
|
|
960
|
+
// report for lastExt if that is unrelated to other open exts or current ext
|
|
961
|
+
message( 'extend-unrelated-layer', [ lastExt.location, lastExt ], {},
|
|
962
|
+
'Unstable element order due to other extension in unrelated layer' );
|
|
1807
963
|
}
|
|
964
|
+
lastExt = ext;
|
|
965
|
+
open = open.filter( name => !extLayer._layerExtends[name] );
|
|
966
|
+
open.push( extLayer.realname );
|
|
1808
967
|
}
|
|
1809
968
|
}
|
|
1810
|
-
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
1811
|
-
return { val: result, literal: 'array' };
|
|
1812
969
|
}
|
|
1813
|
-
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
1814
970
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* @param {XSN.Extension[]} extensions
|
|
974
|
+
* @param {string} prop
|
|
975
|
+
* @param {string} name
|
|
976
|
+
* @param {XSN.Artifact} art
|
|
977
|
+
* @param {object} validDict
|
|
978
|
+
*/
|
|
979
|
+
function extendNothing( extensions, prop, name, art, validDict ) {
|
|
980
|
+
const artName = searchName( art, name, dictKinds[prop] );
|
|
981
|
+
for (const ext of extensions) {
|
|
982
|
+
// TODO: use shared functionality with notFound in resolver.js
|
|
983
|
+
const { location } = ext.name;
|
|
984
|
+
const extName = { ...artName, kind: ext.kind };
|
|
985
|
+
const msg
|
|
986
|
+
= error( 'extend-undefined', [ location, extName ],
|
|
987
|
+
{ art: artName },
|
|
988
|
+
{
|
|
989
|
+
std: 'Unknown $(ART) - nothing to extend',
|
|
990
|
+
// eslint-disable-next-line max-len
|
|
991
|
+
element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
|
|
992
|
+
action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
|
|
993
|
+
} );
|
|
994
|
+
attachAndEmitValidNames(msg, validDict);
|
|
1823
995
|
}
|
|
1824
|
-
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// includes ----------------------------------------------------------------
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Returns true, if `art.includes` can be applied on `target`.
|
|
1002
|
+
* They can't be applied if any of the artifacts referenced in
|
|
1003
|
+
* `art.includes` are yet to be extended.
|
|
1004
|
+
* `art !== target` if `art` is an extension.
|
|
1005
|
+
*
|
|
1006
|
+
* @param {XSN.Definition} art
|
|
1007
|
+
* @param {XSN.Artifact} target
|
|
1008
|
+
* @returns {boolean}
|
|
1009
|
+
*/
|
|
1010
|
+
function canApplyIncludes( art, target, justResolveCyclic ) {
|
|
1011
|
+
if (!art.includes)
|
|
1825
1012
|
return true;
|
|
1013
|
+
const isView = !!target.query;
|
|
1014
|
+
for (const ref of art.includes) {
|
|
1015
|
+
const name = resolveUncheckedPath( ref, 'include', art );
|
|
1016
|
+
// console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
|
|
1017
|
+
if (justResolveCyclic) {
|
|
1018
|
+
if (!justResolveCyclic.includes( name ))
|
|
1019
|
+
continue;
|
|
1020
|
+
delete ref._artifact;
|
|
1021
|
+
}
|
|
1022
|
+
else if (name && name in extensionsDict) {
|
|
1023
|
+
// one of the includes has itself extensions that need to be applied first
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
else if (ref._artifact) {
|
|
1027
|
+
delete ref._artifact;
|
|
1028
|
+
}
|
|
1029
|
+
resolvePath( ref, isView ? 'viewInclude' : 'include', art );
|
|
1826
1030
|
}
|
|
1827
|
-
|
|
1828
|
-
{ anno: annoName, code: '... up to', '#': literal },
|
|
1829
|
-
{
|
|
1830
|
-
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
1831
|
-
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
1832
|
-
// eslint-disable-next-line max-len
|
|
1833
|
-
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
1834
|
-
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
1835
|
-
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
1836
|
-
} );
|
|
1837
|
-
return false;
|
|
1031
|
+
return true;
|
|
1838
1032
|
}
|
|
1839
1033
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
return
|
|
1034
|
+
/**
|
|
1035
|
+
* Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
|
|
1036
|
+
* If `ext === art`, then includes of the artifact itself are applied.
|
|
1037
|
+
* If `ext !== art`, applies includes on the extensions, not artifact.
|
|
1038
|
+
* Sets `_ancestor` links on `art`.
|
|
1039
|
+
*
|
|
1040
|
+
* Examples:
|
|
1041
|
+
* ext === art: `entity E : F {}` => add elements of F to E
|
|
1042
|
+
* ext !== art: `extend E with F` => add elements of F to extension on E
|
|
1043
|
+
*
|
|
1044
|
+
* @param {XSN.Extension} ext
|
|
1045
|
+
* @param {XSN.Artifact} art
|
|
1046
|
+
*/
|
|
1047
|
+
function applyIncludes( ext, art ) {
|
|
1048
|
+
if (kindProperties[art.kind].include !== true) {
|
|
1049
|
+
error( 'extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
1050
|
+
{ meta: art.kind } );
|
|
1051
|
+
return;
|
|
1858
1052
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1053
|
+
|
|
1054
|
+
if (!art.query) {
|
|
1055
|
+
if (!art._ancestors)
|
|
1056
|
+
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
1057
|
+
for (const ref of ext.includes) {
|
|
1058
|
+
const template = ref._artifact;
|
|
1059
|
+
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
1060
|
+
if (template) {
|
|
1061
|
+
if (template._ancestors)
|
|
1062
|
+
art._ancestors.push( ...template._ancestors );
|
|
1063
|
+
art._ancestors.push( template );
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1862
1066
|
}
|
|
1863
|
-
|
|
1067
|
+
if (!art.query) // do not set art.elements and art.enums with query entity!
|
|
1068
|
+
includeMembers( ext, art, 'elements' );
|
|
1069
|
+
includeMembers( ext, art, 'actions' );
|
|
1864
1070
|
}
|
|
1865
1071
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`.
|
|
1074
|
+
* If `art` is `ext`, set the parent link accordingly.
|
|
1075
|
+
*
|
|
1076
|
+
* @param {XSN.Extension} ext
|
|
1077
|
+
* @param {XSN.Artifact} art
|
|
1078
|
+
* @param {string} prop: 'elements' or 'actions'
|
|
1079
|
+
*/
|
|
1080
|
+
function includeMembers( ext, art, prop ) {
|
|
1081
|
+
// TODO two kind of messages:
|
|
1082
|
+
// Error 'More than one include defines element "A"' (at include ref)
|
|
1083
|
+
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
1084
|
+
const parent = ext === art && art;
|
|
1085
|
+
const members = ext[prop];
|
|
1086
|
+
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
1087
|
+
for (const ref of ext.includes) {
|
|
1088
|
+
const template = ref._artifact; // already resolved
|
|
1089
|
+
if (template) { // be robust
|
|
1090
|
+
forEachInOrder( template, prop, ( origin, name ) => {
|
|
1091
|
+
if (members && name in members)
|
|
1092
|
+
return; // warning for overwritten element in checks.js
|
|
1093
|
+
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
1094
|
+
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
1095
|
+
dictAdd( ext[prop], name, elem );
|
|
1096
|
+
elem.$inferred = 'include';
|
|
1097
|
+
if (origin.masked)
|
|
1098
|
+
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1099
|
+
if (origin.key)
|
|
1100
|
+
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1101
|
+
if (origin.value && origin.$syntax === 'calc') {
|
|
1102
|
+
// TODO: If paths become invalid in the new artifact, should we mark
|
|
1103
|
+
// all usages in the expressions? Possibly just the first one?
|
|
1104
|
+
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
1105
|
+
elem.$syntax = 'calc';
|
|
1106
|
+
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
1107
|
+
}
|
|
1108
|
+
// TODO: also complain if elem is just defined in art
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
checkRedefinitionThroughIncludes( parent, prop );
|
|
1113
|
+
// TODO: expand elements having direct elements (if needed)
|
|
1114
|
+
if (members) {
|
|
1115
|
+
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
1116
|
+
dictAdd( ext[prop], name, elem );
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1869
1119
|
}
|
|
1870
1120
|
|
|
1871
1121
|
/**
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1122
|
+
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1123
|
+
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
|
1874
1124
|
*
|
|
1875
|
-
*
|
|
1876
|
-
* @param {object} source
|
|
1125
|
+
* TODO(v4): Make this a hard error; see checkRedefinition(); maybe combine both;
|
|
1877
1126
|
*/
|
|
1878
|
-
function
|
|
1879
|
-
if (!
|
|
1127
|
+
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
1128
|
+
if (!parent[prop])
|
|
1880
1129
|
return;
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1130
|
+
forEachInOrder(parent, prop, ( member, name ) => {
|
|
1131
|
+
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
1132
|
+
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
1133
|
+
if (isBetaEnabled(options, 'v4preview')) {
|
|
1134
|
+
error( 'duplicate-definition', [ parent.name.location, member ],
|
|
1135
|
+
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
// Error accidentally removed in v2/v3, therefore only a warning.
|
|
1139
|
+
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
|
|
1140
|
+
{ '#': prop, name, sorted_arts: includes } );
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1891
1144
|
}
|
|
1892
1145
|
}
|
|
1893
1146
|
|
|
@@ -1901,7 +1154,8 @@ function extend( model ) {
|
|
|
1901
1154
|
function layeredExtensions( extensions ) {
|
|
1902
1155
|
const layered = Object.create(null);
|
|
1903
1156
|
for (const ext of extensions) {
|
|
1904
|
-
const layer = (ext.kind === 'annotate' || ext.kind === 'extend'
|
|
1157
|
+
const layer = (ext.kind === 'annotate' || ext.kind === 'extend' || ext.kind === 'source') &&
|
|
1158
|
+
layers.layer( ext );
|
|
1905
1159
|
// just consider layer if Extend/Annotate, not Define
|
|
1906
1160
|
const name = (layer) ? layer.realname : '';
|
|
1907
1161
|
const done = layered[name];
|
|
@@ -1974,72 +1228,33 @@ function inMoreThanOneFile( extensions ) {
|
|
|
1974
1228
|
* Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
|
|
1975
1229
|
*/
|
|
1976
1230
|
function compareExtensions( a, b ) {
|
|
1977
|
-
const fileA = layers.realname( a
|
|
1978
|
-
const fileB = layers.realname( b
|
|
1231
|
+
const fileA = layers.realname( a );
|
|
1232
|
+
const fileB = layers.realname( b );
|
|
1979
1233
|
if (fileA !== fileB)
|
|
1980
1234
|
return (fileA > fileB) ? 1 : -1;
|
|
1981
1235
|
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
|
|
1982
1236
|
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
1983
1237
|
}
|
|
1984
1238
|
|
|
1985
|
-
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
1986
|
-
const args = relations.map( eq );
|
|
1987
|
-
return (args.length === 1)
|
|
1988
|
-
? args[0]
|
|
1989
|
-
: { op: { val: 'and', location }, args, location };
|
|
1990
|
-
|
|
1991
|
-
function eq( refs ) {
|
|
1992
|
-
if (Array.isArray(refs))
|
|
1993
|
-
return { op: { val: '=', location }, args: refs.map( ref ), location };
|
|
1994
|
-
|
|
1995
|
-
const { id } = refs.name;
|
|
1996
|
-
return {
|
|
1997
|
-
op: { val: '=', location },
|
|
1998
|
-
args: [
|
|
1999
|
-
{ path: [ { id: assocname, location }, { id, location } ], location },
|
|
2000
|
-
{ path: [ { id: `${ prefix }${ id }`, location } ], location },
|
|
2001
|
-
],
|
|
2002
|
-
location,
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
function ref( path ) {
|
|
2006
|
-
return { path: path.split('.').map( id => ({ id, location }) ), location };
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
1239
|
|
|
2010
1240
|
/**
|
|
2011
|
-
*
|
|
2012
|
-
*
|
|
2013
|
-
* `typeExtensions()` later.
|
|
1241
|
+
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
1242
|
+
* if they have the property.
|
|
2014
1243
|
*
|
|
2015
|
-
* @param {XSN.
|
|
2016
|
-
* @param {
|
|
1244
|
+
* @param {XSN.Definition} art
|
|
1245
|
+
* @param {string} prop
|
|
2017
1246
|
*/
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
const languages = model.definitions['sap.common.Languages'];
|
|
2029
|
-
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
|
|
2030
|
-
|
|
2031
|
-
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
2032
|
-
const variant = !languages ? 'std' : 'code';
|
|
2033
|
-
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
2034
|
-
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
2035
|
-
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
2036
|
-
}, {
|
|
2037
|
-
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
2038
|
-
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
2039
|
-
});
|
|
1247
|
+
function propagateEarly( art, prop ) {
|
|
1248
|
+
if (art[prop])
|
|
1249
|
+
return;
|
|
1250
|
+
for (const ref of art.includes) {
|
|
1251
|
+
const aspect = ref._artifact;
|
|
1252
|
+
if (aspect) {
|
|
1253
|
+
const anno = aspect[prop];
|
|
1254
|
+
if (anno && (anno.val !== null || !art[prop]))
|
|
1255
|
+
art[prop] = Object.assign( { $inferred: 'include' }, anno );
|
|
1256
|
+
}
|
|
2040
1257
|
}
|
|
2041
|
-
|
|
2042
|
-
return !!commonLanguagesEntity;
|
|
2043
1258
|
}
|
|
2044
1259
|
|
|
2045
1260
|
|