@sap/cds-compiler 3.0.2 → 3.1.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 (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. package/lib/checks/unknownMagic.js +0 -41
@@ -0,0 +1,349 @@
1
+ 'use strict';
2
+
3
+ const edmUtils = require('./edmUtils.js');
4
+ const { setProp } = require('../base/model');
5
+ const { forEachGeneric } = require('../model/csnUtils');
6
+
7
+ /*
8
+ * Late application specific transformations
9
+ * At present there are two transformation targets: Structure and Element
10
+ * These transformations are available today:
11
+ *
12
+ * Analytical Scenario:
13
+ * If a structure is annotated with @Aggregation.ApplySupported.PropertyRestrictions
14
+ * then a number of annotation rewrites are done to this structure and to the
15
+ * elements of this structure
16
+ * Also the key properties of all structure elements are removed and a new
17
+ * artificial key element 'key _ID : String' is inserted at first position of
18
+ * the elements dictionary
19
+ *
20
+ * PDM (Personal Data Management)
21
+ * Planned but not yet implemented annotation rewriting (pending to finalization)
22
+ */
23
+
24
+ /* eslint max-statements-per-line:off */
25
+
26
+ function mapAnnotationAssignment(artifact, parent, mappingDictionary)
27
+ {
28
+ let props = edmUtils.intersect(Object.keys(mappingDictionary), Object.keys(artifact));
29
+ // now start the substitution
30
+ props.forEach(prop => {
31
+ let [ mapping, value, remove_original ] = mappingDictionary[prop];
32
+ if(mapping instanceof Function)
33
+ {
34
+ mapping(artifact, parent, prop);
35
+ }
36
+ else
37
+ {
38
+ edmUtils.assignAnnotation(artifact, mapping, value || artifact[prop]['='] || artifact[prop]);
39
+ }
40
+
41
+ if(remove_original)
42
+ delete artifact[prop];
43
+ });
44
+ }
45
+
46
+ function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
47
+ edmUtils.assignProp(carrier, '_SetAttributes', Object.create(null));
48
+ edmUtils.assignAnnotation(carrier._SetAttributes, propName, propValue);
49
+ if(removeFromType) {
50
+ delete carrier[propName];
51
+ }
52
+ }
53
+
54
+ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct, error)
55
+ {
56
+ if(options.isV2())
57
+ {
58
+ if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
59
+ {
60
+ mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
61
+ }
62
+ mapAnnotationAssignment(element, struct, PDMSemantics());
63
+ }
64
+
65
+ // etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
66
+ // Oliver Heinrich mentions in the issue that the Okra runtime must be set to a
67
+ // concurrent runtime mode by the caller, if the annotation is added this late,
68
+ // it doesn't appear in the forOData processed CSN, meaning that the
69
+ // runtime cannot set that okra flag (alternatively the runtime has to search
70
+ // for @[odata|cds].etag annotations...
71
+ if(options.isV4())
72
+ {
73
+ if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
74
+ // don't put element name into collection as per advice from Ralf Handl, as
75
+ // no runtime is interested in the property itself, it is sufficient to mark
76
+ // the entity set.
77
+ edmUtils.assignAnnotation(struct, '@Core.OptimisticConcurrency',
78
+ (struct['@Core.OptimisticConcurrency'] || [])/*.push(element.name)*/);
79
+ }
80
+ }
81
+
82
+ // nested functions begin
83
+ function PDMSemantics()
84
+ {
85
+ /*
86
+ let dict = Object.create(null);
87
+
88
+ dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
89
+ dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
90
+ dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
91
+ dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
92
+ dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
93
+ dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
94
+ dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
95
+ dict['@PDM.xxx8'] = [ '@sap.deletable' ];
96
+ dict['@PDM.xxx8'] = [ '@sap.updatable' ];
97
+
98
+ // respect flattened annotation $value
99
+ Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
100
+ */
101
+ return Object.create(null);
102
+ }
103
+
104
+ function AnalyticalAnnotations()
105
+ {
106
+ function mapCommonAttributes(element, struct, prop)
107
+ {
108
+ let CommonAttributes = element[prop];
109
+ if(!Array.isArray(CommonAttributes)) {
110
+ error(null, ['definitions', struct.name, 'elements', element.name],
111
+ { anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
112
+ `Expect array value for $(ANNOTATION): $(CODE)`);
113
+ return;
114
+ }
115
+
116
+ let targets = edmUtils.intersect(CommonAttributes, Object.keys(struct.elements));
117
+ targets.forEach(tgt => {
118
+ edmUtils.assignAnnotation(struct.elements[tgt], '@sap.attribute-for', element.name);
119
+ });
120
+ }
121
+
122
+ function mapContextDefiningProperties(element, struct, prop)
123
+ {
124
+ let ContextDefiningProperties = element[prop];
125
+ if(!Array.isArray(ContextDefiningProperties)) {
126
+ error(null, ['definitions', struct.name, 'elements', element.name],
127
+ { anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
128
+ `Expect array value for $(ANNOTATION): $(CODE)`);
129
+ return;
130
+ }
131
+ if(ContextDefiningProperties.length > 0)
132
+ edmUtils.assignAnnotation(element, '@sap.super-ordinate', ContextDefiningProperties[ContextDefiningProperties.length-1]);
133
+ }
134
+
135
+ let dict = Object.create(null);
136
+ //analytics term definition unknown, lower case
137
+ dict['@Analytics.Measure'] = [ '@sap.aggregation-role', 'measure' ];
138
+ dict['@Analytics.Dimension'] = [ '@sap.aggregation-role', 'dimension' ];
139
+ dict['@Semantics.currencyCode'] = [ '@sap.semantics', 'currency-code', true ];
140
+ dict['@Semantics.unitOfMeasure'] = [ '@sap.semantics', 'unit-of-measure', true ];
141
+
142
+ dict['@Measures.ISOCurrency'] = [ '@sap.unit' ];
143
+ dict['@Measures.Unit'] = [ '@sap.unit' ];
144
+
145
+ dict['@Common.Label'] = [ '@sap.label' ];
146
+ dict['@Common.Text'] = [ '@sap.text' ];
147
+ dict['@Aggregation.ContextDefiningProperties'] = [ mapContextDefiningProperties ];
148
+ dict['@Common.Attributes'] = [ mapCommonAttributes ];
149
+
150
+ // respect flattened annotation $value
151
+ Object.entries(dict).forEach(([k, v]) => dict[k+'.$value'] = v);
152
+ return dict;
153
+ }
154
+ }
155
+
156
+ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error)
157
+ {
158
+ if(options.isV2())
159
+ {
160
+ if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
161
+ {
162
+ transformAnalyticalModel(struct);
163
+ mapAnnotationAssignment(struct, undefined, AnalyticalAnnotations());
164
+ }
165
+ }
166
+
167
+ // nested functions begin
168
+ function transformAnalyticalModel(struct)
169
+ {
170
+ let keyName = 'ID__';
171
+ if(struct == undefined || struct.elements == undefined || struct.elements[keyName] != undefined)
172
+ return;
173
+
174
+ // remove key prop from elements, add new key to elements
175
+ let elements = Object.create(null);
176
+ let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
177
+ elements[keyName] = key;
178
+ setProp(struct, '$keys',{ [keyName] : key } );
179
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
180
+ {
181
+ if(e.key) delete e.key;
182
+ elements[n] = e;
183
+ });
184
+ struct.elements = elements;
185
+ }
186
+
187
+ function AnalyticalAnnotations()
188
+ {
189
+ function mapFilterRestrictions(struct, parent, prop)
190
+ {
191
+ let stringDict = Object.create(null);
192
+ stringDict['SingleValue'] = 'single-value';
193
+ stringDict['MultiValue'] = 'multi-value';
194
+ stringDict['SingleRange'] = 'interval';
195
+
196
+ let filterRestrictions = struct[prop];
197
+ if(!Array.isArray(filterRestrictions)) {
198
+ error(null, ['definitions', struct.name ],
199
+ { anno: '@Capabilities.FilterRestrictions.FilterExpressionRestrictions',
200
+ code: JSON.stringify(filterRestrictions) },
201
+ `Expect array value for $(ANNOTATION): $(CODE)`);
202
+ return;
203
+ }
204
+ filterRestrictions.forEach(v => {
205
+ let e = struct.elements[v.Property];
206
+ if(e)
207
+ edmUtils.assignAnnotation(e, '@sap.filter-restriction', stringDict[v.AllowedExpressions]);
208
+ });
209
+ }
210
+
211
+ function mapRequiredProperties(struct, parent, prop)
212
+ {
213
+ let requiredProperties = struct[prop];
214
+ if(!Array.isArray(requiredProperties)) {
215
+ error(null, ['definitions', struct.name],
216
+ { anno: '@Capabilities.FilterRestrictions.RequiredProperties',
217
+ code: JSON.stringify(requiredProperties) },
218
+ `Expect array value for $(ANNOTATION): $(CODE)`);
219
+ return;
220
+ }
221
+
222
+ let props = edmUtils.intersect(Object.keys(struct.elements), requiredProperties)
223
+ props.forEach(p => {
224
+ edmUtils.assignAnnotation(struct.elements[p], '@sap.required-in-filter', true);
225
+ });
226
+ }
227
+
228
+ function mapRequiresFilter(struct, parent, prop)
229
+ {
230
+ let requiresFilter = struct[prop];
231
+ if(requiresFilter)
232
+ edmUtils.assignAnnotation(struct._SetAttributes, '@sap.requires-filter', requiresFilter);
233
+ }
234
+
235
+ // Entity Props
236
+ let dict = Object.create(null);
237
+ dict['@Aggregation.ApplySupported.PropertyRestrictions'] = [ '@sap.semantics', 'aggregate' ];
238
+ dict['@Common.Label'] = [ '@sap.label' ];
239
+ dict['@Capabilities.FilterRestrictions.RequiresFilter'] = [ mapRequiresFilter ];
240
+ dict['@Capabilities.FilterRestrictions.RequiredProperties'] = [ mapRequiredProperties ];
241
+ dict['@Capabilities.FilterRestrictions.FilterExpressionRestrictions'] = [ mapFilterRestrictions ];
242
+
243
+ // respect flattened annotation $value
244
+ Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
245
+
246
+ return dict;
247
+ }
248
+ }
249
+
250
+ function setSAPSpecificV2AnnotationsToEntityContainer(options, carrier) {
251
+ if(!options.isV2())
252
+ return;
253
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntityContainer
254
+ const SetAttributes = {
255
+ // EntityContainer only
256
+ '@sap.supported.formats' : addToSetAttr,
257
+ '@sap.use.batch': addToSetAttr,
258
+ '@sap.message.scope.supported': addToSetAttr,
259
+ };
260
+
261
+ Object.entries(carrier).forEach(([p, v]) => {
262
+ (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
263
+ });
264
+ }
265
+
266
+ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
267
+ if(!options.isV2())
268
+ return;
269
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntitySet
270
+ const SetAttributes = {
271
+ // EntitySet, EntityType
272
+ '@sap.label' : (s,pn, pv) => { addToSetAttr(s, pn, pv, false); },
273
+ '@sap.semantics': checkSemantics,
274
+ // EntitySet only
275
+ '@sap.creatable' : addToSetAttr,
276
+ '@sap.updatable' : addToSetAttr,
277
+ '@sap.deletable': addToSetAttr,
278
+ '@sap.updatable.path': addToSetAttr,
279
+ '@sap.deletable.path': addToSetAttr,
280
+ '@sap.searchable' : addToSetAttr,
281
+ '@sap.pagable': addToSetAttr,
282
+ '@sap.topable': addToSetAttr,
283
+ '@sap.countable': addToSetAttr,
284
+ '@sap.addressable': addToSetAttr,
285
+ '@sap.requires.filter': addToSetAttr,
286
+ '@sap.change.tracking': addToSetAttr,
287
+ '@sap.maxpagesize': addToSetAttr,
288
+ '@sap.delta.link.validity': addToSetAttr,
289
+ };
290
+
291
+ Object.entries(carrier).forEach(([p, v]) => {
292
+ (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
293
+ });
294
+
295
+ function checkSemantics(struct, propName, propValue) {
296
+ if(propValue === 'timeseries' || propValue === 'aggregate') {
297
+ // aggregate is forwarded to Set and must remain on Type
298
+ addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
299
+ }
300
+ }
301
+ }
302
+
303
+ function setSAPSpecificV2AnnotationsToAssociation(carrier) {
304
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
305
+ const SetAttributes = {
306
+ // Applicable to NavProp and foreign keys, add to AssociationSet
307
+ '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
308
+ // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
309
+ '@sap.updatable' : addToAssociationSet,
310
+ // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
311
+ '@sap.deletable': (c, pn, pv) => {
312
+ addToAssociationSet(c, pn, pv);
313
+ removeFromForeignKey(c, pn);
314
+ },
315
+ // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
316
+ '@sap.creatable.path': removeFromForeignKey,
317
+ '@sap.filterable': removeFromForeignKey,
318
+ };
319
+
320
+ Object.entries(carrier).forEach(([p, v]) => {
321
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
322
+ });
323
+
324
+ function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
325
+ if(carrier.target) {
326
+ edmUtils.assignProp(carrier, '_SetAttributes', Object.create(null));
327
+ edmUtils.assignAnnotation(carrier._SetAttributes, propName, propValue);
328
+ if(removeFromType) {
329
+ delete carrier[propName];
330
+ }
331
+ }
332
+ }
333
+
334
+ function removeFromForeignKey(carrier, propName) {
335
+ if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
336
+ delete carrier[propName];
337
+ }
338
+ }
339
+ }
340
+
341
+
342
+
343
+ module.exports = {
344
+ applyAppSpecificLateCsnTransformationOnElement,
345
+ applyAppSpecificLateCsnTransformationOnStructure,
346
+ setSAPSpecificV2AnnotationsToEntityContainer,
347
+ setSAPSpecificV2AnnotationsToEntitySet,
348
+ setSAPSpecificV2AnnotationsToAssociation
349
+ };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const { setProp, isBetaEnabled } = require('../base/model');
4
+ const {
5
+ forEachDefinition, forEachMemberRecursively, getUtils,
6
+ } = require('../model/csnUtils');
7
+
8
+ // eslint-disable-next-line no-unused-vars
9
+ function resolveForeignKeyRefs(csn) {
10
+ const csnUtils = getUtils(csn);
11
+ forEachDefinition(csn, (def, defName) => {
12
+ let currPath = ['definitions', defName ];
13
+ forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
14
+ if(construct.target && construct.keys) {
15
+ construct.keys.forEach((fk, i) => {
16
+ setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
17
+ });
18
+ }
19
+ }, currPath, true, { elementsOnly: true });
20
+ });
21
+ }
22
+
23
+
24
+ function inboundQualificationChecks(csn, options, messageFunctions,
25
+ serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName) {
26
+ const csnUtils = getUtils(csn);
27
+ const { message, throwWithAnyError } = messageFunctions;
28
+
29
+ forEachDefinition(csn, [ attach$path, checkChainedArray ]);
30
+ checkNestedContextsAndServices();
31
+ throwWithAnyError();
32
+
33
+ // attach $path to all
34
+ function attach$path(def, defName) {
35
+ setProp(def, '$path', [ 'definitions', defName ]);
36
+ forEachMemberRecursively(def,
37
+ (member, _memberName, _prop, path) => {
38
+ setProp(member, '$path', path);
39
+ }, [ 'definitions', defName ]);
40
+ }
41
+
42
+ function checkChainedArray(def, defName) {
43
+ if (!isMyServiceRequested(defName))
44
+ return;
45
+ let currPath = ['definitions', defName];
46
+ checkIfItemsOfItems(def, undefined, undefined, currPath);
47
+ forEachMemberRecursively(def, checkIfItemsOfItems, currPath);
48
+
49
+ function checkIfItemsOfItems(construct, _constructName, _prop, path) {
50
+ const constructType = csnUtils.effectiveType(construct);
51
+ if (constructType.items) {
52
+ if (constructType.items.items) {
53
+ message('chained-array-of', path);
54
+ return;
55
+ }
56
+
57
+ const itemsType = csnUtils.effectiveType(constructType.items);
58
+ if (itemsType.items)
59
+ message('chained-array-of', path);
60
+ }
61
+ }
62
+ }
63
+
64
+ function checkNestedContextsAndServices() {
65
+ !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
66
+ const parent = whatsMyServiceRootName(sn, false);
67
+ if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
68
+ message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
69
+ 'A service can\'t be nested within a service $(ART)' );
70
+ }
71
+ });
72
+
73
+ Object.entries(csn.definitions).forEach(([fqName, art]) => {
74
+ if(art.kind === 'context') {
75
+ const parent = whatsMyServiceRootName(fqName);
76
+ if(requestedServiceNames.includes(parent)) {
77
+ message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
78
+ 'A context can\'t be nested within a service $(ART)' );
79
+ }
80
+ }
81
+ });
82
+ }
83
+ }
84
+
85
+ module.exports = { inboundQualificationChecks }