@sap/cds-compiler 3.4.4 → 3.5.2
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 +58 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +58 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +124 -28
- package/lib/base/messages.js +247 -179
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +1 -1
- package/lib/compiler/assert-consistency.js +0 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +73 -15
- package/lib/compiler/define.js +3 -7
- package/lib/compiler/extend.js +212 -32
- package/lib/compiler/finalize-parse-cdl.js +7 -2
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +2 -5
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/shared.js +23 -12
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +6 -0
- package/lib/edm/annotations/genericTranslation.js +553 -319
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +88 -75
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmPreprocessor.js +106 -76
- package/lib/edm/edmUtils.js +41 -2
- package/lib/gen/Dictionary.json +34 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +66 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14360 -14146
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +82 -40
- package/lib/json/to-csn.js +82 -157
- package/lib/language/.eslintrc.json +1 -4
- package/lib/language/genericAntlrParser.js +59 -38
- package/lib/language/language.g4 +1508 -1490
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.js +3 -3
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/utils/filter.js +4 -3
- package/lib/optionProcessor.js +5 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +225 -159
- package/lib/render/toHdbcds.js +63 -63
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +55 -65
- package/lib/render/utils/common.js +20 -37
- package/lib/render/utils/delta.js +3 -3
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +12 -40
- package/lib/transform/forRelationalDB.js +17 -7
- package/lib/transform/localized.js +2 -2
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +106 -62
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -7
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { isEdmPropertyRendered, isBuiltinType } = require('../../model/csnUtils');
|
|
3
3
|
const edmUtils = require('../edmUtils.js');
|
|
4
|
-
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
5
4
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
5
|
+
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
6
6
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
7
|
-
const { forEach } = require(
|
|
8
|
-
|
|
7
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
8
|
+
const { isBetaEnabled } = require('../../base/model.js');
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
|
|
@@ -16,19 +16,21 @@ const { forEach } = require("../../utils/objectUtils");
|
|
|
16
16
|
JSON (published)
|
|
17
17
|
Measures (published)
|
|
18
18
|
Repeatability (published)
|
|
19
|
-
Temporal (
|
|
19
|
+
Temporal (published)
|
|
20
20
|
Validation (published)
|
|
21
21
|
|
|
22
22
|
SAP: https://github.com/SAP/odata-vocabularies/tree/master/vocabularies
|
|
23
23
|
Analytics (published)
|
|
24
24
|
CodeList (published)
|
|
25
|
-
Common (
|
|
25
|
+
Common (published)
|
|
26
26
|
Communication (published)
|
|
27
27
|
DataIntegration (published)
|
|
28
28
|
Graph (published, experimental)
|
|
29
29
|
Hierarchy (published, experimental)
|
|
30
30
|
HTML5 (published, experimental)
|
|
31
31
|
ODM (published, experimental)
|
|
32
|
+
Offline (experimental)
|
|
33
|
+
PDF (published)
|
|
32
34
|
PersonalData (published)
|
|
33
35
|
Session (published)
|
|
34
36
|
UI (published)
|
|
@@ -110,6 +112,16 @@ const vocabularyDefinitions = {
|
|
|
110
112
|
'inc': { Alias: 'ODM', Namespace: 'com.sap.vocabularies.ODM.v1' },
|
|
111
113
|
'int': { filename: 'ODM.xml' }
|
|
112
114
|
},
|
|
115
|
+
'Offline': {
|
|
116
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Offline.xml' },
|
|
117
|
+
'inc': { Alias: 'Offline', Namespace: 'com.sap.vocabularies.Offline.v1' },
|
|
118
|
+
'int': { filename: 'Offline.xml' }
|
|
119
|
+
},
|
|
120
|
+
'PDF': {
|
|
121
|
+
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PDF.xml' },
|
|
122
|
+
'inc': { Alias: 'PDF', Namespace: 'com.sap.vocabularies.PDF.v1' },
|
|
123
|
+
'int': { filename: 'PDF.xml' }
|
|
124
|
+
},
|
|
113
125
|
'PersonalData': {
|
|
114
126
|
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/PersonalData.xml' },
|
|
115
127
|
'inc': { Alias: 'PersonalData', Namespace: 'com.sap.vocabularies.PersonalData.v1' },
|
|
@@ -139,7 +151,6 @@ const vocabularyDefinitions = {
|
|
|
139
151
|
|
|
140
152
|
const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
141
153
|
|
|
142
|
-
|
|
143
154
|
/**************************************************************************************************
|
|
144
155
|
* csn2annotationEdm
|
|
145
156
|
*
|
|
@@ -147,8 +158,8 @@ const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
|
147
158
|
* v - array with two boolean entries, first is for v2, second is for v4
|
|
148
159
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
149
160
|
*/
|
|
150
|
-
function csn2annotationEdm(
|
|
151
|
-
|
|
161
|
+
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefined, options=undefined, messageFunctions=undefined) {
|
|
162
|
+
|
|
152
163
|
if(!Edm)
|
|
153
164
|
throw new Error('Please debug me: csn2annotationsEdm must be invoked with Edm');
|
|
154
165
|
if(!options)
|
|
@@ -158,13 +169,30 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
158
169
|
// global variable where we store all the generated annotations
|
|
159
170
|
const g_annosArray = [];
|
|
160
171
|
|
|
161
|
-
const {
|
|
172
|
+
const { message } = messageFunctions;
|
|
173
|
+
|
|
174
|
+
const [ adhocDictionary, allKnownVocabularies ] = createAdhocDictionary();
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
allKnownVocabularies is the union of knownVocabularies & adhoc $mySchemaName annotation definition
|
|
178
|
+
this allows to identify namespace prefixes of arbitrary length.
|
|
162
179
|
|
|
163
|
-
|
|
180
|
+
If in the future, the full Oasis vocabulary names shall be made usable for assignments
|
|
181
|
+
eg. @com.sap.vocabularies.Common.v1.Label, all that needs to be done is to x-link the
|
|
182
|
+
vocabulary dictionary whith the full ns as key:
|
|
183
|
+
vocabularyDefinitions['@com.sap.vocabularies.Common.v1'] = vocabularyDefinitions['Common']
|
|
184
|
+
*/
|
|
185
|
+
allKnownVocabularies.push(...knownVocabularies);
|
|
186
|
+
allKnownVocabularies.sort((a,b) => b.length-a.length);
|
|
187
|
+
const whatsMyTermNamespace = function(anno) {
|
|
188
|
+
return allKnownVocabularies.reduce((rc, ns) => !rc && anno && anno.startsWith('@' + ns + '.') ? ns : rc, undefined);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Static dynamic expression dictionary, loaded with Edm creators
|
|
164
192
|
const [ dynamicExpressions, dynamicExpressionNames ] = initEdmJson();
|
|
165
193
|
|
|
166
194
|
// annotation preprocessing
|
|
167
|
-
preprocessAnnotations.preprocessAnnotations(
|
|
195
|
+
preprocessAnnotations.preprocessAnnotations(reqDefs, serviceName, options);
|
|
168
196
|
|
|
169
197
|
// we take note of which vocabularies are actually used in a service in order to avoid
|
|
170
198
|
// producing useless references; reset everything to "unused"
|
|
@@ -178,30 +206,35 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
178
206
|
|
|
179
207
|
// provide functions for dictionary lookup
|
|
180
208
|
// use closure to avoid making "dict" and "experimental" global variables
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
209
|
+
const { getDictTerm, getDictType } = function(){
|
|
210
|
+
const dict = options.dictReplacement || oDataDictionary; // tests can set different dictionary via options
|
|
211
|
+
const experimental = {}; // take note of all experimental annos that have been used
|
|
212
|
+
const deprecated = {}; // take note of all deprecated annos that have been used
|
|
185
213
|
|
|
186
214
|
return {
|
|
187
215
|
// called to look-up a term in the dictionary
|
|
188
216
|
// in addition: - note usage of the respective vocabulary
|
|
189
217
|
// - issue a warning if the term is flagged as "experimental"
|
|
190
|
-
getDictTerm: function(termName,
|
|
191
|
-
const dictTerm = dict.terms[termName]
|
|
218
|
+
getDictTerm: function(termName, msg) {
|
|
219
|
+
const dictTerm = (dict.terms[termName] ||
|
|
220
|
+
adhocDictionary.terms[serviceName + '.' + termName] ||
|
|
221
|
+
adhocDictionary.terms[termName]);
|
|
192
222
|
// register vocabulary usage if possible
|
|
193
223
|
const vocName = termName.slice(0, termName.indexOf('.'));
|
|
194
224
|
if(vocabularyDefinitions[vocName])
|
|
195
225
|
vocabularyDefinitions[vocName].used = true;
|
|
196
|
-
|
|
226
|
+
else if(dictTerm?.$myServiceRoot &&
|
|
227
|
+
adhocDictionary.xrefs[dictTerm?.$myServiceRoot])
|
|
228
|
+
adhocDictionary.xrefs[dictTerm.$myServiceRoot].used = true;
|
|
197
229
|
if (dictTerm) {
|
|
198
|
-
// issue
|
|
230
|
+
// issue message for usage of experimental Terms, but only once per Term
|
|
199
231
|
if (dictTerm['$experimental'] && !experimental[termName]) {
|
|
200
|
-
message(
|
|
232
|
+
message('odata-anno-dict', msg.location, { anno: msg.anno(), '#': 'experimental' });
|
|
201
233
|
experimental[termName] = true;
|
|
202
234
|
}
|
|
203
235
|
if (dictTerm['$deprecated'] && !deprecated[termName]) {
|
|
204
|
-
message(
|
|
236
|
+
message('odata-anno-def', msg.location,
|
|
237
|
+
{ anno: msg.anno(), depr: dictTerm['$deprecationText'], '#': 'deprecated' });
|
|
205
238
|
deprecated[termName] = true;
|
|
206
239
|
}
|
|
207
240
|
}
|
|
@@ -210,10 +243,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
210
243
|
// called to look-up a type in the dictionary
|
|
211
244
|
// in addition, note usage of the respective vocabulary
|
|
212
245
|
getDictType: function (typeName) {
|
|
213
|
-
let dictType = dict.types[typeName]
|
|
246
|
+
let dictType = (dict.types[typeName] ||
|
|
247
|
+
adhocDictionary.types[serviceName + '.' + typeName] ||
|
|
248
|
+
adhocDictionary.types[typeName]);
|
|
214
249
|
if (dictType) {
|
|
215
250
|
// register usage of vocabulary
|
|
216
|
-
vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))]
|
|
251
|
+
const voc = vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))];
|
|
252
|
+
if(voc)
|
|
253
|
+
voc.used = true;
|
|
217
254
|
}
|
|
218
255
|
return dictType;
|
|
219
256
|
}
|
|
@@ -227,18 +264,20 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
227
264
|
// Note: only works for single service
|
|
228
265
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
229
266
|
// looks like <service name, can contain dots>.<id>
|
|
230
|
-
forEachDefinition(
|
|
267
|
+
forEachDefinition(reqDefs, (object, objName) => {
|
|
231
268
|
if (objName === serviceName || objName.startsWith(serviceName + '.')) {
|
|
269
|
+
|
|
270
|
+
const location = [ 'definitions', objName ];
|
|
232
271
|
if (object.kind === 'action' || object.kind === 'function') {
|
|
233
|
-
handleAction(objName, object, null);
|
|
272
|
+
handleAction(objName, object, null, location);
|
|
234
273
|
}
|
|
235
274
|
else { // service, entity, anything else?
|
|
236
275
|
// handle the annotations directly tied to the object
|
|
237
|
-
handleAnnotations(objName, object);
|
|
276
|
+
handleAnnotations(objName, object, location);
|
|
238
277
|
// handle the annotations of the object's elements
|
|
239
|
-
handleElements(objName, object);
|
|
278
|
+
handleElements(objName, object, location);
|
|
240
279
|
// handle the annotations of the object's actions
|
|
241
|
-
handleBoundActions(objName, object);
|
|
280
|
+
handleBoundActions(objName, object, location);
|
|
242
281
|
}
|
|
243
282
|
}
|
|
244
283
|
});
|
|
@@ -246,7 +285,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
246
285
|
// filter out empty <Annotations...> elements
|
|
247
286
|
// add references for the used vocabularies
|
|
248
287
|
return {
|
|
249
|
-
annos: g_annosArray,
|
|
288
|
+
annos: g_annosArray,
|
|
289
|
+
usedVocabularies: Object.values(vocabularyDefinitions).filter(v => v.used),
|
|
290
|
+
xrefs: Object.values(adhocDictionary.xrefs).filter(v => v.used).map(v => v.$myServiceRoot)
|
|
250
291
|
};
|
|
251
292
|
|
|
252
293
|
//-------------------------------------------------------------------------------------------------
|
|
@@ -259,21 +300,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
259
300
|
return v && v[0];
|
|
260
301
|
}
|
|
261
302
|
|
|
262
|
-
// this function is called in the translation code to issue an info/warning/error message
|
|
263
|
-
// messages are reported via the severity function
|
|
264
|
-
// context contains "semantic location"
|
|
265
|
-
function message(severity, context, message, args={}) {
|
|
266
|
-
let fullMessage = 'In annotation translation: ' + message;
|
|
267
|
-
if (context) {
|
|
268
|
-
let loc = 'target: ' + context.target + ', annotation: ' + context.term;
|
|
269
|
-
if (context.stack.length > 0) {
|
|
270
|
-
loc += context.stack.join('');
|
|
271
|
-
}
|
|
272
|
-
fullMessage += ', ' + loc;
|
|
273
|
-
}
|
|
274
|
-
severity(null, null, args, `${fullMessage}`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
303
|
/*
|
|
278
304
|
Mapping annotated thing in cds/csn => annotated thing in edmx:
|
|
279
305
|
|
|
@@ -317,37 +343,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
317
343
|
// handle the annotations of the elements of an object
|
|
318
344
|
// in: objname : name of the object
|
|
319
345
|
// object : the object itself
|
|
320
|
-
function handleElements(objname, object) {
|
|
346
|
+
function handleElements(objname, object, location) {
|
|
321
347
|
if (!object.elements) return;
|
|
322
348
|
Object.entries(object.elements).forEach(([elemName, element]) => {
|
|
323
349
|
// determine the name of the target in the resulting edm
|
|
324
350
|
// for non-assoc element, this simply is "<objectName>/<elementName>"
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// handle sub elements
|
|
329
|
-
if (element.elements) {
|
|
330
|
-
handleNestedElements(objname, elemName, element.elements);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// handling annotations at nested elements is not yet supported
|
|
336
|
-
// => issue a warning, but only if there actually are annotations
|
|
337
|
-
function handleNestedElements(objname, baseElemName, elementsObj) {
|
|
338
|
-
if(!elementsObj) return;
|
|
339
|
-
Object.entries(elementsObj).forEach(([elemName, element]) => {
|
|
340
|
-
if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
|
|
341
|
-
message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (element.elements) {
|
|
345
|
-
handleNestedElements(objname, baseElemName + '.' + elemName, element.elements);
|
|
346
|
-
}
|
|
351
|
+
const edmTargetName = objname + '/' + elemName;
|
|
352
|
+
const eLocation = [ ...location, 'elements', elemName ];
|
|
353
|
+
handleAnnotations(edmTargetName, element, eLocation);
|
|
347
354
|
});
|
|
348
355
|
}
|
|
349
356
|
|
|
350
|
-
|
|
351
357
|
// Annotations for actions and functions (and their parameters)
|
|
352
358
|
// v2, unbound: Target = <service>.EntityContainer/<action/function>
|
|
353
359
|
// v2, bound: Target = <service>.EntityContainer/<entity>_<action/function>
|
|
@@ -359,17 +365,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
359
365
|
// handle the annotations of cObject's (an entity) bound actions/functions and their parameters
|
|
360
366
|
// in: cObjectname : qualified name of the object that holds the actions
|
|
361
367
|
// cObject : the object itself
|
|
362
|
-
function handleBoundActions(cObjectname, cObject) {
|
|
368
|
+
function handleBoundActions(cObjectname, cObject, location) {
|
|
363
369
|
if(!cObject.actions) return;
|
|
364
370
|
// get service name: remove last part of the object name
|
|
365
371
|
// only works if all objects ly flat in the service
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
372
|
+
const nameParts = cObjectname.split('.')
|
|
373
|
+
const entityName = nameParts.pop();
|
|
374
|
+
const serviceName = nameParts.join('.');
|
|
369
375
|
|
|
370
376
|
Object.entries(cObject.actions).forEach(([n, action]) => {
|
|
371
|
-
|
|
372
|
-
handleAction(actionName, action, cObjectname);
|
|
377
|
+
const actionName = serviceName + '.' + (isV2() ? entityName + '_' : '') + n;
|
|
378
|
+
handleAction(actionName, action, cObjectname, [ ...location, 'actions', n ]);
|
|
373
379
|
});
|
|
374
380
|
}
|
|
375
381
|
|
|
@@ -378,63 +384,63 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
378
384
|
// in: cActionName : qualified name of the action
|
|
379
385
|
// cAction : the action object
|
|
380
386
|
// entityNameIfBound : qualified name of entity if bound action/function
|
|
381
|
-
function handleAction(cActionName, cAction, entityNameIfBound) {
|
|
387
|
+
function handleAction(cActionName, cAction, entityNameIfBound, location) {
|
|
382
388
|
let actionName = cActionName;
|
|
383
389
|
if (isV2()) { // Replace up to last dot with <serviceName>.EntityContainer
|
|
384
390
|
const lastDotIndex = actionName.lastIndexOf('.');
|
|
385
391
|
if (lastDotIndex > -1)
|
|
386
|
-
actionName = serviceName + '.EntityContainer/' + actionName.
|
|
392
|
+
actionName = serviceName + '.EntityContainer/' + actionName.substring(lastDotIndex + 1);
|
|
387
393
|
}
|
|
388
394
|
else { // add parameter type list
|
|
389
|
-
actionName += relParList(
|
|
395
|
+
actionName += relParList();
|
|
390
396
|
}
|
|
391
397
|
|
|
392
|
-
handleAnnotations(actionName, cAction);
|
|
398
|
+
handleAnnotations(actionName, cAction, location);
|
|
399
|
+
|
|
393
400
|
if(cAction.params) {
|
|
394
401
|
Object.entries(cAction.params).forEach(([n, p]) => {
|
|
395
|
-
|
|
396
|
-
handleAnnotations(edmTargetName, p);
|
|
402
|
+
const edmTargetName = actionName + '/' + n;
|
|
403
|
+
handleAnnotations(edmTargetName, p, [ ...location, 'params', n ]);
|
|
397
404
|
});
|
|
398
405
|
}
|
|
399
|
-
}
|
|
400
406
|
|
|
401
|
-
|
|
407
|
+
function relParList() {
|
|
402
408
|
// we rely on the order of params in the csn being the correct one
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
const params = [];
|
|
410
|
+
if (entityNameIfBound) {
|
|
411
|
+
params.push(cAction['@cds.odata.bindingparameter.collection'] ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
412
|
+
}
|
|
413
|
+
if (cAction.kind === 'function') {
|
|
414
|
+
if(cAction.params) {
|
|
415
|
+
cAction.params && Object.values(cAction.params).forEach(p => {
|
|
416
|
+
const isArrayType = !p.type && p.items && p.items.type;
|
|
417
|
+
params.push(isArrayType ? 'Collection(' + mapType(p.items) + ')' : mapType(p));
|
|
418
|
+
});
|
|
419
|
+
}
|
|
413
420
|
}
|
|
414
|
-
|
|
415
|
-
return '(' + params.join(',') + ')';
|
|
421
|
+
return '(' + params.join(',') + ')';
|
|
416
422
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
423
|
+
function mapType(p) {
|
|
424
|
+
if(isBuiltinType(p.type))
|
|
425
|
+
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/)
|
|
426
|
+
else if(options.whatsMySchemaName) {
|
|
427
|
+
const schemaName = options.whatsMySchemaName(p.type);
|
|
422
428
|
// strip the service namespace of from a parameter type
|
|
423
|
-
|
|
424
|
-
|
|
429
|
+
if(schemaName && schemaName !== options.serviceName)
|
|
430
|
+
return p.type.replace(options.serviceName + '.', '');
|
|
431
|
+
}
|
|
432
|
+
return p.type;
|
|
425
433
|
}
|
|
426
|
-
return p.type;
|
|
427
434
|
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
435
|
|
|
436
|
+
}
|
|
431
437
|
|
|
432
438
|
|
|
433
439
|
// handle all the annotations for a given cds thing, here called carrier
|
|
434
440
|
// edmTargetName : string, name of the target in edm
|
|
435
441
|
// carrier: object, the annotated cds thing, contains all the annotations
|
|
436
442
|
// as properties with names starting with @
|
|
437
|
-
function handleAnnotations(edmTargetName, carrier) {
|
|
443
|
+
function handleAnnotations(edmTargetName, carrier, location) {
|
|
438
444
|
// collect the names of the carrier's annotation properties
|
|
439
445
|
// keep only those annotations that - start with a known vocabulary name
|
|
440
446
|
// - have a value other than null
|
|
@@ -452,22 +458,20 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
452
458
|
// Filter unknown toplevel annotations
|
|
453
459
|
// Final filtering of all annotations is done in handleTerm
|
|
454
460
|
|
|
455
|
-
let
|
|
456
|
-
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
457
|
-
let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
461
|
+
let knownAnnos = filterKnownAnnotations();
|
|
458
462
|
if (knownAnnos.length === 0) return;
|
|
459
463
|
|
|
460
464
|
if(rewriteInnerAnnotations()) {
|
|
461
|
-
|
|
462
|
-
knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
465
|
+
knownAnnos = filterKnownAnnotations();
|
|
463
466
|
if (knownAnnos.length === 0) return;
|
|
464
467
|
}
|
|
468
|
+
|
|
465
469
|
const prefixTree = createPrefixTree();
|
|
466
470
|
|
|
467
471
|
// usually, for a given carrier there is one target
|
|
468
472
|
// for some carriers (service, entity), there can be an alternative target (usually the EntitySet)
|
|
469
473
|
// alternativeEdmTargetName: name of alternative target
|
|
470
|
-
// which one to choose depends on the "AppliesTo"
|
|
474
|
+
// which one to choose depends on the "AppliesTo" message of the single annotations, so we have
|
|
471
475
|
// to defer this decision; this is why we here construct a function that can make the decision
|
|
472
476
|
// later when looking at single annotations
|
|
473
477
|
|
|
@@ -485,7 +489,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
485
489
|
const alternativeAnnotations = [];
|
|
486
490
|
|
|
487
491
|
// now create annotation objects for all the annotations of carrier
|
|
488
|
-
handleAnno2(addAnnotation,
|
|
492
|
+
handleAnno2(addAnnotation, prefixTree, location);
|
|
489
493
|
|
|
490
494
|
// Produce Edm.Annotations and attach collected Edm.Annotation(s) to the
|
|
491
495
|
// envelope (or directly to the Schema)
|
|
@@ -503,6 +507,44 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
503
507
|
g_annosArray.push(annotations);
|
|
504
508
|
}
|
|
505
509
|
|
|
510
|
+
function filterKnownAnnotations() {
|
|
511
|
+
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
512
|
+
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
513
|
+
const knownAnnos = annoNames.filter(whatsMyTermNamespace).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
514
|
+
if(isBetaEnabled(options, 'odataTerms')) {
|
|
515
|
+
// Extend knownAnnos with the in-service term definitions
|
|
516
|
+
const adhoc = [];
|
|
517
|
+
annoNames.forEach(an => {
|
|
518
|
+
const paths = an.slice(1).split('.');
|
|
519
|
+
const hasNSPrefix = paths[0] === serviceName;
|
|
520
|
+
if(!hasNSPrefix) {
|
|
521
|
+
paths.splice(0, 0, serviceName);
|
|
522
|
+
}
|
|
523
|
+
const fqName = '@' + paths.join('.');
|
|
524
|
+
const i = paths[1].indexOf('#');
|
|
525
|
+
const termNameWithoutQualifiers = i > 0 ? paths[1].substring(0, i) : paths[1];
|
|
526
|
+
const def = reqDefs.definitions[paths[0] + '.' + termNameWithoutQualifiers];
|
|
527
|
+
// if there is a term definition inside the service and the
|
|
528
|
+
// annotation value is != null, then add the annotation to the list
|
|
529
|
+
// of known annotations
|
|
530
|
+
if(def?.kind === 'annotation' && carrier[an] !== null) {
|
|
531
|
+
// Subsequent annotation handler code expects that first path segment
|
|
532
|
+
// is the Vocabulary namespace. The ad-hoc namespace is the service
|
|
533
|
+
// name itself.
|
|
534
|
+
// For service S an annotation assignment could be addressed
|
|
535
|
+
// relative or absolute to the service @S.foo or @foo
|
|
536
|
+
if(!hasNSPrefix) {
|
|
537
|
+
carrier[fqName] = carrier[an];
|
|
538
|
+
delete carrier[an];
|
|
539
|
+
}
|
|
540
|
+
adhoc.push(fqName);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
knownAnnos.push(...adhoc);
|
|
544
|
+
}
|
|
545
|
+
return knownAnnos;
|
|
546
|
+
}
|
|
547
|
+
|
|
506
548
|
// construct a function that is used to add an <Annotation ...> to the
|
|
507
549
|
// respective collector array
|
|
508
550
|
// this function is specific to the actual carrier, following the mapping rules given above
|
|
@@ -527,7 +569,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
527
569
|
}
|
|
528
570
|
// Another crazy hack due to this crazy function:
|
|
529
571
|
// If carrier is a managed association (has keys) and rc is false (annotation was not applicable)
|
|
530
|
-
// return true to NOT trigger 'unapplicable'
|
|
572
|
+
// return true to NOT trigger 'unapplicable' message message
|
|
531
573
|
if(rc === false && carrier.target && carrier.keys && appliesTo.includes('Property'))
|
|
532
574
|
rc = true;
|
|
533
575
|
return rc;
|
|
@@ -563,7 +605,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
563
605
|
alternativeEdmTargetName = carrier.$entitySetName || edmTargetName
|
|
564
606
|
const lastDotIndex = alternativeEdmTargetName.lastIndexOf('.');
|
|
565
607
|
if (lastDotIndex > -1)
|
|
566
|
-
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.
|
|
608
|
+
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.substring(lastDotIndex + 1);
|
|
567
609
|
hasAlternativeCarrier = carrier.$hasEntitySet;
|
|
568
610
|
}
|
|
569
611
|
else if(carrier.kind === 'type') {
|
|
@@ -635,7 +677,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
635
677
|
|
|
636
678
|
function rewriteInnerAnnotations() {
|
|
637
679
|
let rc = false;
|
|
638
|
-
for (
|
|
680
|
+
for (const a of knownAnnos) {
|
|
639
681
|
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
640
682
|
/*
|
|
641
683
|
New inner annotation (de-)structuring of the core compiler to make
|
|
@@ -689,7 +731,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
689
731
|
// see example at definition of function mergePathStepsIntoPrefixTree
|
|
690
732
|
const prefixTree = {};
|
|
691
733
|
|
|
692
|
-
for (
|
|
734
|
+
for (const a of knownAnnos) {
|
|
693
735
|
// remove leading @ and split at "."
|
|
694
736
|
// stop splitting at ".@" (used for nested annotations)
|
|
695
737
|
// Inline JSON EDM allows to add annotations to record members
|
|
@@ -697,7 +739,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
697
739
|
// The splitter should leave such annotations alone, handleEdmJson
|
|
698
740
|
// takes care of assigning these annotations to the record members
|
|
699
741
|
const [ prefix, innerAnnotation ] = a.split('.@');
|
|
700
|
-
const
|
|
742
|
+
const ns = whatsMyTermNamespace(prefix);
|
|
743
|
+
const steps = prefix.replace('@' + ns + '.', '').split('.');
|
|
744
|
+
steps.splice(0,0, ns);
|
|
701
745
|
let i = steps.lastIndexOf('$edmJson');
|
|
702
746
|
if(i > -1) {
|
|
703
747
|
i = steps.findIndex(s => s.includes('@'), i+1);
|
|
@@ -743,7 +787,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
743
787
|
// q2 : ... } } }
|
|
744
788
|
function mergePathStepsIntoPrefixTree(tree, pathSteps, index, carrier) {
|
|
745
789
|
// TODO check nesting level > 3
|
|
746
|
-
|
|
790
|
+
const name = pathSteps[index];
|
|
747
791
|
if (index+1 < pathSteps.length ) {
|
|
748
792
|
if (!tree[name]) {
|
|
749
793
|
tree[name] = {};
|
|
@@ -761,27 +805,35 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
761
805
|
// handle all the annotations for a given carrier
|
|
762
806
|
// addAnnotationFunc: a function that adds the <Annotation ...> tags created here into the
|
|
763
807
|
// correct parent tag (see handleAnnotations())
|
|
764
|
-
// edmTargetName: name of the edmx target, only used for messages
|
|
765
808
|
// prefixTree: the annotations
|
|
766
|
-
function handleAnno2(addAnnotationFunc,
|
|
809
|
+
function handleAnno2(addAnnotationFunc, prefixTree, location) {
|
|
767
810
|
// first level names of prefix tree are the vocabulary names
|
|
768
811
|
// second level names are the term names
|
|
769
812
|
// create an annotation tag <Annotation ...> for each term
|
|
770
|
-
for (
|
|
771
|
-
for (
|
|
772
|
-
|
|
813
|
+
for (const voc of Object.keys(prefixTree)) {
|
|
814
|
+
for (const term of Object.keys(prefixTree[voc])) {
|
|
815
|
+
const fullTermName = voc + '.' + term;
|
|
816
|
+
|
|
817
|
+
// msg is "semantic" location message used for messages
|
|
818
|
+
const msg = {
|
|
819
|
+
fullTermName,
|
|
820
|
+
stack: [],
|
|
821
|
+
location: [ ...location, '@' + fullTermName ],
|
|
822
|
+
};
|
|
823
|
+
msg.anno = () => {
|
|
824
|
+
return msg.fullTermName + msg.stack.join('');
|
|
825
|
+
}
|
|
773
826
|
|
|
774
|
-
// context is "semantic" location info used for messages
|
|
775
|
-
let context = { target: edmTargetName, term: fullTermName, stack: [] };
|
|
776
827
|
// anno is the full <Annotation Term=...>
|
|
777
|
-
|
|
828
|
+
const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
|
|
778
829
|
if(anno !== undefined) {
|
|
779
|
-
// addAnnotationFunc needs AppliesTo
|
|
780
|
-
|
|
781
|
-
|
|
830
|
+
// addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
|
|
831
|
+
const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
|
|
832
|
+
const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
|
|
782
833
|
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
|
|
783
834
|
if(dictTerm && dictTerm.AppliesTo) {
|
|
784
|
-
message(
|
|
835
|
+
message('odata-anno-def', location,
|
|
836
|
+
{ anno: termName, rawvalues: dictTerm.AppliesTo, '#': 'notapplied' });
|
|
785
837
|
}
|
|
786
838
|
}
|
|
787
839
|
}
|
|
@@ -793,9 +845,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
793
845
|
// annoValue : the annotation value from the csn
|
|
794
846
|
// if the csn contains flattened out elements of a structured annotation,
|
|
795
847
|
// they are regrouped here
|
|
796
|
-
//
|
|
848
|
+
// msg : for messages
|
|
797
849
|
// return : object that represents the annotation in the result edmx
|
|
798
|
-
function handleTerm(termName, annoValue,
|
|
850
|
+
function handleTerm(termName, annoValue, msg) {
|
|
799
851
|
/**
|
|
800
852
|
* create the <Annotation ...> tag
|
|
801
853
|
* @type {object}
|
|
@@ -803,52 +855,40 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
803
855
|
let newAnno = undefined;
|
|
804
856
|
const omissions = { 'Aggregation.default':1 };
|
|
805
857
|
const nullList = { 'Core.OperationAvailable':1 };
|
|
806
|
-
|
|
807
|
-
if(vocabularyDefinitions[voc] && annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
808
|
-
newAnno = new Edm.Annotation(v, termName);
|
|
809
|
-
|
|
858
|
+
if(annoValue !== null && !omissions[termName]|| nullList[termName]) {
|
|
810
859
|
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
|
|
811
860
|
// -> remove qualifier from termName and set Qualifier attribute in newAnno
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
861
|
+
const i = termName.indexOf('#');
|
|
862
|
+
const termNameWithoutQualifiers = i > 0 ? termName.substring(0, i) : termName;
|
|
863
|
+
const qualifier = i >= 0 ? termName.substring(i+1) : undefined;
|
|
864
|
+
|
|
865
|
+
termNameWithoutQualifiers.split('.').forEach((id) => {
|
|
866
|
+
if(!edmUtils.isODataSimpleIdentifier(id))
|
|
867
|
+
message('odata-spec-violation-id', msg.location, { id });
|
|
868
|
+
})
|
|
869
|
+
newAnno = new Edm.Annotation(v, termNameWithoutQualifiers);
|
|
870
|
+
if (qualifier?.length) {
|
|
871
|
+
if (!edmUtils.isODataSimpleIdentifier(qualifier)) {
|
|
872
|
+
message('odata-spec-violation-id', msg.location,
|
|
873
|
+
{ id: qualifier, '#': 'qualifier' });
|
|
819
874
|
}
|
|
820
875
|
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
|
|
821
|
-
newAnno.setEdmAttribute('Qualifier',
|
|
876
|
+
newAnno.setEdmAttribute('Qualifier', qualifier);
|
|
822
877
|
}
|
|
823
|
-
if (p.length>2) {
|
|
824
|
-
message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
878
|
// get the type of the term from the dictionary
|
|
828
879
|
let termTypeName = null;
|
|
829
|
-
|
|
880
|
+
const dictTerm = getDictTerm(termNameWithoutQualifiers, msg);
|
|
830
881
|
if (dictTerm) {
|
|
831
882
|
termTypeName = dictTerm.Type;
|
|
832
883
|
}
|
|
833
884
|
else {
|
|
834
|
-
message(
|
|
885
|
+
message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
|
|
835
886
|
}
|
|
836
887
|
|
|
837
888
|
// handle the annotation value and put the result into the <Annotation ...> tag just created above
|
|
838
|
-
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName,
|
|
889
|
+
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, msg);
|
|
839
890
|
}
|
|
840
891
|
return newAnno;
|
|
841
|
-
|
|
842
|
-
function checkOdataTerm(ns) {
|
|
843
|
-
const simpleIdentifiers = ns.split('.');
|
|
844
|
-
simpleIdentifiers.forEach((identifier) => {
|
|
845
|
-
if(!edmUtils.isODataSimpleIdentifier(identifier)){
|
|
846
|
-
message(error, context,
|
|
847
|
-
`OData annotation term "${ identifier }" must consist of one or more dot separated simple identifiers (each starting with a letter or underscore, followed by at most 127 letters)`);
|
|
848
|
-
}
|
|
849
|
-
})
|
|
850
|
-
}
|
|
851
|
-
|
|
852
892
|
}
|
|
853
893
|
|
|
854
894
|
|
|
@@ -857,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
857
897
|
// oTarget: the result object (o: odata)
|
|
858
898
|
// oTermName: current term
|
|
859
899
|
// dTypeName: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
|
|
860
|
-
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName,
|
|
900
|
+
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName, msg) {
|
|
861
901
|
// this function basically only figures out what kind of annotation value we have
|
|
862
902
|
// (can be: array, expression, enum, pseudo-record, record, simple value),
|
|
863
903
|
// then calls a more specific function to deal with it and puts
|
|
@@ -868,65 +908,86 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
868
908
|
if (isEnumType(dTypeName))
|
|
869
909
|
{
|
|
870
910
|
// if we find an array although we expect an enum, this may be a "flag enum"
|
|
871
|
-
checkMultiEnumValue(cAnnoValue
|
|
872
|
-
oTarget.setJSON({ 'EnumMember': generateMultiEnumValue(cAnnoValue,
|
|
873
|
-
oTarget.setXml( { 'EnumMember': generateMultiEnumValue(cAnnoValue,
|
|
911
|
+
checkMultiEnumValue(cAnnoValue);
|
|
912
|
+
oTarget.setJSON({ 'EnumMember': generateMultiEnumValue(cAnnoValue, false), 'EnumMember@odata.type' : '#'+dTypeName });
|
|
913
|
+
oTarget.setXml( { 'EnumMember': generateMultiEnumValue(cAnnoValue, true) });
|
|
874
914
|
}
|
|
875
915
|
else
|
|
876
916
|
{
|
|
877
|
-
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName,
|
|
917
|
+
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, msg));
|
|
878
918
|
}
|
|
879
919
|
}
|
|
880
920
|
else if (cAnnoValue && typeof cAnnoValue === 'object') {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
else if ('=' in cAnnoValue) {
|
|
921
|
+
// an empty record is rendered as <Record/>
|
|
922
|
+
if ('=' in cAnnoValue) {
|
|
885
923
|
// expression
|
|
886
|
-
|
|
924
|
+
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
887
925
|
oTarget.setXml( { [res.name] : res.value });
|
|
888
926
|
oTarget.setJSON( { [res.name] : res.value });
|
|
889
927
|
}
|
|
890
928
|
else if (cAnnoValue['#'] !== undefined) {
|
|
929
|
+
const enumSymbol = cAnnoValue['#'];
|
|
891
930
|
// enum
|
|
892
931
|
if (dTypeName) {
|
|
893
|
-
|
|
894
|
-
|
|
932
|
+
const typeDef = getDictType(stripCollection(dTypeName));
|
|
933
|
+
if(typeDef && typeDef.$Allowed && !typeDef.Members) {
|
|
934
|
+
const allowedValue = typeDef.$Allowed.Symbols[enumSymbol];
|
|
935
|
+
if(!allowedValue) {
|
|
936
|
+
message('odata-anno-value', msg.location,
|
|
937
|
+
{ anno: msg.anno(),
|
|
938
|
+
type: dTypeName,
|
|
939
|
+
value: `"#${enumSymbol}"`,
|
|
940
|
+
rawvalues: Object.keys(typeDef.$Allowed.Symbols).map(m => `"#${m}"`),
|
|
941
|
+
'#': 'enum'
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
oTarget.setXml( { [typeDef.UnderlyingType?.replace('Edm.', '') || 'String']: allowedValue.Value || enumSymbol });
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
else if(checkEnumValue(enumSymbol))
|
|
949
|
+
oTarget.setXml( { 'EnumMember': dTypeName + '/' + enumSymbol });
|
|
950
|
+
else
|
|
951
|
+
oTarget.setXml( { 'String': enumSymbol });
|
|
895
952
|
}
|
|
896
953
|
else {
|
|
897
|
-
oTarget.setXml( { 'EnumMember': oTermName + 'Type/' +
|
|
954
|
+
oTarget.setXml( { 'EnumMember': oTermName + 'Type/' + enumSymbol });
|
|
898
955
|
}
|
|
899
|
-
oTarget.setJSON({ 'Edm.String':
|
|
956
|
+
oTarget.setJSON({ 'Edm.String': enumSymbol });
|
|
900
957
|
}
|
|
901
958
|
else if (cAnnoValue['$value'] !== undefined) {
|
|
902
959
|
// "pseudo-structure" used for annotating scalar annotations
|
|
903
|
-
handleValue(cAnnoValue['$value'], oTarget, oTermName, dTypeName,
|
|
960
|
+
handleValue(cAnnoValue['$value'], oTarget, oTermName, dTypeName, msg);
|
|
904
961
|
|
|
905
|
-
|
|
962
|
+
const k = Object.keys(cAnnoValue).filter( x => x[0] === '@');
|
|
906
963
|
if (!k || k.length === 0) {
|
|
907
|
-
message(
|
|
964
|
+
message('odata-anno-value', msg.location,
|
|
965
|
+
{ anno: msg.anno(), str: 'nested', '#': 'nested' });
|
|
908
966
|
}
|
|
909
|
-
for (
|
|
910
|
-
|
|
967
|
+
for (const nestedAnnoName of k) {
|
|
968
|
+
const nestedAnno = handleTerm(nestedAnnoName.slice(1), cAnnoValue[nestedAnnoName], msg);
|
|
911
969
|
oTarget.append(nestedAnno);
|
|
912
970
|
}
|
|
913
971
|
}
|
|
914
972
|
else if (cAnnoValue['$edmJson']) {
|
|
915
973
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
916
|
-
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'],
|
|
974
|
+
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], msg));
|
|
917
975
|
}
|
|
918
976
|
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
919
977
|
// object consists only of properties starting with "@", no $value
|
|
920
|
-
message(
|
|
978
|
+
message('odata-anno-value', msg.location,
|
|
979
|
+
{ anno: msg.anno(), str: 'base', '#': 'nested'} );
|
|
921
980
|
}
|
|
922
981
|
else {
|
|
923
982
|
// regular record
|
|
924
|
-
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName,
|
|
983
|
+
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, msg));
|
|
925
984
|
}
|
|
926
985
|
}
|
|
927
986
|
else {
|
|
928
|
-
|
|
929
|
-
if(oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean'
|
|
987
|
+
const res = handleSimpleValue(cAnnoValue, dTypeName, msg);
|
|
988
|
+
if(((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
|
|
989
|
+
(oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType'))
|
|
990
|
+
&& cAnnoValue === null) {
|
|
930
991
|
oTarget.append(new Edm.ValueThing(v, 'Null'));
|
|
931
992
|
oTarget._ignoreChildren = true;
|
|
932
993
|
}
|
|
@@ -935,81 +996,95 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
935
996
|
}
|
|
936
997
|
oTarget.setJSON( { [res.jsonName] : res.value });
|
|
937
998
|
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
999
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1000
|
+
// found an enum value ("#"), check whether this fits
|
|
1001
|
+
// the expected type "dTypeName"
|
|
1002
|
+
function checkEnumValue(value) {
|
|
1003
|
+
let rc = true;
|
|
1004
|
+
const expectedType = getDictType(dTypeName);
|
|
1005
|
+
if (!expectedType && !isPrimitiveType(dTypeName)) {
|
|
1006
|
+
message('odata-anno-dict', msg.location,
|
|
1007
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1008
|
+
}
|
|
1009
|
+
else if (isComplexType(dTypeName) || isPrimitiveType(dTypeName) || expectedType['$kind'] !== 'EnumType') {
|
|
1010
|
+
message('odata-anno-value', msg.location,
|
|
1011
|
+
{ anno: msg.anno(),
|
|
1012
|
+
type: dTypeName,
|
|
1013
|
+
value: `"#${value}"` });
|
|
1014
|
+
rc = false;
|
|
1015
|
+
}
|
|
1016
|
+
else if (!expectedType.Members.includes(value)) {
|
|
1017
|
+
message('odata-anno-value', msg.location,
|
|
1018
|
+
{ anno: msg.anno(),
|
|
1019
|
+
type: dTypeName,
|
|
1020
|
+
value: `"#${value}"`,
|
|
1021
|
+
rawvalues: expectedType.Members.map(m => `"#${m}"`),
|
|
1022
|
+
'#': 'enum'
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
return rc;
|
|
956
1026
|
}
|
|
957
|
-
}
|
|
958
1027
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1028
|
+
// cAnnoValue: array
|
|
1029
|
+
// dTypeName: expected type, already identified as enum type
|
|
1030
|
+
// array is expected to contain enum values
|
|
1031
|
+
function checkMultiEnumValue(cAnnoValue) {
|
|
963
1032
|
// we know that dTypeName is not null
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1033
|
+
const type = getDictType(dTypeName);
|
|
1034
|
+
if (!type || type['IsFlags'] !== 'true') {
|
|
1035
|
+
message('odata-anno-value', msg.location,
|
|
1036
|
+
{ anno: msg.anno(),
|
|
1037
|
+
str: 'collection',
|
|
1038
|
+
type: dTypeName,
|
|
1039
|
+
'#': 'struct' });
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
let index = 0;
|
|
1043
|
+
for (let value of cAnnoValue) {
|
|
1044
|
+
msg.stack.push('[' + index + ']');
|
|
1045
|
+
index++;
|
|
1046
|
+
if (value['#']) {
|
|
1047
|
+
checkEnumValue(value['#']);
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
message('odata-anno-value', msg.location,
|
|
1051
|
+
{ anno: msg.anno(),
|
|
1052
|
+
type: dTypeName,
|
|
1053
|
+
value: value['='] || value,
|
|
1054
|
+
rawvalues: type.Members.map(m => `"#${m}"`),
|
|
1055
|
+
'#': 'enum'
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
msg.stack.pop();
|
|
1059
|
+
}
|
|
967
1060
|
}
|
|
968
1061
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
checkEnumValue(e['#'], dTypeName, context);
|
|
975
|
-
}
|
|
976
|
-
else {
|
|
977
|
-
// TODO improve message: but found ...
|
|
978
|
-
message(warning, context, 'expected an enum value');
|
|
979
|
-
}
|
|
980
|
-
context.stack.pop();
|
|
1062
|
+
function generateMultiEnumValue(cAnnoValue, forXml) {
|
|
1063
|
+
// remove all invalid entries (warnining message has already been issued)
|
|
1064
|
+
// replace short enum name by the full name
|
|
1065
|
+
// concatenate all the enums to a string, separated by spaces
|
|
1066
|
+
return cAnnoValue.filter( x => x['#'] != undefined ).map( x => (forXml ? dTypeName + '/' : '') + x['#'] ).join(forXml ? ' ' : ',');
|
|
981
1067
|
}
|
|
982
1068
|
}
|
|
983
1069
|
|
|
984
|
-
function generateMultiEnumValue(cAnnoValue, dTypeName, forXml)
|
|
985
|
-
{
|
|
986
|
-
// remove all invalid entries (warnining message has already been issued)
|
|
987
|
-
// replace short enum name by the full name
|
|
988
|
-
// concatenate all the enums to a string, separated by spaces
|
|
989
|
-
return cAnnoValue.filter( x => x['#'] != undefined ).map( x => (forXml ? dTypeName + '/' : '') + x['#'] ).join(forXml ? ' ' : ',');
|
|
990
|
-
}
|
|
991
1070
|
|
|
992
1071
|
|
|
993
1072
|
// found an expression value ("=") "expr"
|
|
994
1073
|
// expected type is dTypeName
|
|
995
1074
|
// note: expr can also be provided if an enum/complex type/collection is expected
|
|
996
|
-
function handleExpression(
|
|
1075
|
+
function handleExpression(value, dTypeName) {
|
|
997
1076
|
let typeName = 'Path';
|
|
998
1077
|
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(dTypeName) )
|
|
999
1078
|
typeName = dTypeName.split('.')[1];
|
|
1000
1079
|
|
|
1001
|
-
|
|
1002
|
-
if (!expr) {
|
|
1003
|
-
message(warning, context, 'empty expression value');
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1080
|
+
if(value) {
|
|
1006
1081
|
// replace all occurrences of '.' by '/' up to first '@'
|
|
1007
|
-
|
|
1082
|
+
value = value.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
|
|
1008
1083
|
}
|
|
1009
1084
|
|
|
1010
1085
|
return {
|
|
1011
1086
|
name : typeName,
|
|
1012
|
-
value
|
|
1087
|
+
value
|
|
1013
1088
|
}
|
|
1014
1089
|
}
|
|
1015
1090
|
|
|
@@ -1021,7 +1096,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1021
1096
|
// floating point type except Edm.Decimal -> Float
|
|
1022
1097
|
// Edm.Decimal -> Decimal
|
|
1023
1098
|
// integer tpye -> Int
|
|
1024
|
-
function handleSimpleValue(value, dTypeName,
|
|
1099
|
+
function handleSimpleValue(value, dTypeName, msg) {
|
|
1025
1100
|
// these types must be represented as "String" values in XML:
|
|
1026
1101
|
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
|
1027
1102
|
// caller already made sure that val is neither object nor array
|
|
@@ -1034,38 +1109,53 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1034
1109
|
|
|
1035
1110
|
if (isEnumType(resolvedType)) {
|
|
1036
1111
|
const type = getDictType(resolvedType);
|
|
1037
|
-
const expected = type.Members.map(m => `"#${m}"`)
|
|
1038
|
-
message(
|
|
1112
|
+
const expected = type.Members.map(m => `"#${m}"`);
|
|
1113
|
+
message('odata-anno-value', msg.location,
|
|
1114
|
+
{ anno: msg.anno(),
|
|
1115
|
+
value,
|
|
1116
|
+
rawvalues: expected,
|
|
1117
|
+
type: resolvedType,
|
|
1118
|
+
'#': 'enum' });
|
|
1039
1119
|
}
|
|
1040
1120
|
|
|
1041
1121
|
let typeName = 'String';
|
|
1042
1122
|
if(Allowed && !Allowed.Values[value])
|
|
1043
|
-
message(
|
|
1123
|
+
message('odata-anno-value', msg.location,
|
|
1124
|
+
{ anno: msg.anno(),
|
|
1125
|
+
value,
|
|
1126
|
+
rawvalues: Object.keys(Allowed.Values),
|
|
1127
|
+
type: resolvedType,
|
|
1128
|
+
'#': 'enum' });
|
|
1044
1129
|
|
|
1045
1130
|
if (typeof value === 'string') {
|
|
1046
1131
|
if (resolvedType === 'Edm.Boolean') {
|
|
1047
1132
|
typeName = 'Bool';
|
|
1048
1133
|
if (value !== 'true' && value !== 'false') {
|
|
1049
|
-
message(
|
|
1134
|
+
message('odata-anno-value', msg.location,
|
|
1135
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1050
1136
|
}
|
|
1051
1137
|
}
|
|
1052
1138
|
else if (resolvedType === 'Edm.Decimal') {
|
|
1053
1139
|
typeName = 'Decimal';
|
|
1054
1140
|
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1055
|
-
message(
|
|
1141
|
+
message('odata-anno-value', msg.location,
|
|
1142
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1056
1143
|
}
|
|
1057
1144
|
}
|
|
1058
1145
|
else if (resolvedType === 'Edm.Double' || resolvedType === 'Edm.Single') {
|
|
1059
1146
|
typeName = 'Float';
|
|
1060
1147
|
if (isNaN(Number(value)) || isNaN(parseFloat(value))) {
|
|
1061
|
-
message(
|
|
1148
|
+
message('odata-anno-value', msg.location,
|
|
1149
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1062
1150
|
}
|
|
1063
1151
|
}
|
|
1064
1152
|
else if (isComplexType(resolvedType)) {
|
|
1065
|
-
message(
|
|
1153
|
+
message('odata-anno-value', msg.location,
|
|
1154
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1066
1155
|
}
|
|
1067
1156
|
else if (isEnumType(resolvedType)) {
|
|
1068
|
-
message(
|
|
1157
|
+
message('odata-anno-value', msg.location,
|
|
1158
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1069
1159
|
typeName = 'EnumMember';
|
|
1070
1160
|
}
|
|
1071
1161
|
else if (resolvedType && resolvedType.startsWith('Edm.') && !castToXmlString.includes(resolvedType)) {
|
|
@@ -1076,7 +1166,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1076
1166
|
if(resolvedType == undefined || castToXmlString.some(t => t === resolvedType))
|
|
1077
1167
|
resolvedType = 'Edm.String';
|
|
1078
1168
|
// TODO
|
|
1079
|
-
//message(
|
|
1169
|
+
//message(message, msg, "type is not yet handled: found String, expected type: " + dTypeName);
|
|
1080
1170
|
}
|
|
1081
1171
|
}
|
|
1082
1172
|
else if (typeof value === 'boolean') {
|
|
@@ -1091,21 +1181,25 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1091
1181
|
typeName = 'String';
|
|
1092
1182
|
}
|
|
1093
1183
|
else {
|
|
1094
|
-
message(
|
|
1184
|
+
message('odata-anno-value', msg.location,
|
|
1185
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1095
1186
|
}
|
|
1096
1187
|
}
|
|
1097
1188
|
else if (typeof value === 'number') {
|
|
1098
1189
|
if (isComplexType(resolvedType)) {
|
|
1099
|
-
message(
|
|
1190
|
+
message('odata-anno-value', msg.location,
|
|
1191
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1100
1192
|
}
|
|
1101
1193
|
else if (resolvedType === 'Edm.String') {
|
|
1102
1194
|
typeName = 'String';
|
|
1103
1195
|
}
|
|
1104
1196
|
else if (resolvedType === 'Edm.PropertyPath') {
|
|
1105
|
-
message(
|
|
1197
|
+
message('odata-anno-value', msg.location,
|
|
1198
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1106
1199
|
}
|
|
1107
1200
|
else if (resolvedType === 'Edm.Boolean') {
|
|
1108
|
-
message(
|
|
1201
|
+
message('odata-anno-value', msg.location,
|
|
1202
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1109
1203
|
}
|
|
1110
1204
|
else if (resolvedType === 'Edm.Decimal') {
|
|
1111
1205
|
typeName = 'Decimal';
|
|
@@ -1127,12 +1221,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1127
1221
|
}
|
|
1128
1222
|
}
|
|
1129
1223
|
}
|
|
1130
|
-
else if (value === null
|
|
1131
|
-
resolvedType
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1224
|
+
else if (value === null)
|
|
1225
|
+
if((resolvedType == null || resolvedType == 'Edm.PrimitiveType') && typeName === 'String') {
|
|
1226
|
+
resolvedType = 'Edm.String';
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
message('odata-anno-value', msg.location,
|
|
1230
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1231
|
+
}
|
|
1136
1232
|
|
|
1137
1233
|
if( ['Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
|
|
1138
1234
|
resolvedType = resolvedType.split('.')[1];
|
|
@@ -1149,16 +1245,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1149
1245
|
// dTypeName : name of the expected record type according to vocabulary, may be null
|
|
1150
1246
|
//
|
|
1151
1247
|
// can be called for a record directly below a term, or at a deeper level
|
|
1152
|
-
function generateRecord(obj, termName, dTypeName,
|
|
1248
|
+
function generateRecord(obj, termName, dTypeName, msg) {
|
|
1153
1249
|
/** @type {object} */
|
|
1154
|
-
|
|
1250
|
+
const newRecord = new Edm.Record(v);
|
|
1155
1251
|
|
|
1156
1252
|
// first determine what is the actual type to be used for the record
|
|
1157
1253
|
if (dTypeName && !isComplexType(dTypeName)) {
|
|
1158
1254
|
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
|
|
1159
|
-
message(
|
|
1255
|
+
message('odata-anno-dict', msg.location,
|
|
1256
|
+
{ anno: msg.anno(), type: dTypeName, '#': 'std' });
|
|
1160
1257
|
else
|
|
1161
|
-
message(
|
|
1258
|
+
message('odata-anno-value', msg.location,
|
|
1259
|
+
{ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
|
|
1162
1260
|
return newRecord;
|
|
1163
1261
|
}
|
|
1164
1262
|
|
|
@@ -1167,26 +1265,32 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1167
1265
|
actualTypeName = obj['$Type'];
|
|
1168
1266
|
if (!getDictType(actualTypeName)) {
|
|
1169
1267
|
// this type doesn't exist
|
|
1170
|
-
message(
|
|
1171
|
-
|
|
1172
|
-
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1173
|
-
}
|
|
1174
|
-
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1175
|
-
// this type doesn't fit the expected one
|
|
1176
|
-
message(warning, context, `explicitly specified type '${ actualTypeName
|
|
1177
|
-
}' is not derived from expected type '${ dTypeName }'`);
|
|
1178
|
-
actualTypeName = dTypeName;
|
|
1268
|
+
message('odata-anno-type', msg.location,
|
|
1269
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
|
|
1179
1270
|
// explicitly mentioned type, render in XML and JSON
|
|
1180
1271
|
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1181
1272
|
}
|
|
1182
1273
|
else if (isAbstractType(actualTypeName)) {
|
|
1183
1274
|
// this type is abstract
|
|
1184
|
-
message(
|
|
1275
|
+
message('odata-anno-type', msg.location,
|
|
1276
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'abstract' });
|
|
1185
1277
|
if(dTypeName)
|
|
1186
1278
|
actualTypeName = dTypeName;
|
|
1187
1279
|
// set to definition name and render in XML and JSON
|
|
1188
1280
|
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1189
1281
|
}
|
|
1282
|
+
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1283
|
+
// this type doesn't fit the expected one
|
|
1284
|
+
message('odata-anno-type', msg.location,
|
|
1285
|
+
{ anno: msg.anno(),
|
|
1286
|
+
type: actualTypeName,
|
|
1287
|
+
name: dTypeName,
|
|
1288
|
+
code: '$Type',
|
|
1289
|
+
'#': 'derived' });
|
|
1290
|
+
actualTypeName = dTypeName;
|
|
1291
|
+
// explicitly mentioned type, render in XML and JSON
|
|
1292
|
+
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1293
|
+
}
|
|
1190
1294
|
else {
|
|
1191
1295
|
// ok
|
|
1192
1296
|
// Dictionary Type, render in XML only for backward compatibility
|
|
@@ -1203,7 +1307,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1203
1307
|
actualTypeName = dTypeName;
|
|
1204
1308
|
}
|
|
1205
1309
|
if (isAbstractType(actualTypeName))
|
|
1206
|
-
message(
|
|
1310
|
+
message('odata-anno-type', msg.location,
|
|
1311
|
+
{ anno: msg.anno(), type: dTypeName, '#': 'abstract' });
|
|
1207
1312
|
|
|
1208
1313
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1209
1314
|
newRecord.setXml( { Type: actualTypeName });
|
|
@@ -1213,37 +1318,38 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1213
1318
|
}
|
|
1214
1319
|
|
|
1215
1320
|
// now the type is clear, so look ath the value
|
|
1216
|
-
|
|
1321
|
+
const dictProperties = getAllProperties(actualTypeName);
|
|
1217
1322
|
|
|
1218
1323
|
// loop over elements
|
|
1219
|
-
for (
|
|
1220
|
-
|
|
1324
|
+
for (const name of Object.keys(obj)) {
|
|
1325
|
+
msg.stack.push('.' + name);
|
|
1221
1326
|
|
|
1222
|
-
if (
|
|
1327
|
+
if (name === '$Type') {
|
|
1223
1328
|
// ignore, this is an "artificial" property used to indicate the type
|
|
1224
1329
|
}
|
|
1225
|
-
else if (
|
|
1330
|
+
else if (name[0] === '@') {
|
|
1226
1331
|
// not a regular property, but a nested annotation
|
|
1227
|
-
|
|
1332
|
+
const newAnno = handleTerm(name.substring(1, name.length), obj[name], msg);
|
|
1228
1333
|
newRecord.append(newAnno);
|
|
1229
1334
|
}
|
|
1230
1335
|
else {
|
|
1231
1336
|
// regular property
|
|
1232
1337
|
let dictPropertyTypeName = null;
|
|
1233
1338
|
if (dictProperties) {
|
|
1234
|
-
dictPropertyTypeName = dictProperties[
|
|
1235
|
-
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
|
|
1236
|
-
message(
|
|
1339
|
+
dictPropertyTypeName = dictProperties[name];
|
|
1340
|
+
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType) {
|
|
1341
|
+
message('odata-anno-type', msg.location,
|
|
1342
|
+
{ name, anno: termName, type: dTypeName });
|
|
1237
1343
|
}
|
|
1238
1344
|
}
|
|
1239
1345
|
|
|
1240
|
-
let newPropertyValue = new Edm.PropertyValue(v,
|
|
1346
|
+
let newPropertyValue = new Edm.PropertyValue(v, name);
|
|
1241
1347
|
// property value can be anything, so delegate handling to handleValue
|
|
1242
|
-
handleValue(obj[
|
|
1348
|
+
handleValue(obj[name], newPropertyValue, termName, dictPropertyTypeName, msg);
|
|
1243
1349
|
newRecord.append(newPropertyValue);
|
|
1244
1350
|
}
|
|
1245
1351
|
|
|
1246
|
-
|
|
1352
|
+
msg.stack.pop();
|
|
1247
1353
|
}
|
|
1248
1354
|
|
|
1249
1355
|
return newRecord;
|
|
@@ -1252,56 +1358,59 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1252
1358
|
|
|
1253
1359
|
// annoValue is an array
|
|
1254
1360
|
// dTypeName : Collection(...) according to dictionary
|
|
1255
|
-
function generateCollection(annoValue, termName, dTypeName,
|
|
1256
|
-
|
|
1361
|
+
function generateCollection(annoValue, termName, dTypeName, msg) {
|
|
1362
|
+
const newCollection = new Edm.Collection(v);
|
|
1257
1363
|
|
|
1258
1364
|
let innerTypeName = null;
|
|
1259
1365
|
if (dTypeName) {
|
|
1260
|
-
|
|
1366
|
+
const match = dTypeName.match(/^Collection\((.+)\)/);
|
|
1261
1367
|
if (match) {
|
|
1262
1368
|
innerTypeName = match[1];
|
|
1263
1369
|
}
|
|
1264
1370
|
else {
|
|
1265
|
-
message(
|
|
1371
|
+
message('odata-anno-value', msg.location,
|
|
1372
|
+
{ anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'struct' });
|
|
1266
1373
|
}
|
|
1267
1374
|
}
|
|
1268
1375
|
|
|
1269
1376
|
let index = 0;
|
|
1270
1377
|
for (const value of annoValue) {
|
|
1271
|
-
|
|
1378
|
+
msg.stack.push('[' + index + ']');
|
|
1272
1379
|
index++
|
|
1273
1380
|
|
|
1274
1381
|
// for dealing with the single array entries we unfortunately cannot call handleValue(),
|
|
1275
1382
|
// as the values inside an array are represented differently from the values
|
|
1276
1383
|
// in a record or term
|
|
1277
1384
|
if (Array.isArray(value)) {
|
|
1278
|
-
message(
|
|
1385
|
+
message('odata-anno-value', msg.location,
|
|
1386
|
+
{ anno: msg.anno(), '#': 'nestedcollection' });
|
|
1279
1387
|
}
|
|
1280
1388
|
else if (value && typeof value === 'object') {
|
|
1281
1389
|
if (value['=']) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1390
|
+
const res = handleExpression(value['='], innerTypeName);
|
|
1391
|
+
const newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
|
|
1284
1392
|
newPropertyPath.setJSON( { [res.name] : res.value } );
|
|
1285
1393
|
newCollection.append(newPropertyPath);
|
|
1286
1394
|
}
|
|
1287
1395
|
else if (value['#']) {
|
|
1288
|
-
message(
|
|
1396
|
+
message('odata-anno-value', msg.location,
|
|
1397
|
+
{ anno: msg.anno(), '#': 'enumincollection' });
|
|
1289
1398
|
}
|
|
1290
1399
|
else if(value['$edmJson']) {
|
|
1291
|
-
newCollection.append(handleEdmJson(value['$edmJson'],
|
|
1400
|
+
newCollection.append(handleEdmJson(value['$edmJson'], msg));
|
|
1292
1401
|
}
|
|
1293
1402
|
else {
|
|
1294
|
-
newCollection.append(generateRecord(value, termName, innerTypeName,
|
|
1403
|
+
newCollection.append(generateRecord(value, termName, innerTypeName, msg));
|
|
1295
1404
|
}
|
|
1296
1405
|
}
|
|
1297
1406
|
else {
|
|
1298
|
-
|
|
1299
|
-
|
|
1407
|
+
const res = handleSimpleValue(value, innerTypeName, msg);
|
|
1408
|
+
const newThing = (value === null) ?new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
|
|
1300
1409
|
newThing.setJSON( { [res.jsonName] : res.value });
|
|
1301
1410
|
newCollection.append(newThing);
|
|
1302
1411
|
}
|
|
1303
1412
|
|
|
1304
|
-
|
|
1413
|
+
msg.stack.pop();
|
|
1305
1414
|
}
|
|
1306
1415
|
|
|
1307
1416
|
return newCollection;
|
|
@@ -1315,7 +1424,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1315
1424
|
// See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
|
|
1316
1425
|
// and test3/ODataBackends/DynExpr
|
|
1317
1426
|
|
|
1318
|
-
function handleEdmJson(obj,
|
|
1427
|
+
function handleEdmJson(obj, msg, exprDef=undefined) {
|
|
1319
1428
|
|
|
1320
1429
|
let edmNode = undefined;
|
|
1321
1430
|
if(obj === undefined || obj === null)
|
|
@@ -1324,7 +1433,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1324
1433
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
1325
1434
|
|
|
1326
1435
|
if(dynExprs.length > 1) {
|
|
1327
|
-
message(
|
|
1436
|
+
message('odata-anno-value', msg.location,
|
|
1437
|
+
{ anno: msg.anno(), rawvalues: dynExprs, '#': 'multexpr' });
|
|
1328
1438
|
return edmNode;
|
|
1329
1439
|
}
|
|
1330
1440
|
|
|
@@ -1340,7 +1450,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1340
1450
|
if(Array.isArray(obj)) {
|
|
1341
1451
|
// EDM JSON doesn't mention annotations on collections
|
|
1342
1452
|
edmNode = new Edm.Collection(v);
|
|
1343
|
-
obj.forEach(o => edmNode.append(handleEdmJson(o,
|
|
1453
|
+
obj.forEach(o => edmNode.append(handleEdmJson(o, msg)));
|
|
1344
1454
|
}
|
|
1345
1455
|
else if(typeof obj === 'object') {
|
|
1346
1456
|
edmNode = new Edm.Record(v);
|
|
@@ -1354,11 +1464,11 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1354
1464
|
let child = undefined;
|
|
1355
1465
|
const [ head, tail ] = k.split('@');
|
|
1356
1466
|
if(tail) {
|
|
1357
|
-
child = handleTerm(tail, val,
|
|
1467
|
+
child = handleTerm(tail, val, msg);
|
|
1358
1468
|
}
|
|
1359
1469
|
else {
|
|
1360
1470
|
child = new Edm.PropertyValue(v, head);
|
|
1361
|
-
child.append(handleEdmJson(val,
|
|
1471
|
+
child.append(handleEdmJson(val, msg));
|
|
1362
1472
|
}
|
|
1363
1473
|
if(child) {
|
|
1364
1474
|
if(tail && head.length) {
|
|
@@ -1397,7 +1507,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1397
1507
|
forEach(obj, (name, val) => {
|
|
1398
1508
|
if(exprDef) {
|
|
1399
1509
|
if(exprDef.anno && name[0] === '@') {
|
|
1400
|
-
edmNode.append(handleTerm(name.slice(1), val,
|
|
1510
|
+
edmNode.append(handleTerm(name.slice(1), val, msg));
|
|
1401
1511
|
}
|
|
1402
1512
|
else if (exprDef.attr && exprDef.attr.includes(name)) {
|
|
1403
1513
|
if (name[0] === '$') {
|
|
@@ -1412,11 +1522,11 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1412
1522
|
else if(exprDef.children) {
|
|
1413
1523
|
if (Array.isArray(val)) {
|
|
1414
1524
|
val.forEach(a => {
|
|
1415
|
-
edmNode.append(handleEdmJson(a,
|
|
1525
|
+
edmNode.append(handleEdmJson(a, msg, exprDef));
|
|
1416
1526
|
});
|
|
1417
1527
|
}
|
|
1418
1528
|
else {
|
|
1419
|
-
edmNode.append(handleEdmJson(val,
|
|
1529
|
+
edmNode.append(handleEdmJson(val, msg, exprDef));
|
|
1420
1530
|
}
|
|
1421
1531
|
}
|
|
1422
1532
|
}
|
|
@@ -1444,6 +1554,130 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1444
1554
|
}
|
|
1445
1555
|
}
|
|
1446
1556
|
|
|
1557
|
+
/*
|
|
1558
|
+
translate vocabulary definitions into an adhoc dictionary
|
|
1559
|
+
with the same structure as the gobal jsonDictionary that
|
|
1560
|
+
contains all official term and type definitions.
|
|
1561
|
+
|
|
1562
|
+
Return the dictionary and an array of schemas to which
|
|
1563
|
+
the vocabulary definitions belong
|
|
1564
|
+
*/
|
|
1565
|
+
function createAdhocDictionary() {
|
|
1566
|
+
const allKnownVocabularies = [];
|
|
1567
|
+
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
1568
|
+
|
|
1569
|
+
if(!isBetaEnabled(options, 'odataTerms'))
|
|
1570
|
+
return [ dict, allKnownVocabularies ];
|
|
1571
|
+
|
|
1572
|
+
for(let termName in csnVocabularies) {
|
|
1573
|
+
let dictDef = oDataDictionary.terms[termName];
|
|
1574
|
+
if(dictDef) {
|
|
1575
|
+
message('odata-anno-dict', ['vocabularies', termName],
|
|
1576
|
+
{ anno: termName, '#': 'redefinition' } );
|
|
1577
|
+
}
|
|
1578
|
+
else if(!dictDef) {
|
|
1579
|
+
const annoDef = csnVocabularies[termName];
|
|
1580
|
+
if(annoDef?.$mySchemaName) {
|
|
1581
|
+
if(!allKnownVocabularies.includes[annoDef.$mySchemaName])
|
|
1582
|
+
allKnownVocabularies.push(annoDef.$mySchemaName);
|
|
1583
|
+
const myServiceRoot = options.whatsMyServiceRootName(annoDef.$mySchemaName);
|
|
1584
|
+
if(!dict.xrefs[myServiceRoot])
|
|
1585
|
+
dict.xrefs[myServiceRoot] = { $myServiceRoot: myServiceRoot, used: false };
|
|
1586
|
+
const edmType = new Edm.TypeBase(options.v, {}, annoDef);
|
|
1587
|
+
dictDef = edmType._edmAttributes;
|
|
1588
|
+
dictDef['$myServiceRoot'] = myServiceRoot;
|
|
1589
|
+
let val = annoDef['@odata.term.AppliesTo'];
|
|
1590
|
+
if(val != null)
|
|
1591
|
+
dictDef.AppliesTo = Array.isArray(val) ? val.map(v=>v['='] || v) : [val['='] || val];
|
|
1592
|
+
val = annoDef['@odata.term.Experimental'];
|
|
1593
|
+
if(val != null)
|
|
1594
|
+
dictDef['$experimental'] = !!val;
|
|
1595
|
+
val = annoDef['@odata.term.Deprecated'];
|
|
1596
|
+
if(val != null) {
|
|
1597
|
+
dictDef['$deprecated'] = !!val;
|
|
1598
|
+
if(typeof val === 'string')
|
|
1599
|
+
dictDef['$deprecationText'] = val;
|
|
1600
|
+
}
|
|
1601
|
+
dict.terms[termName] = dictDef;
|
|
1602
|
+
|
|
1603
|
+
if((annoDef.items?.enum || annoDef.enum) && isBuiltinType(annoDef.items?.type || annoDef.type)) {
|
|
1604
|
+
const enumType = createTypeDefWithAllowedValues(annoDef, annoDef, dictDef.Type, ['vocabularies', termName ]);
|
|
1605
|
+
const tName = termName + '_$$$EnumType$$$$';
|
|
1606
|
+
dict.types[tName] = enumType;
|
|
1607
|
+
dictDef.Type = tName;
|
|
1608
|
+
}
|
|
1609
|
+
else
|
|
1610
|
+
addTypesToDictionary(annoDef);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return [ dict, allKnownVocabularies ];
|
|
1615
|
+
|
|
1616
|
+
function addTypesToDictionary(node) {
|
|
1617
|
+
const typeName = node.items?.type || node.type;
|
|
1618
|
+
// for type reuse in x-ref mode, the definition has already been
|
|
1619
|
+
// replaced by a reference object in edmPreprocessor.
|
|
1620
|
+
// Fall back to original type (the one of the other service).
|
|
1621
|
+
const typeDef = reqDefs.definitions[typeName] || reqDefs.definitions[typeName.replace(serviceName + '.', '')];
|
|
1622
|
+
if(typeDef) {
|
|
1623
|
+
let dictDef = { };
|
|
1624
|
+
const elements = typeDef.items?.elements || typeDef.elements;
|
|
1625
|
+
if(elements) {
|
|
1626
|
+
// complex type
|
|
1627
|
+
dictDef['$kind'] = 'ComplexType';
|
|
1628
|
+
dictDef.Properties = new Object(null);
|
|
1629
|
+
|
|
1630
|
+
for(let en in elements) {
|
|
1631
|
+
const elt = elements[en];
|
|
1632
|
+
if(isEdmPropertyRendered(elt, options)) {
|
|
1633
|
+
const edmType = new Edm.TypeBase(options.v, {}, elt);
|
|
1634
|
+
dictDef.Properties[en] = edmType._edmAttributes[edmType._typeName];
|
|
1635
|
+
addTypesToDictionary(elt);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
else {
|
|
1640
|
+
// type definition
|
|
1641
|
+
const edmType = new Edm.TypeBase(options.v, {}, typeDef);
|
|
1642
|
+
dictDef = createTypeDefWithAllowedValues(node, typeDef, edmType._edmAttributes[edmType._typeName], ['definitions', typeName ]);
|
|
1643
|
+
if(!typeDef.enum)
|
|
1644
|
+
delete dictDef.$Allowed;
|
|
1645
|
+
}
|
|
1646
|
+
dict.types[typeName] = dictDef;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function createTypeDefWithAllowedValues(node, typeDef, UnderlyingType, path) {
|
|
1651
|
+
const dictTypeDef = { $kind: 'TypeDefinition', UnderlyingType, $Allowed: { Values: {}, Symbols: {} } };
|
|
1652
|
+
// create an artificial type that holds the $Allowed enum symbols and values
|
|
1653
|
+
if(node.items && typeDef.enum || typeDef.items?.enum)
|
|
1654
|
+
message('odata-anno-dict-enum', [ 'vocabularies', node.name ],
|
|
1655
|
+
{ name: node.name,
|
|
1656
|
+
type: typeDef.name,
|
|
1657
|
+
'#': node.name === typeDef.name ? 'std' : 'type'
|
|
1658
|
+
});
|
|
1659
|
+
const enumDic = (typeDef.items?.enum || typeDef.enum);
|
|
1660
|
+
const baseType = (typeDef.items || typeDef).type;
|
|
1661
|
+
if(baseType !== 'cds.String' && Object.values(enumDic).some(v => !v.val)) {
|
|
1662
|
+
message('odata-anno-dict-enum', path,
|
|
1663
|
+
{ name: node.name, type: baseType, '#': 'value' });
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
for(const symbol in enumDic) {
|
|
1667
|
+
const valDic = { '#SymbolicName': symbol };
|
|
1668
|
+
const enumDef = enumDic[symbol];
|
|
1669
|
+
// <Null/> values can't be rendered
|
|
1670
|
+
if(enumDef.val === undefined)
|
|
1671
|
+
valDic.Value = symbol;
|
|
1672
|
+
else if(valDic.val !== null)
|
|
1673
|
+
valDic.Value = enumDef.val;
|
|
1674
|
+
dictTypeDef.$Allowed.Symbols[symbol] = dictTypeDef.$Allowed.Values[symbol] = valDic;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return dictTypeDef;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1447
1681
|
function initEdmJson() {
|
|
1448
1682
|
// Static dynamic expression dictionary, loaded with Edm creators
|
|
1449
1683
|
const dynamicExpressions = {
|
|
@@ -1507,15 +1741,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1507
1741
|
//-------------------------------------------------------------------------------------------------
|
|
1508
1742
|
//-------------------------------------------------------------------------------------------------
|
|
1509
1743
|
|
|
1510
|
-
|
|
1511
|
-
// accepts those strings that start with a known vocabulary name
|
|
1512
|
-
function filterKnownVocabularies(name) {
|
|
1513
|
-
var match = name.match(/^(@)(\w+)/);
|
|
1514
|
-
if (match == null) return false;
|
|
1515
|
-
return knownVocabularies.includes(match[2]); // second match group
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
// resolve "derived types"
|
|
1744
|
+
// resolve "derived types"
|
|
1519
1745
|
// -> if dTypeName is a TypeDefinition, replace by
|
|
1520
1746
|
// underlying type
|
|
1521
1747
|
function resolveTypeDefinition(dTypeName) {
|
|
@@ -1526,6 +1752,14 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1526
1752
|
return dTypeName;
|
|
1527
1753
|
}
|
|
1528
1754
|
|
|
1755
|
+
function stripCollection(typeName) {
|
|
1756
|
+
const match = typeName.match(/^Collection\((.+)\)/);
|
|
1757
|
+
if (match) {
|
|
1758
|
+
typeName = match[1];
|
|
1759
|
+
}
|
|
1760
|
+
return typeName;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1529
1763
|
function isPrimitiveType(typeName) {
|
|
1530
1764
|
return typeName.split('.')[0] === 'Edm';
|
|
1531
1765
|
}
|
|
@@ -1535,17 +1769,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1535
1769
|
}
|
|
1536
1770
|
|
|
1537
1771
|
function isEnumType(dTypeName) {
|
|
1538
|
-
|
|
1772
|
+
const type = getDictType(dTypeName);
|
|
1539
1773
|
return type && type['$kind'] === 'EnumType';
|
|
1540
1774
|
}
|
|
1541
1775
|
|
|
1542
1776
|
function isComplexType(dTypeName) {
|
|
1543
|
-
|
|
1777
|
+
const type = getDictType(dTypeName);
|
|
1544
1778
|
return dTypeName === 'Edm.ComplexType' || type && type['$kind'] === 'ComplexType';
|
|
1545
1779
|
}
|
|
1546
1780
|
|
|
1547
1781
|
function isAbstractType(dTypeName) {
|
|
1548
|
-
|
|
1782
|
+
const type = getDictType(dTypeName);
|
|
1549
1783
|
return type && type['Abstract'] === 'true';
|
|
1550
1784
|
}
|
|
1551
1785
|
|