@sap/cds-compiler 3.5.4 → 3.6.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 (83) hide show
  1. package/CHANGELOG.md +56 -2
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/validate.js +5 -0
  7. package/lib/base/message-registry.js +104 -32
  8. package/lib/base/messages.js +277 -212
  9. package/lib/base/optionProcessorHelper.js +9 -2
  10. package/lib/base/shuffle.js +50 -0
  11. package/lib/checks/actionsFunctions.js +37 -20
  12. package/lib/checks/foreignKeys.js +13 -6
  13. package/lib/checks/nonexpandableStructured.js +1 -2
  14. package/lib/checks/onConditions.js +21 -19
  15. package/lib/checks/parameters.js +1 -1
  16. package/lib/checks/queryNoDbArtifacts.js +2 -0
  17. package/lib/checks/types.js +16 -22
  18. package/lib/compiler/assert-consistency.js +31 -28
  19. package/lib/compiler/builtins.js +20 -4
  20. package/lib/compiler/checks.js +72 -63
  21. package/lib/compiler/define.js +396 -314
  22. package/lib/compiler/extend.js +55 -49
  23. package/lib/compiler/index.js +5 -0
  24. package/lib/compiler/populate.js +28 -11
  25. package/lib/compiler/propagator.js +2 -1
  26. package/lib/compiler/resolve.js +28 -13
  27. package/lib/compiler/shared.js +15 -10
  28. package/lib/compiler/utils.js +7 -7
  29. package/lib/edm/annotations/genericTranslation.js +51 -46
  30. package/lib/edm/annotations/preprocessAnnotations.js +37 -40
  31. package/lib/edm/csn2edm.js +69 -21
  32. package/lib/edm/edm.js +2 -2
  33. package/lib/edm/edmInboundChecks.js +6 -8
  34. package/lib/edm/edmPreprocessor.js +88 -80
  35. package/lib/edm/edmUtils.js +6 -15
  36. package/lib/gen/Dictionary.json +81 -13
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +2 -1
  39. package/lib/gen/languageParser.js +4680 -4484
  40. package/lib/inspect/inspectModelStatistics.js +2 -1
  41. package/lib/inspect/inspectPropagation.js +2 -1
  42. package/lib/json/from-csn.js +131 -78
  43. package/lib/json/to-csn.js +39 -23
  44. package/lib/language/antlrParser.js +0 -3
  45. package/lib/language/docCommentParser.js +7 -3
  46. package/lib/language/errorStrategy.js +3 -2
  47. package/lib/language/genericAntlrParser.js +96 -41
  48. package/lib/language/language.g4 +112 -128
  49. package/lib/language/multiLineStringParser.js +2 -1
  50. package/lib/main.d.ts +115 -2
  51. package/lib/main.js +16 -3
  52. package/lib/model/csnRefs.js +3 -3
  53. package/lib/model/csnUtils.js +109 -179
  54. package/lib/model/enrichCsn.js +13 -8
  55. package/lib/model/revealInternalProperties.js +4 -3
  56. package/lib/optionProcessor.js +19 -3
  57. package/lib/render/manageConstraints.js +11 -15
  58. package/lib/render/toCdl.js +144 -47
  59. package/lib/render/toHdbcds.js +22 -22
  60. package/lib/render/toRename.js +3 -4
  61. package/lib/render/toSql.js +29 -20
  62. package/lib/render/utils/delta.js +3 -1
  63. package/lib/render/utils/sql.js +2 -14
  64. package/lib/transform/db/associations.js +6 -6
  65. package/lib/transform/db/cdsPersistence.js +3 -3
  66. package/lib/transform/db/constraints.js +4 -6
  67. package/lib/transform/db/expansion.js +4 -4
  68. package/lib/transform/db/flattening.js +12 -15
  69. package/lib/transform/db/temporal.js +4 -3
  70. package/lib/transform/db/transformExists.js +2 -1
  71. package/lib/transform/draft/db.js +7 -7
  72. package/lib/transform/forOdataNew.js +15 -4
  73. package/lib/transform/forRelationalDB.js +53 -39
  74. package/lib/transform/odata/toFinalBaseType.js +106 -82
  75. package/lib/transform/odata/typesExposure.js +26 -17
  76. package/lib/transform/odata/utils.js +1 -1
  77. package/lib/transform/parseExpr.js +1 -1
  78. package/lib/transform/transformUtilsNew.js +33 -10
  79. package/lib/transform/translateAssocsToJoins.js +8 -7
  80. package/lib/transform/universalCsn/coreComputed.js +7 -5
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  82. package/lib/utils/timetrace.js +2 -2
  83. package/package.json +1 -2
