@sap/cds-compiler 3.4.4 → 3.5.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 +58 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +58 -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 +124 -28
- package/lib/base/messages.js +247 -179
- 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/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 +1 -1
- package/lib/compiler/assert-consistency.js +0 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +73 -15
- package/lib/compiler/define.js +3 -7
- package/lib/compiler/extend.js +212 -32
- package/lib/compiler/finalize-parse-cdl.js +7 -2
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +2 -5
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/shared.js +23 -12
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +6 -0
- package/lib/edm/annotations/genericTranslation.js +553 -319
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +88 -75
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmPreprocessor.js +106 -76
- package/lib/edm/edmUtils.js +41 -2
- package/lib/gen/Dictionary.json +34 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +66 -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 +14360 -14146
- 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 +82 -40
- package/lib/json/to-csn.js +82 -157
- package/lib/language/.eslintrc.json +1 -4
- package/lib/language/genericAntlrParser.js +59 -38
- package/lib/language/language.g4 +1508 -1490
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.js +3 -3
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/utils/filter.js +4 -3
- package/lib/optionProcessor.js +5 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +225 -159
- package/lib/render/toHdbcds.js +63 -63
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +55 -65
- package/lib/render/utils/common.js +20 -37
- package/lib/render/utils/delta.js +3 -3
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- 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/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +12 -40
- package/lib/transform/forRelationalDB.js +17 -7
- package/lib/transform/localized.js +2 -2
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +106 -62
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +2 -2
- 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 -7
- 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/compiler/builtins.js
CHANGED
|
@@ -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
|
@@ -27,7 +27,8 @@ function check( model ) { // = XSN
|
|
|
27
27
|
} = model.$messageFunctions;
|
|
28
28
|
forEachDefinition( model, checkArtifact );
|
|
29
29
|
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
30
|
-
checkSapCommonLocale( model
|
|
30
|
+
checkSapCommonLocale( model );
|
|
31
|
+
checkSapCommonTextsAspects( model );
|
|
31
32
|
return;
|
|
32
33
|
|
|
33
34
|
function checkArtifact( art ) {
|
|
@@ -359,13 +360,13 @@ function check( model ) { // = XSN
|
|
|
359
360
|
const { literal, val, location } = elem.cardinality[prop];
|
|
360
361
|
if (!(literal === 'number' && val > 0 ||
|
|
361
362
|
literal === 'string' && val === '*')) {
|
|
362
|
-
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
363
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val, newcode: '*' }, {
|
|
363
364
|
// eslint-disable-next-line max-len
|
|
364
|
-
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)',
|
|
365
366
|
// eslint-disable-next-line max-len
|
|
366
|
-
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)',
|
|
367
368
|
// eslint-disable-next-line max-len
|
|
368
|
-
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)',
|
|
369
370
|
});
|
|
370
371
|
}
|
|
371
372
|
}
|
|
@@ -485,7 +486,13 @@ function check( model ) { // = XSN
|
|
|
485
486
|
if (Array.isArray(onCond)) // condition in brackets results an array
|
|
486
487
|
onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
|
|
487
488
|
else
|
|
488
|
-
checkAssociationConditionArgs(elem, onCond.args, onCond
|
|
489
|
+
checkAssociationConditionArgs(elem, onCond.args, getBinaryOp( onCond ));
|
|
490
|
+
}
|
|
491
|
+
|
|
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;
|
|
489
496
|
}
|
|
490
497
|
|
|
491
498
|
function checkAssociationConditionArgs( elem, args, op ) {
|
|
@@ -495,10 +502,10 @@ function check( model ) { // = XSN
|
|
|
495
502
|
|
|
496
503
|
function checkAssociationOnCondArg( elem, arg, op ) {
|
|
497
504
|
if (Array.isArray(arg)) {
|
|
498
|
-
arg.forEach(Arg =>
|
|
505
|
+
arg.forEach(Arg => checkAssociationCondition(elem, Arg));
|
|
499
506
|
}
|
|
500
507
|
else {
|
|
501
|
-
|
|
508
|
+
checkAssociationCondition(elem, arg);
|
|
502
509
|
singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op);
|
|
503
510
|
}
|
|
504
511
|
}
|
|
@@ -537,7 +544,7 @@ function check( model ) { // = XSN
|
|
|
537
544
|
}
|
|
538
545
|
|
|
539
546
|
function checkAssociationArgumentStartingWithSelf( op, elem ) {
|
|
540
|
-
if (op
|
|
547
|
+
if (op?.val === 'xpr') // no check for xpr, would require re-structuring
|
|
541
548
|
return;
|
|
542
549
|
if (op && op.val !== '=')
|
|
543
550
|
error(null, [ op.location, elem ], {}, '$self comparison is only allowed with \'=\'');
|
|
@@ -585,7 +592,7 @@ function check( model ) { // = XSN
|
|
|
585
592
|
function checkExpression( xpr, allowAssocTail = false ) {
|
|
586
593
|
// Since the checks for tree-like and token-stream expressions differ,
|
|
587
594
|
// check here what kind of expression we are looking at
|
|
588
|
-
if (xpr.op
|
|
595
|
+
if (xpr.op?.val === 'xpr')
|
|
589
596
|
return checkTokenStreamExpression(xpr, allowAssocTail);
|
|
590
597
|
return checkTreeLikeExpression(xpr, allowAssocTail);
|
|
591
598
|
}
|
|
@@ -921,20 +928,71 @@ function check( model ) { // = XSN
|
|
|
921
928
|
}
|
|
922
929
|
}
|
|
923
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
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
924
984
|
/**
|
|
925
985
|
* Checks that sap.common.Locale is of type cds.String. This limitation may
|
|
926
986
|
* be lifted later on.
|
|
927
987
|
*
|
|
928
988
|
* @param {XSN.Model} model
|
|
929
|
-
* @param {object} messageFunctions
|
|
930
989
|
*/
|
|
931
|
-
function checkSapCommonLocale( model
|
|
990
|
+
function checkSapCommonLocale( model ) {
|
|
932
991
|
const localeArt = model.definitions['sap.common.Locale'];
|
|
933
992
|
if (localeArt) {
|
|
934
993
|
const type = localeArt._effectiveType;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const { message } = messageFunctions;
|
|
994
|
+
if (type?.name?.absolute !== 'cds.String') {
|
|
995
|
+
const { message } = model.$messageFunctions;
|
|
938
996
|
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
939
997
|
{ name: 'sap.common.Locale' },
|
|
940
998
|
'Expected $(NAME) to be a string type');
|
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');
|
|
@@ -837,7 +838,7 @@ function define( model ) {
|
|
|
837
838
|
dictAddArray( p.$tableAliases, table.name.id, table );
|
|
838
839
|
}
|
|
839
840
|
if (table.name.id[0] === '$') {
|
|
840
|
-
warning( '
|
|
841
|
+
warning( 'name-invalid-dollar-alias', [ table.name.location, table ], {
|
|
841
842
|
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
|
|
842
843
|
name: '$',
|
|
843
844
|
keyword: 'as',
|
|
@@ -924,7 +925,7 @@ function define( model ) {
|
|
|
924
925
|
error( 'duplicate-definition', [ loc, query ], { name: dupName, '#': 'alias' } );
|
|
925
926
|
} );
|
|
926
927
|
if (mixin.name.id[0] === '$') {
|
|
927
|
-
warning( '
|
|
928
|
+
warning( 'name-invalid-dollar-alias', [ mixin.name.location, mixin ],
|
|
928
929
|
{ '#': 'mixin', name: '$' } );
|
|
929
930
|
}
|
|
930
931
|
}
|
|
@@ -932,11 +933,6 @@ function define( model ) {
|
|
|
932
933
|
}
|
|
933
934
|
}
|
|
934
935
|
|
|
935
|
-
function isDirectComposition( art ) {
|
|
936
|
-
const type = art.type && art.type.path;
|
|
937
|
-
return type && type[0] && type[0].id === 'cds.Composition';
|
|
938
|
-
}
|
|
939
|
-
|
|
940
936
|
// Return whether the `target` is actually a `targetAspect`
|
|
941
937
|
// TODO: really do that here and not in kick-start.js?
|
|
942
938
|
function targetIsTargetAspect( elem ) {
|
package/lib/compiler/extend.js
CHANGED
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
setMemberParent,
|
|
21
21
|
dependsOnSilent,
|
|
22
22
|
augmentPath,
|
|
23
|
-
splitIntoPath,
|
|
23
|
+
splitIntoPath, isDirectComposition,
|
|
24
24
|
} = require('./utils');
|
|
25
25
|
const layers = require('./moduleLayers');
|
|
26
26
|
const { typeParameters } = require('./builtins');
|
|
@@ -54,6 +54,7 @@ function extend( model ) {
|
|
|
54
54
|
applyExtensions();
|
|
55
55
|
|
|
56
56
|
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
57
|
+
const useTextsAspects = checkTextsAspects();
|
|
57
58
|
|
|
58
59
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
59
60
|
|
|
@@ -293,6 +294,97 @@ function extend( model ) {
|
|
|
293
294
|
});
|
|
294
295
|
}
|
|
295
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Check that special `sap.common.*` aspects for `.texts` entities are
|
|
299
|
+
* consistent with compiler expectations. Emits messages and returns
|
|
300
|
+
* false if the aspects are not valid.
|
|
301
|
+
*
|
|
302
|
+
* @return {boolean}
|
|
303
|
+
*/
|
|
304
|
+
function checkTextsAspects() {
|
|
305
|
+
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
306
|
+
const fioriTextsAspect = model.definitions['sap.common.FioriTextsAspect'];
|
|
307
|
+
|
|
308
|
+
let hasError = false;
|
|
309
|
+
|
|
310
|
+
if (textsAspect) {
|
|
311
|
+
const specialElements = { locale: { key: true } };
|
|
312
|
+
if (!checkTextsAspect(textsAspect, specialElements))
|
|
313
|
+
hasError = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (fioriTextsAspect) {
|
|
317
|
+
const specialElements = { ID_texts: { key: true }, locale: { key: false } };
|
|
318
|
+
if (!checkTextsAspect(fioriTextsAspect, specialElements))
|
|
319
|
+
hasError = true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return !hasError;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function checkTextsAspect( art, specialElements ) {
|
|
326
|
+
if (art.kind !== 'aspect' || !art.elements) {
|
|
327
|
+
error('def-invalid-texts-aspect', [ art.name.location, art ], { '#': 'no-aspect', art });
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let hasError = false;
|
|
332
|
+
if (addTextsLanguageAssoc && art.elements.language) {
|
|
333
|
+
const lang = art.elements.language;
|
|
334
|
+
error('def-unexpected-element', [ lang.name.location, lang ],
|
|
335
|
+
{ option: 'addTextsLanguageAssoc', art, name: 'language' },
|
|
336
|
+
// eslint-disable-next-line max-len
|
|
337
|
+
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
338
|
+
hasError = true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const name in specialElements) {
|
|
342
|
+
const expected = specialElements[name];
|
|
343
|
+
const elem = art.elements[name];
|
|
344
|
+
if (!elem) {
|
|
345
|
+
error('def-invalid-texts-aspect', [ art.name.location, art ],
|
|
346
|
+
{ '#': 'missing', art, name });
|
|
347
|
+
hasError = true;
|
|
348
|
+
}
|
|
349
|
+
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
350
|
+
const loc = elem.key?.location || elem.name?.location || art.name.location;
|
|
351
|
+
error('def-invalid-texts-aspect', [ loc, elem ],
|
|
352
|
+
{ '#': expected.key ? 'key' : 'no-key', art: elem });
|
|
353
|
+
hasError = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (hasError) // avoid subsequent errors, if the special elements are already wrong
|
|
358
|
+
return false;
|
|
359
|
+
|
|
360
|
+
for (const name in art.elements) {
|
|
361
|
+
const elem = art.elements[name];
|
|
362
|
+
const include = elem.$inferred === 'include';
|
|
363
|
+
if (!specialElements[name] && elem.key) {
|
|
364
|
+
const loc = include ? elem.location : elem.key.location;
|
|
365
|
+
error( 'def-unexpected-key', [ loc, elem ],
|
|
366
|
+
{ '#': !include ? 'std' : 'include', art } );
|
|
367
|
+
hasError = true;
|
|
368
|
+
}
|
|
369
|
+
else if (hasTruthyProp( elem, 'localized' )) {
|
|
370
|
+
// TODO: T:loc, i.e. "localized" from other type (needs resolver?)
|
|
371
|
+
// Not supported anyway, but important for recompilation (which fails correctly).
|
|
372
|
+
const loc = elem.localized?.location || elem.location;
|
|
373
|
+
error( 'def-unexpected-localized', [ loc, elem ],
|
|
374
|
+
{ '#': !include ? 'std' : 'include', art } );
|
|
375
|
+
hasError = true;
|
|
376
|
+
}
|
|
377
|
+
else if (elem.targetAspect) {
|
|
378
|
+
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ], { art },
|
|
379
|
+
'$(ART) can\'t have composition of aspects' );
|
|
380
|
+
hasError = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return !hasError;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
296
388
|
/**
|
|
297
389
|
* Copy columns for EXTEND PROJECTION
|
|
298
390
|
*
|
|
@@ -614,7 +706,7 @@ function extend( model ) {
|
|
|
614
706
|
function applyIncludes( ext, art ) {
|
|
615
707
|
if (kindProperties[art.kind].include !== true) {
|
|
616
708
|
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
617
|
-
{
|
|
709
|
+
{ meta: art.kind });
|
|
618
710
|
return;
|
|
619
711
|
}
|
|
620
712
|
|
|
@@ -696,9 +788,8 @@ function extend( model ) {
|
|
|
696
788
|
return;
|
|
697
789
|
if (textsEntity) // expanded localized data in source
|
|
698
790
|
return; // -> make it idempotent
|
|
699
|
-
|
|
791
|
+
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
700
792
|
addTextsAssociations( art, textsName, localized );
|
|
701
|
-
copyPersistenceAnnotations(newTextsEntity, art, options);
|
|
702
793
|
}
|
|
703
794
|
|
|
704
795
|
/**
|
|
@@ -714,6 +805,9 @@ function extend( model ) {
|
|
|
714
805
|
let keys = 0;
|
|
715
806
|
const textElems = [];
|
|
716
807
|
const conflictingElements = [];
|
|
808
|
+
// These elements are required or the localized-mechanism does not work.
|
|
809
|
+
// Other elements from sap.common.TextsAspect may be "overridden" as per
|
|
810
|
+
// usual include-mechanism.
|
|
717
811
|
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
718
812
|
if (fioriEnabled)
|
|
719
813
|
protectedElements.push('ID_texts');
|
|
@@ -798,16 +892,37 @@ function extend( model ) {
|
|
|
798
892
|
* @param {boolean} fioriEnabled
|
|
799
893
|
*/
|
|
800
894
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
801
|
-
const
|
|
895
|
+
const name = (fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect');
|
|
896
|
+
const withTextsAspect = useTextsAspects && model.definitions[name];
|
|
897
|
+
|
|
898
|
+
const art = withTextsAspect
|
|
899
|
+
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
900
|
+
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
802
901
|
|
|
803
902
|
const { location } = base.name;
|
|
804
|
-
|
|
903
|
+
|
|
904
|
+
if (addTextsLanguageAssoc) {
|
|
905
|
+
const language = {
|
|
906
|
+
name: { location, id: 'language' },
|
|
907
|
+
kind: 'element',
|
|
908
|
+
location,
|
|
909
|
+
type: augmentPath( location, 'cds.Association' ),
|
|
910
|
+
target: augmentPath( location, 'sap.common.Languages' ),
|
|
911
|
+
on: {
|
|
912
|
+
op: { val: '=', location },
|
|
913
|
+
args: [
|
|
914
|
+
{ path: [ { id: 'language', location }, { id: 'code', location } ], location },
|
|
915
|
+
{ path: [ { id: 'locale', location } ], location },
|
|
916
|
+
],
|
|
917
|
+
location,
|
|
918
|
+
},
|
|
919
|
+
};
|
|
920
|
+
setLink( language, '_block', model.$internal );
|
|
921
|
+
dictAdd( art.elements, 'language', language );
|
|
922
|
+
}
|
|
805
923
|
|
|
806
924
|
// assertUnique array value, first entry is 'locale'
|
|
807
|
-
const assertUniqueValue = [
|
|
808
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
809
|
-
location: locale.location,
|
|
810
|
-
} ];
|
|
925
|
+
const assertUniqueValue = [];
|
|
811
926
|
|
|
812
927
|
for (const orig of textElems) {
|
|
813
928
|
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
@@ -833,13 +948,73 @@ function extend( model ) {
|
|
|
833
948
|
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
834
949
|
}
|
|
835
950
|
}
|
|
836
|
-
|
|
951
|
+
|
|
952
|
+
initArtifact( art );
|
|
953
|
+
if (art.includes) {
|
|
954
|
+
// add elements `locale`, etc. which are required below.
|
|
955
|
+
applyIncludes(art, art);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (fioriEnabled ) {
|
|
959
|
+
const { locale } = art.elements;
|
|
960
|
+
assertUniqueValue.unshift({
|
|
961
|
+
path: [ { id: locale.name.id, location: locale.location } ],
|
|
962
|
+
location: locale.location,
|
|
963
|
+
});
|
|
837
964
|
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
copyPersistenceAnnotations(art, base, options);
|
|
968
|
+
return art;
|
|
969
|
+
}
|
|
838
970
|
|
|
971
|
+
/**
|
|
972
|
+
* Create the `.texts` entity for the given base artifact.
|
|
973
|
+
* In contrast to createTextsEntityWithDefaultElements(), this one creates
|
|
974
|
+
* an include for `sap.common.TextsAspect`.
|
|
975
|
+
*
|
|
976
|
+
* Does NOT apply the include!
|
|
977
|
+
*
|
|
978
|
+
* TODO: When beta flag textsAspect is removed, update caller-site and remove old coding.
|
|
979
|
+
*
|
|
980
|
+
* @param {XSN.Artifact} base
|
|
981
|
+
* @param {string} absolute
|
|
982
|
+
* @param {boolean} fioriEnabled
|
|
983
|
+
*/
|
|
984
|
+
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
|
|
985
|
+
const elements = Object.create(null);
|
|
986
|
+
const { location } = base.name;
|
|
987
|
+
|
|
988
|
+
const include = fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect';
|
|
989
|
+
const art = {
|
|
990
|
+
kind: 'entity',
|
|
991
|
+
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
992
|
+
includes: [ createInclude( include, base.location ) ],
|
|
993
|
+
location: base.location,
|
|
994
|
+
elements,
|
|
995
|
+
$inferred: 'localized-entity',
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
if (!fioriEnabled) {
|
|
999
|
+
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1000
|
+
// TODO (next major version): remove?
|
|
1001
|
+
annotateWith( art, '@odata.draft.enabled', art.location, false );
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (addTextsLanguageAssoc && art.elements.language)
|
|
1005
|
+
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
1006
|
+
|
|
1007
|
+
setLink( art, '_block', model.$internal );
|
|
1008
|
+
model.definitions[absolute] = art;
|
|
839
1009
|
return art;
|
|
840
1010
|
}
|
|
841
1011
|
|
|
842
|
-
|
|
1012
|
+
/**
|
|
1013
|
+
* @param {XSN.Artifact} base
|
|
1014
|
+
* @param {string} absolute
|
|
1015
|
+
* @param {boolean} fioriEnabled
|
|
1016
|
+
*/
|
|
1017
|
+
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
843
1018
|
const elements = Object.create(null);
|
|
844
1019
|
const { location } = base.name;
|
|
845
1020
|
const art = {
|
|
@@ -879,28 +1054,8 @@ function extend( model ) {
|
|
|
879
1054
|
}
|
|
880
1055
|
|
|
881
1056
|
dictAdd( art.elements, 'locale', locale );
|
|
882
|
-
if (addTextsLanguageAssoc) {
|
|
883
|
-
const language = {
|
|
884
|
-
name: { location, id: 'language' },
|
|
885
|
-
kind: 'element',
|
|
886
|
-
location,
|
|
887
|
-
type: augmentPath( location, 'cds.Association' ),
|
|
888
|
-
target: augmentPath( location, 'sap.common.Languages' ),
|
|
889
|
-
on: {
|
|
890
|
-
op: { val: '=', location },
|
|
891
|
-
args: [
|
|
892
|
-
{ path: [ { id: 'language', location }, { id: 'code', location } ], location },
|
|
893
|
-
{ path: [ { id: 'locale', location } ], location },
|
|
894
|
-
],
|
|
895
|
-
location,
|
|
896
|
-
},
|
|
897
|
-
};
|
|
898
|
-
setLink( language, '_block', model.$internal );
|
|
899
|
-
dictAdd( art.elements, 'language', language );
|
|
900
|
-
}
|
|
901
1057
|
setLink( art, '_block', model.$internal );
|
|
902
1058
|
model.definitions[absolute] = art;
|
|
903
|
-
initArtifact( art );
|
|
904
1059
|
|
|
905
1060
|
return art;
|
|
906
1061
|
}
|
|
@@ -943,6 +1098,22 @@ function extend( model ) {
|
|
|
943
1098
|
setLink( localized, '_block', model.$internal );
|
|
944
1099
|
}
|
|
945
1100
|
|
|
1101
|
+
/**
|
|
1102
|
+
* Create a structure that can be used as an item in `includes`.
|
|
1103
|
+
*
|
|
1104
|
+
* @param {string} name
|
|
1105
|
+
* @param {XSN.Location} location
|
|
1106
|
+
*/
|
|
1107
|
+
function createInclude( name, location ) {
|
|
1108
|
+
const include = {
|
|
1109
|
+
path: [ { id: name, location } ],
|
|
1110
|
+
location,
|
|
1111
|
+
};
|
|
1112
|
+
setArtifactLink( include.path[0], model.definitions[name] );
|
|
1113
|
+
setArtifactLink( include, model.definitions[name] );
|
|
1114
|
+
return include;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
946
1117
|
/**
|
|
947
1118
|
* Returns whether `art` directly or indirectly has the property 'prop',
|
|
948
1119
|
* following the 'origin' and the 'type' (not involving elements).
|
|
@@ -1092,6 +1263,15 @@ function extend( model ) {
|
|
|
1092
1263
|
});
|
|
1093
1264
|
return false;
|
|
1094
1265
|
}
|
|
1266
|
+
|
|
1267
|
+
if (elem.type && !isDirectComposition(elem)) {
|
|
1268
|
+
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
1269
|
+
// TODO: Make it configurable error; v4: error
|
|
1270
|
+
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
|
|
1271
|
+
{ prop: 'Composition of', otherprop: 'Association to' },
|
|
1272
|
+
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1095
1275
|
return true;
|
|
1096
1276
|
}
|
|
1097
1277
|
|
|
@@ -186,8 +186,13 @@ function finalizeParseCdl( model ) {
|
|
|
186
186
|
// For better error messages, check for invalid TYPE OFs similarly
|
|
187
187
|
// to how `resolveType()` does.
|
|
188
188
|
let struct = artWithType;
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
// `items` have no kind, but need to be skipped as well
|
|
190
|
+
while (struct.kind === 'element' || struct._outer?.items) {
|
|
191
|
+
if (struct._outer?.items)
|
|
192
|
+
struct = struct._outer;
|
|
193
|
+
else
|
|
194
|
+
struct = struct._parent;
|
|
195
|
+
}
|
|
191
196
|
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
|
|
192
197
|
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
193
198
|
{ keyword: 'type of', '#': struct.kind } );
|
package/lib/compiler/index.js
CHANGED
|
@@ -40,8 +40,14 @@ const { cdsFs } = require('../utils/file');
|
|
|
40
40
|
const fs = require('fs');
|
|
41
41
|
const path = require('path');
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const extensionParsers = {
|
|
44
|
+
csn: parseCsn.parse,
|
|
45
|
+
json: parseCsn.parse,
|
|
46
|
+
cds: parseLanguage,
|
|
47
|
+
cdl: parseLanguage,
|
|
48
|
+
hdbcds: parseLanguage,
|
|
49
|
+
hdbdd: parseLanguage,
|
|
50
|
+
};
|
|
45
51
|
|
|
46
52
|
// Class for command invocation errors. Additional members:
|
|
47
53
|
// `errors`: vector of errors (file IO or ArgumentError)
|
|
@@ -75,21 +81,18 @@ class ArgumentError extends Error {
|
|
|
75
81
|
function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
76
82
|
if (!messageFunctions)
|
|
77
83
|
messageFunctions = createMessageFunctions( options, 'parse' );
|
|
78
|
-
const ext = path.extname( filename ).toLowerCase();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return parseLanguage( source, filename, options, messageFunctions );
|
|
87
|
-
if (source.startsWith('{')) // Source may be JSON.
|
|
88
|
-
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
84
|
+
const ext = path.extname( filename ).slice(1).toLowerCase();
|
|
85
|
+
// eslint-disable-next-line no-nested-ternary
|
|
86
|
+
const parser = options.fallbackParser === 'auto!'
|
|
87
|
+
? (source?.startsWith('{') ? parseCsn.parse : parseLanguage)
|
|
88
|
+
: (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
|
|
89
|
+
source.startsWith('{') && parseCsn.parse);
|
|
90
|
+
if (parser)
|
|
91
|
+
return parser( source, filename, options, messageFunctions );
|
|
89
92
|
|
|
90
93
|
const model = { location: { file: filename } };
|
|
91
94
|
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
|
|
92
|
-
{ file: ext
|
|
95
|
+
{ file: ext, '#': !ext && 'none' }, {
|
|
93
96
|
std: 'Unknown file extension $(FILE)',
|
|
94
97
|
none: 'No file extension',
|
|
95
98
|
} );
|
package/lib/compiler/populate.js
CHANGED
|
@@ -198,12 +198,9 @@ function populate( model ) {
|
|
|
198
198
|
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
199
199
|
art.target && redirectImplicitly( a, art ) ||
|
|
200
200
|
art.elements && expandElements( a, art ) ||
|
|
201
|
-
art.items && expandItems( a, art )
|
|
201
|
+
art.items && expandItems( a, art ) ||
|
|
202
|
+
art.enum && expandEnum( a, art ))
|
|
202
203
|
art = a;
|
|
203
|
-
|
|
204
|
-
else if (art.enum && expandEnum( a, art ))
|
|
205
|
-
art = a;
|
|
206
|
-
|
|
207
204
|
setLink( a, '_effectiveType', art );
|
|
208
205
|
}
|
|
209
206
|
}
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
isDeprecatedEnabled,
|
|
16
16
|
} = require( '../base/model');
|
|
17
17
|
const { setLink, linkToOrigin, withAssociation } = require('./utils');
|
|
18
|
+
const $inferred = Symbol.for('cds.$inferred');
|
|
18
19
|
// const { refString } = require( '../base/messages')
|
|
19
20
|
|
|
20
21
|
// Note that propagation here is also used for deep-copying (function `onlyViaParent`)
|
|
@@ -225,6 +226,7 @@ function propagate( model ) {
|
|
|
225
226
|
member.$inferred = 'proxy';
|
|
226
227
|
setEffectiveType(member, dict[name]);
|
|
227
228
|
}
|
|
229
|
+
target[prop][$inferred] = 'prop';
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
function onlyViaParent( prop, target, source ) {
|