@sap/cds-compiler 6.1.0 → 6.3.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/cdsc.js +17 -6
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +1 -1
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +41 -10
  10. package/lib/base/messages.js +13 -6
  11. package/lib/base/model.js +1 -1
  12. package/lib/base/optionProcessorHelper.js +7 -2
  13. package/lib/checks/assocOutsideService.js +17 -30
  14. package/lib/checks/checkForTypes.js +0 -18
  15. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  16. package/lib/checks/featureFlags.js +4 -1
  17. package/lib/checks/onConditions.js +2 -2
  18. package/lib/checks/queryNoDbArtifacts.js +16 -15
  19. package/lib/checks/types.js +1 -1
  20. package/lib/checks/utils.js +30 -6
  21. package/lib/checks/validator.js +4 -5
  22. package/lib/compiler/assert-consistency.js +3 -1
  23. package/lib/compiler/base.js +1 -1
  24. package/lib/compiler/builtins.js +1 -1
  25. package/lib/compiler/checks.js +85 -39
  26. package/lib/compiler/define.js +24 -5
  27. package/lib/compiler/extend.js +1 -1
  28. package/lib/compiler/finalize-parse-cdl.js +9 -1
  29. package/lib/compiler/generate.js +4 -4
  30. package/lib/compiler/index.js +88 -6
  31. package/lib/compiler/lsp-api.js +2 -0
  32. package/lib/compiler/populate.js +8 -8
  33. package/lib/compiler/propagator.js +1 -1
  34. package/lib/compiler/resolve.js +22 -21
  35. package/lib/compiler/shared.js +6 -6
  36. package/lib/compiler/tweak-assocs.js +53 -31
  37. package/lib/compiler/utils.js +9 -16
  38. package/lib/compiler/xpr-rewrite.js +2 -2
  39. package/lib/gen/BaseParser.js +35 -29
  40. package/lib/gen/CdlGrammar.checksum +1 -1
  41. package/lib/gen/CdlParser.js +1424 -1430
  42. package/lib/gen/Dictionary.json +1 -2
  43. package/lib/gen/cdlKeywords.json +26 -0
  44. package/lib/inspect/inspectPropagation.js +1 -1
  45. package/lib/json/from-csn.js +2 -2
  46. package/lib/json/to-csn.js +1 -1
  47. package/lib/language/multiLineStringParser.js +1 -1
  48. package/lib/model/cloneCsn.js +1 -0
  49. package/lib/model/csnRefs.js +9 -4
  50. package/lib/model/csnUtils.js +67 -2
  51. package/lib/optionProcessor.js +9 -9
  52. package/lib/parsers/AstBuildingParser.js +28 -26
  53. package/lib/parsers/identifiers.js +2 -30
  54. package/lib/render/toCdl.js +73 -13
  55. package/lib/render/toSql.js +127 -108
  56. package/lib/render/utils/common.js +4 -2
  57. package/lib/render/utils/sql.js +67 -0
  58. package/lib/transform/addTenantFields.js +4 -4
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/associations.js +37 -1
  61. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  62. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/expansion.js +37 -36
  65. package/lib/transform/db/killAnnotations.js +1 -0
  66. package/lib/transform/db/processSqlServices.js +20 -2
  67. package/lib/transform/draft/db.js +20 -20
  68. package/lib/transform/draft/odata.js +38 -40
  69. package/lib/transform/effective/associations.js +1 -1
  70. package/lib/transform/effective/flattening.js +40 -47
  71. package/lib/transform/effective/main.js +6 -4
  72. package/lib/transform/forOdata.js +201 -92
  73. package/lib/transform/forRelationalDB.js +151 -142
  74. package/lib/transform/localized.js +116 -109
  75. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  76. package/lib/transform/odata/createForeignKeys.js +73 -70
  77. package/lib/transform/odata/flattening.js +216 -200
  78. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  79. package/lib/transform/odata/toFinalBaseType.js +40 -39
  80. package/lib/transform/odata/typesExposure.js +151 -133
  81. package/lib/transform/odata/utils.js +7 -6
  82. package/lib/transform/parseExpr.js +165 -162
  83. package/lib/transform/transformUtils.js +184 -551
  84. package/lib/transform/translateAssocsToJoins.js +511 -596
  85. package/lib/transform/tupleExpansion.js +495 -0
  86. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  87. package/lib/utils/moduleResolve.js +1 -1
  88. package/package.json +2 -2
  89. package/lib/base/cleanSymbols.js +0 -17
  90. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -2,16 +2,17 @@