@@ -70,6 +70,7 @@ module.exports = { transform4odataWithCsn };
70
70
 
71
71
  function transform4odataWithCsn(inputModel, options) {
72
72
  timetrace.start('OData transformation');
73
+
73
74
  // copy the model as we don't want to change the input model
74
75
  let csn = cloneCsnNonDict(inputModel, options);
75
76
 
@@ -125,6 +126,9 @@ function transform4odataWithCsn(inputModel, options) {
125
126
 
126
127
  addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
127
128
 
129
+ // replace all type refs to builtin types with direct type
130
+ transformUtils.rewriteBuiltinTypeRef(csn);
131
+
128
132
  const cleanup = validate.forOdata(csn, {
129
133
  message, error, warning, info, inspectRef, effectiveType, getFinalBaseTypeWithProps, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
130
134
  });
@@ -148,7 +152,12 @@ function transform4odataWithCsn(inputModel, options) {
148
152
  { skipArtifact: isExternalServiceMember }
149
153
  );
150
154
 
151
- expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember);
155
+ // All type refs must be resolved, including external APIs.
156
+ // OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
157
+ // If in the future 'other' APIs that might support type refs are imported, these refs must be
158
+ // resolved here, as this is the OData transformation and sets the foundation for subsequent EDM
159
+ // rendering which may has to publish external definitions
160
+ expandToFinalBaseType(csn, transformers, csnUtils, services, options);
152
161
 
153
162
  // Check if structured elements and managed associations are compared in an expression
154
163
  // and expand these structured elements. This tuple expansion allows all other
@@ -157,7 +166,7 @@ function transform4odataWithCsn(inputModel, options) {
157
166
  expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
158
167
 
159
168
  if (!structuredOData) {
160
- expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, { skipArtifact: isExternalServiceMember });
169
+ expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
161
170
  const resolved = new WeakMap();
162
171
  // No refs with struct-steps exist anymore
163
172
  flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
@@ -177,13 +186,13 @@ function transform4odataWithCsn(inputModel, options) {
177
186
 
178
187
  // TODO: add the generated foreign keys to the columns when we are in a view
179
188
  // see db/views.js::addForeignKeysToColumns
180
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, { skipArtifact: isExternalServiceMember });
189
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
181
190
 
182
191
  // Allow using managed associations as steps in on-conditions to access their fks
183
192
  // To be done after handleManagedAssociationsAndCreateForeignKeys,
184
193
  // since then the foreign keys of the managed assocs are part of the elements
185
194
  if(!structuredOData)
186
- forEachDefinition(csn, associations.getFKAccessFinalizer(csn, '_'));
195
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
187
196
 
188
197
  // structure flattener reports errors, further processing is not safe -> throw exception in case of errors
189
198
  throwWithAnyError();
@@ -221,6 +230,8 @@ function transform4odataWithCsn(inputModel, options) {
221
230
  def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
222
231
 
223
232
  forEachMemberRecursively(def, (member, memberName, propertyName) => {
233
+ if (memberName === '' && propertyName === 'params')
234
+ return; // ignore "returns" type
224
235
  // Annotate elements, foreign keys, parameters, etc. with their DB names if requested
225
236
  // Only these are actually required and don't annotate virtual elements in entities or types
226
237
  // as they have no DB representation (although in views)
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
- const { getUtils, cloneCsnNonDict,
4
+ const { cloneCsnNonDict,
5
5
  forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
6
6
  getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
7
7
  isAspect, walkCsnPath, isPersistedOnDatabase,
@@ -113,11 +113,26 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
113
113
 
114
114
  const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
115
115
 
116
+ let csnUtils;
116
117
  let message, error, warning, info; // message functions
117
118
  /** @type {() => void} */
118
119
  let throwWithAnyError;
119
- let artifactRef, inspectRef, effectiveType, get$combined,
120
- addDefaultTypeFacets, expandStructsInExpression; // transformUtils
120
+ // csnUtils
121
+ let artifactRef,
122
+ inspectRef,
123
+ effectiveType,
124
+ get$combined,
125
+ getCsnDef,
126
+ isAssocOrComposition,
127
+ addStringAnnotationTo,
128
+ cloneWithTransformations;
129
+ // transformUtils
130
+ let addDefaultTypeFacets,
131
+ expandStructsInExpression,
132
+ flattenStructuredElement,
133
+ flattenStructStepsInRef,
134
+ isAssociationOperand,
135
+ isDollarSelfOrProjectionOperand;
121
136
 
122
137
  bindCsnReference();
123
138
 
@@ -133,10 +148,13 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
133
148
  if (!doA2J)
134
149
  forEachDefinition(csn, handleMixinOnConditions);
135
150
 
151
+ // replace all type refs to builtin types with direct type
152
+ transformUtils.rewriteBuiltinTypeRef(csn);
153
+
136
154
  timetrace.start('Validate');
137
155
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
138
156
  const cleanup = validate.forRelationalDB(csn, {
139
- message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, isAspect
157
+ message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils, csn, options, isAspect
140
158
  });
141
159
  timetrace.stop('Validate');
142
160
 
@@ -161,7 +179,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
161
179
  timetrace.start('temporal');
162
180
  // (001) Add a temporal where condition to views where applicable before assoc2join
163
181
  // assoc2join eventually rewrites the table aliases
164
- forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
182
+ forEachDefinition(csn, temporal.getViewDecorator(csn, {info}, csnUtils));
165
183
  timetrace.stop('temporal');
166
184
 
167
185
  // check unique constraints - further processing is done in rewriteUniqueConstraints
@@ -169,7 +187,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
169
187
 
170
188
  if(doA2J) {
171
189
  // Expand a structured thing in: keys, columns, order by, group by
172
- expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError});
190
+ expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError}, csnUtils);
173
191
  bindCsnReference();
174
192
  }
175
193
 
@@ -220,20 +238,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
220
238
  }
221
239
  });
