@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +12 -12
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +9 -1
  7. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  8. package/lib/api/main.js +58 -59
  9. package/lib/api/options.js +4 -2
  10. package/lib/api/validate.js +2 -2
  11. package/lib/base/cleanSymbols.js +2 -3
  12. package/lib/base/dictionaries.js +6 -6
  13. package/lib/base/error.js +2 -2
  14. package/lib/base/keywords.js +6 -6
  15. package/lib/base/location.js +11 -12
  16. package/lib/base/message-registry.js +124 -28
  17. package/lib/base/messages.js +247 -179
  18. package/lib/base/model.js +14 -11
  19. package/lib/base/node-helpers.js +9 -10
  20. package/lib/base/optionProcessorHelper.js +138 -129
  21. package/lib/checks/actionsFunctions.js +5 -5
  22. package/lib/checks/annotationsOData.js +4 -4
  23. package/lib/checks/arrayOfs.js +1 -1
  24. package/lib/checks/cdsPersistence.js +1 -1
  25. package/lib/checks/checkForTypes.js +3 -3
  26. package/lib/checks/defaultValues.js +3 -3
  27. package/lib/checks/elements.js +7 -7
  28. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  29. package/lib/checks/foreignKeys.js +1 -1
  30. package/lib/checks/invalidTarget.js +4 -4
  31. package/lib/checks/managedInType.js +1 -1
  32. package/lib/checks/managedWithoutKeys.js +1 -1
  33. package/lib/checks/nonexpandableStructured.js +5 -3
  34. package/lib/checks/nullableKeys.js +1 -1
  35. package/lib/checks/onConditions.js +5 -6
  36. package/lib/checks/parameters.js +1 -1
  37. package/lib/checks/queryNoDbArtifacts.js +2 -2
  38. package/lib/checks/selectItems.js +4 -4
  39. package/lib/checks/sql-snippets.js +4 -4
  40. package/lib/checks/types.js +7 -7
  41. package/lib/checks/utils.js +4 -4
  42. package/lib/checks/validator.js +16 -13
  43. package/lib/compiler/.eslintrc.json +1 -1
  44. package/lib/compiler/assert-consistency.js +0 -1
  45. package/lib/compiler/builtins.js +1 -1
  46. package/lib/compiler/checks.js +73 -15
  47. package/lib/compiler/define.js +3 -7
  48. package/lib/compiler/extend.js +212 -32
  49. package/lib/compiler/finalize-parse-cdl.js +7 -2
  50. package/lib/compiler/index.js +17 -14
  51. package/lib/compiler/populate.js +2 -5
  52. package/lib/compiler/propagator.js +2 -0
  53. package/lib/compiler/shared.js +23 -12
  54. package/lib/compiler/tweak-assocs.js +5 -6
  55. package/lib/compiler/utils.js +6 -0
  56. package/lib/edm/annotations/genericTranslation.js +553 -319
  57. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  58. package/lib/edm/csn2edm.js +88 -75
  59. package/lib/edm/edm.js +17 -3
  60. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  61. package/lib/edm/edmPreprocessor.js +106 -76
  62. package/lib/edm/edmUtils.js +41 -2
  63. package/lib/gen/Dictionary.json +34 -0
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +66 -63
  66. package/lib/gen/language.tokens +81 -81
  67. package/lib/gen/languageLexer.interp +4 -10
  68. package/lib/gen/languageLexer.js +854 -869
  69. package/lib/gen/languageLexer.tokens +79 -81
  70. package/lib/gen/languageParser.js +14360 -14146
  71. package/lib/inspect/inspectModelStatistics.js +2 -2
  72. package/lib/inspect/inspectPropagation.js +6 -6
  73. package/lib/inspect/inspectUtils.js +2 -2
  74. package/lib/json/from-csn.js +82 -40
  75. package/lib/json/to-csn.js +82 -157
  76. package/lib/language/.eslintrc.json +1 -4
  77. package/lib/language/genericAntlrParser.js +59 -38
  78. package/lib/language/language.g4 +1508 -1490
  79. package/lib/language/multiLineStringParser.js +1 -1
  80. package/lib/main.js +3 -3
  81. package/lib/model/csnUtils.js +130 -122
  82. package/lib/model/revealInternalProperties.js +1 -1
  83. package/lib/model/sortViews.js +4 -6
  84. package/lib/modelCompare/utils/filter.js +4 -3
  85. package/lib/optionProcessor.js +5 -0
  86. package/lib/render/DuplicateChecker.js +1 -1
  87. package/lib/render/manageConstraints.js +12 -12
  88. package/lib/render/toCdl.js +225 -159
  89. package/lib/render/toHdbcds.js +63 -63
  90. package/lib/render/toRename.js +5 -5
  91. package/lib/render/toSql.js +55 -65
  92. package/lib/render/utils/common.js +20 -37
  93. package/lib/render/utils/delta.js +3 -3
  94. package/lib/render/utils/sql.js +22 -6
  95. package/lib/render/utils/stringEscapes.js +3 -3
  96. package/lib/transform/db/applyTransformations.js +3 -3
  97. package/lib/transform/db/assertUnique.js +13 -12
  98. package/lib/transform/db/associations.js +5 -5
  99. package/lib/transform/db/cdsPersistence.js +10 -8
  100. package/lib/transform/db/constraints.js +14 -14
  101. package/lib/transform/db/expansion.js +20 -22
  102. package/lib/transform/db/flattening.js +24 -42
  103. package/lib/transform/db/groupByOrderBy.js +3 -3
  104. package/lib/transform/db/temporal.js +6 -6
  105. package/lib/transform/db/transformExists.js +23 -23
  106. package/lib/transform/db/views.js +16 -16
  107. package/lib/transform/draft/db.js +10 -10
  108. package/lib/transform/draft/odata.js +2 -2
  109. package/lib/transform/forOdataNew.js +12 -40
  110. package/lib/transform/forRelationalDB.js +17 -7
  111. package/lib/transform/localized.js +2 -2
  112. package/lib/transform/odata/toFinalBaseType.js +41 -27
  113. package/lib/transform/odata/typesExposure.js +106 -62
  114. package/lib/transform/parseExpr.js +209 -106
  115. package/lib/transform/transformUtilsNew.js +2 -2
  116. package/lib/transform/translateAssocsToJoins.js +24 -19
  117. package/lib/transform/universalCsn/coreComputed.js +10 -10
  118. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  119. package/lib/transform/universalCsn/utils.js +3 -3
  120. package/lib/utils/file.js +5 -5
  121. package/lib/utils/moduleResolve.js +13 -13
  122. package/lib/utils/objectUtils.js +6 -6
  123. package/lib/utils/term.js +5 -2
  124. package/lib/utils/timetrace.js +51 -24
  125. package/package.json +5 -7
  126. package/share/messages/check-proper-type-of.md +1 -1
  127. package/share/messages/message-explanations.json +1 -1
  128. package/share/messages/redirected-to-complex.md +4 -4
  129. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
