@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
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');
|
|
@@ -53,10 +53,9 @@ function extend( model ) {
|
|
|
53
53
|
|
|
54
54
|
applyExtensions();
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
commonLanguagesEntity.elements.code);
|
|
56
|
+
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
57
|
+
const useTextsAspects = checkTextsAspects();
|
|
58
|
+
|
|
60
59
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
61
60
|
|
|
62
61
|
lateExtensions( false );
|
|
@@ -107,7 +106,7 @@ function extend( model ) {
|
|
|
107
106
|
const processed = new WeakSet();
|
|
108
107
|
forEachDefinition(model, processCompositionPersistence);
|
|
109
108
|
|
|
110
|
-
function processCompositionPersistence(def) {
|
|
109
|
+
function processCompositionPersistence( def ) {
|
|
111
110
|
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
112
111
|
if (def._parent)
|
|
113
112
|
processCompositionPersistence(def._parent);
|
|
@@ -207,7 +206,7 @@ function extend( model ) {
|
|
|
207
206
|
* @param {XSN.Definition} art
|
|
208
207
|
* @param {boolean|'gen'} [noIncludes=false]
|
|
209
208
|
*/
|
|
210
|
-
function extendArtifact( extensions, art, noIncludes = false) {
|
|
209
|
+
function extendArtifact( extensions, art, noIncludes = false ) {
|
|
211
210
|
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
212
211
|
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
213
212
|
return false;
|
|
@@ -295,6 +294,97 @@ function extend( model ) {
|
|
|
295
294
|
});
|
|
296
295
|
}
|
|
297
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
|
+
|
|
298
388
|
/**
|
|
299
389
|
* Copy columns for EXTEND PROJECTION
|
|
300
390
|
*
|
|
@@ -332,7 +422,7 @@ function extend( model ) {
|
|
|
332
422
|
*
|
|
333
423
|
* @param art
|
|
334
424
|
*/
|
|
335
|
-
function applyTypeExtensions(art) {
|
|
425
|
+
function applyTypeExtensions( art ) {
|
|
336
426
|
/**
|
|
337
427
|
* Contains the previous extension for each property that was applied
|
|
338
428
|
* successfully.
|
|
@@ -616,7 +706,7 @@ function extend( model ) {
|
|
|
616
706
|
function applyIncludes( ext, art ) {
|
|
617
707
|
if (kindProperties[art.kind].include !== true) {
|
|
618
708
|
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
619
|
-
{
|
|
709
|
+
{ meta: art.kind });
|
|
620
710
|
return;
|
|
621
711
|
}
|
|
622
712
|
|
|
@@ -698,9 +788,8 @@ function extend( model ) {
|
|
|
698
788
|
return;
|
|
699
789
|
if (textsEntity) // expanded localized data in source
|
|
700
790
|
return; // -> make it idempotent
|
|
701
|
-
|
|
791
|
+
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
702
792
|
addTextsAssociations( art, textsName, localized );
|
|
703
|
-
copyPersistenceAnnotations(newTextsEntity, art, options);
|
|
704
793
|
}
|
|
705
794
|
|
|
706
795
|
/**
|
|
@@ -716,6 +805,9 @@ function extend( model ) {
|
|
|
716
805
|
let keys = 0;
|
|
717
806
|
const textElems = [];
|
|
718
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.
|
|
719
811
|
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
720
812
|
if (fioriEnabled)
|
|
721
813
|
protectedElements.push('ID_texts');
|
|
@@ -742,7 +834,7 @@ function extend( model ) {
|
|
|
742
834
|
|
|
743
835
|
if (isKey && isLocalized) { // key with localized is wrong - ignore localized
|
|
744
836
|
const errpos = elem.localized || elem.type || elem.name;
|
|
745
|
-
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
837
|
+
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
746
838
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
747
839
|
}
|
|
748
840
|
}
|
|
@@ -750,7 +842,7 @@ function extend( model ) {
|
|
|
750
842
|
return false;
|
|
751
843
|
|
|
752
844
|
if (!keys) {
|
|
753
|
-
warning(
|
|
845
|
+
warning( 'def-expecting-key', [ art.name.location, art ], {},
|
|
754
846
|
'No texts entity can be created when no key element exists' );
|
|
755
847
|
return false;
|
|
756
848
|
}
|
|
@@ -800,46 +892,15 @@ function extend( model ) {
|
|
|
800
892
|
* @param {boolean} fioriEnabled
|
|
801
893
|
*/
|
|
802
894
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
const art = {
|
|
806
|
-
kind: 'entity',
|
|
807
|
-
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
808
|
-
location: base.location,
|
|
809
|
-
elements,
|
|
810
|
-
$inferred: 'localized-entity',
|
|
811
|
-
};
|
|
812
|
-
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
813
|
-
// If not, use the default `cds.String` with a length of 14.
|
|
814
|
-
const hasLocaleType = model.definitions['sap.common.Locale'] &&
|
|
815
|
-
model.definitions['sap.common.Locale'].kind === 'type';
|
|
816
|
-
const locale = {
|
|
817
|
-
name: { location, id: 'locale' },
|
|
818
|
-
kind: 'element',
|
|
819
|
-
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
820
|
-
location,
|
|
821
|
-
};
|
|
822
|
-
if (!hasLocaleType)
|
|
823
|
-
locale.length = { literal: 'number', val: 14, location };
|
|
895
|
+
const name = (fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect');
|
|
896
|
+
const withTextsAspect = useTextsAspects && model.definitions[name];
|
|
824
897
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
else {
|
|
832
|
-
const textId = {
|
|
833
|
-
name: { location, id: 'ID_texts' },
|
|
834
|
-
kind: 'element',
|
|
835
|
-
key: { val: true, location },
|
|
836
|
-
type: augmentPath( location, 'cds.UUID' ),
|
|
837
|
-
location,
|
|
838
|
-
};
|
|
839
|
-
dictAdd( art.elements, 'ID_texts', textId );
|
|
840
|
-
}
|
|
898
|
+
const art = withTextsAspect
|
|
899
|
+
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
900
|
+
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
901
|
+
|
|
902
|
+
const { location } = base.name;
|
|
841
903
|
|
|
842
|
-
dictAdd( art.elements, 'locale', locale );
|
|
843
904
|
if (addTextsLanguageAssoc) {
|
|
844
905
|
const language = {
|
|
845
906
|
name: { location, id: 'language' },
|
|
@@ -859,15 +920,9 @@ function extend( model ) {
|
|
|
859
920
|
setLink( language, '_block', model.$internal );
|
|
860
921
|
dictAdd( art.elements, 'language', language );
|
|
861
922
|
}
|
|
862
|
-
setLink( art, '_block', model.$internal );
|
|
863
|
-
model.definitions[absolute] = art;
|
|
864
|
-
initArtifact( art );
|
|
865
923
|
|
|
866
924
|
// assertUnique array value, first entry is 'locale'
|
|
867
|
-
const assertUniqueValue = [
|
|
868
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
869
|
-
location: locale.location,
|
|
870
|
-
} ];
|
|
925
|
+
const assertUniqueValue = [];
|
|
871
926
|
|
|
872
927
|
for (const orig of textElems) {
|
|
873
928
|
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
@@ -893,8 +948,114 @@ function extend( model ) {
|
|
|
893
948
|
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
894
949
|
}
|
|
895
950
|
}
|
|
896
|
-
|
|
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
|
+
});
|
|
897
964
|
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
copyPersistenceAnnotations(art, base, options);
|
|
968
|
+
return art;
|
|
969
|
+
}
|
|
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;
|
|
1009
|
+
return art;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* @param {XSN.Artifact} base
|
|
1014
|
+
* @param {string} absolute
|
|
1015
|
+
* @param {boolean} fioriEnabled
|
|
1016
|
+
*/
|
|
1017
|
+
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
1018
|
+
const elements = Object.create(null);
|
|
1019
|
+
const { location } = base.name;
|
|
1020
|
+
const art = {
|
|
1021
|
+
kind: 'entity',
|
|
1022
|
+
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
1023
|
+
location: base.location,
|
|
1024
|
+
elements,
|
|
1025
|
+
$inferred: 'localized-entity',
|
|
1026
|
+
};
|
|
1027
|
+
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
1028
|
+
// If not, use the default `cds.String` with a length of 14.
|
|
1029
|
+
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
1030
|
+
const locale = {
|
|
1031
|
+
name: { location, id: 'locale' },
|
|
1032
|
+
kind: 'element',
|
|
1033
|
+
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
1034
|
+
location,
|
|
1035
|
+
};
|
|
1036
|
+
if (!hasLocaleType)
|
|
1037
|
+
locale.length = { literal: 'number', val: 14, location };
|
|
1038
|
+
|
|
1039
|
+
if (!fioriEnabled) {
|
|
1040
|
+
locale.key = { val: true, location };
|
|
1041
|
+
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
1042
|
+
// TODO (next major version): remove?
|
|
1043
|
+
annotateWith( art, '@odata.draft.enabled', art.location, false );
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
const textId = {
|
|
1047
|
+
name: { location, id: 'ID_texts' },
|
|
1048
|
+
kind: 'element',
|
|
1049
|
+
key: { val: true, location },
|
|
1050
|
+
type: augmentPath( location, 'cds.UUID' ),
|
|
1051
|
+
location,
|
|
1052
|
+
};
|
|
1053
|
+
dictAdd( art.elements, 'ID_texts', textId );
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
dictAdd( art.elements, 'locale', locale );
|
|
1057
|
+
setLink( art, '_block', model.$internal );
|
|
1058
|
+
model.definitions[absolute] = art;
|
|
898
1059
|
|
|
899
1060
|
return art;
|
|
900
1061
|
}
|
|
@@ -937,6 +1098,22 @@ function extend( model ) {
|
|
|
937
1098
|
setLink( localized, '_block', model.$internal );
|
|
938
1099
|
}
|
|
939
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
|
+
|
|
940
1117
|
/**
|
|
941
1118
|
* Returns whether `art` directly or indirectly has the property 'prop',
|
|
942
1119
|
* following the 'origin' and the 'type' (not involving elements).
|
|
@@ -1086,6 +1263,15 @@ function extend( model ) {
|
|
|
1086
1263
|
});
|
|
1087
1264
|
return false;
|
|
1088
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
|
+
|
|
1089
1275
|
return true;
|
|
1090
1276
|
}
|
|
1091
1277
|
|
|
@@ -1268,7 +1454,7 @@ function compareAssignments( a, b ) {
|
|
|
1268
1454
|
* @param {object} source
|
|
1269
1455
|
* @param {CSN.Options} options
|
|
1270
1456
|
*/
|
|
1271
|
-
function copyPersistenceAnnotations(target, source, options) {
|
|
1457
|
+
function copyPersistenceAnnotations( target, source, options ) {
|
|
1272
1458
|
if (!source)
|
|
1273
1459
|
return;
|
|
1274
1460
|
|
|
@@ -1325,4 +1511,24 @@ function storeTypeExtension( ext, art ) {
|
|
|
1325
1511
|
art._extendType.push( ext );
|
|
1326
1512
|
}
|
|
1327
1513
|
|
|
1514
|
+
|
|
1515
|
+
function checkTextsLanguageAssocOption( model, options ) {
|
|
1516
|
+
const languages = model.definitions['sap.common.Languages'];
|
|
1517
|
+
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
|
|
1518
|
+
|
|
1519
|
+
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
1520
|
+
const variant = !languages ? 'std' : 'code';
|
|
1521
|
+
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
1522
|
+
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
1523
|
+
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
1524
|
+
}, {
|
|
1525
|
+
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
1526
|
+
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
return !!commonLanguagesEntity;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
|
|
1328
1534
|
module.exports = extend;
|
|
@@ -67,7 +67,7 @@ function finalizeParseCdl( model ) {
|
|
|
67
67
|
* @param {*} artifact
|
|
68
68
|
* @param {XSN.Artifact} main
|
|
69
69
|
*/
|
|
70
|
-
function resolveTypesForParseCdl(artifact, main) {
|
|
70
|
+
function resolveTypesForParseCdl( artifact, main ) {
|
|
71
71
|
if (!artifact || typeof artifact !== 'object')
|
|
72
72
|
return;
|
|
73
73
|
|
|
@@ -160,7 +160,7 @@ function finalizeParseCdl( model ) {
|
|
|
160
160
|
* @param {object} artWithType
|
|
161
161
|
* @param {XSN.Artifact} user
|
|
162
162
|
*/
|
|
163
|
-
function resolveTypeUnchecked(artWithType, user) {
|
|
163
|
+
function resolveTypeUnchecked( artWithType, user ) {
|
|
164
164
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
165
165
|
if (!root) // parse error
|
|
166
166
|
return;
|
|
@@ -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 } );
|
|
@@ -202,7 +207,7 @@ function finalizeParseCdl( model ) {
|
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
|
|
205
|
-
function chooseAndReportDuplicateAnnotation(artifact, annoName) {
|
|
210
|
+
function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
|
|
206
211
|
for (const anno of artifact[annoName])
|
|
207
212
|
message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
|
|
208
213
|
|
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
|
@@ -45,6 +45,7 @@ const {
|
|
|
45
45
|
dependsOn,
|
|
46
46
|
traverseQueryPost,
|
|
47
47
|
setExpandStatus,
|
|
48
|
+
setExpandStatusAnnotate,
|
|
48
49
|
} = require('./utils');
|
|
49
50
|
|
|
50
51
|
const $inferred = Symbol.for('cds.$inferred');
|
|
@@ -163,7 +164,6 @@ function populate( model ) {
|
|
|
163
164
|
const chain = [];
|
|
164
165
|
while (art && !('_effectiveType' in art) &&
|
|
165
166
|
(art.type || art._origin || art.value?.path || art.value?.type) &&
|
|
166
|
-
// TODO: really stop at art.enum? See #8942
|
|
167
167
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
168
168
|
chain.push( art );
|
|
169
169
|
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
@@ -192,17 +192,15 @@ function populate( model ) {
|
|
|
192
192
|
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
193
193
|
let cardinality = art.cardinality ||
|
|
194
194
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
195
|
-
let prev = art;
|
|
196
195
|
for (const a of chain) {
|
|
197
196
|
if (a.cardinality)
|
|
198
197
|
cardinality = a.cardinality;
|
|
199
198
|
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
200
199
|
art.target && redirectImplicitly( a, art ) ||
|
|
201
200
|
art.elements && expandElements( a, art ) ||
|
|
202
|
-
art.items && expandItems( a, art )
|
|
201
|
+
art.items && expandItems( a, art ) ||
|
|
202
|
+
art.enum && expandEnum( a, art ))
|
|
203
203
|
art = a;
|
|
204
|
-
else if (art.enum && expandEnum( a, prev ))
|
|
205
|
-
prev = a; // do not set art - effective type is base
|
|
206
204
|
setLink( a, '_effectiveType', art );
|
|
207
205
|
}
|
|
208
206
|
}
|
|
@@ -469,11 +467,21 @@ function populate( model ) {
|
|
|
469
467
|
'Element $(ID) is missing in specified elements' );
|
|
470
468
|
}
|
|
471
469
|
else {
|
|
470
|
+
let wasAnnotated = false;
|
|
472
471
|
for (const prop in selem) {
|
|
473
472
|
// just annotation assignments and doc comments for the moment
|
|
474
|
-
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
473
|
+
if (prop.charAt(0) === '@' || prop === 'doc') {
|
|
475
474
|
ielem[prop] = selem[prop];
|
|
475
|
+
// required for gensrc mode of to-csn.js, otherwise the annotation
|
|
476
|
+
// may be lost during recompilation.
|
|
477
|
+
ielem[prop].$priority = 'annotate';
|
|
478
|
+
wasAnnotated = true;
|
|
479
|
+
}
|
|
476
480
|
}
|
|
481
|
+
|
|
482
|
+
if (wasAnnotated)
|
|
483
|
+
setExpandStatusAnnotate(art, 'annotate');
|
|
484
|
+
|
|
477
485
|
selem.$replacement = true;
|
|
478
486
|
if (selem.elements) {
|
|
479
487
|
setLink(ielem, 'elements$', selem.elements);
|
|
@@ -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 ) {
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -63,6 +63,7 @@ const {
|
|
|
63
63
|
storeExtension,
|
|
64
64
|
dependsOn,
|
|
65
65
|
dependsOnSilent,
|
|
66
|
+
setExpandStatusAnnotate,
|
|
66
67
|
testExpr,
|
|
67
68
|
targetMaxNotOne,
|
|
68
69
|
traverseQueryPost,
|
|
@@ -618,20 +619,6 @@ function resolve( model ) {
|
|
|
618
619
|
}
|
|
619
620
|
}
|
|
620
621
|
|
|
621
|
-
function setExpandStatusAnnotate( elem, status ) {
|
|
622
|
-
for (;;) {
|
|
623
|
-
if (elem.$expand === status)
|
|
624
|
-
return; // already set
|
|
625
|
-
elem.$expand = status; // meaning: expanded, containing annos
|
|
626
|
-
for (let line = elem.items; line; line = line.items)
|
|
627
|
-
line.$expand = status; // to-csn just uses the innermost $expand
|
|
628
|
-
if (!elem._main)
|
|
629
|
-
return;
|
|
630
|
-
elem = elem._parent;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
622
|
function expandParameters( action ) {
|
|
636
623
|
// see also expandElements()
|
|
637
624
|
if (!effectiveType( action ))
|
|
@@ -1291,7 +1278,7 @@ function resolve( model ) {
|
|
|
1291
1278
|
}
|
|
1292
1279
|
}
|
|
1293
1280
|
|
|
1294
|
-
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
|
1281
|
+
function resolveExpr( expr, expected, user, extDict, expandOrInline ) {
|
|
1295
1282
|
// TODO: when we have rewritten the resolvePath functions,
|
|
1296
1283
|
// define a traverseExpr() in ./utils.js
|
|
1297
1284
|
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
|