@sap/cds-compiler 3.7.2 → 3.8.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 +71 -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 +25 -6
- package/lib/compiler/base.js +51 -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 +717 -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 +20 -58
- 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 +4844 -4508
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +56 -7
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +49 -9
- package/lib/language/language.g4 +106 -83
- 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 +19 -4
- 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,458 @@ 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
|
+
// set _artifact links for “main extensions” late as it would disturb the
|
|
192
|
+
// still existing old extend mechanism, see chooseAnnotationsInArtifact(),
|
|
193
|
+
// needed for LSP and friends:
|
|
194
|
+
Object.values( model.sources ).forEach( setArtifactLinkForExtensions );
|
|
195
|
+
Object.values( model.definitions ).forEach( setArtifactLinkForExtensions );
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// TODO: delete again
|
|
199
|
+
function setArtifactLinkForExtensions( source ) {
|
|
200
|
+
if (!source.extensions)
|
|
201
|
+
return;
|
|
202
|
+
for (const ext of source.extensions ) {
|
|
203
|
+
const { name } = ext;
|
|
204
|
+
if (name?.absolute && name._artifact === undefined) // no link set yet
|
|
205
|
+
resolvePath( name, ext.kind, ext ); // for LSP
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// For extendArtifactAfter(): -------------------------------------------------
|
|
210
|
+
|
|
211
|
+
// Remarks on messages: we allow the type extensions only if the artifact
|
|
212
|
+
// originally had that property → any check of the kind “type prop can only be
|
|
213
|
+
// used with FooBar” is independent from `extend … with type`. Function
|
|
214
|
+
// checkTypeArguments() in resolve.js reports 'type-unexpected-argument', but
|
|
215
|
+
// that is currently incomplete.
|
|
216
|
+
//
|
|
217
|
+
// We then report (in the future), use the first message of:
|
|
218
|
+
// - the usual messages if a type argument is wrong, independently from `extend`
|
|
219
|
+
// - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
|
|
220
|
+
// - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
|
|
221
|
+
//
|
|
222
|
+
// TODO v4: do not allow `extend … with (precision: …)` alone if original def also has `scale`
|
|
223
|
+
function applyTypeExtensions( art, ext, prop, scaleDiff ) {
|
|
224
|
+
// console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
|
|
225
|
+
if (!ext?.[prop])
|
|
226
|
+
return 0;
|
|
227
|
+
if (!art[prop]) {
|
|
228
|
+
const isBuiltin = art._effectiveType?.builtin;
|
|
229
|
+
if (isBuiltin && !allowsTypeArgument( art, prop )) {
|
|
230
|
+
// Let checkTypeArguments() in resolve.js report a message, is incomplete
|
|
231
|
+
// though, i.e. can only safely be used for scalars at the moment. But we
|
|
232
|
+
// will improve that function and not try to do extra things here.
|
|
233
|
+
art[prop] = ext[prop]; // enable checkTypeArguments() doing its job
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
// TODO: think about 'ext-unexpected-type-argument'
|
|
237
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
238
|
+
{ '#': (isBuiltin ? 'indirect' : 'new-prop'), prop } );
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
const artVal = art[prop].val;
|
|
242
|
+
const extVal = ext[prop].val;
|
|
243
|
+
if (prop === 'srid') {
|
|
244
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'prop', prop } );
|
|
245
|
+
}
|
|
246
|
+
else if (typeof artVal !== 'number' || typeof extVal !== 'number' ) {
|
|
247
|
+
// Users can't change from/to string value for property,
|
|
248
|
+
// e.g. `variable`/`floating` for Decimal
|
|
249
|
+
// TODO: Shouldn't the text distinguish between orig string and extension string?
|
|
250
|
+
// Not sure whether to talk about strings if we have a keyword in CDL
|
|
251
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], { '#': 'string', prop } );
|
|
252
|
+
}
|
|
253
|
+
else if (extVal < artVal + (scaleDiff || 0)) {
|
|
254
|
+
const number = artVal + (scaleDiff || 0);
|
|
255
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
256
|
+
// eslint-disable-next-line object-curly-newline
|
|
257
|
+
{ '#': (scaleDiff ? 'scale' : 'number'), prop, number, otherprop: 'scale' } );
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
art[prop] = ext[prop];
|
|
261
|
+
return extVal - artVal;
|
|
262
|
+
}
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function allowsTypeArgument( art, prop ) {
|
|
267
|
+
const { parameters } = art._effectiveType;
|
|
268
|
+
if (!parameters)
|
|
269
|
+
return false;
|
|
270
|
+
return parameters.includes( prop ) || parameters[0]?.name === prop;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
|
|
274
|
+
// TODO: setExpandStatusAnnotate
|
|
275
|
+
const extensions = extensionsMap[extProp];
|
|
276
|
+
if (!extensions)
|
|
277
|
+
return;
|
|
278
|
+
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
279
|
+
|
|
280
|
+
for (const ext of extensions) {
|
|
281
|
+
const extDict = ext[extProp];
|
|
282
|
+
for (const name in extDict) {
|
|
283
|
+
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
284
|
+
const elemExt = extDict[name];
|
|
285
|
+
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
286
|
+
continue; // definitions inside extend, already handled
|
|
287
|
+
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
288
|
+
const elem = artDict[name] || annotateFor( art, extProp, name );
|
|
289
|
+
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
|
|
290
|
+
pushToDict( elem, '_extensions', elemExt );
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// function moveReturnsExtensions( art, extensionsMap ) {
|
|
296
|
+
// const artReturns = art.returns;
|
|
297
|
+
// const extensions = extensionsMap.returns;
|
|
298
|
+
// // TODO: artItem is null
|
|
299
|
+
// for (const ext of extensions)
|
|
300
|
+
// pushToDict( artReturns, '_extensions', ext.returns );
|
|
301
|
+
// }
|
|
302
|
+
|
|
303
|
+
function annotateFor( art, prop, name ) {
|
|
304
|
+
const base = annotateBase( art );
|
|
305
|
+
if (name === '' && prop === 'params')
|
|
306
|
+
return base.returns || annotateCreate( base, name, base, 'returns' );
|
|
307
|
+
const dict = base[prop] || (base[prop] = Object.create( null ));
|
|
308
|
+
if (name == null)
|
|
309
|
+
return dict;
|
|
310
|
+
return dict[name] || annotateCreate( dict, name, base );
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function annotateBase( art ) {
|
|
314
|
+
while (art._outer) // TOOD: think about anonymous target aspect
|
|
315
|
+
art = art._outer;
|
|
316
|
+
// if (art._annotateS)
|
|
317
|
+
// return art._annotateS;
|
|
318
|
+
if (art.kind === 'annotate')
|
|
319
|
+
return art;
|
|
320
|
+
|
|
321
|
+
// TODO: more to do if annotate can have `returns` property
|
|
322
|
+
if (art.kind === 'select')
|
|
323
|
+
art = art._parent;
|
|
324
|
+
if (art._main)
|
|
325
|
+
return annotateFor( art._parent, kindProperties[art.kind].dict, art.name.id );
|
|
326
|
+
|
|
327
|
+
const { absolute } = art.name;
|
|
328
|
+
return model.$lateExtensions[absolute] || annotateCreate( model.$lateExtensions, absolute );
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function annotateCreate( dict, id, parent, prop ) {
|
|
332
|
+
const annotate = {
|
|
333
|
+
kind: 'annotate',
|
|
334
|
+
name: { id, location: genLocation },
|
|
335
|
+
$inferred: '',
|
|
336
|
+
location: genLocation,
|
|
337
|
+
};
|
|
338
|
+
if (parent) {
|
|
339
|
+
setLink( annotate, '_parent', parent );
|
|
340
|
+
setLink( annotate, '_main', parent._main || parent );
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
annotate.name.absolute = id; // TODO later (if all names are sparse): delete absolute
|
|
344
|
+
}
|
|
345
|
+
dict[prop || id] = annotate;
|
|
346
|
+
return annotate;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function extendHandleReturns( extensions, art ) {
|
|
350
|
+
for (const ext of extensions || []) {
|
|
351
|
+
if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
|
|
352
|
+
ext.$syntax = '$inside-returns';
|
|
353
|
+
delete ext.params;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
warning( 'ext-expected-returns', [ ext.name.location, ext ], {
|
|
357
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
358
|
+
}, {
|
|
359
|
+
std: 'Expected $(CODE)', // unused variant
|
|
360
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
361
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
362
|
+
} );
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// const unexpected_props = {
|
|
368
|
+
// elements: 'anno-unexpected-elements',
|
|
369
|
+
// enum: 'anno-unexpected-elements', // TODO
|
|
370
|
+
// params: 'anno-unexpected-params',
|
|
371
|
+
// actions: 'anno-unexpected-actions',
|
|
372
|
+
// };
|
|
373
|
+
// const undefined_props = {
|
|
374
|
+
// elements: 'anno-undefined-element',
|
|
375
|
+
// enum: 'anno-undefined-element', // TODO
|
|
376
|
+
// params: 'anno-undefined-param',
|
|
377
|
+
// actions: 'anno-undefined-action',
|
|
378
|
+
// };
|
|
379
|
+
|
|
380
|
+
function checkRemainingMemberExtensions( parent, ext, prop, name ) {
|
|
381
|
+
// console.log('CRME:',prop,name,parent,ext)
|
|
382
|
+
const dict = parent[prop];
|
|
383
|
+
if (!dict) {
|
|
384
|
+
// TODO: check - for each name? - better locations
|
|
385
|
+
const location = ext._parent[prop][$location] || ext.name.location;
|
|
386
|
+
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
387
|
+
switch (prop) {
|
|
388
|
+
// TODO: change texts, somehow similar to checkDefinitions() ?
|
|
389
|
+
case 'elements':
|
|
390
|
+
case 'enum': // TODO: extra?
|
|
391
|
+
warning( 'anno-unexpected-elements', [ location, ext._parent ],
|
|
392
|
+
{ '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
|
|
393
|
+
std: 'Elements only exist in entities, types or typed constructs',
|
|
394
|
+
entity: 'Elements of entity types can\'t be annotated',
|
|
395
|
+
});
|
|
396
|
+
break;
|
|
397
|
+
case 'params':
|
|
398
|
+
warning( 'anno-unexpected-params', [ location, ext._parent ], {},
|
|
399
|
+
'Parameters only exist for actions or functions' );
|
|
400
|
+
break;
|
|
401
|
+
case 'actions':
|
|
402
|
+
warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
|
|
403
|
+
'Actions and functions only exist top-level and for entities' );
|
|
404
|
+
break;
|
|
405
|
+
default:
|
|
406
|
+
// assert
|
|
407
|
+
}
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
else if (!dict[name]) {
|
|
411
|
+
// TODO: make variant `returns` an auto-variant for ($ART) ?
|
|
412
|
+
const inReturns = parent._parent?.returns && parent._parent;
|
|
413
|
+
const art = inReturns || parent;
|
|
414
|
+
switch (prop) {
|
|
415
|
+
case 'elements':
|
|
416
|
+
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
417
|
+
{ '#': (inReturns ? 'returns' : 'element'), art, name },
|
|
418
|
+
parent.elements );
|
|
419
|
+
break;
|
|
420
|
+
case 'enum': // TODO: extra msg id?
|
|
421
|
+
notFound( 'anno-undefined-element', ext.name.location, ext,
|
|
422
|
+
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
423
|
+
parent.enum );
|
|
424
|
+
break;
|
|
425
|
+
case 'params':
|
|
426
|
+
notFound( 'anno-undefined-param', ext.name.location, ext,
|
|
427
|
+
{ '#': 'param', art: parent, name },
|
|
428
|
+
parent.params );
|
|
429
|
+
break;
|
|
430
|
+
case 'actions':
|
|
431
|
+
notFound( 'anno-undefined-action', ext.name.location, ext,
|
|
432
|
+
{ '#': 'action', art: parent, name },
|
|
433
|
+
parent.actions );
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
// assert
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function notFound( msgId, location, address, args, validDict ) {
|
|
443
|
+
const msg = message( msgId, [ location, address ], args );
|
|
444
|
+
attachAndEmitValidNames( msg, validDict );
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// For createRemainingAnnotateStatements(): -----------------------------------
|
|
448
|
+
|
|
449
|
+
function createSuperAnnotate( annotate ) {
|
|
450
|
+
const extensions = annotate._extensions;
|
|
451
|
+
if (extensions && !annotate._main) {
|
|
452
|
+
const { absolute } = annotate.name;
|
|
453
|
+
const isLocalized = absolute.startsWith( 'localized.' ); // TODO: && anno
|
|
454
|
+
const art = model.definitions[absolute];
|
|
455
|
+
for (const ext of extensions)
|
|
456
|
+
checkRemainingMainExtensions( art, ext, isLocalized );
|
|
457
|
+
if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
|
|
458
|
+
setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
|
|
459
|
+
// direct annotations on builtins or on the builtins for propagation, and
|
|
460
|
+
// also shallow-copied to $collectedExtensions for to-csn
|
|
461
|
+
for (const prop in art) {
|
|
462
|
+
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
463
|
+
annotate[prop] = art[prop];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
|
|
467
|
+
annotate.location = extensions[0].location;
|
|
468
|
+
annotate.name.location = extensions[0].name.location;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
chooseAnnotationsInArtifact( annotate );
|
|
472
|
+
extendArtifactAfter( annotate );
|
|
473
|
+
forEachMember( annotate, createSuperAnnotate );
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
477
|
+
if (localized) // TODO v4: ignore only for annotate
|
|
478
|
+
return;
|
|
479
|
+
if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
|
|
480
|
+
return;
|
|
481
|
+
// else if (ext.kind === 'extend') { // TODO v4 - add error
|
|
482
|
+
// }
|
|
483
|
+
if (art?.kind === 'namespace') {
|
|
484
|
+
// TODO: not at all different to having no definition
|
|
485
|
+
info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
486
|
+
'Namespaces can\'t be annotated' );
|
|
487
|
+
}
|
|
488
|
+
else if (art?.builtin) {
|
|
489
|
+
info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
|
|
490
|
+
'Builtin types should not be annotated. Use custom type instead' );
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Issue messages for annotations on namespaces and builtins
|
|
495
|
+
// (TODO: really here?, probably split main artifacts vs returns)
|
|
496
|
+
// see also lateExtensions() where similar messages are reported
|
|
497
|
+
function checkAnnotate( construct, art ) {
|
|
498
|
+
// TODO: Handle extend statements properly: Different message for empty extend?
|
|
499
|
+
|
|
500
|
+
// --> without art._block, art not found
|
|
501
|
+
if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
|
|
502
|
+
if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
|
|
503
|
+
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
|
|
504
|
+
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
505
|
+
// `art._block` ensures that `art` is a defined def.
|
|
506
|
+
return;
|
|
507
|
+
// warning('ext-unexpected-returns', [ construct.name.location, construct ],
|
|
508
|
+
// { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
|
|
509
|
+
}
|
|
510
|
+
else if (construct.$syntax !== 'returns' &&
|
|
511
|
+
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
|
|
512
|
+
warning('ext-expected-returns', [ construct.name.location, construct ], {
|
|
513
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
514
|
+
}, {
|
|
515
|
+
std: 'Expected $(CODE)', // unused variant
|
|
516
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
517
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
124
523
|
// extend ------------------------------------------------------------------
|
|
125
524
|
|
|
126
525
|
/**
|
|
127
526
|
* Apply the extensions inside the extensionsDict on the model.
|
|
128
527
|
*
|
|
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.
|
|
528
|
+
* First try normally: extends with structure includes; with remaining cyclic
|
|
529
|
+
* includes, do so without includes.
|
|
135
530
|
*/
|
|
136
531
|
function applyExtensions() {
|
|
137
|
-
let
|
|
532
|
+
let noIncludes = false;
|
|
138
533
|
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
|
|
534
|
+
|
|
145
535
|
while (extNames.length) {
|
|
146
536
|
const { length } = extNames;
|
|
147
537
|
for (const name of extNames) {
|
|
148
538
|
const art = model.definitions[name];
|
|
149
|
-
if (
|
|
150
|
-
|
|
539
|
+
if (art && art.kind !== 'namespace' &&
|
|
540
|
+
extendArtifact( extensionsDict[name], art, noIncludes ))
|
|
151
541
|
delete extensionsDict[name];
|
|
152
|
-
}
|
|
153
|
-
else if (art.$duplicates) { // cannot extend redefinitions
|
|
154
|
-
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
542
|
}
|
|
162
543
|
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
else if (extNames.length >= length)
|
|
166
|
-
phase = Object.keys( extensionsDict ); // = no includes
|
|
544
|
+
if (extNames.length >= length)
|
|
545
|
+
noIncludes = Object.keys( extensionsDict ); // = no includes
|
|
167
546
|
}
|
|
168
547
|
}
|
|
169
548
|
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
549
|
+
function checkExtensionsKind( extensions, art ) {
|
|
550
|
+
for (const ext of extensions) {
|
|
551
|
+
const kind = ext.expectedKind?.val;
|
|
552
|
+
if (kind && kind !== art.kind) {
|
|
553
|
+
const loc = ext.expectedKind.location;
|
|
554
|
+
if (kind === 'context' || kind === 'service') {
|
|
555
|
+
// We have no real artifact during the construction of a super-annotate statement:
|
|
556
|
+
const msgArgs = {
|
|
557
|
+
'#': (art.kind === 'service' || art.kind === 'annotate') ? art.kind : 'std',
|
|
558
|
+
art,
|
|
559
|
+
kind,
|
|
560
|
+
code: 'extend … with definitions',
|
|
561
|
+
keyword: 'extend service',
|
|
562
|
+
};
|
|
563
|
+
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
564
|
+
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
565
|
+
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
566
|
+
// do not mention 'extend context', that is not in CAPire
|
|
567
|
+
service: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) or $(KEYWORD) instead',
|
|
183
568
|
});
|
|
184
569
|
}
|
|
570
|
+
// TODO: Use similar checks for EXTEND ENTITY etc - 'ext-ignoring-kind'
|
|
185
571
|
}
|
|
186
|
-
return false;
|
|
187
572
|
}
|
|
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
573
|
}
|
|
203
574
|
|
|
204
575
|
/**
|
|
@@ -231,6 +602,7 @@ function extend( model ) {
|
|
|
231
602
|
}
|
|
232
603
|
if (!noIncludes && art.includes)
|
|
233
604
|
applyIncludes( art, art );
|
|
605
|
+
// checkExtensionsKind( extensions, art );
|
|
234
606
|
extendMembers( extensions, art, noIncludes === 'gen' );
|
|
235
607
|
if (!noIncludes && art.includes) {
|
|
236
608
|
// early propagation of specific annotation assignments
|
|
@@ -244,12 +616,14 @@ function extend( model ) {
|
|
|
244
616
|
function extendMembers( extensions, art, noExtend ) {
|
|
245
617
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
246
618
|
const elemExtensions = [];
|
|
247
|
-
|
|
619
|
+
if (art._main) // extensions already sorted for main artifacts
|
|
620
|
+
extensions.sort( layers.compareLayer );
|
|
248
621
|
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
249
|
-
|
|
622
|
+
// console.log('EM:',art.name,extensions,art._extensions)
|
|
623
|
+
for (const ext of extensions) { // those in extMap.includes
|
|
250
624
|
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
251
625
|
// 'Info', 'EXT').toString())
|
|
252
|
-
if (
|
|
626
|
+
if (ext.name._artifact === undefined) { // not already applied
|
|
253
627
|
setArtifactLink( ext.name, art );
|
|
254
628
|
if (noExtend && ext.kind === 'extend') {
|
|
255
629
|
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
@@ -267,12 +641,10 @@ function extend( model ) {
|
|
|
267
641
|
art.includes = [ ...ext.includes ];
|
|
268
642
|
applyIncludes( ext, art );
|
|
269
643
|
}
|
|
644
|
+
// console.log(ext,art)
|
|
270
645
|
checkAnnotate( ext, art );
|
|
271
|
-
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
272
|
-
copyAnnotationsForExtensions( ext, art );
|
|
273
646
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
274
647
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
275
|
-
storeTypeExtension( ext, art );
|
|
276
648
|
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
277
649
|
}
|
|
278
650
|
for (const name in ext.elements) {
|
|
@@ -282,17 +654,24 @@ function extend( model ) {
|
|
|
282
654
|
break; // more than one elem in same EXTEND is fine
|
|
283
655
|
}
|
|
284
656
|
}
|
|
285
|
-
|
|
286
|
-
if (ext.columns) // extend projection
|
|
287
|
-
extendColumns( ext, art );
|
|
288
657
|
}
|
|
289
658
|
if (elemExtensions.length > 1)
|
|
290
659
|
reportUnstableExtensions( elemExtensions );
|
|
291
|
-
if (art._extendType && art._extendType.length > 0)
|
|
292
|
-
reportTypeExtensionsInSameLayer( art._extendType );
|
|
293
660
|
|
|
661
|
+
// This whole function will be removed with a next change - no need to have nice code here:
|
|
662
|
+
const extsTmp = { elements: Object.create(null), actions: Object.create(null) };
|
|
663
|
+
for (const e of extensions) {
|
|
664
|
+
for (const n in e.elements || []) {
|
|
665
|
+
if (e.elements[n].kind === 'extend')
|
|
666
|
+
pushToDict( extsTmp.elements, n, e.elements[n] );
|
|
667
|
+
}
|
|
668
|
+
for (const n in extensions.actions || []) {
|
|
669
|
+
if (e.actions[n].kind === 'extend')
|
|
670
|
+
pushToDict( extsTmp.actions, n, e.actions[n] );
|
|
671
|
+
}
|
|
672
|
+
}
|
|
294
673
|
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
295
|
-
const dict =
|
|
674
|
+
const dict = extsTmp[prop];
|
|
296
675
|
for (const name in dict) {
|
|
297
676
|
let obj = art;
|
|
298
677
|
if (obj.targetAspect)
|
|
@@ -386,129 +765,6 @@ function extend( model ) {
|
|
|
386
765
|
return !hasError;
|
|
387
766
|
}
|
|
388
767
|
|
|
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
768
|
/**
|
|
513
769
|
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
514
770
|
* except if all extensions are in the same file.
|
|
@@ -550,26 +806,6 @@ function extend( model ) {
|
|
|
550
806
|
}
|
|
551
807
|
}
|
|
552
808
|
|
|
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
809
|
|
|
574
810
|
/**
|
|
575
811
|
* @param {XSN.Extension[]} extensions
|
|
@@ -597,77 +833,6 @@ function extend( model ) {
|
|
|
597
833
|
}
|
|
598
834
|
}
|
|
599
835
|
|
|
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
836
|
// includes ----------------------------------------------------------------
|
|
672
837
|
|
|
673
838
|
/**
|
|
@@ -693,6 +858,7 @@ function extend( model ) {
|
|
|
693
858
|
delete ref._artifact;
|
|
694
859
|
}
|
|
695
860
|
else if (name && name in extensionsDict) {
|
|
861
|
+
// one of the includes has itself extensions that need to be applied first
|
|
696
862
|
return false;
|
|
697
863
|
}
|
|
698
864
|
else if (ref._artifact) {
|
|
@@ -775,6 +941,7 @@ function extend( model ) {
|
|
|
775
941
|
// all usages in the expressions? Possibly just the first one?
|
|
776
942
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
777
943
|
elem.$syntax = 'calc';
|
|
944
|
+
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
778
945
|
}
|
|
779
946
|
// TODO: also complain if elem is just defined in art
|
|
780
947
|
});
|
|
@@ -801,8 +968,15 @@ function extend( model ) {
|
|
|
801
968
|
forEachInOrder(parent, prop, ( member, name ) => {
|
|
802
969
|
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
803
970
|
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
804
|
-
|
|
805
|
-
|
|
971
|
+
if (isBetaEnabled(options, 'v4preview')) {
|
|
972
|
+
error( 'duplicate-definition', [ parent.name.location, member ],
|
|
973
|
+
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
// Error accidentally removed in v2/v3, therefore only a warning.
|
|
977
|
+
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
|
|
978
|
+
{ '#': prop, name, sorted_arts: includes } );
|
|
979
|
+
}
|
|
806
980
|
}
|
|
807
981
|
});
|
|
808
982
|
}
|
|
@@ -933,6 +1107,7 @@ function extend( model ) {
|
|
|
933
1107
|
const art = useTextsAspect
|
|
934
1108
|
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
935
1109
|
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
1110
|
+
// both functions are rather similar...
|
|
936
1111
|
|
|
937
1112
|
const { location } = base.name;
|
|
938
1113
|
|
|
@@ -968,7 +1143,7 @@ function extend( model ) {
|
|
|
968
1143
|
elem.key = { val: true, $inferred: 'localized', location };
|
|
969
1144
|
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
970
1145
|
// they should be omitted from OData containment EDM
|
|
971
|
-
|
|
1146
|
+
setAnnotation( elem, '@odata.containment.ignore', location );
|
|
972
1147
|
}
|
|
973
1148
|
else {
|
|
974
1149
|
// add the former key paths to the unique constraint
|
|
@@ -1005,10 +1180,10 @@ function extend( model ) {
|
|
|
1005
1180
|
path: [ { id: locale.name.id, location: locale.location } ],
|
|
1006
1181
|
location: locale.location,
|
|
1007
1182
|
});
|
|
1008
|
-
|
|
1183
|
+
setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
1009
1184
|
}
|
|
1010
1185
|
|
|
1011
|
-
copyPersistenceAnnotations(art, base
|
|
1186
|
+
copyPersistenceAnnotations( art, base );
|
|
1012
1187
|
return art;
|
|
1013
1188
|
}
|
|
1014
1189
|
|
|
@@ -1040,7 +1215,7 @@ function extend( model ) {
|
|
|
1040
1215
|
if (!fioriEnabled) {
|
|
1041
1216
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1042
1217
|
// TODO (next major version): remove?
|
|
1043
|
-
|
|
1218
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
1044
1219
|
}
|
|
1045
1220
|
else {
|
|
1046
1221
|
// @fiori.draft.enabled artifacts need default elements ID_texts and locale.
|
|
@@ -1061,9 +1236,11 @@ function extend( model ) {
|
|
|
1061
1236
|
|
|
1062
1237
|
if (addTextsLanguageAssoc && art.elements.language)
|
|
1063
1238
|
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
1239
|
+
// TODO: what is this necessary? We do not create a text entity in this case
|
|
1064
1240
|
|
|
1065
1241
|
setLink( art, '_block', model.$internal );
|
|
1066
1242
|
model.definitions[absolute] = art;
|
|
1243
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1067
1244
|
return art;
|
|
1068
1245
|
}
|
|
1069
1246
|
|
|
@@ -1098,7 +1275,7 @@ function extend( model ) {
|
|
|
1098
1275
|
locale.key = { val: true, location };
|
|
1099
1276
|
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1100
1277
|
// TODO (next major version): remove?
|
|
1101
|
-
|
|
1278
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
1102
1279
|
}
|
|
1103
1280
|
else {
|
|
1104
1281
|
const textId = {
|
|
@@ -1110,11 +1287,11 @@ function extend( model ) {
|
|
|
1110
1287
|
};
|
|
1111
1288
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
1112
1289
|
}
|
|
1113
|
-
|
|
1114
1290
|
dictAdd( art.elements, 'locale', locale );
|
|
1291
|
+
|
|
1115
1292
|
setLink( art, '_block', model.$internal );
|
|
1116
1293
|
model.definitions[absolute] = art;
|
|
1117
|
-
|
|
1294
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1118
1295
|
return art;
|
|
1119
1296
|
}
|
|
1120
1297
|
|
|
@@ -1398,9 +1575,9 @@ function extend( model ) {
|
|
|
1398
1575
|
model.definitions[entityName] = art;
|
|
1399
1576
|
initArtifact( art );
|
|
1400
1577
|
|
|
1578
|
+
chooseAnnotationsInArtifact( art ); // having extensions here would be wrong
|
|
1401
1579
|
// Copy persistence annotations from aspect.
|
|
1402
|
-
copyPersistenceAnnotations(art, target
|
|
1403
|
-
|
|
1580
|
+
copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
|
|
1404
1581
|
return art;
|
|
1405
1582
|
}
|
|
1406
1583
|
|
|
@@ -1416,138 +1593,204 @@ function extend( model ) {
|
|
|
1416
1593
|
if (origin.key)
|
|
1417
1594
|
proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1418
1595
|
if (anno)
|
|
1419
|
-
|
|
1596
|
+
setAnnotation( proxy, anno );
|
|
1420
1597
|
dictAdd( proxyDict.elements, pname, proxy );
|
|
1421
1598
|
}
|
|
1422
1599
|
}
|
|
1423
1600
|
|
|
1424
1601
|
|
|
1425
1602
|
// 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
|
-
}
|
|
1603
|
+
// move to top
|
|
1467
1604
|
|
|
1468
1605
|
/**
|
|
1469
1606
|
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
1470
1607
|
* if multiple exist according to the module layer.
|
|
1608
|
+
* TODO: rename to extendArtifactBefore
|
|
1471
1609
|
*
|
|
1472
1610
|
* @param {XSN.Artifact} art
|
|
1473
1611
|
*/
|
|
1474
1612
|
function chooseAnnotationsInArtifact( art ) {
|
|
1475
|
-
for
|
|
1476
|
-
|
|
1477
|
-
|
|
1613
|
+
// for main artifacts, move extensions from `$lateExtensions` model dictionary:
|
|
1614
|
+
if (!art._main && !art._outer && art._extensions === undefined &&
|
|
1615
|
+
art.kind !== 'namespace') {
|
|
1616
|
+
// if (!art.name) console.log(art)
|
|
1617
|
+
const { absolute } = art.name;
|
|
1618
|
+
setLink( art, '_extensions', model.$lateExtensions[absolute]?._extensions || null );
|
|
1619
|
+
if (art._extensions && !art.builtin) { // keep extensions for builtin in $lateExtensions
|
|
1620
|
+
delete model.$lateExtensions[absolute];
|
|
1621
|
+
// TODO: if the extension mechanism has been completed, we could uncomment:
|
|
1622
|
+
// art._extensions.forEach( ext => resolvePath( ext.name, ext.kind, ext )); // for LSP
|
|
1623
|
+
// for now, we do that at the end of lateExtensions()
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (art._extensions) {
|
|
1627
|
+
// TODO: the following function can now be simplified
|
|
1628
|
+
// if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
|
|
1629
|
+
// With extensions, member appears in CSN, affects directly the rendering of
|
|
1630
|
+
// elements etc. TODO: do that more specifically on the dicts (via symbol)
|
|
1631
|
+
// Probably better: we could use the _extensions dict prop directly in to-csn
|
|
1632
|
+
if (art.$inferred)
|
|
1633
|
+
setExpandStatusAnnotate( art, 'annotate' );
|
|
1634
|
+
if (Array.isArray( art._extensions )) {
|
|
1635
|
+
checkExtensionsKind( art._extensions, art ); // TODO: check with builtins
|
|
1636
|
+
transformArtifactExtensions( art );
|
|
1637
|
+
}
|
|
1638
|
+
applyAllExtensions( art );
|
|
1478
1639
|
}
|
|
1479
|
-
if (art.doc)
|
|
1480
|
-
chooseAssignment( 'doc', art );
|
|
1481
1640
|
}
|
|
1482
1641
|
|
|
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;
|
|
1642
|
+
// TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
|
|
1643
|
+
function transformArtifactExtensions( art ) {
|
|
1644
|
+
const hasOnlySubExtensions = art._outer; // items, anonymous aspects
|
|
1645
|
+
const dict = Object.create(null);
|
|
1646
|
+
for (const ext of art._extensions) {
|
|
1647
|
+
for (const prop in ext) {
|
|
1648
|
+
if (ext[prop] === undefined) // deleted propery
|
|
1649
|
+
continue;
|
|
1650
|
+
// TODO: do this check nicer (after complete move to new extensions mechanism)
|
|
1651
|
+
if (prop.charAt(0) === '@' || prop === 'doc' ||
|
|
1652
|
+
prop === 'includes' || prop === 'columns' ||
|
|
1653
|
+
prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
|
|
1654
|
+
if (!hasOnlySubExtensions)
|
|
1655
|
+
pushToDict( dict, prop, ext );
|
|
1656
|
+
}
|
|
1657
|
+
else if (prop === 'elements' || prop === 'enum' || prop === 'actions' ||
|
|
1658
|
+
prop === 'params' || prop === 'returns') {
|
|
1659
|
+
if (ext.kind === 'extend')
|
|
1660
|
+
pushToDict( dict, 'includes', ext );
|
|
1661
|
+
pushToDict( dict, prop, ext );
|
|
1506
1662
|
}
|
|
1507
1663
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1664
|
+
}
|
|
1665
|
+
art._extensions = dict;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function applyAllExtensions( art ) {
|
|
1669
|
+
const extensions = art._extensions;
|
|
1670
|
+
for (const prop in extensions) {
|
|
1671
|
+
// TODO: do the following `if` in a nicer way
|
|
1672
|
+
if ([ 'elements', 'enum', 'actions', 'params', 'returns' ].includes( prop ))
|
|
1673
|
+
continue; // currently just annotates on sub elements - TODO: error here
|
|
1674
|
+
// annotations, `doc`, `includes`, `columns`, `length`, ...
|
|
1675
|
+
const scheduled = [];
|
|
1676
|
+
// sort extensions according to layer (specified elements are bottom layer):
|
|
1677
|
+
const layered = layeredExtensions( extensions[prop] );
|
|
1678
|
+
|
|
1679
|
+
let cont = true;
|
|
1680
|
+
while (cont) {
|
|
1681
|
+
const { highest, issue } = extensionsOfHighestLayers( layered );
|
|
1682
|
+
// console.log( 'CA:', annoName, issue, extensions)
|
|
1683
|
+
let index = highest.length;
|
|
1684
|
+
cont = !!index; // safety
|
|
1685
|
+
while (--index >= 0) {
|
|
1686
|
+
const ext = highest[index];
|
|
1687
|
+
scheduled.push( ext );
|
|
1688
|
+
if (extensionOverwrites( ext, prop )) {
|
|
1689
|
+
cont = false;
|
|
1690
|
+
break;
|
|
1520
1691
|
}
|
|
1521
1692
|
}
|
|
1693
|
+
if (issue || index > 0)
|
|
1694
|
+
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
1695
|
+
}
|
|
1696
|
+
// Now apply the relevant extensions
|
|
1697
|
+
scheduled.reverse();
|
|
1698
|
+
for (const ext of scheduled)
|
|
1699
|
+
applySingleExtension( art, ext, prop );
|
|
1700
|
+
delete extensions[prop];
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function extensionOverwrites( ext, prop ) {
|
|
1705
|
+
return (prop.charAt(0) !== '@')
|
|
1706
|
+
? [ 'doc', 'length', 'precision', 'scale', 'srid' ].includes( prop )
|
|
1707
|
+
: !annotationHasEllipsis( ext[prop] );
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// TODO: still a bit annotation assignment specific
|
|
1711
|
+
function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
|
|
1712
|
+
// TODO: think about messages for these
|
|
1713
|
+
if (prop === 'elements' || prop === 'enum' || prop === 'actions' || prop === 'columns' ||
|
|
1714
|
+
prop === 'params' || prop === 'returns' || prop === 'includes' )
|
|
1715
|
+
return; // extensions currently handled extra
|
|
1716
|
+
if (issue) {
|
|
1717
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1718
|
+
let msg = (index < 0)
|
|
1719
|
+
? 'anno-unstable-array'
|
|
1720
|
+
: (issue === true)
|
|
1721
|
+
? 'anno-duplicate'
|
|
1722
|
+
: 'anno-duplicate-unrelated-layer';
|
|
1723
|
+
if (prop.charAt(0) !== '@' && prop !== 'doc') {
|
|
1724
|
+
msg = (issue === true)
|
|
1725
|
+
? 'ext-duplicate-extend-type'
|
|
1726
|
+
: 'ext-duplicate-extend-type-unrelated-layer';
|
|
1727
|
+
// not sure whether to repeat the extended artifact in the message (we
|
|
1728
|
+
// have the semantic location, after all)
|
|
1522
1729
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
{ '#': variant, anno:
|
|
1730
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
1731
|
+
for (const ext of extensions) {
|
|
1732
|
+
const anno = ext[prop];
|
|
1733
|
+
if (anno && !anno.$errorReported) {
|
|
1734
|
+
message( msg, [ anno.name?.location || anno.location, ext ],
|
|
1735
|
+
{ '#': variant, anno: prop, type: art } );
|
|
1529
1736
|
}
|
|
1530
1737
|
}
|
|
1531
1738
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1739
|
+
else if (index > 0) { // more than one set (not just ...)
|
|
1740
|
+
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
1741
|
+
const msgid = (prop.charAt(0) === '@' || prop === 'doc')
|
|
1742
|
+
? 'anno-duplicate-same-file' // TODO: always ext-duplicate-…
|
|
1743
|
+
: 'ext-duplicate-same-file';
|
|
1744
|
+
while (index >= 0) { // do not report for trailing [...]
|
|
1745
|
+
const ext = extensions[index--];
|
|
1746
|
+
const anno = ext[prop];
|
|
1747
|
+
warning( msgid, [ anno.name?.location || anno.location, ext ],
|
|
1748
|
+
{ '#': variant, prop, anno: prop } );
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function applySingleExtension( art, ext, prop ) {
|
|
1754
|
+
if (prop === 'includes') {
|
|
1755
|
+
if (ext.kind === 'extend' && art.$inferred) {
|
|
1756
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
|
|
1757
|
+
'You can\'t use EXTEND on the generated $(ART)' );
|
|
1758
|
+
}
|
|
1759
|
+
else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
|
|
1760
|
+
const { absolute } = art.name;
|
|
1761
|
+
const dict = extensionsDict[absolute] || (extensionsDict[absolute] = []);
|
|
1762
|
+
dict.push( ext ); // TODO: change
|
|
1763
|
+
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[absolute])
|
|
1764
|
+
}
|
|
1765
|
+
// art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
|
|
1766
|
+
}
|
|
1767
|
+
else if (prop === 'columns') {
|
|
1768
|
+
const { query } = art;
|
|
1769
|
+
if (!query?.from?.path)
|
|
1770
|
+
error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
|
|
1771
|
+
else if (!query.columns)
|
|
1772
|
+
query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
|
|
1773
|
+
else
|
|
1774
|
+
query.columns.push( ...ext.columns );
|
|
1775
|
+
}
|
|
1776
|
+
else if ([ 'length', 'precision', 'scale', 'srid' ].includes( prop )) {
|
|
1777
|
+
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
1778
|
+
typeExts[prop] = ext;
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
const result = applyAssignment( art[prop], ext[prop], ext, prop );
|
|
1782
|
+
art[prop] = (result.name) ? result : Object.assign( {}, art[prop], result );
|
|
1783
|
+
}
|
|
1539
1784
|
}
|
|
1540
1785
|
|
|
1541
1786
|
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
1787
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
1788
|
+
if (!firstEllipsis)
|
|
1789
|
+
return anno;
|
|
1542
1790
|
const hasBase = previousAnno?.literal === 'array';
|
|
1543
1791
|
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
|
-
}
|
|
1792
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
1793
|
+
message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
|
|
1551
1794
|
previousAnno = { val: [] };
|
|
1552
1795
|
}
|
|
1553
1796
|
else if (previousAnno.literal !== 'array') {
|
|
@@ -1641,69 +1884,70 @@ function extend( model ) {
|
|
|
1641
1884
|
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1642
1885
|
}
|
|
1643
1886
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1887
|
+
/**
|
|
1888
|
+
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1889
|
+
* source to target if present on source but not target.
|
|
1890
|
+
*
|
|
1891
|
+
* @param {object} target
|
|
1892
|
+
* @param {object} source
|
|
1893
|
+
*/
|
|
1894
|
+
function copyPersistenceAnnotations( target, source ) {
|
|
1895
|
+
if (!source)
|
|
1896
|
+
return;
|
|
1897
|
+
|
|
1898
|
+
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1899
|
+
if (copyExists)
|
|
1900
|
+
copy( '@cds.persistence.exists' );
|
|
1901
|
+
copy( '@cds.persistence.skip' );
|
|
1902
|
+
|
|
1903
|
+
function copy( anno ) {
|
|
1904
|
+
if ( source[anno] && !target[anno] )
|
|
1905
|
+
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1657
1906
|
}
|
|
1658
1907
|
}
|
|
1659
1908
|
}
|
|
1660
1909
|
|
|
1661
1910
|
/**
|
|
1662
|
-
* Group
|
|
1911
|
+
* Group extensions by their layers. A definition (for specified elements)
|
|
1663
1912
|
* is considered to be provided in a layer named '', the lowest layer.
|
|
1664
1913
|
*
|
|
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[]}`
|
|
1914
|
+
* @param {object[]} extensions Array of extensions.
|
|
1915
|
+
* @returns {Record<string, object>} key: layer name, value: {name, layer, extensions[]}`
|
|
1670
1916
|
*/
|
|
1671
|
-
function
|
|
1917
|
+
function layeredExtensions( extensions ) {
|
|
1672
1918
|
const layered = Object.create(null);
|
|
1673
|
-
for (const
|
|
1674
|
-
const layer =
|
|
1919
|
+
for (const ext of extensions) {
|
|
1920
|
+
const layer = (ext.kind === 'annotate' || ext.kind === 'extend') && layers.layer( ext );
|
|
1675
1921
|
// just consider layer if Extend/Annotate, not Define
|
|
1676
1922
|
const name = (layer) ? layer.realname : '';
|
|
1677
1923
|
const done = layered[name];
|
|
1678
1924
|
if (done)
|
|
1679
|
-
done.
|
|
1925
|
+
done.extensions.push( ext );
|
|
1680
1926
|
else
|
|
1681
|
-
layered[name] = { name, layer,
|
|
1682
|
-
// TODO: file - if set: unique in layer
|
|
1927
|
+
layered[name] = { name, layer, extensions: [ ext ] };
|
|
1683
1928
|
}
|
|
1684
1929
|
return layered;
|
|
1685
1930
|
}
|
|
1686
1931
|
|
|
1687
1932
|
/**
|
|
1688
|
-
* Return
|
|
1933
|
+
* Return extensions of the highest layers.
|
|
1689
1934
|
* 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
|
|
1935
|
+
* - false: there are just extensions in one file,
|
|
1936
|
+
* - 'unrelated': there is just one extension per layer
|
|
1937
|
+
* - true: there is at least one layer with two or more extensions, and
|
|
1693
1938
|
* at least two files are involved
|
|
1694
|
-
* TODO: make this usable for extend (elements), too.
|
|
1695
1939
|
*
|
|
1696
|
-
* @param {Record<string, object>}
|
|
1940
|
+
* @param {Record<string, object>} layered Structure as returned by layeredExtensions()
|
|
1697
1941
|
* @returns {{assignments, issue: boolean|string}}
|
|
1698
1942
|
*/
|
|
1699
|
-
function
|
|
1700
|
-
const layerNames = Object.keys(
|
|
1943
|
+
function extensionsOfHighestLayers( layered ) {
|
|
1944
|
+
const layerNames = Object.keys( layered );
|
|
1701
1945
|
// console.log('HIB:',layerNames)
|
|
1702
1946
|
if (layerNames.length <= 1) {
|
|
1703
1947
|
const name = layerNames[0];
|
|
1704
|
-
const
|
|
1705
|
-
delete
|
|
1706
|
-
return {
|
|
1948
|
+
const highest = layered[name]?.extensions || [];
|
|
1949
|
+
delete layered[name];
|
|
1950
|
+
return { highest, issue: inMoreThanOneFile( highest ) };
|
|
1707
1951
|
}
|
|
1708
1952
|
|
|
1709
1953
|
// collect all layers which are lower than another layer
|
|
@@ -1711,42 +1955,41 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
|
1711
1955
|
allExtends[''] = {}; // the "Define" layer
|
|
1712
1956
|
for (const name of layerNames) {
|
|
1713
1957
|
if (name) // not the "Define" layer
|
|
1714
|
-
Object.assign( allExtends,
|
|
1958
|
+
Object.assign( allExtends, layered[name].layer._layerExtends );
|
|
1715
1959
|
}
|
|
1716
1960
|
// console.log('HIE:',Object.keys(allExtends))
|
|
1717
|
-
const
|
|
1718
|
-
const
|
|
1961
|
+
const highest = []; // extensions
|
|
1962
|
+
const highestLayers = [];
|
|
1719
1963
|
for (const name of layerNames) {
|
|
1720
1964
|
if (!(name in allExtends)) {
|
|
1721
|
-
const layer =
|
|
1722
|
-
delete
|
|
1723
|
-
|
|
1724
|
-
|
|
1965
|
+
const layer = layered[name];
|
|
1966
|
+
delete layered[name];
|
|
1967
|
+
highestLayers.push( layer );
|
|
1968
|
+
highest.push( ...layer.extensions );
|
|
1725
1969
|
}
|
|
1726
1970
|
}
|
|
1727
|
-
|
|
1728
|
-
const good =
|
|
1971
|
+
highest.sort( compareExtensions );
|
|
1972
|
+
const good = highestLayers.every( layer => !inMoreThanOneFile( layer.extensions ));
|
|
1729
1973
|
// TODO: use layer.file instead
|
|
1730
|
-
const issue = !good ||
|
|
1731
|
-
// console.log('HI:',highest.map(l=>l.name),issue,issue&&
|
|
1732
|
-
return {
|
|
1974
|
+
const issue = !good || highestLayers.length > 1 && 'unrelated';
|
|
1975
|
+
// console.log('HI:',highest.map(l=>l.name),issue,issue&&extensions)
|
|
1976
|
+
return { highest, issue };
|
|
1733
1977
|
}
|
|
1734
1978
|
|
|
1735
|
-
function inMoreThanOneFile(
|
|
1736
|
-
if (
|
|
1979
|
+
function inMoreThanOneFile( extensions ) {
|
|
1980
|
+
if (extensions.length <= 1)
|
|
1737
1981
|
return false;
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return !file || assignments.slice(1).some( a => (a.name || a).location?.file !== file );
|
|
1982
|
+
const file = extensions[0].location?.file;
|
|
1983
|
+
return !file || extensions.slice(1).some( e => e.location?.file !== file );
|
|
1741
1984
|
}
|
|
1742
1985
|
|
|
1743
1986
|
/**
|
|
1744
|
-
* Compare two
|
|
1745
|
-
* - via the fs.realpath of the file (not layer!) of the
|
|
1746
|
-
* - via the line, then column of the
|
|
1987
|
+
* Compare two extensions which are not comparable via layering:
|
|
1988
|
+
* - via the fs.realpath of the file (not layer!) of the extensions, then
|
|
1989
|
+
* - via the line, then column of the extensions.
|
|
1747
1990
|
* Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
|
|
1748
1991
|
*/
|
|
1749
|
-
function
|
|
1992
|
+
function compareExtensions( a, b ) {
|
|
1750
1993
|
const fileA = layers.realname( a._block );
|
|
1751
1994
|
const fileB = layers.realname( b._block );
|
|
1752
1995
|
if (fileA !== fileB)
|
|
@@ -1755,29 +1998,6 @@ function compareAssignments( a, b ) {
|
|
|
1755
1998
|
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
1756
1999
|
}
|
|
1757
2000
|
|
|
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
2001
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
1782
2002
|
const args = relations.map( eq );
|
|
1783
2003
|
return (args.length === 1)
|
|
@@ -1811,15 +2031,14 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
1811
2031
|
* @param {XSN.Extension} ext
|
|
1812
2032
|
* @param {object} art
|
|
1813
2033
|
*/
|
|
1814
|
-
function storeTypeExtension( ext, art ) {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
|
|
2034
|
+
// function storeTypeExtension( ext, art ) {
|
|
2035
|
+
// // If there are no parameters to apply, don't store the extension.
|
|
2036
|
+
// if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
|
|
2037
|
+
// return;
|
|
2038
|
+
// else if (!art._extendType)
|
|
2039
|
+
// setLink( art, '_extendType', [] );
|
|
2040
|
+
// art._extendType.push( ext );
|
|
2041
|
+
// }
|
|
1823
2042
|
|
|
1824
2043
|
function checkTextsLanguageAssocOption( model, options ) {
|
|
1825
2044
|
const languages = model.definitions['sap.common.Languages'];
|