@sap/cds-compiler 4.9.6 → 5.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachDefinition, forEachMemberRecursively, findAnnotationExpression, applyTransformationsOnNonDictionary, transformExpression,
5
+ } = require('../../model/csnUtils');
6
+ const { getStructStepsFlattener } = require('../db/flattening');
7
+ const { setProp } = require('../../base/model');
8
+ const { forEach } = require('../../utils/objectUtils');
9
+
10
+
11
+ /**
12
+ *
13
+ * @param csn
14
+ * @param options
15
+ * @param csnUtils
16
+ * @param messageFunctions
17
+ */
18
+ function flattenRefs( csn, options, csnUtils, messageFunctions ) {
19
+ const cleanup = [];
20
+ forEachDefinition(csn, (artifact) => {
21
+ if (artifact.elements) {
22
+ const stack = [ { prefix: [], elements: artifact.elements } ];
23
+ while (stack.length > 0) {
24
+ const { prefix, elements } = stack.pop();
25
+ forEach(elements, (elementName, element) => {
26
+ if (element.elements)
27
+ stack.push({ prefix: prefix.concat(elementName), elements: element.elements });
28
+
29
+ const absolutifier = absolutifyPaths(prefix, cleanup);
30
+ // Absolutify paths in on-conditions
31
+ if (element.on) {
32
+ transformExpression(element, 'on', {
33
+ ref: absolutifier,
34
+ }, []);
35
+ }
36
+
37
+ // Absolutify paths in annotation expressions
38
+ Object.keys(element)
39
+ .filter(pn => findAnnotationExpression(element, pn))
40
+ .forEach((anno) => {
41
+ applyTransformationsOnNonDictionary(element, anno, {
42
+ xpr: (parent, prop) => {
43
+ transformExpression(parent, prop, {
44
+ ref: absolutifier,
45
+ }, []);
46
+ },
47
+ }, {}, []);
48
+ if (element[anno].ref)
49
+ absolutifier(element[anno], 'ref', element[anno].ref);
50
+ });
51
+ });
52
+ }
53
+ }
54
+ });
55
+
56
+
57
+ const adaptRefs = [];
58
+ const resolved = new WeakMap();
59
+ const refFlattener = getStructStepsFlattener(csn, options, messageFunctions, resolved, '_', adaptRefs);
60
+
61
+ forEachDefinition(csn, (def, defName) => {
62
+ if (def.kind === 'entity') {
63
+ applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: true, skipDict: { actions: 1 } }, [ 'definitions' ]);
64
+
65
+ adaptRefs.forEach(fn => fn());
66
+ adaptRefs.length = 0;
67
+
68
+ // explicit binding parameter of bound action
69
+ if (def.actions) {
70
+ const special$self = !csn?.definitions?.$self && '$self';
71
+ Object.entries(def.actions).forEach(([ an, a ]) => {
72
+ if (a.params) {
73
+ const params = Object.entries(a.params);
74
+ const firstParam = params[0][1];
75
+ const type = firstParam?.items?.type || firstParam?.type;
76
+ if (type === special$self) {
77
+ const bindingParamName = params[0][0];
78
+ const markBindingParam = {
79
+ ref: (parent, prop, xpr) => {
80
+ if ((xpr[0].id || xpr[0]) === bindingParamName)
81
+ setProp(parent, '$bparam', true);
82
+ },
83
+ };
84
+
85
+ Object.keys(a)
86
+ .filter(pn => findAnnotationExpression(a, pn))
87
+ .forEach((pn) => {
88
+ transformExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
89
+ adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
90
+ adaptRefs.length = 0;
91
+ });
92
+
93
+
94
+ forEachMemberRecursively(a, (member, memberName, prop, path) => {
95
+ Object.keys(member).filter(pn => findAnnotationExpression(member, pn)).forEach((pn) => {
96
+ transformExpression(member, pn, [ markBindingParam, refFlattener ], path);
97
+ adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));
98
+ adaptRefs.length = 0;
99
+ });
100
+ }, [ 'definitions', defName, 'actions', an ]);
101
+ }
102
+ }
103
+ });
104
+ }
105
+ }
106
+ else {
107
+ applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, { processAnnotations: false }, [ 'definitions' ]);
108
+ adaptRefs.forEach(fn => fn());
109
+ adaptRefs.length = 0;
110
+ }
111
+ });
112
+
113
+ cleanup.forEach((obj) => {
114
+ if (obj.ref && obj.ref[0] === '$self')
115
+ obj.ref.shift();
116
+ });
117
+ }
118
+
119
+ function absolutifyPaths(prefix, cleanup) {
120
+ return function absolutify(_parent, _prop, ref) {
121
+ if (ref[0].id || ref[0] !== '$self' && ref[0] !== '$projection' && !ref[0].startsWith('$') && !_parent.param) {
122
+ _parent.ref = [ '$self', ...prefix, ...ref ];
123
+ cleanup.push(_parent);
124
+
125
+ return true;
126
+ }
127
+
128
+ return false;
129
+ };
130
+ }
131
+
132
+
133
+ module.exports = {
134
+ flattenRefs,
135
+ };
@@ -4,6 +4,7 @@ const {
4
4
  getUtils, isAspect, mergeTransformers, applyTransformations,
5
5
  } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
