@sap/cds-compiler 2.15.4 → 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 +33 -1590
- package/bin/cdsc.js +36 -33
- 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 +220 -103
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +60 -20
- package/lib/base/messages.js +65 -24
- package/lib/base/model.js +44 -2
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +1 -1
- 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 +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +87 -9
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/extend.js +59 -11
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +13 -13
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +193 -218
- package/lib/compiler/shared.js +47 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/csn2edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +25 -30
- package/lib/edm/edmUtils.js +10 -24
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -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 +20632 -22313
- package/lib/json/from-csn.js +56 -49
- package/lib/json/to-csn.js +10 -8
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +303 -229
- package/lib/language/language.g4 +573 -629
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +170 -283
- package/lib/model/revealInternalProperties.js +28 -8
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +12 -21
- 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 +334 -339
- package/lib/render/toHdbcds.js +19 -15
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +53 -51
- package/lib/render/utils/common.js +15 -1
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- package/lib/transform/db/views.js +3 -3
- 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 +19 -22
- package/lib/transform/forOdataNew.js +10 -12
- package/lib/transform/localized.js +22 -16
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +3 -3
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- 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/compiler/resolve.js
CHANGED
|
@@ -49,13 +49,13 @@ const { dictAdd } = require('../base/dictionaries');
|
|
|
49
49
|
const { dictLocation } = require('../base/location');
|
|
50
50
|
const { searchName, weakLocation } = require('../base/messages');
|
|
51
51
|
const { combinedLocation } = require('../base/location');
|
|
52
|
-
const { forEachValue } = require('../utils/objectUtils');
|
|
53
52
|
const { typeParameters } = require('./builtins');
|
|
54
53
|
|
|
55
54
|
const { kindProperties } = require('./base');
|
|
56
55
|
const {
|
|
57
56
|
setLink,
|
|
58
57
|
setArtifactLink,
|
|
58
|
+
annotationHasEllipsis,
|
|
59
59
|
pathName,
|
|
60
60
|
linkToOrigin,
|
|
61
61
|
setMemberParent,
|
|
@@ -73,9 +73,6 @@ const layers = require('./moduleLayers');
|
|
|
73
73
|
|
|
74
74
|
const $location = Symbol.for('cds.$location');
|
|
75
75
|
|
|
76
|
-
const annotationPriorities = {
|
|
77
|
-
define: 1, extend: 2, annotate: 2, edmx: 3,
|
|
78
|
-
};
|
|
79
76
|
const $inferred = Symbol.for('cds.$inferred');
|
|
80
77
|
|
|
81
78
|
// Export function of this file. Resolve type references in augmented CSN
|
|
@@ -107,7 +104,7 @@ function resolve( model ) {
|
|
|
107
104
|
/** @type {any} may also be a boolean */
|
|
108
105
|
|
|
109
106
|
// behavior depending on option `deprecated`:
|
|
110
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '
|
|
107
|
+
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
111
108
|
// TODO: we should get rid of noElementsExpansion soon; both
|
|
112
109
|
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
113
110
|
|
|
@@ -211,9 +208,9 @@ function resolve( model ) {
|
|
|
211
208
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany },
|
|
212
209
|
{
|
|
213
210
|
// eslint-disable-next-line max-len
|
|
214
|
-
std: '
|
|
211
|
+
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
215
212
|
// eslint-disable-next-line max-len
|
|
216
|
-
element: '
|
|
213
|
+
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
217
214
|
} );
|
|
218
215
|
}
|
|
219
216
|
// Check that all keys from the source are projected:
|
|
@@ -714,241 +711,219 @@ function resolve( model ) {
|
|
|
714
711
|
}
|
|
715
712
|
|
|
716
713
|
function chooseAssignment( annoName, art ) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
714
|
+
let anno = art[annoName];
|
|
715
|
+
if (!Array.isArray( anno )) { // just one assignment -> use it
|
|
716
|
+
if (!annotationHasEllipsis( anno ))
|
|
717
|
+
return;
|
|
718
|
+
anno = [ anno ];
|
|
719
|
+
}
|
|
720
|
+
// console.log('ASSIGN:',art.name.absolute,annoName)
|
|
721
|
+
const scheduledAssignments = [];
|
|
722
|
+
// sort assignment according to layer (define is bottom layer):
|
|
723
|
+
const layeredAnnos = layeredAssignments( anno );
|
|
724
|
+
let cont = true;
|
|
725
|
+
while (cont) {
|
|
726
|
+
const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
|
|
727
|
+
let index = assignments.length;
|
|
728
|
+
cont = !!index; // safety
|
|
729
|
+
while (--index >= 0) {
|
|
730
|
+
const a = assignments[index];
|
|
731
|
+
scheduledAssignments.push( a );
|
|
732
|
+
if (!annotationHasEllipsis( a )) {
|
|
733
|
+
cont = false;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
723
736
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
737
|
+
if (issue) {
|
|
738
|
+
// eslint-disable-next-line no-nested-ternary
|
|
739
|
+
const msg = (issue === true)
|
|
740
|
+
? 'anno-duplicate'
|
|
741
|
+
: (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
|
|
742
|
+
for (const a of assignments) {
|
|
743
|
+
if (!a.$errorReported)
|
|
744
|
+
message( msg, [ a.name.location, art ], { anno: annoName } );
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// else if (index > 0) -- if we allow multiple assignments in one file - the last wins
|
|
748
|
+
}
|
|
749
|
+
// Now apply the assignments - all but the first have a '...'
|
|
750
|
+
let result = null;
|
|
751
|
+
scheduledAssignments.reverse();
|
|
752
|
+
for (const a of scheduledAssignments)
|
|
753
|
+
result = applyAssignment( result, a, art, annoName );
|
|
754
|
+
art[annoName] = result.name ? result
|
|
755
|
+
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Group assignments by their layers. An assignment provided with a definition
|
|
759
|
+
// is considered to be provided in a layer named '', the lowest layer.
|
|
760
|
+
// TODO: make this usable for extend (elements), too =
|
|
761
|
+
// do not use $priority, make assignments on define do not have own _block
|
|
762
|
+
function layeredAssignments( assignment ) {
|
|
763
|
+
const layered = Object.create(null);
|
|
764
|
+
for (const a of assignment) {
|
|
765
|
+
const layer = a.$priority && layers.layer( a );
|
|
766
|
+
// just consider layer if Extend/Annotate, not Define
|
|
730
767
|
const name = (layer) ? layer.realname : '';
|
|
731
|
-
const done =
|
|
768
|
+
const done = layered[name];
|
|
732
769
|
if (done)
|
|
733
|
-
done.
|
|
770
|
+
done.assignments.push( a );
|
|
734
771
|
else
|
|
735
|
-
|
|
772
|
+
layered[name] = { name, layer, assignments: [ a ] };
|
|
773
|
+
// TODO: file - if set: unique in layer
|
|
736
774
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return;
|
|
775
|
+
return layered;
|
|
776
|
+
}
|
|
740
777
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
function mergeLayeredArrays( mergeTarget ) {
|
|
775
|
-
if (mergeTarget.literal === 'array') {
|
|
776
|
-
let layer = layers.layer( mergeTarget._block );
|
|
777
|
-
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
778
|
-
let pos = findEllipsis( mergeTarget );
|
|
779
|
-
let hasRun = false;
|
|
780
|
-
while (pos > -1 && Object.keys( layerAnnos ).length ) {
|
|
781
|
-
hasRun = true;
|
|
782
|
-
const mergeSource = findLayerCandidate();
|
|
783
|
-
if (mergeSource.literal !== 'array') {
|
|
784
|
-
error( 'anno-mismatched-ellipsis',
|
|
785
|
-
[ mergeSource.name.location, art ], { code: '...' } );
|
|
786
|
-
return mergeTarget;
|
|
787
|
-
}
|
|
788
|
-
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
789
|
-
layer = layers.layer( mergeSource._block );
|
|
790
|
-
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
791
|
-
pos = findEllipsis( mergeTarget );
|
|
792
|
-
}
|
|
793
|
-
// All layers were processed. Remove excess ellipsis.
|
|
794
|
-
if (removeEllipsis( mergeTarget, pos ) && hasRun) {
|
|
795
|
-
// There shouldn't be any ellipsis or we don't have a base annotation.
|
|
796
|
-
// But only if the loop above has run. Otherwise the in-layer merge
|
|
797
|
-
// already warned about this case.
|
|
798
|
-
message( 'anno-unexpected-ellipsis-layers',
|
|
799
|
-
[ mergeTarget.name.location, art ], { code: '...' } );
|
|
800
|
-
}
|
|
778
|
+
// Return assignments of the highest layers.
|
|
779
|
+
// Also return whether there could be an issue:
|
|
780
|
+
// - false: there is just one assignment
|
|
781
|
+
// - 'unrelated': there is just one assignment per layer
|
|
782
|
+
// - true: there is at least one layer with two or more assignments
|
|
783
|
+
// TODO: make this usable for extend (elements), too
|
|
784
|
+
function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
785
|
+
const layerNames = Object.keys( layeredAnnos );
|
|
786
|
+
// console.log('HIB:',layerNames)
|
|
787
|
+
if (layerNames.length <= 1) {
|
|
788
|
+
const name = layerNames[0];
|
|
789
|
+
const { assignments } = layeredAnnos[name] || { assignments: [] };
|
|
790
|
+
delete layeredAnnos[name];
|
|
791
|
+
return { assignments, issue: assignments.length > 1 };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// collect all layers which are lower than another layer
|
|
795
|
+
const allExtends = Object.create(null);
|
|
796
|
+
allExtends[''] = {}; // the "Define" layer
|
|
797
|
+
for (const name of layerNames) {
|
|
798
|
+
if (name) // not the "Define" layer
|
|
799
|
+
Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
|
|
800
|
+
}
|
|
801
|
+
// console.log('HIE:',Object.keys(allExtends))
|
|
802
|
+
const assignments = [];
|
|
803
|
+
const highest = [];
|
|
804
|
+
for (const name of layerNames) {
|
|
805
|
+
if (!(name in allExtends)) {
|
|
806
|
+
const layer = layeredAnnos[name];
|
|
807
|
+
delete layeredAnnos[name];
|
|
808
|
+
highest.push( layer );
|
|
809
|
+
assignments.push( ...layer.assignments );
|
|
801
810
|
}
|
|
802
|
-
return mergeTarget;
|
|
803
811
|
}
|
|
812
|
+
assignments.sort( compareAssignments );
|
|
813
|
+
const good = highest.every( layer => layer.assignments.length === 1 );
|
|
814
|
+
// TODO: use layer.file instead
|
|
815
|
+
const issue = !good || highest.length > 1 && 'unrelated';
|
|
816
|
+
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
|
|
817
|
+
return { assignments, issue };
|
|
818
|
+
}
|
|
804
819
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
820
|
+
function compareAssignments( a, b ) {
|
|
821
|
+
const fileA = layers.realname( a._block );
|
|
822
|
+
const fileB = layers.realname( b._block );
|
|
823
|
+
if (fileA !== fileB)
|
|
824
|
+
return (fileA > fileB) ? 1 : -1;
|
|
825
|
+
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
|
|
826
|
+
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
830
|
+
if (!previousAnno) {
|
|
831
|
+
if (!annotationHasEllipsis( anno ))
|
|
832
|
+
return anno;
|
|
833
|
+
if (anno.$priority) { // already complained about with Define
|
|
834
|
+
message( 'anno-unexpected-ellipsis-layers', // TODO: better location
|
|
835
|
+
[ anno.name.location, art ], { code: '...' } );
|
|
836
|
+
}
|
|
837
|
+
previousAnno = { val: [] };
|
|
838
|
+
}
|
|
839
|
+
else if (previousAnno.literal !== 'array') {
|
|
840
|
+
error( 'anno-mismatched-ellipsis', // TODO: better location
|
|
841
|
+
[ anno.name.location, art ], { code: '...' } );
|
|
842
|
+
previousAnno = { val: [] };
|
|
843
|
+
}
|
|
844
|
+
const previousValue = previousAnno.val;
|
|
845
|
+
let prevPos = 0;
|
|
846
|
+
const result = [];
|
|
847
|
+
for (const item of anno.val) {
|
|
848
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
849
|
+
if (!ell) {
|
|
850
|
+
result.push( item );
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
|
|
854
|
+
while (prevPos < previousValue.length) {
|
|
855
|
+
const prevItem = previousValue[prevPos++];
|
|
856
|
+
result.push( prevItem );
|
|
857
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
858
|
+
upToSpec = false;
|
|
859
|
+
break;
|
|
826
860
|
}
|
|
827
861
|
}
|
|
862
|
+
if (upToSpec) { // non-matched UP TO
|
|
863
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
864
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
865
|
+
}
|
|
828
866
|
}
|
|
829
|
-
return result;
|
|
830
867
|
}
|
|
868
|
+
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
869
|
+
return { val: result, literal: 'array' };
|
|
870
|
+
}
|
|
871
|
+
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
831
872
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
else if (literal === 'struct') {
|
|
839
|
-
return Object.values( upToSpec.struct ).every( checkUpToSpec );
|
|
840
|
-
}
|
|
841
|
-
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
873
|
+
function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
|
|
874
|
+
const { literal } = upToSpec;
|
|
875
|
+
if (!isFullUpTo) { // inside struct of UP TO
|
|
876
|
+
if (literal !== 'struct' && literal !== 'array' )
|
|
842
877
|
return true;
|
|
843
|
-
}
|
|
844
|
-
error( null, [ upToSpec.location, art ],
|
|
845
|
-
{ anno: annoName, code: '... up to', '#': literal },
|
|
846
|
-
{
|
|
847
|
-
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
848
|
-
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
849
|
-
// eslint-disable-next-line max-len
|
|
850
|
-
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
851
|
-
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
852
|
-
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
853
|
-
} );
|
|
854
|
-
return false;
|
|
855
878
|
}
|
|
879
|
+
else if (literal === 'struct') {
|
|
880
|
+
return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
|
|
881
|
+
}
|
|
882
|
+
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
error( null, [ upToSpec.location, art ],
|
|
886
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
887
|
+
{
|
|
888
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
889
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
890
|
+
// eslint-disable-next-line max-len
|
|
891
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
892
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
893
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
894
|
+
} );
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
856
897
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
return false;
|
|
860
|
-
if ('val' in upToSpec) {
|
|
861
|
-
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
862
|
-
return true;
|
|
863
|
-
const typeUpTo = typeof upToSpec.val;
|
|
864
|
-
const typePrev = typeof previousItem.val;
|
|
865
|
-
if (typeUpTo === 'number')
|
|
866
|
-
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
867
|
-
if (typePrev === 'number')
|
|
868
|
-
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
869
|
-
}
|
|
870
|
-
else if (upToSpec.path) {
|
|
871
|
-
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
872
|
-
}
|
|
873
|
-
else if (upToSpec.sym) {
|
|
874
|
-
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
875
|
-
}
|
|
876
|
-
else if (upToSpec.struct && previousItem.struct) {
|
|
877
|
-
return Object.entries( upToSpec.struct )
|
|
878
|
-
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
879
|
-
}
|
|
898
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
899
|
+
if (!previousItem)
|
|
880
900
|
return false;
|
|
901
|
+
if ('val' in upToSpec) {
|
|
902
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
903
|
+
return true;
|
|
904
|
+
const typeUpTo = typeof upToSpec.val;
|
|
905
|
+
const typePrev = typeof previousItem.val;
|
|
906
|
+
if (typeUpTo === 'number')
|
|
907
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
908
|
+
if (typePrev === 'number')
|
|
909
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
881
910
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const ref = pathName( node.path );
|
|
885
|
-
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
889
|
-
let count = 0;
|
|
890
|
-
while (a.literal === 'array' && pos > -1) {
|
|
891
|
-
count++;
|
|
892
|
-
a.val.splice(pos, 1);
|
|
893
|
-
pos = findEllipsis( a );
|
|
894
|
-
}
|
|
895
|
-
return count;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
function findEllipsis(a) {
|
|
899
|
-
return (a.literal === 'array' && a.val)
|
|
900
|
-
? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
|
|
911
|
+
else if (upToSpec.path) {
|
|
912
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
901
913
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
// collect assignments of upper layers (are in no _layerExtends)
|
|
905
|
-
const exts = Object.keys( layerAnnos ).map( layerExtends );
|
|
906
|
-
const allExtends = Object.assign( Object.create(null), ...exts );
|
|
907
|
-
const collected = [];
|
|
908
|
-
for (const name in layerAnnos) {
|
|
909
|
-
if (!(name in allExtends))
|
|
910
|
-
collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
|
|
911
|
-
}
|
|
912
|
-
// inspect collected assignments - choose the one or signal error
|
|
913
|
-
const justOnePerLayer = collected.every( annos => annos.length === 1);
|
|
914
|
-
if (!justOnePerLayer || collected.length > 1) {
|
|
915
|
-
for (const annos of collected) {
|
|
916
|
-
for (const a of annos ) {
|
|
917
|
-
// Only the message ID is different.
|
|
918
|
-
if (justOnePerLayer) {
|
|
919
|
-
message( 'anno-duplicate-unrelated-layer',
|
|
920
|
-
[ a.name.location, art ], { anno: annoName },
|
|
921
|
-
'Duplicate assignment with $(ANNO)' );
|
|
922
|
-
}
|
|
923
|
-
else {
|
|
924
|
-
message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
return collected[0][0]; // just choose any one with error
|
|
914
|
+
else if (upToSpec.sym) {
|
|
915
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
930
916
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
return layer && layer._layerExtends;
|
|
917
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
918
|
+
return Object.entries( upToSpec.struct )
|
|
919
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
935
920
|
}
|
|
921
|
+
return false;
|
|
936
922
|
}
|
|
937
923
|
|
|
938
|
-
function
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
for (const a of annos) {
|
|
942
|
-
const p = annotationPriorities[a.$priority] || annotationPriorities.define;
|
|
943
|
-
if (p === prio) {
|
|
944
|
-
r.push(a);
|
|
945
|
-
}
|
|
946
|
-
else if (p > prio) {
|
|
947
|
-
r = [ a ];
|
|
948
|
-
prio = p;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
return r;
|
|
924
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
925
|
+
const ref = pathName( node.path );
|
|
926
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
952
927
|
}
|
|
953
928
|
|
|
954
929
|
// Phase 4 - queries and associations --------------------------------------
|
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 ) {
|
|
@@ -512,7 +513,7 @@ function fns( model ) {
|
|
|
512
513
|
if (args.length > 0) {
|
|
513
514
|
const loc = [ args[args.length - 1].location, user ];
|
|
514
515
|
if (typeArtifact.builtin)
|
|
515
|
-
|
|
516
|
+
message( 'type-ignoring-argument', loc, { art: typeArtifact } );
|
|
516
517
|
else
|
|
517
518
|
error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
|
|
518
519
|
}
|
|
@@ -673,6 +674,9 @@ function fns( model ) {
|
|
|
673
674
|
art = item._artifact;
|
|
674
675
|
if (Array.isArray(art))
|
|
675
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 ]);
|
|
676
680
|
continue;
|
|
677
681
|
}
|
|
678
682
|
|
|
@@ -808,11 +812,30 @@ function fns( model ) {
|
|
|
808
812
|
attachAndEmitValidNames(err, ...valid.reverse());
|
|
809
813
|
}
|
|
810
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
|
+
|
|
811
834
|
/**
|
|
812
835
|
* Attaches a dictionary of valid names to the given compiler message.
|
|
813
836
|
* In test mode, an info message is emitted with a list of valid names.
|
|
814
837
|
*
|
|
815
|
-
* @param {
|
|
838
|
+
* @param {CompileMessage} msg CDS Compiler message
|
|
816
839
|
* @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
|
|
817
840
|
*/
|
|
818
841
|
function attachAndEmitValidNames(msg, ...validDicts) {
|
|
@@ -838,14 +861,10 @@ function fns( model ) {
|
|
|
838
861
|
}
|
|
839
862
|
}
|
|
840
863
|
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
|
|
845
|
-
// * do something for extensions by CSN or Properties parsers
|
|
846
|
-
// * make sure that we do not issue repeated warnings due to flattening if an
|
|
847
|
-
// annotation definition is missing
|
|
848
|
-
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 ) {
|
|
849
868
|
if (!options.parseCdl && construct.kind === 'annotate') {
|
|
850
869
|
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
851
870
|
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
@@ -863,76 +882,28 @@ function fns( model ) {
|
|
|
863
882
|
'Builtin types should not be annotated. Use custom type instead' );
|
|
864
883
|
}
|
|
865
884
|
}
|
|
866
|
-
|
|
867
|
-
if (construct.$annotations && construct.$annotations.doc )
|
|
868
|
-
art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
|
|
869
|
-
else if (construct.doc)
|
|
885
|
+
if (construct.doc)
|
|
870
886
|
art.doc = construct.doc; // e.g. through `extensions` array in CSN
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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: '...' } );
|
|
885
904
|
}
|
|
886
905
|
}
|
|
887
906
|
}
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
for (const anno of construct.$annotations) {
|
|
891
|
-
const ref = anno.name;
|
|
892
|
-
const name = resolveUncheckedPath( ref, 'annotation', { _block: block } );
|
|
893
|
-
const annoProp = (anno.name.variant)
|
|
894
|
-
? `@${ name }#${ anno.name.variant.id }`
|
|
895
|
-
: `@${ name }`;
|
|
896
|
-
flatten( ref.path, annoProp, anno.value || {}, anno.name.variant, anno.name.location );
|
|
897
|
-
}
|
|
898
|
-
return;
|
|
899
|
-
|
|
900
|
-
function flatten( path, annoProp, value, iHaveVariant, location ) {
|
|
901
|
-
// Be robust if struct value has duplicate element names
|
|
902
|
-
if (Array.isArray(value)) // TODO: do that differently in CDL parser
|
|
903
|
-
return; // discard duplicates in flattened form
|
|
904
|
-
|
|
905
|
-
if (value.literal === 'struct') {
|
|
906
|
-
for (const item of value._struct || []) {
|
|
907
|
-
let prop = pathName(item.name.path);
|
|
908
|
-
if (item.name.variant) {
|
|
909
|
-
if (iHaveVariant) {
|
|
910
|
-
error( 'anno-duplicate-variant', [ item.name.variant.location, construct ],
|
|
911
|
-
{}, // TODO: params
|
|
912
|
-
'Annotation variant has been already provided' );
|
|
913
|
-
}
|
|
914
|
-
prop = `${ prop }#${ item.name.variant.id }`; // TODO: check for double variants
|
|
915
|
-
}
|
|
916
|
-
flatten( [ ...path, ...item.name.path ], `${ annoProp }.${ prop }`, item, iHaveVariant || item.name.variant);
|
|
917
|
-
}
|
|
918
|
-
for (const prop in value.struct) {
|
|
919
|
-
const item = value.struct[prop];
|
|
920
|
-
flatten( [ ...path, item.name ], `${ annoProp }.${ prop }`, item, iHaveVariant );
|
|
921
|
-
}
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
const anno = Object.assign( {}, value ); // shallow copy
|
|
925
|
-
anno.name = {
|
|
926
|
-
path,
|
|
927
|
-
location: location ||
|
|
928
|
-
value.name && value.name.location ||
|
|
929
|
-
value.path && value.path.location,
|
|
930
|
-
};
|
|
931
|
-
setLink( anno, '_block', block );
|
|
932
|
-
// TODO: _parent, _main is set later (if we have ElementRef), or do we
|
|
933
|
-
// set _artifact?
|
|
934
|
-
anno.$priority = priority;
|
|
935
|
-
addAnnotation( art, annoProp, anno );
|
|
936
907
|
}
|
|
937
908
|
}
|
|
938
909
|
}
|