2
2
 
3
3
  const { isBetaEnabled } = require('../base/model');
4
4
  const transformUtils = require('./transformUtils');
5
- const { forEachDefinition,
6
- forEachMemberRecursively,
7
- applyTransformationsOnNonDictionary,
8
- getArtifactDatabaseNameOf,
9
- getElementDatabaseNameOf,
10
- getServiceNames,
11
- forEachGeneric,
12
- cardinality2str,
13
- getUtils
14
- } = require('../model/csnUtils');
5
+ const {
6
+ forEachDefinition,
7
+ forEachMemberRecursively,
8
+ applyTransformationsOnNonDictionary,
9
+ getArtifactDatabaseNameOf,
10
+ getElementDatabaseNameOf,
11
+ getServiceNames,
12
+ forEachGeneric,
13
+ cardinality2str,
14
+ getUtils,
15
+ } = require('../model/csnUtils');
15
16
  const { checkCSNVersion } = require('../json/csnVersion');
16
17
  const validate = require('../checks/validator');
17
18
  const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
@@ -20,7 +21,7 @@ const { timetrace } = require('../utils/timetrace');
20
21
  const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
21
22
  const flattening = require('./odata/flattening');
22
23
  const createForeignKeyElements = require('./odata/createForeignKeys');
23
- const associations = require('./db/associations')
24
+ const associations = require('./db/associations');
24
25
  const expansion = require('./db/expansion');
25
26
  const generateDrafts = require('./draft/odata');
26
27
 
@@ -29,6 +30,7 @@ const { addLocalizationViews } = require('./localized');
29
30
  const { cloneFullCsn } = require('../model/cloneCsn');
30
31
  const { csnRefs } = require('../model/csnRefs');
31
32
  const replaceForeignKeyRefsInExpressionAnnotations = require('./odata/foreignKeyRefsInXprAnnos');
33
+ const { isAnnotationExpression, xprInAnnoProperties } = require('../base/builtins');
32
34
 
33
35
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
34
36
  // The result should be suitable for consumption by EDMX processors (annotations and metadata)
@@ -79,7 +81,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
79
81
  const csn = cloneFullCsn(inputModel, options);
80
82
  messageFunctions.setModel(csn);
81
83
 
82
- const { message, error, warning, info, throwWithAnyError } = messageFunctions;
84
+ const {
85
+ message, error, warning, info, throwWithAnyError,
86
+ } = messageFunctions;
83
87
  throwWithAnyError();
84
88
 
85
89
  // the new transformer works only with new CSN
@@ -115,9 +119,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
115
119
  // @ts-ignore
116
120
  const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
117
121
  // @ts-ignore
118
- const isExternalServiceMember = (art, name) => {
119
- return !!(externalServices.includes(getServiceName(name)) || (art && art['@cds.external']))
120
- }
122
+ const isExternalServiceMember = (art, name) => !!(externalServices.includes(getServiceName(name)) || (art && art['@cds.external']));
121
123
 
122
124
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
123
125
  enrichUniversalCsn(csn, options);
@@ -140,13 +142,25 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
140
142
  // replace all type refs to builtin types with direct type
141
143
  transformUtils.rewriteBuiltinTypeRef(csn);
142
144
 
143
- // Rewrite paths in annotations only if beta modes are set
145
+ // Rewrite paths in annotations only if beta modes are set
144
146
 
145
147
  options.enrichAnnotations = true;
