@sap/cds-compiler 3.4.2 → 3.5.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 +80 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +15 -16
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +2 -2
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +61 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +177 -58
- package/lib/base/messages.js +252 -180
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/builtins.js +14 -14
- package/lib/compiler/checks.js +123 -48
- package/lib/compiler/define.js +12 -13
- package/lib/compiler/extend.js +266 -60
- package/lib/compiler/finalize-parse-cdl.js +10 -5
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +14 -6
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +27 -16
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +20 -0
- package/lib/edm/annotations/genericTranslation.js +604 -358
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +275 -222
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +6 -6
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +107 -77
- package/lib/edm/edmUtils.js +44 -5
- package/lib/gen/Dictionary.json +210 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +67 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14309 -13832
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +102 -55
- package/lib/json/to-csn.js +119 -198
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +113 -133
- package/lib/language/language.g4 +1550 -1506
- package/lib/language/multiLineStringParser.js +3 -3
- package/lib/language/textUtils.js +2 -2
- package/lib/main.js +3 -3
- package/lib/model/csnRefs.js +5 -0
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +100 -0
- package/lib/optionProcessor.js +5 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +311 -276
- package/lib/render/toHdbcds.js +97 -94
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +127 -223
- package/lib/render/utils/common.js +141 -108
- package/lib/render/utils/delta.js +227 -0
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +8 -29
- package/lib/transform/forRelationalDB.js +16 -6
- package/lib/transform/localized.js +11 -10
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +113 -47
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +17 -10
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -8
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
- package/lib/modelCompare/filter.js +0 -83
|
@@ -404,7 +404,6 @@ function assertConsistency( model, stage ) {
|
|
|
404
404
|
// TODO: "yes" instead "none": val: true, optional literal/location
|
|
405
405
|
val: {
|
|
406
406
|
requires: [ 'literal', 'location' ],
|
|
407
|
-
// TODO: rename symbol to sym
|
|
408
407
|
// TODO: struct and variant only for annotation assignments
|
|
409
408
|
optional: [
|
|
410
409
|
'val', 'sym', 'name', '$inferred', '$parens',
|
|
@@ -418,7 +417,6 @@ function assertConsistency( model, stage ) {
|
|
|
418
417
|
'args',
|
|
419
418
|
'func',
|
|
420
419
|
'suffix',
|
|
421
|
-
'quantifier',
|
|
422
420
|
'$inferred',
|
|
423
421
|
'$parens',
|
|
424
422
|
'_artifact', // _artifact with "localized data"s 'coalesce'
|
|
@@ -441,7 +439,7 @@ function assertConsistency( model, stage ) {
|
|
|
441
439
|
test: isVal, // the following for array/struct value
|
|
442
440
|
requires: [ 'location' ],
|
|
443
441
|
optional: [
|
|
444
|
-
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$
|
|
442
|
+
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
445
443
|
],
|
|
446
444
|
// TODO: restrict path to #simplePath
|
|
447
445
|
},
|
|
@@ -462,7 +460,10 @@ function assertConsistency( model, stage ) {
|
|
|
462
460
|
join: { test: locationVal( isString ) },
|
|
463
461
|
quantifier: { test: locationVal( isString ) },
|
|
464
462
|
// preliminary -----------------------------------------------------------
|
|
465
|
-
doc: {
|
|
463
|
+
doc: {
|
|
464
|
+
kind: true,
|
|
465
|
+
test: locationVal( isStringOrNull ),
|
|
466
|
+
}, // doc comment
|
|
466
467
|
'@': {
|
|
467
468
|
kind: true,
|
|
468
469
|
inherits: 'value',
|
|
@@ -888,7 +889,7 @@ function assertConsistency( model, stage ) {
|
|
|
888
889
|
return function valWithLocation( node, parent, prop, spec, name ) {
|
|
889
890
|
const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
|
|
890
891
|
const requires = [ 'val', 'location' ];
|
|
891
|
-
const optional = [ 'literal', '$inferred', '_pathHead' ];
|
|
892
|
+
const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
|
|
892
893
|
standard( node, parent, prop, { schema: valSchema, requires, optional }, name );
|
|
893
894
|
};
|
|
894
895
|
}
|
|
@@ -912,7 +913,7 @@ function assertConsistency( model, stage ) {
|
|
|
912
913
|
isString(node, parent, prop, spec);
|
|
913
914
|
}
|
|
914
915
|
|
|
915
|
-
function isOneOf(values) {
|
|
916
|
+
function isOneOf( values ) {
|
|
916
917
|
return function isOneOfInner( node, parent, prop ) {
|
|
917
918
|
if (!values.includes(node))
|
|
918
919
|
throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
|
|
@@ -959,7 +960,7 @@ function assertConsistency( model, stage ) {
|
|
|
959
960
|
}
|
|
960
961
|
}
|
|
961
962
|
|
|
962
|
-
function isScope(node, parent, prop) {
|
|
963
|
+
function isScope( node, parent, prop ) {
|
|
963
964
|
// artifact refs in CDL have scope:0 in XSN
|
|
964
965
|
if (Number.isInteger(node))
|
|
965
966
|
return;
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -259,7 +259,7 @@ const quotedLiteralPatterns = {
|
|
|
259
259
|
*
|
|
260
260
|
* @returns {boolean} True if the date is valid.
|
|
261
261
|
*/
|
|
262
|
-
function checkDate(year, month, day) {
|
|
262
|
+
function checkDate( year, month, day ) {
|
|
263
263
|
// Negative years are allowed
|
|
264
264
|
year = Math.abs(Number.parseInt(year, 10));
|
|
265
265
|
month = Number.parseInt(month, 10);
|
|
@@ -275,7 +275,7 @@ function checkDate(year, month, day) {
|
|
|
275
275
|
*
|
|
276
276
|
* @returns {boolean} True if the date is valid.
|
|
277
277
|
*/
|
|
278
|
-
function checkTime(hour, minutes, seconds) {
|
|
278
|
+
function checkTime( hour, minutes, seconds ) {
|
|
279
279
|
hour = Number.parseInt(hour, 10);
|
|
280
280
|
minutes = Number.parseInt(minutes, 10);
|
|
281
281
|
seconds = seconds ? Number.parseInt(seconds, 10) : 0;
|
|
@@ -311,35 +311,35 @@ Object.keys(coreHana).forEach((type) => {
|
|
|
311
311
|
});
|
|
312
312
|
|
|
313
313
|
/** @param {string} typeName */
|
|
314
|
-
function isIntegerTypeName(typeName) {
|
|
314
|
+
function isIntegerTypeName( typeName ) {
|
|
315
315
|
return typeCategories.integer.includes(typeName);
|
|
316
316
|
}
|
|
317
317
|
/** @param {string} typeName */
|
|
318
|
-
function isDecimalTypeName(typeName) {
|
|
318
|
+
function isDecimalTypeName( typeName ) {
|
|
319
319
|
return typeCategories.decimal.includes(typeName);
|
|
320
320
|
}
|
|
321
321
|
/** @param {string} typeName */
|
|
322
|
-
function isNumericTypeName(typeName) {
|
|
322
|
+
function isNumericTypeName( typeName ) {
|
|
323
323
|
return isIntegerTypeName(typeName) || isDecimalTypeName(typeName);
|
|
324
324
|
}
|
|
325
325
|
/** @param {string} typeName */
|
|
326
|
-
function isStringTypeName(typeName) {
|
|
326
|
+
function isStringTypeName( typeName ) {
|
|
327
327
|
return typeCategories.string.includes(typeName);
|
|
328
328
|
}
|
|
329
329
|
/** @param {string} typeName */
|
|
330
|
-
function isDateOrTimeTypeName(typeName) {
|
|
330
|
+
function isDateOrTimeTypeName( typeName ) {
|
|
331
331
|
return typeCategories.dateTime.includes(typeName);
|
|
332
332
|
}
|
|
333
333
|
/** @param {string} typeName */
|
|
334
|
-
function isBooleanTypeName(typeName) {
|
|
334
|
+
function isBooleanTypeName( typeName ) {
|
|
335
335
|
return typeCategories.boolean.includes(typeName);
|
|
336
336
|
}
|
|
337
337
|
/** @param {string} typeName */
|
|
338
|
-
function isBinaryTypeName(typeName) {
|
|
338
|
+
function isBinaryTypeName( typeName ) {
|
|
339
339
|
return typeCategories.binary.includes(typeName);
|
|
340
340
|
}
|
|
341
341
|
/** @param {string} typeName */
|
|
342
|
-
function isGeoTypeName(typeName) {
|
|
342
|
+
function isGeoTypeName( typeName ) {
|
|
343
343
|
return typeCategories.geo.includes(typeName);
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
@@ -347,7 +347,7 @@ function isGeoTypeName(typeName) {
|
|
|
347
347
|
*
|
|
348
348
|
* @param {string} typeName
|
|
349
349
|
*/
|
|
350
|
-
function isRelationTypeName(typeName) {
|
|
350
|
+
function isRelationTypeName( typeName ) {
|
|
351
351
|
return typeCategories.relation.includes(typeName);
|
|
352
352
|
}
|
|
353
353
|
|
|
@@ -357,7 +357,7 @@ function isRelationTypeName(typeName) {
|
|
|
357
357
|
* @param {string} absolute
|
|
358
358
|
* @returns {boolean}
|
|
359
359
|
*/
|
|
360
|
-
function isInReservedNamespace(absolute) {
|
|
360
|
+
function isInReservedNamespace( absolute ) {
|
|
361
361
|
return absolute.startsWith( 'cds.') &&
|
|
362
362
|
!absolute.match(/^cds\.foundation(\.|$)/) &&
|
|
363
363
|
!absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
|
|
@@ -375,7 +375,7 @@ function isInReservedNamespace(absolute) {
|
|
|
375
375
|
* @param {string} type
|
|
376
376
|
* @returns {boolean}
|
|
377
377
|
*/
|
|
378
|
-
function isBuiltinType(type) {
|
|
378
|
+
function isBuiltinType( type ) {
|
|
379
379
|
return typeof type === 'string' && isInReservedNamespace(type);
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -451,7 +451,7 @@ function initBuiltins( model ) {
|
|
|
451
451
|
for (const name in builtins) {
|
|
452
452
|
const magic = builtins[name];
|
|
453
453
|
// TODO: rename to $builtinFunction
|
|
454
|
-
const art = { kind: 'builtin', name: {
|
|
454
|
+
const art = { kind: 'builtin', name: { id: name, absolute: name } };
|
|
455
455
|
artifacts[name] = art;
|
|
456
456
|
|
|
457
457
|
if (magic.$autoElement)
|
package/lib/compiler/checks.js
CHANGED
|
@@ -23,10 +23,12 @@ const { pathName } = require('./utils');
|
|
|
23
23
|
|
|
24
24
|
function check( model ) { // = XSN
|
|
25
25
|
const {
|
|
26
|
-
error, warning, message,
|
|
26
|
+
error, warning, message, info,
|
|
27
27
|
} = model.$messageFunctions;
|
|
28
28
|
forEachDefinition( model, checkArtifact );
|
|
29
|
-
|
|
29
|
+
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
30
|
+
checkSapCommonLocale( model );
|
|
31
|
+
checkSapCommonTextsAspects( model );
|
|
30
32
|
return;
|
|
31
33
|
|
|
32
34
|
function checkArtifact( art ) {
|
|
@@ -36,6 +38,11 @@ function check( model ) { // = XSN
|
|
|
36
38
|
art.$queries.forEach( checkQuery );
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
function checkAnnotationDefinition( art ) {
|
|
42
|
+
checkEnumType( art );
|
|
43
|
+
// TODO: Should we check elements similar to definition-elements as well?
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
function checkGenericConstruct( art ) {
|
|
40
47
|
checkName( art );
|
|
41
48
|
if (model.vocabularies) {
|
|
@@ -80,14 +87,15 @@ function check( model ) { // = XSN
|
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
|
|
83
|
-
function checkLocalizedElement(elem) {
|
|
90
|
+
function checkLocalizedElement( elem ) {
|
|
84
91
|
// if it is directly a localized element
|
|
85
92
|
if (elem.localized && elem.localized.val) {
|
|
86
93
|
const type = elem._effectiveType;
|
|
87
94
|
// See discussion issue #6520: should we allow all scalar types?
|
|
88
95
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
info('ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
97
|
+
{ keyword: 'localized' },
|
|
98
|
+
'Expecting a string type in combination with keyword $(KEYWORD)');
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
// "key" keyword at localized element in SELECT list.
|
|
@@ -151,7 +159,7 @@ function check( model ) { // = XSN
|
|
|
151
159
|
*
|
|
152
160
|
* @param {XSN.Artifact} enumNode
|
|
153
161
|
*/
|
|
154
|
-
function checkEnum(enumNode) {
|
|
162
|
+
function checkEnum( enumNode ) {
|
|
155
163
|
if (!enumNode.value)
|
|
156
164
|
return;
|
|
157
165
|
|
|
@@ -167,7 +175,7 @@ function check( model ) { // = XSN
|
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
function checkEnumType(enumNode) {
|
|
178
|
+
function checkEnumType( enumNode ) {
|
|
171
179
|
// Either the type is an enum or an arrayed enum. We are only interested in
|
|
172
180
|
// the enum and don't care whether the enum is arrayed.
|
|
173
181
|
enumNode = enumNode.enum ? enumNode : enumNode.items;
|
|
@@ -222,7 +230,7 @@ function check( model ) { // = XSN
|
|
|
222
230
|
*
|
|
223
231
|
* @param {XSN.Definition} enumNode
|
|
224
232
|
*/
|
|
225
|
-
function checkEnumValue(enumNode) {
|
|
233
|
+
function checkEnumValue( enumNode ) {
|
|
226
234
|
const type = enumNode.type && enumNode.type._artifact &&
|
|
227
235
|
enumNode.type._artifact._effectiveType;
|
|
228
236
|
if (!enumNode.enum || !type || !type.builtin)
|
|
@@ -293,7 +301,7 @@ function check( model ) { // = XSN
|
|
|
293
301
|
*
|
|
294
302
|
* @param {XSN.Artifact} element
|
|
295
303
|
*/
|
|
296
|
-
function checkLocalizedSubElement(element) {
|
|
304
|
+
function checkLocalizedSubElement( element ) {
|
|
297
305
|
if (element._parent.kind !== 'element')
|
|
298
306
|
return;
|
|
299
307
|
|
|
@@ -310,7 +318,7 @@ function check( model ) { // = XSN
|
|
|
310
318
|
return;
|
|
311
319
|
|
|
312
320
|
// TODO: Recursive check
|
|
313
|
-
function isTypeLocalized(type) {
|
|
321
|
+
function isTypeLocalized( type ) {
|
|
314
322
|
return (type && type.localized && type.localized.val);
|
|
315
323
|
}
|
|
316
324
|
}
|
|
@@ -321,7 +329,7 @@ function check( model ) { // = XSN
|
|
|
321
329
|
*
|
|
322
330
|
* @param {any} element Element to check recursively
|
|
323
331
|
*/
|
|
324
|
-
function checkForUnmanagedAssociations(element, keyObj) {
|
|
332
|
+
function checkForUnmanagedAssociations( element, keyObj ) {
|
|
325
333
|
if (element.targetAspect) {
|
|
326
334
|
// TODO: bad location / message
|
|
327
335
|
message('composition-as-key', [ keyObj.location, element ], {},
|
|
@@ -342,7 +350,7 @@ function check( model ) { // = XSN
|
|
|
342
350
|
|
|
343
351
|
// Check that min and max cardinalities of 'elem' in 'art' have legal values
|
|
344
352
|
// TODO: move to define.js or parsers
|
|
345
|
-
function checkCardinality(elem) {
|
|
353
|
+
function checkCardinality( elem ) {
|
|
346
354
|
if (!elem.cardinality)
|
|
347
355
|
return;
|
|
348
356
|
|
|
@@ -352,13 +360,13 @@ function check( model ) { // = XSN
|
|
|
352
360
|
const { literal, val, location } = elem.cardinality[prop];
|
|
353
361
|
if (!(literal === 'number' && val > 0 ||
|
|
354
362
|
literal === 'string' && val === '*')) {
|
|
355
|
-
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
363
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val, newcode: '*' }, {
|
|
356
364
|
// eslint-disable-next-line max-len
|
|
357
|
-
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or
|
|
365
|
+
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or $(NEWCODE)',
|
|
358
366
|
// eslint-disable-next-line max-len
|
|
359
|
-
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or
|
|
367
|
+
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or $(NEWCODE)',
|
|
360
368
|
// eslint-disable-next-line max-len
|
|
361
|
-
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or
|
|
369
|
+
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or $(NEWCODE)',
|
|
362
370
|
});
|
|
363
371
|
}
|
|
364
372
|
}
|
|
@@ -444,7 +452,7 @@ function check( model ) { // = XSN
|
|
|
444
452
|
|
|
445
453
|
// Traverses 'node' recursively and applies 'checkExpression' to all expressions
|
|
446
454
|
// found within paths (e.g. filters, parameters, ...)
|
|
447
|
-
function checkExpressionsInPaths(node) {
|
|
455
|
+
function checkExpressionsInPaths( node ) {
|
|
448
456
|
foreachPath(node, (path) => {
|
|
449
457
|
for (const pathStep of path) {
|
|
450
458
|
if (pathStep.where)
|
|
@@ -458,7 +466,7 @@ function check( model ) { // = XSN
|
|
|
458
466
|
});
|
|
459
467
|
}
|
|
460
468
|
|
|
461
|
-
function checkAssociation(elem) {
|
|
469
|
+
function checkAssociation( elem ) {
|
|
462
470
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
463
471
|
// when virtual element is part of association
|
|
464
472
|
if (elem.foreignKeys) {
|
|
@@ -474,24 +482,30 @@ function check( model ) { // = XSN
|
|
|
474
482
|
checkAssociationCondition(elem, elem.on);
|
|
475
483
|
}
|
|
476
484
|
|
|
477
|
-
function checkAssociationCondition(elem, onCond) {
|
|
485
|
+
function checkAssociationCondition( elem, onCond ) {
|
|
478
486
|
if (Array.isArray(onCond)) // condition in brackets results an array
|
|
479
487
|
onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
|
|
480
488
|
else
|
|
481
|
-
checkAssociationConditionArgs(elem, onCond.args, onCond
|
|
489
|
+
checkAssociationConditionArgs(elem, onCond.args, getBinaryOp( onCond ));
|
|
482
490
|
}
|
|
483
491
|
|
|
484
|
-
function
|
|
492
|
+
function getBinaryOp( cond ) {
|
|
493
|
+
const { op, args } = cond;
|
|
494
|
+
return op?.val === 'ixpr' && args.length === 3 && args[1].literal === 'token' &&
|
|
495
|
+
args[1] || op;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function checkAssociationConditionArgs( elem, args, op ) {
|
|
485
499
|
if (args)
|
|
486
500
|
args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
|
|
487
501
|
}
|
|
488
502
|
|
|
489
|
-
function checkAssociationOnCondArg(elem, arg, op) {
|
|
503
|
+
function checkAssociationOnCondArg( elem, arg, op ) {
|
|
490
504
|
if (Array.isArray(arg)) {
|
|
491
|
-
arg.forEach(Arg =>
|
|
505
|
+
arg.forEach(Arg => checkAssociationCondition(elem, Arg));
|
|
492
506
|
}
|
|
493
507
|
else {
|
|
494
|
-
|
|
508
|
+
checkAssociationCondition(elem, arg);
|
|
495
509
|
singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op);
|
|
496
510
|
}
|
|
497
511
|
}
|
|
@@ -503,7 +517,7 @@ function check( model ) { // = XSN
|
|
|
503
517
|
// integration into name resolution - did the first step.
|
|
504
518
|
// It is also incomplete, as associations in structures are not checked.
|
|
505
519
|
// Additionally, `$self.assoc` references are also not found.
|
|
506
|
-
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
|
|
520
|
+
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
|
|
507
521
|
if (!arg.path)
|
|
508
522
|
return;
|
|
509
523
|
const path0 = arg.path[0];
|
|
@@ -530,7 +544,7 @@ function check( model ) { // = XSN
|
|
|
530
544
|
}
|
|
531
545
|
|
|
532
546
|
function checkAssociationArgumentStartingWithSelf( op, elem ) {
|
|
533
|
-
if (op
|
|
547
|
+
if (op?.val === 'xpr') // no check for xpr, would require re-structuring
|
|
534
548
|
return;
|
|
535
549
|
if (op && op.val !== '=')
|
|
536
550
|
error(null, [ op.location, elem ], {}, '$self comparison is only allowed with \'=\'');
|
|
@@ -545,7 +559,7 @@ function check( model ) { // = XSN
|
|
|
545
559
|
*
|
|
546
560
|
* @param {XSN.Artifact} artifact
|
|
547
561
|
*/
|
|
548
|
-
function checkTypeStructure(artifact) {
|
|
562
|
+
function checkTypeStructure( artifact ) {
|
|
549
563
|
// Just a basic check. We do not check that the inner structure of `items`
|
|
550
564
|
// is the same as the type but only that all are arrayed or structured.
|
|
551
565
|
if (artifact.type && artifact.type._artifact) {
|
|
@@ -575,10 +589,10 @@ function check( model ) { // = XSN
|
|
|
575
589
|
* @param {Boolean} allowAssocTail
|
|
576
590
|
* @returns {void}
|
|
577
591
|
*/
|
|
578
|
-
function checkExpression(xpr, allowAssocTail = false) {
|
|
592
|
+
function checkExpression( xpr, allowAssocTail = false ) {
|
|
579
593
|
// Since the checks for tree-like and token-stream expressions differ,
|
|
580
594
|
// check here what kind of expression we are looking at
|
|
581
|
-
if (xpr.op
|
|
595
|
+
if (xpr.op?.val === 'xpr')
|
|
582
596
|
return checkTokenStreamExpression(xpr, allowAssocTail);
|
|
583
597
|
return checkTreeLikeExpression(xpr, allowAssocTail);
|
|
584
598
|
}
|
|
@@ -590,7 +604,7 @@ function check( model ) { // = XSN
|
|
|
590
604
|
* @param {any} arg Argument to check (part of an expression)
|
|
591
605
|
* @returns {Boolean}
|
|
592
606
|
*/
|
|
593
|
-
function isVirtualElement(arg) {
|
|
607
|
+
function isVirtualElement( arg ) {
|
|
594
608
|
return arg.path &&
|
|
595
609
|
arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true &&
|
|
596
610
|
arg._artifact.kind && arg._artifact.kind === 'element';
|
|
@@ -602,7 +616,7 @@ function check( model ) { // = XSN
|
|
|
602
616
|
* @param {any} xpr The expression to check
|
|
603
617
|
* @returns {void}
|
|
604
618
|
*/
|
|
605
|
-
function checkTokenStreamExpression(xpr, allowAssocTail) {
|
|
619
|
+
function checkTokenStreamExpression( xpr, allowAssocTail ) {
|
|
606
620
|
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
|
|
607
621
|
// Check for illegal argument usage within the expression
|
|
608
622
|
for (const arg of args) {
|
|
@@ -621,7 +635,7 @@ function check( model ) { // = XSN
|
|
|
621
635
|
* @param {any} xpr The expression to check
|
|
622
636
|
* @returns {void}
|
|
623
637
|
*/
|
|
624
|
-
function checkTreeLikeExpression(xpr, allowAssocTail) {
|
|
638
|
+
function checkTreeLikeExpression( xpr, allowAssocTail ) {
|
|
625
639
|
// No further checks regarding associations and $self required if this is a
|
|
626
640
|
// backlink-like expression (a comparison of $self with an assoc)
|
|
627
641
|
if (isBinaryDollarSelfComparisonWithAssoc(xpr))
|
|
@@ -655,7 +669,7 @@ function check( model ) { // = XSN
|
|
|
655
669
|
}
|
|
656
670
|
}
|
|
657
671
|
// Return true if 'arg' is an expression argument of type association or composition
|
|
658
|
-
function isAssociationOperand(arg) {
|
|
672
|
+
function isAssociationOperand( arg ) {
|
|
659
673
|
if (!arg.path) {
|
|
660
674
|
// Not a path, hence not an association (literal, expression, function, whatever ...)
|
|
661
675
|
return false;
|
|
@@ -666,7 +680,7 @@ function check( model ) { // = XSN
|
|
|
666
680
|
}
|
|
667
681
|
|
|
668
682
|
// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
|
|
669
|
-
function isDollarSelfOrProjectionOperand(arg) {
|
|
683
|
+
function isDollarSelfOrProjectionOperand( arg ) {
|
|
670
684
|
return arg.path && arg.path.length === 1 &&
|
|
671
685
|
(arg.path[0].id === '$self' || arg.path[0].id === '$projection');
|
|
672
686
|
}
|
|
@@ -677,7 +691,7 @@ function check( model ) { // = XSN
|
|
|
677
691
|
* @param {any} xpr The expression to check
|
|
678
692
|
* @returns {Boolean}
|
|
679
693
|
*/
|
|
680
|
-
function isBinaryDollarSelfComparisonWithAssoc(xpr) {
|
|
694
|
+
function isBinaryDollarSelfComparisonWithAssoc( xpr ) {
|
|
681
695
|
// Must be an expression with arguments
|
|
682
696
|
if (!xpr.op || !xpr.args)
|
|
683
697
|
return false;
|
|
@@ -735,7 +749,7 @@ function check( model ) { // = XSN
|
|
|
735
749
|
// Perform checks for annotation assignment 'anno', using corresponding annotation declaration,
|
|
736
750
|
// made of 'annoDecl' (artifact or undefined) and 'elementDecl' (annotation or element
|
|
737
751
|
// or undefined). Report errors on 'options.messages.
|
|
738
|
-
function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) {
|
|
752
|
+
function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) {
|
|
739
753
|
// Nothing to check if no actual annotation declaration was found
|
|
740
754
|
if (!annoDecl || annoDecl.artifacts && !elementDecl)
|
|
741
755
|
return;
|
|
@@ -779,7 +793,7 @@ function check( model ) { // = XSN
|
|
|
779
793
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
780
794
|
// 'val') is potentially assignable to element 'element'. Complain on 'loc'
|
|
781
795
|
// if not
|
|
782
|
-
function checkValueAssignableTo(value, elementDecl, art) {
|
|
796
|
+
function checkValueAssignableTo( value, elementDecl, art ) {
|
|
783
797
|
// FIXME: We currently do not have any element declaration that could match
|
|
784
798
|
// a 'path' value, so we simply leave those alone
|
|
785
799
|
if (value.path)
|
|
@@ -840,7 +854,7 @@ function check( model ) { // = XSN
|
|
|
840
854
|
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
|
|
841
855
|
warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
|
|
842
856
|
}
|
|
843
|
-
else {
|
|
857
|
+
else if (!elementDecl._effectiveType.enum) {
|
|
844
858
|
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
|
|
845
859
|
}
|
|
846
860
|
|
|
@@ -861,13 +875,23 @@ function check( model ) { // = XSN
|
|
|
861
875
|
}
|
|
862
876
|
else if (expectedEnum) {
|
|
863
877
|
// Enum symbol not provided but expected
|
|
864
|
-
|
|
878
|
+
const hasValidValue = Object.keys(expectedEnum)
|
|
879
|
+
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
|
|
880
|
+
if (!hasValidValue) {
|
|
865
881
|
// ... and none of the valid enum symbols matches the value
|
|
866
882
|
warning(null, loc, {}, 'An enum value is required here');
|
|
867
883
|
}
|
|
868
884
|
}
|
|
869
885
|
}
|
|
870
886
|
|
|
887
|
+
function getEnumValue( enumSymbol ) {
|
|
888
|
+
if (enumSymbol.value)
|
|
889
|
+
return enumSymbol.value?.val;
|
|
890
|
+
if (enumSymbol._effectiveType)
|
|
891
|
+
return enumSymbol._effectiveType?.value?.val;
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
|
|
871
895
|
// TODO: remove the following
|
|
872
896
|
|
|
873
897
|
// Return the artifact (and possibly, its element) found by following 'path'
|
|
@@ -877,7 +901,7 @@ function check( model ) { // = XSN
|
|
|
877
901
|
// represented by the full path (or 'undefined' if not found). Note that
|
|
878
902
|
// only elements and artifacts are considered for path traversal (no actions,
|
|
879
903
|
// functions, parameters etc.)
|
|
880
|
-
function resolvePathFrom(path, from, result = {}) {
|
|
904
|
+
function resolvePathFrom( path, from, result = {} ) {
|
|
881
905
|
// Keep last encountered artifacts
|
|
882
906
|
if (from && !from._main)
|
|
883
907
|
result.artifact = from;
|
|
@@ -896,11 +920,64 @@ function check( model ) { // = XSN
|
|
|
896
920
|
|
|
897
921
|
// Return the absolute name of the final type of 'node'. May return 'undefined' for
|
|
898
922
|
// anonymous types
|
|
899
|
-
function getFinalTypeNameOf(node) {
|
|
923
|
+
function getFinalTypeNameOf( node ) {
|
|
900
924
|
let type = node._effectiveType;
|
|
901
925
|
if (type.type)
|
|
902
926
|
type = type.type._artifact;
|
|
903
|
-
return type
|
|
927
|
+
return type?.name?.absolute;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Ensure that the sap.common.[Fiori]TextsAspect has proper types for
|
|
933
|
+
* e.g. `locale` and `ID_texts`.
|
|
934
|
+
*
|
|
935
|
+
* @param {XSN.Model} model
|
|
936
|
+
*/
|
|
937
|
+
function checkSapCommonTextsAspects( model ) {
|
|
938
|
+
checkSapCommonTextsAspectLocale( model, 'sap.common.TextsAspect' );
|
|
939
|
+
checkSapCommonTextsAspectLocale( model, 'sap.common.FioriTextsAspect' );
|
|
940
|
+
|
|
941
|
+
// Check ID_texts: Fiori requires it to be UUID.
|
|
942
|
+
const fioriTextsAspect = model.definitions['sap.common.FioriTextsAspect'];
|
|
943
|
+
const id = fioriTextsAspect?.elements?.ID_texts;
|
|
944
|
+
if (id) {
|
|
945
|
+
const idType = id._effectiveType;
|
|
946
|
+
if (!idType || idType.name?.absolute !== 'cds.UUID') {
|
|
947
|
+
const { error } = model.$messageFunctions;
|
|
948
|
+
error('def-invalid-element-type', [ id.type.location, id ], {
|
|
949
|
+
'#': 'std',
|
|
950
|
+
art: 'sap.common.FioriTextsAspect',
|
|
951
|
+
elemref: 'ID_texts',
|
|
952
|
+
type: 'cds.UUID',
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Ensure that the `locale` element of sap.common.[Fiori]TextsAspects
|
|
960
|
+
* is a string type. This is required by CAP runtimes to work properly.
|
|
961
|
+
*
|
|
962
|
+
* @param {XSN.Model} model
|
|
963
|
+
* @param {string} name Either sap.common.TextsAspects or sap.common.FioriTextsAspects
|
|
964
|
+
*/
|
|
965
|
+
function checkSapCommonTextsAspectLocale( model, name ) {
|
|
966
|
+
const locale = model.definitions[name]?.elements?.locale;
|
|
967
|
+
if (locale) {
|
|
968
|
+
// `locale` could also be `sap.common.Locale`, which must also be a string.
|
|
969
|
+
const type = locale._effectiveType;
|
|
970
|
+
if (type?.name?.absolute !== 'cds.String') {
|
|
971
|
+
const hasCommonLocale = !!model.definitions['sap.common.Locale'];
|
|
972
|
+
const { error } = model.$messageFunctions;
|
|
973
|
+
error('def-invalid-element-type', [ locale.type.location, locale ], {
|
|
974
|
+
'#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
|
|
975
|
+
art: name,
|
|
976
|
+
elemref: 'locale',
|
|
977
|
+
type: 'cds.String',
|
|
978
|
+
othertype: 'sap.common.Locale',
|
|
979
|
+
});
|
|
980
|
+
}
|
|
904
981
|
}
|
|
905
982
|
}
|
|
906
983
|
|
|
@@ -909,15 +986,13 @@ function check( model ) { // = XSN
|
|
|
909
986
|
* be lifted later on.
|
|
910
987
|
*
|
|
911
988
|
* @param {XSN.Model} model
|
|
912
|
-
* @param {object} messageFunctions
|
|
913
989
|
*/
|
|
914
|
-
function checkSapCommonLocale( model
|
|
990
|
+
function checkSapCommonLocale( model ) {
|
|
915
991
|
const localeArt = model.definitions['sap.common.Locale'];
|
|
916
992
|
if (localeArt) {
|
|
917
993
|
const type = localeArt._effectiveType;
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const { message } = messageFunctions;
|
|
994
|
+
if (type?.name?.absolute !== 'cds.String') {
|
|
995
|
+
const { message } = model.$messageFunctions;
|
|
921
996
|
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
922
997
|
{ name: 'sap.common.Locale' },
|
|
923
998
|
'Expected $(NAME) to be a string type');
|
|
@@ -928,7 +1003,7 @@ function checkSapCommonLocale( model, messageFunctions ) {
|
|
|
928
1003
|
// For each property named 'path' in 'node' (recursively), call callback(path, node)
|
|
929
1004
|
//
|
|
930
1005
|
// TODO: remove - this is not a good way to traverse expressions
|
|
931
|
-
function foreachPath(node, callback) {
|
|
1006
|
+
function foreachPath( node, callback ) {
|
|
932
1007
|
if (node === null || typeof node !== 'object') {
|
|
933
1008
|
// Primitive node
|
|
934
1009
|
return;
|
package/lib/compiler/define.js
CHANGED
|
@@ -132,6 +132,7 @@ const {
|
|
|
132
132
|
pathName,
|
|
133
133
|
splitIntoPath,
|
|
134
134
|
annotationHasEllipsis,
|
|
135
|
+
isDirectComposition,
|
|
135
136
|
} = require('./utils');
|
|
136
137
|
const { compareLayer } = require('./moduleLayers');
|
|
137
138
|
const { initBuiltins, isInReservedNamespace } = require('./builtins');
|
|
@@ -672,14 +673,17 @@ function define( model ) {
|
|
|
672
673
|
setLink( col, '_block', parent._block );
|
|
673
674
|
initAnnotations( col, parent._block );
|
|
674
675
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
675
|
-
if (col.doc)
|
|
676
|
-
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
677
|
-
|
|
676
|
+
if (col.doc) {
|
|
677
|
+
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
678
|
+
{ '#': 'doc', code: '.{ ‹inline› }' } );
|
|
679
|
+
}
|
|
678
680
|
// col.$annotations no available for CSN input, have to search.
|
|
679
681
|
// Warning about first annotation should be enough to avoid spam.
|
|
680
682
|
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
|
|
681
|
-
if (firstAnno)
|
|
682
|
-
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ]
|
|
683
|
+
if (firstAnno) {
|
|
684
|
+
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
|
|
685
|
+
{ code: '.{ ‹inline› }' } );
|
|
686
|
+
}
|
|
683
687
|
}
|
|
684
688
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
685
689
|
if (columns)
|
|
@@ -738,7 +742,7 @@ function define( model ) {
|
|
|
738
742
|
*
|
|
739
743
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
740
744
|
*/
|
|
741
|
-
function approveExistsInChildren(exprOrPathElement) {
|
|
745
|
+
function approveExistsInChildren( exprOrPathElement ) {
|
|
742
746
|
if (!exprOrPathElement) // may be null in case of parse error
|
|
743
747
|
return;
|
|
744
748
|
if (exprOrPathElement.$expected === 'exists')
|
|
@@ -834,7 +838,7 @@ function define( model ) {
|
|
|
834
838
|
dictAddArray( p.$tableAliases, table.name.id, table );
|
|
835
839
|
}
|
|
836
840
|
if (table.name.id[0] === '$') {
|
|
837
|
-
warning( '
|
|
841
|
+
warning( 'name-invalid-dollar-alias', [ table.name.location, table ], {
|
|
838
842
|
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
|
|
839
843
|
name: '$',
|
|
840
844
|
keyword: 'as',
|
|
@@ -921,7 +925,7 @@ function define( model ) {
|
|
|
921
925
|
error( 'duplicate-definition', [ loc, query ], { name: dupName, '#': 'alias' } );
|
|
922
926
|
} );
|
|
923
927
|
if (mixin.name.id[0] === '$') {
|
|
924
|
-
warning( '
|
|
928
|
+
warning( 'name-invalid-dollar-alias', [ mixin.name.location, mixin ],
|
|
925
929
|
{ '#': 'mixin', name: '$' } );
|
|
926
930
|
}
|
|
927
931
|
}
|
|
@@ -929,11 +933,6 @@ function define( model ) {
|
|
|
929
933
|
}
|
|
930
934
|
}
|
|
931
935
|
|
|
932
|
-
function isDirectComposition( art ) {
|
|
933
|
-
const type = art.type && art.type.path;
|
|
934
|
-
return type && type[0] && type[0].id === 'cds.Composition';
|
|
935
|
-
}
|
|
936
|
-
|
|
937
936
|
// Return whether the `target` is actually a `targetAspect`
|
|
938
937
|
// TODO: really do that here and not in kick-start.js?
|
|
939
938
|
function targetIsTargetAspect( elem ) {
|