@sap/cds-compiler 6.6.0 → 6.7.1
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 +34 -1
- package/bin/cdsc.js +2 -0
- package/bin/cdsse.js +1 -1
- package/lib/base/message-registry.js +6 -7
- package/lib/base/model.js +0 -72
- package/lib/checks/elements.js +1 -1
- package/lib/checks/featureFlags.js +2 -2
- package/lib/compiler/assert-consistency.js +3 -4
- package/lib/compiler/base.js +8 -0
- package/lib/compiler/builtins.js +8 -9
- package/lib/compiler/checks.js +27 -6
- package/lib/compiler/cycle-detector.js +4 -4
- package/lib/compiler/define.js +65 -83
- package/lib/compiler/extend.js +357 -325
- package/lib/compiler/finalize-parse-cdl.js +3 -4
- package/lib/compiler/generate.js +205 -203
- package/lib/compiler/kick-start.js +34 -49
- package/lib/compiler/populate.js +95 -28
- package/lib/compiler/propagator.js +3 -5
- package/lib/compiler/resolve.js +17 -13
- package/lib/compiler/shared.js +47 -19
- package/lib/compiler/tweak-assocs.js +2 -4
- package/lib/compiler/utils.js +84 -31
- package/lib/gen/BaseParser.js +924 -1055
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +5 -2
- package/lib/json/from-csn.js +25 -16
- package/lib/main.d.ts +13 -0
- package/lib/model/revealInternalProperties.js +18 -0
- package/lib/parsers/AstBuildingParser.js +22 -5
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/utils/sql.js +2 -2
- package/lib/render/utils/standardDatabaseFunctions.js +2 -2
- package/lib/transform/db/constraints.js +3 -4
- package/lib/transform/db/killAnnotations.js +1 -1
- package/lib/transform/db/processSqlServices.js +10 -11
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/forOdata.js +7 -124
- package/lib/transform/odata/fioriTreeViews.js +173 -0
- package/lib/transform/odata/flattening.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +7 -4
- package/package.json +1 -1
- package/share/messages/message-explanations.json +0 -2
- package/share/messages/type-unexpected-foreign-keys.md +0 -52
- package/share/messages/type-unexpected-on-condition.md +0 -52
package/lib/compiler/extend.js
CHANGED
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const { weakRefLocation } = require('../base/location');
|
|
6
6
|
const { searchName } = require('../base/messages');
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
forEachDefinition,
|
|
10
|
-
forEachMember,
|
|
11
|
-
forEachGeneric,
|
|
12
|
-
isDeprecatedEnabled,
|
|
13
|
-
} = require('../base/model');
|
|
14
|
-
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
7
|
+
const { isDeprecatedEnabled } = require('../base/model');
|
|
8
|
+
const { dictAdd, pushToDict, dictForEach } = require('../base/dictionaries');
|
|
15
9
|
const { kindProperties, dictKinds } = require('./base');
|
|
16
10
|
const {
|
|
17
11
|
setLink,
|
|
@@ -26,9 +20,14 @@ const {
|
|
|
26
20
|
initDollarSelf,
|
|
27
21
|
initBoundSelfParam,
|
|
28
22
|
dependsOnSilent,
|
|
29
|
-
storeExtension,
|
|
30
23
|
pathName,
|
|
31
24
|
annotationHasEllipsis,
|
|
25
|
+
forEachInOrder,
|
|
26
|
+
forEachDefinition,
|
|
27
|
+
forEachMember,
|
|
28
|
+
forEachGeneric,
|
|
29
|
+
isDirectComposition,
|
|
30
|
+
targetCantBeAspect,
|
|
32
31
|
} = require('./utils');
|
|
33
32
|
const layers = require('./moduleLayers');
|
|
34
33
|
const { CompilerAssertion } = require('../base/error');
|
|
@@ -36,8 +35,9 @@ const { Location } = require('../base/location');
|
|
|
36
35
|
const { typeParameters } = require('./builtins');
|
|
37
36
|
|
|
38
37
|
const $location = Symbol.for( 'cds.$location' );
|
|
38
|
+
const $inferred = Symbol.for( 'cds.$inferred' ); // TODO: no $inferred yet?
|
|
39
39
|
|
|
40
|
-
// attach stupid location - TODO: remove in
|
|
40
|
+
// attach stupid location - TODO: remove in v7
|
|
41
41
|
const genLocation = new Location( '' );
|
|
42
42
|
|
|
43
43
|
const draftElements = [
|
|
@@ -68,15 +68,14 @@ function extend( model ) {
|
|
|
68
68
|
resolveTypeArgumentsUnchecked,
|
|
69
69
|
resolveDefinitionName,
|
|
70
70
|
attachAndEmitValidNames,
|
|
71
|
-
targetIsTargetAspect,
|
|
72
71
|
checkRedefinition,
|
|
73
|
-
initSelectItems,
|
|
74
72
|
} = model.$functions;
|
|
75
73
|
|
|
76
74
|
Object.assign( model.$functions, {
|
|
77
75
|
createRemainingAnnotateStatements,
|
|
78
76
|
extendArtifactBefore,
|
|
79
77
|
extendArtifactAfter,
|
|
78
|
+
extendArtifactAdd,
|
|
80
79
|
extendForeignKeys,
|
|
81
80
|
withLocalizedData,
|
|
82
81
|
applyIncludes, // TODO: re-check
|
|
@@ -85,20 +84,56 @@ function extend( model ) {
|
|
|
85
84
|
const includesNonShadowedFirst
|
|
86
85
|
= isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
|
|
87
86
|
|
|
87
|
+
forEachGeneric( model, 'definitions', tagCompositionTargets );
|
|
88
|
+
dictForEach( model.$collectedExtensions, e => e._extensions.forEach( tagCompositionTargets ) );
|
|
89
|
+
// remark: tagging on extensions works _before_ running extendArtifactBefore() on each artifact
|
|
88
90
|
sortModelSources();
|
|
89
|
-
const extensionsDict = Object.create( null ); // TODO TMP
|
|
90
|
-
forEachDefinition( model, tagIncludes ); // TODO TMP
|
|
91
91
|
|
|
92
|
+
// Set annotations on user-provided artifacts, but they are not propagated yet!
|
|
93
|
+
// Use them in the main compiler phase (and before) with extra care:
|
|
92
94
|
forEachDefinition( model, extendArtifactBefore );
|
|
93
|
-
applyExtensions(); // old-style
|
|
94
95
|
return;
|
|
95
96
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// Tag composition targets: ---------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function tagCompositionTargets( elem ) {
|
|
100
|
+
if (elem.$inferred) // TODO: probably no $inferred yet
|
|
101
|
+
return;
|
|
102
|
+
if (elem.targetAspect?.elements)
|
|
103
|
+
elem = elem.targetAspect;
|
|
104
|
+
if (elem.elements) {
|
|
105
|
+
forEachGeneric( elem, 'elements', tagCompositionTargets );
|
|
106
|
+
}
|
|
107
|
+
else if (elem.columns) { // `elem` is query or extension
|
|
108
|
+
elem.columns.forEach( tagCompositionTargets );
|
|
109
|
+
}
|
|
110
|
+
else if (elem.$queries) {
|
|
111
|
+
for (const query of elem.$queries) {
|
|
112
|
+
if (query.mixin)
|
|
113
|
+
forEachGeneric( query, 'mixin', tagCompositionTargets );
|
|
114
|
+
if (query.columns)
|
|
115
|
+
query.columns.forEach( tagCompositionTargets );
|
|
116
|
+
// Remark: no directly published expand/inline column yet
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (elem.target && isDirectComposition( elem )) {
|
|
120
|
+
const name = resolveUncheckedPath( elem.target, 'target', elem );
|
|
121
|
+
if (!name)
|
|
122
|
+
return;
|
|
123
|
+
const target = model.definitions[name];
|
|
124
|
+
// move target aspect in `target` to `targetAspect` (in define.js, we only
|
|
125
|
+
// do it with anonymous aspect)
|
|
126
|
+
if (target?.kind in { aspect: 1, type: 1 } && // type is sloppy
|
|
127
|
+
target.elements && !target.elements[$inferred] &&
|
|
128
|
+
!targetCantBeAspect( elem, false, model.definitions )) { // tests `!elem.targetAspect`
|
|
129
|
+
elem.targetAspect = elem.target;
|
|
130
|
+
delete elem.target;
|
|
131
|
+
}
|
|
132
|
+
model.$compositionTargets[name] = true; // does not hurt if set on aspect
|
|
133
|
+
}
|
|
100
134
|
}
|
|
101
135
|
|
|
136
|
+
|
|
102
137
|
//-----------------------------------------------------------------------------
|
|
103
138
|
// Extensions: general algorithm
|
|
104
139
|
//-----------------------------------------------------------------------------
|
|
@@ -110,6 +145,9 @@ function extend( model ) {
|
|
|
110
145
|
* if multiple exist according to the module layer.
|
|
111
146
|
* TODO: update comment if extension algorithm is finished
|
|
112
147
|
*
|
|
148
|
+
* Called at the beginning for each main artifact, and must not call
|
|
149
|
+
* resolvePath() then. It is later called via effectiveType().
|
|
150
|
+
*
|
|
113
151
|
* @param {XSN.Artifact} art
|
|
114
152
|
*/
|
|
115
153
|
function extendArtifactBefore( art ) {
|
|
@@ -126,6 +164,9 @@ function extend( model ) {
|
|
|
126
164
|
// for now, we do that at the end of createRemainingAnnotateStatements()
|
|
127
165
|
}
|
|
128
166
|
}
|
|
167
|
+
if (art.kind === 'entity' && art.includes && !art._entityIncludes)
|
|
168
|
+
setEntityIncludes( art, art );
|
|
169
|
+
|
|
129
170
|
if (art._extensions) {
|
|
130
171
|
// TODO: the following function can now be simplified
|
|
131
172
|
// if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
|
|
@@ -135,19 +176,39 @@ function extend( model ) {
|
|
|
135
176
|
if (art.$inferred)
|
|
136
177
|
setExpandStatusAnnotate( art, 'annotate' );
|
|
137
178
|
if (Array.isArray( art._extensions )) {
|
|
138
|
-
checkExtensionsKind( art._extensions, art );
|
|
179
|
+
checkExtensionsKind( art._extensions, art );
|
|
139
180
|
transformArtifactExtensions( art );
|
|
140
181
|
}
|
|
141
|
-
|
|
182
|
+
// console.log('EA:',require('../model/revealInternalProperties')
|
|
183
|
+
// .ref(art),Object.keys(art._extensions))
|
|
184
|
+
for (const prop in art._extensions) {
|
|
185
|
+
if (Object.hasOwn( art._extensions, prop ) &&
|
|
186
|
+
// remark: if we change the array, consider whether to delete the artifact
|
|
187
|
+
![ 'elements', 'actions', 'params', '$gen' ].includes( prop ))
|
|
188
|
+
applyPropertyExtensions( art, prop );
|
|
189
|
+
}
|
|
142
190
|
}
|
|
143
191
|
}
|
|
144
192
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Push down extensions on member properties like `elements` to the individual
|
|
195
|
+
* members (setting their `_extensions` property).
|
|
196
|
+
* Currently, definitions in `extend` members are ignored,
|
|
197
|
+
* because they are handled a-priori by the old-style extension mechanism.
|
|
198
|
+
*
|
|
199
|
+
* Outside this file: only called in effectiveType() and indirectly in
|
|
200
|
+
* tweak-assocs.js (for foreign keys and super-annotates).
|
|
201
|
+
*/
|
|
148
202
|
function extendArtifactAfter( art ) {
|
|
203
|
+
// TODO: assert that we have not yet transformed/used _extensions on sub elements
|
|
204
|
+
// TODO necessary(?): transformArtifactExtensions must ensure that each annotate
|
|
205
|
+
// is in either returns,items,elements,enum
|
|
206
|
+
if (art.builtin) // builtin members handled via "super annotate"
|
|
207
|
+
return;
|
|
208
|
+
// If elements are added in the future via includes or extend, it should be done here
|
|
209
|
+
|
|
149
210
|
const extensionsMap = art._extensions;
|
|
150
|
-
if (!extensionsMap
|
|
211
|
+
if (!extensionsMap) // builtin members handled via "super annotate"
|
|
151
212
|
return;
|
|
152
213
|
// type extensions after having “populated” the artifact ($typeArgs -> length,
|
|
153
214
|
// …, TODO: do that there) and setting an _effectiveType:
|
|
@@ -165,38 +226,39 @@ function extend( model ) {
|
|
|
165
226
|
delete art.$typeExts;
|
|
166
227
|
}
|
|
167
228
|
|
|
168
|
-
if (art.kind === 'annotate' && !art.returns && extensionsMap.returns && !art._parent?.returns)
|
|
169
|
-
annotateCreate( art, '', art, 'returns' );
|
|
170
|
-
|
|
171
229
|
moveDictExtensions( art, extensionsMap, 'actions' );
|
|
172
230
|
moveDictExtensions( art, extensionsMap, 'params' );
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
231
|
+
if (!extensionsMap.elements)
|
|
232
|
+
return;
|
|
233
|
+
|
|
234
|
+
// after populateArtifact, it is clear which properties the artifact has:
|
|
235
|
+
const artProp = art.kind === 'action' || art.kind === 'function'
|
|
236
|
+
? 'returns'
|
|
237
|
+
: [ 'returns', 'targetAspect', 'items' ].find( p => art[p] );
|
|
238
|
+
if (artProp) {
|
|
239
|
+
const returnsDict = artProp === 'returns' && !art.returns &&
|
|
240
|
+
annotateFor( art, 'params', '' ); // create anno for non-existing returns
|
|
241
|
+
for (const ext of extensionsMap.elements) {
|
|
242
|
+
if (!ext.returns) {
|
|
243
|
+
pushToDict( returnsDict || art[artProp], '_extensions', ext );
|
|
244
|
+
if (artProp === 'returns')
|
|
245
|
+
extendHandleReturns( ext, art );
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
pushToDict( returnsDict || art[artProp], '_extensions', ext.returns );
|
|
249
|
+
if (!art.returns)
|
|
250
|
+
checkReturnsExtension( ext, art );
|
|
251
|
+
}
|
|
183
252
|
}
|
|
253
|
+
// TODO: what about `many many Type` (via CSN)?
|
|
184
254
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let elementsProp = 'elements';
|
|
193
|
-
if (art.kind !== 'annotate')
|
|
194
|
-
elementsProp = art.enum && 'enum' || art.target && 'foreignKeys' || 'elements';
|
|
195
|
-
|
|
196
|
-
// keys are handled in tweak-assocs.js; don't push them down; see extendForeignKeys()
|
|
197
|
-
if (elementsProp !== 'foreignKeys')
|
|
198
|
-
moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
|
|
199
|
-
moveDictExtensions( art, extensionsMap, 'enum' );
|
|
255
|
+
else if (!art.target) { // TODO: foreign keys currently handled specially
|
|
256
|
+
for (const ext of extensionsMap.elements) {
|
|
257
|
+
if (ext.returns)
|
|
258
|
+
checkReturnsExtension( ext, art );
|
|
259
|
+
}
|
|
260
|
+
// if (art.elements || art.enum || art.kind === 'annotate')
|
|
261
|
+
moveDictExtensions( art, extensionsMap, (art.enum ? 'enum' : 'elements'), 'elements' );
|
|
200
262
|
}
|
|
201
263
|
}
|
|
202
264
|
|
|
@@ -281,6 +343,9 @@ function extend( model ) {
|
|
|
281
343
|
|
|
282
344
|
// For extendArtifactBefore(): ------------------------------------------------
|
|
283
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Complain about invalid `extend service` and `extend context`.
|
|
348
|
+
*/
|
|
284
349
|
function checkExtensionsKind( extensions, art ) {
|
|
285
350
|
for (const ext of extensions) {
|
|
286
351
|
const kind = ext.expectedKind?.val;
|
|
@@ -295,7 +360,7 @@ function extend( model ) {
|
|
|
295
360
|
code: 'extend … with definitions',
|
|
296
361
|
keyword: 'extend service',
|
|
297
362
|
};
|
|
298
|
-
// TODO
|
|
363
|
+
// TODO v7: Discuss: make this an error?
|
|
299
364
|
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
300
365
|
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
301
366
|
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
@@ -308,26 +373,63 @@ function extend( model ) {
|
|
|
308
373
|
}
|
|
309
374
|
}
|
|
310
375
|
|
|
311
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Transform `art._extensions` from an array of extensions into an object
|
|
378
|
+
* `{ prop: relevantExtensions, … }` where `relevantExtensions` are the extensions
|
|
379
|
+
* which are relevant for the value of `art[prop]` after the application of extensions.
|
|
380
|
+
*
|
|
381
|
+
* Remark: it is not necessarily clear at this moment whether `art` has
|
|
382
|
+
* `elements`, `enums`, `items`, etc.
|
|
383
|
+
*/
|
|
312
384
|
function transformArtifactExtensions( art ) {
|
|
313
|
-
|
|
314
|
-
const dict =
|
|
385
|
+
// TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
|
|
386
|
+
const dict = {};
|
|
315
387
|
for (const ext of art._extensions) {
|
|
388
|
+
// `annotate SomeFunction with @action { r @elem };` would later be moved to
|
|
389
|
+
// `someFunction.returns._extensions` due to `elements` → @action not relevant then:
|
|
390
|
+
// (TODO: should we use some “already applied” flag instead?)
|
|
391
|
+
const isAutoItemsOrReturns = art._outer || // in items or targetAspect
|
|
392
|
+
art._parent?.returns === art && !ext._parent?.returns;
|
|
316
393
|
for (const prop in ext) {
|
|
317
|
-
if (ext[prop] === undefined) // deleted property
|
|
394
|
+
if (!Object.hasOwn( ext, prop ) || ext[prop] === undefined) // deleted property
|
|
318
395
|
continue;
|
|
319
396
|
// TODO: do this check nicer (after complete move to new extensions mechanism)
|
|
320
|
-
if (prop
|
|
321
|
-
|
|
397
|
+
if (prop === 'includes') {
|
|
398
|
+
pushToDict( dict, prop, ext );
|
|
399
|
+
pushTo$add( dict, ext );
|
|
400
|
+
}
|
|
401
|
+
else if (prop.charAt(0) === '@' || prop === 'doc' || prop === 'columns' ||
|
|
322
402
|
prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
|
|
323
|
-
if (!
|
|
403
|
+
if (!isAutoItemsOrReturns)
|
|
324
404
|
pushToDict( dict, prop, ext );
|
|
325
405
|
}
|
|
326
|
-
else if (prop === 'elements' || prop === 'enum'
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
406
|
+
else if (prop === 'elements' || prop === 'enum') {
|
|
407
|
+
if (ext.returns) { // TODO: currently, an annotate can have both
|
|
408
|
+
// if this is a hard error, we could use syntax-unexpected-property#sibling
|
|
409
|
+
message( 'syntax-unexpected-with-returns',
|
|
410
|
+
[ ext[prop][$location] || ext.location, ext ],
|
|
411
|
+
{ prop, siblingprop: 'returns' },
|
|
412
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
413
|
+
'Property $(PROP) of an annotate statement is ignored when it also has a property $(SIBLINGPROP)' );
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
pushToDict( dict, 'elements', ext ); // yes, enum → elements here
|
|
417
|
+
if (ext.kind === 'extend' && !isAutoItemsOrReturns)
|
|
418
|
+
pushTo$add( dict, ext );
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else if (prop === 'returns') {
|
|
422
|
+
pushToDict( dict, 'elements', ext );
|
|
423
|
+
// create 'returns' for the super annotate, store in elements anyway
|
|
424
|
+
if (!art.returns && art.kind === 'annotate')
|
|
425
|
+
annotateCreate( art, '', art, 'returns' );
|
|
426
|
+
if (ext.kind === 'extend' && !isAutoItemsOrReturns)
|
|
427
|
+
pushTo$add( dict, ext );
|
|
428
|
+
}
|
|
429
|
+
else if (prop === 'actions' || prop === 'params') {
|
|
330
430
|
pushToDict( dict, prop, ext );
|
|
431
|
+
if (ext.kind === 'extend' && prop === 'actions' && !isAutoItemsOrReturns)
|
|
432
|
+
pushTo$add( dict, ext );
|
|
331
433
|
}
|
|
332
434
|
}
|
|
333
435
|
}
|
|
@@ -360,40 +462,49 @@ function extend( model ) {
|
|
|
360
462
|
setLink( model, '_sortedSources', scheduled );
|
|
361
463
|
}
|
|
362
464
|
|
|
363
|
-
|
|
465
|
+
/**
|
|
466
|
+
* For all `prop → extensions` in `art._extensions`, apply the extensions.
|
|
467
|
+
* Currently only for annotations, the `doc` property, `columns` and type properties,
|
|
468
|
+
* not for `elements` and other members.
|
|
469
|
+
*/
|
|
470
|
+
function applyPropertyExtensions( art, prop ) {
|
|
364
471
|
const extensions = art._extensions;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
let
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
scheduled.push( ext );
|
|
383
|
-
if (extensionOverwrites( ext, prop )) {
|
|
384
|
-
cont = false;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
472
|
+
// annotations, `doc`, `includes`, `columns`, `length`, ...
|
|
473
|
+
const scheduled = [];
|
|
474
|
+
// sort extensions according to layer (specified elements are bottom layer):
|
|
475
|
+
const layered = layeredExtensions( extensions[prop] );
|
|
476
|
+
|
|
477
|
+
let cont = true;
|
|
478
|
+
while (cont) {
|
|
479
|
+
const { highest, issue } = extensionsOfHighestLayers( layered );
|
|
480
|
+
// console.log( 'CA:', annoName, issue, extensions)
|
|
481
|
+
let index = highest.length;
|
|
482
|
+
cont = !!index; // safety
|
|
483
|
+
while (--index >= 0) {
|
|
484
|
+
const ext = highest[index];
|
|
485
|
+
scheduled.push( ext );
|
|
486
|
+
if (extensionOverwrites( ext, prop )) {
|
|
487
|
+
cont = false;
|
|
488
|
+
break;
|
|
387
489
|
}
|
|
388
|
-
if (issue || index > 0)
|
|
389
|
-
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
390
490
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
491
|
+
if (issue || index > 0)
|
|
492
|
+
reportDuplicateExtensions( highest, prop, issue, index, art );
|
|
493
|
+
}
|
|
494
|
+
// Now apply the relevant extensions
|
|
495
|
+
scheduled.reverse();
|
|
496
|
+
if (prop === 'includes' && !art.includes?.$original) {
|
|
497
|
+
const $original = art.includes ?? [];
|
|
498
|
+
art.includes = [ ...$original ];
|
|
499
|
+
art.includes.$original = $original;
|
|
500
|
+
}
|
|
501
|
+
if (prop === '$add') {
|
|
502
|
+
extensions[prop] = scheduled;
|
|
503
|
+
return; // the $add is applied in extendArtifactAdd()
|
|
504
|
+
}
|
|
505
|
+
for (const ext of scheduled)
|
|
506
|
+
applySingleExtension( art, ext, prop );
|
|
507
|
+
delete extensions[prop];
|
|
397
508
|
}
|
|
398
509
|
|
|
399
510
|
function extensionOverwrites( ext, prop ) {
|
|
@@ -405,9 +516,10 @@ function extend( model ) {
|
|
|
405
516
|
// TODO: still a bit annotation assignment specific
|
|
406
517
|
function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
|
|
407
518
|
// TODO: think about messages for these
|
|
408
|
-
if (prop === 'elements' || prop === '
|
|
409
|
-
prop === 'params' || prop === '
|
|
519
|
+
if (prop === 'elements' || prop === 'actions' || prop === 'columns' ||
|
|
520
|
+
prop === 'params' || prop === '$add' )
|
|
410
521
|
return; // extensions currently handled extra
|
|
522
|
+
// TODO: columns?
|
|
411
523
|
if (issue) {
|
|
412
524
|
// eslint-disable-next-line no-nested-ternary
|
|
413
525
|
let msg = (index < 0)
|
|
@@ -421,6 +533,7 @@ function extend( model ) {
|
|
|
421
533
|
: 'ext-duplicate-extend-type-unrelated-layer';
|
|
422
534
|
// not sure whether to repeat the extended artifact in the message (we
|
|
423
535
|
// have the semantic location, after all)
|
|
536
|
+
// extend-repeated-intralayer / extend-unrelated-layer
|
|
424
537
|
}
|
|
425
538
|
const variant = prop === 'doc' ? 'doc' : 'std';
|
|
426
539
|
for (const ext of extensions) {
|
|
@@ -447,14 +560,16 @@ function extend( model ) {
|
|
|
447
560
|
|
|
448
561
|
function applySingleExtension( art, ext, prop ) {
|
|
449
562
|
if (prop === 'includes') {
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
563
|
+
if (art.kind !== 'annotate' && !art._outer) { // TODO: why this check?
|
|
564
|
+
// not with elem extension in targetAspect
|
|
565
|
+
art.includes.push( ...ext.includes );
|
|
566
|
+
// head of ref in in ext.includes must be set in _block of ext; for
|
|
567
|
+
// remaining path items, effectiveType() has `art` as user
|
|
568
|
+
// (alternatively, we could set some _user on `includes` items):
|
|
569
|
+
for (const ref of ext.includes)
|
|
570
|
+
resolveUncheckedPath( ref, 'include', ext );
|
|
571
|
+
if (art.kind === 'entity')
|
|
572
|
+
setEntityIncludes( ext, art );
|
|
458
573
|
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[id])
|
|
459
574
|
}
|
|
460
575
|
// art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
|
|
@@ -476,7 +591,7 @@ function extend( model ) {
|
|
|
476
591
|
query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
|
|
477
592
|
else
|
|
478
593
|
query.columns.push( ...ext.columns );
|
|
479
|
-
|
|
594
|
+
ext.columns.forEach( col => changeParentLinks( col, query ) );
|
|
480
595
|
}
|
|
481
596
|
else if (typeParameters.list.includes( prop )) {
|
|
482
597
|
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
@@ -488,6 +603,24 @@ function extend( model ) {
|
|
|
488
603
|
}
|
|
489
604
|
}
|
|
490
605
|
|
|
606
|
+
function changeParentLinks( art, queryOrMain ) {
|
|
607
|
+
// TODO: we might also change the implicit name (if name.id is a number,
|
|
608
|
+
// adding the previous column lenght - 1) for better error messages
|
|
609
|
+
const parent = art._parent;
|
|
610
|
+
if (!art._parent)
|
|
611
|
+
return;
|
|
612
|
+
if (parent.kind === 'extend')
|
|
613
|
+
art._parent = queryOrMain;
|
|
614
|
+
if (art._main.kind === 'extend') // TODO: probably always
|
|
615
|
+
art._main = queryOrMain._main;
|
|
616
|
+
if (art._columnParent?.kind === 'extend')
|
|
617
|
+
art._columnParent = queryOrMain;
|
|
618
|
+
const subColumns = art.expand || art.inline;
|
|
619
|
+
if (subColumns)
|
|
620
|
+
subColumns.forEach( a => changeParentLinks( a, queryOrMain ) );
|
|
621
|
+
forEachMember( art, a => changeParentLinks( a, queryOrMain ) );
|
|
622
|
+
}
|
|
623
|
+
|
|
491
624
|
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
492
625
|
const firstEllipsis = annotationHasEllipsis( anno );
|
|
493
626
|
if (!firstEllipsis)
|
|
@@ -683,17 +816,16 @@ function extend( model ) {
|
|
|
683
816
|
/**
|
|
684
817
|
* If the target artifact has both precision and scale set, then extensions on it must also
|
|
685
818
|
* provide both to avoid user errors for subsequent `extend` statements.
|
|
819
|
+
* There is already a syntax error [syntax-missing-type-property] for `scale` without `precision`.
|
|
686
820
|
*
|
|
687
821
|
* @param {XSN.Artifact} art
|
|
688
822
|
* @param {object} exts
|
|
689
823
|
*/
|
|
690
824
|
function checkPrecisionScaleExtension( art, exts ) {
|
|
691
825
|
if (art.precision && art.scale) {
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
error( 'ext-missing-type-property', [ exts[prop].location, exts[prop] ],
|
|
696
|
-
{ art, prop, otherprop: missing } );
|
|
826
|
+
if (exts.precision && !exts.scale) {
|
|
827
|
+
error( 'ext-missing-type-property', [ exts.precision.location, exts.precision ],
|
|
828
|
+
{ art, prop: 'precision', otherprop: 'scale' } );
|
|
697
829
|
}
|
|
698
830
|
}
|
|
699
831
|
}
|
|
@@ -711,58 +843,22 @@ function extend( model ) {
|
|
|
711
843
|
if (!extensions)
|
|
712
844
|
return;
|
|
713
845
|
|
|
714
|
-
const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
|
|
715
|
-
|
|
716
846
|
for (const ext of extensions) {
|
|
717
847
|
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
718
|
-
forEachGeneric(ext, extProp, (elemExt, name) => {
|
|
848
|
+
forEachGeneric( ext, extProp, ( elemExt, name ) => {
|
|
719
849
|
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
720
850
|
return; // definitions inside extend, already handled
|
|
721
851
|
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
722
|
-
const elem =
|
|
852
|
+
const elem = art[artProp]?.[name] || annotateFor( art, extProp, name );
|
|
723
853
|
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
|
|
854
|
+
// TODO: why null for annotate?
|
|
724
855
|
ensureArtifactNotProcessed( elem );
|
|
725
|
-
if (elem.$duplicates !== true)
|
|
856
|
+
if (elem.$duplicates !== true) // TODO: re-check
|
|
726
857
|
pushToDict( elem, '_extensions', elemExt );
|
|
727
858
|
});
|
|
728
859
|
}
|
|
729
860
|
}
|
|
730
861
|
|
|
731
|
-
function moveReturnsExtensions( art, extensionsMap ) {
|
|
732
|
-
const extensions = extensionsMap.returns;
|
|
733
|
-
if (!extensions)
|
|
734
|
-
return;
|
|
735
|
-
const artReturns = art.returns;
|
|
736
|
-
let extReturns = artReturns;
|
|
737
|
-
const isAction = art.kind === 'action' || art.kind === 'function';
|
|
738
|
-
|
|
739
|
-
for (const ext of extensions) {
|
|
740
|
-
if (!artReturns && art.kind !== 'annotate') {
|
|
741
|
-
const msgId = ext.returns && hasSecurityAnno( ext.returns )
|
|
742
|
-
? 'ext-unexpected-returns-sec'
|
|
743
|
-
: 'ext-unexpected-returns';
|
|
744
|
-
message( msgId, [ ext.returns.location, ext ], {
|
|
745
|
-
'#': (isAction ? art.kind : 'std'), keyword: 'returns',
|
|
746
|
-
}, {
|
|
747
|
-
std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
|
|
748
|
-
action: 'Unexpected $(KEYWORD) for action without return parameter',
|
|
749
|
-
// function without `returns` can happen via CSN input! TODO: check in parser
|
|
750
|
-
function: 'Unexpected $(KEYWORD) for function without return parameter',
|
|
751
|
-
} );
|
|
752
|
-
// Do not put completely wrong returns into a “super annotate” statement;
|
|
753
|
-
// this could induce consequential errors with [..., …]:
|
|
754
|
-
if (!isAction)
|
|
755
|
-
continue; // do not put into 'extensions'
|
|
756
|
-
// add to 'extensions' for action/function without returns:
|
|
757
|
-
extReturns ??= annotateFor( art, 'params', '' );
|
|
758
|
-
}
|
|
759
|
-
if (extReturns) {
|
|
760
|
-
setLink( ext.name, '_artifact', (isAction ? artReturns : null ) );
|
|
761
|
-
pushToDict( extReturns, '_extensions', ext.returns );
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
862
|
function annotateFor( art, prop, name ) {
|
|
767
863
|
const base = annotateBase( art );
|
|
768
864
|
if (name === '' && prop === 'params')
|
|
@@ -805,16 +901,33 @@ function extend( model ) {
|
|
|
805
901
|
return annotate;
|
|
806
902
|
}
|
|
807
903
|
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
904
|
+
function checkReturnsExtension( ext, art ) {
|
|
905
|
+
const msgId = hasSecurityAnno( ext.returns )
|
|
906
|
+
? 'ext-unexpected-returns-sec'
|
|
907
|
+
: 'ext-unexpected-returns';
|
|
908
|
+
message( msgId, [ ext.returns.location, ext ],
|
|
909
|
+
{ '#': art.kind, keyword: 'returns' },
|
|
910
|
+
{
|
|
911
|
+
std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
|
|
912
|
+
action: 'Unexpected $(KEYWORD) for action without return parameter',
|
|
913
|
+
// function without `returns` can happen via CSN input! TODO: check in parser
|
|
914
|
+
function: 'Unexpected $(KEYWORD) for function without return parameter',
|
|
915
|
+
} );
|
|
916
|
+
// Do not put completely wrong returns into a “super annotate” statement;
|
|
917
|
+
// this could induce consequential errors with [..., …]:
|
|
918
|
+
return art.kind === 'action' || art.kind === 'function';
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function extendHandleReturns( ext, art ) {
|
|
922
|
+
warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
|
|
923
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
924
|
+
}, {
|
|
925
|
+
std: 'Expected $(CODE)', // unused variant
|
|
926
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
927
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
928
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
929
|
+
annotate: 'Expected $(KEYWORD) when annotating a return structure of an unknown action or function, i.e. $(CODE)',
|
|
930
|
+
} );
|
|
818
931
|
}
|
|
819
932
|
|
|
820
933
|
function checkRemainingMemberExtensions( parent, ext, prop, name ) {
|
|
@@ -915,7 +1028,7 @@ function extend( model ) {
|
|
|
915
1028
|
if (extensions && !annotate._main) {
|
|
916
1029
|
const art = model.definitions[annotate.name.id];
|
|
917
1030
|
for (const ext of extensions)
|
|
918
|
-
checkRemainingMainExtensions( art, ext );
|
|
1031
|
+
checkRemainingMainExtensions( art, ext ); // induce messages for extension path
|
|
919
1032
|
if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
|
|
920
1033
|
setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
|
|
921
1034
|
// direct annotations on builtins or on the builtins for propagation, and
|
|
@@ -991,28 +1104,22 @@ function extend( model ) {
|
|
|
991
1104
|
|
|
992
1105
|
// extend, mainly old-style ---------------------------------------------------
|
|
993
1106
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
for (const name of extNames) {
|
|
1007
|
-
const art = model.definitions[name];
|
|
1008
|
-
if (art && art.kind !== 'namespace' &&
|
|
1009
|
-
extendArtifact( extensionsDict[name], art, cyclicIncludeNames ))
|
|
1010
|
-
delete extensionsDict[name];
|
|
1011
|
-
}
|
|
1012
|
-
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
1013
|
-
if (extNames.length >= length)
|
|
1014
|
-
cyclicIncludeNames = Object.keys( extensionsDict ); // = no includes
|
|
1107
|
+
function extendArtifactAdd( art ) {
|
|
1108
|
+
const { includes } = art;
|
|
1109
|
+
if (includes) {
|
|
1110
|
+
if (includes.$original) // if extensions with includes:
|
|
1111
|
+
art.includes = includes.$original; // original includes have been stored
|
|
1112
|
+
if (art.includes?.length)
|
|
1113
|
+
applyIncludes( art, art );
|
|
1114
|
+
art.includes = includes;
|
|
1115
|
+
// early propagation of specific annotation assignments
|
|
1116
|
+
// TODO: propagate in effectiveType() ?
|
|
1117
|
+
propagateEarly( art, '@cds.autoexpose' );
|
|
1118
|
+
propagateEarly( art, '@fiori.draft.enabled' );
|
|
1015
1119
|
}
|
|
1120
|
+
if (art._extensions?.$add)
|
|
1121
|
+
extendArtifact( art._extensions.$add, art );
|
|
1122
|
+
// TODO: for proper shadowing: first collect defined element & action names
|
|
1016
1123
|
}
|
|
1017
1124
|
|
|
1018
1125
|
/**
|
|
@@ -1028,40 +1135,13 @@ function extend( model ) {
|
|
|
1028
1135
|
* @param {XSN.Definition} art
|
|
1029
1136
|
* @param {String[]|false} [cyclicIncludeNames=false]
|
|
1030
1137
|
*/
|
|
1031
|
-
function extendArtifact( extensions, art
|
|
1032
|
-
if (!
|
|
1033
|
-
extensions.every( ext => canApplyIncludes( ext, art ) )))
|
|
1034
|
-
return false;
|
|
1035
|
-
if (cyclicIncludeNames) {
|
|
1036
|
-
canApplyIncludes( art, art, cyclicIncludeNames );
|
|
1037
|
-
extensions.forEach( ext => canApplyIncludes( ext, art, cyclicIncludeNames ) );
|
|
1038
|
-
}
|
|
1039
|
-
else if (!(canApplyIncludes( art, art ) &&
|
|
1040
|
-
extensions.every( ext => canApplyIncludes( ext, art ) ))) {
|
|
1041
|
-
// console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
|
|
1042
|
-
return false;
|
|
1043
|
-
}
|
|
1044
|
-
if (!art.query) {
|
|
1138
|
+
function extendArtifact( extensions, art ) {
|
|
1139
|
+
if (!art.query && !art._main && !art._outer) { // TODO: remove _entities
|
|
1045
1140
|
model._entities.push( art ); // add structure with includes in dep order
|
|
1046
|
-
art.$entity = ++model.$entity;
|
|
1047
|
-
}
|
|
1048
|
-
if (art.includes) {
|
|
1049
|
-
if (!cyclicIncludeNames) {
|
|
1050
|
-
applyIncludes( art, art );
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
// resolve artifacts to induce errors: either ref-invalid-include or ref-cyclic
|
|
1054
|
-
for (const ref of art.includes)
|
|
1055
|
-
resolvePath( ref, 'include', art );
|
|
1056
|
-
}
|
|
1057
1141
|
}
|
|
1142
|
+
// TODO: complain if $inferred
|
|
1058
1143
|
// checkExtensionsKind( extensions, art );
|
|
1059
1144
|
extendMembers( extensions, art );
|
|
1060
|
-
if (!cyclicIncludeNames && art.includes) {
|
|
1061
|
-
// early propagation of specific annotation assignments
|
|
1062
|
-
propagateEarly( art, '@cds.autoexpose' );
|
|
1063
|
-
propagateEarly( art, '@fiori.draft.enabled' );
|
|
1064
|
-
}
|
|
1065
1145
|
// TODO: complain about element extensions inside projection
|
|
1066
1146
|
return true;
|
|
1067
1147
|
}
|
|
@@ -1069,32 +1149,29 @@ function extend( model ) {
|
|
|
1069
1149
|
function extendMembers( extensions, art ) {
|
|
1070
1150
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
1071
1151
|
const elemExtensions = [];
|
|
1072
|
-
if (art._main) // extensions already sorted for main artifacts
|
|
1073
|
-
|
|
1152
|
+
// if (art._main) // extensions already sorted for main artifacts
|
|
1153
|
+
// extensions.sort( layers.compareLayer );
|
|
1074
1154
|
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
1075
1155
|
// console.log('EM:',art.name,extensions,art._extensions)
|
|
1076
1156
|
for (const ext of extensions) { // those in extMap.includes
|
|
1157
|
+
if (art.$inferred) {
|
|
1158
|
+
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
|
|
1159
|
+
'You can\'t use $(KEYWORD) on the generated $(ART)' ); // or with inferred elements
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
|
|
1077
1163
|
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
1078
1164
|
// 'Info', 'EXT').toString())
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
art.includes = [ ...ext.includes ];
|
|
1090
|
-
applyIncludes( ext, art );
|
|
1091
|
-
}
|
|
1092
|
-
// console.log(ext,art)
|
|
1093
|
-
checkAnnotate( ext, art );
|
|
1094
|
-
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
1095
|
-
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
1096
|
-
dependsOnSilent( art, ext ); // art depends silently on ext (inverse to normal dep!)
|
|
1097
|
-
}
|
|
1165
|
+
setArtifactLink( ext.name, art ); // TODO: probably already done
|
|
1166
|
+
if (ext.includes)
|
|
1167
|
+
applyIncludes( ext, art );
|
|
1168
|
+
|
|
1169
|
+
// console.log(ext,art)
|
|
1170
|
+
checkAnnotate( ext, art );
|
|
1171
|
+
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
1172
|
+
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
1173
|
+
dependsOnSilent( art, ext ); // art depends silently on ext (inverse to normal dep!)
|
|
1174
|
+
|
|
1098
1175
|
for (const name in ext.elements) {
|
|
1099
1176
|
const elem = ext.elements[name];
|
|
1100
1177
|
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
@@ -1104,7 +1181,7 @@ function extend( model ) {
|
|
|
1104
1181
|
}
|
|
1105
1182
|
}
|
|
1106
1183
|
if (elemExtensions.length > 1)
|
|
1107
|
-
reportUnstableExtensions( elemExtensions );
|
|
1184
|
+
reportUnstableExtensions( elemExtensions ); // TODO: still?
|
|
1108
1185
|
|
|
1109
1186
|
// This whole function will be removed with a next change - no need to have nice code here:
|
|
1110
1187
|
const dict = Object.create( null );
|
|
@@ -1128,8 +1205,6 @@ function extend( model ) {
|
|
|
1128
1205
|
const member = validDict && validDict[name];
|
|
1129
1206
|
if (!member)
|
|
1130
1207
|
extendNothing( dict[name], 'elements', name, art, validDict );
|
|
1131
|
-
else if (!(member.$duplicates))
|
|
1132
|
-
extendMembers( dict[name], member );
|
|
1133
1208
|
}
|
|
1134
1209
|
}
|
|
1135
1210
|
|
|
@@ -1217,23 +1292,10 @@ function extend( model ) {
|
|
|
1217
1292
|
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
1218
1293
|
let obj = initItemsLinks( construct, block );
|
|
1219
1294
|
initExprAnnoBlock( construct, block );
|
|
1220
|
-
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1221
|
-
obj.targetAspect = obj.target;
|
|
1222
|
-
delete obj.target;
|
|
1223
|
-
}
|
|
1224
1295
|
const { targetAspect } = obj;
|
|
1225
|
-
if (targetAspect)
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1229
|
-
}
|
|
1230
|
-
if (obj.on && !obj.target) {
|
|
1231
|
-
error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
|
|
1232
|
-
delete obj.on; // continuation semantics: not specified
|
|
1233
|
-
}
|
|
1234
|
-
if (targetAspect.elements)
|
|
1235
|
-
initAnonymousAspect();
|
|
1236
|
-
}
|
|
1296
|
+
if (targetAspect?.elements)
|
|
1297
|
+
initAnonymousAspect();
|
|
1298
|
+
|
|
1237
1299
|
if (obj !== parent && obj.elements && parent.enum) { // applying the extension
|
|
1238
1300
|
initElementsAsEnum();
|
|
1239
1301
|
}
|
|
@@ -1258,6 +1320,8 @@ function extend( model ) {
|
|
|
1258
1320
|
}
|
|
1259
1321
|
return;
|
|
1260
1322
|
|
|
1323
|
+
// We allow `extend MyEnum with {…}` and not just `extend MyEnum with enum {…}`
|
|
1324
|
+
// for compatibility, with a warning.
|
|
1261
1325
|
function initElementsAsEnum() {
|
|
1262
1326
|
// in extensions, extended enums are represented as elements
|
|
1263
1327
|
let hasElement = false;
|
|
@@ -1265,11 +1329,7 @@ function extend( model ) {
|
|
|
1265
1329
|
const e = obj.elements[n];
|
|
1266
1330
|
if (e.kind === 'extend')
|
|
1267
1331
|
continue;
|
|
1268
|
-
|
|
1269
|
-
// TODO: forbid #symbol as enum value
|
|
1270
|
-
if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
|
|
1271
|
-
noVal && e.$syntax !== 'enum' || // no value in CDL input
|
|
1272
|
-
e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
|
|
1332
|
+
if (e.$syntax !== 'or-enum') { // parser might set $syntax: 'or-enum'
|
|
1273
1333
|
// We do not want to complain separately about all element properties:
|
|
1274
1334
|
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1275
1335
|
{ name: e.name.id, code: 'extend … with enum' },
|
|
@@ -1279,13 +1339,9 @@ function extend( model ) {
|
|
|
1279
1339
|
return;
|
|
1280
1340
|
}
|
|
1281
1341
|
e.kind = 'enum';
|
|
1282
|
-
|
|
1283
|
-
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1342
|
+
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1284
1343
|
}
|
|
1285
1344
|
if (hasElement) {
|
|
1286
|
-
// This message is similar to the one above. In v6, we could probably
|
|
1287
|
-
// turn this warning into an error, remove `$syntax: 'element',
|
|
1288
|
-
// and use the above `ext-unexpected-element` only for CSN input.
|
|
1289
1345
|
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1290
1346
|
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1291
1347
|
}
|
|
@@ -1329,8 +1385,6 @@ function extend( model ) {
|
|
|
1329
1385
|
}
|
|
1330
1386
|
|
|
1331
1387
|
function init( elem, name, prop ) {
|
|
1332
|
-
if (!elem.kind) // wrong CSN input
|
|
1333
|
-
elem.kind = dictKinds[prop];
|
|
1334
1388
|
if (!elem.name && !elem._outer) {
|
|
1335
1389
|
const ref = elem.targetElement || elem.kind === 'element' && elem.value;
|
|
1336
1390
|
if (ref && ref.path) {
|
|
@@ -1345,10 +1399,9 @@ function extend( model ) {
|
|
|
1345
1399
|
if (!elem._block)
|
|
1346
1400
|
setLink( elem, '_block', block );
|
|
1347
1401
|
// if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
|
|
1348
|
-
if ((elem.kind === 'extend' || elem.kind === 'annotate'))
|
|
1349
|
-
storeExtension( elem, name, prop, parent );
|
|
1402
|
+
if ((elem.kind === 'extend' || elem.kind === 'annotate'))
|
|
1350
1403
|
return;
|
|
1351
|
-
|
|
1404
|
+
|
|
1352
1405
|
if (isQueryExtension && elem.kind === 'element') {
|
|
1353
1406
|
error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
|
|
1354
1407
|
{ code: 'extend projection' },
|
|
@@ -1359,19 +1412,21 @@ function extend( model ) {
|
|
|
1359
1412
|
const existing = parent[prop]?.[name];
|
|
1360
1413
|
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1361
1414
|
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
1362
|
-
|
|
1415
|
+
const { $duplicates } = elem;
|
|
1416
|
+
if ($duplicates === true && add)
|
|
1363
1417
|
elem.$duplicates = null;
|
|
1364
1418
|
setMemberParent( elem, name, parent, add && prop );
|
|
1365
|
-
|
|
1419
|
+
if (!$duplicates) // not already reported
|
|
1420
|
+
checkRedefinition( elem );
|
|
1366
1421
|
initMembers( elem, elem, elem._block );
|
|
1367
1422
|
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1368
1423
|
initBoundSelfParam( elem.params, elem._main );
|
|
1369
1424
|
|
|
1370
1425
|
// for a correct home path, setMemberParent needed to be called
|
|
1371
1426
|
|
|
1372
|
-
if (!elem.value || elem.kind !== 'element'
|
|
1373
|
-
elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
|
|
1427
|
+
if (!elem.value || elem.kind !== 'element')
|
|
1374
1428
|
return;
|
|
1429
|
+
// remark: potential enum elements have already been turned into enums
|
|
1375
1430
|
// -> it's a calculated element
|
|
1376
1431
|
if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
|
|
1377
1432
|
if (!elem.target)
|
|
@@ -1469,47 +1524,13 @@ function extend( model ) {
|
|
|
1469
1524
|
|
|
1470
1525
|
// includes ----------------------------------------------------------------
|
|
1471
1526
|
|
|
1472
|
-
/**
|
|
1473
|
-
* Returns true, if `art.includes` can be applied on `target`.
|
|
1474
|
-
* They can't be applied if any of the artifacts referenced in
|
|
1475
|
-
* `art.includes` are yet to be extended.
|
|
1476
|
-
* `art !== target` if `art` is an extension.
|
|
1477
|
-
*
|
|
1478
|
-
* @param {XSN.Definition} art
|
|
1479
|
-
* @param {XSN.Artifact} target
|
|
1480
|
-
* @param {string[]} [cyclicIncludeNames]
|
|
1481
|
-
* @returns {boolean}
|
|
1482
|
-
*/
|
|
1483
|
-
function canApplyIncludes( art, target, cyclicIncludeNames ) {
|
|
1484
|
-
if (!art.includes)
|
|
1485
|
-
return true;
|
|
1486
|
-
for (const ref of art.includes) {
|
|
1487
|
-
const name = resolveUncheckedPath( ref, 'include', art );
|
|
1488
|
-
// console.log('CAI:',cyclicIncludeNames, name, ref.path, Object.keys(extensionsDict))
|
|
1489
|
-
if (cyclicIncludeNames) {
|
|
1490
|
-
if (!cyclicIncludeNames.includes( name ))
|
|
1491
|
-
continue;
|
|
1492
|
-
delete ref._artifact;
|
|
1493
|
-
}
|
|
1494
|
-
else if (name && name in extensionsDict) {
|
|
1495
|
-
// one of the includes has itself extensions that need to be applied first
|
|
1496
|
-
return false;
|
|
1497
|
-
}
|
|
1498
|
-
else if (ref._artifact) {
|
|
1499
|
-
delete ref._artifact;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
return true;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
1527
|
/**
|
|
1506
1528
|
* Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
|
|
1507
1529
|
* If `ext === art`, then includes of the artifact itself are applied.
|
|
1508
1530
|
* If `ext !== art`, applies includes on the extensions, not artifact.
|
|
1509
|
-
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
* non-entities - TODO: make intermediate non-entities stop chain).
|
|
1531
|
+
|
|
1532
|
+
* Sets `_ancestors` links on `art` if it is an entity, collecting the entity
|
|
1533
|
+
* includes with their ancestors.
|
|
1513
1534
|
*
|
|
1514
1535
|
* Examples:
|
|
1515
1536
|
* ext === art: `entity E : F {}` => add elements of F to E
|
|
@@ -1518,25 +1539,24 @@ function extend( model ) {
|
|
|
1518
1539
|
* @param {XSN.Extension} ext
|
|
1519
1540
|
* @param {XSN.Artifact} art
|
|
1520
1541
|
*/
|
|
1542
|
+
function setEntityIncludes( ext, art ) {
|
|
1543
|
+
setLink( art, '_entityIncludes', [] );
|
|
1544
|
+
for (const ref of ext.includes) {
|
|
1545
|
+
const name = resolveUncheckedPath( ref, 'include', ext );
|
|
1546
|
+
const template = name && model.definitions[name];
|
|
1547
|
+
if (template?.kind === 'entity')
|
|
1548
|
+
art._entityIncludes.push( template );
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1521
1552
|
function applyIncludes( ext, art ) {
|
|
1522
1553
|
if (kindProperties[art.kind].include !== true) {
|
|
1523
1554
|
error( 'extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
1524
1555
|
{ meta: art.kind } );
|
|
1525
1556
|
return;
|
|
1526
1557
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
1530
|
-
const shouldSetAncestors = art.kind === 'entity' && !art.query;
|
|
1531
|
-
for (const ref of ext.includes) {
|
|
1532
|
-
const template = resolvePath( ref, 'include', art );
|
|
1533
|
-
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
1534
|
-
if (template?.kind === 'entity' && shouldSetAncestors) {
|
|
1535
|
-
if (template._ancestors)
|
|
1536
|
-
art._ancestors.push( ...template._ancestors );
|
|
1537
|
-
art._ancestors.push( template );
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1558
|
+
// console.log( (ext===art?'D-INCL:':'E-INCL:'), art.kind, art.name.id,
|
|
1559
|
+
// ...ext.includes.map( r => r._artifact?.name.id ));
|
|
1540
1560
|
if (!art.query && art.elements) // do not set art.elements and art.enums with query entity!
|
|
1541
1561
|
includeMembers( ext, art, 'elements' );
|
|
1542
1562
|
if (art.kind !== 'type') {
|
|
@@ -1567,6 +1587,7 @@ function extend( model ) {
|
|
|
1567
1587
|
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
1568
1588
|
const parent = ext === art && art;
|
|
1569
1589
|
const members = ext[prop];
|
|
1590
|
+
// if (members)console.log( 'EXT:', prop, art.kind, art.name.id, ...Object.keys(members));
|
|
1570
1591
|
if (members) {
|
|
1571
1592
|
ext[prop] = Object.create( null );
|
|
1572
1593
|
ext[prop][$location] = members[$location];
|
|
@@ -1609,7 +1630,8 @@ function extend( model ) {
|
|
|
1609
1630
|
// TODO: If paths become invalid in the new artifact, should we mark
|
|
1610
1631
|
// all usages in the expressions? Possibly just the first one?
|
|
1611
1632
|
// TODO: Unify with other code in extend.js
|
|
1612
|
-
elem.value = Object.assign( { $inferred: 'include' },
|
|
1633
|
+
elem.value = Object.assign( { $inferred: 'include' },
|
|
1634
|
+
copyExpr( origin.value, location, true ) );
|
|
1613
1635
|
elem.$syntax = 'calc';
|
|
1614
1636
|
createAndLinkCalcDepElement( elem );
|
|
1615
1637
|
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
@@ -1640,17 +1662,21 @@ function extend( model ) {
|
|
|
1640
1662
|
* contain elements `texts` and `localized` which are compiler-generated for
|
|
1641
1663
|
* localized data. Due to recompilation, we cannot just check the `$inferred`
|
|
1642
1664
|
* property in the XSN, but need to apply an heuristics.
|
|
1665
|
+
* (We could still use $inferred as speed-up if run without `testMode`.)
|
|
1643
1666
|
*
|
|
1644
1667
|
* It is as follows: the elements `texts` and `localized` in entity `‹E›` and
|
|
1645
1668
|
* the entity `‹E›.texts` are considered compiler-generated for Localized Data if
|
|
1646
1669
|
*
|
|
1647
1670
|
* - `‹E›.texts` has a key element `locale`
|
|
1671
|
+
* (TODO: currently weakend to “entity exists”)
|
|
1648
1672
|
* - `texts` of `‹E›` is an unmanaged composition to `‹E›.texts`
|
|
1649
1673
|
* - `localized` of `‹E›` is an unmanaged association to `‹E›.texts`
|
|
1650
1674
|
*/
|
|
1651
1675
|
function withLocalizedData( { name, elements }, ext ) {
|
|
1652
1676
|
const textsEntityName = `${ name.id }.texts`;
|
|
1653
|
-
if (
|
|
1677
|
+
// if we test (again) for element `locale` - it might come from an extend or
|
|
1678
|
+
// TextsAspect include, which might not have yet applied
|
|
1679
|
+
if (!model.definitions[textsEntityName])
|
|
1654
1680
|
return false;
|
|
1655
1681
|
const { texts, localized } = elements ?? {};
|
|
1656
1682
|
return texts?.target && localized?.target && texts.on && localized.on &&
|
|
@@ -1791,5 +1817,11 @@ function propagateEarly( art, prop ) {
|
|
|
1791
1817
|
}
|
|
1792
1818
|
}
|
|
1793
1819
|
|
|
1820
|
+
function pushTo$add( extensions, ext ) {
|
|
1821
|
+
if (!extensions.$add)
|
|
1822
|
+
extensions.$add = [ ext ];
|
|
1823
|
+
else if (extensions.$add.at( -1 ) !== ext)
|
|
1824
|
+
extensions.$add.push( ext );
|
|
1825
|
+
}
|
|
1794
1826
|
|
|
1795
1827
|
module.exports = extend;
|