146
148
  const cleanup = validate.forOdata(csn, {
147
- message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef,
148
- options, csnUtils, services, isExternalServiceMember, recurseElements,
149
- checkMultipleAssignments, csn,
149
+ message,
150
+ error,
151
+ warning,
152
+ info,
153
+ inspectRef,
154
+ effectiveType,
155
+ getFinalTypeInfo,
156
+ artifactRef,
157
+ options,
158
+ csnUtils,
159
+ services,
160
+ isExternalServiceMember,
161
+ recurseElements,
162
+ checkMultipleAssignments,
163
+ csn,
150
164
  });
151
165
 
152
166
 
@@ -154,19 +168,22 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
154
168
  throwWithAnyError();
155
169
 
156
170
  // TODO: Refactor out the following logic
171
+ const hasProjection = new Set();
157
172
  forEachDefinition(csn, [
158
173
  (def) => {
159
174
  // Convert a projection into a query for internal processing will be re-converted
160
175
  // at the end of the OData processing
161
176
  // TODO: handle artifact.projection instead of artifact.query correctly in future V2
162
177
  if (def.kind === 'entity' && def.projection) {
178
+ hasProjection.add(def);
163
179
  def.query = { SELECT: def.projection };
180
+ delete def.projection;
164
181
  dropDefinitionCache(def);
165
182
  initDefinition(def);
166
183
  }
167
- }],
168
- { skipArtifact: isExternalServiceMember }
169
- );
184
+ },
185
+ ],
186
+ { skipArtifact: isExternalServiceMember });
170
187
 
171
188
  // All type refs must be resolved, including external APIs.
172
189
  // OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
@@ -180,13 +197,13 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
180
197
  // and expand these structured elements. This tuple expansion allows all other
181
198
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
182
199
  // If errors are detected, throwWithAnyError() will return from further processing
183
- expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
200
+ expandStructsInExpression({ skipArtifact: isExternalServiceMember, drillRef: true });
184
201
 
185
202
  // do expansion before Fk creation because of messages reporting
186
203
  if (!structuredOData) {
187
204
  expansion.expandStructureReferences(csn, options, '_',
188
- { error, info, throwWithAnyError }, csnUtils,
189
- { skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
205
+ { error, info, throwWithAnyError }, csnUtils,
206
+ { skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
190
207
  }
191
208
 
192
209
  createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
@@ -201,34 +218,34 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
201
218
  const resolved = new WeakMap();
202
219
  const { inspectRef, effectiveType } = csnRefs(csn);
203
220
  const { getFinalTypeInfo } = getUtils(csn);
204
- const { adaptRefs, transformer: refFlattener } =
205
- flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
221
+ const { adaptRefs, transformer: refFlattener }
222
+ = flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
206
223
 
207
224
  const allMgdAssocDefs = flattening.allInOneFlattening(csn, refFlattener, adaptRefs,
208
- inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options);
225
+ inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options);
209
226
  flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs,
210
- inspectRef, effectiveType, csnUtils, error, options,
211
- { //skip: ['action', 'aspect', 'event', 'function', 'type'],
212
- skipArtifact: isExternalServiceMember,
213
- });
227
+ inspectRef, effectiveType, csnUtils, error, options,
228
+ { // skip: ['action', 'aspect', 'event', 'function', 'type'],
229
+ skipArtifact: isExternalServiceMember,
230
+ });
214
231
  flattening.replaceManagedAssocsAsKeys(allMgdAssocDefs, csnUtils);
215
232
 
216
233
  // replace structured with flat dictionaries that contain
217
234
  // rewritten path expressions
