@sap/cds-compiler 3.7.2 → 3.8.0
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 +63 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +24 -5
- package/lib/compiler/base.js +49 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +701 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +12 -53
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4810 -4482
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +55 -5
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +47 -8
- package/lib/language/language.g4 +88 -62
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +14 -2
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
package/lib/compiler/extend.js
CHANGED
|
@@ -9,14 +9,17 @@ const { searchName, weakLocation } = require('../base/messages');
|
|
|
9
9
|
const {
|
|
10
10
|
isDeprecatedEnabled,
|
|
11
11
|
forEachGeneric, forEachInOrder, forEachDefinition,
|
|
12
|
+
forEachMember,
|
|
13
|
+
isBetaEnabled,
|
|
12
14
|
} = require('../base/model');
|
|
13
|
-
const { dictAdd,
|
|
15
|
+
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
14
16
|
const { kindProperties, dictKinds } = require('./base');
|
|
15
17
|
const {
|
|
16
18
|
setLink,
|
|
17
19
|
setArtifactLink,
|
|
18
20
|
copyExpr,
|
|
19
|
-
|
|
21
|
+
setAnnotation,
|
|
22
|
+
setExpandStatusAnnotate,
|
|
20
23
|
linkToOrigin,
|
|
21
24
|
setMemberParent,
|
|
22
25
|
dependsOnSilent,
|
|
@@ -27,8 +30,10 @@ const {
|
|
|
27
30
|
annotationHasEllipsis,
|
|
28
31
|
} = require('./utils');
|
|
29
32
|
const layers = require('./moduleLayers');
|
|
30
|
-
|
|
31
|
-
const
|
|
33
|
+
|
|
34
|
+
const $location = Symbol.for('cds.$location');
|
|
35
|
+
|
|
36
|
+
const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
|
|
32
37
|
|
|
33
38
|
function extend( model ) {
|
|
34
39
|
const { options } = model;
|
|
@@ -39,23 +44,22 @@ function extend( model ) {
|
|
|
39
44
|
const {
|
|
40
45
|
resolvePath,
|
|
41
46
|
resolveUncheckedPath,
|
|
42
|
-
|
|
43
|
-
checkAnnotate,
|
|
47
|
+
resolveTypeArgumentsUnchecked,
|
|
44
48
|
attachAndEmitValidNames,
|
|
45
|
-
checkDefinitions,
|
|
46
49
|
initArtifact,
|
|
47
50
|
initMembers,
|
|
48
|
-
extensionsDict, // not a function - TODO
|
|
49
51
|
} = model.$functions;
|
|
50
52
|
|
|
51
53
|
Object.assign( model.$functions, {
|
|
52
54
|
lateExtensions,
|
|
53
|
-
applyTypeExtensions,
|
|
54
55
|
chooseAnnotationsInArtifact,
|
|
55
|
-
|
|
56
|
-
copyAnnotationsForExtensions,
|
|
56
|
+
extendArtifactAfter,
|
|
57
57
|
} );
|
|
58
58
|
|
|
59
|
+
const extensionsDict = Object.create(null);
|
|
60
|
+
forEachDefinition( model, tagIncludes ); // TODO TMP
|
|
61
|
+
|
|
62
|
+
forEachDefinition( model, chooseAnnotationsInArtifact );
|
|
59
63
|
applyExtensions();
|
|
60
64
|
|
|
61
65
|
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
@@ -63,9 +67,8 @@ function extend( model ) {
|
|
|
63
67
|
|
|
64
68
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
65
69
|
|
|
66
|
-
lateExtensions( false );
|
|
67
|
-
|
|
68
70
|
compositionChildPersistence();
|
|
71
|
+
return;
|
|
69
72
|
|
|
70
73
|
/**
|
|
71
74
|
* Process "composition of" artifacts.
|
|
@@ -115,90 +118,442 @@ function extend( model ) {
|
|
|
115
118
|
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
116
119
|
if (def._parent)
|
|
117
120
|
processCompositionPersistence(def._parent);
|
|
118
|
-
copyPersistenceAnnotations(def, def._parent
|
|
121
|
+
copyPersistenceAnnotations( def, def._parent );
|
|
119
122
|
processed.add(def);
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
// TMP:
|
|
128
|
+
function tagIncludes( art ) {
|
|
129
|
+
if (art.includes)
|
|
130
|
+
extensionsDict[art.name.absolute] = [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//-----------------------------------------------------------------------------
|
|
134
|
+
// Extensions: general algorithm
|
|
135
|
+
//-----------------------------------------------------------------------------
|
|
136
|
+
// extendArtifactBefore, extendArtifactAfter, createRemainingAnnotateStatements
|
|
137
|
+
|
|
138
|
+
// TODO: assert that we have not yet transformed/used _extensions on sub elements
|
|
139
|
+
// TODO necessary(?): transformArtifactExtensions must ensure that each annotate
|
|
140
|
+
// is in either returns,items,elements,enum
|
|
141
|
+
function extendArtifactAfter( art ) {
|
|
142
|
+
const extensionsMap = art._extensions;
|
|
143
|
+
if (!extensionsMap || art.builtin) // builtin members handled via "super annotate"
|
|
144
|
+
return;
|
|
145
|
+
// type extensions after having “populated” the artifact ($typeArgs -> length,
|
|
146
|
+
// …, TODO: do that there) and setting an _effectiveType:
|
|
147
|
+
if (art.$typeExts) {
|
|
148
|
+
const { type } = art; // if the type is not inferred, it is the origin...
|
|
149
|
+
if (type?._artifact && !type.$inferred) // ...and thus is resolved
|
|
150
|
+
resolveTypeArgumentsUnchecked( art, type._artifact, art );
|
|
151
|
+
const exts = art.$typeExts;
|
|
152
|
+
applyTypeExtensions( art, exts.length, 'length' );
|
|
153
|
+
const scaleDiff = applyTypeExtensions( art, exts.scale, 'scale' );
|
|
154
|
+
applyTypeExtensions( art, exts.precision, 'precision', scaleDiff );
|
|
155
|
+
applyTypeExtensions( art, exts.srid, 'srid' );
|
|
156
|
+
delete art.$typeExts;
|
|
157
|
+
}
|
|
158
|
+
// TODO tmp: no proper XSN representation yet for annotate … with returns:
|
|
159
|
+
if (art.kind === 'annotate' && !art.returns &&
|
|
160
|
+
(extensionsMap.elements?.some( e => e.$syntax === 'returns' ) ||
|
|
161
|
+
extensionsMap.enum?.some( e => e.$syntax === 'returns' )))
|
|
162
|
+
annotateCreate( art, '', art, 'returns' );
|
|
163
|
+
|
|
164
|
+
moveDictExtensions( art, extensionsMap, 'params' );
|
|
165
|
+
// moveReturnsExtensions( art, extensionsMap );
|
|
166
|
+
const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;
|
|
167
|
+
if (sub) {
|
|
168
|
+
if (art.returns) { // after having applied params!
|
|
169
|
+
extendHandleReturns( extensionsMap.elements, art );
|
|
170
|
+
extendHandleReturns( extensionsMap.enum, art );
|
|
171
|
+
}
|
|
172
|
+
// care about 'ext-unexpected-returns' in a later change if we have XSN returns
|
|
173
|
+
pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
|
|
174
|
+
pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
moveDictExtensions( art, extensionsMap,
|
|
178
|
+
(art.enum && art.kind !== 'annotate' ? 'enum' : 'elements'), 'elements' );
|
|
179
|
+
moveDictExtensions( art, extensionsMap, 'enum' );
|
|
180
|
+
}
|
|
181
|
+
moveDictExtensions( art, extensionsMap, 'actions' );
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create super annotate statements for remaining extensions
|
|
186
|
+
*/
|
|
187
|
+
function lateExtensions() { // -> createRemainingAnnotateStatements
|
|
188
|
+
model.extensions = Object.values( model.$lateExtensions );
|
|
189
|
+
// TODO: testMode sort?
|
|
190
|
+
model.extensions.forEach( createSuperAnnotate );
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// For extendArtifactAfter(): -------------------------------------------------
|
|
194
|
+
|
|
195
|
+
// Remarks on messages: we allow the type extensions only if the artifact
|
|
196
|
+
// originally had that property → any check of the kind “type prop can only be
|
|
197
|
+
// used with FooBar” is independent from `extend … with type`. Function
|
|
198
|
+
// checkTypeArguments() in resolve.js reports 'type-unexpected-argument', but
|
|
199
|
+
// that is currently incomplete.
|
|
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;
|
|
246
|
+
}
|
|
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
|
+
}
|
|
256
|
+
|
|
257
|
+
function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
|
|
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
|
|
263
|
+
|
|
264
|
+
for (const ext of extensions) {
|
|
265
|
+
const extDict = ext[extProp];
|
|
266
|
+
for (const name in extDict) {
|
|
267
|
+
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
268
|
+
const elemExt = extDict[name];
|
|
269
|
+
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
270
|
+
continue; // definitions inside extend, already handled
|
|
271
|
+
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
272
|
+
const elem = artDict[name] || annotateFor( art, extProp, name );
|
|
273
|
+
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
|
|
274
|
+
pushToDict( elem, '_extensions', elemExt );
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// function moveReturnsExtensions( art, extensionsMap ) {
|
|
280
|
+
// const artReturns = art.returns;
|
|
281
|
+
// const extensions = extensionsMap.returns;
|
|
282
|
+
// // TODO: artItem is null
|
|
283
|
+
// for (const ext of extensions)
|
|
284
|
+
// pushToDict( artReturns, '_extensions', ext.returns );
|
|
285
|
+
// }
|
|
286
|
+
|
|
287
|
+
function annotateFor( art, prop, name ) {
|
|
288
|
+
const base = annotateBase( art );
|
|
289
|
+
if (name === '' && prop === 'params')
|
|
290
|
+
return base.returns || annotateCreate( base, name, base, 'returns' );
|
|
291
|
+
const dict = base[prop] || (base[prop] = Object.create( null ));
|
|
292
|
+
if (name == null)
|
|
293
|
+
return dict;
|
|
294
|
+
return dict[name] || annotateCreate( dict, name, base );
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function annotateBase( art ) {
|
|
298
|
+
while (art._outer) // TOOD: think about anonymous target aspect
|
|
299
|
+
art = art._outer;
|
|
300
|
+
// if (art._annotateS)
|
|
301
|
+
// return art._annotateS;
|
|
302
|
+
if (art.kind === 'annotate')
|
|
303
|
+
return art;
|
|
304
|
+
|
|
305
|
+
// TODO: more to do if annotate can have `returns` property
|
|
306
|
+
if (art.kind === 'select')
|
|
307
|
+
art = art._parent;
|
|
308
|
+
if (art._main)
|
|
309
|
+
return annotateFor( art._parent, kindProperties[art.kind].dict, art.name.id );
|
|
310
|
+
|
|
311
|
+
const { absolute } = art.name;
|
|
312
|
+
return model.$lateExtensions[absolute] || annotateCreate( model.$lateExtensions, absolute );
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function annotateCreate( dict, id, parent, prop ) {
|
|
316
|
+
const annotate = {
|
|
317
|
+
kind: 'annotate',
|
|
318
|
+
name: { id, location: genLocation },
|
|
319
|
+
$inferred: '',
|
|
320
|
+
location: genLocation,
|
|
321
|
+
};
|
|
322
|
+
if (parent) {
|
|
323
|
+
setLink( annotate, '_parent', parent );
|
|
324
|
+
setLink( annotate, '_main', parent._main || parent );
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
annotate.name.absolute = id; // TODO later (if all names are sparse): delete absolute
|
|
328
|
+
}
|
|
329
|
+
dict[prop || id] = annotate;
|
|
330
|
+
return annotate;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function extendHandleReturns( extensions, art ) {
|
|
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
|
+
|
|
124
507
|
// extend ------------------------------------------------------------------
|
|
125
508
|
|
|
126
509
|
/**
|
|
127
510
|
* Apply the extensions inside the extensionsDict on the model.
|
|
128
511
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* Before phase 1: all artifact extensions have been collected (even those
|
|
133
|
-
* inside extend context), only "empty" ones from structure includes are still unknown.
|
|
134
|
-
* After phase 1, all main artifacts are known, also "empty" extensions are known.
|
|
512
|
+
* First try normally: extends with structure includes; with remaining cyclic
|
|
513
|
+
* includes, do so without includes.
|
|
135
514
|
*/
|
|
136
515
|
function applyExtensions() {
|
|
137
|
-
let
|
|
516
|
+
let noIncludes = false;
|
|
138
517
|
let extNames = Object.keys( extensionsDict ).sort();
|
|
139
|
-
|
|
140
|
-
// after the extend for C has been applied (which could have defined C.E).
|
|
141
|
-
// Looping over model.definitions in Phase 1 would miss the `extend
|
|
142
|
-
// context` for a context C.C defined in an `extend context C`.
|
|
143
|
-
//
|
|
144
|
-
// TODO: no need to sort anymore
|
|
518
|
+
|
|
145
519
|
while (extNames.length) {
|
|
146
520
|
const { length } = extNames;
|
|
147
521
|
for (const name of extNames) {
|
|
148
522
|
const art = model.definitions[name];
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
delete extensionsDict[name];
|
|
152
|
-
}
|
|
153
|
-
else if (art.$duplicates) { // cannot extend redefinitions
|
|
523
|
+
if (art && art.kind !== 'namespace' &&
|
|
524
|
+
extendArtifact( extensionsDict[name], art, noIncludes ))
|
|
154
525
|
delete extensionsDict[name];
|
|
155
|
-
}
|
|
156
|
-
else if (phase === 1
|
|
157
|
-
? extendContext( name, art )
|
|
158
|
-
: extendArtifact( extensionsDict[name], art, Array.isArray( phase ) && phase )) {
|
|
159
|
-
delete extensionsDict[name];
|
|
160
|
-
}
|
|
161
526
|
}
|
|
162
527
|
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
else if (extNames.length >= length)
|
|
166
|
-
phase = Object.keys( extensionsDict ); // = no includes
|
|
528
|
+
if (extNames.length >= length)
|
|
529
|
+
noIncludes = Object.keys( extensionsDict ); // = no includes
|
|
167
530
|
}
|
|
168
531
|
}
|
|
169
532
|
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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',
|
|
183
552
|
});
|
|
184
553
|
}
|
|
554
|
+
// TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
|
|
185
555
|
}
|
|
186
|
-
return false;
|
|
187
556
|
}
|
|
188
|
-
|
|
189
|
-
for (const ext of extensionsDict[name]) {
|
|
190
|
-
setArtifactLink( ext.name, art );
|
|
191
|
-
checkDefinitions( ext, art, 'elements'); // error for elements etc
|
|
192
|
-
checkDefinitions( ext, art, 'enum');
|
|
193
|
-
checkDefinitions( ext, art, 'actions');
|
|
194
|
-
checkDefinitions( ext, art, 'params');
|
|
195
|
-
checkDefinitions( ext, art, 'columns');
|
|
196
|
-
if (ext.includes)
|
|
197
|
-
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
198
|
-
checkAnnotate( ext, art );
|
|
199
|
-
copyAnnotationsForExtensions( ext, art );
|
|
200
|
-
}
|
|
201
|
-
return true;
|
|
202
557
|
}
|
|
203
558
|
|
|
204
559
|
/**
|
|
@@ -231,6 +586,7 @@ function extend( model ) {
|
|
|
231
586
|
}
|
|
232
587
|
if (!noIncludes && art.includes)
|
|
233
588
|
applyIncludes( art, art );
|
|
589
|
+
// checkExtensionsKind( extensions, art );
|
|
234
590
|
extendMembers( extensions, art, noIncludes === 'gen' );
|
|
235
591
|
if (!noIncludes && art.includes) {
|
|
236
592
|
// early propagation of specific annotation assignments
|
|
@@ -244,12 +600,14 @@ function extend( model ) {
|
|
|
244
600
|
function extendMembers( extensions, art, noExtend ) {
|
|
245
601
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
246
602
|
const elemExtensions = [];
|
|
247
|
-
|
|
603
|
+
if (art._main) // extensions already sorted for main artifacts
|
|
604
|
+
extensions.sort( layers.compareLayer );
|
|
248
605
|
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
249
|
-
|
|
606
|
+
// console.log('EM:',art.name,extensions,art._extensions)
|
|
607
|
+
for (const ext of extensions) { // those in extMap.includes
|
|
250
608
|
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
251
609
|
// 'Info', 'EXT').toString())
|
|
252
|
-
if (
|
|
610
|
+
if (ext.name._artifact === undefined) { // not already applied
|
|
253
611
|
setArtifactLink( ext.name, art );
|
|
254
612
|
if (noExtend && ext.kind === 'extend') {
|
|
255
613
|
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
@@ -267,12 +625,10 @@ function extend( model ) {
|
|
|
267
625
|
art.includes = [ ...ext.includes ];
|
|
268
626
|
applyIncludes( ext, art );
|
|
269
627
|
}
|
|
628
|
+
// console.log(ext,art)
|
|
270
629
|
checkAnnotate( ext, art );
|
|
271
|
-
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
272
|
-
copyAnnotationsForExtensions( ext, art );
|
|
273
630
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
274
631
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
275
|
-
storeTypeExtension( ext, art );
|
|
276
632
|
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
277
633
|
}
|
|
278
634
|
for (const name in ext.elements) {
|
|
@@ -282,17 +638,24 @@ function extend( model ) {
|
|
|
282
638
|
break; // more than one elem in same EXTEND is fine
|
|
283
639
|
}
|
|
284
640
|
}
|
|
285
|
-
|
|
286
|
-
if (ext.columns) // extend projection
|
|
287
|
-
extendColumns( ext, art );
|
|
288
641
|
}
|
|
289
642
|
if (elemExtensions.length > 1)
|
|
290
643
|
reportUnstableExtensions( elemExtensions );
|
|
291
|
-
if (art._extendType && art._extendType.length > 0)
|
|
292
|
-
reportTypeExtensionsInSameLayer( art._extendType );
|
|
293
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
|
+
}
|
|
294
657
|
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
295
|
-
const dict =
|
|
658
|
+
const dict = extsTmp[prop];
|
|
296
659
|
for (const name in dict) {
|
|
297
660
|
let obj = art;
|
|
298
661
|
if (obj.targetAspect)
|
|
@@ -386,129 +749,6 @@ function extend( model ) {
|
|
|
386
749
|
return !hasError;
|
|
387
750
|
}
|
|
388
751
|
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Copy columns for EXTEND PROJECTION
|
|
392
|
-
*
|
|
393
|
-
* @param {XSN.Extension} ext
|
|
394
|
-
* @param {XSN.Artifact} art
|
|
395
|
-
*/
|
|
396
|
-
function extendColumns( ext, art ) {
|
|
397
|
-
// TODO: consider reportUnstableExtensions
|
|
398
|
-
|
|
399
|
-
const { location } = ext.name;
|
|
400
|
-
const { query } = art;
|
|
401
|
-
if (!query) {
|
|
402
|
-
if (art.kind !== 'annotate')
|
|
403
|
-
error( 'extend-columns', [ location, ext ], { art } );
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (!query.from || !query.from.path) {
|
|
407
|
-
error( 'extend-columns', [ location, ext ], { art } );
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
if (!query.columns)
|
|
411
|
-
query.columns = [ { location, val: '*' } ];
|
|
412
|
-
|
|
413
|
-
for (const column of ext.columns) {
|
|
414
|
-
setLink( column, '_block', ext._block );
|
|
415
|
-
query.columns.push(column);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Similar to chooseAssignment for annotations, this function applies type extensions in order,
|
|
422
|
-
* such that hierarchies are respected.
|
|
423
|
-
* Order already set in `extendMembers()` using `compareLayers()`.
|
|
424
|
-
*
|
|
425
|
-
* @param art
|
|
426
|
-
*/
|
|
427
|
-
function applyTypeExtensions( art ) {
|
|
428
|
-
/**
|
|
429
|
-
* Contains the previous extension for each property that was applied
|
|
430
|
-
* successfully.
|
|
431
|
-
*/
|
|
432
|
-
const previousSuccess = Object.create(null);
|
|
433
|
-
const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
|
|
434
|
-
const artType = art.type?._artifact;
|
|
435
|
-
|
|
436
|
-
for (const ext of art._extendType) {
|
|
437
|
-
if (art.$inferred) {
|
|
438
|
-
// Only report first extension for $inferred artifact. Reduces noise.
|
|
439
|
-
error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const baseType = art._effectiveType; // may be an ENUM -- TODO: not here!
|
|
444
|
-
if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
|
|
445
|
-
// Only report first extension for non-scalar base type. Reduces noise.
|
|
446
|
-
error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
|
|
447
|
-
break;
|
|
448
|
-
}
|
|
449
|
-
else if (!allowedBuiltinCategories.includes(baseType.category)) {
|
|
450
|
-
// Only report first extension for non-scalar type. Reduces noise.
|
|
451
|
-
error('ref-expected-scalar-type', [ ext.name.location, ext ],
|
|
452
|
-
{ '#': 'unsupported', prop: baseType.category });
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
for (const prop of typeParameters.list) {
|
|
457
|
-
if (!ext[prop])
|
|
458
|
-
continue;
|
|
459
|
-
|
|
460
|
-
if (!baseType.parameters?.includes(prop)) {
|
|
461
|
-
// For `extend T with type (length:10)` where T does not expect a length.
|
|
462
|
-
error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
|
|
463
|
-
'#': 'type', prop, art, type: baseType,
|
|
464
|
-
});
|
|
465
|
-
break; // one error for first property is enough
|
|
466
|
-
}
|
|
467
|
-
else if (!art[prop]) {
|
|
468
|
-
// Can only extend properties that exist directly on the artifact.
|
|
469
|
-
error('ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
470
|
-
{ prop, '#': 'new-prop' });
|
|
471
|
-
break; // one error for first property is enough
|
|
472
|
-
}
|
|
473
|
-
else if (art[prop].val === ext[prop].val) {
|
|
474
|
-
// Ignore extensions with same value
|
|
475
|
-
}
|
|
476
|
-
else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
|
|
477
|
-
// Users can't change from/to string value for property,
|
|
478
|
-
// e.g. `variable`/`floating` for Decimal
|
|
479
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
480
|
-
{ '#': 'string', prop } );
|
|
481
|
-
}
|
|
482
|
-
else if (art[prop].val > ext[prop].val) {
|
|
483
|
-
// TODO: Future: Sub-message that points to previous extension?
|
|
484
|
-
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
|
|
485
|
-
prop,
|
|
486
|
-
value: ext[prop].val,
|
|
487
|
-
number: art[prop].val,
|
|
488
|
-
'#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
|
|
489
|
-
} );
|
|
490
|
-
break; // one error for first property is enough
|
|
491
|
-
}
|
|
492
|
-
else if (art[prop].val < ext[prop].val) {
|
|
493
|
-
art[prop] = ext[prop];
|
|
494
|
-
previousSuccess[prop] = ext;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// If `scale` is increased, but `precision` is not, data may be lost in a migration.
|
|
499
|
-
// e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
|
|
500
|
-
// invalid and only allow `1.234`.
|
|
501
|
-
// TODO: Should we actually check that the increase is correct?
|
|
502
|
-
if (ext.scale && !ext.precision) {
|
|
503
|
-
error('ext-invalid-type-property', [ ext.scale.location, ext ], {
|
|
504
|
-
prop: 'scale',
|
|
505
|
-
otherprop: 'precision',
|
|
506
|
-
'#': 'scale',
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
752
|
/**
|
|
513
753
|
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
514
754
|
* except if all extensions are in the same file.
|
|
@@ -550,26 +790,6 @@ function extend( model ) {
|
|
|
550
790
|
}
|
|
551
791
|
}
|
|
552
792
|
|
|
553
|
-
/**
|
|
554
|
-
* Report type extensions in same layer, similar mechanism to chooseAssignment()
|
|
555
|
-
* for annotations.
|
|
556
|
-
*
|
|
557
|
-
* @param {XSN.Extension[]} extensions
|
|
558
|
-
*/
|
|
559
|
-
function reportTypeExtensionsInSameLayer( extensions ) {
|
|
560
|
-
// Group assignments by layer
|
|
561
|
-
const extLayers = layeredAssignments( extensions );
|
|
562
|
-
// We only care about the highest layer
|
|
563
|
-
const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
|
|
564
|
-
|
|
565
|
-
if (issue || assignments.length > 1) { // TODO: allow in same file?
|
|
566
|
-
const id = (issue === 'unrelated')
|
|
567
|
-
? 'ext-duplicate-extend-type-unrelated-layer'
|
|
568
|
-
: 'ext-duplicate-extend-type';
|
|
569
|
-
for (const ext of assignments)
|
|
570
|
-
message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
793
|
|
|
574
794
|
/**
|
|
575
795
|
* @param {XSN.Extension[]} extensions
|
|
@@ -597,77 +817,6 @@ function extend( model ) {
|
|
|
597
817
|
}
|
|
598
818
|
}
|
|
599
819
|
|
|
600
|
-
/**
|
|
601
|
-
* @param {Function|false} [veryLate]
|
|
602
|
-
*/
|
|
603
|
-
function lateExtensions( veryLate ) {
|
|
604
|
-
for (const name in model.$lateExtensions) {
|
|
605
|
-
const art = model.definitions[name];
|
|
606
|
-
const exts = model.$lateExtensions[name];
|
|
607
|
-
if (art && art.kind !== 'namespace') {
|
|
608
|
-
if (art.builtin) {
|
|
609
|
-
for (const ext of exts)
|
|
610
|
-
info( 'anno-builtin', [ ext.name.location, ext ] );
|
|
611
|
-
}
|
|
612
|
-
// created texts entity, auto-exposed entity
|
|
613
|
-
if (exts) {
|
|
614
|
-
extendArtifact( exts, art, 'gen' );
|
|
615
|
-
if (veryLate)
|
|
616
|
-
veryLate( art );
|
|
617
|
-
model.$lateExtensions[name] = null; // done
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
else if (veryLate) {
|
|
621
|
-
// Complain about unused extensions, i.e. those
|
|
622
|
-
// which do not point to a valid artifact
|
|
623
|
-
for (const ext of exts) {
|
|
624
|
-
delete ext.name.path[0]._artifact; // get message for root
|
|
625
|
-
// TODO: make resolvePath('extend'/'annotate') ignore namespaces
|
|
626
|
-
// Don't try to apply annotations in the `localized.` namespace.
|
|
627
|
-
// That's done in `localized.js`.
|
|
628
|
-
if (!name.startsWith('localized.') &&
|
|
629
|
-
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
630
|
-
// should issue error for cds extensions (annotate ok)
|
|
631
|
-
if (art.kind === 'namespace') {
|
|
632
|
-
// TODO: Emit error if namespace is extended by non-definitions.
|
|
633
|
-
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
|
634
|
-
'Namespaces can\'t be annotated' );
|
|
635
|
-
}
|
|
636
|
-
// Builtin annotations would be represented as annotations in to-csn.js
|
|
637
|
-
else if (art.builtin) {
|
|
638
|
-
info( 'anno-builtin', [ ext.name.location, ext ] );
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
// TODO: warning for context/service extension on non-correct
|
|
642
|
-
if (ext.kind === 'annotate')
|
|
643
|
-
delete ext.name._artifact; // make it be considered by extendArtifact()
|
|
644
|
-
}
|
|
645
|
-
// create "super" ANNOTATE containing all non-applied ones
|
|
646
|
-
const first = exts[0];
|
|
647
|
-
const { location } = first.name;
|
|
648
|
-
|
|
649
|
-
/** @type {XSN.Definition} */
|
|
650
|
-
const annotationArtifact = {
|
|
651
|
-
kind: 'annotate',
|
|
652
|
-
name: { path: [ { id: name, location } ], absolute: name, location },
|
|
653
|
-
location: first.location,
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
if (!model.extensions)
|
|
657
|
-
model.extensions = [];
|
|
658
|
-
|
|
659
|
-
model.extensions.push(annotationArtifact);
|
|
660
|
-
extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
|
|
661
|
-
// if one of the annotate statement mentions 'returns', assume it
|
|
662
|
-
// TODO: with warning/info?
|
|
663
|
-
for (const ext of exts) {
|
|
664
|
-
if (ext.$syntax === 'returns')
|
|
665
|
-
annotationArtifact.$syntax = 'returns';
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
820
|
// includes ----------------------------------------------------------------
|
|
672
821
|
|
|
673
822
|
/**
|
|
@@ -693,6 +842,7 @@ function extend( model ) {
|
|
|
693
842
|
delete ref._artifact;
|
|
694
843
|
}
|
|
695
844
|
else if (name && name in extensionsDict) {
|
|
845
|
+
// one of the includes has itself extensions that need to be applied first
|
|
696
846
|
return false;
|
|
697
847
|
}
|
|
698
848
|
else if (ref._artifact) {
|
|
@@ -775,6 +925,7 @@ function extend( model ) {
|
|
|
775
925
|
// all usages in the expressions? Possibly just the first one?
|
|
776
926
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
777
927
|
elem.$syntax = 'calc';
|
|
928
|
+
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
778
929
|
}
|
|
779
930
|
// TODO: also complain if elem is just defined in art
|
|
780
931
|
});
|
|
@@ -801,8 +952,15 @@ function extend( model ) {
|
|
|
801
952
|
forEachInOrder(parent, prop, ( member, name ) => {
|
|
802
953
|
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
803
954
|
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
804
|
-
|
|
805
|
-
|
|
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
|
+
}
|
|
806
964
|
}
|
|
807
965
|
});
|
|
808
966
|
}
|
|
@@ -933,6 +1091,7 @@ function extend( model ) {
|
|
|
933
1091
|
const art = useTextsAspect
|
|
934
1092
|
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
935
1093
|
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
1094
|
+
// both functions are rather similar...
|
|
936
1095
|
|
|
937
1096
|
const { location } = base.name;
|
|
938
1097
|
|
|
@@ -968,7 +1127,7 @@ function extend( model ) {
|
|
|
968
1127
|
elem.key = { val: true, $inferred: 'localized', location };
|
|
969
1128
|
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
970
1129
|
// they should be omitted from OData containment EDM
|
|
971
|
-
|
|
1130
|
+
setAnnotation( elem, '@odata.containment.ignore', location );
|
|
972
1131
|
}
|
|
973
1132
|
else {
|
|
974
1133
|
// add the former key paths to the unique constraint
|
|
@@ -1005,10 +1164,10 @@ function extend( model ) {
|
|
|
1005
1164
|
path: [ { id: locale.name.id, location: locale.location } ],
|
|
1006
1165
|
location: locale.location,
|
|
1007
1166
|
});
|
|
1008
|
-
|
|
1167
|
+
setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
1009
1168
|
}
|
|
1010
1169
|
|
|
1011
|
-
copyPersistenceAnnotations(art, base
|
|
1170
|
+
copyPersistenceAnnotations( art, base );
|
|
1012
1171
|
return art;
|
|
1013
1172
|
}
|
|
1014
1173
|
|
|
@@ -1040,7 +1199,7 @@ function extend( model ) {
|
|
|
1040
1199
|
if (!fioriEnabled) {
|
|
1041
1200
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1042
1201
|
// TODO (next major version): remove?
|
|
1043
|
-
|
|
1202
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
1044
1203
|
}
|
|
1045
1204
|
else {
|
|
1046
1205
|
// @fiori.draft.enabled artifacts need default elements ID_texts and locale.
|
|
@@ -1061,9 +1220,11 @@ function extend( model ) {
|
|
|
1061
1220
|
|
|
1062
1221
|
if (addTextsLanguageAssoc && art.elements.language)
|
|
1063
1222
|
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
1223
|
+
// TODO: what is this necessary? We do not create a text entity in this case
|
|
1064
1224
|
|
|
1065
1225
|
setLink( art, '_block', model.$internal );
|
|
1066
1226
|
model.definitions[absolute] = art;
|
|
1227
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1067
1228
|
return art;
|
|
1068
1229
|
}
|
|
1069
1230
|
|
|
@@ -1098,7 +1259,7 @@ function extend( model ) {
|
|
|
1098
1259
|
locale.key = { val: true, location };
|
|
1099
1260
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1100
1261
|
// TODO (next major version): remove?
|
|
1101
|
-
|
|
1262
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
1102
1263
|
}
|
|
1103
1264
|
else {
|
|
1104
1265
|
const textId = {
|
|
@@ -1110,11 +1271,11 @@ function extend( model ) {
|
|
|
1110
1271
|
};
|
|
1111
1272
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
1112
1273
|
}
|
|
1113
|
-
|
|
1114
1274
|
dictAdd( art.elements, 'locale', locale );
|
|
1275
|
+
|
|
1115
1276
|
setLink( art, '_block', model.$internal );
|
|
1116
1277
|
model.definitions[absolute] = art;
|
|
1117
|
-
|
|
1278
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1118
1279
|
return art;
|
|
1119
1280
|
}
|
|
1120
1281
|
|
|
@@ -1398,9 +1559,9 @@ function extend( model ) {
|
|
|
1398
1559
|
model.definitions[entityName] = art;
|
|
1399
1560
|
initArtifact( art );
|
|
1400
1561
|
|
|
1562
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1401
1563
|
// Copy persistence annotations from aspect.
|
|
1402
|
-
copyPersistenceAnnotations(art, target
|
|
1403
|
-
|
|
1564
|
+
copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
|
|
1404
1565
|
return art;
|
|
1405
1566
|
}
|
|
1406
1567
|
|
|
@@ -1416,138 +1577,204 @@ function extend( model ) {
|
|
|
1416
1577
|
if (origin.key)
|
|
1417
1578
|
proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1418
1579
|
if (anno)
|
|
1419
|
-
|
|
1580
|
+
setAnnotation( proxy, anno );
|
|
1420
1581
|
dictAdd( proxyDict.elements, pname, proxy );
|
|
1421
1582
|
}
|
|
1422
1583
|
}
|
|
1423
1584
|
|
|
1424
1585
|
|
|
1425
1586
|
// Phase 4 - annotations ---------------------------------------------------
|
|
1426
|
-
|
|
1427
|
-
function extensionFor( art ) {
|
|
1428
|
-
if (art.kind === 'annotate')
|
|
1429
|
-
return art;
|
|
1430
|
-
if (art._extension)
|
|
1431
|
-
return art._extension;
|
|
1432
|
-
|
|
1433
|
-
// $extension means: already applied
|
|
1434
|
-
const ext = {
|
|
1435
|
-
kind: art.kind, // set kind for setMemberParent()
|
|
1436
|
-
$extension: 'exists',
|
|
1437
|
-
location: art.location, // location( extension to existing art ) = location(art)
|
|
1438
|
-
};
|
|
1439
|
-
const { location } = art.name;
|
|
1440
|
-
if (!art._main) {
|
|
1441
|
-
ext.name = {
|
|
1442
|
-
path: [ { id: art.name.absolute, location } ],
|
|
1443
|
-
location,
|
|
1444
|
-
absolute: art.name.absolute,
|
|
1445
|
-
};
|
|
1446
|
-
if (model.extensions)
|
|
1447
|
-
model.extensions.push(ext);
|
|
1448
|
-
else
|
|
1449
|
-
model.extensions = [ ext ];
|
|
1450
|
-
}
|
|
1451
|
-
else {
|
|
1452
|
-
ext.name = { id: art.name.id, location };
|
|
1453
|
-
const parent = extensionFor( art._parent );
|
|
1454
|
-
const kind = kindProperties[art.kind].normalized || art.kind;
|
|
1455
|
-
// enums would be first in elements
|
|
1456
|
-
if ( parent[kindProperties[kind].dict]?.[art.name.id] )
|
|
1457
|
-
throw new CompilerAssertion(art.name.id);
|
|
1458
|
-
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
1459
|
-
}
|
|
1460
|
-
ext.kind = 'annotate'; // after setMemberParent()!
|
|
1461
|
-
setLink( art, '_extension', ext );
|
|
1462
|
-
setArtifactLink( ext.name, art );
|
|
1463
|
-
if (art.returns)
|
|
1464
|
-
ext.$syntax = 'returns';
|
|
1465
|
-
return ext;
|
|
1466
|
-
}
|
|
1587
|
+
// move to top
|
|
1467
1588
|
|
|
1468
1589
|
/**
|
|
1469
1590
|
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
1470
1591
|
* if multiple exist according to the module layer.
|
|
1592
|
+
* TODO: rename to extendArtifactBefore
|
|
1471
1593
|
*
|
|
1472
1594
|
* @param {XSN.Artifact} art
|
|
1473
1595
|
*/
|
|
1474
1596
|
function chooseAnnotationsInArtifact( art ) {
|
|
1475
|
-
for
|
|
1476
|
-
|
|
1477
|
-
|
|
1597
|
+
// for main artifacts, move extensions from `$lateExtensions` model dictionary:
|
|
1598
|
+
if (!art._main && !art._outer && art._extensions === undefined &&
|
|
1599
|
+
art.kind !== 'namespace') {
|
|
1600
|
+
// if (!art.name) console.log(art)
|
|
1601
|
+
const { absolute } = art.name;
|
|
1602
|
+
setLink( art, '_extensions', model.$lateExtensions[absolute]?._extensions || null );
|
|
1603
|
+
if (art._extensions && !art.builtin) { // keep extensions for builtin in $lateExtensions
|
|
1604
|
+
delete model.$lateExtensions[absolute];
|
|
1605
|
+
// TODO: if the extension mechanism has been completed, we could uncomment:
|
|
1606
|
+
// art._extensions.forEach( ext => resolvePath( ext.name, ext.kind, ext )); // for LSP
|
|
1607
|
+
// (if we do it now, the old extend functionality might think “already applied”)
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (art._extensions) {
|
|
1611
|
+
// TODO: the following function can now be simplified
|
|
1612
|
+
// if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
|
|
1613
|
+
// With extensions, member appears in CSN, affects directly the rendering of
|
|
1614
|
+
// elements etc. TODO: do that more specifically on the dicts (via symbol)
|
|
1615
|
+
// Probably better: we could use the _extensions dict prop directly in to-csn
|
|
1616
|
+
if (art.$inferred)
|
|
1617
|
+
setExpandStatusAnnotate( art, 'annotate' );
|
|
1618
|
+
if (Array.isArray( art._extensions )) {
|
|
1619
|
+
checkExtensionsKind( art._extensions, art ); // TODO: check with builtins
|
|
1620
|
+
transformArtifactExtensions( art );
|
|
1621
|
+
}
|
|
1622
|
+
applyAllExtensions( art );
|
|
1478
1623
|
}
|
|
1479
|
-
if (art.doc)
|
|
1480
|
-
chooseAssignment( 'doc', art );
|
|
1481
1624
|
}
|
|
1482
1625
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
if (!annotationHasEllipsis( a )) {
|
|
1504
|
-
cont = false;
|
|
1505
|
-
break;
|
|
1626
|
+
// TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
|
|
1627
|
+
function transformArtifactExtensions( art ) {
|
|
1628
|
+
const hasOnlySubExtensions = art._outer; // items, anonymous aspects
|
|
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 );
|
|
1506
1646
|
}
|
|
1507
1647
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1648
|
+
}
|
|
1649
|
+
art._extensions = dict;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function applyAllExtensions( art ) {
|
|
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] );
|
|
1662
|
+
|
|
1663
|
+
let cont = true;
|
|
1664
|
+
while (cont) {
|
|
1665
|
+
const { highest, issue } = extensionsOfHighestLayers( layered );
|
|
1666
|
+
// console.log( 'CA:', annoName, issue, extensions)
|
|
1667
|
+
let index = highest.length;
|
|
1668
|
+
cont = !!index; // safety
|
|
1669
|
+
while (--index >= 0) {
|
|
1670
|
+
const ext = highest[index];
|
|
1671
|
+
scheduled.push( ext );
|
|
1672
|
+
if (extensionOverwrites( ext, prop )) {
|
|
1673
|
+
cont = false;
|
|
1674
|
+
break;
|
|
1520
1675
|
}
|
|
1521
1676
|
}
|
|
1677
|
+
if (issue || index > 0)
|
|
1678
|
+
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
1679
|
+
}
|
|
1680
|
+
// Now apply the relevant extensions
|
|
1681
|
+
scheduled.reverse();
|
|
1682
|
+
for (const ext of scheduled)
|
|
1683
|
+
applySingleExtension( art, ext, prop );
|
|
1684
|
+
delete extensions[prop];
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function extensionOverwrites( ext, prop ) {
|
|
1689
|
+
return (prop.charAt(0) !== '@')
|
|
1690
|
+
? [ 'doc', 'length', 'precision', 'scale', 'srid' ].includes( prop )
|
|
1691
|
+
: !annotationHasEllipsis( ext[prop] );
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// TODO: still a bit annotation assignment specific
|
|
1695
|
+
function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
|
|
1696
|
+
// TODO: think about messages for these
|
|
1697
|
+
if (prop === 'elements' || prop === 'enum' || prop === 'actions' || prop === 'columns' ||
|
|
1698
|
+
prop === 'params' || prop === 'returns' || prop === 'includes' )
|
|
1699
|
+
return; // extensions currently handled extra
|
|
1700
|
+
if (issue) {
|
|
1701
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1702
|
+
let msg = (index < 0)
|
|
1703
|
+
? 'anno-unstable-array'
|
|
1704
|
+
: (issue === true)
|
|
1705
|
+
? 'anno-duplicate'
|
|
1706
|
+
: 'anno-duplicate-unrelated-layer';
|
|
1707
|
+
if (prop.charAt(0) !== '@' && prop !== 'doc') {
|
|
1708
|
+
msg = (issue === true)
|
|
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)
|
|
1522
1713
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
{ '#': variant, anno:
|
|
1714
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
1715
|
+
for (const ext of extensions) {
|
|
1716
|
+
const anno = ext[prop];
|
|
1717
|
+
if (anno && !anno.$errorReported) {
|
|
1718
|
+
message( msg, [ anno.name?.location || anno.location, ext ],
|
|
1719
|
+
{ '#': variant, anno: prop, type: art } );
|
|
1529
1720
|
}
|
|
1530
1721
|
}
|
|
1531
1722
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1723
|
+
else if (index > 0) { // more than one set (not just ...)
|
|
1724
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
1725
|
+
const msgid = (prop.charAt(0) === '@' || prop === 'doc')
|
|
1726
|
+
? 'anno-duplicate-same-file' // TODO: always ext-duplicate-…
|
|
1727
|
+
: 'ext-duplicate-same-file';
|
|
1728
|
+
while (index >= 0) { // do not report for trailing [...]
|
|
1729
|
+
const ext = extensions[index--];
|
|
1730
|
+
const anno = ext[prop];
|
|
1731
|
+
warning( msgid, [ anno.name?.location || anno.location, ext ],
|
|
1732
|
+
{ '#': variant, prop, anno: prop } );
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function applySingleExtension( art, ext, prop ) {
|
|
1738
|
+
if (prop === 'includes') {
|
|
1739
|
+
if (ext.kind === 'extend' && art.$inferred) {
|
|
1740
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
1741
|
+
'You can\'t use EXTEND on the generated $(ART)' );
|
|
1742
|
+
}
|
|
1743
|
+
else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
|
|
1744
|
+
const { absolute } = art.name;
|
|
1745
|
+
const dict = extensionsDict[absolute] || (extensionsDict[absolute] = []);
|
|
1746
|
+
dict.push( ext ); // TODO: change
|
|
1747
|
+
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[absolute])
|
|
1748
|
+
}
|
|
1749
|
+
// art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
|
|
1750
|
+
}
|
|
1751
|
+
else if (prop === 'columns') {
|
|
1752
|
+
const { query } = art;
|
|
1753
|
+
if (!query?.from?.path)
|
|
1754
|
+
error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
|
|
1755
|
+
else if (!query.columns)
|
|
1756
|
+
query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
|
|
1757
|
+
else
|
|
1758
|
+
query.columns.push( ...ext.columns );
|
|
1759
|
+
}
|
|
1760
|
+
else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
|
|
1761
|
+
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
1762
|
+
typeExts[prop] = ext;
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
const result = applyAssignment( art[prop], ext[prop], ext, prop );
|
|
1766
|
+
art[prop] = (result.name) ? result : Object.assign( {}, art[prop], result );
|
|
1767
|
+
}
|
|
1539
1768
|
}
|
|
1540
1769
|
|
|
1541
1770
|
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
1771
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
1772
|
+
if (!firstEllipsis)
|
|
1773
|
+
return anno;
|
|
1542
1774
|
const hasBase = previousAnno?.literal === 'array';
|
|
1543
1775
|
if (!previousAnno) {
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
return anno;
|
|
1547
|
-
if (anno.$priority) { // already complained about with Define
|
|
1548
|
-
const loc = firstEllipsis.location || anno.name.location;
|
|
1549
|
-
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
1550
|
-
}
|
|
1776
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
1777
|
+
message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
|
|
1551
1778
|
previousAnno = { val: [] };
|
|
1552
1779
|
}
|
|
1553
1780
|
else if (previousAnno.literal !== 'array') {
|
|
@@ -1641,69 +1868,70 @@ function extend( model ) {
|
|
|
1641
1868
|
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1642
1869
|
}
|
|
1643
1870
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1871
|
+
/**
|
|
1872
|
+
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1873
|
+
* source to target if present on source but not target.
|
|
1874
|
+
*
|
|
1875
|
+
* @param {object} target
|
|
1876
|
+
* @param {object} source
|
|
1877
|
+
*/
|
|
1878
|
+
function copyPersistenceAnnotations( target, source ) {
|
|
1879
|
+
if (!source)
|
|
1880
|
+
return;
|
|
1881
|
+
|
|
1882
|
+
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1883
|
+
if (copyExists)
|
|
1884
|
+
copy( '@cds.persistence.exists' );
|
|
1885
|
+
copy( '@cds.persistence.skip' );
|
|
1886
|
+
|
|
1887
|
+
function copy( anno ) {
|
|
1888
|
+
if ( source[anno] && !target[anno] )
|
|
1889
|
+
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1657
1890
|
}
|
|
1658
1891
|
}
|
|
1659
1892
|
}
|
|
1660
1893
|
|
|
1661
1894
|
/**
|
|
1662
|
-
* Group
|
|
1895
|
+
* Group extensions by their layers. A definition (for specified elements)
|
|
1663
1896
|
* is considered to be provided in a layer named '', the lowest layer.
|
|
1664
1897
|
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
* @param {object[]} assignments Array of assignments, e.g. extensions.
|
|
1669
|
-
* @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
|
|
1898
|
+
* @param {object[]} extensions Array of extensions.
|
|
1899
|
+
* @returns {Record<string, object>} key: layer name, value: {name, layer, extensions[]}`
|
|
1670
1900
|
*/
|
|
1671
|
-
function
|
|
1901
|
+
function layeredExtensions( extensions ) {
|
|
1672
1902
|
const layered = Object.create(null);
|
|
1673
|
-
for (const
|
|
1674
|
-
const layer =
|
|
1903
|
+
for (const ext of extensions) {
|
|
1904
|
+
const layer = (ext.kind === 'annotate' || ext.kind === 'extend') && layers.layer( ext );
|
|
1675
1905
|
// just consider layer if Extend/Annotate, not Define
|
|
1676
1906
|
const name = (layer) ? layer.realname : '';
|
|
1677
1907
|
const done = layered[name];
|
|
1678
1908
|
if (done)
|
|
1679
|
-
done.
|
|
1909
|
+
done.extensions.push( ext );
|
|
1680
1910
|
else
|
|
1681
|
-
layered[name] = { name, layer,
|
|
1682
|
-
// TODO: file - if set: unique in layer
|
|
1911
|
+
layered[name] = { name, layer, extensions: [ ext ] };
|
|
1683
1912
|
}
|
|
1684
1913
|
return layered;
|
|
1685
1914
|
}
|
|
1686
1915
|
|
|
1687
1916
|
/**
|
|
1688
|
-
* Return
|
|
1917
|
+
* Return extensions of the highest layers.
|
|
1689
1918
|
* Also returns whether there could be an issue:
|
|
1690
|
-
* - false: there are just
|
|
1691
|
-
* - 'unrelated': there is just one
|
|
1692
|
-
* - true: there is at least one layer with two or more
|
|
1919
|
+
* - false: there are just extensions in one file,
|
|
1920
|
+
* - 'unrelated': there is just one extension per layer
|
|
1921
|
+
* - true: there is at least one layer with two or more extensions, and
|
|
1693
1922
|
* at least two files are involved
|
|
1694
|
-
* TODO: make this usable for extend (elements), too.
|
|
1695
1923
|
*
|
|
1696
|
-
* @param {Record<string, object>}
|
|
1924
|
+
* @param {Record<string, object>} layered Structure as returned by layeredExtensions()
|
|
1697
1925
|
* @returns {{assignments, issue: boolean|string}}
|
|
1698
1926
|
*/
|
|
1699
|
-
function
|
|
1700
|
-
const layerNames = Object.keys(
|
|
1927
|
+
function extensionsOfHighestLayers( layered ) {
|
|
1928
|
+
const layerNames = Object.keys( layered );
|
|
1701
1929
|
// console.log('HIB:',layerNames)
|
|
1702
1930
|
if (layerNames.length <= 1) {
|
|
1703
1931
|
const name = layerNames[0];
|
|
1704
|
-
const
|
|
1705
|
-
delete
|
|
1706
|
-
return {
|
|
1932
|
+
const highest = layered[name]?.extensions || [];
|
|
1933
|
+
delete layered[name];
|
|
1934
|
+
return { highest, issue: inMoreThanOneFile( highest ) };
|
|
1707
1935
|
}
|
|
1708
1936
|
|
|
1709
1937
|
// collect all layers which are lower than another layer
|
|
@@ -1711,42 +1939,41 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
|
1711
1939
|
allExtends[''] = {}; // the "Define" layer
|
|
1712
1940
|
for (const name of layerNames) {
|
|
1713
1941
|
if (name) // not the "Define" layer
|
|
1714
|
-
Object.assign( allExtends,
|
|
1942
|
+
Object.assign( allExtends, layered[name].layer._layerExtends );
|
|
1715
1943
|
}
|
|
1716
1944
|
// console.log('HIE:',Object.keys(allExtends))
|
|
1717
|
-
const
|
|
1718
|
-
const
|
|
1945
|
+
const highest = []; // extensions
|
|
1946
|
+
const highestLayers = [];
|
|
1719
1947
|
for (const name of layerNames) {
|
|
1720
1948
|
if (!(name in allExtends)) {
|
|
1721
|
-
const layer =
|
|
1722
|
-
delete
|
|
1723
|
-
|
|
1724
|
-
|
|
1949
|
+
const layer = layered[name];
|
|
1950
|
+
delete layered[name];
|
|
1951
|
+
highestLayers.push( layer );
|
|
1952
|
+
highest.push( ...layer.extensions );
|
|
1725
1953
|
}
|
|
1726
1954
|
}
|
|
1727
|
-
|
|
1728
|
-
const good =
|
|
1955
|
+
highest.sort( compareExtensions );
|
|
1956
|
+
const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ));
|
|
1729
1957
|
// TODO: use layer.file instead
|
|
1730
|
-
const issue = !good ||
|
|
1731
|
-
// console.log('HI:',highest.map(l=>l.name),issue,issue&&
|
|
1732
|
-
return {
|
|
1958
|
+
const issue = !good || highestLayers.length > 1 && 'unrelated';
|
|
1959
|
+
// console.log('HI:',highest.map(l=>l.name),issue,issue&&extensions)
|
|
1960
|
+
return { highest, issue };
|
|
1733
1961
|
}
|
|
1734
1962
|
|
|
1735
|
-
function inMoreThanOneFile(
|
|
1736
|
-
if (
|
|
1963
|
+
function inMoreThanOneFile( extensions ) {
|
|
1964
|
+
if (extensions.length <= 1)
|
|
1737
1965
|
return false;
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return !file || assignments.slice(1).some( a => (a.name || a).location?.file !== file );
|
|
1966
|
+
const file = extensions[0].location?.file;
|
|
1967
|
+
return !file || extensions.slice(1).some( e => e.location?.file !== file );
|
|
1741
1968
|
}
|
|
1742
1969
|
|
|
1743
1970
|
/**
|
|
1744
|
-
* Compare two
|
|
1745
|
-
* - via the fs.realpath of the file (not layer!) of the
|
|
1746
|
-
* - via the line, then column of the
|
|
1971
|
+
* Compare two extensions which are not comparable via layering:
|
|
1972
|
+
* - via the fs.realpath of the file (not layer!) of the extensions, then
|
|
1973
|
+
* - via the line, then column of the extensions.
|
|
1747
1974
|
* Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
|
|
1748
1975
|
*/
|
|
1749
|
-
function
|
|
1976
|
+
function compareExtensions( a, b ) {
|
|
1750
1977
|
const fileA = layers.realname( a._block );
|
|
1751
1978
|
const fileB = layers.realname( b._block );
|
|
1752
1979
|
if (fileA !== fileB)
|
|
@@ -1755,29 +1982,6 @@ function compareAssignments( a, b ) {
|
|
|
1755
1982
|
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
1756
1983
|
}
|
|
1757
1984
|
|
|
1758
|
-
/**
|
|
1759
|
-
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1760
|
-
* source to target if present on source but not target.
|
|
1761
|
-
*
|
|
1762
|
-
* @param {object} target
|
|
1763
|
-
* @param {object} source
|
|
1764
|
-
* @param {CSN.Options} options
|
|
1765
|
-
*/
|
|
1766
|
-
function copyPersistenceAnnotations( target, source, options ) {
|
|
1767
|
-
if (!source)
|
|
1768
|
-
return;
|
|
1769
|
-
|
|
1770
|
-
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1771
|
-
if (copyExists)
|
|
1772
|
-
copy( '@cds.persistence.exists' );
|
|
1773
|
-
copy( '@cds.persistence.skip' );
|
|
1774
|
-
|
|
1775
|
-
function copy( anno ) {
|
|
1776
|
-
if ( source[anno] && !target[anno] )
|
|
1777
|
-
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
1985
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
1782
1986
|
const args = relations.map( eq );
|
|
1783
1987
|
return (args.length === 1)
|
|
@@ -1811,15 +2015,14 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
1811
2015
|
* @param {XSN.Extension} ext
|
|
1812
2016
|
* @param {object} art
|
|
1813
2017
|
*/
|
|
1814
|
-
function storeTypeExtension( ext, art ) {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
|
|
2018
|
+
// function storeTypeExtension( ext, art ) {
|
|
2019
|
+
// // If there are no parameters to apply, don't store the extension.
|
|
2020
|
+
// if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
|
|
2021
|
+
// return;
|
|
2022
|
+
// else if (!art._extendType)
|
|
2023
|
+
// setLink( art, '_extendType', [] );
|
|
2024
|
+
// art._extendType.push( ext );
|
|
2025
|
+
// }
|
|
1823
2026
|
|
|
1824
2027
|
function checkTextsLanguageAssocOption( model, options ) {
|
|
1825
2028
|
const languages = model.definitions['sap.common.Languages'];
|