@sap/cds-compiler 6.5.2 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/lib/api/main.js +0 -3
- package/lib/base/builtins.js +2 -1
- package/lib/base/message-registry.js +0 -1
- package/lib/base/model.js +0 -1
- package/lib/checks/sql-snippets.js +8 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +54 -22
- package/lib/compiler/generate.js +31 -8
- package/lib/compiler/index.js +2 -2
- package/lib/compiler/kick-start.js +18 -28
- package/lib/compiler/propagator.js +20 -1
- package/lib/compiler/resolve.js +24 -0
- package/lib/compiler/shared.js +49 -9
- package/lib/compiler/utils.js +1 -0
- package/lib/edm/annotations/edmJson.js +111 -37
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/main.d.ts +0 -3
- package/lib/main.js +2 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/associations.js +24 -15
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/views.js +0 -39
- package/lib/transform/effective/associations.js +5 -55
- package/lib/transform/effective/main.js +4 -2
- package/lib/transform/effective/misc.js +1 -1
- package/lib/transform/effective/types.js +36 -12
- package/lib/transform/forOdata.js +126 -3
- package/lib/transform/forRelationalDB.js +13 -4
- package/lib/transform/transformUtils.js +51 -1
- package/lib/transform/translateAssocsToJoins.js +43 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,32 @@ we might not list every change in its behavior here.
|
|
|
13
13
|
Productive code should never require a `beta` flag to be set, and
|
|
14
14
|
might use a deprecated flag only for a limited period of time.
|
|
15
15
|
|
|
16
|
+
## Version 6.6.0 - 2025-12-12
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- compiler:
|
|
21
|
+
+ Support for upcoming ESlint rules (by other team) for Fiori elements annotations.
|
|
22
|
+
+ Namespace `cds.dataproducts` is no longer reserved by the cds-compiler. It is used by the CAP @sap/cds-data-products plugin.
|
|
23
|
+
- for.odata/to.edm(x):
|
|
24
|
+
+ Enumeration symbols are now supported in annotation expression syntax.
|
|
25
|
+
+ For projections and views, the `@hierarchy` annotation now triggers generation of
|
|
26
|
+
additional Fiori Tree View relevant annotations and fields.
|
|
27
|
+
- for.effective: First non-beta release.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- `to.sql`: Annotating a foreign key of an association in a view with a sql-snippet annotation (e.g. `@sql.append`)
|
|
32
|
+
now results in an error. This is the default behaviour for any element in a view.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- compiler:
|
|
37
|
+
+ Minor fixes for auto-redirections and recompilation with localized data
|
|
38
|
+
in very rare situations when an aspect definition uses an entity as include.
|
|
39
|
+
+ Don't let “namespaces” prevent the compiler to generate texts/target entities.
|
|
40
|
+
- to.sql: Improve foreign key flattening for various edge cases.
|
|
41
|
+
|
|
16
42
|
## Version 6.5.2 - 2025-12-02
|
|
17
43
|
|
|
18
44
|
### Fixed
|
package/lib/api/main.js
CHANGED
|
@@ -321,9 +321,6 @@ function forSeal( csn, options, messageFunctions ) {
|
|
|
321
321
|
function forEffective( csn, options, messageFunctions ) {
|
|
322
322
|
const internalOptions = prepareOptions.for.effective(options);
|
|
323
323
|
internalOptions.transformation = 'effective';
|
|
324
|
-
// for.effective is still beta mode
|
|
325
|
-
if (!baseModel.isBetaEnabled(options, 'effectiveCsn'))
|
|
326
|
-
throw new baseError.CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
|
|
327
324
|
|
|
328
325
|
return forEffectiveInternal(csn, options, internalOptions, messageFunctions);
|
|
329
326
|
}
|
package/lib/base/builtins.js
CHANGED
|
@@ -50,7 +50,8 @@ function isInReservedNamespace( absolute ) {
|
|
|
50
50
|
!absolute.match( /^cds\.foundation(\.|$)/ ) &&
|
|
51
51
|
!absolute.match( /^cds\.outbox(\.|$)/ ) && // Requested by Node runtime
|
|
52
52
|
!absolute.match( /^cds\.core(\.|$)/ ) && // Requested by Node runtime
|
|
53
|
-
!absolute.match( /^cds\.xt(\.|$)/ )
|
|
53
|
+
!absolute.match( /^cds\.xt(\.|$)/ ) && // Requested by Mtx
|
|
54
|
+
!absolute.match( /^cds\.dataproducts(\.|$)/ ); // Requested by @sap/cds-data-products
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -735,7 +735,6 @@ const centralMessageTexts = {
|
|
|
735
735
|
},
|
|
736
736
|
'ref-undefined-art': {
|
|
737
737
|
std: 'No artifact has been found with name $(ART)',
|
|
738
|
-
namespace: 'No artifact has been found with name $(ART) which can be extended with annotations',
|
|
739
738
|
localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
|
|
740
739
|
},
|
|
741
740
|
// TODO: proposal 'No definition found for $(NAME)',
|
package/lib/base/model.js
CHANGED
|
@@ -38,6 +38,14 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
|
|
|
38
38
|
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
// recursive check for keys
|
|
43
|
+
if (member.keys) {
|
|
44
|
+
for (const keyName of Object.keys(member.keys)) {
|
|
45
|
+
const key = member.keys[keyName];
|
|
46
|
+
checkSqlAnnotationOnElement.call(this, key, keyName, prop, path.concat([ 'keys', keyName ]));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
/**
|
package/lib/checks/validator.js
CHANGED
|
@@ -93,7 +93,6 @@ const forRelationalDBCsnValidators = [
|
|
|
93
93
|
navigationIntoMany,
|
|
94
94
|
checkPathsInStoredCalcElement,
|
|
95
95
|
featureFlags,
|
|
96
|
-
checkAndRemoveEnums,
|
|
97
96
|
];
|
|
98
97
|
/**
|
|
99
98
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
@@ -200,8 +199,11 @@ function _validate( csn, that,
|
|
|
200
199
|
function getDBCsnValidators( options ) {
|
|
201
200
|
const validations = [ ...forRelationalDBCsnValidators ];
|
|
202
201
|
|
|
203
|
-
if (options.transformation !== 'effective')
|
|
202
|
+
if (options.transformation !== 'effective') {
|
|
203
|
+
validations.push(checkAndRemoveEnums);
|
|
204
204
|
validations.push(checkForParams.csnValidator);
|
|
205
|
+
}
|
|
206
|
+
|
|
205
207
|
if (options.sqlDialect === 'h2' || options.sqlDialect === 'postgres')
|
|
206
208
|
validations.push(checkForHanaTypes);
|
|
207
209
|
|
package/lib/compiler/define.js
CHANGED
|
@@ -528,7 +528,7 @@ function define( model ) {
|
|
|
528
528
|
if (!reInit) // not for auto-exposed entity
|
|
529
529
|
initArtifactParentLink( art, model.definitions );
|
|
530
530
|
checkRedefinition( art );
|
|
531
|
-
initDollarSelf( art ); // $self
|
|
531
|
+
initDollarSelf( art ); // $self, TODO: also for 'namespace'?
|
|
532
532
|
initMembers( art );
|
|
533
533
|
if (art.params)
|
|
534
534
|
initDollarParameters( art ); // $parameters
|
package/lib/compiler/extend.js
CHANGED
|
@@ -78,6 +78,7 @@ function extend( model ) {
|
|
|
78
78
|
extendArtifactBefore,
|
|
79
79
|
extendArtifactAfter,
|
|
80
80
|
extendForeignKeys,
|
|
81
|
+
withLocalizedData,
|
|
81
82
|
applyIncludes, // TODO: re-check
|
|
82
83
|
} );
|
|
83
84
|
|
|
@@ -940,32 +941,28 @@ function extend( model ) {
|
|
|
940
941
|
}
|
|
941
942
|
|
|
942
943
|
function checkRemainingMainExtensions( art, ext ) {
|
|
943
|
-
const refCtx =
|
|
944
|
-
? 'annotate-sec'
|
|
945
|
-
: ext.kind;
|
|
944
|
+
const refCtx = extensionRefContext( ext );
|
|
946
945
|
if (!resolvePath( ext.name, refCtx, ext )) // error for extend, info for annotate
|
|
947
946
|
return;
|
|
948
947
|
|
|
949
|
-
if (art?.builtin) {
|
|
948
|
+
if (art?.builtin) { // TODO: do via accept
|
|
950
949
|
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
951
950
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
// Non-artifact extensions are reported in resolvePath() already (for v5).
|
|
959
|
-
// Because "namespaces" are the same as "unknown" artifacts in CSN, we don't report
|
|
960
|
-
// an error for `annotate`s.
|
|
961
|
-
// FIXME: The compiler generates empty `annotate` statements for
|
|
962
|
-
// `extend ns with definitions {…}`. That's why we check the frontend.
|
|
963
|
-
if (hasAnnotations || (!ext.artifacts && ext._block.$frontend !== 'json')) {
|
|
964
|
-
error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
|
|
965
|
-
'#': 'namespace', art: ext,
|
|
966
|
-
} );
|
|
967
|
-
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function extensionRefContext( ext ) {
|
|
954
|
+
if (ext.kind === 'annotate') {
|
|
955
|
+
if (hasSecurityAnno( ext ))
|
|
956
|
+
return 'annotate-sec';
|
|
968
957
|
}
|
|
958
|
+
else if (ext.artifacts || // extend … with definitions
|
|
959
|
+
ext._block.$frontend === 'json' && !ext.elements && !ext.actions) {
|
|
960
|
+
// TODO: not for `extend context` and `extend service` → !ext.expectedKind
|
|
961
|
+
// TODO v7: also fully with CSN input
|
|
962
|
+
if (!ext.doc && !Object.keys( ext ).some( a => a.charAt(0) === '@') )
|
|
963
|
+
return 'annotate'; // TODO: or an extra refCtx ?
|
|
964
|
+
}
|
|
965
|
+
return ext.kind;
|
|
969
966
|
}
|
|
970
967
|
|
|
971
968
|
// Issue messages for annotations on namespaces and builtins
|
|
@@ -1530,10 +1527,11 @@ function extend( model ) {
|
|
|
1530
1527
|
|
|
1531
1528
|
if (!art._ancestors && !art.query)
|
|
1532
1529
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
1530
|
+
const shouldSetAncestors = art.kind === 'entity' && !art.query;
|
|
1533
1531
|
for (const ref of ext.includes) {
|
|
1534
1532
|
const template = resolvePath( ref, 'include', art );
|
|
1535
1533
|
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
1536
|
-
if (template &&
|
|
1534
|
+
if (template?.kind === 'entity' && shouldSetAncestors) {
|
|
1537
1535
|
if (template._ancestors)
|
|
1538
1536
|
art._ancestors.push( ...template._ancestors );
|
|
1539
1537
|
art._ancestors.push( template );
|
|
@@ -1581,8 +1579,17 @@ function extend( model ) {
|
|
|
1581
1579
|
if (template[prop] && !ext[prop])
|
|
1582
1580
|
ext[prop] = Object.create( null );
|
|
1583
1581
|
const location = weakRefLocation( ref );
|
|
1582
|
+
|
|
1583
|
+
// prevent recompilation issue when localized entity is included in aspect or
|
|
1584
|
+
// entity without keys (via shadowing):
|
|
1585
|
+
const checkForLocalized = prop === 'elements' &&
|
|
1586
|
+
template.kind === 'entity' && !template.query &&
|
|
1587
|
+
withLocalizedData( template, ext );
|
|
1588
|
+
|
|
1584
1589
|
// eslint-disable-next-line no-loop-func
|
|
1585
1590
|
forEachInOrder( template, prop, ( origin, name ) => {
|
|
1591
|
+
if (checkForLocalized && (name === 'texts' || name === 'localized'))
|
|
1592
|
+
return;
|
|
1586
1593
|
if (members && members[name]) {
|
|
1587
1594
|
if (!includesNonShadowedFirst && !ext[prop][name])
|
|
1588
1595
|
dictAdd( ext[prop], name, members[name] ); // to keep order
|
|
@@ -1601,7 +1608,7 @@ function extend( model ) {
|
|
|
1601
1608
|
if (origin.value && origin.$syntax === 'calc') {
|
|
1602
1609
|
// TODO: If paths become invalid in the new artifact, should we mark
|
|
1603
1610
|
// all usages in the expressions? Possibly just the first one?
|
|
1604
|
-
// TODO: Unify with
|
|
1611
|
+
// TODO: Unify with other code in extend.js
|
|
1605
1612
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
1606
1613
|
elem.$syntax = 'calc';
|
|
1607
1614
|
createAndLinkCalcDepElement( elem );
|
|
@@ -1628,6 +1635,31 @@ function extend( model ) {
|
|
|
1628
1635
|
}
|
|
1629
1636
|
}
|
|
1630
1637
|
|
|
1638
|
+
/**
|
|
1639
|
+
* Return true if include (with `name` and `elements`) in `ext` highly likely
|
|
1640
|
+
* contain elements `texts` and `localized` which are compiler-generated for
|
|
1641
|
+
* localized data. Due to recompilation, we cannot just check the `$inferred`
|
|
1642
|
+
* property in the XSN, but need to apply an heuristics.
|
|
1643
|
+
*
|
|
1644
|
+
* It is as follows: the elements `texts` and `localized` in entity `‹E›` and
|
|
1645
|
+
* the entity `‹E›.texts` are considered compiler-generated for Localized Data if
|
|
1646
|
+
*
|
|
1647
|
+
* - `‹E›.texts` has a key element `locale`
|
|
1648
|
+
* - `texts` of `‹E›` is an unmanaged composition to `‹E›.texts`
|
|
1649
|
+
* - `localized` of `‹E›` is an unmanaged association to `‹E›.texts`
|
|
1650
|
+
*/
|
|
1651
|
+
function withLocalizedData( { name, elements }, ext ) {
|
|
1652
|
+
const textsEntityName = `${ name.id }.texts`;
|
|
1653
|
+
if (!model.definitions[textsEntityName]?.elements?.locale?.key?.val)
|
|
1654
|
+
return false;
|
|
1655
|
+
const { texts, localized } = elements ?? {};
|
|
1656
|
+
return texts?.target && localized?.target && texts.on && localized.on &&
|
|
1657
|
+
resolveUncheckedPath( texts.target, 'target', ext ) === textsEntityName &&
|
|
1658
|
+
resolveUncheckedPath( localized.target, 'target', ext ) === textsEntityName &&
|
|
1659
|
+
resolveUncheckedPath( texts.type, 'type', ext ) === 'cds.Composition' &&
|
|
1660
|
+
resolveUncheckedPath( localized.type, 'type', ext ) === 'cds.Association';
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1631
1663
|
/**
|
|
1632
1664
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1633
1665
|
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
package/lib/compiler/generate.js
CHANGED
|
@@ -173,8 +173,8 @@ function generate( model ) {
|
|
|
173
173
|
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
174
174
|
if (!localized)
|
|
175
175
|
return;
|
|
176
|
-
if (textsEntity
|
|
177
|
-
return;
|
|
176
|
+
if (textsEntity && textsEntity.kind !== 'namespace')
|
|
177
|
+
return; // expanded texts entity in source -> nothing to do
|
|
178
178
|
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
179
179
|
addTextsAssociations( art, textsName, localized );
|
|
180
180
|
}
|
|
@@ -236,7 +236,10 @@ function generate( model ) {
|
|
|
236
236
|
return false;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
if (textsEntity) {
|
|
239
|
+
if (textsEntity?.kind === 'namespace') { // namespace Base.texts
|
|
240
|
+
textsEntity = null;
|
|
241
|
+
}
|
|
242
|
+
else if (textsEntity) {
|
|
240
243
|
if (textsEntity.$duplicates)
|
|
241
244
|
return false;
|
|
242
245
|
if (textsEntity.kind !== 'entity' || textsEntity.query ||
|
|
@@ -283,15 +286,19 @@ function generate( model ) {
|
|
|
283
286
|
*/
|
|
284
287
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
285
288
|
const location = weakLocation( base.elements[$location] || base.location );
|
|
286
|
-
|
|
289
|
+
let art = {
|
|
287
290
|
kind: 'entity',
|
|
288
291
|
name: { id: absolute, location },
|
|
289
292
|
location,
|
|
290
293
|
elements: Object.create( null ),
|
|
291
294
|
$inferred: 'localized-entity',
|
|
292
295
|
};
|
|
296
|
+
const gap = model.definitions[absolute];
|
|
297
|
+
if (gap)
|
|
298
|
+
art = Object.assign( gap, art );
|
|
299
|
+
else
|
|
300
|
+
model.definitions[absolute] = art;
|
|
293
301
|
setLink( art, '_block', model.$internal );
|
|
294
|
-
model.definitions[absolute] = art;
|
|
295
302
|
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
296
303
|
|
|
297
304
|
if (!fioriEnabled) {
|
|
@@ -341,6 +348,11 @@ function generate( model ) {
|
|
|
341
348
|
addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
|
|
342
349
|
|
|
343
350
|
initMainArtifact( art );
|
|
351
|
+
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
352
|
+
// _descendants would have been set already for a gap artifact
|
|
353
|
+
setLink( art, '_service', art._parent._service );
|
|
354
|
+
model.$compositionTargets[absolute] = true;
|
|
355
|
+
|
|
344
356
|
if (art.includes) {
|
|
345
357
|
// add elements `locale`, etc. which are required below.
|
|
346
358
|
applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
|
|
@@ -641,7 +653,8 @@ function generate( model ) {
|
|
|
641
653
|
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
642
654
|
return false;
|
|
643
655
|
}
|
|
644
|
-
|
|
656
|
+
const place = model.definitions[entityName];
|
|
657
|
+
if (place && place.kind !== 'namespace') {
|
|
645
658
|
error( null, [ location, elem ], { art: entityName },
|
|
646
659
|
// eslint-disable-next-line @stylistic/max-len
|
|
647
660
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
@@ -687,7 +700,7 @@ function generate( model ) {
|
|
|
687
700
|
$inferred: 'aspect-composition',
|
|
688
701
|
};
|
|
689
702
|
|
|
690
|
-
|
|
703
|
+
let art = {
|
|
691
704
|
kind: 'entity',
|
|
692
705
|
name: {
|
|
693
706
|
id: entityName,
|
|
@@ -698,6 +711,12 @@ function generate( model ) {
|
|
|
698
711
|
elements: Object.create( null ),
|
|
699
712
|
$inferred: 'composition-entity',
|
|
700
713
|
};
|
|
714
|
+
const gap = model.definitions[entityName];
|
|
715
|
+
if (gap)
|
|
716
|
+
art = Object.assign( gap, art );
|
|
717
|
+
else
|
|
718
|
+
model.definitions[entityName] = art;
|
|
719
|
+
|
|
701
720
|
if (target.name) { // named target aspect
|
|
702
721
|
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
|
|
703
722
|
art.includes = [ createInclude( target.name.id, location ) ];
|
|
@@ -741,9 +760,13 @@ function generate( model ) {
|
|
|
741
760
|
addProxyElements( art, target.elements, 'aspect-composition', enforceLocation && location );
|
|
742
761
|
|
|
743
762
|
setLink( art, '_block', model.$internal );
|
|
744
|
-
model.definitions[entityName] = art;
|
|
745
763
|
initMainArtifact( art );
|
|
746
764
|
|
|
765
|
+
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
766
|
+
// _descendants would have been set already for a gap artifact
|
|
767
|
+
setLink( art, '_service', art._parent._service );
|
|
768
|
+
model.$compositionTargets[entityName] = true;
|
|
769
|
+
|
|
747
770
|
// Apply annotations to generated artifact, prepare (not apply!) element
|
|
748
771
|
// annotations (remark: adding elements is not allowed for generated artifacts):
|
|
749
772
|
extendArtifactBefore( art );
|
package/lib/compiler/index.js
CHANGED
|
@@ -495,8 +495,8 @@ function compileDoXSync( model ) {
|
|
|
495
495
|
return model;
|
|
496
496
|
}
|
|
497
497
|
extend( model );
|
|
498
|
-
generate( model );
|
|
499
498
|
kickStart( model );
|
|
499
|
+
generate( model );
|
|
500
500
|
populate( model );
|
|
501
501
|
|
|
502
502
|
model.definitions = model.$functions.shuffleDict( model.definitions );
|
|
@@ -546,7 +546,7 @@ async function compileDoX( model ) {
|
|
|
546
546
|
return model;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
-
for (const phase of [ extend,
|
|
549
|
+
for (const phase of [ extend, kickStart, generate, populate ]) {
|
|
550
550
|
phase( model );
|
|
551
551
|
// eslint-disable-next-line no-await-in-loop
|
|
552
552
|
await checkAsyncAbortFlag( options.abortSignal );
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { builtinLocation } = require('../base/location');
|
|
5
6
|
const { isBetaEnabled, forEachGeneric } = require('../base/model');
|
|
6
7
|
const {
|
|
7
8
|
setLink,
|
|
@@ -14,14 +15,12 @@ function kickStart( model ) {
|
|
|
14
15
|
const { options } = model;
|
|
15
16
|
const { message } = model.$messageFunctions;
|
|
16
17
|
|
|
17
|
-
const { resolveUncheckedPath,
|
|
18
|
+
const { resolveUncheckedPath, initMainArtifact } = model.$functions;
|
|
18
19
|
|
|
19
20
|
// Set _service link (sorted to set it on parent first). Could be set
|
|
20
21
|
// directly, but beware a namespace becoming a service later.
|
|
21
22
|
Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
|
|
22
23
|
forEachGeneric( model, 'definitions', postProcessArtifact );
|
|
23
|
-
|
|
24
|
-
forEachGeneric( model, 'sources', resolveUsings );
|
|
25
24
|
return;
|
|
26
25
|
|
|
27
26
|
|
|
@@ -85,7 +84,7 @@ function kickStart( model ) {
|
|
|
85
84
|
chain.push( art );
|
|
86
85
|
setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
|
|
87
86
|
const name = resolveUncheckedPath( art.query.from, 'from', art );
|
|
88
|
-
art = name && model.definitions[name];
|
|
87
|
+
art = name && (model.definitions[name] || createGapArtifact( name ));
|
|
89
88
|
if (autoexposed)
|
|
90
89
|
break; // only direct projection for auto-exposed
|
|
91
90
|
}
|
|
@@ -98,6 +97,18 @@ function kickStart( model ) {
|
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
|
|
100
|
+
function createGapArtifact( name, location = builtinLocation() ) {
|
|
101
|
+
// TODO: make it probably part of define.js
|
|
102
|
+
// TODO: make it work without location (or value undefined/null)
|
|
103
|
+
// TODO: change the location later if overwritten
|
|
104
|
+
const art = {
|
|
105
|
+
kind: 'namespace', name: { id: name, location }, location,
|
|
106
|
+
};
|
|
107
|
+
model.definitions[name] = art;
|
|
108
|
+
initMainArtifact( art );
|
|
109
|
+
return art;
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
function postProcessArtifact( art ) {
|
|
102
113
|
tagCompositionTargets( art );
|
|
103
114
|
if (art.$queries) {
|
|
@@ -128,40 +139,19 @@ function kickStart( model ) {
|
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
function tagCompositionTargets( elem ) {
|
|
142
|
+
// TODO: together with test for targetIsTargetAspect()
|
|
131
143
|
if (elem.target && isDirectComposition( elem )) {
|
|
132
144
|
// A target aspect would have already moved to property `targetAspect` in
|
|
133
145
|
// define.js (hm... more something for kick-start.js...)
|
|
134
146
|
// TODO: for safety, just use resolveUncheckedPath()
|
|
135
|
-
const target =
|
|
147
|
+
const target = resolveUncheckedPath( elem.target, 'target', elem );
|
|
136
148
|
if (target)
|
|
137
|
-
model.$compositionTargets[target
|
|
149
|
+
model.$compositionTargets[target] = true;
|
|
138
150
|
}
|
|
139
151
|
if (elem.targetAspect?.elements)
|
|
140
152
|
elem = elem.targetAspect;
|
|
141
153
|
forEachGeneric( elem, 'elements', tagCompositionTargets );
|
|
142
154
|
}
|
|
143
|
-
|
|
144
|
-
// Resolve the using declarations in `using`. Issue
|
|
145
|
-
// error message if the referenced artifact does not exist.
|
|
146
|
-
// TODO: think of moving this to resolve.js
|
|
147
|
-
function resolveUsings( src, topLevel ) {
|
|
148
|
-
if (!src.usings)
|
|
149
|
-
return;
|
|
150
|
-
for (const def of src.usings) {
|
|
151
|
-
if (def.usings) // using {...}
|
|
152
|
-
resolveUsings( def );
|
|
153
|
-
if (!def.name || !def.name.id)
|
|
154
|
-
continue; // using {...}, parse error
|
|
155
|
-
const art = model.definitions[def.name.absolute];
|
|
156
|
-
if (art && art.$duplicates)
|
|
157
|
-
continue;
|
|
158
|
-
const ref = def.extern;
|
|
159
|
-
const user = (topLevel ? def : src);
|
|
160
|
-
const from = user.fileDep;
|
|
161
|
-
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
162
|
-
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
module.exports = kickStart;
|
|
@@ -47,7 +47,7 @@ function propagate( model ) {
|
|
|
47
47
|
precision: always,
|
|
48
48
|
scale: always,
|
|
49
49
|
srid: always,
|
|
50
|
-
localized
|
|
50
|
+
localized,
|
|
51
51
|
target: notWithExpand,
|
|
52
52
|
targetAspect,
|
|
53
53
|
cardinality: notWithExpand,
|
|
@@ -78,6 +78,7 @@ function propagate( model ) {
|
|
|
78
78
|
const { rewriteAnnotationsRefs, rewriteRefsInExpression } = xprRewriteFns( model );
|
|
79
79
|
|
|
80
80
|
const { message, throwWithError } = model.$messageFunctions;
|
|
81
|
+
const { withLocalizedData } = model.$functions;
|
|
81
82
|
|
|
82
83
|
forEachDefinition( model, run );
|
|
83
84
|
forEachGeneric( model, 'vocabularies', run );
|
|
@@ -336,6 +337,24 @@ function propagate( model ) {
|
|
|
336
337
|
annotation( prop, target, source );
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Propagate `localized`, but not for a texts entity
|
|
342
|
+
* (as `null` in an Universal CSN).
|
|
343
|
+
*/
|
|
344
|
+
function localized( prop, target, source ) {
|
|
345
|
+
const main = target._main;
|
|
346
|
+
if (target.kind === 'element' && main.kind === 'entity' && !main.query) {
|
|
347
|
+
const { id } = main.name;
|
|
348
|
+
const base = id.endsWith( '.texts' ) &&
|
|
349
|
+
model.definitions[id.slice( 0, -'.texts'.length )];
|
|
350
|
+
if (base && withLocalizedData( base, main )) {
|
|
351
|
+
target.localized = { $inferred: 'NULL', val: undefined }; // null in UCSN
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
withKind( prop, target, source );
|
|
356
|
+
}
|
|
357
|
+
|
|
339
358
|
function withKind( prop, target, source ) {
|
|
340
359
|
if (target.kind === 'param' && source.kind === 'entity')
|
|
341
360
|
return; // Don't propagate from entity types to parameters (+ return type).
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -108,8 +108,32 @@ function resolve( model ) {
|
|
|
108
108
|
const ignoreSpecifiedElements
|
|
109
109
|
= isDeprecatedEnabled( options, 'ignoreSpecifiedQueryElements' );
|
|
110
110
|
|
|
111
|
+
forEachGeneric( model, 'sources', resolveUsings );
|
|
111
112
|
return doResolve();
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the using declarations in `using`.
|
|
116
|
+
* Issue error message if the referenced artifact does not exist.
|
|
117
|
+
*/
|
|
118
|
+
function resolveUsings( src, topLevel ) {
|
|
119
|
+
if (!src.usings)
|
|
120
|
+
return;
|
|
121
|
+
for (const def of src.usings) {
|
|
122
|
+
if (def.usings) // using {...}
|
|
123
|
+
resolveUsings( def );
|
|
124
|
+
if (!def.name || !def.name.id)
|
|
125
|
+
continue; // using {...}, parse error
|
|
126
|
+
const art = model.definitions[def.name.absolute];
|
|
127
|
+
if (art && art.$duplicates)
|
|
128
|
+
continue;
|
|
129
|
+
const ref = def.extern;
|
|
130
|
+
const user = (topLevel ? def : src);
|
|
131
|
+
const from = user.fileDep;
|
|
132
|
+
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
133
|
+
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
113
137
|
function doResolve() {
|
|
114
138
|
// Phase 1: check paths in `usings` has been moved to kick-start.js Phase 2:
|
|
115
139
|
// calculate/init view elements & collect views in order:
|
package/lib/compiler/shared.js
CHANGED
|
@@ -846,6 +846,8 @@ function fns( model ) {
|
|
|
846
846
|
|
|
847
847
|
if (!semantics.dollar) {
|
|
848
848
|
valid.push( dynamicDict );
|
|
849
|
+
if (isMainRef) // eslint-disable-next-line no-return-assign
|
|
850
|
+
valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d )) );
|
|
849
851
|
}
|
|
850
852
|
else {
|
|
851
853
|
const filterFn = semantics.variableFilter || removeRestrictedVariables;
|
|
@@ -908,14 +910,22 @@ function fns( model ) {
|
|
|
908
910
|
}
|
|
909
911
|
// need to do that here, because we also need to disallow Service.AutoExposed:elem
|
|
910
912
|
// TODO: but Service.AutoExposed.NotAuto should be fine
|
|
911
|
-
if (isMainRef !== 'all' && artItemsCount === 0
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
913
|
+
if (isMainRef && isMainRef !== 'all' && artItemsCount === 0) {
|
|
914
|
+
if (art.kind === 'namespace') {
|
|
915
|
+
if (env !== false) {
|
|
916
|
+
semantics.notFound( user, item, [ removeGapArtifact( env ) ],
|
|
917
|
+
null, prev, path, semantics );
|
|
918
|
+
}
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
else if (art.$inferred === 'autoexposed' && !user.$inferred) {
|
|
922
|
+
// Depending on the processing sequence, the following could be a
|
|
923
|
+
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
|
|
924
|
+
// could "change" to this message at the end of compile():
|
|
925
|
+
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
926
|
+
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
927
|
+
return null; // continuation semantics: like “not found”
|
|
928
|
+
}
|
|
919
929
|
}
|
|
920
930
|
}
|
|
921
931
|
return art;
|
|
@@ -978,7 +988,25 @@ function fns( model ) {
|
|
|
978
988
|
return def;
|
|
979
989
|
if (def.$duplicates)
|
|
980
990
|
return false;
|
|
981
|
-
|
|
991
|
+
art = setArtifactLink( head, def ); // we do not want to see the using
|
|
992
|
+
if (art.kind !== 'namespace')
|
|
993
|
+
return art;
|
|
994
|
+
}
|
|
995
|
+
/* FALLTHROUGH */
|
|
996
|
+
case 'namespace': {
|
|
997
|
+
if (semantics.isMainRef === 'all' || path.length !== 1 && ref.scope !== 1)
|
|
998
|
+
return art;
|
|
999
|
+
const valid = [];
|
|
1000
|
+
const lexical = userBlock( user );
|
|
1001
|
+
if (lexical) {
|
|
1002
|
+
for (let env = lexical; env; env = env._block)
|
|
1003
|
+
valid.push( removeGapArtifact( env.artifacts || Object.create( null ) ) );
|
|
1004
|
+
}
|
|
1005
|
+
valid.push( removeGapArtifact( model.definitions ) );
|
|
1006
|
+
semantics.notFound?.( user._user || user, head, valid, model.definitions,
|
|
1007
|
+
null, path, semantics );
|
|
1008
|
+
|
|
1009
|
+
return null;
|
|
982
1010
|
}
|
|
983
1011
|
case 'mixin': {
|
|
984
1012
|
// use a source element having that name if in `extend … with columns`:
|
|
@@ -2153,6 +2181,18 @@ function removeDollarNames( dict ) {
|
|
|
2153
2181
|
return r;
|
|
2154
2182
|
}
|
|
2155
2183
|
|
|
2184
|
+
function removeGapArtifact( dict ) {
|
|
2185
|
+
const r = Object.create( null );
|
|
2186
|
+
for (const name in dict) {
|
|
2187
|
+
const art = dict[name];
|
|
2188
|
+
// TODO: for gaps with sub artifacts, we use use `${name}.` as name
|
|
2189
|
+
// TODO: clarify with LSP
|
|
2190
|
+
if (art.kind !== 'namespace' || art._subArtifacts)
|
|
2191
|
+
r[name] = dict[name];
|
|
2192
|
+
}
|
|
2193
|
+
return r;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2156
2196
|
module.exports = {
|
|
2157
2197
|
fns,
|
|
2158
2198
|
};
|
package/lib/compiler/utils.js
CHANGED
|
@@ -206,6 +206,7 @@ function initExprAnnoBlock( art, block ) {
|
|
|
206
206
|
|
|
207
207
|
function initDollarSelf( art ) {
|
|
208
208
|
// TODO: use setMemberParent() ?
|
|
209
|
+
// TODO: also on 'namespace's? (test with annotation with checked ref on them)
|
|
209
210
|
const self = {
|
|
210
211
|
name: { id: '$self', location: art.location },
|
|
211
212
|
kind: '$self',
|