218
235
  forEachDefinition(csn, (def) => {
219
- ['elements', 'params'].forEach(dictName => {
220
- if(def[`$flat${dictName}`])
221
- def[dictName] = def[`$flat${dictName}`];
222
- })
223
- if(def.$flatAnnotations) {
224
- Object.entries(def.$flatAnnotations).forEach(([an, av]) => {
236
+ [ 'elements', 'params' ].forEach((dictName) => {
237
+ if (def[`$flat${ dictName }`])
238
+ def[dictName] = def[`$flat${ dictName }`];
239
+ });
240
+ if (def.$flatAnnotations) {
241
+ Object.entries(def.$flatAnnotations).forEach(([ an, av ]) => {
225
242
  def[an] = av;
226
- })
243
+ });
227
244
  }
228
- if(def.actions) {
245
+ if (def.actions) {
229
246
  Object.values(def.actions).forEach((action) => {
230
- if(action.$flatAnnotations) {
231
- Object.entries(action.$flatAnnotations).forEach(([an, av]) => {
247
+ if (action.$flatAnnotations) {
248
+ Object.entries(action.$flatAnnotations).forEach(([ an, av ]) => {
232
249
  action[an] = av;
233
250
  });
234
251
  }
@@ -242,9 +259,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
242
259
  // Allow using managed associations as steps in on-conditions to access their fks
243
260
  // To be done after handleManagedAssociationsAndCreateForeignKeys,
244
261
  // since then the foreign keys of the managed assocs are part of the elements
245
- if(!structuredOData) {
246
- forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
247
- }
262
+ if (!structuredOData)
263
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, options, csnUtils, '_'));
264
+
248
265
 
249
266
  // structure flattener reports errors, further processing is not safe -> throw exception in case of errors
250
267
  throwWithAnyError();
@@ -269,7 +286,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
269
286
  // - Perform checks for exposed non-abstract entities and views - check media type and key-ness
270
287
 
271
288
  // Deal with all kind of annotations manipulations here
272
- const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
289
+ const skipPersNameKinds = {
290
+ service: 1, context: 1, namespace: 1, annotation: 1, action: 1, function: 1,
291
+ };
273
292
  forEachDefinition(csn, (def, defName) => {
274
293
  // Resolve annotation shorthands for entities, types, annotations, ...
275
294
  renameShorthandAnnotations(def);
@@ -277,7 +296,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
277
296
  // Annotate artifacts with their DB names if requested.
278
297
  // Skip artifacts that have no DB equivalent anyway
279
298
  if (options.sqlMapping && !(def.kind in skipPersNameKinds))
280
- // hana to allow naming mode "hdbcds"
299
+ // hana to allow naming mode "hdbcds"
281
300
  def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana');
282
301
 
283
302
  forEachMemberRecursively(def, (member, memberName, propertyName) => {
@@ -289,10 +308,12 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
289
308
  !(propertyName === 'enum' || propertyName === 'returns') &&
290
309
  (!member.virtual || def.query)) {
291
310
  // If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
292
- member['@cds.persistence.name'] = getElementDatabaseNameOf((!member['@odata.foreignKey4'] && member.$defPath?.slice(1).join('.'))
293
- || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
311
+ member['@cds.persistence.name'] = getElementDatabaseNameOf((!member['@odata.foreignKey4'] && member.$defPath?.slice(1).join('.')) ||
312
+ memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
294
313
  }
295
314
 
315
+ processDynamicFieldControlAnnotations(member);
316
+
296
317
  // Mark fields with @odata.on.insert/update as @Core.Computed
297
318
  annotateCoreComputed(member);
298
319
 
@@ -316,18 +337,19 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
316
337
  // to the foreign keys is done very late in edmPreprocessor.initializeAssociation()
317
338
  addCommonValueListviaAssociation(member, memberName);
318
339
  }
319
- }, ['definitions', defName]);
340
+ }, [ 'definitions', defName ]);
320
341
 
321
342
  // Convert a query back into a projection for CSN compliance as
322
343
  // the very last conversion step of the OData transformation
323
- if (def.kind === 'entity' && def.query && def.projection) {
344
+ if (def.kind === 'entity' && hasProjection.has(def)) {
345
+ def.projection = def.query.SELECT;
324
346
  delete def.query;
325
347
  }
326
- }, { skipArtifact: isExternalServiceMember })
348
+ }, { skipArtifact: isExternalServiceMember });
327
349
 
328
- if(isBetaEnabled(options, 'odataTerms')) {
350
+ if (isBetaEnabled(options, 'odataTerms'))
329
351
  forEachGeneric(csn, 'vocabularies', renameShorthandAnnotations);
330
- }
352
+
331
353
 
332
354
  cleanup();
333
355
  // Throw exception in case of errors
@@ -338,14 +360,101 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
338
360
  //--------------------------------------------------------------------
339
361
  // HELPER SECTION STARTS HERE
340
362
 
363
+ // Transform @readonly/@mandatory/@disabled into @Common.FieldControl annotation
364
+ // with a when/then/else expression consisting of the input from the annotations.
365
+ function processDynamicFieldControlAnnotations(node) {
366
+ if (node['@Common.FieldControl'])
367
+ return;
368
+ // TODO (SO): factor this out into a constant so we don't create a fresh array all the time?
369
+ if ([ '@readonly', '@mandatory', '@disabled' ].some(key => typeof node[key] === 'boolean'))
370
+ return;
371
+
372
+
373
+ const definedAnnotations = [ '@disabled', '@readonly', '@mandatory' ]
374
+ .filter(key => node[key] && isAnnotationExpression(node[key]));
375
+
376
+ if (definedAnnotations.length === 0)
377
+ return;
378
+
379
+ const values = {
380
+ '@disabled': { val: 0 },
381
+ '@readonly': { val: 1 },
382
+ '@mandatory': { val: 7 },
383
+ };
384
+
385
+ const fieldControl = {
386
+ '=': true,
387
+ xpr: createFieldControlExpression(definedAnnotations),
388
+ };
389
+
390
+ setAnnotation(node, '@Common.FieldControl', fieldControl);
391
+
392
+ function createFieldControlExpression(annotations) {
393
+ let nestedExpression = null;
394
+
395
+ for (let i = annotations.length - 1; i >= 0; i--) {
396
+ const annotation = annotations[i];
397
+ const xprInAnnoValue = getXprFromAnno(node[annotation]);
398
+ const annotationVal = values[annotation];
399
+
400
+ // Build the current annotation's expression
401
+ const currentExpression = [
402
+ 'case',
403
+ 'when',
404
+ ...(Array.isArray(xprInAnnoValue) ? xprInAnnoValue : [ xprInAnnoValue ]),
405
+ 'then',
406
+ annotationVal,
407
+ 'else',
408
+ // Use the previous nested expression or default value. Note that annotations
409
+ // are looped backwards
410
+ nestedExpression ? { xpr: nestedExpression } : { val: 3 },
411
+ 'end',
412
+ ];
413
+
414
+ // Update the nested expression
415
+ nestedExpression = currentExpression;
416
+ }
417
+ return nestedExpression;
418
+ }
419
+
420
+ function getXprFromAnno(anno) {
421
+ const xprProp = xprInAnnoProperties.find(prop => anno[prop] !== undefined);
422
+ const constructResult = {
423
+ ref: () => {
424
+ const result = { ref: anno.ref };
425
+ if (anno.cast)
426
+ result.cast = anno.cast;
427
+ return result;
428
+ },
429
+ xpr: () => anno.xpr,
430
+ list: () => ({ list: anno.list }),
431
+ literal: () => constructResult.val(),
432
+ val: () => {
433
+ const result = { val: anno.val };
434
+ if (anno.literal)
435
+ result.literal = anno.literal;
436
+ return result;
437
+ },
438
+ '#': () => ({ '#': anno['#'] }),
439
+ func: () => ({ func: anno.func }),
440
+ args: () => ({ args: anno.args }),
441
+ SELECT: () => ({ SELECT: anno.SELECT }),
442
+ SET: () => ({ SET: anno.SET }),
443
+ cast: () => constructResult.ref(),
444
+ };
445
+ return constructResult[xprProp]();
446
+ }
447
+ }
448
+
341
449
  // Mark elements that are annotated with @odata.on.insert/update with the annotation @Core.Computed.
342
450
  function annotateCoreComputed(node) {
343
451
  // If @Core.Computed is explicitly set, don't overwrite it!
344
- if (node['@Core.Computed'] !== undefined) return;
452
+ if (node['@Core.Computed'] !== undefined)
453
+ return;
345
454
 
346
455
  // For @odata.on.insert/update, also add @Core.Computed
347
456
  // @odata.on is deprecated, use @cds.on {update|insert} instead
348
- if(['@odata.on.insert', '@odata.on.update', '@cds.on.insert', '@cds.on.update'].some(a => node[a]))
457
+ if ([ '@odata.on.insert', '@odata.on.update', '@cds.on.insert', '@cds.on.update' ].some(a => node[a]))
349
458
  node['@Core.Computed'] = true;
350
459
  }
351
460
 
@@ -358,37 +467,36 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
358
467
  };
359
468
  const renameMappings = {
360
469
  '@ValueList.entity': { val: '@Common.ValueList', op: 'entity' },
361
- '@ValueList.type': { val: '@Common.ValueList', op: 'type' },
470
+ '@ValueList.type': { val: '@Common.ValueList', op: 'type' },
362
471
  '@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' },
363
472
  '@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' },
364
473
  '@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' },
365
- '@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' }
474
+ '@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' },
366
475
  };