+ const effectiveFlattening = require('./flattening');
7
8
  const flattening = require('../db/flattening');
8
9
  const types = require('./types');
9
10
  // const { addLocalizationViews } = require('../../transform/localized');
@@ -65,8 +66,9 @@ function effectiveCsn( model, options, messageFunctions ) {
65
66
  // Remove properties attached by validator - they do not "grow" as the model grows.
66
67
  cleanup();
67
68
 
68
- flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, new WeakMap(), '_');
69
- flattening.flattenElements(csn, options, messageFunctions, '_');
69
+
70
+ effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
71
+ flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
70
72
 
71
73
  resolveTypesInActionsAfterFlattening();
72
74
 
@@ -76,8 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
76
78
  processCalculatedElementsInEntities(csn);
77
79
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
78
80
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
79
- const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
80
- applyTransformations(csn, transformers, [], { skipIgnore: false });
81
+ const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
82
+ applyTransformations(csn, transformers, [], { skipIgnore: false, processAnnotations: true });
81
83
 
82
84
  if (!options.resolveProjections)
83
85
  redoProjections.forEach(fn => fn());
@@ -18,18 +18,15 @@ const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
18
18
  * @param {CSN.Model} csn will be transformed
19
19
  * @param {object} csnUtils
20
20
  * @param {CSN.Options} options
21
- * @returns {Function} Callback to resolve things (actions and their returns) later - as for them, $self would lead to unresolvable constructs at this point
21
+ * @returns {Function} Callback to resolve types in action returns later - as for them, $self would lead to unresolvable constructs at this point
22
22
  * so we can call this callback after flattening is done - then we can safely resolve their types.
23
23
  */
24
24
  function resolveTypes( csn, csnUtils, options ) {
25
25
  const { getFinalTypeInfo } = csnUtils;
26
26
  const later = [];
27
27
  applyTransformations(csn, {
28
- type: (parent, prop, type, path) => {
29
- const artifact = csn.definitions[path[1]];
30
- // TODO: What about events?
31
- if (!(artifact.kind === 'action' || artifact.kind === 'function'))
32
- resolveType(parent);
28
+ type: (parent) => {
29
+ resolveType(parent);
33
30
  },
34
31
  }, [ (definitions, artifactName, artifact) => {
35
32
  // In a non-flat model, replacing types with some $self inside causes issues for actions (bound or unbound)
@@ -38,12 +35,20 @@ function resolveTypes( csn, csnUtils, options ) {
38
35
  later.push({ [artifactName]: artifact });
39
36
  else if (artifact.actions)
40
37
  later.push(artifact.actions);
41
- } ], { skipDict: { actions: true }, processAnnotations: true });
38
+ } ], { skipStandard: { returns: true }, processAnnotations: true });
42
39
 
40
+ // TODO: Directly push the .returns into the later so we have a more minimal looping
43
41
  return function resolveTypesInActions() {
44
- later.forEach(action => applyTransformationsOnDictionary(action, { type: parent => resolveType(parent) }));
42
+ later.forEach((a) => {
43
+ applyTransformationsOnDictionary(a, {
44
+ type: (parent) => {
45
+ resolveType(parent);
46
+ },
47
+ });
48
+ });
45
49
  };
