@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/resolve.js
CHANGED
|
@@ -49,12 +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 {
|
|
52
|
+
const { typeParameters } = require('./builtins');
|
|
53
53
|
|
|
54
54
|
const { kindProperties } = require('./base');
|
|
55
55
|
const {
|
|
56
56
|
setLink,
|
|
57
57
|
setArtifactLink,
|
|
58
|
+
annotationHasEllipsis,
|
|
58
59
|
pathName,
|
|
59
60
|
linkToOrigin,
|
|
60
61
|
setMemberParent,
|
|
@@ -72,9 +73,6 @@ const layers = require('./moduleLayers');
|
|
|
72
73
|
|
|
73
74
|
const $location = Symbol.for('cds.$location');
|
|
74
75
|
|
|
75
|
-
const annotationPriorities = {
|
|
76
|
-
define: 1, extend: 2, annotate: 2, edmx: 3,
|
|
77
|
-
};
|
|
78
76
|
const $inferred = Symbol.for('cds.$inferred');
|
|
79
77
|
|
|
80
78
|
// Export function of this file. Resolve type references in augmented CSN
|
|
@@ -89,13 +87,13 @@ function resolve( model ) {
|
|
|
89
87
|
} = model.$messageFunctions;
|
|
90
88
|
const {
|
|
91
89
|
resolvePath,
|
|
92
|
-
resolveTypeArguments,
|
|
93
90
|
defineAnnotations,
|
|
94
91
|
attachAndEmitValidNames,
|
|
95
92
|
lateExtensions,
|
|
96
93
|
effectiveType,
|
|
97
94
|
directType,
|
|
98
95
|
resolveType,
|
|
96
|
+
resolveTypeArgumentsUnchecked,
|
|
99
97
|
populateQuery,
|
|
100
98
|
} = model.$functions;
|
|
101
99
|
const { environment } = model.$volatileFunctions;
|
|
@@ -106,7 +104,7 @@ function resolve( model ) {
|
|
|
106
104
|
/** @type {any} may also be a boolean */
|
|
107
105
|
|
|
108
106
|
// behavior depending on option `deprecated`:
|
|
109
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '
|
|
107
|
+
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
110
108
|
// TODO: we should get rid of noElementsExpansion soon; both
|
|
111
109
|
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
112
110
|
|
|
@@ -210,9 +208,9 @@ function resolve( model ) {
|
|
|
210
208
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany },
|
|
211
209
|
{
|
|
212
210
|
// eslint-disable-next-line max-len
|
|
213
|
-
std: '
|
|
211
|
+
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
214
212
|
// eslint-disable-next-line max-len
|
|
215
|
-
element: '
|
|
213
|
+
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
216
214
|
} );
|
|
217
215
|
}
|
|
218
216
|
// Check that all keys from the source are projected:
|
|
@@ -371,8 +369,8 @@ function resolve( model ) {
|
|
|
371
369
|
// console.log(obj.name,obj._origin.name)
|
|
372
370
|
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
373
371
|
resolveTarget( art, obj._origin );
|
|
374
|
-
// console.log(
|
|
375
|
-
//
|
|
372
|
+
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
373
|
+
// { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
|
|
376
374
|
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
377
375
|
resolveTarget( art, obj );
|
|
378
376
|
else
|
|
@@ -448,8 +446,10 @@ function resolve( model ) {
|
|
|
448
446
|
// propagation/rewrite has been done yet, cyclic dependency must have been
|
|
449
447
|
// checked before!
|
|
450
448
|
function getAssocSpec( type ) {
|
|
449
|
+
const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
|
|
451
450
|
// only to be called without cycles
|
|
452
|
-
while (type) {
|
|
451
|
+
while (type && !cyclic.has(type)) {
|
|
452
|
+
cyclic.add(type);
|
|
453
453
|
if (type.on || type.foreignKeys || type.targetAspect)
|
|
454
454
|
return type;
|
|
455
455
|
type = directType( type );
|
|
@@ -464,7 +464,7 @@ function resolve( model ) {
|
|
|
464
464
|
// op.val is also correctly set with CSN input
|
|
465
465
|
elem.type = { ...type, $inferred: 'cast' };
|
|
466
466
|
setArtifactLink( elem.type, type._artifact );
|
|
467
|
-
for (const prop of
|
|
467
|
+
for (const prop of typeParameters.list) {
|
|
468
468
|
if (elem.value[prop])
|
|
469
469
|
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
470
470
|
}
|
|
@@ -711,241 +711,219 @@ function resolve( model ) {
|
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
function chooseAssignment( annoName, art ) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
+
}
|
|
720
736
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
|
727
767
|
const name = (layer) ? layer.realname : '';
|
|
728
|
-
const done =
|
|
768
|
+
const done = layered[name];
|
|
729
769
|
if (done)
|
|
730
|
-
done.
|
|
770
|
+
done.assignments.push( a );
|
|
731
771
|
else
|
|
732
|
-
|
|
772
|
+
layered[name] = { name, layer, assignments: [ a ] };
|
|
773
|
+
// TODO: file - if set: unique in layer
|
|
733
774
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
return;
|
|
775
|
+
return layered;
|
|
776
|
+
}
|
|
737
777
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
function mergeLayeredArrays( mergeTarget ) {
|
|
772
|
-
if (mergeTarget.literal === 'array') {
|
|
773
|
-
let layer = layers.layer( mergeTarget._block );
|
|
774
|
-
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
775
|
-
let pos = findEllipsis( mergeTarget );
|
|
776
|
-
let hasRun = false;
|
|
777
|
-
while (pos > -1 && Object.keys( layerAnnos ).length ) {
|
|
778
|
-
hasRun = true;
|
|
779
|
-
const mergeSource = findLayerCandidate();
|
|
780
|
-
if (mergeSource.literal !== 'array') {
|
|
781
|
-
error( 'anno-mismatched-ellipsis',
|
|
782
|
-
[ mergeSource.name.location, art ], { code: '...' } );
|
|
783
|
-
return mergeTarget;
|
|
784
|
-
}
|
|
785
|
-
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
786
|
-
layer = layers.layer( mergeSource._block );
|
|
787
|
-
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
788
|
-
pos = findEllipsis( mergeTarget );
|
|
789
|
-
}
|
|
790
|
-
// All layers were processed. Remove excess ellipsis.
|
|
791
|
-
if (removeEllipsis( mergeTarget, pos ) && hasRun) {
|
|
792
|
-
// There shouldn't be any ellipsis or we don't have a base annotation.
|
|
793
|
-
// But only if the loop above has run. Otherwise the in-layer merge
|
|
794
|
-
// already warned about this case.
|
|
795
|
-
message( 'anno-unexpected-ellipsis-layers',
|
|
796
|
-
[ mergeTarget.name.location, art ], { code: '...' } );
|
|
797
|
-
}
|
|
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 );
|
|
798
810
|
}
|
|
799
|
-
return mergeTarget;
|
|
800
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
|
+
}
|
|
801
819
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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;
|
|
823
860
|
}
|
|
824
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
|
+
}
|
|
825
866
|
}
|
|
826
|
-
return result;
|
|
827
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 ; }
|
|
828
872
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
else if (literal === 'struct') {
|
|
836
|
-
return Object.values( upToSpec.struct ).every( checkUpToSpec );
|
|
837
|
-
}
|
|
838
|
-
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' )
|
|
839
877
|
return true;
|
|
840
|
-
}
|
|
841
|
-
error( null, [ upToSpec.location, art ],
|
|
842
|
-
{ anno: annoName, code: '... up to', '#': literal },
|
|
843
|
-
{
|
|
844
|
-
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
845
|
-
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
846
|
-
// eslint-disable-next-line max-len
|
|
847
|
-
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
848
|
-
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
849
|
-
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
850
|
-
} );
|
|
851
|
-
return false;
|
|
852
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
|
+
}
|
|
853
897
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
return false;
|
|
857
|
-
if ('val' in upToSpec) {
|
|
858
|
-
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
859
|
-
return true;
|
|
860
|
-
const typeUpTo = typeof upToSpec.val;
|
|
861
|
-
const typePrev = typeof previousItem.val;
|
|
862
|
-
if (typeUpTo === 'number')
|
|
863
|
-
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
864
|
-
if (typePrev === 'number')
|
|
865
|
-
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
866
|
-
}
|
|
867
|
-
else if (upToSpec.path) {
|
|
868
|
-
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
869
|
-
}
|
|
870
|
-
else if (upToSpec.sym) {
|
|
871
|
-
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
872
|
-
}
|
|
873
|
-
else if (upToSpec.struct && previousItem.struct) {
|
|
874
|
-
return Object.entries( upToSpec.struct )
|
|
875
|
-
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
876
|
-
}
|
|
898
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
899
|
+
if (!previousItem)
|
|
877
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();
|
|
878
910
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const ref = pathName( node.path );
|
|
882
|
-
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
886
|
-
let count = 0;
|
|
887
|
-
while (a.literal === 'array' && pos > -1) {
|
|
888
|
-
count++;
|
|
889
|
-
a.val.splice(pos, 1);
|
|
890
|
-
pos = findEllipsis( a );
|
|
891
|
-
}
|
|
892
|
-
return count;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
function findEllipsis(a) {
|
|
896
|
-
return (a.literal === 'array' && a.val)
|
|
897
|
-
? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
|
|
911
|
+
else if (upToSpec.path) {
|
|
912
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
898
913
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
// collect assignments of upper layers (are in no _layerExtends)
|
|
902
|
-
const exts = Object.keys( layerAnnos ).map( layerExtends );
|
|
903
|
-
const allExtends = Object.assign( Object.create(null), ...exts );
|
|
904
|
-
const collected = [];
|
|
905
|
-
for (const name in layerAnnos) {
|
|
906
|
-
if (!(name in allExtends))
|
|
907
|
-
collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
|
|
908
|
-
}
|
|
909
|
-
// inspect collected assignments - choose the one or signal error
|
|
910
|
-
const justOnePerLayer = collected.every( annos => annos.length === 1);
|
|
911
|
-
if (!justOnePerLayer || collected.length > 1) {
|
|
912
|
-
for (const annos of collected) {
|
|
913
|
-
for (const a of annos ) {
|
|
914
|
-
// Only the message ID is different.
|
|
915
|
-
if (justOnePerLayer) {
|
|
916
|
-
message( 'anno-duplicate-unrelated-layer',
|
|
917
|
-
[ a.name.location, art ], { anno: annoName },
|
|
918
|
-
'Duplicate assignment with $(ANNO)' );
|
|
919
|
-
}
|
|
920
|
-
else {
|
|
921
|
-
message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
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;
|
|
927
916
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
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 ) );
|
|
932
920
|
}
|
|
921
|
+
return false;
|
|
933
922
|
}
|
|
934
923
|
|
|
935
|
-
function
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
for (const a of annos) {
|
|
939
|
-
const p = annotationPriorities[a.$priority] || annotationPriorities.define;
|
|
940
|
-
if (p === prio) {
|
|
941
|
-
r.push(a);
|
|
942
|
-
}
|
|
943
|
-
else if (p > prio) {
|
|
944
|
-
r = [ a ];
|
|
945
|
-
prio = p;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
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;
|
|
949
927
|
}
|
|
950
928
|
|
|
951
929
|
// Phase 4 - queries and associations --------------------------------------
|
|
@@ -1026,6 +1004,15 @@ function resolve( model ) {
|
|
|
1026
1004
|
return;
|
|
1027
1005
|
}
|
|
1028
1006
|
const target = resolvePath( obj.target, 'target', art );
|
|
1007
|
+
|
|
1008
|
+
if (obj._pathHead && obj.type && !obj.type.$inferred && art._main && art._main.query) {
|
|
1009
|
+
// New association inside expand/inline: The on-condition can't be properly checked,
|
|
1010
|
+
// so abort early. See #8797
|
|
1011
|
+
error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
|
|
1012
|
+
'Unexpected new association in expand/inline' );
|
|
1013
|
+
return; // avoid subsequent errors
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1029
1016
|
if (obj.on) {
|
|
1030
1017
|
if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
|
|
1031
1018
|
// TODO: test of .items a bit unclear - we should somehow restrict the
|
|
@@ -1057,20 +1044,29 @@ function resolve( model ) {
|
|
|
1057
1044
|
else if (art.kind === 'mixin') {
|
|
1058
1045
|
error( 'assoc-in-mixin', [ obj.target.location, art ], {},
|
|
1059
1046
|
'Managed associations are not allowed for MIXIN elements' );
|
|
1047
|
+
return; // avoid subsequent errors
|
|
1048
|
+
}
|
|
1049
|
+
else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
|
|
1050
|
+
// New association in views, i.e. parent is a query.
|
|
1051
|
+
error( 'query-expected-on-condition', [ obj.target.location, art ], {},
|
|
1052
|
+
'Expected on-condition for published association' );
|
|
1053
|
+
return; // avoid subsequent errors
|
|
1060
1054
|
}
|
|
1061
1055
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
1062
1056
|
if (obj.$inferred === 'REDIRECTED') {
|
|
1063
1057
|
addImplicitForeignKeys( art, obj, target );
|
|
1064
1058
|
}
|
|
1065
|
-
else if (
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
|
|
1059
|
+
else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
|
|
1060
|
+
// cds.Association, ...
|
|
1069
1061
|
addImplicitForeignKeys( art, obj, target );
|
|
1070
1062
|
}
|
|
1071
|
-
// else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
|
|
1072
1063
|
}
|
|
1073
|
-
|
|
1064
|
+
|
|
1065
|
+
if (target && !target.$inferred) {
|
|
1066
|
+
if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
|
|
1067
|
+
resolveRedirected( art, target );
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1074
1070
|
}
|
|
1075
1071
|
|
|
1076
1072
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
@@ -1130,7 +1126,9 @@ function resolve( model ) {
|
|
|
1130
1126
|
const assoc = directType( elem );
|
|
1131
1127
|
const origType = assoc && effectiveType( assoc );
|
|
1132
1128
|
if (!origType || !origType.target) {
|
|
1133
|
-
|
|
1129
|
+
const path = (elem.value && elem.value.path);
|
|
1130
|
+
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
1131
|
+
error( 'redirected-no-assoc', [ loc, elem ], {},
|
|
1134
1132
|
'Only an association can be redirected' );
|
|
1135
1133
|
return;
|
|
1136
1134
|
}
|
|
@@ -1138,7 +1136,7 @@ function resolve( model ) {
|
|
|
1138
1136
|
// .toString(), elem.value)
|
|
1139
1137
|
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1140
1138
|
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1141
|
-
if (origType.on) {
|
|
1139
|
+
if (!elem.on && origType.on) {
|
|
1142
1140
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
1143
1141
|
// TODO: Better text ?
|
|
1144
1142
|
'The ON condition is not rewritten here - provide an explicit ON condition' );
|
|
@@ -1151,7 +1149,9 @@ function resolve( model ) {
|
|
|
1151
1149
|
|
|
1152
1150
|
const chain = [];
|
|
1153
1151
|
if (target === origTarget) {
|
|
1154
|
-
if (!elem.target.$inferred) {
|
|
1152
|
+
if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1153
|
+
// Only a managed redirection gets this info message. Because otherwise
|
|
1154
|
+
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1155
1155
|
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1156
1156
|
'The redirected target is the original $(ART)' );
|
|
1157
1157
|
}
|
|
@@ -1265,8 +1265,71 @@ function resolve( model ) {
|
|
|
1265
1265
|
// Resolve the type and its arguments if applicable.
|
|
1266
1266
|
function resolveTypeExpr( art, user ) {
|
|
1267
1267
|
const typeArt = resolveType( art.type, user );
|
|
1268
|
-
if (typeArt)
|
|
1269
|
-
|
|
1268
|
+
if (typeArt) {
|
|
1269
|
+
resolveTypeArgumentsUnchecked( art, typeArt, user );
|
|
1270
|
+
checkTypeArguments( art );
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* Check the type arguments on `artWithType`.
|
|
1276
|
+
* If the effective type is an array or structured type, an error is emitted.
|
|
1277
|
+
*/
|
|
1278
|
+
function checkTypeArguments( artWithType ) {
|
|
1279
|
+
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
|
|
1280
|
+
// Also: For enums, it points to the enum type, which is why this trick is needed.
|
|
1281
|
+
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
|
|
1282
|
+
// trick may be removed if effectiveType() does not stop at enums.
|
|
1283
|
+
const cyclic = new Set();
|
|
1284
|
+
let effectiveTypeArt = effectiveType( artWithType );
|
|
1285
|
+
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
|
|
1286
|
+
cyclic.add(effectiveTypeArt);
|
|
1287
|
+
const underlyingEnumType = directType(effectiveTypeArt);
|
|
1288
|
+
if (underlyingEnumType)
|
|
1289
|
+
effectiveTypeArt = effectiveType(underlyingEnumType);
|
|
1290
|
+
else
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (!effectiveTypeArt)
|
|
1295
|
+
return; // e.g. illegal definition references
|
|
1296
|
+
|
|
1297
|
+
const params = effectiveTypeArt.parameters &&
|
|
1298
|
+
effectiveTypeArt.parameters.map(p => p.name || p) || [];
|
|
1299
|
+
|
|
1300
|
+
for (const param of typeParameters.list) {
|
|
1301
|
+
if (artWithType[param] !== undefined) {
|
|
1302
|
+
if (!params.includes(param)) {
|
|
1303
|
+
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
1304
|
+
const type = directType(artWithType);
|
|
1305
|
+
|
|
1306
|
+
let variant;
|
|
1307
|
+
if (type.builtin)
|
|
1308
|
+
// `.type` is already a builtin: use a nicer message.
|
|
1309
|
+
variant = 'builtin';
|
|
1310
|
+
else if (effectiveTypeArt.builtin)
|
|
1311
|
+
// base type is a builtin, i.e. a scalar
|
|
1312
|
+
variant = 'type';
|
|
1313
|
+
else
|
|
1314
|
+
// effectiveType is not a builtin -> array or structured
|
|
1315
|
+
variant = 'non-scalar';
|
|
1316
|
+
|
|
1317
|
+
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1318
|
+
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
|
|
1319
|
+
});
|
|
1320
|
+
break; // Avoid spam: Only emit the first error.
|
|
1321
|
+
}
|
|
1322
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
|
|
1323
|
+
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1324
|
+
'#': 'incorrect-type',
|
|
1325
|
+
prop: param,
|
|
1326
|
+
code: artWithType[param].literal,
|
|
1327
|
+
names: typeParameters.expectedLiteralsFor[param],
|
|
1328
|
+
});
|
|
1329
|
+
break; // Avoid spam: Only emit the first error.
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1270
1333
|
}
|
|
1271
1334
|
|
|
1272
1335
|
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|