@sap/cds-compiler 6.9.1 → 6.9.3

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 CHANGED
@@ -13,6 +13,26 @@ 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.3 - 2026-06-17
17
+
18
+ ### Bug Fixes
19
+
20
+ - **api:** New compiler option `noErrorForUnknownAnnotateTarget` downgrades errors to warnings
21
+ for `annotate` statements with security-relevant annotations (`@restrict`, `@requires`, `@ams`)
22
+ whose target does not exist.
23
+
24
+
25
+
26
+ ## Version 6.9.2 - 2026-05-08
27
+
28
+ ### Bug Fixes
29
+
30
+ - **api:** when the environment variable `CDSC_TRACE_API` is set,
31
+ the compiler writes a trace for calls of API functions;
32
+ it now has more information, and also traces the exit of the API function.
33
+
34
+
35
+
16
36
  ## Version 6.9.1 - 2026-05-05
17
37
 
18
38
  ### Bug Fixes
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.traceApi(_name, options);
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
- return processor( recompiledCsn, options, messageFunctions, ...args );
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
  }
@@ -19,6 +19,7 @@ const publicOptionsNewAPI = [
19
19
  'addTextsLanguageAssoc',
20
20
  'localizedLanguageFallback', // why can't I define the option type here?
21
21
  'severities',
22
+ 'noErrorForUnknownAnnotateTarget',
22
23
  'messages',
23
24
  'withLocations',
24
25
  'structXpr',
@@ -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');
@@ -219,6 +220,17 @@ const severitySpecs = {
219
220
  debug: { name: 'Debug', level: 3 },
220
221
  };
221
222
 
223
+ // Message IDs raised for security-relevant annotate statements with non-existing targets.
224
+ // Downgraded to warning when noErrorForUnknownAnnotateTarget option is set.
225
+ const securityAnnotateTargetIds = new Set([
226
+ 'ext-undefined-art-sec',
227
+ 'ext-undefined-def-sec',
228
+ 'ext-undefined-element-sec',
229
+ 'ext-undefined-action-sec',
230
+ 'ext-undefined-param-sec',
231
+ 'ext-unexpected-returns-sec',
232
+ ]);
233
+
222
234
  /**
223
235
  * Get the reclassified severity of the given message using:
224
236
  *
@@ -258,6 +270,11 @@ function reclassifiedSeverity( msg, options, moduleName ) {
258
270
  }
259
271
  }
260
272
 
273
+ if (options.noErrorForUnknownAnnotateTarget &&
274
+ severity === 'Error' &&
275
+ securityAnnotateTargetIds.has(msg.messageId))
276
+ severity = 'Warning';
277
+
261
278
  if (!options.severities)
262
279
  return severity;
263
280
 
@@ -567,8 +584,10 @@ function makeMessageFunction( model, options, _moduleName = null ) {
567
584
  }
568
585
 
569
586
  function throwWithError() {
570
- if (hasNewError)
587
+ if (hasNewError) {
588
+ trace.log( `stop compilation with ${ messages.length } messages` );
571
589
  throw new CompilationError(messages, options.attachValidNames && model);
590
+ }
572
591
  }
573
592
 
574
593
  /**
@@ -583,8 +602,10 @@ function makeMessageFunction( model, options, _moduleName = null ) {
583
602
  if (!messages || !messages.length)
584
603
  return;
585
604
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
586
- if (hasError( messages, moduleName, options ))
605
+ if (hasError( messages, moduleName, options )) {
606
+ trace.log( `stop compilation with ${ messages.length } messages` );
587
607
  throw new CompilationError(messages, options.attachValidNames && model);
608
+ }
588
609
  }
589
610
 
590
611
  /**
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
- // eslint-disable-next-line no-unused-vars
14
- function noOp( apiName, options, ...args ) {
9
+ function noOp() {
15
10
  // no-op
16
11
  }
17
12
 
18
13
  /**
19
- * Print args to stderr if CDSC_TRACE_API is set
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 traceApi( apiName, options, ...args ) {
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( `CDSC_TRACE_API | ${ version } | ${ apiName }() | options: ${ optStr }${ rest }`);
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
- module.exports = {
36
- traceApi: shouldTraceApi ? traceApi : noOp,
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/main.d.ts CHANGED
@@ -32,6 +32,18 @@ declare namespace compiler {
32
32
  * during compilation otherwise.
33
33
  */
34
34
  severities?: { [messageId: string]: MessageSeverity}
35
+ /**
36
+ * Downgrade the errors raised for `annotate` statements containing a
37
+ * security-relevant annotation (`@restrict`, `@requires`, `@ams`) but
38
+ * whose target does not exist. Intended as a long-lived migration
39
+ * switch for projects that cannot fix all such statements at once.
40
+ *
41
+ * Explicit per-id entries in `severities` still take precedence over
42
+ * this option.
43
+ *
44
+ * @default false
45
+ */
46
+ noErrorForUnknownAnnotateTarget?: boolean
35
47
  /**
36
48
  * Dictionary of beta flag names. This option allows fine-grained control
37
49
  * over which beta features should be enabled.
package/lib/main.js CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  const lazyload = require('./utils/lazyload')( module );
18
18
 
19
- const { traceApi } = require('./base/trace');
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 toCsn.compactModel( model );
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
- traceApi( 'compile', options );
86
- return compiler.compileX(filenames, dir, options, fileCache).then(toCsn.compactModel);
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
- traceApi('compileSync', options);
90
- return toCsn.compactModel(compiler.compileSyncX(filenames, dir, options, fileCache));
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
- traceApi('compileSources', options);
94
- return toCsn.compactModel(compiler.compileSourcesX(sourcesDict, options));
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.9.1",
3
+ "version": "6.9.3",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",