367
476
 
368
477
  const setShortCuts = Object.keys(setMappings);
369
478
  const renameShortCuts = Object.keys(renameMappings);
370
479
 
371
480
  // Capabilities shortcuts have precedence over @readonly/@insertonly
372
- Object.keys(node).forEach( name => {
481
+ Object.keys(node).forEach( (name) => {
373
482
  if (!name.startsWith('@'))
374
483
  return;
375
484
  // Rename according to map above
376
485
  const renamePrefix = (name in renameMappings)
377
486
  ? name
378
- : renameShortCuts.find(p => name.startsWith(p + '.'));
379
- if(renamePrefix) {
487
+ : renameShortCuts.find(p => name.startsWith(`${ p }.`));
488
+ if (renamePrefix) {
380
489
  const mapping = renameMappings[renamePrefix];
381
- renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`));
490
+ renameAnnotation(node, name, name.replace(renamePrefix, `${ mapping.val }.${ mapping.op }`));
382
491
  }
383
492
  else {
384
493
  // The two mappings have no overlap, so no need to check for second map if first matched.
385
494
  // Rename according to map above
386
495
  const setPrefix = (name in setMappings)
387
496
  ? name
388
- : setShortCuts.find(p => name.startsWith(p + '.') || name.startsWith(p + '#'));
389
- if(setPrefix) {
497
+ : setShortCuts.find(p => name.startsWith(`${ p }.`) || name.startsWith(`${ p }#`));
498
+ if (setPrefix)
390
499
  setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
391
- }
392
500
  }
393
501
  });
394
502
 
@@ -396,14 +504,17 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
396
504
  // but '@Core.Computed' for everything else.
397
505
 
398
506
  // only if not both readonly/insertonly are true do the mapping
399
- if(!(node['@readonly'] && node['@insertonly'])) {
400
- if(node['@readonly']) {
507
+ if (!(node['@readonly'] && node['@insertonly'])) {
508
+ if (node['@readonly']) {
401
509
  const setRO = (qualifier) => {
402
510
  if (node.kind === 'entity' || node.kind === 'aspect') {
403
- setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
404
- setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false);
405
- setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
406
- } else {
511
+ const qualifierStr = qualifier ? `#${ qualifier }` : '';
512
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifierStr }.Deletable`, false);
513
+ setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifierStr }.Insertable`, false);
514
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifierStr }.Updatable`, false);
515
+ }
516
+ else if (!isAnnotationExpression(node['@readonly'])) {
517
+ // add @Core.Computed only for non-xpr values of @readonly
407
518
  setAnnotation(node, '@Core.Computed', true);
408
519
  }
409
520
  };
@@ -412,10 +523,11 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
412
523
  // @insertonly is effective on entities/queries only
413
524
  if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) {
414
525
  const setIO = (qualifier) => {
415
- setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
416
- setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifier ? '#' + qualifier : ''}.Readable`, false);
417
- setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
418
- }
526
+ const qualifierStr = qualifier ? `#${ qualifier }` : '';
527
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifierStr }.Deletable`, false);
528
+ setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifierStr }.Readable`, false);
529
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifierStr }.Updatable`, false);
530
+ };
419
531
  setIO(undefined);
420
532
  }
421
533
  }
@@ -425,10 +537,11 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
425
537
  setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
426
538
 
427
539
  // Only on element level
428
- if(node.kind == null) {
429
- if (node['@mandatory'] && !Object.entries(node).some(([k,v]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null)) {
540
+ if (node.kind == null) {
541
+ if (node['@mandatory'] && !isAnnotationExpression(node['@mandatory']) &&
542
+ !Object.entries(node).some(([ k, v ]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null))
430
543
  setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
431
- }
544
+
432
545
  if (node['@assert.range'] != null)
433
546
  setAssertRangeAnnotation(node);
434
547
  }
@@ -438,7 +551,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
438
551
  function setAssertRangeAnnotation(node) {
439
552
  const range = node['@assert.range'];
440
553
  if (!Array.isArray(range) || range.length !== 2)
441
- return; // TODO: Warning for wrong format?
554
+ return; // TODO: Warning for wrong format?
442
555
 
443
556
  const min = range[0];
444
557
  const max = range[1];
@@ -450,7 +563,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
450
563
  // via `@assert.range: [ _, _ ]`.
451
564
  // For `_`, minVal is an object and this function returns false, which is ok,
452
565
  // since we don't render the annotation for "infinite" values.
453
- const shouldSet = (val) => (typeof val !== 'object' && val !== undefined && val !== null);
566
+ const shouldSet = val => (typeof val !== 'object' && val !== undefined && val !== null);
454
567
 
455
568
  if (shouldSet(minVal)) {
456
569
  setAnnotation(node, '@Validation.Minimum', minVal);
@@ -462,7 +575,6 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
462
575
  if (max['='] !== undefined)
463
576
  setAnnotation(node, '@Validation.Maximum.@Validation.Exclusive', true);
464
577
  }
465
-
466
578
  }
467
579
 
468
580
  // If an association was modelled as not null, like so:
@@ -489,30 +601,30 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
489
601
  // Apply default type facets to each type definition and every member
490
602
  // But do not apply default string length (as in DB)
491
603
  function setDefaultTypeFacets(def) {
492
- addDefaultTypeFacets(def.items || def, null)
493
- forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, null));
494
- if(def.returns)
604
+ addDefaultTypeFacets(def.items || def, null);
605
+ forEachMemberRecursively(def, m => addDefaultTypeFacets(m.items || m, null));
606
+ if (def.returns)
495
607
  addDefaultTypeFacets(def.returns.items || def.returns, null);
496
608
  }
497
609
 
498
610
  // Handles on-conditions in unmanaged associations
499
611
  function processOnCond(def) {
500
612
  forEachMemberRecursively(def, (member) => {
501
- if (member.on && isAssocOrComposition(member)) {
613
+ if (member.on && isAssocOrComposition(member))
502
614
  removeLeadingDollarSelfInOnCondition(member);
503
- }
504
615
  });
505
616
 
506
617
  // removes leading $self in on-conditions's references
507
618
  function removeLeadingDollarSelfInOnCondition(assoc) {
508
- if (!assoc.on) return; // nothing to do
619
+ if (!assoc.on)
620
+ return; // nothing to do
509
621
  // TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
510
622
  applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
511
623
  ref: (node, prop, ref) => {
512
624
  // remove leading $self when at the beginning of a ref
513
625
  if (ref.length > 1 && ref[0] === '$self')
514
626
  node.ref.splice(0, 1);
515
- }
627
+ },
516
628
  });
517
629
  }
518
630
  }
@@ -527,9 +639,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
527
639
  if (isAssociation(member)) {
528
640
  const navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
529
641
  const targetDef = getCsnDef(member.target);
530
- if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno]) {
642
+ if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno])
531
643
  setAnnotation(member, vlAnno, { '=': memberName });
532
- }
533
644
  }
534
645
  }
535
646
 
@@ -538,6 +649,4 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
538
649
  const csnRefApi = csnRefs(csn);
539
650
  Object.assign(csnUtils, csnRefApi);
540
651
  }
541
-
542
-
543
652
  } // transform4odataWithCsn