@sap/cds-compiler 3.9.4 → 4.0.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 +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/base/messages.js
CHANGED
|
@@ -12,9 +12,11 @@ const _messageIdsWithExplanation = require('../../share/messages/message-explana
|
|
|
12
12
|
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
|
|
13
13
|
const { CompilerAssertion } = require('./error');
|
|
14
14
|
const { getArtifactName } = require('../compiler/base');
|
|
15
|
+
const { cdlNewLineRegEx } = require('../language/textUtils');
|
|
15
16
|
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const path = require('path');
|
|
19
|
+
const { inspect } = require('util')
|
|
18
20
|
|
|
19
21
|
// term instance for messages
|
|
20
22
|
const colorTerm = term();
|
|
@@ -41,12 +43,12 @@ function hasErrors( messages ) {
|
|
|
41
43
|
*
|
|
42
44
|
* @param {CompileMessage[]} messages
|
|
43
45
|
* @param {string} moduleName
|
|
46
|
+
* @param {CSN.Options} options
|
|
44
47
|
* @returns {boolean}
|
|
45
48
|
*/
|
|
46
|
-
function hasNonDowngradableErrors( messages, moduleName,
|
|
49
|
+
function hasNonDowngradableErrors( messages, moduleName, options ) {
|
|
47
50
|
return messages &&
|
|
48
|
-
messages.some( m => m.severity === 'Error' &&
|
|
49
|
-
!isDowngradable( m.messageId, moduleName, deprecatedDowngradable ));
|
|
51
|
+
messages.some( m => m.severity === 'Error' && !isDowngradable( m.messageId, moduleName, options ));
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -56,10 +58,10 @@ function hasNonDowngradableErrors( messages, moduleName, deprecatedDowngradable
|
|
|
56
58
|
*
|
|
57
59
|
* @param {string} messageId
|
|
58
60
|
* @param {string} moduleName
|
|
59
|
-
* @param {
|
|
61
|
+
* @param {CSN.Options} options Options used to check for test mode and beta flags.
|
|
60
62
|
* @returns {boolean}
|
|
61
63
|
*/
|
|
62
|
-
function isDowngradable( messageId, moduleName,
|
|
64
|
+
function isDowngradable( messageId, moduleName, options ) {
|
|
63
65
|
if (!messageId || !centralMessages[messageId])
|
|
64
66
|
return false;
|
|
65
67
|
|
|
@@ -74,43 +76,40 @@ function isDowngradable( messageId, moduleName, deprecatedDowngradable ) {
|
|
|
74
76
|
const { configurableFor } = msg;
|
|
75
77
|
return (Array.isArray( configurableFor ))
|
|
76
78
|
? configurableFor.includes( moduleName )
|
|
77
|
-
: configurableFor && (configurableFor !== 'deprecated' ||
|
|
79
|
+
: configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
81
83
|
* Class for combined compiler errors. Additional members:
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* TODO: standard param order
|
|
85
|
-
* @class CompilationError
|
|
86
|
-
* @extends {Error}
|
|
84
|
+
* - `messages`: array of compiler messages (CompileMessage)
|
|
85
|
+
* - `model`: the CSN model
|
|
87
86
|
*/
|
|
88
87
|
class CompilationError extends Error {
|
|
89
88
|
/**
|
|
90
|
-
*
|
|
91
|
-
* @param {array} messages vector of errors
|
|
89
|
+
* @param {CompileMessage[]} messages
|
|
92
90
|
* @param {XSN.Model} [model] the XSN model, only to be set with options.attachValidNames
|
|
93
|
-
* @param {string} [text] Text of the error
|
|
94
|
-
* @param {any} args Any args to pass to the super constructor
|
|
95
|
-
*
|
|
96
|
-
* @memberOf CompilationError
|
|
97
91
|
*/
|
|
98
|
-
constructor(messages, model
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
constructor(messages, model) {
|
|
93
|
+
// TODO(v5): Don't store stringified messages.
|
|
94
|
+
super( `CDS compilation failed\n${messages?.map( m => m.toString() ).join('\n') || ''}` );
|
|
95
|
+
/** @since v4.0.0 */
|
|
96
|
+
this.code = 'ERR_CDS_COMPILATION_FAILURE';
|
|
102
97
|
this.messages = messages;
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
this
|
|
99
|
+
// TODO: remove this bin/cdsc.js specifics
|
|
100
|
+
Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
|
|
106
101
|
// property `model` is only set with options.attachValidNames:
|
|
107
102
|
Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Called by `console.*()` functions in NodeJs. To avoid `err.messages` being
|
|
107
|
+
* printed using `util.inspect()`.
|
|
108
|
+
*
|
|
109
|
+
* @return {string}
|
|
110
|
+
*/
|
|
111
|
+
[inspect.custom]() {
|
|
112
|
+
return this.stack || this.message;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
/**
|
|
@@ -140,8 +139,7 @@ class CompileMessage {
|
|
|
140
139
|
*/
|
|
141
140
|
constructor(location, msg, severity = 'Error', id = null, home = null, moduleName = null) {
|
|
142
141
|
this.message = msg;
|
|
143
|
-
this
|
|
144
|
-
this.$location = { ...this.location, address: undefined };
|
|
142
|
+
this.$location = { ...location, address: undefined };
|
|
145
143
|
this.validNames = null;
|
|
146
144
|
this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
|
|
147
145
|
this.severity = severity;
|
|
@@ -180,16 +178,12 @@ const severitySpecs = {
|
|
|
180
178
|
* @param {object} msg The CompileMessage.
|
|
181
179
|
* @param {CSN.Options} options
|
|
182
180
|
* @param {string} moduleName
|
|
183
|
-
* @param {boolean} deprecatedDowngradable
|
|
184
181
|
* @returns {MessageSeverity}
|
|
185
182
|
*/
|
|
186
|
-
function reclassifiedSeverity( msg, options, moduleName
|
|
183
|
+
function reclassifiedSeverity( msg, options, moduleName ) {
|
|
187
184
|
const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
|
|
188
185
|
if (spec.severity === 'Error') {
|
|
189
|
-
|
|
190
|
-
if (!(Array.isArray( configurableFor )
|
|
191
|
-
? configurableFor.includes( moduleName )
|
|
192
|
-
: configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable)))
|
|
186
|
+
if (!isDowngradable(msg.messageId, moduleName, options))
|
|
193
187
|
return 'Error';
|
|
194
188
|
}
|
|
195
189
|
else {
|
|
@@ -311,7 +305,6 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
311
305
|
}
|
|
312
306
|
|
|
313
307
|
const hasMessageArray = !!options.messages;
|
|
314
|
-
const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
|
|
315
308
|
/**
|
|
316
309
|
* Array of collected compiler messages. Only use it for debugging. Will not
|
|
317
310
|
* contain the messages created during a `callTransparently` call.
|
|
@@ -362,12 +355,12 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
362
355
|
if (id) {
|
|
363
356
|
if (options.testMode && !options.$recompile)
|
|
364
357
|
_check$Consistency( id, moduleName, severity, texts, options );
|
|
365
|
-
msg.severity = reclassifiedSeverity(msg, options, moduleName
|
|
358
|
+
msg.severity = reclassifiedSeverity( msg, options, moduleName );
|
|
366
359
|
}
|
|
367
360
|
|
|
368
361
|
messages.push( msg );
|
|
369
362
|
hasNewError = hasNewError || msg.severity === 'Error' &&
|
|
370
|
-
!(options.testMode && isDowngradable( msg.messageId, moduleName,
|
|
363
|
+
!(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
|
|
371
364
|
if (!hasMessageArray)
|
|
372
365
|
console.error( messageString( msg ) );
|
|
373
366
|
return msg;
|
|
@@ -410,7 +403,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
410
403
|
|
|
411
404
|
/**
|
|
412
405
|
* Normalize the given location. Location may be a CSN path, XSN/CSN location or an
|
|
413
|
-
* array of the form `[CSN.Location, user, suffix]`.
|
|
406
|
+
* array of the form `[CSN.Location, XSN user, suffix]`.
|
|
414
407
|
*
|
|
415
408
|
* @param {any} location
|
|
416
409
|
* @returns {[CSN.Location, string, string]} Location, semantic location and definition.
|
|
@@ -514,7 +507,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
514
507
|
if (!messages || !messages.length)
|
|
515
508
|
return;
|
|
516
509
|
const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
|
|
517
|
-
if (hasError( messages, moduleName,
|
|
510
|
+
if (hasError( messages, moduleName, options ))
|
|
518
511
|
throw new CompilationError( messages, options.attachValidNames && model );
|
|
519
512
|
}
|
|
520
513
|
|
|
@@ -526,13 +519,13 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
526
519
|
function reclassifyMessagesForModule() {
|
|
527
520
|
for (const msg of messages) {
|
|
528
521
|
if (msg.messageId && msg.severity !== 'Error') {
|
|
529
|
-
const severity = reclassifiedSeverity(msg, options, moduleName
|
|
522
|
+
const severity = reclassifiedSeverity( msg, options, moduleName );
|
|
530
523
|
if (severity !== msg.severity) {
|
|
531
524
|
msg.severity = severity;
|
|
532
525
|
// Re-set the module regardless of severity, since we reclassified it.
|
|
533
526
|
Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
|
|
534
527
|
hasNewError = hasNewError || severity === 'Error' &&
|
|
535
|
-
!(options.testMode && isDowngradable( msg.messageId, moduleName,
|
|
528
|
+
!(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
|
|
536
529
|
}
|
|
537
530
|
}
|
|
538
531
|
}
|
|
@@ -621,7 +614,7 @@ function _check$Severities( id, moduleName, severity ) {
|
|
|
621
614
|
return;
|
|
622
615
|
}
|
|
623
616
|
// now try whether the message could be something less than an Error in the module due to user wishes
|
|
624
|
-
if (!isDowngradable(
|
|
617
|
+
if (!isDowngradable(id, moduleName, { testMode: true, deprecated: { downgradableErrors: true } } )) { // always an error in module
|
|
625
618
|
if (severity !== 'Error')
|
|
626
619
|
throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${ severity }" for message ID "${ id }" in module "${ moduleName }"` );
|
|
627
620
|
}
|
|
@@ -656,7 +649,7 @@ function _check$Texts( id, prop, val ) {
|
|
|
656
649
|
}
|
|
657
650
|
|
|
658
651
|
const quote = { // could be an option in the future
|
|
659
|
-
double: p => `“${ p }”`, // for names, including annotation names (with
|
|
652
|
+
double: p => `“${ p }”`, // for names, including annotation names (with preceding `@`)
|
|
660
653
|
single: p => `‘${ p }’`, // for other things cited from or expected in the model
|
|
661
654
|
angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
|
|
662
655
|
direct: p => p, // e.g. for numbers _not cited from or expected in_ the source
|
|
@@ -786,7 +779,7 @@ function quoted( name ) {
|
|
|
786
779
|
|
|
787
780
|
function tokenSymbol( token ) {
|
|
788
781
|
if (token.match( /^[A-Z][A-Z]/ )) // keyword
|
|
789
|
-
return
|
|
782
|
+
return keyword( token );
|
|
790
783
|
else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...
|
|
791
784
|
return quote.angle( token );
|
|
792
785
|
if (token.startsWith('\'') && token.endsWith('\'')) // operator token symbol
|
|
@@ -803,12 +796,8 @@ function transformElementRef( arg ) {
|
|
|
803
796
|
if (arg.ref) {
|
|
804
797
|
// Can be used by CSN backends to create a simple path such as E:elem
|
|
805
798
|
return quoted(arg.ref.map((ref) => {
|
|
806
|
-
if (ref.id)
|
|
807
|
-
|
|
808
|
-
if (ref.where)
|
|
809
|
-
return `${ ref.id }[…]`;
|
|
810
|
-
return ref.id;
|
|
811
|
-
}
|
|
799
|
+
if (ref.id)
|
|
800
|
+
return `${ ref.id }${ref.args ? '(…)' : ''}${ref.where ? '[…]' : ''}`;
|
|
812
801
|
return ref;
|
|
813
802
|
}).join('.'));
|
|
814
803
|
}
|
|
@@ -914,26 +903,54 @@ function weakLocation( loc ) {
|
|
|
914
903
|
}
|
|
915
904
|
|
|
916
905
|
/**
|
|
917
|
-
* Return message string with location if present in compact form (i.e. one line)
|
|
906
|
+
* Return message string with location if present in compact form (i.e. one line).
|
|
907
|
+
*
|
|
908
|
+
* IMPORTANT:
|
|
909
|
+
* cds-compiler <v4 used following signature:
|
|
910
|
+
* `messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) : string`
|
|
911
|
+
* This signature is still supported for backwards compatibility but is deprecated.
|
|
918
912
|
*
|
|
919
913
|
* Example:
|
|
920
914
|
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
|
|
921
915
|
*
|
|
922
916
|
* @param {CompileMessage} err
|
|
923
|
-
*
|
|
924
|
-
* @param {
|
|
925
|
-
*
|
|
917
|
+
*
|
|
918
|
+
* @param {object} [config = {}]
|
|
919
|
+
*
|
|
920
|
+
* @param {boolean} [config.normalizeFilename]
|
|
921
|
+
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
|
|
922
|
+
*
|
|
923
|
+
* @param {boolean} [config.noMessageId]
|
|
924
|
+
* If true, will _not_ show the message ID (+ explanation hint) in the output.
|
|
925
|
+
*
|
|
926
|
+
* @param {boolean} [config.noHome]
|
|
927
|
+
* If true, will _not_ show message's semantic location.
|
|
928
|
+
*
|
|
929
|
+
* @param {string} [config.module]
|
|
930
|
+
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
931
|
+
* the message can be downgraded for the given module.
|
|
932
|
+
*
|
|
926
933
|
* @returns {string}
|
|
927
934
|
*/
|
|
928
|
-
function messageString( err,
|
|
929
|
-
|
|
935
|
+
function messageString( err, config ) {
|
|
936
|
+
// backwards compatibility <v4
|
|
937
|
+
if (!config || typeof config === 'boolean' || arguments.length > 2) {
|
|
938
|
+
config = {
|
|
939
|
+
normalizeFilename: arguments[1],
|
|
940
|
+
noMessageId: arguments[2],
|
|
941
|
+
noHome: arguments[3],
|
|
942
|
+
module: arguments[4],
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const location = (err.$location?.file ? `${ locationString( err.$location, config.normalizeFilename ) }: ` : '');
|
|
930
947
|
const severity = err.severity || 'Error';
|
|
931
|
-
const downgradable = severity === 'Error' &&
|
|
932
|
-
isDowngradable(err.messageId,
|
|
948
|
+
const downgradable = severity === 'Error' && config.module &&
|
|
949
|
+
isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
|
|
933
950
|
// even with noHome, print err.home if the location is weak
|
|
934
|
-
const home = !err.home || noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
|
|
951
|
+
const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
|
|
935
952
|
// TODO: the plan was with brackets = `Error[ref-undefined-def]`
|
|
936
|
-
const id = err.messageId && !noMessageId ? ` ${ err.messageId }` : '';
|
|
953
|
+
const id = err.messageId && !config.noMessageId ? ` ${ err.messageId }` : '';
|
|
937
954
|
return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;
|
|
938
955
|
}
|
|
939
956
|
|
|
@@ -957,91 +974,151 @@ function messageHash( msg ) {
|
|
|
957
974
|
}
|
|
958
975
|
|
|
959
976
|
/**
|
|
960
|
-
* Returns a message string with file- and semantic location if present
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
* Example:
|
|
964
|
-
* ```txt
|
|
965
|
-
* Error[message-id]: Can't find type `nu` in this scope
|
|
966
|
-
* |
|
|
967
|
-
* <source>.cds:3:11, at entity:“E”/element:“e”
|
|
968
|
-
* ```
|
|
977
|
+
* Returns a message string with file- and semantic location if present in multiline form
|
|
978
|
+
* with a source code snippet below that has highlights for the message's location.
|
|
979
|
+
* The message (+ message id) are colored according to their severity.
|
|
969
980
|
*
|
|
970
981
|
* @param {CompileMessage} err
|
|
982
|
+
*
|
|
971
983
|
* @param {object} [config = {}]
|
|
972
|
-
*
|
|
984
|
+
*
|
|
985
|
+
* @param {boolean} [config.normalizeFilename]
|
|
986
|
+
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
|
|
987
|
+
*
|
|
973
988
|
* @param {boolean} [config.noMessageId]
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
* @param {boolean
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
989
|
+
* If true, will _not_ show the message ID (+ explanation hint) in the output.
|
|
990
|
+
*
|
|
991
|
+
* @param {boolean} [config.hintExplanation]
|
|
992
|
+
* If true, messages with explanations will get a "…" marker.
|
|
993
|
+
*
|
|
994
|
+
* @param {string} [config.module]
|
|
995
|
+
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
996
|
+
* the message can be downgraded for the given module.
|
|
997
|
+
*
|
|
998
|
+
* @param {Record<string, string>} [config.sourceMap]
|
|
999
|
+
* A dictionary of filename<->source-code entries. You can pass the `fileCache` that is used
|
|
1000
|
+
* by the compiler.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {Record<string, number[]>} [config.sourceLineMap]
|
|
1003
|
+
* A dictionary of filename<->source-newline-indices entries. Is used to extract source code
|
|
1004
|
+
* snippets for message locations. If not set, will be set and filled by this function on-demand.
|
|
1005
|
+
* An entry is an array of character/byte offsets to new-lines, for example sourceLineMap[1] is the
|
|
1006
|
+
* end-newline for the second line.
|
|
1007
|
+
*
|
|
1008
|
+
* @param {string} [config.cwd]
|
|
1009
|
+
* The current working directory (cwd) that was passed to the compiler.
|
|
1010
|
+
* This value is only used if a source map is provided and relative paths needs to be
|
|
1011
|
+
* resolved to absolute ones.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {boolean | 'auto' | 'never' | 'always'} [config.color]
|
|
1014
|
+
* If true/'always', ANSI escape codes will be used for coloring the severity. If false/'never',
|
|
1015
|
+
* no coloring will be used. If 'auto', we will decide based on certain factors such
|
|
1016
|
+
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
|
|
1017
|
+
* unset.
|
|
1018
|
+
*
|
|
980
1019
|
* @returns {string}
|
|
981
1020
|
*/
|
|
982
1021
|
function messageStringMultiline( err, config = {} ) {
|
|
983
1022
|
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
984
1023
|
|
|
985
1024
|
const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
|
|
986
|
-
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
|
|
987
1025
|
const home = !err.home ? '' : (`at ${ err.home }`);
|
|
988
1026
|
const severity = err.severity || 'Error';
|
|
1027
|
+
const downgradable = config.module && severity === 'Error' &&
|
|
1028
|
+
isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
|
|
1029
|
+
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${downgradable}${ explainHelp }]` : '';
|
|
989
1030
|
|
|
990
1031
|
let location = '';
|
|
991
|
-
|
|
1032
|
+
let context = '';
|
|
1033
|
+
if (err.$location?.file) {
|
|
992
1034
|
location += locationString( err.$location, config.normalizeFilename );
|
|
993
1035
|
if (home)
|
|
994
1036
|
location += ', ';
|
|
1037
|
+
context = _messageContext(err, config);
|
|
1038
|
+
if (context !== '')
|
|
1039
|
+
context = `\n${context}`;
|
|
995
1040
|
}
|
|
996
1041
|
else if (!home) {
|
|
997
1042
|
return `${ colorTerm.severity(severity, severity + msgId) } ${ err.message }`;
|
|
998
1043
|
}
|
|
999
1044
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1045
|
+
const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
|
|
1046
|
+
const lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
|
|
1047
|
+
|
|
1048
|
+
return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }${context}`;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Used by _messageContext() to create an array of line start offsets.
|
|
1053
|
+
* Each entry in the returned array contains the offset for the start line,
|
|
1054
|
+
* where the line is the index in the array.
|
|
1055
|
+
*
|
|
1056
|
+
* @param source
|
|
1057
|
+
* @return {number[]}
|
|
1058
|
+
* @private
|
|
1059
|
+
*/
|
|
1060
|
+
function _createSourceLineMap(source) {
|
|
1061
|
+
const newlines = [ 0 ];
|
|
1062
|
+
|
|
1063
|
+
const re = new RegExp(cdlNewLineRegEx, 'g');
|
|
1064
|
+
let line;
|
|
1065
|
+
while((line = re.exec(source)) !== null) {
|
|
1066
|
+
newlines.push(line.index + line[0].length);
|
|
1004
1067
|
}
|
|
1068
|
+
newlines.push(source.length); // EOF marker
|
|
1005
1069
|
|
|
1006
|
-
return
|
|
1070
|
+
return newlines;
|
|
1007
1071
|
}
|
|
1008
1072
|
|
|
1009
1073
|
/**
|
|
1010
|
-
* Returns a context (code) string that is human
|
|
1074
|
+
* Returns a context (code) string that is human-readable (similar to rust's compiler).
|
|
1075
|
+
*
|
|
1076
|
+
* IMPORTANT: In case that `config.sourceMap[err.loc.file]` does not exist, this function
|
|
1077
|
+
* uses `path.resolve()` to get the absolute filename.
|
|
1011
1078
|
*
|
|
1012
1079
|
* Example Output:
|
|
1013
1080
|
* |
|
|
1014
1081
|
* 3 | num * nu
|
|
1015
1082
|
* | ^^
|
|
1016
1083
|
*
|
|
1017
|
-
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
|
|
1018
|
-
* from `lib/utils/file.js`
|
|
1019
1084
|
* @param {CompileMessage} err Error object containing all details like line, message, etc.
|
|
1020
|
-
* @param {object} [config = {}]
|
|
1021
|
-
*
|
|
1022
|
-
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
1023
|
-
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
1024
|
-
* unset.
|
|
1085
|
+
* @param {object} [config = {}] See `messageStringMultiline()` for details.
|
|
1086
|
+
*
|
|
1025
1087
|
* @returns {string}
|
|
1088
|
+
* @private
|
|
1026
1089
|
*/
|
|
1027
|
-
function
|
|
1028
|
-
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
1090
|
+
function _messageContext( err, config ) {
|
|
1029
1091
|
const MAX_COL_LENGTH = 100;
|
|
1030
1092
|
|
|
1031
1093
|
const loc = err.$location;
|
|
1032
|
-
if (!loc || !loc.line)
|
|
1094
|
+
if (!loc || !loc.line || !loc.file || !config.sourceMap)
|
|
1095
|
+
return '';
|
|
1096
|
+
|
|
1097
|
+
let filepath = config.sourceMap[loc.file]?.realname || loc.file;
|
|
1098
|
+
if (!config.sourceMap[filepath])
|
|
1099
|
+
filepath = path.resolve(config.cwd || '', filepath);
|
|
1100
|
+
|
|
1101
|
+
const source = config.sourceMap[filepath];
|
|
1102
|
+
if (!source || source === true) // true: file exists, no further knowledge
|
|
1033
1103
|
return '';
|
|
1034
1104
|
|
|
1105
|
+
if (!config.sourceLineMap)
|
|
1106
|
+
config.sourceLineMap = Object.create(null);
|
|
1107
|
+
if (!config.sourceLineMap[filepath])
|
|
1108
|
+
config.sourceLineMap[filepath] = _createSourceLineMap(source);
|
|
1109
|
+
|
|
1110
|
+
const sourceLines = config.sourceLineMap[filepath];
|
|
1035
1111
|
|
|
1036
1112
|
// Lines are 1-based, we need 0-based ones for arrays
|
|
1037
|
-
const startLine = loc.line - 1;
|
|
1038
|
-
const endLine = loc.endLine ? loc.endLine - 1 : startLine;
|
|
1113
|
+
const startLine = Math.min(sourceLines.length, loc.line - 1);
|
|
1114
|
+
const endLine = Math.min(sourceLines.length, loc.endLine ? loc.endLine - 1 : startLine);
|
|
1115
|
+
/** Only print N lines even if the error spans more lines. */
|
|
1116
|
+
const maxLine = Math.min((startLine + 2), endLine);
|
|
1039
1117
|
|
|
1040
1118
|
// check that source lines exists
|
|
1041
|
-
if (typeof sourceLines[startLine] !== '
|
|
1119
|
+
if (typeof sourceLines[startLine] !== 'number')
|
|
1042
1120
|
return '';
|
|
1043
1121
|
|
|
1044
|
-
|
|
1045
1122
|
const digits = String(endLine + 1).length;
|
|
1046
1123
|
const severity = err.severity || 'Error';
|
|
1047
1124
|
const indent = ' '.repeat(2 + digits);
|
|
@@ -1054,15 +1131,13 @@ function messageContext( sourceLines, err, config ) {
|
|
|
1054
1131
|
let endColumn = (loc.endCol && loc.endCol > loc.col) ? loc.endCol - 1 : loc.col;
|
|
1055
1132
|
endColumn = Math.min(MAX_COL_LENGTH, endColumn);
|
|
1056
1133
|
|
|
1057
|
-
/** Only print N lines even if the error spans more lines. */
|
|
1058
|
-
const maxLine = Math.min((startLine + 2), endLine);
|
|
1059
|
-
|
|
1060
1134
|
let msg = `${ indent }|\n`;
|
|
1061
1135
|
|
|
1062
1136
|
// print source line(s)
|
|
1063
1137
|
for (let line = startLine; line <= maxLine; line++) {
|
|
1064
1138
|
// Replaces tabs with 1 space
|
|
1065
|
-
let sourceCode = sourceLines[line]
|
|
1139
|
+
let sourceCode = source.substring(sourceLines[line], sourceLines[line+1] || source.length).trimEnd();
|
|
1140
|
+
sourceCode = sourceCode.replace(/\t/g, ' ');
|
|
1066
1141
|
if (sourceCode.length >= MAX_COL_LENGTH)
|
|
1067
1142
|
sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
|
|
1068
1143
|
// Only prepend space if the line contains any sources.
|
|
@@ -1081,7 +1156,7 @@ function messageContext( sourceLines, err, config ) {
|
|
|
1081
1156
|
}
|
|
1082
1157
|
else if (maxLine !== endLine) {
|
|
1083
1158
|
// error spans more lines which we don't print
|
|
1084
|
-
msg += `${ indent }|
|
|
1159
|
+
msg += `${ indent }| …`;
|
|
1085
1160
|
}
|
|
1086
1161
|
else {
|
|
1087
1162
|
msg += `${ indent }|`;
|
|
@@ -1090,6 +1165,36 @@ function messageContext( sourceLines, err, config ) {
|
|
|
1090
1165
|
return msg;
|
|
1091
1166
|
}
|
|
1092
1167
|
|
|
1168
|
+
/**
|
|
1169
|
+
* Returns a context (code) string that is human-readable (similar to rust's compiler)
|
|
1170
|
+
*
|
|
1171
|
+
* Example Output:
|
|
1172
|
+
* |
|
|
1173
|
+
* 3 | num * nu
|
|
1174
|
+
* | ^^
|
|
1175
|
+
*
|
|
1176
|
+
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
|
|
1177
|
+
* from `lib/utils/file.js`
|
|
1178
|
+
* @param {CompileMessage} err Error object containing all details like line, message, etc.
|
|
1179
|
+
* @param {object} [config = {}]
|
|
1180
|
+
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
|
|
1181
|
+
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
1182
|
+
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
1183
|
+
* unset.
|
|
1184
|
+
* @returns {string}
|
|
1185
|
+
*
|
|
1186
|
+
* @deprecated Use `messageStringMultiline()` with `config.sourceMap` and `config.sourceLineMap` instead!
|
|
1187
|
+
*/
|
|
1188
|
+
function messageContext( sourceLines, err, config ) {
|
|
1189
|
+
const loc = err.$location;
|
|
1190
|
+
if (!loc || !loc.line|| !loc.file)
|
|
1191
|
+
return '';
|
|
1192
|
+
|
|
1193
|
+
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
1194
|
+
const sourceMap = { [err.$location.file]: sourceLines.join('\n') };
|
|
1195
|
+
return _messageContext(err, { ...config, sourceMap });
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1093
1198
|
/**
|
|
1094
1199
|
* Compare two messages `a` and `b`. Return 0 if they are equal, 1 if `a` is
|
|
1095
1200
|
* larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
|
|
@@ -1238,6 +1343,8 @@ function memberActionName( art ) {
|
|
|
1238
1343
|
function homeName( art, absoluteOnly ) {
|
|
1239
1344
|
if (!art)
|
|
1240
1345
|
return art;
|
|
1346
|
+
if (art._user) // when providing a path item with filter as “user”
|
|
1347
|
+
return homeName( art._user, absoluteOnly );
|
|
1241
1348
|
if (art._outer) // in items property
|
|
1242
1349
|
return homeName( art._outer, absoluteOnly );
|
|
1243
1350
|
else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
|
|
@@ -1446,8 +1553,8 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1446
1553
|
result += '/target';
|
|
1447
1554
|
break;
|
|
1448
1555
|
}
|
|
1449
|
-
else if (step === 'xpr' || step === 'ref' || step === 'as') {
|
|
1450
|
-
break; // don't go into xprs, refs, aliases, etc.
|
|
1556
|
+
else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
|
|
1557
|
+
break; // don't go into xprs, refs, aliases, values, etc.
|
|
1451
1558
|
}
|
|
1452
1559
|
else if (step === 'returns') {
|
|
1453
1560
|
result += '/returns';
|
|
@@ -1675,7 +1782,6 @@ module.exports = {
|
|
|
1675
1782
|
deduplicateMessages,
|
|
1676
1783
|
CompileMessage,
|
|
1677
1784
|
CompilationError,
|
|
1678
|
-
isMessageDowngradable: isDowngradable,
|
|
1679
1785
|
explainMessage,
|
|
1680
1786
|
hasMessageExplanation,
|
|
1681
1787
|
messageIdsWithExplanation,
|
package/lib/base/model.js
CHANGED
|
@@ -22,31 +22,29 @@ const queryOps = {
|
|
|
22
22
|
const availableBetaFlags = {
|
|
23
23
|
// enabled by --beta-mode
|
|
24
24
|
annotationExpressions: true,
|
|
25
|
-
toRename: true,
|
|
26
|
-
assocsWithParams: true,
|
|
25
|
+
toRename: true, // Removes once it's publicly documented
|
|
26
|
+
assocsWithParams: true, // beta, because runtimes don't support it, yet.
|
|
27
27
|
hanaAssocRealCardinality: true,
|
|
28
|
-
mapAssocToJoinCardinality: true,
|
|
29
|
-
ignoreAssocPublishingInUnion: true,
|
|
28
|
+
mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
|
|
30
29
|
enableUniversalCsn: true,
|
|
31
|
-
postgres: true,
|
|
32
30
|
aspectWithoutElements: true, // TODO: do not just remove beta flag - remove elements, too!
|
|
33
31
|
odataTerms: true,
|
|
34
|
-
optionalActionFunctionParameters: true,
|
|
35
|
-
calculatedElementsOnWrite: true,
|
|
32
|
+
optionalActionFunctionParameters: true, // not supported by runtime, yet.
|
|
36
33
|
// disabled by --beta-mode
|
|
37
34
|
nestedServices: false,
|
|
38
|
-
v4preview: false,
|
|
39
35
|
};
|
|
40
36
|
|
|
37
|
+
// Used by isDeprecatedEnabled() to check if any flag ist set.
|
|
41
38
|
const availableDeprecatedFlags = {
|
|
42
39
|
// the old ones starting with _, : false
|
|
43
|
-
|
|
40
|
+
downgradableErrors: true,
|
|
41
|
+
includesNonShadowedFirst: true,
|
|
44
42
|
eagerPersistenceForGeneratedEntities: true,
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
const oldDeprecatedFlags_v2 = [
|
|
48
46
|
'createLocalizedViews',
|
|
49
|
-
'downgradableErrors',
|
|
47
|
+
// 'downgradableErrors', // re-added in v4
|
|
50
48
|
'generatedEntityNameWithUnderscore',
|
|
51
49
|
'longAutoexposed',
|
|
52
50
|
'noElementsExpansion',
|
|
@@ -60,13 +58,15 @@ const oldDeprecatedFlags_v2 = [
|
|
|
60
58
|
'shortAutoexposed',
|
|
61
59
|
'unmanagedUpInComponent',
|
|
62
60
|
'v1KeysForTemporal',
|
|
61
|
+
// do not add old deprecated flags which should not lead to an error:
|
|
62
|
+
// autoCorrectOrderBySourceRefs - just info would be ok
|
|
63
63
|
];
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Test for early-adaptor feature, stored in option `beta`(new-style) / `betaMode`(old-style)
|
|
67
67
|
* With that, the value of `beta` is a dictionary of feature=>Boolean.
|
|
68
68
|
*
|
|
69
|
-
* Beta features cannot be used when options.deprecated is set.
|
|
69
|
+
* Beta features cannot be used when `options.deprecated` is set.
|
|
70
70
|
*
|
|
71
71
|
* A feature always needs to be provided - otherwise false will be returned.
|
|
72
72
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @param {CSN.Element} member Member to be checked
|
|
14
14
|
*/
|
|
15
|
-
function
|
|
15
|
+
function checkCoreMediaTypeAllowance( member ) {
|
|
16
16
|
const allowedCoreMediaTypes = {
|
|
17
17
|
'cds.String': 1,
|
|
18
18
|
'cds.LargeString': 1,
|
|
@@ -67,7 +67,7 @@ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
module.exports = {
|
|
70
|
-
|
|
70
|
+
checkCoreMediaTypeAllowance,
|
|
71
71
|
checkAnalytics,
|
|
72
72
|
checkAtSapAnnotations,
|
|
73
73
|
checkReadOnlyAndInsertOnly,
|
package/lib/checks/elements.js
CHANGED
|
@@ -153,7 +153,7 @@ function checkManagedAssoc( art ) {
|
|
|
153
153
|
/**
|
|
154
154
|
* All DB & OData flat mode must reject recursive type usages in entities
|
|
155
155
|
* 'items' break recursion as 'items' will turn into an NCLOB and the path
|
|
156
|
-
* prefix to 'items' can be
|
|
156
|
+
* prefix to 'items' can be flattened in the DB.
|
|
157
157
|
* In OData flat mode the first appearance of 'items' breaks out into structured
|
|
158
158
|
* mode producing (legal) recursive complex types.
|
|
159
159
|
*
|