46
50
 
51
+
47
52
  /**
48
53
  * Resolve a type to its
49
54
  * - elements
@@ -123,7 +128,6 @@ function resolveTypes( csn, csnUtils, options ) {
123
128
  }
124
129
  }
125
130
 
126
-
127
131
  module.exports = {
128
132
  resolve: resolveTypes,
129
133
  };
@@ -167,8 +167,6 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
167
167
  { skipArtifact: isExternalServiceMember }
168
168
  );
169
169
 
170
- flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
171
-
172
170
  // All type refs must be resolved, including external APIs.
173
171
  // OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
174
172
  // If in the future 'other' APIs that might support type refs are imported, these refs must be
@@ -34,6 +34,7 @@ const backlinks = require('./db/backlinks');
34
34
  const { getDefaultTypeLengths } = require('../render/utils/common');
35
35
  const { featureFlags } = require('./db/featureFlags');
36
36
  const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
37
+ const { processSqlServices } = require('./db/processSqlServices');
37
38
 
38
39
  // By default: Do not process non-entities/views
39
40
  function forEachDefinition(csn, cb) {
@@ -169,8 +170,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
169
170
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
170
171
  handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
171
172
 
172
- doA2J && flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
173
-
174
173
  // Check if structured elements and managed associations are compared in an expression
175
174
  // and expand these structured elements. This tuple expansion allows all other
176
175
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
@@ -370,6 +369,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
370
369
  removeKeyPropInType,
371
370
  ]);
372
371
 
372
+ // TODO: Might have to do this earlier if we want special rendering for projections?
373
+ const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
373
374
 
374
375
  // TODO: Could we maybe merge this with the final applyTransformations?
375
376
  applyTransformations(csn, {
@@ -389,6 +390,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
389
390
  // Attach @cds.persistence.name to artifacts
390
391
  if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
391
392
  csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
393
+
394
+ findAndMarkSqlServiceArtifacts(artifact, artifactName);
392
395
  }], { allowArtifact: artifact => artifact.kind === 'entity'});
393
396
 
394
397
  throwWithAnyError();
@@ -702,23 +705,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
702
705
  newCsn.definitions[artName].technicalConfig = art.technicalConfig;
703
706
 
704
707
  });
705
- // restore $fkExtensions and $structRef for foreign key annotations
706
- if (isBetaEnabled(options, 'annotateForeignKeys')) {
707
- forEachDefinition(csn, (oldDef, artName) => {
708
- const newDef = newCsn.definitions[artName];
709
- if(oldDef?.elements) {
710
- Object.entries(oldDef.elements).forEach(([eltName, oldElt]) => {
711
- const newElt = newDef.elements[eltName];
712
- if(oldElt.$fkExtensions)
713
- setProp(newElt, '$fkExtensions', oldElt.$fkExtensions);
714
- oldElt.keys?.forEach((fk, i) => {
715
- if(fk.$structRef && newElt.keys?.[i])
716
- setProp(newElt.keys[i], '$structRef', fk.$structRef);
717
- })
718
- })
719
- }
720
- });
721
- }
708
+
709
+ // To ensure we preserve feature flags
710
+ newCsn.meta = csn.meta;
711
+
722
712
  csn = newCsn;
723
713
  }
724
714
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
- const { setProp, isDeprecatedEnabled, isBetaEnabled} = require('../base/model');
4
+ const { setProp } = require('../base/model');
5
5
  const { forEachKey } = require('../utils/objectUtils');
6
6
  const { cleanSymbols } = require('../base/cleanSymbols.js');
7
7
  const {
@@ -91,6 +91,7 @@ const annoPersistenceSkip = '@cds.persistence.skip';
91
91
  * Deprecated version of localizedLanguageFallback. Do not use.
92
92
  *
93
93
  * @param {boolean} [options.fewerLocalizedViews]
94
+ * Default: true
94
95
  *
95
96
  * @param {object} config
96
97
  * Configuration for creating convenience views. Non-user visible options.
@@ -114,7 +115,8 @@ function _addLocalizationViews(csn, options, config) {
114
115
  const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
115
116
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
116
117
  options.localizedWithoutCoalesce);
117
- const ignoreAssocToLocalized = !!options.fewerLocalizedViews;
118
+ // default is true, hence only check for explicitly disabled option
119
+ const ignoreAssocToLocalized = options.fewerLocalizedViews !== false;
118
120
 
119
121
  createDirectConvenienceViews(); // 1
120
122
  createTransitiveConvenienceViews(); // 2 + 3
@@ -176,7 +178,7 @@ function _addLocalizationViews(csn, options, config) {
176
178
  else
177
179
  view = createLocalizedViewForEntity(art, artName, textElements);
178
180
 
179
- copyPersistenceAnnotations(view, art, options);
181
+ copyPersistenceAnnotations(view, art);
180
182
  csn.definitions[viewName] = view;
181
183
  }
182
184
 
@@ -752,18 +754,15 @@ function copyLocation(target, source) {
752
754
  *
753
755
  * @param {CSN.Artifact} target
754
756
  * @param {CSN.Artifact} source
755
- * @param {CSN.Options} options
756
757
  */
