@sap/cds-compiler 6.9.0 → 6.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/lib/api/main.js +7 -2
- package/lib/base/message-registry.js +0 -1
- package/lib/base/messages.js +7 -2
- package/lib/base/trace.js +74 -20
- package/lib/compiler/extend.js +18 -8
- package/lib/compiler/shared.js +0 -2
- package/lib/edm/csn2edm.js +4 -2
- package/lib/edm/edm.js +6 -3
- package/lib/main.js +18 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,29 @@ 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.9.2 - 2026-05-08
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
- **api:** when the environment variable `CDSC_TRACE_API` is set,
|
|
21
|
+
the compiler writes a trace for calls of API functions;
|
|
22
|
+
it now has more information, and also traces the exit of the API function.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Version 6.9.1 - 2026-05-05
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
- **compiler:**
|
|
31
|
+
+ make an element added via `extend` correctly shadow an element from an include
|
|
32
|
+
+ do not issue a warning for a correct use of `$projection`
|
|
33
|
+
- **odata:**
|
|
34
|
+
+ do not generate wrong ReferentialConstraints for unmanaged Composition without a partner
|
|
35
|
+
+ render Partner attribute on forward association correctly
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
16
39
|
## Version 6.9.0 - 2026-04-21
|
|
17
40
|
|
|
18
41
|
### Features
|
package/lib/api/main.js
CHANGED
|
@@ -1169,7 +1169,7 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
1169
1169
|
* @returns {any} What ever the processor returns
|
|
1170
1170
|
*/
|
|
1171
1171
|
function api( csn, options = {}, ...args ) {
|
|
1172
|
-
trace.
|
|
1172
|
+
trace.call(_name, options, csn);
|
|
1173
1173
|
const originalMessageLength = options.messages?.length;
|
|
1174
1174
|
try {
|
|
1175
1175
|
const messageFunctions = messages.makeMessageFunction(csn, options, _name);
|
|
@@ -1184,6 +1184,7 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
1184
1184
|
timetrace.timetrace.start(_name);
|
|
1185
1185
|
const result = processor( csn, options, messageFunctions, ...args );
|
|
1186
1186
|
timetrace.timetrace.stop(_name);
|
|
1187
|
+
trace.exit(_name, result);
|
|
1187
1188
|
return result;
|
|
1188
1189
|
}
|
|
1189
1190
|
catch (err) {
|
|
@@ -1200,6 +1201,7 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
1200
1201
|
if (originalMessageLength !== undefined)
|
|
1201
1202
|
options.messages.length = originalMessageLength;
|
|
1202
1203
|
|
|
1204
|
+
trace.log( 'recompile CSN and retry backend' );
|
|
1203
1205
|
const messageFunctions = messages.makeMessageFunction( csn, options, _name );
|
|
1204
1206
|
const recompileMsg = messageFunctions.info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {},
|
|
1205
1207
|
'CSN input had to be recompiled' );
|
|
@@ -1211,7 +1213,10 @@ function publishCsnProcessor( processor, _name ) {
|
|
|
1211
1213
|
const xsn = compiler.recompileX(csn, options);
|
|
1212
1214
|
const recompiledCsn = toCsn.compactModel(xsn);
|
|
1213
1215
|
messageFunctions.setModel(recompiledCsn);
|
|
1214
|
-
|
|
1216
|
+
const result = processor( recompiledCsn, options, messageFunctions, ...args );
|
|
1217
|
+
timetrace.timetrace.stop(_name);
|
|
1218
|
+
trace.exit(_name, result);
|
|
1219
|
+
return result;
|
|
1215
1220
|
}
|
|
1216
1221
|
}
|
|
1217
1222
|
}
|
|
@@ -1138,7 +1138,6 @@ const centralMessageTexts = {
|
|
|
1138
1138
|
'old-not-target': 'Expected element $(NAME) not to be an association, because it overrides the included element from $(ART)',
|
|
1139
1139
|
},
|
|
1140
1140
|
|
|
1141
|
-
'ref-expecting-$self': 'Use $(NEWCODE) instead of $(CODE) here or remove $(CODE) altogether if possible; the compiler has rewritten it to $(NEWCODE) in CSN',
|
|
1142
1141
|
'ref-expecting-assoc': {
|
|
1143
1142
|
std: 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition',
|
|
1144
1143
|
'with-type': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
|
package/lib/base/messages.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
|
|
17
17
|
const { analyseCsnPath, traverseQuery } = require('../base/csnRefs');
|
|
18
18
|
const { CompilerAssertion } = require('./error');
|
|
19
|
+
const trace = require('./trace');
|
|
19
20
|
const { getArtifactName } = require('../compiler/base');
|
|
20
21
|
const { cdlNewLineRegEx } = require('../language/textUtils');
|
|
21
22
|
const meta = require('./meta');
|
|
@@ -567,8 +568,10 @@ function makeMessageFunction( model, options, _moduleName = null ) {
|
|
|
567
568
|
}
|
|
568
569
|
|
|
569
570
|
function throwWithError() {
|
|
570
|
-
if (hasNewError)
|
|
571
|
+
if (hasNewError) {
|
|
572
|
+
trace.log( `stop compilation with ${ messages.length } messages` );
|
|
571
573
|
throw new CompilationError(messages, options.attachValidNames && model);
|
|
574
|
+
}
|
|
572
575
|
}
|
|
573
576
|
|
|
574
577
|
/**
|
|
@@ -583,8 +586,10 @@ function makeMessageFunction( model, options, _moduleName = null ) {
|
|
|
583
586
|
if (!messages || !messages.length)
|
|
584
587
|
return;
|
|
585
588
|
const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
|
|
586
|
-
if (hasError( messages, moduleName, options ))
|
|
589
|
+
if (hasError( messages, moduleName, options )) {
|
|
590
|
+
trace.log( `stop compilation with ${ messages.length } messages` );
|
|
587
591
|
throw new CompilationError(messages, options.attachValidNames && model);
|
|
592
|
+
}
|
|
588
593
|
}
|
|
589
594
|
|
|
590
595
|
/**
|
package/lib/base/trace.js
CHANGED
|
@@ -5,33 +5,87 @@ const shouldTraceApi = process?.env?.CDSC_TRACE_API;
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Placeholder for disabled tracing (no-op).
|
|
8
|
-
*
|
|
9
|
-
* @param {string} apiName API name
|
|
10
|
-
* @param {object} options Options passed to the API.
|
|
11
|
-
* @param {...any} [args] Arguments to be logged to stderr
|
|
12
8
|
*/
|
|
13
|
-
|
|
14
|
-
function noOp( apiName, options, ...args ) {
|
|
9
|
+
function noOp() {
|
|
15
10
|
// no-op
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
/**
|
|
19
|
-
* Print
|
|
20
|
-
*
|
|
21
|
-
* @param {string} apiName API name
|
|
22
|
-
* @param {object} options Options passed to the API.
|
|
23
|
-
* @param {...any} [args] Arguments to be logged to stderr
|
|
14
|
+
* Print trace info to stderr when calling an API function
|
|
24
15
|
*/
|
|
25
|
-
function
|
|
26
|
-
const optStr = typeof options === 'object' ? JSON.stringify(options, null, 2) : options;
|
|
27
|
-
const argsStr = args.map(val => JSON.stringify(val)).join(', ');
|
|
28
|
-
const rest = args.length > 0 ? ` | ${ argsStr }` : '';
|
|
29
|
-
// Local require: Only load on-demand, not when tracing is disabled.
|
|
16
|
+
function call( apiName, options, csn, files ) {
|
|
30
17
|
const { version } = require('../../package.json');
|
|
18
|
+
const now = (new Date( Date.now() )).toISOString();
|
|
19
|
+
const args = (files || csn)
|
|
20
|
+
? `${ optionsString( options ) } on ${ filesInfo( files ) || csnInfo( csn ) }`
|
|
21
|
+
: optionsString( options );
|
|
31
22
|
// eslint-disable-next-line no-console
|
|
32
|
-
console.error(
|
|
23
|
+
console.error( 'CDSC_TRACE_API: at %s, call %s() of v%s with options %s',
|
|
24
|
+
now, apiName, version, args );
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Print trace info to stderr when exiting an API function
|
|
29
|
+
*/
|
|
30
|
+
function exit( apiName, result ) {
|
|
31
|
+
const now = (new Date( Date.now() )).toISOString();
|
|
32
|
+
const info = (result?.definitions || result?.extensions)
|
|
33
|
+
? csnInfo( result )
|
|
34
|
+
: `a result of type ${ typeof result }`;
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.error( 'CDSC_TRACE_API: at %s, exit %s() and return %s',
|
|
37
|
+
now, apiName, info );
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Print trace info to stderr for miscellaneous use cases
|
|
42
|
+
*/
|
|
43
|
+
function log( info ) {
|
|
44
|
+
const now = (new Date( Date.now() )).toISOString();
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error( 'CDSC_TRACE_API: at %s, %s', now, info );
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function optionsString( obj ) {
|
|
50
|
+
if (!obj || typeof obj !== 'object')
|
|
51
|
+
return obj.toString();
|
|
52
|
+
try {
|
|
53
|
+
if (Array.isArray( obj.messages ) && obj.messages.length) {
|
|
54
|
+
const messages = {};
|
|
55
|
+
for (const msg of obj.messages)
|
|
56
|
+
messages[msg.severity] = (messages[msg.severity] || 0) + 1;
|
|
57
|
+
obj = { ...obj, messages };
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify( obj, null, 2 );
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return err.toString();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function filesInfo( files ) {
|
|
67
|
+
if (!files)
|
|
68
|
+
return files;
|
|
69
|
+
if (!Array.isArray( files ))
|
|
70
|
+
files = Object.keys( files );
|
|
71
|
+
return files.length ? [ 'files', ...files ].join( '\n ') : 'no files';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function csnInfo( csn ) {
|
|
75
|
+
if (!csn || typeof csn !== 'object' || Array.isArray( csn ))
|
|
76
|
+
return `some value of type ${ typeof csn }`;
|
|
77
|
+
try {
|
|
78
|
+
JSON.stringify( csn );
|
|
79
|
+
const defs = csn.definitions ? Object.keys( csn.definitions ).length : 'no';
|
|
80
|
+
const exts = csn.extensions ? csn.extensions.length : 'no';
|
|
81
|
+
const flavor = csn.meta?.flavor || csn.meta?.compilerCsnFlavor || 'unknown';
|
|
82
|
+
return `a CSN of flavor '${ flavor }' with ${ defs } definitions and ${ exts } extensions`;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return `a CORRUPTED CSN (${ err.toString() })`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = (shouldTraceApi)
|
|
90
|
+
? { call, exit, log }
|
|
91
|
+
: { call: noOp, exit: noOp, log: noOp };
|
package/lib/compiler/extend.js
CHANGED
|
@@ -1173,7 +1173,8 @@ function extend( model ) {
|
|
|
1173
1173
|
}
|
|
1174
1174
|
if (art._extensions?.$add)
|
|
1175
1175
|
extendArtifact( art._extensions.$add, art );
|
|
1176
|
-
|
|
1176
|
+
checkRedefinitionThroughIncludes( art, 'elements' );
|
|
1177
|
+
checkRedefinitionThroughIncludes( art, 'actions' );
|
|
1177
1178
|
}
|
|
1178
1179
|
|
|
1179
1180
|
/**
|
|
@@ -1497,6 +1498,9 @@ function extend( model ) {
|
|
|
1497
1498
|
}
|
|
1498
1499
|
|
|
1499
1500
|
const existing = parent[prop]?.[name];
|
|
1501
|
+
const shadowsIncludeMember = construct !== parent &&
|
|
1502
|
+
elem.$inferred !== 'include' &&
|
|
1503
|
+
existing?.$inferred === 'include';
|
|
1500
1504
|
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1501
1505
|
if (!add && existing?.$inferred === 'include' && elem.$inferred === 'include') {
|
|
1502
1506
|
includeCollisions.push( {
|
|
@@ -1507,9 +1511,17 @@ function extend( model ) {
|
|
|
1507
1511
|
const { $duplicates } = elem;
|
|
1508
1512
|
if ($duplicates === true && add)
|
|
1509
1513
|
elem.$duplicates = null;
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1514
|
+
if (add && elem.$inferred === 'include' && Array.isArray( $duplicates ))
|
|
1515
|
+
elem.$duplicates = null;
|
|
1516
|
+
if (shadowsIncludeMember) {
|
|
1517
|
+
parent[prop][name] = elem;
|
|
1518
|
+
setMemberParent( elem, name, parent );
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
setMemberParent( elem, name, parent, add && prop );
|
|
1522
|
+
if (!$duplicates) // not already reported
|
|
1523
|
+
checkRedefinition( elem );
|
|
1524
|
+
}
|
|
1513
1525
|
initMembers( elem, elem, elem._block );
|
|
1514
1526
|
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1515
1527
|
initBoundSelfParam( elem.params, elem._main );
|
|
@@ -1740,9 +1752,6 @@ function extend( model ) {
|
|
|
1740
1752
|
} );
|
|
1741
1753
|
}
|
|
1742
1754
|
}
|
|
1743
|
-
|
|
1744
|
-
checkRedefinitionThroughIncludes( parent, prop );
|
|
1745
|
-
|
|
1746
1755
|
if (!hasNewElement && members) {
|
|
1747
1756
|
ext[prop] = members;
|
|
1748
1757
|
}
|
|
@@ -1788,7 +1797,8 @@ function extend( model ) {
|
|
|
1788
1797
|
|
|
1789
1798
|
/**
|
|
1790
1799
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1791
|
-
* same member.
|
|
1800
|
+
* same member. Run after includes and extends so shadowing members have already
|
|
1801
|
+
* replaced any include-derived survivors in-place.
|
|
1792
1802
|
*/
|
|
1793
1803
|
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
1794
1804
|
if (!parent[prop])
|
package/lib/compiler/shared.js
CHANGED
|
@@ -984,8 +984,6 @@ function fns( model ) {
|
|
|
984
984
|
art.kind === '$self' && path[0].id === '$projection') {
|
|
985
985
|
// Rewrite $projection to $self
|
|
986
986
|
path[0].id = '$self';
|
|
987
|
-
warning( 'ref-expecting-$self', [ path[0].location, user ],
|
|
988
|
-
{ code: '$projection', newcode: '$self' });
|
|
989
987
|
}
|
|
990
988
|
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
991
989
|
}
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -472,8 +472,10 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
472
472
|
|
|
473
473
|
navigationProperties.forEach((np) => {
|
|
474
474
|
if (options.isV4()) {
|
|
475
|
-
// V4: No referential constraints for Containment Relationships
|
|
476
|
-
|
|
475
|
+
// V4: No referential constraints for Containment Relationships or
|
|
476
|
+
// unmanaged Compositions without a partner
|
|
477
|
+
const isUnmanagedCompositionWithoutPartner = np._csn.type === 'cds.Composition' && np._csn.on && !np._csn._constraints._partnerCsn;
|
|
478
|
+
if ((!np.isContainment() || (options.renderForeignKeys)) && !np.isToMany() && !isUnmanagedCompositionWithoutPartner)
|
|
477
479
|
np.addReferentialConstraintNodes();
|
|
478
480
|
}
|
|
479
481
|
else {
|
package/lib/edm/edm.js
CHANGED
|
@@ -1053,11 +1053,14 @@ function getEdm( options ) {
|
|
|
1053
1053
|
delete this._edmAttributes.Nullable;
|
|
1054
1054
|
}
|
|
1055
1055
|
// we have exactly one selfReference or the default partner
|
|
1056
|
-
|
|
1057
|
-
|
|
1056
|
+
// $noPartner can be set when a second projection creates an ambiguous backlink, but if exactly
|
|
1057
|
+
// one self-reference targets the same entity as this association, that unambiguous partner still applies.
|
|
1058
|
+
const selfRefsToTarget = csn._selfReferences.filter(ref => ref.$abspath[0] === csn._target?.name);
|
|
1059
|
+
const isPartner = selfRefsToTarget.length === 1 && selfRefsToTarget[0]._constraints?._partnerCsn === csn;
|
|
1060
|
+
if (!csn.$noPartner || isPartner) {
|
|
1058
1061
|
const partner = csn._selfReferences.length === 1
|
|
1059
1062
|
? csn._selfReferences[0]
|
|
1060
|
-
: csn._constraints._partnerCsn;
|
|
1063
|
+
: selfRefsToTarget[0] || csn._constraints._partnerCsn;
|
|
1061
1064
|
if (partner && partner['@odata.navigable'] !== false && this._csn._edmParentCsn.kind !== 'type') {
|
|
1062
1065
|
// $abspath[0] is main entity
|
|
1063
1066
|
this._edmAttributes.Partner = partner.$abspath.slice(1).join('/');
|
package/lib/main.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const lazyload = require('./utils/lazyload')( module );
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const trace = require('./base/trace');
|
|
20
20
|
|
|
21
21
|
const snapi = lazyload('./api/main');
|
|
22
22
|
const csnUtils = lazyload('./model/csnUtils');
|
|
@@ -40,6 +40,7 @@ const meta = lazyload('./base/meta');
|
|
|
40
40
|
const toCsn = lazyload('./json/to-csn');
|
|
41
41
|
|
|
42
42
|
function parseCdl( cdlSource, filename, options = {} ) {
|
|
43
|
+
trace.call( 'parse.cdl', options );
|
|
43
44
|
options = Object.assign( {}, options, { parseCdl: true } );
|
|
44
45
|
const sources = Object.create(null);
|
|
45
46
|
/** @type {XSN.Model} */
|
|
@@ -56,7 +57,7 @@ function parseCdl( cdlSource, filename, options = {} ) {
|
|
|
56
57
|
define( model );
|
|
57
58
|
finalizeParseCdl( model );
|
|
58
59
|
messageFunctions.throwWithError();
|
|
59
|
-
return
|
|
60
|
+
return compactAndTrace( model, 'parse.cdl' );
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function parseCql( cdlSource, filename = '<query>.cds', options = {} ) {
|
|
@@ -75,6 +76,12 @@ function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
|
|
|
75
76
|
return toCsn.compactExpr( xsn );
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
function compactAndTrace( xsn, apiName = 'compile' ) {
|
|
80
|
+
const csn = toCsn.compactModel( xsn );
|
|
81
|
+
trace.exit( apiName, csn );
|
|
82
|
+
return csn;
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
// FIXME: The implementation of those functions that delegate to 'backends'
|
|
79
86
|
// should probably move here
|
|
80
87
|
// ATTENTION: Keep in sync with main.d.ts!
|
|
@@ -82,16 +89,19 @@ module.exports = {
|
|
|
82
89
|
// Compiler
|
|
83
90
|
version: () => meta.version(),
|
|
84
91
|
compile: (filenames, dir, options, fileCache) => { // main function
|
|
85
|
-
|
|
86
|
-
return compiler.compileX(filenames, dir, options, fileCache)
|
|
92
|
+
trace.call( 'compile', options, null, filenames );
|
|
93
|
+
return compiler.compileX( filenames, dir, options, fileCache )
|
|
94
|
+
.then( compactAndTrace );
|
|
87
95
|
},
|
|
88
96
|
compileSync: (filenames, dir, options, fileCache) => { // main function
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
trace.call( 'compileSync', options, null, filenames );
|
|
98
|
+
const xsn = compiler.compileSyncX( filenames, dir, options, fileCache );
|
|
99
|
+
return compactAndTrace( xsn, 'compileSync' );
|
|
91
100
|
},
|
|
92
101
|
compileSources: (sourcesDict, options) => { // main function
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
trace.call( 'compileSources', options, null, sourcesDict );
|
|
103
|
+
const xsn = compiler.compileSourcesX( sourcesDict, options );
|
|
104
|
+
return compactAndTrace( xsn, 'compileSources' );
|
|
95
105
|
},
|
|
96
106
|
compactModel: csn => csn, // for easy v2 migration
|
|
97
107
|
get CompilationError() {
|