@@ -10,12 +10,12 @@ const { forEachDefinition } = require('../../model/csnUtils.js');
10
10
  * options:
11
11
  * v
12
12
  *
13
- * This module never produces errors. In case of "unexpected" situations we issue a warning and
13
+ * This module never produces errors. In case of "unexpected" situations we issue a message and
14
14
  * try to proceed with the processing as good as possible.
15
15
  *
16
16
  */
17
17
  function preprocessAnnotations(csn, serviceName, options) {
18
- const { warning } = makeMessageFunction(csn, options);
18
+ const { message } = makeMessageFunction(csn, options);
19
19
  let fkSeparator = '_';
20
20
 
21
21
  resolveShortcuts();
@@ -32,7 +32,7 @@ function preprocessAnnotations(csn, serviceName, options) {
32
32
  }
33
33
 
34
34
  // return value can be null is target has no key
35
- function getKeyOfTargetOfManagedAssoc(assoc) {
35
+ function getKeyOfTargetOfManagedAssoc(anno, assoc) {
36
36
  // assoc.target can be the name of the target or the object itself
37
37
  let targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
38
38
  let target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
@@ -40,10 +40,11 @@ function preprocessAnnotations(csn, serviceName, options) {
40
40
  let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
41
41
  if (keyNames.length === 0) {
42
42
  keyNames.push('MISSING');
43
- warning(null, null, `in annotation preprocessing: target ${targetName} has no key`);
43
+ message('odata-anno-preproc', null, { anno, name: targetName, '#': 'nokey' },
44
+ 'target $(NAME) has no key');
44
45
  }
45
46
  else if (keyNames.length > 1)
46
- warning(null, null, `in annotation preprocessing: target ${targetName} has multiple key elements`);
47
+ message('odata-anno-preproc', null, { anno, name: targetName, '#': 'multkeys' });
47
48
 
48
49
  return keyNames[0];
49
50
  }
@@ -58,32 +59,29 @@ function preprocessAnnotations(csn, serviceName, options) {
58
59
  let art = null;
59
60
 
60
61
  forEachDefinition(csn, (artifact, artifactName) => {
62
+ const location = [ 'definitions', artifactName ];
61
63
  if(artifactName === serviceName || artifactName.startsWith(serviceName + '.')) {
62
64
  art = artifactName;
63
- handleAnnotations(artifactName, artifact);
65
+ handleAnnotations(artifactName, artifact, location);
64
66
  artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
65
- handleAnnotations(elementName, element);
67
+ handleAnnotations(elementName, element, [ ...location, 'elements', elementName ]);
66
68
  });
67
69
  artifact.actions && Object.values(artifact.actions).forEach(action => {
68
70
  action.params && Object.entries(action.params).forEach(([paramName, param]) => {
69
- handleAnnotations(paramName, param);
71
+ handleAnnotations(paramName, param, [ ...location, 'actions', action, 'params', paramName ]);
70
72
  });
71
73
  });
72
74
  }
73
75
  });
74
76
 
75
- function handleAnnotations(carrierName, carrier) {
77
+ function handleAnnotations(carrierName, carrier, location) {
76
78
 
77
79
  // collect the names of the carrier's annotation properties
78
- let annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@')
80
+ let annoNames = Object.keys(carrier).filter( x => x[0] === '@')
79
81
 
80
82
  for (let aName of annoNames) {
81
83
  let aNameWithoutQualifier = aName.split('#')[0];
82
84
 
83
- //for warning messages
84
- let ctx = 'target: ' + art + '/' + carrierName;
85
-
86
-
87
85
  // Always - draft annotations, value is action name
88
86
  // - v2: prefix with entity name
89
87
  // - prefix with service name
@@ -91,11 +89,11 @@ function preprocessAnnotations(csn, serviceName, options) {
91
89
 
92
90
  // Always - FixedValueListShortcut
93
91
  // expand shortcut form of ValueList annotation
94
- fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx);
92
+ fixedValueListShortCut(carrier, aNameWithoutQualifier);
95
93
 
96
94
  // Always - TextArrangementReordering
97
95
  // convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation
98
- textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx);
96
+ textArrangementReordering(carrier, aName, aNameWithoutQualifier);
99
97
  }
100
98
 
101
99
  // inner functions
@@ -121,9 +119,9 @@ function preprocessAnnotations(csn, serviceName, options) {
121
119
  }
122
120
  }
123
121
 
124
- function fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx) {
125
- if (aNameWithoutQualifier === '@Common.ValueList.entity' ||
126
- aNameWithoutQualifier === '@Common.ValueList.viaAssociation') {
122
+ function fixedValueListShortCut(carrier, anno) {
123
+ if (anno === '@Common.ValueList.entity' ||
124
+ anno === '@Common.ValueList.viaAssociation') {
127
125
 
128
126
  const _fixedValueListShortCut = () => {
129
127
  // note: we loop over all annotations that were originally present, even if they are
@@ -136,7 +134,7 @@ function preprocessAnnotations(csn, serviceName, options) {
136
134
  }
137
135
 
138
136
  if (carrier.kind === 'entity') {
139
- warning(null, null, `annotation preprocessing/${aNameWithoutQualifier}: annotation must not be used for an entity, ${ctx}`);
137
+ message('odata-anno-preproc', [...location, anno], { anno, '#': 'notforentity' });
140
138
  return false;
141
139
  }
142
140
 
@@ -146,32 +144,37 @@ function preprocessAnnotations(csn, serviceName, options) {
146
144
  let enameShort = null; // (string) name of value list entity, short (i.e. name within service)
147
145
  let enameFull = null; // (string) name of value list entity, fully qualified name
148
146
 
149
- if (aNameWithoutQualifier === '@Common.ValueList.viaAssociation') {
147
+ if (anno === '@Common.ValueList.viaAssociation') {
150
148
  // value is expected to be an expression, namely the path to an association of the carrier entity
151
149
  let assocName = carrier['@Common.ValueList.viaAssociation']['='];
152
150
  if (!assocName) {
153
- warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: value of 'viaAssociation' must be a path, ${ctx}`);
151
+ message('odata-anno-preproc', [...location, anno], { anno, '#': 'viaassoc' });
154
152
  return false;
155
153
  }
156
154
  let assoc = csn.definitions[art].elements[assocName];
157
155
  if (!assoc || !assoc.target) {
158
- warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);
156
+ message('odata-anno-preproc', [...location, anno], { anno, id: assocName, '#': 'noassoc' });
159
157
  return false;
160
158
  }
161
159
 
162
160
  enameFull = assoc.target.name || assoc.target; // full name
163
161
  enameShort = enameFull.split('.').pop();
164
162
  }
165
- else if (aNameWithoutQualifier === '@Common.ValueList.entity') {
163
+ else if (anno === '@Common.ValueList.entity') {
166
164
  // if both annotations are present, ignore 'entity' and raise a message
167
165
  if (annoNames.map(x=>x.split('#')[0]).find(x=>(x==='@Common.ValueList.viaAssociation'))) {
168
- warning(null, null, `in annotation preprocessing/@Common.ValueList: 'entity' is ignored, as 'viaAssociation' is present, ${ctx}`);
166
+ message('odata-anno-preproc', [...location, anno],
167
+ {
168
+ name: '@Common.ValueList.entity', anno: '@Common.ValueList',
169
+ value: 'entity', code: 'viaAssociation', '#': 'vallistignored'
170
+ });
169
171
  return false;
170
172
  }
171
173
 
172
174
  let annoVal = carrier['@Common.ValueList.entity']; // name of value list entity
173
175
  if (annoVal['=']) {
174
- warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: annotation value must be a string, ${ctx}`);
176
+ message('odata-anno-preproc', [...location, anno], { anno, '#': 'notastring' },
177
+ );
175
178
  }
176
179
 
177
180
  let nameprefix = art.replace(/.[^.]+$/, ''); // better way of getting the service name?
@@ -182,7 +185,7 @@ function preprocessAnnotations(csn, serviceName, options) {
182
185
 
183
186
  let vlEntity = csn.definitions[enameFull]; // (object) value list entity
184
187
  if (!vlEntity) {
185
- warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: entity "${enameFull}" does not exist, ${ctx}`);
188
+ message('odata-anno-preproc', [...location, anno ], { anno, id: enameFull, '#': 'notexist' });
186
189
  return false;
187
190
  }
188
191
 
@@ -196,7 +199,8 @@ function preprocessAnnotations(csn, serviceName, options) {
196
199
  // if this is a managed assoc, use fk field instead (if there is a single one)
197
200
  let localDataProp = carrierName.split('/').pop();
198
201
  if (carrier.target && carrier.on === undefined) {
199
- localDataProp = localDataProp + fkSeparator + getKeyOfTargetOfManagedAssoc(carrier);
202
+ localDataProp = localDataProp + fkSeparator +
203
+ getKeyOfTargetOfManagedAssoc(anno, carrier);
200
204
  }
201
205
 
202
206
  // if this carrier is a generated foreign key field and the association is marked @cds.api.ignore
@@ -209,15 +213,15 @@ function preprocessAnnotations(csn, serviceName, options) {
209
213
  }
210
214
 
211
215
  // valueListProp: the (single) key field of the value list entity
212
- // if no key or multiple keys -> warning
216
+ // if no key or multiple keys -> message
213
217
  let valueListProp = null;
214
218
  let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
215
219
  if (keys.length === 0) {
216
- warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);
220
+ message('odata-anno-preproc', [...location, anno], { anno, name: enameFull, '#': 'vhlnokey' });
217
221
  return false;
218
222
  }
219
223
  else if (keys.length > 1)
220
- warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has more than one key, ${ctx}`);
224
+ message('odata-anno-preproc', [...location, anno], { anno, name: enameFull, '#': 'vhlmultkeys' });
221
225
  valueListProp = keys[0];
222
226
 
223
227
  // textField:
@@ -278,20 +282,20 @@ function preprocessAnnotations(csn, serviceName, options) {
278
282
 
279
283
  const success = _fixedValueListShortCut();
280
284
  if (!success) {
281
- // In case of failure, avoid subsequent warnings
282
- delete carrier[aNameWithoutQualifier];
285
+ // In case of failure, avoid subsequent messages
286
+ delete carrier[anno];
283
287
  delete carrier['@Common.ValueList.type'];
284
288
  }
285
289
  }
286
290
  }
287
291
 
288
- function textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx) {
292
+ function textArrangementReordering(carrier, aName, aNameWithoutQualifier) {
289
293
  if (aNameWithoutQualifier === '@Common.TextArrangement') {
290
294
  let value = carrier[aName];
291
295
  let textAnno = carrier['@Common.Text'];
292
296
  // can only occur if there is a @Common.Text annotation at the same target
293
297
  if (!textAnno) {
294
- warning(null, null, `in annotation preprocessing: TextArrangement shortcut without Text annotation, ${ctx}`);
298
+ message('odata-anno-preproc', [...location, '@Common.TextArrangement'], { anno: '@Common.TextArrangement', name: '@Common.Text', '#': 'txtarr' });
295
299
  }
296
300
 
297
301
  //change the scalar anno into a "pseudo-structured" one
@@ -9,7 +9,7 @@ const VALUELIST_NAVPROP_PREFIX = '';
9
9
  const edmUtils = require('./edmUtils.js')
10
10
  const { initializeModel } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
- const { setProp } = require('../base/model');
12
+ const { setProp, isBetaEnabled } = require('../base/model');
13
13
  const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
@@ -43,7 +43,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
43
43
  setProp(csn.meta, 'options', _csn.meta.options);
44
44
  }
45
45
 
46
- const [ allServices,
46
+ const [
47
+ allServices,
47
48
  allSchemas,
48
49
  reqDefs,
49
50
  whatsMyServiceRootName,
@@ -133,11 +134,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
133
134
  -----------------------------------------------*/
134
135
  let LeadSchema;
135
136
  const fqSchemaXRef = [serviceCsn.name];
136
- const whatsMySchemaName = function(n, arr=fqSchemaXRef) {
137
- return arr.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
137
+ const whatsMySchemaName = function(n) {
138
+ return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
138
139
  }
139
140
 
140
- let xServiceRefs = [];
141
+ // tunnel schema xref and servicename in options to edm.Typebase to rectify
142
+ // type references that are eventually also prefixed with the service schema name.
143
+ options.serviceName = serviceCsn.name;
144
+ // List of all schema names in this service, including the service itself
145
+ options.whatsMySchemaName = whatsMySchemaName;
146
+ options.whatsMyServiceRootName = whatsMyServiceRootName;
147
+
148
+ let xServiceRefs = {};
141
149
  const UsedTypes = {};
142
150
  function collectUsedType(csn, typeName = (csn.items?.type || csn.type)) {
143
151
  if(typeName) {
@@ -160,26 +168,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
160
168
  };
161
169
 
162
170
  if(options.isV4()) {
163
- // tunnel schema xref and servicename in options to edm.Typebase to rectify
164
- // type references that are eventually also prefixed with the service schema name.
165
- options.serviceName = serviceCsn.name;
166
- // List of all schema names in this service, including the service itself
167
- options.whatsMySchemaName = whatsMySchemaName;
168
-
169
171
  // Add additional schema containers as sub contexts to the service
170
172
  Object.entries(allSchemas).forEach(([fqName, art]) => {
171
173
  if(serviceCsn.name === whatsMyServiceRootName(fqName) &&
172
- fqName.startsWith(serviceCsn.name + '.') && art.kind === 'schema') {
173
- fqSchemaXRef.push(fqName);
174
- // Strip the toplevel service schema name (see comment above)
175
- const name = fqName.replace(serviceCsn.name + '.', '');
176
- subSchemaDictionary[name] = {
177
- name,
178
- fqName,
179
- _csn: art,
180
- container: false,
181
- definitions: Object.create(null)
182
- };
174
+ fqName.startsWith(serviceCsn.name + '.')) {
175
+ if(art.kind === 'reference')
176
+ fqSchemaXRef.push(fqName);
177
+ if(art.kind === 'schema') {
178
+ fqSchemaXRef.push(fqName);
179
+ // Strip the toplevel service schema name (see comment above)
180
+ const name = fqName.replace(serviceCsn.name + '.', '');
181
+ subSchemaDictionary[name] = {
182
+ name,
183
+ fqName,
184
+ _csn: art,
185
+ container: false,
186
+ definitions: Object.create(null)
187
+ };
188
+ }
183
189
  }
184
190
  }, subSchemaDictionary);
185
191
 
@@ -203,13 +209,6 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
203
209
  const schema = subSchemaDictionary[name];
204
210
  service.registerSchema(schema.fqName, createSchema(schema));
205
211
  });
206
-
207
- // Add cross service references to the EDM
208
- xServiceRefs.forEach(ref => {
209
- let r = new Edm.Reference(v, ref.ref);
210
- r.append(new Edm.Include(v, ref.inc))
211
- edm._defaultRefs.push(r);
212
- });
213
212
  }
214
213
  else {
215
214
  populateSchemas(subSchemaDictionary);
@@ -231,40 +230,43 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
231
230
  }
232
231
  }));
233
232
  });
234
- // Create annotations and distribute into Schemas
235
- addAnnotations();
233
+ // Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
234
+ addAnnotations(xServiceRefs);
235
+
236
+ // Finally add cross service references into the EDM and extract the targetSchemaNames
237
+ // for the type cross check
238
+ Object.values(xServiceRefs).forEach(ref => {
239
+ let r = new Edm.Reference(v, ref.ref);
240
+ r.append(new Edm.Include(v, ref.inc))
241
+ edm._defaultRefs.push(r);
242
+ });
236
243
 
237
- // type cross check
238
- const xServiceRefNames = xServiceRefs.map(r => r.name).sort((a,b)=>b.length-a.length);
239
244
  for(let typeName in UsedTypes) {
240
245
  if(!isBuiltinType(typeName)) {
241
246
  let iTypeName = typeName;
242
247
  /*
243
248
  Report type ref, if the type is
244
249
  - not a builtin,
245
- - not included in required definitions (for this service)
250
+ - not included in required definitions
246
251
  - not a type clash (reported in type exposure),
247
252
  - a @cds.external service member but can't be rendered
248
- - and not a cross referenced type
249
253
  */
250
254
  if(!typeName.startsWith(serviceCsn.name + '.'))
251
255
  iTypeName = serviceCsn.name + '.' + typeName;
252
256
  const def = reqDefs.definitions[iTypeName];
253
257
 
254
258
  const usages = UsedTypes[typeName].filter(u => !u.$NameClashReported);
255
- if(usages.length > 0) {
256
- if(def && !def.$isRendered && def['@cds.external'])
257
- message('odata-spec-violation-type', usages[0].$location,
258
- {
259
- type: typeName,
260
- anno: '@cds.external',
261
- name: serviceCsn.name,
262
- code: def.elements ? 'Edm.ComplexType' : 'Edm.TypeDefinition',
263
- version: options.isV4() ? '4.0' : '2.0',
264
- '#': 'external' } );
265
- else if(!def && !whatsMySchemaName(typeName, xServiceRefNames))
266
- message('odata-spec-violation-type', usages[0].$location,
267
- { type: typeName, name: serviceCsn.name, '#': 'missing' } );
259
+ if(usages.length > 0 && def && !def.$isRendered && def['@cds.external']) {
260
+ message('odata-spec-violation-type', usages[0].$location,
261
+ {
262
+ type: typeName,
263
+ anno: '@cds.external',
264
+ name: serviceCsn.name,
265
+ code: def.elements ? 'Edm.ComplexType' : 'Edm.TypeDefinition',
266
+ version: options.isV4() ? '4.0' : '2.0',
267
+ '#': 'external'
268
+ }
269
+ );
268
270
  }
269
271
  }
270
272
  }
@@ -328,11 +330,13 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
328
330
 
329
331
  return Object.entries(allSchemas).reduce((references, [fqName, art]) => {
330
332
  // add references
331
- if(art.kind === 'reference' && whatsMySchemaName(fqName) && serviceCsn.name === whatsMyServiceRootName(fqName, false)) {
332
- references.push(art);
333
+ if(art.kind === 'reference' &&
334
+ whatsMySchemaName(fqName) &&
335
+ serviceCsn.name === whatsMyServiceRootName(fqName, false)) {
336
+ references[art.inc.Namespace] = art;
333
337
  }
334
338
  return references;
335
- }, []);
339
+ }, {});
336
340
  }
337
341
 
338
342
  // Main schema creator function
@@ -385,12 +389,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
385
389
  {
386
390
  edmUtils.foreach(schemaCsn.definitions,
387
391
  artifact => edmUtils.isDerivedType(artifact) &&
388
- !artifact.target &&
392
+ !artifact.target&&
389
393
  artifact.name.startsWith(schemaNamePrefix),
390
394
  [createTypeDefinitionV4, markRendered]);
391
395
  }
392
396
 
393
- // fetch all existing child names in a map
397
+ if(isBetaEnabled(options, 'odataTerms')) {
398
+ edmUtils.foreach(schemaCsn.definitions,
399
+ a => a.kind === 'annotation' && a.name.startsWith(schemaNamePrefix),
400
+ createTerm);
401
+ }
402
+
403
+ // fetch all exising children names in a map
394
404
  const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
395
405
  const name = cur._edmAttributes.Name;
396
406
  if(acc[name] === undefined) {
@@ -412,12 +422,6 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
412
422
  }
413
423
  });
414
424
 
415
- if(Schema._children.length === 0 ||
416
- (Schema._children.length === 1 && Schema._children[0].kind === 'EntityContainer')) {
417
- // FIXME: Location for sub schemas?
418
- warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
419
- }
420
-
421
425
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
422
426
  if(refs.length > 1) {
423
427
  error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
@@ -448,7 +452,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
448
452
  const pLoc = [...loc, 'elements', p._edmAttributes.Name];
449
453
  edmTypeCompatibilityCheck(p, pLoc);
450
454
  if(p._edmAttributes.Name === EntityTypeName)
451
- message('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
455
+ message('odata-spec-violation-property-name', pLoc, { meta: entityCsn.kind });
452
456
 
453
457
  if(options.isV2() && p._isCollection && !p._csn.target)
454
458
  message('odata-spec-violation-array', pLoc, { version: '2.0' });
@@ -524,7 +528,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
524
528
  const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
525
529
  edmTypeCompatibilityCheck(p, pLoc);
526
530
  if(p._edmAttributes.Name === complexType._edmAttributes.Name)
527
- message('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
531
+ message('odata-spec-violation-property-name', pLoc, { meta: structuredTypeCsn.kind });
528
532
 
529
533
  if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
530
534
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
@@ -618,6 +622,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
618
622
  return [ props, hasStream ];
619
623
  }
620
624
 
625
+ function createTerm(termCsn) {
626
+ const attributes = { Name: termCsn.name.replace(schemaNamePrefix, '') };
627
+ const term = new Edm.Term(v, attributes, termCsn);
628
+ Schema.append(term);
629
+ }
630
+
621
631
  // V4 <TypeDefintion>
622
632
  function createTypeDefinitionV4(typeCsn)
623
633
  {
@@ -970,8 +980,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
970
980
  }
971
981
 
972
982
  // generate the Edm.Annotations tree and append it to the corresponding schema
973
- function addAnnotations() {
974
- let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
983
+ function addAnnotations(xServiceRefs) {
984
+ let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions);
975
985
  // distribute edm:Annotations into the schemas
976
986
  // Distribute each anno into Schema
977
987
  annos.forEach(anno => {
@@ -979,21 +989,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
979
989
  // if no target schema has been found, it's a service annotation that applies to the service schema
980
990
  if(targetSchema === undefined)
981
991
  targetSchema = serviceCsn.name;
982
- if(targetSchema) {
983
- if(targetSchema !== serviceCsn.name) {
984
- const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
985
- anno.setEdmAttribute('Target', newTarget);
986
- }
987
- edm._service._schemas[targetSchema]._annotations.push(anno);
992
+ if(targetSchema !== serviceCsn.name) {
993
+ const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
994
+ anno.setEdmAttribute('Target', newTarget);
988
995
  }
996
+ edm._service._schemas[targetSchema]._annotations.push(anno);
989
997
  });
990
998
  annos = [];
991
- // add references for the used vocabularies
992
- usedVocabularies.forEach(voc => {
993
- let r = new Edm.Reference(v, voc.ref);
994
- r.append(new Edm.Include(v, voc.inc))
995
- edm._defaultRefs.push(r);
996
- })
999
+
1000
+ // create service cross reference and merge it into xServiceRefs
1001
+ xrefs.forEach(xr => {
1002
+ if(xr !== serviceCsn.name) {
1003
+ const art = edmUtils.createSchemaRef(allServices, xr);
1004
+ if(xServiceRefs[art.inc.Namespace] === undefined)
1005
+ xServiceRefs[art.inc.Namespace] = art;
1006
+ }
1007
+ });
1008
+ // merge vocabulary cross references into xServiceRefs
1009
+ usedVocabularies.forEach(art => xServiceRefs[art.inc.Namespace] = art);
997
1010
  }
998
1011
 
999
1012
  function edmTypeCompatibilityCheck(p, pLoc) {
package/lib/edm/edm.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const edmUtils = require('./edmUtils.js');
4
4
  const { isBuiltinType } = require('../model/csnUtils.js');
5
- const { forEach } = require("../utils/objectUtils");
5
+ const { forEach } = require('../utils/objectUtils');
6
6
  const { isBetaEnabled } = require('../base/model.js');
7
7
 
8
8
  // facet definitions, optional could either be true or array of edm types
@@ -722,8 +722,8 @@ function getEdm(options, messageFunctions) {
722
722
  }
723
723
  }
724
724
 
725
- // Set the collection property if this is either an element or a parameter
726
- this._isCollection = (csn.kind === undefined) ? csn._isCollection : false;
725
+ // Set the collection property if this is either an element, parameter or a term def
726
+ this._isCollection = (csn.kind === undefined || csn.kind === 'annotation') ? csn._isCollection : false;
727
727
 
728
728
  if(options.whatsMySchemaName && this._edmAttributes[typeName]) {
729
729
  let schemaName = options.whatsMySchemaName(this._edmAttributes[typeName]);
@@ -834,6 +834,18 @@ function getEdm(options, messageFunctions) {
834
834
  }
835
835
  }
836
836
 
837
+ class Term extends TypeBase
838
+ {
839
+ constructor(v, attributes, csn)
840
+ {
841
+ super(v, attributes, csn);
842
+ const appliesTo = csn['@odata.term.AppliesTo'];
843
+ if(appliesTo) {
844
+ this.setEdmAttribute('AppliesTo', Array.isArray(appliesTo) ? appliesTo.map(v=>v['=']||v).join(' ') : appliesTo['='] || appliesTo);
845
+ }
846
+ }
847
+ }
848
+
837
849
  class TypeDefinition extends TypeBase
838
850
  {
839
851
  constructor(v, attributes, csn)
@@ -1639,6 +1651,8 @@ function getEdm(options, messageFunctions) {
1639
1651
  EntityContainer,
1640
1652
  EntitySet,
1641
1653
  Singleton,
1654
+ TypeBase,
1655
+ Term,
1642
1656
  TypeDefinition,
1643
1657
  EnumType,
1644
1658
  ComplexType,
@@ -108,8 +108,8 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
108
108
  let CommonAttributes = element[prop];
109
109
  if(!Array.isArray(CommonAttributes)) {
110
110
  error(null, ['definitions', struct.name, 'elements', element.name],
111
- { anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
112
- 'Expect array value for $(ANNOTATION): $(CODE)');
111
+ { anno: '@Common.Attributes', code: JSON.stringify(CommonAttributes) },
112
+ 'Expecting array value for $(ANNO): $(CODE)');
113
113
  return;
114
114
  }
115
115
 
@@ -125,7 +125,7 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
125
125
  if(!Array.isArray(ContextDefiningProperties)) {
126
126
  error(null, ['definitions', struct.name, 'elements', element.name],
127
127
  { anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
128
- 'Expect array value for $(ANNOTATION): $(CODE)');
128
+ 'Expecting array value for $(ANNO): $(CODE)');
129
129
  return;
130
130
  }
131
131
  if(ContextDefiningProperties.length > 0)
@@ -198,7 +198,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
198
198
  error(null, ['definitions', struct.name ],
199
199
  { anno: '@Capabilities.FilterRestrictions.FilterExpressionRestrictions',
200
200
  code: JSON.stringify(filterRestrictions) },
201
- 'Expect array value for $(ANNOTATION): $(CODE)');
201
+ 'Expected array value for $(ANNO): $(CODE)');
202
202
  return;
203
203
  }
204
204
  filterRestrictions.forEach(v => {
@@ -215,7 +215,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
215
215
  error(null, ['definitions', struct.name],
216
216
  { anno: '@Capabilities.FilterRestrictions.RequiredProperties',
217
217
  code: JSON.stringify(requiredProperties) },
218
- 'Expect array value for $(ANNOTATION): $(CODE)');
218
+ 'Expecting array value for $(ANNO): $(CODE)');
219
219
  return;
220
220
  }
221
221