222
240
 
223
- const {
224
- flattenStructuredElement,
225
- flattenStructStepsInRef,
226
- isAssociationOperand, isDollarSelfOrProjectionOperand,
227
- csnUtils,
228
- } = transformUtils.getTransformers(csn, options, pathDelimiter);
229
-
230
- const {
231
- getCsnDef,
232
- isAssocOrComposition,
233
- addStringAnnotationTo,
234
- cloneWithTransformations,
235
- } = csnUtils;
236
-
237
241
  timetrace.start('Transform CSN')
238
242
 
239
243
  // (000) Rename primitive types, make UUID a String
@@ -260,11 +264,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
260
264
  forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
261
265
 
262
266
  // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
263
- doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
267
+ doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
264
268
 
265
269
  doA2J && forEachDefinition(csn, flattenIndexes);
266
270
  // Managed associations get an on-condition - in views and entities
267
- doA2J && associations.attachOnConditions(csn, pathDelimiter);
271
+ doA2J && associations.attachOnConditions(csn, csnUtils, pathDelimiter);
268
272
 
269
273
  // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
270
274
  // and make them entities
@@ -274,7 +278,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
274
278
  // To be done after handleAssociations, since then the foreign keys of the managed assocs
275
279
  // are part of the elements
276
280
  if (doA2J)