757
- function copyPersistenceAnnotations(target, source, options) {
758
- const copyExists = !isBetaEnabled(options, 'v5preview') &&
759
- !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
758
+ function copyPersistenceAnnotations(target, source) {
760
759
  forEachKey(source, anno => {
761
760
  // Note:
762
761
  // v3/v4: Because `.exists` is copied to the convenience view, it could
763
762
  // lead to some localization views referencing non-existing ones.
764
763
  // But that is the contract: User says that it already exists!
765
764
  // v2/>=v5, `.exists` is never copied.
766
- if (anno === annoPersistenceSkip || (copyExists && anno === '@cds.persistence.exists'))
765
+ if (anno === annoPersistenceSkip)
767
766
  target[anno] = source[anno];
768
767
  });
769
768
  }
@@ -8,8 +8,7 @@ const transformUtils = require('../transformUtils');
8
8
  const { setProp } = require('../../base/model');
9
9
  const { applyTransformationsOnDictionary,
10
10
  applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
11
- const { linkForeignKeyAnnotationExtensionsToAssociation,
12
- handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
11
+ const { handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
13
12
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
14
13
  const { forEach } = require('../../utils/objectUtils');
15
14
 
@@ -293,17 +292,27 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
293
292
 
294
293
  const refCheck = {
295
294
  ref: (elemref, prop, xpr, path) => {
296
- const { art, scope } = inspectRef(path);
295
+ const { links, art, scope } = inspectRef(path);
297
296
  if (scope !== '$magic' && art) {
298
- const ft = csnUtils.getFinalTypeInfo(art.type);
299
- if (!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
300
- error('odata-anno-xpr-ref', path,
301
- {
302
- anno: refCheck.anno,
303
- elemref,
304
- name: refCheck.eltLocationStr,
305
- '#': 'flatten_builtin'
306
- });
297
+ // try to find rightmost 'items', terminate if association comes first.
298
+ let i = links.length-1;
299
+ const getProp = (propName) => links[i].art?.[propName];
300
+
301
+ let hasItems = false;
302
+ for(; i >= 0 && !getProp('target') && !hasItems; i--) {
303
+ hasItems = !!getProp('items')
304
+ }
305
+ if(!hasItems) {
306
+ const ft = csnUtils.getFinalTypeInfo(art.type);
307
+ if (!isBuiltinType(ft?.items?.type || ft?.type) && refCheck.anno !== 'value') {
308
+ error('odata-anno-xpr-ref', path,
309
+ {
310
+ anno: refCheck.anno,
311
+ elemref,
312
+ name: refCheck.eltLocationStr,
313
+ '#': 'flatten_builtin'
314
+ });
315
+ }
307
316
  }
308
317
  }
309
318
  }
@@ -421,24 +430,24 @@ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef,
421
430
 
422
431
  const refCheck = {
423
432
  ref: (elemref, prop, xpr, path) => {
424
- const { links, art } = (elemref._links && elemref._art
425
- ? { links: elemref._links, art: elemref._art }
426
- : inspectRef(path) );
427
-
428
- let i = links.length-2;
429
- const getProp = (propName) =>
430
- (links[i].art?.[propName] ||
431
- effectiveType(links[i].art)[propName]);
432
-
433
- const ft = csnUtils.getFinalTypeInfo(art?.type);
434
- let target = undefined;
435
- for(; i >= 0 && !getProp('items') && !target; i--) {
436
- target = getProp('target');
437
- }
438
- if (target && csn.definitions[target].$flatelements
439
- && !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
440
- error('odata-anno-xpr-ref', path,
441
- { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
433
+ const { links, art, scope } = inspectRef(path);
434
+
435
+ if (scope !== '$magic' && art) {
436
+ let i = links.length-2;
437
+ const getProp = (propName) =>
438
+ (links[i].art?.[propName] ||
439
+ effectiveType(links[i].art)[propName]);
440
+
441
+ let target = undefined;
442
+ for(; i >= 0 && !getProp('items') && !target; i--) {
443
+ target = getProp('target');
444
+ }
445
+ const ft = csnUtils.getFinalTypeInfo(art.type);
446
+ if (target && csn.definitions[target].$flatelements
447
+ && !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
448
+ error('odata-anno-xpr-ref', path,
449
+ { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
450
+ }
442
451
  }
443
452
  }
444
453
  }
@@ -548,7 +557,6 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
548
557
  module.exports = {
549
558
  allInOneFlattening,
550
559
  flattenAllStructStepsInRefs,
551
- linkForeignKeyAnnotationExtensionsToAssociation,
552
560
  handleManagedAssociationsAndCreateForeignKeys,
553
561
  getStructRefFlatteningTransformer
554
562
  };
@@ -8,7 +8,7 @@
8
8
 
9
9
  const { setProp, isBetaEnabled } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
- const { getNamespace, copyAnnotations, findAnnotationExpression,
11
+ const { getNamespace, copyAnnotations,
12
12
  forEachDefinition, forEachMember, forEachGeneric, isEdmPropertyRendered } = require('../../model/csnUtils');
13
13
  const { isBuiltinType } = require('../../base/builtins');
14
14
  const { CompilerAssertion } = require('../../base/error');
@@ -229,22 +229,10 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
229
229
  }
230
230
  });
231
231
 
232
- // TODO: remove the this if and the else clause for the V5 release
233
- if (isBetaEnabled(options, 'v5preview')) {
234
- // We need this check, as we only need to copy the annotions if the type
235
- // is user defined structured type outside of the service
236
- if (!isAnonymous) {
237
- copyAnnotations(typeDef, newType);
238
- }
239
- } else {
240
- // expression annos and their sub annotations are not propagated to type
241
- let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
242
- if (pn[0] === '@')
243
- acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
244
- return acc;
245
- }, [ [], [] ]);
246
- nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
247
- copyAnnotations(typeDef, newType, false, {}, nxprANames);
232
+ // Annotations are propagated only from user defined structured
233
+ // types that need to be added to a service
234
+ if (!isAnonymous) {
235
+ copyAnnotations(typeDef, newType);
248
236
  }
249
237
 
250
238
  // if the origin type had items, add items to exposed type
@@ -266,7 +266,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
266
266
  } else {
267
267
  // Primitive child - clone it and restore its cross references
268
268
  const flatElemName = elemName + pathDelimiter + childName;
269
- const flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
269
+ const flatElem = cloneCsnNonDict(childElem, options);
270
270
  // Don't take over notNull from leaf elements
271
271
  delete flatElem.notNull;
272
272
  setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
@@ -1710,7 +1710,6 @@ function translateAssocsToJoins(model, inputOptions = {})
1710
1710
  const path = pathDict.path;
1711
1711
  const s = pathAsStr(path, '"');
1712
1712
  const me = env.lead && (env.lead.name.id || env.lead.op);
1713
- // eslint-disable-next-line no-console
1714
1713
  console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
1715
1714
  }
1716
1715
 
package/lib/utils/file.js CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const util = require('util');
7
+ const path = require('path');
8
+ const os = require('os');
7
9
 
8
10
  /**
9
11
  * Split the given source string into its lines. Respects Unix,
@@ -16,6 +18,74 @@ function splitLines( src ) {
16
18
  return src.split(/\r\n?|\n/);
17
19
  }
18
20
 
21
+ /**
22
+ * Returns the file's normalized extension, e.g. for `file.CDS` -> `cds`
23
+ * Returns null if the given filename is not a string.
24
+ *
25
+ * @param filename
26
+ * @returns {null|string}
27
+ */
28
+ function fileExtension( filename ) {
29
+ if (typeof filename === 'string')
30
+ return path.extname( filename ).slice(1).toLowerCase();
31
+ return null;
32
+ }
33
+
34
+ /**
35
+ * Create a temporary file path using the system's temporary folder and a filename
36
+ * consisting of the given name/extension and a random string.
37
+ *
38
+ * @param {string} name
39
+ * @param {string} extension
40
+ * @returns {string}
41
+ */
42
+ function tmpFilePath( name, extension ) {
43
+ const crypto = require('crypto');
44
+ const id = crypto.randomBytes(32).toString('hex');
45
+ const filename = `${ name }-${ id }.${ extension }`;
46
+ return path.join(os.tmpdir(), filename);
47
+ }
48
+
49
+ /**
50
+ * Read input from the given stream.
51
+ * See https://nodejs.org/api/stream.html#readablereadsize
52
+ *
53
+ * @returns {Promise<string>}
54
+ */
55
+ function readStream( stream ) {
56
+ return new Promise((resolve, reject) => {
57
+ const chunks = [];
58
+
59
+ const listeners = {
60
+ __proto__: null,
61
+ data: onData,
62
+ error: onError,
63
+ end: onEnd,
64
+ };
65
+ for (const name in listeners)
66
+ stream.on(name, listeners[name]);
67
+
68
+ function onData( chunk ) {
69
+ chunks.push(chunk);
70
+ }
71
+
72
+ function onEnd() {
73
+ removeListeners();
74
+ resolve(chunks.join(''));
75
+ }
76
+
77
+ function onError( error ) {
78
+ removeListeners();
79
+ reject(error);
80
+ }
81
+
82
+ function removeListeners() {
83
+ for (const name in listeners)
84
+ stream.removeListener(name, listeners[name]);
85
+ }
86
+ });
87
+ }
88
+
19
89
  /**
20
90
  * Returns filesystem utils readFile(), isFile(), realpath() for _CDS_ usage.
21
91
  * This includes a trace as well as usage of a file cache.
@@ -49,24 +119,30 @@ function cdsFs( fileCache, enableTrace ) {
49
119
 
50
120
  return {
51
121
  /** @type {function(string, string)} */
52
- readFileAsync: util.promisify(readFile),
122
+ readFileAsync: util.promisify( readFile ),
53
123
  readFile,
54
124
  readFileSync,
55
- isFileAsync: util.promisify(isFile),
56
125
  isFile,
57
126
  isFileSync,
58
- realpathAsync: util.promisify(realpath),
59
- realpath,
127
+ realpath: fs.realpath,
128
+ realpathNative: fs.realpath.native,
60
129
  realpathSync,
130
+ realpathSyncNative,
61
131
  };
62
132
 
63
- function realpath( path, cb ) {
64
- return fs.realpath.native(path, cb);
133
+
134
+ function realpathSync( filepath, cb ) {
135
+ try {
136
+ cb(null, fs.realpathSync(filepath));
137
+ }
138
+ catch (err) {
139
+ cb(err, null);
140
+ }
65
141
  }
66
142
 
67
- function realpathSync( path, cb ) {
143
+ function realpathSyncNative( filepath, cb ) {
68
144
  try {
69
- cb(null, fs.realpathSync.native(path));
145
+ cb(null, fs.realpathSync.native(filepath));
70
146
  }
71
147
  catch (err) {
72
148
  cb(err, null);
@@ -188,5 +264,8 @@ function cdsFs( fileCache, enableTrace ) {
188
264
 
189
265
  module.exports = {
190
266
  splitLines,
267
+ readStream,
268
+ fileExtension,
269
+ tmpFilePath,
191
270
  cdsFs,
192
271
  };