@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/base/messages.js
CHANGED
|
@@ -42,9 +42,10 @@ function hasErrors( messages ) {
|
|
|
42
42
|
* @param {string} moduleName
|
|
43
43
|
* @returns {boolean}
|
|
44
44
|
*/
|
|
45
|
-
function hasNonDowngradableErrors( messages, moduleName ) {
|
|
46
|
-
return messages &&
|
|
47
|
-
|
|
45
|
+
function hasNonDowngradableErrors( messages, moduleName, deprecatedDowngradable ) {
|
|
46
|
+
return messages &&
|
|
47
|
+
messages.some( m => m.severity === 'Error' &&
|
|
48
|
+
!isDowngradable( m.messageId, moduleName, deprecatedDowngradable ));
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
@@ -54,10 +55,11 @@ function hasNonDowngradableErrors( messages, moduleName ) {
|
|
|
54
55
|
*
|
|
55
56
|
* @param {string} messageId
|
|
56
57
|
* @param {string} moduleName
|
|
58
|
+
* @param {boolean} deprecatedDowngradable
|
|
57
59
|
* @returns {boolean}
|
|
58
60
|
*/
|
|
59
|
-
function isDowngradable( messageId, moduleName ) {
|
|
60
|
-
if (!centralMessages[messageId])
|
|
61
|
+
function isDowngradable( messageId, moduleName, deprecatedDowngradable ) {
|
|
62
|
+
if (!messageId || !centralMessages[messageId])
|
|
61
63
|
return false;
|
|
62
64
|
|
|
63
65
|
const msg = centralMessages[messageId];
|
|
@@ -66,10 +68,12 @@ function isDowngradable( messageId, moduleName ) {
|
|
|
66
68
|
// the module, it is NEVER downgradable.
|
|
67
69
|
if (msg.errorFor && msg.errorFor.includes(moduleName))
|
|
68
70
|
return false;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
if (msg.severity !== 'Error')
|
|
72
|
+
return true;
|
|
73
|
+
const { configurableFor } = msg;
|
|
74
|
+
return (Array.isArray( configurableFor ))
|
|
75
|
+
? configurableFor.includes( moduleName )
|
|
76
|
+
: configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
/**
|
|
@@ -136,14 +140,11 @@ class CompileMessage {
|
|
|
136
140
|
this.location = location;
|
|
137
141
|
this.$location = { ...this.location, address: undefined };
|
|
138
142
|
this.validNames = null;
|
|
139
|
-
|
|
140
|
-
this.home = home;
|
|
143
|
+
this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
|
|
141
144
|
this.severity = severity;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (moduleName)
|
|
146
|
-
Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
|
|
145
|
+
Object.defineProperty( this, 'messageId', { value: id } );
|
|
146
|
+
// this.messageId = id; // ids not yet finalized
|
|
147
|
+
Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
toString() { // should have no argument...
|
|
@@ -176,7 +177,7 @@ const severitySpecs = {
|
|
|
176
177
|
* @param {boolean} deprecatedDowngradable
|
|
177
178
|
* @returns {MessageSeverity}
|
|
178
179
|
*/
|
|
179
|
-
function reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable ) {
|
|
180
|
+
function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable ) {
|
|
180
181
|
const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
|
|
181
182
|
if (spec.severity === 'Error') {
|
|
182
183
|
const { configurableFor } = spec;
|
|
@@ -353,7 +354,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
353
354
|
|
|
354
355
|
messages.push( msg );
|
|
355
356
|
hasNewError = hasNewError || msg.severity === 'Error' &&
|
|
356
|
-
!(options.testMode &&
|
|
357
|
+
!(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
|
|
357
358
|
if (!hasMessageArray)
|
|
358
359
|
console.error( messageString( msg ) );
|
|
359
360
|
return msg;
|
|
@@ -499,7 +500,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
499
500
|
if (!messages || !messages.length)
|
|
500
501
|
return;
|
|
501
502
|
const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
|
|
502
|
-
if (hasError( messages, moduleName ))
|
|
503
|
+
if (hasError( messages, moduleName, deprecatedDowngradable ))
|
|
503
504
|
throw new CompilationError( messages, options.attachValidNames && model );
|
|
504
505
|
}
|
|
505
506
|
|
|
@@ -517,7 +518,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
517
518
|
// Re-set the module regardless of severity, since we reclassified it.
|
|
518
519
|
Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
|
|
519
520
|
hasNewError = hasNewError || severity === 'Error' &&
|
|
520
|
-
!(options.testMode &&
|
|
521
|
+
!(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
|
|
521
522
|
}
|
|
522
523
|
}
|
|
523
524
|
}
|
|
@@ -605,7 +606,7 @@ function _check$Severities( id, moduleName, severity ) {
|
|
|
605
606
|
return;
|
|
606
607
|
}
|
|
607
608
|
// now try whether the message could be something less than an Error in the module due to user wishes
|
|
608
|
-
if (!isDowngradable( id, moduleName )) { // always an error in module
|
|
609
|
+
if (!isDowngradable( id, moduleName, true )) { // always an error in module
|
|
609
610
|
if (severity !== 'Error')
|
|
610
611
|
throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
|
|
611
612
|
}
|
|
@@ -639,14 +640,12 @@ function _check$Texts( id, prop, value ) {
|
|
|
639
640
|
throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
const quote = {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// TODO: probably use keyword as function name, but its name would not have length 4 :-(
|
|
649
|
-
word: w => w.toUpperCase(), // keyword
|
|
643
|
+
const quote = { // could be an option in the future
|
|
644
|
+
double: p => `“${ p }”`, // for names, including annotation names (with preceeding `@`)
|
|
645
|
+
single: p => `‘${ p }’`, // for other things cited from the model
|
|
646
|
+
angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
|
|
647
|
+
number: p => p, // for numbers like 42
|
|
648
|
+
upper: p => p.toUpperCase(), // for keywords reported by ANTLR, use prop.single in v4
|
|
650
649
|
}
|
|
651
650
|
|
|
652
651
|
const paramsTransform = {
|
|
@@ -654,32 +653,38 @@ const paramsTransform = {
|
|
|
654
653
|
name: quoted,
|
|
655
654
|
id: quoted,
|
|
656
655
|
alias: quoted,
|
|
657
|
-
anno: a => (a.charAt(0) === '@' ? quote.
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
656
|
+
anno: a => (a.charAt(0) === '@' ? quote.double( a ) : quote.double( '@' + a )),
|
|
657
|
+
annos: anno => anno.map(paramsTransform.anno).join(', '),
|
|
658
|
+
delimited: n => '![' + n + ']', // TODO: use quote.single around?
|
|
659
|
+
file: quote.single,
|
|
660
|
+
prop: quote.single,
|
|
661
|
+
siblingprop: quote.single,
|
|
662
|
+
parentprop: quote.single,
|
|
663
|
+
otherprop: quote.single,
|
|
664
|
+
code: quote.single,
|
|
665
|
+
newcode: quote.single,
|
|
666
|
+
kind: quote.angle, // consider using text variants instead
|
|
667
|
+
keyword: quote.upper,
|
|
666
668
|
// more complex convenience:
|
|
667
669
|
names: transformManyWith( quoted ),
|
|
668
|
-
number:
|
|
669
|
-
line:
|
|
670
|
-
col:
|
|
671
|
-
|
|
670
|
+
number: quote.number,
|
|
671
|
+
line: quote.number,
|
|
672
|
+
col: quote.number,
|
|
673
|
+
value: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),
|
|
674
|
+
othervalue: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),
|
|
672
675
|
art: transformArg,
|
|
673
676
|
service: transformArg,
|
|
674
677
|
sorted_arts: transformManyWith( transformArg, true ),
|
|
675
678
|
target: transformArg,
|
|
679
|
+
source: transformArg,
|
|
676
680
|
elemref: transformElementRef,
|
|
677
681
|
type: transformArg,
|
|
678
682
|
offending: tokenSymbol,
|
|
683
|
+
op: quote.single,
|
|
679
684
|
expecting: transformManyWith( tokenSymbol ),
|
|
680
685
|
// msg: m => m,
|
|
681
686
|
$reviewed: ignoreTextTransform,
|
|
682
|
-
version: quote.
|
|
687
|
+
version: quote.single, // TODO delete: just use for OData $(VERSION), with version: 2.0
|
|
683
688
|
};
|
|
684
689
|
|
|
685
690
|
function ignoreTextTransform() {
|
|
@@ -702,20 +707,20 @@ function transformManyWith( t, sorted ) {
|
|
|
702
707
|
}
|
|
703
708
|
|
|
704
709
|
function quoted( name ) {
|
|
705
|
-
return (name) ? quote.
|
|
710
|
+
return (name) ? quote.double( name ) : quote.angle( '?' ); // TODO: failure in --test-mode, then remove
|
|
706
711
|
}
|
|
707
712
|
|
|
708
713
|
function tokenSymbol( token ) {
|
|
709
714
|
if (token.match( /^[A-Z][A-Z]/ )) // keyword
|
|
710
|
-
return quote.
|
|
715
|
+
return quote.upper( token );
|
|
711
716
|
else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...
|
|
712
|
-
return quote.
|
|
717
|
+
return quote.angle( token );
|
|
713
718
|
if (token.startsWith("'") && token.endsWith("'")) // operator token symbol
|
|
714
|
-
return quote.
|
|
719
|
+
return quote.single( token.slice( 1, -1 ));
|
|
715
720
|
else if (token === '<EOF>')
|
|
716
|
-
return quote.
|
|
721
|
+
return quote.angle( 'EOF' );
|
|
717
722
|
else
|
|
718
|
-
return quote.
|
|
723
|
+
return quote.single( token ); // should not happen
|
|
719
724
|
}
|
|
720
725
|
|
|
721
726
|
/**
|
|
@@ -801,6 +806,7 @@ function messageText( texts, params, transform ) {
|
|
|
801
806
|
}
|
|
802
807
|
|
|
803
808
|
function replaceInString( text, params ) {
|
|
809
|
+
const usedParams = [ '#', '$reviewed' ];
|
|
804
810
|
let pattern = /\$\(([A-Z_]+)\)/g;
|
|
805
811
|
let parts = [];
|
|
806
812
|
let start = 0;
|
|
@@ -808,11 +814,11 @@ function replaceInString( text, params ) {
|
|
|
808
814
|
let prop = p[1].toLowerCase();
|
|
809
815
|
parts.push( text.substring( start, p.index ),
|
|
810
816
|
(prop in params ? params[prop] : p[0]) );
|
|
811
|
-
|
|
817
|
+
usedParams.push(prop);
|
|
812
818
|
start = pattern.lastIndex;
|
|
813
819
|
}
|
|
814
820
|
parts.push( text.substring( start ) );
|
|
815
|
-
let remain = (params['#']) ? [] : Object.keys( params ).filter( n =>
|
|
821
|
+
let remain = (params['#']) ? [] : Object.keys( params ).filter( n => !usedParams.includes(n) );
|
|
816
822
|
return (remain.length)
|
|
817
823
|
? parts.join('') + '; ' +
|
|
818
824
|
remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
|
|
@@ -1024,6 +1030,7 @@ function compareMessage( a, b ) {
|
|
|
1024
1030
|
c( aEnd.endLine, bEnd.endLine ) ||
|
|
1025
1031
|
c( aEnd.endCol, bEnd.endCol ) ||
|
|
1026
1032
|
c( homeSortName( a ), homeSortName( b ) ) ||
|
|
1033
|
+
// TODO: severities?
|
|
1027
1034
|
c( a.message, b.message ) );
|
|
1028
1035
|
}
|
|
1029
1036
|
else if (!aFile && !bFile)
|
|
@@ -1107,7 +1114,7 @@ function shortArtName( art ) {
|
|
|
1107
1114
|
const { name } = art;
|
|
1108
1115
|
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
|
|
1109
1116
|
!name.absolute.includes(':'))
|
|
1110
|
-
return quote.
|
|
1117
|
+
return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
|
|
1111
1118
|
return artName( art );
|
|
1112
1119
|
}
|
|
1113
1120
|
|
|
@@ -1172,7 +1179,9 @@ function homeNameForExtend( art ) {
|
|
|
1172
1179
|
|
|
1173
1180
|
// Surrounding parent may be another extension.
|
|
1174
1181
|
const parent = art._parent;
|
|
1175
|
-
if (!parent)
|
|
1182
|
+
if (!parent && art.name.absolute)
|
|
1183
|
+
return kind + ':' + artName(removeBlock(art));
|
|
1184
|
+
else if (!parent)
|
|
1176
1185
|
return kind + ':' + quoted(absoluteName);
|
|
1177
1186
|
|
|
1178
1187
|
if (art.name.param && parent.params) {
|
|
@@ -1199,6 +1208,14 @@ function homeNameForExtend( art ) {
|
|
|
1199
1208
|
}
|
|
1200
1209
|
// This case should not happen, but just in case
|
|
1201
1210
|
return kind + ':' + artName(parent);
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Remove `select` from the 'art's name to avoid `block:1` in artName().
|
|
1214
|
+
* @TODO: Refactor homeNameForExtend (and possibly artName) to get rid of this function
|
|
1215
|
+
**/
|
|
1216
|
+
function removeBlock(obj) {
|
|
1217
|
+
return { ...obj, name: { ...obj.name, select: null } };
|
|
1218
|
+
}
|
|
1202
1219
|
}
|
|
1203
1220
|
|
|
1204
1221
|
function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
@@ -1220,11 +1237,15 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1220
1237
|
|
|
1221
1238
|
let { query } = analyseCsnPath(csnPath, model, false);
|
|
1222
1239
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1240
|
+
const dictName = csnPath.shift();
|
|
1241
|
+
const dict = model[dictName];
|
|
1225
1242
|
const artifactName = csnPath.shift();
|
|
1226
|
-
let currentThing =
|
|
1227
|
-
let result = `${ (currentThing
|
|
1243
|
+
let currentThing = dict ? dict[artifactName] : undefined;
|
|
1244
|
+
let result = `${ (currentThing?.kind)
|
|
1245
|
+
? currentThing.kind
|
|
1246
|
+
: (dictName === 'vocabularies'
|
|
1247
|
+
? 'annotation'
|
|
1248
|
+
: 'artifact') }:${ _quoted(artifactName) }`;
|
|
1228
1249
|
|
|
1229
1250
|
if (!currentThing)
|
|
1230
1251
|
return result;
|
package/lib/base/model.js
CHANGED
|
@@ -26,8 +26,9 @@ const availableBetaFlags = {
|
|
|
26
26
|
hanaAssocRealCardinality: true,
|
|
27
27
|
mapAssocToJoinCardinality: true,
|
|
28
28
|
ignoreAssocPublishingInUnion: true,
|
|
29
|
-
nestedProjections: true,
|
|
30
29
|
enableUniversalCsn: true,
|
|
30
|
+
postgres: true,
|
|
31
|
+
aspectWithoutElements: true,
|
|
31
32
|
// disabled by --beta-mode
|
|
32
33
|
nestedServices: false,
|
|
33
34
|
odataOpenType: true,
|
|
@@ -22,7 +22,7 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
22
22
|
|
|
23
23
|
const serviceName = this.csnUtils.getServiceName(artName);
|
|
24
24
|
if (!serviceName)
|
|
25
|
-
this.warning(null, path,
|
|
25
|
+
this.warning(null, path, {}, 'Functions and actions must be declared in a service');
|
|
26
26
|
|
|
27
27
|
if (art.kind === 'entity') {
|
|
28
28
|
for (const [ actName, act ] of Object.entries(art.actions)) {
|
|
@@ -36,7 +36,7 @@ function checkCoreMediaTypeAllowence(member) {
|
|
|
36
36
|
*/
|
|
37
37
|
function checkAnalytics(member) {
|
|
38
38
|
if (member['@Analytics.Measure'] && !member['@Aggregation.default']) {
|
|
39
|
-
this.info(null, member.$path,
|
|
39
|
+
this.info(null, member.$path, {},
|
|
40
40
|
'Annotation “@Analytics.Measure” expects “@Aggregation.default” to be assigned for the same element as well');
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -63,7 +63,7 @@ function checkReadOnlyAndInsertOnly(artifact, artifactName) {
|
|
|
63
63
|
if (!this.csnUtils.getServiceName(artifactName))
|
|
64
64
|
return;
|
|
65
65
|
if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
|
|
66
|
-
this.warning(null, artifact.$path, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
|
|
66
|
+
this.warning(null, artifact.$path, {}, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
module.exports = {
|
package/lib/checks/arrayOfs.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
|
|
3
5
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -24,20 +26,26 @@ function validateAssociationsInItems(member) {
|
|
|
24
26
|
if (element.on) { // Unmanaged association
|
|
25
27
|
// Unmanaged associations are always forbidden for now
|
|
26
28
|
// TODO: Check if the on-condition only references things inside of the .items
|
|
27
|
-
this.error(null, member.$path, 'Unmanaged associations in "array of" or "many" are not allowed');
|
|
29
|
+
this.error(null, member.$path, {}, 'Unmanaged associations in "array of" or "many" are not allowed');
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
35
|
if (this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items && member.$path[2] === 'elements') {
|
|
34
|
-
if (member.items.type
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if (member.items.type) {
|
|
37
|
+
const type = member.items.type.ref
|
|
38
|
+
? this.artifactRef(member.items.type)
|
|
39
|
+
: this.csn.definitions[member.items.type];
|
|
40
|
+
if (type && !type.$visited) {
|
|
41
|
+
setProp(type, '$visited', true);
|
|
42
|
+
validate(type);
|
|
43
|
+
delete type.$visited;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
40
47
|
validate(member.items);
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -17,7 +17,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
|
|
|
17
17
|
// TODO: Why not filter over persistenceAnnos, is shorter!
|
|
18
18
|
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
|
|
19
19
|
if (TableUdfCv.length > 1)
|
|
20
|
-
this.error(null, path,
|
|
20
|
+
this.error(null, path, { annos: TableUdfCv }, 'Annotations $(ANNOS) can\'t be used in combination');
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that `cds.hana` types are not used - we don't support them for postgres
|
|
7
|
+
*
|
|
8
|
+
* @param {object} parent Object with a type
|
|
9
|
+
* @param {string} name Name of the type property on parent
|
|
10
|
+
* @param {Array} type type to check
|
|
11
|
+
* @param {CSN.Path} path
|
|
12
|
+
*/
|
|
13
|
+
function checkForHanaTypes(parent, name, type, path) {
|
|
14
|
+
const artifact = this.csn.definitions[path[1]];
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && typeof parent.type === 'string' && parent.type.startsWith('cds.hana.')) {
|
|
16
|
+
this.error('ref-unexpected-hana-type', [ ...path, 'type' ], { type: 'cds.hana', value: this.options.sqlDialect },
|
|
17
|
+
'Types in the $(TYPE) namespace can\'t be used with sqlDialect $(VALUE)');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check that `cds.UInt8` is not used - we don't have a clear idea how to represent it on postgres and h2
|
|
23
|
+
*
|
|
24
|
+
* @param {object} parent Object with a type
|
|
25
|
+
* @param {string} name Name of the type property on parent
|
|
26
|
+
* @param {Array} type type to check
|
|
27
|
+
* @param {CSN.Path} path
|
|
28
|
+
*/
|
|
29
|
+
function CheckForUInt8(parent, name, type, path) {
|
|
30
|
+
const artifact = this.csn.definitions[path[1]];
|
|
31
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.type === 'cds.UInt8') {
|
|
32
|
+
this.error('ref-unexpected-type', [ ...path, 'type' ], { type: 'cds.UInt8', value: this.options.sqlDialect },
|
|
33
|
+
'Type $(TYPE) can\'t be used with sqlDialect $(VALUE)');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check types - specifically for postgres and h2
|
|
39
|
+
*
|
|
40
|
+
* @param {object} parent Object with a type
|
|
41
|
+
* @param {string} name Name of the type property on parent
|
|
42
|
+
* @param {Array} type type to check
|
|
43
|
+
* @param {CSN.Path} path
|
|
44
|
+
*/
|
|
45
|
+
function checkTypes(parent, name, type, path) {
|
|
46
|
+
checkForHanaTypes.bind(this)(parent, name, type, path);
|
|
47
|
+
if (this.options.sqlDialect === 'postgres' || this.options.sqlDialect === 'h2')
|
|
48
|
+
CheckForUInt8.bind(this)(parent, name, type, path);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
type: checkTypes,
|
|
53
|
+
};
|
|
@@ -20,8 +20,10 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
20
20
|
// consume all unary signs
|
|
21
21
|
while (member.default.xpr[i] === '-' || member.default.xpr[i] === '+')
|
|
22
22
|
i++;
|
|
23
|
+
// TODO: This check only counts the number of leading signs, not inbetween (e.g. 1 - - 1).
|
|
24
|
+
// The message also needs to be improved.
|
|
23
25
|
if (i > 1)
|
|
24
|
-
this.error(null, path,
|
|
26
|
+
this.error(null, path, {}, 'Illegal number of unary ‘+’/‘-’ operators');
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -36,7 +38,7 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
36
38
|
*/
|
|
37
39
|
function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
|
|
38
40
|
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
|
|
39
|
-
this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
|
|
41
|
+
this.error(null, path, {}, 'Parameter default values are not supported in SAP HANA CDS');
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
package/lib/checks/elements.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
|
|
3
|
+
const { forEachMember, forEachMemberRecursively, isBuiltinType } = require('../model/csnUtils');
|
|
4
4
|
const { isGeoTypeName } = require('../compiler/builtins');
|
|
5
|
-
|
|
5
|
+
const { setProp } = require('../base/model');
|
|
6
6
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -42,13 +42,15 @@ function checkPrimaryKey(art) {
|
|
|
42
42
|
{ type: finalBaseType.type, name: elemFqName },
|
|
43
43
|
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
|
|
44
44
|
}
|
|
45
|
-
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
45
|
+
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
|
|
46
|
+
setProp(finalBaseType, '$visited', true);
|
|
46
47
|
forEachMemberRecursively(finalBaseType,
|
|
47
48
|
(subMember, subMemberName) => checkIfPrimaryKeyIsOfGeoType
|
|
48
49
|
.bind(this)(subMember,
|
|
49
50
|
`${ elemFqName }/${ subMemberName }`,
|
|
50
51
|
member.key || parentIsKey,
|
|
51
52
|
member.$path));
|
|
53
|
+
delete finalBaseType.$visited;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -68,13 +70,15 @@ function checkPrimaryKey(art) {
|
|
|
68
70
|
this.error(null, parentPath || member.$path, { name: elemFqName },
|
|
69
71
|
'Array-like type in element $(NAME) can\'t be used as primary key');
|
|
70
72
|
}
|
|
71
|
-
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
73
|
+
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
|
|
74
|
+
setProp(finalBaseType, '$visited', true);
|
|
72
75
|
forEachMemberRecursively(finalBaseType,
|
|
73
76
|
(subMember, subMemberName) => checkIfPrimaryKeyIsArray
|
|
74
77
|
.bind(this)(subMember,
|
|
75
78
|
`${ elemFqName }/${ subMemberName }`,
|
|
76
79
|
member.key || parentIsKey,
|
|
77
80
|
member.$path));
|
|
81
|
+
delete finalBaseType.$visited;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -89,7 +93,7 @@ function checkPrimaryKey(art) {
|
|
|
89
93
|
function checkVirtualElement(member) {
|
|
90
94
|
if (member.virtual) {
|
|
91
95
|
if (this.csnUtils.isAssociation(member.type)) { // or Composition ???
|
|
92
|
-
this.error(null, member.$path,
|
|
96
|
+
this.error(null, member.$path, {}, 'Element can\'t be virtual and an association');
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
}
|
|
@@ -135,4 +139,75 @@ function checkManagedAssoc(art) {
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
/**
|
|
143
|
+
* All DB & OData flat mode must reject recursive type usages in entities
|
|
144
|
+
* 'items' break recursion as 'items' will turn into an NCLOB and the path
|
|
145
|
+
* prefix to 'items' can be flattend in the DB.
|
|
146
|
+
* In OData flat mode the first appearance of 'items' breaks out into structured
|
|
147
|
+
* mode producing (legal) recursive complex types.
|
|
148
|
+
*
|
|
149
|
+
* @param {CSN.Artifact} art The artifact
|
|
150
|
+
*/
|
|
151
|
+
function checkRecursiveTypeUsage(art) {
|
|
152
|
+
const visit = (def) => {
|
|
153
|
+
const loc = def.$path;
|
|
154
|
+
// recursive types are allowed inside arrays
|
|
155
|
+
if (def.items)
|
|
156
|
+
return;
|
|
157
|
+
let { type } = def;
|
|
158
|
+
let prevType;
|
|
159
|
+
let isDeref = false;
|
|
160
|
+
if (type && !isBuiltinType(type) && !def.elements) {
|
|
161
|
+
do {
|
|
162
|
+
prevType = type;
|
|
163
|
+
// TODO: `type.ref.length > 1`, but OData backend must be tested first (#5144)
|
|
164
|
+
// e.g. `{ ref: [ "MyType" ] }`
|
|
165
|
+
if (type.ref) {
|
|
166
|
+
def = this.artifactRef(type);
|
|
167
|
+
isDeref = true;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
def = this.csn.definitions[type];
|
|
171
|
+
}
|
|
172
|
+
type = def.type;
|
|
173
|
+
} while (type && !isBuiltinType(type) && !def.items && !def.elements && prevType !== type);
|
|
174
|
+
}
|
|
175
|
+
if (def.$visited || (type && prevType === type)) {
|
|
176
|
+
// Recursion via type is allowed in V4 struct, but not via dereferencing
|
|
177
|
+
if (!isDeref && this.options.odataVersion === 'v4' && this.options.odataFormat === 'structured')
|
|
178
|
+
return;
|
|
179
|
+
if (!def.$recErr) {
|
|
180
|
+
this.error(null, loc, {}, 'Unexpected recursive type definition');
|
|
181
|
+
setProp(def, '$recErr', true);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (def.elements) {
|
|
185
|
+
setProp(def, '$visited', true);
|
|
186
|
+
for (const n in def.elements)
|
|
187
|
+
visit(def.elements[n]);
|
|
188
|
+
delete def.$visited;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
// elements & params are flattening candidates
|
|
192
|
+
// FUTURE:
|
|
193
|
+
// Once we have universal CSN for the runtimes
|
|
194
|
+
// Validate service members only for OData
|
|
195
|
+
if (art.kind === 'entity') {
|
|
196
|
+
for (const n in art.elements)
|
|
197
|
+
visit(art.elements[n]);
|
|
198
|
+
for (const n in art.params)
|
|
199
|
+
visit(art.params[n]);
|
|
200
|
+
}
|
|
201
|
+
if (this.options.odataVersion) {
|
|
202
|
+
// func/action params/returns don't allow recursive type derefs
|
|
203
|
+
if (art.kind === 'action' || art.kind === 'function') {
|
|
204
|
+
for (const n in art.params)
|
|
205
|
+
visit(art.params[n]);
|
|
206
|
+
if (art.returns)
|
|
207
|
+
visit(art.returns);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
module.exports = {
|
|
212
|
+
checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
|
|
213
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
|
|
3
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -41,10 +43,10 @@ function validateForeignKeys(member) {
|
|
|
41
43
|
// Declared as arrow-function to keep scope the same (this value)
|
|
42
44
|
const checkForItems = (mem) => {
|
|
43
45
|
if (mem.items) {
|
|
44
|
-
this.error(null, member.$path, 'Array-like properties must not be foreign keys');
|
|
46
|
+
this.error(null, member.$path, {}, 'Array-like properties must not be foreign keys');
|
|
45
47
|
}
|
|
46
48
|
else if (isUnmanagedAssoc(mem)) {
|
|
47
|
-
this.error(null, member.$path, 'Unmanaged association must not be a foreign key');
|
|
49
|
+
this.error(null, member.$path, {}, 'Unmanaged association must not be a foreign key');
|
|
48
50
|
}
|
|
49
51
|
else if (mem.keys) {
|
|
50
52
|
handleAssociation(mem);
|
|
@@ -52,17 +54,14 @@ function validateForeignKeys(member) {
|
|
|
52
54
|
else if (mem.elements) {
|
|
53
55
|
handleStructured(mem);
|
|
54
56
|
}
|
|
55
|
-
else if (mem.type
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else if (type.elements)
|
|
65
|
-
handleStructured(type);
|
|
57
|
+
else if (mem.type) {
|
|
58
|
+
const type = mem.type.ref
|
|
59
|
+
? this.artifactRef(mem.type)
|
|
60
|
+
: this.csn.definitions[mem.type];
|
|
61
|
+
if (type && !type.$visited) {
|
|
62
|
+
setProp(type, '$visited', true);
|
|
63
|
+
checkForItems(type);
|
|
64
|
+
delete type.$visited;
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
};
|