277
- forEachDefinition(csn, associations.getFKAccessFinalizer(csn, pathDelimiter));
281
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, pathDelimiter));
278
282
 
279
283
  // Create convenience views for localized entities/views.
280
284
  // To be done after getFKAccessFinalizer because associations are
@@ -330,7 +334,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
330
334
  assertUnique.rewrite(csn, options, pathDelimiter);
331
335
 
332
336
  // Associations that point to things marked with @cds.persistence.skip are removed
333
- forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
337
+ forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}, csnUtils));
334
338
 
335
339
  // Apply view-specific transformations
336
340
  // (160) Projections now finally become views
@@ -424,9 +428,25 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
424
428
 
425
429
  function bindCsnReference(){
426
430
  ({ error, warning, info, message, throwWithAnyError } = makeMessageFunction(csn, options, moduleName));
427
- ({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));
428
- // TODO: Can we use csnUtils of the call above (transformUtils.getTransformers)?
429
- ({ artifactRef, inspectRef, effectiveType, get$combined } = getUtils(csn));
431
+
432
+ ({ flattenStructuredElement,
433
+ flattenStructStepsInRef,
434
+ isAssociationOperand,
435
+ isDollarSelfOrProjectionOperand,
436
+ addDefaultTypeFacets,
437
+ expandStructsInExpression,
438
+ csnUtils
439
+ } = transformUtils.getTransformers(csn, options, pathDelimiter));
440
+
441
+ ({ artifactRef,
442
+ inspectRef,
443
+ effectiveType,
444
+ get$combined,
445
+ getCsnDef,
446
+ isAssocOrComposition,
447
+ addStringAnnotationTo,
448
+ cloneWithTransformations
449
+ } = csnUtils);
430
450
  }
431
451
 
