@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/compiler/shared.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
setArtifactLink,
|
|
12
12
|
dependsOn,
|
|
13
13
|
pathName,
|
|
14
|
+
annotationHasEllipsis,
|
|
14
15
|
} = require('./utils');
|
|
15
16
|
|
|
16
17
|
function artifactsEnv( art ) {
|
|
@@ -157,8 +158,8 @@ function fns( model ) {
|
|
|
157
158
|
const VolatileFns = model.$volatileFunctions;
|
|
158
159
|
Object.assign( model.$functions, {
|
|
159
160
|
resolveUncheckedPath,
|
|
161
|
+
resolveTypeArgumentsUnchecked,
|
|
160
162
|
resolvePath,
|
|
161
|
-
resolveTypeArguments,
|
|
162
163
|
defineAnnotations,
|
|
163
164
|
attachAndEmitValidNames,
|
|
164
165
|
} );
|
|
@@ -459,35 +460,64 @@ function fns( model ) {
|
|
|
459
460
|
}
|
|
460
461
|
}
|
|
461
462
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Resolve the type arguments of `artifact` according to the type `typeArtifact`.
|
|
465
|
+
* User is used for semantic message location.
|
|
466
|
+
*
|
|
467
|
+
* For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
|
|
468
|
+
* in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
|
|
469
|
+
*
|
|
470
|
+
* For non-builtins, we take either one or two arguments and interpret them
|
|
471
|
+
* as `length` or `precision`/`scale`.
|
|
472
|
+
*
|
|
473
|
+
* Left-over arguments are errors for non-builtins and warnings for builtins.
|
|
474
|
+
*
|
|
475
|
+
* @param {object} artifact
|
|
476
|
+
* @param {object} typeArtifact
|
|
477
|
+
* @param {CSN.Artifact} user
|
|
478
|
+
*/
|
|
479
|
+
function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) {
|
|
480
|
+
let args = artifact.$typeArgs || [];
|
|
473
481
|
const parameters = typeArtifact.parameters || [];
|
|
474
|
-
const parLength = parameters.length;
|
|
475
482
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
par =
|
|
480
|
-
|
|
481
|
-
|
|
483
|
+
if (parameters.length > 0) {
|
|
484
|
+
// For Builtins
|
|
485
|
+
for (let i = 0; i < parameters.length; ++i) {
|
|
486
|
+
let par = parameters[i];
|
|
487
|
+
if (!(par instanceof Object))
|
|
488
|
+
par = { name: par };
|
|
489
|
+
if (!artifact[par.name] && i < args.length)
|
|
490
|
+
artifact[par.name] = args[i];
|
|
491
|
+
}
|
|
492
|
+
args = args.slice(parameters.length);
|
|
482
493
|
}
|
|
483
|
-
if (args.length >
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
494
|
+
else if (args.length > 0 && !typeArtifact.builtin) {
|
|
495
|
+
// One or two arguments are interpreted as either length or precision/scale.
|
|
496
|
+
// For builtins, we know what arguments are expected, and we do not need this mapping.
|
|
497
|
+
// Also, we expect non-structured types.
|
|
498
|
+
if (args.length === 1) {
|
|
499
|
+
artifact.length = args[0];
|
|
500
|
+
args = args.slice(1);
|
|
501
|
+
}
|
|
502
|
+
else if (args.length === 2) {
|
|
503
|
+
artifact.precision = args[0];
|
|
504
|
+
artifact.scale = args[1];
|
|
505
|
+
args = args.slice(2);
|
|
506
|
+
}
|
|
487
507
|
}
|
|
488
|
-
|
|
489
|
-
|
|
508
|
+
|
|
509
|
+
if (!artifact.$typeArgs)
|
|
510
|
+
return;
|
|
511
|
+
|
|
512
|
+
// Warn about left-over arguments.
|
|
513
|
+
if (args.length > 0) {
|
|
514
|
+
const loc = [ args[args.length - 1].location, user ];
|
|
515
|
+
if (typeArtifact.builtin)
|
|
516
|
+
message( 'type-ignoring-argument', loc, { art: typeArtifact } );
|
|
517
|
+
else
|
|
518
|
+
error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
|
|
490
519
|
}
|
|
520
|
+
artifact.$typeArgs = undefined;
|
|
491
521
|
}
|
|
492
522
|
|
|
493
523
|
// Return artifact or element referred by name `head`. The first environment
|
|
@@ -644,6 +674,9 @@ function fns( model ) {
|
|
|
644
674
|
art = item._artifact;
|
|
645
675
|
if (Array.isArray(art))
|
|
646
676
|
return false;
|
|
677
|
+
if (art.$requireElementAccess && path.length === 1)
|
|
678
|
+
// Path with only one item, but we expect an element, e.g. `$at.from`.
|
|
679
|
+
signalMissingElementAccess(art, [ item.location, user ]);
|
|
647
680
|
continue;
|
|
648
681
|
}
|
|
649
682
|
|
|
@@ -732,7 +765,7 @@ function fns( model ) {
|
|
|
732
765
|
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
|
|
733
766
|
[ env ], { art: a } );
|
|
734
767
|
}
|
|
735
|
-
else if (art.name.select && art.name.select > 1) {
|
|
768
|
+
else if (art.name && art.name.select && art.name.select > 1) {
|
|
736
769
|
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
737
770
|
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
738
771
|
// TODO: probably not extra messageId, but text variant
|
|
@@ -748,8 +781,13 @@ function fns( model ) {
|
|
|
748
781
|
{ param: 'Entity $(ART) has no parameter $(MEMBER)' } );
|
|
749
782
|
}
|
|
750
783
|
else {
|
|
784
|
+
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
751
785
|
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
|
|
752
|
-
[ env ], {
|
|
786
|
+
[ env ], {
|
|
787
|
+
'#': variant,
|
|
788
|
+
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
789
|
+
id: item.id,
|
|
790
|
+
} );
|
|
753
791
|
}
|
|
754
792
|
return null;
|
|
755
793
|
}
|
|
@@ -774,11 +812,30 @@ function fns( model ) {
|
|
|
774
812
|
attachAndEmitValidNames(err, ...valid.reverse());
|
|
775
813
|
}
|
|
776
814
|
|
|
815
|
+
/**
|
|
816
|
+
* Emit a 'ref-expected-element' error for magic variable references
|
|
817
|
+
* that require element accesses but don't do.
|
|
818
|
+
* For example: `$at`, but `$at.from` or `$at.to` is required.
|
|
819
|
+
*
|
|
820
|
+
* @param {object} art
|
|
821
|
+
* @param {any} location
|
|
822
|
+
*/
|
|
823
|
+
function signalMissingElementAccess(art, location) {
|
|
824
|
+
const err = message( 'ref-expected-element', location,
|
|
825
|
+
{ '#': 'magicVar', id: art.name.id } );
|
|
826
|
+
// Mapping for better valid names: from -> $at.from
|
|
827
|
+
const valid = Object.keys(art.elements || {}).reduce((prev, curr) => {
|
|
828
|
+
prev[`${ art.name.id }.${ curr }`] = true;
|
|
829
|
+
return prev;
|
|
830
|
+
}, Object.create(null));
|
|
831
|
+
attachAndEmitValidNames(err, valid);
|
|
832
|
+
}
|
|
833
|
+
|
|
777
834
|
/**
|
|
778
835
|
* Attaches a dictionary of valid names to the given compiler message.
|
|
779
836
|
* In test mode, an info message is emitted with a list of valid names.
|
|
780
837
|
*
|
|
781
|
-
* @param {
|
|
838
|
+
* @param {CompileMessage} msg CDS Compiler message
|
|
782
839
|
* @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
|
|
783
840
|
*/
|
|
784
841
|
function attachAndEmitValidNames(msg, ...validDicts) {
|
|
@@ -804,14 +861,10 @@ function fns( model ) {
|
|
|
804
861
|
}
|
|
805
862
|
}
|
|
806
863
|
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
// * do something for extensions by CSN or Properties parsers
|
|
812
|
-
// * make sure that we do not issue repeated warnings due to flattening if an
|
|
813
|
-
// annotation definition is missing
|
|
814
|
-
function defineAnnotations( construct, art, block, priority = 'define' ) {
|
|
864
|
+
// Set _block links for annotations (necessary for layering).
|
|
865
|
+
// Issue messages for annotations on namespaces and builtins (TODO: really here?)
|
|
866
|
+
// Also copy annotations from `construct` to `art` (TODO: separate that functionality).
|
|
867
|
+
function defineAnnotations( construct, art, block, priority = false ) {
|
|
815
868
|
if (!options.parseCdl && construct.kind === 'annotate') {
|
|
816
869
|
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
817
870
|
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
@@ -829,76 +882,28 @@ function fns( model ) {
|
|
|
829
882
|
'Builtin types should not be annotated. Use custom type instead' );
|
|
830
883
|
}
|
|
831
884
|
}
|
|
832
|
-
|
|
833
|
-
if (construct.$annotations && construct.$annotations.doc )
|
|
834
|
-
art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
|
|
835
|
-
else if (construct.doc)
|
|
885
|
+
if (construct.doc)
|
|
836
886
|
art.doc = construct.doc; // e.g. through `extensions` array in CSN
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
for (const anno of construct.$annotations) {
|
|
857
|
-
const ref = anno.name;
|
|
858
|
-
const name = resolveUncheckedPath( ref, 'annotation', { _block: block } );
|
|
859
|
-
const annoProp = (anno.name.variant)
|
|
860
|
-
? `@${ name }#${ anno.name.variant.id }`
|
|
861
|
-
: `@${ name }`;
|
|
862
|
-
flatten( ref.path, annoProp, anno.value || {}, anno.name.variant, anno.name.location );
|
|
863
|
-
}
|
|
864
|
-
return;
|
|
865
|
-
|
|
866
|
-
function flatten( path, annoProp, value, iHaveVariant, location ) {
|
|
867
|
-
// Be robust if struct value has duplicate element names
|
|
868
|
-
if (Array.isArray(value)) // TODO: do that differently in CDL parser
|
|
869
|
-
return; // discard duplicates in flattened form
|
|
870
|
-
|
|
871
|
-
if (value.literal === 'struct') {
|
|
872
|
-
for (const item of value._struct || []) {
|
|
873
|
-
let prop = pathName(item.name.path);
|
|
874
|
-
if (item.name.variant) {
|
|
875
|
-
if (iHaveVariant) {
|
|
876
|
-
error( 'anno-duplicate-variant', [ item.name.variant.location, construct ],
|
|
877
|
-
{}, // TODO: params
|
|
878
|
-
'Annotation variant has been already provided' );
|
|
879
|
-
}
|
|
880
|
-
prop = `${ prop }#${ item.name.variant.id }`; // TODO: check for double variants
|
|
887
|
+
// set _block (for layering) and $priority, shallow-copy from extension
|
|
888
|
+
// TODO: think of removing $priority, then
|
|
889
|
+
// no _block: define, _block: annotate/extend/edmx
|
|
890
|
+
// would fit with extending defs with props like length
|
|
891
|
+
for (const annoProp in construct) {
|
|
892
|
+
if (annoProp.charAt(0) === '@') {
|
|
893
|
+
let annos = construct[annoProp];
|
|
894
|
+
if (!(Array.isArray(annos)))
|
|
895
|
+
annos = [ annos ];
|
|
896
|
+
for (const a of annos) {
|
|
897
|
+
setLink( a, '_block', block );
|
|
898
|
+
a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
|
|
899
|
+
if (construct !== art)
|
|
900
|
+
addAnnotation( art, annoProp, a );
|
|
901
|
+
if (!priority && annotationHasEllipsis( a )) {
|
|
902
|
+
error( 'anno-unexpected-ellipsis',
|
|
903
|
+
[ a.name.location, art ], { code: '...' } );
|
|
881
904
|
}
|
|
882
|
-
flatten( [ ...path, ...item.name.path ], `${ annoProp }.${ prop }`, item, iHaveVariant || item.name.variant);
|
|
883
|
-
}
|
|
884
|
-
for (const prop in value.struct) {
|
|
885
|
-
const item = value.struct[prop];
|
|
886
|
-
flatten( [ ...path, item.name ], `${ annoProp }.${ prop }`, item, iHaveVariant );
|
|
887
905
|
}
|
|
888
|
-
return;
|
|
889
906
|
}
|
|
890
|
-
const anno = Object.assign( {}, value ); // shallow copy
|
|
891
|
-
anno.name = {
|
|
892
|
-
path,
|
|
893
|
-
location: location ||
|
|
894
|
-
value.name && value.name.location ||
|
|
895
|
-
value.path && value.path.location,
|
|
896
|
-
};
|
|
897
|
-
setLink( anno, '_block', block );
|
|
898
|
-
// TODO: _parent, _main is set later (if we have ElementRef), or do we
|
|
899
|
-
// set _artifact?
|
|
900
|
-
anno.$priority = priority;
|
|
901
|
-
addAnnotation( art, annoProp, anno );
|
|
902
907
|
}
|
|
903
908
|
}
|
|
904
909
|
}
|
|
@@ -39,7 +39,7 @@ function tweakAssocs( model ) {
|
|
|
39
39
|
const { environment } = model.$volatileFunctions;
|
|
40
40
|
|
|
41
41
|
// behavior depending on option `deprecated`:
|
|
42
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '
|
|
42
|
+
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
43
43
|
// TODO: we should get rid of noElementsExpansion soon; both
|
|
44
44
|
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
45
45
|
|
|
@@ -95,15 +95,14 @@ function tweakAssocs( model ) {
|
|
|
95
95
|
if (!target || target._service) // assoc to other service is OK
|
|
96
96
|
return;
|
|
97
97
|
if (!elem.$inferred) { // && !elem.target.$inferred
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} );
|
|
98
|
+
info( 'assoc-target-not-in-service', [ elem.target.location, elem ],
|
|
99
|
+
{ target, '#': (elem._main.query ? 'select' : 'define') }, {
|
|
100
|
+
std: 'Target $(TARGET) of association is outside any service', // not used
|
|
101
|
+
// eslint-disable-next-line max-len
|
|
102
|
+
define: 'Target $(TARGET) of explicitly defined association is outside any service',
|
|
103
|
+
// eslint-disable-next-line max-len
|
|
104
|
+
select: 'Target $(TARGET) of explicitly selected association is outside any service',
|
|
105
|
+
} );
|
|
107
106
|
}
|
|
108
107
|
else {
|
|
109
108
|
info( 'assoc-outside-service', [ elem.target.location, elem ],
|
|
@@ -459,7 +458,13 @@ function tweakAssocs( model ) {
|
|
|
459
458
|
if (!forKeys)
|
|
460
459
|
break;
|
|
461
460
|
setArtifactLink( item, null );
|
|
462
|
-
|
|
461
|
+
const culprit = elem.target && !elem.target.$inferred && elem.target ||
|
|
462
|
+
(elem.value && elem.value.path &&
|
|
463
|
+
elem.value.path[elem.value.path.length - 1]) ||
|
|
464
|
+
elem;
|
|
465
|
+
// TODO: probably better to collect the non-projected foreign keys
|
|
466
|
+
// and have one message for all
|
|
467
|
+
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ],
|
|
463
468
|
{ id: item.id, art: alias._main },
|
|
464
469
|
'Foreign key $(ID) has not been found in target $(ART)' );
|
|
465
470
|
return null;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -31,6 +31,10 @@ function annotationVal( anno ) {
|
|
|
31
31
|
function annotationIsFalse( anno ) { // falsy, but not null (unset)
|
|
32
32
|
return anno && (anno.val === false || anno.val === 0 || anno.val === '');
|
|
33
33
|
}
|
|
34
|
+
function annotationHasEllipsis( anno ) {
|
|
35
|
+
const { val } = anno || {};
|
|
36
|
+
return Array.isArray( val ) && val.some( v => v.literal === 'token' && v.val === '...' );
|
|
37
|
+
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* Set compiler-calculated annotation value.
|
|
@@ -381,6 +385,7 @@ module.exports = {
|
|
|
381
385
|
pushLink,
|
|
382
386
|
annotationVal,
|
|
383
387
|
annotationIsFalse,
|
|
388
|
+
annotationHasEllipsis,
|
|
384
389
|
annotateWith,
|
|
385
390
|
setLink,
|
|
386
391
|
setArtifactLink,
|
|
@@ -4,15 +4,16 @@ const edmUtils = require('../edmUtils.js');
|
|
|
4
4
|
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
5
5
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
6
6
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
7
|
+
const { forEach } = require("../../utils/objectUtils");
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
/*
|
|
10
|
-
|
|
10
|
+
/*
|
|
11
11
|
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
|
|
12
12
|
Aggregation (published)
|
|
13
13
|
Authorization (published)
|
|
14
14
|
Capabilities (published)
|
|
15
15
|
Core (published)
|
|
16
|
+
JSON (published)
|
|
16
17
|
Measures (published)
|
|
17
18
|
Repeatability (published)
|
|
18
19
|
Temporal (not published, not yet finalized)
|
|
@@ -23,8 +24,9 @@ const { forEachDefinition } = require('../../model/csnUtils');
|
|
|
23
24
|
CodeList (published)
|
|
24
25
|
Common (pubished)
|
|
25
26
|
Communication (published)
|
|
27
|
+
DataIntegration (published)
|
|
26
28
|
Graph (published, experimental)
|
|
27
|
-
Hierarchy (
|
|
29
|
+
Hierarchy (published, experimental)
|
|
28
30
|
HTML5 (published, experimental)
|
|
29
31
|
ODM (published, experimental)
|
|
30
32
|
PersonalData (published)
|
|
@@ -73,16 +75,31 @@ const vocabularyDefinitions = {
|
|
|
73
75
|
'inc': { Alias: 'Core', Namespace: 'Org.OData.Core.V1' },
|
|
74
76
|
'int': { filename: 'Core.xml' }
|
|
75
77
|
},
|
|
78
|
+
'DataIntegration': {
|
|
79
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/DataIntegration.xml' },
|
|
80
|
+
'inc': { Alias: 'DataIntegration', Namespace: 'com.sap.vocabularies.DataIntegration.v1' },
|
|
81
|
+
'int': { filename: 'DataIntegration.xml' }
|
|
82
|
+
},
|
|
76
83
|
'Graph': {
|
|
77
84
|
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Graph.xml' },
|
|
78
85
|
'inc': { Alias: 'Graph', Namespace: 'com.sap.vocabularies.Graph.v1' },
|
|
79
86
|
'int': { filename: 'Graph.xml' }
|
|
80
87
|
},
|
|
88
|
+
'Hierarchy': {
|
|
89
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Hierarchy.xml' },
|
|
90
|
+
'inc': { Alias: 'Hierarchy', Namespace: 'com.sap.vocabularies.Hierarchy.v1' },
|
|
91
|
+
'int': { filename: 'Hierarchy.xml' }
|
|
92
|
+
},
|
|
81
93
|
'HTML5': {
|
|
82
94
|
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/HTML5.xml' },
|
|
83
95
|
'inc': { Alias: 'HTML5', Namespace: 'com.sap.vocabularies.HTML5.v1' },
|
|
84
96
|
'int': { filename: 'HTML5.xml' }
|
|
85
97
|
},
|
|
98
|
+
'JSON': {
|
|
99
|
+
'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.JSON.V1.xml' },
|
|
100
|
+
'inc': { Alias: 'JSON', Namespace: 'Org.OData.JSON.V1' },
|
|
101
|
+
'int': { filename: 'JSON.xml' }
|
|
102
|
+
},
|
|
86
103
|
'Measures': {
|
|
87
104
|
'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml' },
|
|
88
105
|
'inc': { Alias: 'Measures', Namespace: 'Org.OData.Measures.V1' },
|
|
@@ -316,7 +333,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
316
333
|
function handleNestedElements(objname, baseElemName, elementsObj) {
|
|
317
334
|
if(!elementsObj) return;
|
|
318
335
|
Object.entries(elementsObj).forEach(([elemName, element]) => {
|
|
319
|
-
if (Object.keys(element).filter( x => x
|
|
336
|
+
if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
|
|
320
337
|
message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
|
|
321
338
|
}
|
|
322
339
|
|
|
@@ -414,17 +431,23 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
414
431
|
// do nothing
|
|
415
432
|
|
|
416
433
|
if(!isEdmPropertyRendered(carrier, options) ||
|
|
417
|
-
(isV2() && (edmUtils.isDerivedType(carrier)
|
|
434
|
+
(isV2() && (edmUtils.isDerivedType(carrier)))) {
|
|
418
435
|
return;
|
|
419
436
|
}
|
|
420
437
|
|
|
421
438
|
// Filter unknown toplevel annotations
|
|
422
439
|
// Final filtering of all annotations is done in handleTerm
|
|
423
|
-
|
|
440
|
+
|
|
441
|
+
let annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
424
442
|
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
425
|
-
|
|
443
|
+
let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
426
444
|
if (knownAnnos.length === 0) return;
|
|
427
445
|
|
|
446
|
+
if(rewriteInnerAnnotations()) {
|
|
447
|
+
annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
448
|
+
knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
449
|
+
if (knownAnnos.length === 0) return;
|
|
450
|
+
}
|
|
428
451
|
const prefixTree = createPrefixTree();
|
|
429
452
|
|
|
430
453
|
// usually, for a given carrier there is one target
|
|
@@ -593,6 +616,51 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
593
616
|
*/
|
|
594
617
|
}
|
|
595
618
|
|
|
619
|
+
function rewriteInnerAnnotations() {
|
|
620
|
+
let rc = false;
|
|
621
|
+
for (let a of knownAnnos) {
|
|
622
|
+
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
623
|
+
/*
|
|
624
|
+
New inner annotation (de-)structuring of the core compiler to make
|
|
625
|
+
$value arrays extendable via ellipsis
|
|
626
|
+
@anno: { $value: [ ... ], @innerAnno: ... } is now cracked up by
|
|
627
|
+
the core compiler into:
|
|
628
|
+
@anno: [ ...]
|
|
629
|
+
@anno.@innerAnno: ...
|
|
630
|
+
|
|
631
|
+
Conflict handling if $value is present:
|
|
632
|
+
@anno
|
|
633
|
+
@anno.$value
|
|
634
|
+
@anno.@innerAnno
|
|
635
|
+
|
|
636
|
+
@anno has precedence (as it was before this change) but now
|
|
637
|
+
@anno.$value is overwritten with @anno and the inner annotations
|
|
638
|
+
are applied.
|
|
639
|
+
|
|
640
|
+
Trigger is always the inner annotation, if no inner annotation
|
|
641
|
+
is available, @anno has precedence.
|
|
642
|
+
|
|
643
|
+
Insert $value into $edmJson with inner annotation as well.
|
|
644
|
+
*/
|
|
645
|
+
if(innerAnnotation) {
|
|
646
|
+
if(carrier[prefix]) {
|
|
647
|
+
const valPrefix = prefix + '.$value';
|
|
648
|
+
carrier[valPrefix] = carrier[prefix];
|
|
649
|
+
delete carrier[prefix];
|
|
650
|
+
rc = true;
|
|
651
|
+
}
|
|
652
|
+
const edmJsonPrefix = prefix + '.$edmJson';
|
|
653
|
+
if(carrier[edmJsonPrefix]) {
|
|
654
|
+
const valPrefix = prefix + '.$value.$edmJson';
|
|
655
|
+
carrier[valPrefix] = carrier[edmJsonPrefix];
|
|
656
|
+
delete carrier[edmJsonPrefix];
|
|
657
|
+
rc = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return rc;
|
|
662
|
+
}
|
|
663
|
+
|
|
596
664
|
function createPrefixTree() {
|
|
597
665
|
// in csn, all annotations are flattened
|
|
598
666
|
// => values can be - primitive values (string, number)
|
|
@@ -602,6 +670,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
602
670
|
// by building a "prefix tree" for the annotations attached to the carrier
|
|
603
671
|
// see example at definition of function mergePathStepsIntoPrefixTree
|
|
604
672
|
const prefixTree = {};
|
|
673
|
+
|
|
605
674
|
for (let a of knownAnnos) {
|
|
606
675
|
// remove leading @ and split at "."
|
|
607
676
|
// stop splitting at ".@" (used for nested annotations)
|
|
@@ -663,7 +732,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
663
732
|
}
|
|
664
733
|
mergePathStepsIntoPrefixTree(tree[name], pathSteps, index+1, carrier);
|
|
665
734
|
}
|
|
666
|
-
else {
|
|
735
|
+
else if(typeof tree === 'object' ){
|
|
667
736
|
tree[name] = carrier['@' + pathSteps.join('.')];
|
|
668
737
|
}
|
|
669
738
|
}
|
|
@@ -714,9 +783,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
714
783
|
* @type {object}
|
|
715
784
|
* */
|
|
716
785
|
let newAnno = undefined;
|
|
717
|
-
const
|
|
786
|
+
const omissions = { 'Aggregation.default':1 };
|
|
787
|
+
const nullList = { 'Core.OperationAvailable':1 };
|
|
718
788
|
const voc = termName.slice(0, termName.indexOf('.'));
|
|
719
|
-
if(vocabularyDefinitions[voc] && annoValue !== null ||
|
|
789
|
+
if(vocabularyDefinitions[voc] && annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
720
790
|
newAnno = new Edm.Annotation(v, termName);
|
|
721
791
|
|
|
722
792
|
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
|
|
@@ -729,8 +799,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
729
799
|
message(error, context,
|
|
730
800
|
`OData annotation qualifier "${ p[1] }" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`);
|
|
731
801
|
}
|
|
732
|
-
newAnno.Term
|
|
733
|
-
newAnno.Qualifier
|
|
802
|
+
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
|
|
803
|
+
newAnno.setEdmAttribute('Qualifier', p[1]);
|
|
734
804
|
}
|
|
735
805
|
if (p.length>2) {
|
|
736
806
|
message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
|
|
@@ -827,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
827
897
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
828
898
|
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
|
|
829
899
|
}
|
|
830
|
-
else if ( Object.keys(cAnnoValue).filter( x => x
|
|
900
|
+
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
831
901
|
// object consists only of properties starting with "@"
|
|
832
902
|
message(warning, context, 'nested annotations without corresponding base annotation');
|
|
833
903
|
}
|
|
@@ -934,6 +1004,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
934
1004
|
// Edm.Decimal -> Decimal
|
|
935
1005
|
// integer tpye -> Int
|
|
936
1006
|
function handleSimpleValue(val, dTypeName, context) {
|
|
1007
|
+
// these types must be represented as "String" values in XML:
|
|
1008
|
+
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
|
937
1009
|
// caller already made sure that val is neither object nor array
|
|
938
1010
|
dTypeName = resolveType(dTypeName);
|
|
939
1011
|
|
|
@@ -971,12 +1043,12 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
971
1043
|
message(warning, context, `found String, but expected enum type ${ dTypeName }`);
|
|
972
1044
|
typeName = 'EnumMember';
|
|
973
1045
|
}
|
|
974
|
-
else if (dTypeName && dTypeName.startsWith('Edm.') && dTypeName
|
|
1046
|
+
else if (dTypeName && dTypeName.startsWith('Edm.') && !castToXmlString.includes(dTypeName)) {
|
|
975
1047
|
// this covers also all paths
|
|
976
1048
|
typeName = dTypeName.substring(4);
|
|
977
1049
|
}
|
|
978
1050
|
else {
|
|
979
|
-
if(dTypeName == undefined ||
|
|
1051
|
+
if(dTypeName == undefined || castToXmlString.some(t => t === dTypeName))
|
|
980
1052
|
dTypeName = 'Edm.String';
|
|
981
1053
|
// TODO
|
|
982
1054
|
//message(warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
@@ -1072,7 +1144,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1072
1144
|
// this type doesn't exist
|
|
1073
1145
|
message(warning, context, `explicitly specified type '${ actualTypeName }' not found in vocabulary`);
|
|
1074
1146
|
// explicitly mentioned type, render in XML and JSON
|
|
1075
|
-
newRecord.Type
|
|
1147
|
+
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1076
1148
|
}
|
|
1077
1149
|
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1078
1150
|
// this type doesn't fit the expected one
|
|
@@ -1080,7 +1152,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1080
1152
|
}' is not derived from expected type '${ dTypeName }'`);
|
|
1081
1153
|
actualTypeName = dTypeName;
|
|
1082
1154
|
// explicitly mentioned type, render in XML and JSON
|
|
1083
|
-
newRecord.Type
|
|
1155
|
+
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1084
1156
|
}
|
|
1085
1157
|
else if (isAbstractType(actualTypeName)) {
|
|
1086
1158
|
// this type is abstract
|
|
@@ -1088,7 +1160,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1088
1160
|
if(dTypeName)
|
|
1089
1161
|
actualTypeName = dTypeName;
|
|
1090
1162
|
// set to definition name and render in XML and JSON
|
|
1091
|
-
newRecord.Type
|
|
1163
|
+
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1092
1164
|
}
|
|
1093
1165
|
else {
|
|
1094
1166
|
// ok
|
|
@@ -1251,7 +1323,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1251
1323
|
const props = Object.create(null);
|
|
1252
1324
|
Object.entries(obj).forEach(([k, val]) => {
|
|
1253
1325
|
if(k === '@type') {
|
|
1254
|
-
edmNode.Type
|
|
1326
|
+
edmNode.setEdmAttribute('Type', val);
|
|
1255
1327
|
}
|
|
1256
1328
|
else {
|
|
1257
1329
|
let child = undefined;
|
|
@@ -1297,14 +1369,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1297
1369
|
edmNode = exprDef.create(obj);
|
|
1298
1370
|
|
|
1299
1371
|
// iterate over each obj.property and translate expression into EDM
|
|
1300
|
-
|
|
1372
|
+
forEach(obj, (name, val) => {
|
|
1301
1373
|
if(exprDef) {
|
|
1302
1374
|
if(exprDef.anno && name[0] === '@') {
|
|
1303
1375
|
edmNode.append(handleTerm(name.slice(1), val, context));
|
|
1304
1376
|
}
|
|
1305
1377
|
else if (exprDef.attr && exprDef.attr.includes(name)) {
|
|
1306
1378
|
if (name[0] === '$') {
|
|
1307
|
-
edmNode
|
|
1379
|
+
edmNode.setEdmAttribute(name.slice(1), val);
|
|
1308
1380
|
}
|
|
1309
1381
|
}
|
|
1310
1382
|
else if (exprDef.jsonAttr && exprDef.jsonAttr.includes(name)) {
|