@sap/cds-compiler 2.10.4 → 2.12.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/model/csnUtils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { setProp } = require('../base/model');
|
|
4
3
|
const { csnRefs } = require('../model/csnRefs');
|
|
4
|
+
const { applyTransformations, applyTransformationsOnNonDictionary } = require('../transform/db/applyTransformations');
|
|
5
|
+
const { isBuiltinType } = require('../compiler/builtins.js')
|
|
5
6
|
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
|
|
6
7
|
const version = require('../../package.json').version;
|
|
7
8
|
|
|
@@ -11,8 +12,8 @@ const version = require('../../package.json').version;
|
|
|
11
12
|
* Generic Callback
|
|
12
13
|
*
|
|
13
14
|
* @callback genericCallback
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
15
|
+
* @param {any} art
|
|
16
|
+
* @param {string} name Artifact Name
|
|
16
17
|
* @param {string} prop Dictionary Property
|
|
17
18
|
* @param {CSN.Path} path Location
|
|
18
19
|
* @param {CSN.Artifact} [dictionary]
|
|
@@ -60,6 +61,7 @@ function getUtils(model) {
|
|
|
60
61
|
effectiveType,
|
|
61
62
|
get$combined,
|
|
62
63
|
getOrigin,
|
|
64
|
+
getQueryPrimarySource,
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
/**
|
|
@@ -175,10 +177,10 @@ function getUtils(model) {
|
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Return the name part of the artifact name - no namespace etc.
|
|
182
|
+
* @param {string|object} name Absolute name of the artifact
|
|
183
|
+
*/
|
|
182
184
|
function getBaseName(name) {
|
|
183
185
|
if (!name)
|
|
184
186
|
return name;
|
|
@@ -190,6 +192,27 @@ function getUtils(model) {
|
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Return the left-most, primary source of the given query.
|
|
197
|
+
* @param {*} query Definition's query object
|
|
198
|
+
*/
|
|
199
|
+
function getQueryPrimarySource(query) {
|
|
200
|
+
if (!query)
|
|
201
|
+
return undefined;
|
|
202
|
+
else if (query.SELECT) {
|
|
203
|
+
return getQueryPrimarySource(query.SELECT);
|
|
204
|
+
} else if (query.SET) {
|
|
205
|
+
return getQueryPrimarySource(query.SET);
|
|
206
|
+
} else if (query.from) {
|
|
207
|
+
return getQueryPrimarySource(query.from);
|
|
208
|
+
} else if (query.ref) {
|
|
209
|
+
return query;
|
|
210
|
+
} else if (query.args) {
|
|
211
|
+
return getQueryPrimarySource(query.args[0]);
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
193
216
|
|
|
194
217
|
/**
|
|
195
218
|
* Create an object to track visited objects identified by a unique string.
|
|
@@ -229,7 +252,7 @@ function getUtils(model) {
|
|
|
229
252
|
* Returns true if an artifact is a structured type
|
|
230
253
|
* or a typedef of a structured type.
|
|
231
254
|
*
|
|
232
|
-
* @param {
|
|
255
|
+
* @param {object} obj
|
|
233
256
|
*/
|
|
234
257
|
function isStructured(obj) {
|
|
235
258
|
return obj.elements ||
|
|
@@ -396,7 +419,7 @@ function getUtils(model) {
|
|
|
396
419
|
function getServiceName(artifactName) {
|
|
397
420
|
for(;;) {
|
|
398
421
|
let idx = artifactName.lastIndexOf('.');
|
|
399
|
-
if (idx
|
|
422
|
+
if (idx === -1) return null;
|
|
400
423
|
artifactName = artifactName.substring(0, idx);
|
|
401
424
|
let artifact = model.definitions[artifactName];
|
|
402
425
|
if (artifact && artifact.kind === 'service') {
|
|
@@ -457,8 +480,8 @@ function getUtils(model) {
|
|
|
457
480
|
return resultNode;
|
|
458
481
|
}
|
|
459
482
|
}
|
|
460
|
-
|
|
461
|
-
|
|
483
|
+
|
|
484
|
+
|
|
462
485
|
/**
|
|
463
486
|
* Resolve to the final type of a type, that means follow type chains, references to other types or
|
|
464
487
|
* elements a.s.o
|
|
@@ -497,12 +520,12 @@ function getUtils(model) {
|
|
|
497
520
|
* Composed types (structures, entities, views, ...) are returned as type objects, if not drilled down into
|
|
498
521
|
* the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
|
|
499
522
|
* no type (e.g. expr in a view without explicit type) returns 'null'
|
|
500
|
-
*
|
|
523
|
+
*
|
|
501
524
|
* @param {string|object} type Type - either string or ref
|
|
502
|
-
* @param {CSN.Path} path
|
|
525
|
+
* @param {CSN.Path} path
|
|
503
526
|
* @param {WeakMap} [resolved=new WeakMap()] WeakMap containing already resolved refs - if a ref is not cached, it will be resolved JIT
|
|
504
|
-
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
|
|
505
|
-
* @returns
|
|
527
|
+
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
|
|
528
|
+
* @returns
|
|
506
529
|
*/
|
|
507
530
|
function getFinalBaseType(type, path = [], resolved = new WeakMap(), cycleCheck = undefined) {
|
|
508
531
|
if (!type)
|
|
@@ -552,16 +575,6 @@ function getUtils(model) {
|
|
|
552
575
|
}
|
|
553
576
|
}
|
|
554
577
|
|
|
555
|
-
// Tell if a type is (directly) a builtin type
|
|
556
|
-
// Note that in CSN builtins are not in the definition of the model, so we can only check against their absolute names.
|
|
557
|
-
// Builtin types are "cds.<something>", i.e. they are directly in 'cds', but not for example
|
|
558
|
-
// in 'cds.foundation'. Also note, that a type might be a ref object, that refers to something else,
|
|
559
|
-
// so if you consider type chains don't forget first to resolve to the final type before
|
|
560
|
-
function isBuiltinType(type) {
|
|
561
|
-
return typeof(type) === 'string' && type.startsWith('cds.') && !type.startsWith('cds.foundation.')
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
578
|
/**
|
|
566
579
|
* Deeply clone the given CSN model and return it.
|
|
567
580
|
* In testMode (or with testSortCsn), definitions are sorted.
|
|
@@ -817,11 +830,25 @@ function forAllQueries(query, callback, path = []){
|
|
|
817
830
|
}
|
|
818
831
|
}
|
|
819
832
|
|
|
820
|
-
function forAllElements(artifact, artifactName, cb){
|
|
833
|
+
function forAllElements(artifact, artifactName, cb, includeActions = false){
|
|
821
834
|
if(artifact.elements) {
|
|
822
835
|
cb(artifact, artifact.elements, ['definitions', artifactName, 'elements']);
|
|
823
836
|
}
|
|
824
837
|
|
|
838
|
+
if(includeActions && artifact.actions) {
|
|
839
|
+
Object.entries(artifact.actions).forEach( ([actionName, action]) => {
|
|
840
|
+
const path = ['definitions', artifactName, 'actions', actionName];
|
|
841
|
+
if(action.params) {
|
|
842
|
+
Object.entries(action.params).forEach( ([paramName, param]) => {
|
|
843
|
+
if(param.elements)
|
|
844
|
+
cb(param, param.elements, path.concat(['params', paramName, 'elements']));
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
if(action.returns && action.returns.elements)
|
|
848
|
+
cb(action.returns, action.returns.elements,path.concat(['returns', 'elements']));
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
825
852
|
if(artifact.query) {
|
|
826
853
|
forAllQueries(artifact.query, (q, p) => {
|
|
827
854
|
const s = q.SELECT;
|
|
@@ -854,11 +881,14 @@ function forAllElements(artifact, artifactName, cb){
|
|
|
854
881
|
* @param {CSN.Artifact} artifact
|
|
855
882
|
* @param {string} annotationName Name of the annotation (including the at-sign)
|
|
856
883
|
* @param {any} expected
|
|
884
|
+
* @param {boolean} caseInsensitive
|
|
857
885
|
* @returns {boolean}
|
|
858
886
|
*/
|
|
859
|
-
function hasAnnotationValue(artifact, annotationName, expected = true) {
|
|
887
|
+
function hasAnnotationValue(artifact, annotationName, expected = true, caseInsensitive = false) {
|
|
860
888
|
if(expected === false)
|
|
861
889
|
return artifact[annotationName] === expected || artifact[annotationName] === null;
|
|
890
|
+
else if (typeof artifact[annotationName] === 'string' && caseInsensitive === true)
|
|
891
|
+
return artifact[annotationName].toLowerCase() === expected.toLowerCase();
|
|
862
892
|
else
|
|
863
893
|
return artifact[annotationName] === expected;
|
|
864
894
|
}
|
|
@@ -871,7 +901,7 @@ function hasAnnotationValue(artifact, annotationName, expected = true) {
|
|
|
871
901
|
* function accepts EDM internal and external options
|
|
872
902
|
*
|
|
873
903
|
* @param {CSN.Element} elementCsn
|
|
874
|
-
* @param {
|
|
904
|
+
* @param {ODataOptions} options EDM specific options
|
|
875
905
|
*/
|
|
876
906
|
function isEdmPropertyRendered(elementCsn, options) {
|
|
877
907
|
if(options.toOdata)
|
|
@@ -921,10 +951,10 @@ function getArtifactDatabaseNameOf(artifactName, namingConvention, csn) {
|
|
|
921
951
|
throw new Error('Unknown naming convention: ' + namingConvention);
|
|
922
952
|
}
|
|
923
953
|
else {
|
|
924
|
-
const namespace = csn;
|
|
925
954
|
console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
|
|
926
955
|
if (namingConvention === 'hdbcds') {
|
|
927
|
-
if (
|
|
956
|
+
if (csn) {
|
|
957
|
+
const namespace = String(csn);
|
|
928
958
|
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
|
|
929
959
|
}
|
|
930
960
|
return artifactName;
|
|
@@ -1048,112 +1078,6 @@ function getElementDatabaseNameOf(elemName, namingConvention) {
|
|
|
1048
1078
|
}
|
|
1049
1079
|
}
|
|
1050
1080
|
|
|
1051
|
-
|
|
1052
|
-
/**
|
|
1053
|
-
* Loop through the model, applying the custom transformations on the node's matching.
|
|
1054
|
-
*
|
|
1055
|
-
* Each transformer gets:
|
|
1056
|
-
* - the parent having the property
|
|
1057
|
-
* - the name of the property
|
|
1058
|
-
* - the value of the property
|
|
1059
|
-
* - the path to the property
|
|
1060
|
-
*
|
|
1061
|
-
* @param {object} csn CSN to enrich in-place
|
|
1062
|
-
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
1063
|
-
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
1064
|
-
* @param {Boolean} [skipIgnore=true] Wether to skip _ignore elements or not
|
|
1065
|
-
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
1066
|
-
* @returns {object} CSN with transformations applied
|
|
1067
|
-
*/
|
|
1068
|
-
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true, options = {} ) {
|
|
1069
|
-
const transformers = {
|
|
1070
|
-
elements: dictionary,
|
|
1071
|
-
definitions: dictionary,
|
|
1072
|
-
actions: dictionary,
|
|
1073
|
-
params: dictionary,
|
|
1074
|
-
enum: dictionary,
|
|
1075
|
-
mixin: dictionary,
|
|
1076
|
-
ref: pathRef,
|
|
1077
|
-
//type: simpleRef,
|
|
1078
|
-
//target: simpleRef,
|
|
1079
|
-
//includes: simpleRef,
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
const csnPath = [];
|
|
1083
|
-
if (csn.definitions)
|
|
1084
|
-
definitions( csn, 'definitions', csn.definitions );
|
|
1085
|
-
return csn;
|
|
1086
|
-
|
|
1087
|
-
function standard( parent, prop, node ) {
|
|
1088
|
-
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')) || (skipIgnore && node._ignore))
|
|
1089
|
-
return;
|
|
1090
|
-
|
|
1091
|
-
csnPath.push( prop );
|
|
1092
|
-
|
|
1093
|
-
if (Array.isArray(node)) {
|
|
1094
|
-
node.forEach( (n, i) => standard( node, i, n ) );
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
else {
|
|
1098
|
-
for (let name of Object.getOwnPropertyNames( node )) {
|
|
1099
|
-
const trans = transformers[name] || standard;
|
|
1100
|
-
if(customTransformers[name])
|
|
1101
|
-
customTransformers[name](node, name, node[name], csnPath, parent, prop);
|
|
1102
|
-
|
|
1103
|
-
trans( node, name, node[name], csnPath );
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
csnPath.pop();
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
function dictionary( node, prop, dict ) {
|
|
1110
|
-
// Allow skipping dicts like actions in forHanaNew
|
|
1111
|
-
if(options.skipDict && options.skipDict[prop])
|
|
1112
|
-
return;
|
|
1113
|
-
csnPath.push( prop );
|
|
1114
|
-
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
1115
|
-
standard( dict, name, dict[name] );
|
|
1116
|
-
}
|
|
1117
|
-
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
|
|
1118
|
-
setProp(node, '$' + prop, dict);
|
|
1119
|
-
csnPath.pop();
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
function definitions( node, prop, dict ) {
|
|
1123
|
-
csnPath.push( prop );
|
|
1124
|
-
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
1125
|
-
const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
|
|
1126
|
-
if(!skip) {
|
|
1127
|
-
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
1128
|
-
standard( dict, name, dict[name] );
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
|
|
1132
|
-
setProp(node, '$' + prop, dict);
|
|
1133
|
-
csnPath.pop();
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
//Keep looping through the pathRef
|
|
1137
|
-
function pathRef( node, prop, path ) {
|
|
1138
|
-
csnPath.push( prop );
|
|
1139
|
-
path.forEach( function step( s, i ) {
|
|
1140
|
-
if (s && typeof s === 'object') {
|
|
1141
|
-
csnPath.push( i );
|
|
1142
|
-
if(options.drillRef) {
|
|
1143
|
-
standard(path, i, s);
|
|
1144
|
-
} else {
|
|
1145
|
-
if (s.args)
|
|
1146
|
-
standard( s, 'args', s.args );
|
|
1147
|
-
if (s.where)
|
|
1148
|
-
standard( s, 'where', s.where );
|
|
1149
|
-
}
|
|
1150
|
-
csnPath.pop();
|
|
1151
|
-
}
|
|
1152
|
-
} );
|
|
1153
|
-
csnPath.pop();
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
1081
|
const _dependencies = Symbol('_dependencies');
|
|
1158
1082
|
const _dependents = Symbol('_dependents');
|
|
1159
1083
|
|
|
@@ -1333,38 +1257,6 @@ function mergeOptions(...optionsObjects) {
|
|
|
1333
1257
|
}
|
|
1334
1258
|
}
|
|
1335
1259
|
|
|
1336
|
-
// Return the name of the top-level artifact surrounding the artifact 'name'
|
|
1337
|
-
// in 'model'.
|
|
1338
|
-
// We define "top-level artifact" to be an artifact that has either no parent or only
|
|
1339
|
-
// ancestors of kind 'namespace'. Note that it is possible for a non-top-level artifact
|
|
1340
|
-
// to have a namespace as parent and e.g. a context as grandparent (weird but true).
|
|
1341
|
-
// Will return the artifact 'name' if it is a top-level artifact itself, and 'undefined'
|
|
1342
|
-
// if there is no artifact surrounding 'name' in the model
|
|
1343
|
-
// TODO: to be checked by author: still intended behaviour with 'cds' prefix?
|
|
1344
|
-
// TODO: Can this be replaced by getRootArtifactName? Or maybe not rely on namespace-hacking...
|
|
1345
|
-
// FIXME: This only works with namespace-hacking, i.e. adding them as artifacts...
|
|
1346
|
-
function getTopLevelArtifactNameOf(name, model) {
|
|
1347
|
-
let dotIdx = name.indexOf('.');
|
|
1348
|
-
if (dotIdx == -1) {
|
|
1349
|
-
// No '.' in the name, i.e. no parent - this is a top-level artifact (if it exists)
|
|
1350
|
-
return model.definitions[name] ? name : undefined;
|
|
1351
|
-
}
|
|
1352
|
-
// If the first name part is not in the model, there is nothing to find
|
|
1353
|
-
if (!model.definitions[name.substring(0, dotIdx)]) {
|
|
1354
|
-
return undefined;
|
|
1355
|
-
}
|
|
1356
|
-
// Skip forward through '.'s until finding a non-namespace
|
|
1357
|
-
while (dotIdx != -1 && (!model.definitions[name.substring(0, dotIdx)] || model.definitions[name.substring(0, dotIdx)].kind === 'namespace')) {
|
|
1358
|
-
dotIdx = name.indexOf('.', dotIdx + 1);
|
|
1359
|
-
}
|
|
1360
|
-
if (dotIdx == -1) {
|
|
1361
|
-
// This is a top-level artifact
|
|
1362
|
-
return name;
|
|
1363
|
-
}
|
|
1364
|
-
// The skipped part of 'name' is the top-level artifact name
|
|
1365
|
-
return name.substring(0, dotIdx);
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
1260
|
/**
|
|
1369
1261
|
* If the artifact with the name given is part of a context (or multiple), return the top-most context.
|
|
1370
1262
|
* Else, return the artifact itself. Namespaces are not of concern here.
|
|
@@ -1532,7 +1424,7 @@ function sortCsnDefinitionsForTests(csn, options) {
|
|
|
1532
1424
|
if (!options.testMode)
|
|
1533
1425
|
return;
|
|
1534
1426
|
const sorted = Object.create(null);
|
|
1535
|
-
Object.keys(csn.definitions).sort().forEach((name) => {
|
|
1427
|
+
Object.keys(csn.definitions || {}).sort().forEach((name) => {
|
|
1536
1428
|
sorted[name] = csn.definitions[name];
|
|
1537
1429
|
});
|
|
1538
1430
|
csn.definitions = sorted;
|
|
@@ -1542,7 +1434,7 @@ function sortCsnDefinitionsForTests(csn, options) {
|
|
|
1542
1434
|
* Return an array of non-abstract service names contained in CSN
|
|
1543
1435
|
*
|
|
1544
1436
|
* @param {CSN.Model} csn
|
|
1545
|
-
* @returns {
|
|
1437
|
+
* @returns {string[]}
|
|
1546
1438
|
*/
|
|
1547
1439
|
function getServiceNames(csn) {
|
|
1548
1440
|
let result = [];
|
|
@@ -1556,8 +1448,8 @@ function getServiceNames(csn) {
|
|
|
1556
1448
|
|
|
1557
1449
|
/**
|
|
1558
1450
|
* Check wether the artifact is @cds.persistence.skip
|
|
1559
|
-
*
|
|
1560
|
-
* @param {CSN.Artifact} artifact
|
|
1451
|
+
*
|
|
1452
|
+
* @param {CSN.Artifact} artifact
|
|
1561
1453
|
* @returns {Boolean}
|
|
1562
1454
|
*/
|
|
1563
1455
|
function isSkipped(artifact) {
|
|
@@ -1566,21 +1458,49 @@ function isSkipped(artifact) {
|
|
|
1566
1458
|
|
|
1567
1459
|
/**
|
|
1568
1460
|
* Walk path in the CSN and return the result.
|
|
1569
|
-
*
|
|
1570
|
-
* @param {CSN.Model} csn
|
|
1571
|
-
* @param {CSN.Path} path
|
|
1461
|
+
*
|
|
1462
|
+
* @param {CSN.Model} csn
|
|
1463
|
+
* @param {CSN.Path} path
|
|
1572
1464
|
* @returns {object} Whatever is at the end of path
|
|
1573
1465
|
*/
|
|
1574
1466
|
function walkCsnPath(csn, path) {
|
|
1575
1467
|
/** @type {object} */
|
|
1576
1468
|
let obj = csn;
|
|
1577
|
-
for(
|
|
1578
|
-
obj = obj[
|
|
1469
|
+
for(const segment of path){
|
|
1470
|
+
obj = obj[segment];
|
|
1579
1471
|
}
|
|
1580
1472
|
|
|
1581
1473
|
return obj;
|
|
1582
1474
|
}
|
|
1583
1475
|
|
|
1476
|
+
/**
|
|
1477
|
+
* If provided, get the replacement string for the given magic variable ref.
|
|
1478
|
+
* No validation is done that the ref is actually magic!
|
|
1479
|
+
*
|
|
1480
|
+
* @param {array} ref
|
|
1481
|
+
* @param {CSN.Options} options
|
|
1482
|
+
* @returns {string|null}
|
|
1483
|
+
*/
|
|
1484
|
+
function getVariableReplacement(ref, options) {
|
|
1485
|
+
if(options && options.variableReplacements) {
|
|
1486
|
+
let replacement = options.variableReplacements;
|
|
1487
|
+
for(const segment of ref) {
|
|
1488
|
+
replacement = replacement[segment];
|
|
1489
|
+
if(replacement === undefined)
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
if(replacement === undefined)
|
|
1494
|
+
return null; // no valid replacement found
|
|
1495
|
+
else if(typeof replacement === 'string')
|
|
1496
|
+
return replacement; // valid replacement
|
|
1497
|
+
else
|
|
1498
|
+
return null; // $user.foo, but we only have configured $user.foo.bar -> error
|
|
1499
|
+
} else {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1584
1504
|
module.exports = {
|
|
1585
1505
|
getUtils,
|
|
1586
1506
|
cloneCsn,
|
|
@@ -1601,12 +1521,12 @@ module.exports = {
|
|
|
1601
1521
|
getUnderscoredName,
|
|
1602
1522
|
getElementDatabaseNameOf,
|
|
1603
1523
|
applyTransformations,
|
|
1524
|
+
applyTransformationsOnNonDictionary,
|
|
1604
1525
|
setDependencies,
|
|
1605
1526
|
isPersistedOnDatabase,
|
|
1606
1527
|
generatedByCompilerVersion,
|
|
1607
1528
|
getNormalizedQuery,
|
|
1608
1529
|
mergeOptions,
|
|
1609
|
-
getTopLevelArtifactNameOf,
|
|
1610
1530
|
getRootArtifactName,
|
|
1611
1531
|
getLastPartOfRef,
|
|
1612
1532
|
getParentNamesOf,
|
|
@@ -1621,4 +1541,5 @@ module.exports = {
|
|
|
1621
1541
|
getServiceNames,
|
|
1622
1542
|
isSkipped,
|
|
1623
1543
|
walkCsnPath,
|
|
1544
|
+
getVariableReplacement
|
|
1624
1545
|
};
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -7,23 +7,35 @@
|
|
|
7
7
|
|
|
8
8
|
// * `File.cds:3:5` if the original CSN has a non-enumerable `$location` property
|
|
9
9
|
// with value `{file: "File.cds", line: 3, col: 5}`.
|
|
10
|
-
// * `File.cds:3:5
|
|
10
|
+
// * `File.cds:3:5^` if the original CSN has _no_ `$location` property, for an
|
|
11
11
|
// inferred member of a main artifact or member with `$location: `File.cds:3:5`;
|
|
12
|
-
// the number of
|
|
12
|
+
// the number of `^`s in the suffix is the member depth.
|
|
13
13
|
|
|
14
14
|
// Other enumerable properties in the JSON for non-enumerable properties in the
|
|
15
15
|
// original CSN:
|
|
16
16
|
|
|
17
|
-
// * `$
|
|
18
|
-
//
|
|
17
|
+
// * `$parens`: the number of parentheses provided by the user around an expression
|
|
18
|
+
// or query if the number is different to the usual (mostly 0, sometimes 1).
|
|
19
|
+
// * `$elements` (in client-style CSN only) for a non-enumerable `elements` property
|
|
20
|
+
// for sub queries.
|
|
19
21
|
|
|
20
22
|
// The following properties in the JSON represent the result of the CSN API
|
|
21
23
|
// functions:
|
|
22
24
|
|
|
23
|
-
// * `_type`, `_includes` and `_targets` have as values the `$
|
|
25
|
+
// * `_type`, `_includes` and `_targets` have as values the `$location`s of the
|
|
24
26
|
// referred artifacts which are returned by function `artifactRef`.
|
|
25
|
-
// * `_links
|
|
26
|
-
//
|
|
27
|
+
// * `_links` and `_art` as sibling properties of `ref` have as values the
|
|
28
|
+
// `$locations` of the artifacts/members returned by function `inspectRef` (`_art`
|
|
29
|
+
// for ref in `from` only, where it is different to the last item of `_links`).
|
|
30
|
+
// * `_scope` and `_env` as sibling properties of `ref` have (string) values,
|
|
31
|
+
// returned by function `inspectRef`, giving add/ info about the “ref base”.
|
|
32
|
+
// * `_origin` (in Universal CSN only) has as value the `$location` of the
|
|
33
|
+
// prototype returned by function getOrigin().
|
|
34
|
+
// * `_test.inspect.csnpath` as sibling property of `ref` has an object value
|
|
35
|
+
// with properties `_links` and `_scope` of a further `inspectRef` call;
|
|
36
|
+
// it is only called, with `[‹art›, ...‹csnpath›]` as argument, if `‹art›`,
|
|
37
|
+
// which is the referred artifact returned by the first `inspectRef` call,
|
|
38
|
+
// has an annotation `@$test.inspect.csnpath` with array value `‹csnpath›`.
|
|
27
39
|
|
|
28
40
|
'use strict';
|
|
29
41
|
|
|
@@ -32,13 +44,14 @@ const { locationString } = require('../base/location');
|
|
|
32
44
|
|
|
33
45
|
function enrichCsn( csn, options = {} ) {
|
|
34
46
|
const transformers = {
|
|
35
|
-
// $env: reveal,
|
|
36
47
|
elements: dictionary,
|
|
37
48
|
definitions: dictionary,
|
|
38
49
|
actions: dictionary,
|
|
39
50
|
params: dictionary,
|
|
40
51
|
enum: dictionary,
|
|
41
52
|
mixin: dictionary,
|
|
53
|
+
returns: definition,
|
|
54
|
+
items: definition,
|
|
42
55
|
ref: pathRef,
|
|
43
56
|
type: simpleRef,
|
|
44
57
|
targetAspect: simpleRef,
|
|
@@ -81,10 +94,20 @@ function enrichCsn( csn, options = {} ) {
|
|
|
81
94
|
csnPath.pop();
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
function definition( parent, prop, obj ) {
|
|
98
|
+
// call getOrigin() before standard() to set implicit protos inside standard():
|
|
99
|
+
const origin = handleError( err => err ? err.toString() : getOrigin( obj ) );
|
|
100
|
+
standard( parent, prop, obj );
|
|
101
|
+
if (obj.$origin === undefined && !obj.type && origin != null)
|
|
102
|
+
obj._origin = refLocation( origin );
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
function dictionary( parent, prop, dict ) {
|
|
106
|
+
if (!dict) // value null for inheritance interruption
|
|
107
|
+
return;
|
|
85
108
|
csnPath.push( prop );
|
|
86
109
|
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
87
|
-
|
|
110
|
+
definition( dict, name, dict[name] );
|
|
88
111
|
}
|
|
89
112
|
if (!Object.prototype.propertyIsEnumerable.call( parent, prop ))
|
|
90
113
|
parent['$'+prop] = dict;
|
|
@@ -92,10 +115,10 @@ function enrichCsn( csn, options = {} ) {
|
|
|
92
115
|
}
|
|
93
116
|
|
|
94
117
|
function refLocation( art ) {
|
|
95
|
-
if (art)
|
|
118
|
+
if (art && typeof art === 'object')
|
|
96
119
|
return art.$location || '<no location>';
|
|
97
120
|
if (!options.testMode)
|
|
98
|
-
return '<illegal link>';
|
|
121
|
+
return art || '<illegal link>';
|
|
99
122
|
throw new Error( 'Undefined reference' );
|
|
100
123
|
}
|
|
101
124
|
|
|
@@ -122,33 +145,21 @@ function enrichCsn( csn, options = {} ) {
|
|
|
122
145
|
}
|
|
123
146
|
|
|
124
147
|
function $origin( parent, prop, ref ) {
|
|
125
|
-
|
|
126
|
-
if (
|
|
148
|
+
handleError( err => {
|
|
149
|
+
if (err)
|
|
150
|
+
parent._origin = err.toString();
|
|
151
|
+
else if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […] / "short-ref"
|
|
127
152
|
parent._origin = refLocation( getOrigin( parent ) );
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (Array.isArray( ref )) // $origin: […], not $origin: {…}
|
|
132
|
-
parent._origin = refLocation( getOrigin( parent ) );
|
|
133
|
-
} catch (e) {
|
|
134
|
-
parent._origin = e.toString();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
153
|
+
else if ( ref ) // $origin: {…}
|
|
154
|
+
standard( parent, prop, ref );
|
|
155
|
+
} );
|
|
137
156
|
}
|
|
138
157
|
|
|
139
|
-
function pathRef( parent, prop, path ) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
return inspectRef( csnPath );
|
|
146
|
-
}
|
|
147
|
-
catch (e) {
|
|
148
|
-
return { scope: e.toString() };
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
} )();
|
|
158
|
+
function pathRef( parent, prop, path, inspectionPath = csnPath ) {
|
|
159
|
+
const inspection = handleError( (err) => {
|
|
160
|
+
return (err) ? { scope: err.toString() } : inspectRef( inspectionPath );
|
|
161
|
+
});
|
|
162
|
+
const { links, art, scope, $env } = inspection;
|
|
152
163
|
if (links)
|
|
153
164
|
parent._links = links.map( l => refLocation( l.art ) );
|
|
154
165
|
if (links && links[links.length-1].art !== art)
|
|
@@ -157,6 +168,15 @@ function enrichCsn( csn, options = {} ) {
|
|
|
157
168
|
if ($env)
|
|
158
169
|
parent._env = $env;
|
|
159
170
|
|
|
171
|
+
if (!prop) // recursive call for @$test.inspect.csnpath
|
|
172
|
+
return;
|
|
173
|
+
const testPath = art && art['@$test.inspect.csnpath'];
|
|
174
|
+
if (testPath && parent.ref) {
|
|
175
|
+
const further = {};
|
|
176
|
+
pathRef( further, null, null, [ inspection, ...testPath ] );
|
|
177
|
+
parent['_test.inspect.csnpath'] = further;
|
|
178
|
+
}
|
|
179
|
+
|
|
160
180
|
csnPath.push( prop );
|
|
161
181
|
path.forEach( function step( s, i ) {
|
|
162
182
|
if (s && typeof s === 'object') {
|
|
@@ -171,10 +191,20 @@ function enrichCsn( csn, options = {} ) {
|
|
|
171
191
|
csnPath.pop();
|
|
172
192
|
}
|
|
173
193
|
|
|
174
|
-
function
|
|
194
|
+
function handleError( callback ) {
|
|
195
|
+
if (options.testMode)
|
|
196
|
+
return callback();
|
|
197
|
+
try {
|
|
198
|
+
return callback();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return callback( err );
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function _cache_debug( obj, subCache ) {
|
|
175
205
|
if (options.enrichCsn !== 'DEBUG')
|
|
176
206
|
return;
|
|
177
|
-
const cache = __getCache_forEnrichCsnDebugging( obj );
|
|
207
|
+
const cache = subCache || __getCache_forEnrichCsnDebugging( obj );
|
|
178
208
|
if (!cache)
|
|
179
209
|
return;
|
|
180
210
|
if (cache.$$objectNumber > 0) {
|
|
@@ -197,8 +227,20 @@ function enrichCsn( csn, options = {} ) {
|
|
|
197
227
|
}
|
|
198
228
|
else if (name[0] !== '$' || !Object.getPrototypeOf( val )) {
|
|
199
229
|
// ‹name›: dictionary of CSN nodes,
|
|
200
|
-
// ‹$name›: dictionary of cache values if no prototype
|
|
201
|
-
|
|
230
|
+
// ‹$name›: dictionary of cache values if no prototype
|
|
231
|
+
if (name !== '$aliases') {
|
|
232
|
+
obj.$$cacheObject[name] = Object.keys( val ); // TODO: or dict?
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const sub = Object.create(null);
|
|
236
|
+
for (const n in val) {
|
|
237
|
+
const alias = val[n];
|
|
238
|
+
const c = {};
|
|
239
|
+
_cache_debug( c, alias );
|
|
240
|
+
sub[n] = c.$$cacheObject;
|
|
241
|
+
}
|
|
242
|
+
obj.$$cacheObject[name] = sub;
|
|
243
|
+
}
|
|
202
244
|
}
|
|
203
245
|
else if (Array.isArray( val )) {
|
|
204
246
|
obj.$$cacheObject[name] = val.map( item => {
|
|
@@ -231,12 +273,11 @@ function setLocations( node, prop, loc ) {
|
|
|
231
273
|
return;
|
|
232
274
|
const isMember = artifactProperties.includes( prop );
|
|
233
275
|
if (!isMember && node.$location) {
|
|
234
|
-
|
|
235
|
-
reveal( node, '$location',
|
|
236
|
-
loc = value + '-';
|
|
276
|
+
loc = locationString( node.$location, true );
|
|
277
|
+
reveal( node, '$location', loc );
|
|
237
278
|
}
|
|
238
279
|
else if (prop === true) {
|
|
239
|
-
loc += '
|
|
280
|
+
loc += '^';
|
|
240
281
|
node.$location = loc;
|
|
241
282
|
}
|
|
242
283
|
if (Array.isArray( node )) {
|