432
452
  function bindCsnReferenceOnly(){
@@ -445,7 +465,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
445
465
  .filter((c) => {
446
466
  return c.ref && c.ref.length > 1;
447
467
  })
448
- .map((usedAssoc) => {
468
+ .forEach((usedAssoc) => {
449
469
  const assocName = pathId(usedAssoc.ref[0]);
450
470
  const mixinAssociation = mixin[assocName];
451
471
  if(mixinAssociation){
@@ -528,6 +548,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
528
548
  addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
529
549
 
530
550
  forEachMemberRecursively(artifact, (member, memberName, property, path) => {
551
+ if (memberName === '' && property === 'params')
552
+ return; // ignore "returns" type
531
553
  transformCommon(member, memberName, path);
532
554
  // (240 b) Annotate elements, foreign keys, parameters etc with their DB names
533
555
  // Virtual elements in entities and types are not annotated, as they have no DB representation.
@@ -874,9 +896,9 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
874
896
  // a backlink association element from an entity, the forward link will point to the entity,
875
897
  // not to the view).
876
898
  // FIXME: This also means that corresponding key fields should be in the select list etc ...
877
- if (!art.query && !art.projection && assoc.target && assoc.target != artifactName)
878
- error(null, path, { name: '$self' }, 'Only an association that points back to this artifact can be compared to $(NAME)');
879
-
899
+ if (!art.query && !art.projection && assoc.target && assoc.target !== artifactName)
900
+ error( null, path, { id: '$self', name: artifactName, target: assoc.target },
901
+ 'Expected association using $(ID) to point back to $(NAME) but found $(TARGET)' );
880
902
 
881
903
  // Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
882
904
  if (assoc.on) {
@@ -997,14 +1019,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
997
1019
  function _check(node, nodeName, model, path) {
998
1020
  if (node.type) {
999
1021
  const absolute = node.type;
1000
- const parameters = node.parameters || [];
1001
- // :FIXME: Is this dead code? node.parameters is always undefined...
1002
- // forRelationalDB tested against the parameters of the type definition which is not available in CSN
1003
- for (const name in parameters) {
1004
- const param = parameters[name];
1005
- if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
1006
- error('missing-type-parameter', path, { name: param, id: absolute, $reviewed: false });
1007
- }
1008
1022
  switch (absolute) {
1009
1023
  case 'cds.String':
1010
1024
  case 'cds.Binary':
@@ -9,6 +9,7 @@ const { isArtifactInSomeService, isArtifactInService } = require('./utils');
9
9
 
10
10
  function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
11
11
  const isV4 = options.odataVersion === 'v4';
12
+ const special$self = !csn?.definitions?.$self && '$self';
12
13
  forEachDefinition(csn, (def, defName) => {
13
14
  // Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
14
15
  forEachMemberRecursively(def, (member) => {
@@ -100,109 +101,132 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
100
101
  // EDMX at the moment and the reference in the OData CSN is fulfilled.
101
102
  if (node.kind === 'event') return;
102
103
 
103
- // elements have precedence over type
104
- if (node.type && (!isBuiltinType(node.type) && isExpandable(node, defName) || node.kind === 'type')) {
105
- // 1. Get the final type of the node (resolve derived type chain)
106
- const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
107
- if (finalType) {
108
- // The type replacement depends on whether 'node' is a definition or a member[element].
109
- if (node.kind) {
110
- // It is a definition and we expand to builtin type and to elements
111
- // type T: S; --> Integer;
112
- // type S: X; --> Integer;
113
- // type X: Integer;
114
- //
115
- // type A: B; -> {...}
116
- // type B: C; -> { ... }
117
- // type C { .... };
118
- if (isBuiltinType(finalType.type)) {
119
- // use transformUrilsNew::toFinalBaseType for the moment,
120
- // as it is collects along the chain of types
121
- // attributes that need to be propagated
122
- // enum, length, scale, etc.
123
- transformers.toFinalBaseType(node);
124
- // node.type = finalType;
125
- }
126
- else if (csnUtils.isStructured(finalType)) {
127
- cloneElements(finalType);
104
+ if(node.type && !isBuiltinType(node.type)) {
105
+ const finalBaseType = csnUtils.getFinalBaseTypeWithProps(node.type);
106
+ if(!finalBaseType) {
107
+ /*
108
+ type could not be resolved, set it to null
109
+ Today, all type refs must be resolvable,
110
+ input validations checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
111
+ guarantee this. In the future this may change.
112
+ */
113
+ node.type = null;
114
+ }
115
+ else {
116
+ if (isExpandable(finalBaseType) || node.kind === 'type') {
117
+ // 1. Get the final type of the node (resolve derived type chain)
118
+ if (finalBaseType.type !== special$self) {
119
+ // The type replacement depends on whether 'node' is a definition or a member[element].
120
+ if (node.kind) {
121
+ /*
122
+ It is a definition and we expand to builtin type and to elements
123
+ type T: S; --> Integer;
124
+ type S: X; --> Integer;
125
+ type X: Integer;
126
+
127
+ type A: B; -> {...}
128
+ type B: C; -> { ... }
129
+ type C { .... };
130
+ */
131
+ if (isBuiltinType(finalBaseType.type)) {
132
+ /*
133
+ use transformUtilsNew::toFinalBaseType for the moment,
134
+ as it is collects along the chain of types
135
+ attributes that need to be propagated
136
+ enum, length, scale, etc.
137
+ */
138
+ transformers.toFinalBaseType(node);
139
+ }
140
+ else if (csnUtils.isStructured(finalBaseType)) {
141
+ cloneElements(finalBaseType);
142
+ }
143
+ else if (node.type && node.items)
144
+ delete node.type;
145
+ }
146
+ else {
147
+ /*
148
+ this is a member and we expand to final base only if builtin
149
+ type T: S; --> Integer;
150
+ type S: X; --> Integer;
151
+ type X: Integer;
152
+
153
+ type {
154
+ struct_elt: many A; ---> stays the same
155
+ scalar_elt: T; ---> Integer;
156
+ type_ref_elt: type of struct_elt;
157
+ };
158
+ type A: B; -> {...}
159
+ type B: C; -> { ... }
160
+ type C { .... };
161
+ */
162
+ if (isBuiltinType(finalBaseType.type)) {
163
+ /*
164
+ use transformUtilsNew::toFinalBaseType for the moment,
165
+ as it is collects along the chain of types
166
+ attributes that need to be propagated
167
+ enum, length, scale, etc.
168
+ */
169
+ transformers.toFinalBaseType(node);
170
+ // node.type = finalType;
171
+ }
172
+ else if (node.type && node.type.ref) {
173
+ cloneElements(finalBaseType);
174
+ }
175
+ }
128
176
  }
129
- else if (node.type && node.items)
130
- delete node.type;
131
177
  }
132
- else {
133
- // this is a member and we expand to final base only if builtin
134
- // type T: S; --> Integer;
135
- // type S: X; --> Integer;
136
- // type X: Integer;
137
- //
138
- // type {
139
- // struct_elt: many A; ---> stays the same
140
- // scalar_elt: T; ---> Integer;
141
- // type_ref_elt: type of struct_elt;
142
- // };
143
- // type A: B; -> {...}
144
- // type B: C; -> { ... }
145
- // type C { .... };
146
- if (isBuiltinType(finalType.type)) {
147
- // use transformUtilsNew::toFinalBaseType for the moment,
148
- // as it is collects along the chain of types
149
- // attributes that need to be propagated
150
- // enum, length, scale, etc.
151
- transformers.toFinalBaseType(node);
152
- // node.type = finalType;
153
- }
154
- else if (node.type && node.type.ref) {
155
- cloneElements(finalType);
178
+ if (/*the resolved type is not built in*/ !isBuiltinType(node.type)) {
179
+ // handle array of defined via a named type
180
+ // example in actions: 'action act() return Primitive; type Primitive: array of String;'
181
+ const currService = csnUtils.getServiceName(defName);
182
+ const isArrayOfBuiltin = finalBaseType.items &&
183
+ isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalBaseType.items.type)?.type)
184
+ if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
185
+ node.items = finalBaseType.items;
186
+ delete node.type;
156
187
  }
157
188
  }
158
189
  }
159
190
  }
160
- if (node.type && !isBuiltinType(node.type)) {
161
- // handle array of defined via a named type
162
- // example in actions: 'action act() return Primitive; type Primitive: array of String;'
163
- const currService = csnUtils.getServiceName(defName);
164
- const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
165
- const isArrayOfBuiltin = finalType?.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)
166
- if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
167
- node.items = finalType.items;
168
- delete node.type;
169
- }
170
- }
171
191
 
172
- function cloneElements(finalType) {
192
+ function cloneElements(finalBaseType) {
173
193
  // cloneCsn only works correctly if we start "from the top"
174
194
  let clone;
175
195
  // do the clone only if really needed
176
- if((finalType.items && !node.items) ||
177
- (finalType.elements && !node.elements))
178
- clone = cloneCsnNonDict({ definitions: { 'TypeDef': finalType } }, options);
179
- if (finalType.items) {
196
+ if((finalBaseType.items && !node.items) ||
197
+ (finalBaseType.elements && !node.elements))
198
+ clone = cloneCsnNonDict({ definitions: { 'TypeDef': finalBaseType } }, options);
199
+ if (finalBaseType.items) {
180
200
  delete node.type;
181
201
  if(!node.items)
182
202
  Object.assign(node, { items: clone.definitions.TypeDef.items });
183
203
  }
184
- if (finalType.elements) {
185
- if(!finalType.items)
204
+ if (finalBaseType.elements) {
205
+ if(!finalBaseType.items)
186
206
  delete node.type;
187
207
  if(!node.elements)
188
208
  Object.assign(node, { elements: clone.definitions.TypeDef.elements });
189
209
  }
190
210
  }
191
- }
192
211
 
193
- function isExpandable(node, defName) {
194
- return !isV4 || !isUserDefinedBuiltinFromTheCurrService(node, defName);
195
- }
212
+ /*
213
+ Check, if a type needs to be expanded into the service
196
214
 
197
- function isUserDefinedBuiltinFromTheCurrService(node, defName) {
198
- // in V4 we should use TypeDefinitions whenever possible, thus in case the final type of a field is
199
- // a builtin from the service - do not expand to the final base type
200
- let finalBaseType = csnUtils.getFinalBaseTypeWithProps(node.type).type;
201
- // if (finalBaseType && finalBaseType.items) finalBaseType = csnUtils.getFinalBaseTypeWithProps(finalBaseType.items);
202
- const currService = csnUtils.getServiceName(defName);
203
- return node.type && !node.type.ref
204
- && isBuiltinType(finalBaseType) && !csnUtils.isAssocOrComposition(finalBaseType)
205
- && isArtifactInService(node.type, currService);
215
+ All types are expansion candidates except these in V4:
216
+ - it's a builtin
217
+ - it's an assoc
218
+ - the referred type is defined in the service
219
+ */
220
+ function isExpandable(finalBaseType) {
221
+ // in V4 we should use TypeDefinitions whenever possible, thus in case the final type of a field is
222
+ // a builtin from the service - do not expand to the final base type
223
+
224
+ const currService = csnUtils.getServiceName(defName);
225
+ const isBuiltin = isBuiltinType(finalBaseType.type);
226
+ const isAssoc = node.target;
227
+ const isInCurServ = isArtifactInService(node.type, currService);
228
+ return !isV4 || !(isBuiltin && !isAssoc && isInCurServ);
229
+ }
206
230
  }
207
231
  }
208
232
 
@@ -9,11 +9,13 @@
9
9
  const { setProp } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
11
  const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
12
- const { copyAnnotations } = require('../../model/csnUtils');
12
+ const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
13
13
  const { isBetaEnabled } = require('../../base/model.js');
14
+ const { CompilerAssertion } = require('../../base/error');
14
15
 
15
16
  function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
16
17
  const { error } = message;
18
+ const special$self = !csn?.definitions?.$self && '$self';
17
19
  // are we working with OData proxies or cross-service refs
18
20
  const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
19
21
  // collect in this variable all the newly exposed types
@@ -163,7 +165,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
163
165
  const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
164
166
  getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef);
165
167
  // if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
166
- // been catched by expandToFinalBaseType() (forODataNew must not modify external imported services)
168
+ // been caught by expandToFinalBaseType() (forODataNew must not modify external imported services)
167
169
  if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
168
170
  if(typeDef.items) {
169
171
  newElem.items = typeDef.items;
@@ -214,16 +216,19 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
214
216
  }
215
217
  return { isExposable, typeDef, typeName, isAnonymous };
216
218
 
217
- /**
219
+ /**
218
220
  * Check if the node's type can be exposed:
219
- * 1) If it's an anonymous structured type (items.elements || elements)
221
+ * 1) If it's an anonymous, structured type (items.elements || elements)
220
222
  * 2) If it's a named type resolve to the final type definition and
221
223
  * check if this is a structured type
224
+ *
222
225
  * Returns an object that indicates
223
- * - wether or not the type needs exposure
224
- * - the elements dictionary that needs to be cloned
225
- * - the typeDef, either the resolved type def or the node itself
226
+ * - `isExposable`: whether the type needs exposure
227
+ * - `elements`: dictionary that needs to be cloned
228
+ * - `typeDef`: either the resolved type def or the node itself
229
+ * - `typeName`
226
230
  * - if structured type was anonymously defined
231
+ *
227
232
  * @returns {object} { isExposable, typeDef, typeName, elements, isAnonymous }
228
233
  */
229
234
  function isTypeExposable() {
@@ -236,21 +241,22 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
236
241
  // named type, resolve the type to inspect it
237
242
  let type = node.items?.type || node.type;
238
243
  if(type) {
239
- typeName = (type.ref && csnUtils.getFinalType(type)) || type;
244
+ typeName = (type.ref && csnUtils.artifactRef(type)) || type;
240
245
  const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
241
- if(!isBuiltinType(typeName)) {
242
- rc.typeDef = typeDef = csnUtils.getFinalTypeDef(typeName);
246
+ if(!isBuiltinType(typeName) && typeName !== special$self) {
247
+ rc.typeDef = typeDef = csnUtils.artifactRef(typeName, typeName);
243
248
  if(!isArtifactInService(typeName, serviceName)) {
244
249
  while(!isBuiltinType(typeName)) {
245
- typeDef = csnUtils.getFinalTypeDef(typeName);
246
- if(typeDef) {
250
+ typeDef = csnUtils.artifactRef(typeName, typeName);
251
+ if(typeDef !== typeName) {
252
+ // Implementation note: For `type S: T:struct;`, elements from `T:struct` were already propagated to `S`.
247
253
  if((isTermDef && typeDef.enum) || (rc.elements = (typeDef.items?.elements || typeDef.elements)) !== undefined)
248
254
  return rc;
249
255
  type = typeDef.items?.type || typeDef.type;
250
- typeName = (type.ref && csnUtils.getFinalType(type)) || type;
256
+ typeName = (type.ref && csnUtils.artifactRef(type)) || type;
251
257
  }
252
258
  else {
253
- throw Error(`Please debug me: ${typeName} not found`);
259
+ throw new CompilerAssertion(`Please debug me: ${typeName} not found`);
254
260
  }
255
261
  }
256
262
  }
@@ -285,11 +291,14 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
285
291
  return `${newSchemaName}.${typePlainName.replace(/\./g, '_')}`;
286
292
  } else {
287
293
  const typeContext = csnUtils.getContextOfArtifact(typeName);
288
- const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
294
+ const typeNamespace = getNamespace(csn, typeName);
289
295
  const newSchemaName = `${serviceName}.${typeContext || typeNamespace || fallBackSchemaName}`;
290
296
  // new type name without any prefixes
291
- const typePlainName = typeContext ? defNameWithoutServiceOrContextName(typeName, typeContext)
292
- : typeName.replace(`${typeNamespace}.`, '');
297
+ const typePlainName = typeContext
298
+ ? defNameWithoutServiceOrContextName(typeName, typeContext)
299
+ : (typeNamespace
300
+ ? typeName.replace(typeNamespace + '.', '')
301
+ : typeName);
293
302
  createSchema(newSchemaName);
294
303
  // return the new type name
295
304
  return `${newSchemaName}.${typePlainName.replace(/\./g, '_')}`;
@@ -26,7 +26,7 @@ function getServiceOfArtifact(artName, services) {
26
26
  * @param {string} service Name of the service
27
27
  */
28
28
  function isArtifactInService(artName, service) {
29
- return artName.startsWith(`${service}.`);
29
+ return typeof artName === 'string' ? artName.startsWith(`${service}.`) : false;
30
30
  }
31
31
 
32
32
  /**
@@ -39,7 +39,7 @@
39
39
  * nary: return n-ary or binary tree
40
40
  */
41
41
 
42
- function parseExpr(xpr, state = { anno: 0, array: true, nary: true }) {
42
+ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
43
43
  // Notes:
44
44
  // - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
45
45
  // - xpr's are our CSN expressions, see <https://pages.github.tools.sap/cap/docs/cds/cxn>