@sap/cds-compiler 4.9.4 → 5.0.6

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 (87) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +15 -11
  4. package/bin/cdshi.js +1 -0
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +6 -18
  7. package/lib/api/options.js +3 -11
  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 +29 -29
  12. package/lib/base/messages.js +22 -26
  13. package/lib/base/model.js +0 -2
  14. package/lib/base/node-helpers.js +0 -1
  15. package/lib/checks/enricher.js +1 -5
  16. package/lib/checks/structuredAnnoExpressions.js +30 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +4 -1
  19. package/lib/compiler/base.js +1 -1
  20. package/lib/compiler/builtins.js +18 -2
  21. package/lib/compiler/checks.js +2 -5
  22. package/lib/compiler/define.js +7 -7
  23. package/lib/compiler/extend.js +68 -33
  24. package/lib/compiler/generate.js +1 -1
  25. package/lib/compiler/index.js +23 -6
  26. package/lib/compiler/lsp-api.js +501 -2
  27. package/lib/compiler/populate.js +2 -2
  28. package/lib/compiler/propagator.js +1 -4
  29. package/lib/compiler/resolve.js +2 -15
  30. package/lib/compiler/shared.js +112 -31
  31. package/lib/compiler/tweak-assocs.js +2 -16
  32. package/lib/compiler/utils.js +2 -1
  33. package/lib/compiler/xsn-model.js +4 -0
  34. package/lib/edm/annotations/genericTranslation.js +95 -42
  35. package/lib/edm/csn2edm.js +16 -4
  36. package/lib/edm/edm.js +2 -3
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  38. package/lib/edm/edmPreprocessor.js +1 -7
  39. package/lib/gen/Dictionary.json +29 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +2 -1
  42. package/lib/gen/languageParser.js +4995 -4817
  43. package/lib/json/csnVersion.js +1 -1
  44. package/lib/json/from-csn.js +4 -7
  45. package/lib/json/to-csn.js +23 -12
  46. package/lib/language/antlrParser.js +2 -2
  47. package/lib/language/errorStrategy.js +0 -1
  48. package/lib/language/genericAntlrParser.js +35 -12
  49. package/lib/language/multiLineStringParser.js +3 -2
  50. package/lib/language/textUtils.js +1 -0
  51. package/lib/main.d.ts +28 -9
  52. package/lib/main.js +7 -4
  53. package/lib/model/csnRefs.js +20 -4
  54. package/lib/model/csnUtils.js +0 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +1 -1
  57. package/lib/optionProcessor.js +28 -9
  58. package/lib/render/manageConstraints.js +1 -1
  59. package/lib/render/toCdl.js +36 -7
  60. package/lib/render/toSql.js +1 -0
  61. package/lib/render/utils/common.js +12 -9
  62. package/lib/render/utils/stringEscapes.js +1 -0
  63. package/lib/transform/db/applyTransformations.js +13 -8
  64. package/lib/transform/db/associations.js +62 -54
  65. package/lib/transform/db/expansion.js +1 -6
  66. package/lib/transform/db/flattening.js +89 -111
  67. package/lib/transform/db/temporal.js +3 -4
  68. package/lib/transform/db/views.js +0 -1
  69. package/lib/transform/draft/odata.js +51 -3
  70. package/lib/transform/effective/annotations.js +3 -2
  71. package/lib/transform/effective/flattening.js +135 -0
  72. package/lib/transform/effective/main.js +6 -6
  73. package/lib/transform/effective/types.js +13 -9
  74. package/lib/transform/forOdata.js +0 -2
  75. package/lib/transform/forRelationalDB.js +0 -19
  76. package/lib/transform/localized.js +7 -8
  77. package/lib/transform/odata/flattening.js +39 -31
  78. package/lib/transform/odata/typesExposure.js +5 -17
  79. package/lib/transform/transformUtils.js +1 -1
  80. package/lib/transform/translateAssocsToJoins.js +21 -3
  81. package/lib/utils/file.js +13 -7
  82. package/lib/utils/moduleResolve.js +59 -8
  83. package/lib/utils/term.js +3 -2
  84. package/package.json +7 -3
  85. package/share/messages/message-explanations.json +2 -0
  86. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  87. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -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
@@ -169,8 +169,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
169
169
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
170
170
  handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
171
171
 
172
- doA2J && flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
173
-
174
172
  // Check if structured elements and managed associations are compared in an expression
175
173
  // and expand these structured elements. This tuple expansion allows all other
176
174
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
@@ -702,23 +700,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
702
700
  newCsn.definitions[artName].technicalConfig = art.technicalConfig;
703
701
 
704
702
  });
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
- }
722
703
  csn = newCsn;
723
704
  }
724
705
 
@@ -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('.'));
@@ -1110,8 +1110,27 @@ function translateAssocsToJoins(model, inputOptions = {})
1110
1110
  }
1111
1111
  else {
1112
1112
  const [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
1113
- const pathStr = path.map(ps => ps.id).join(pathDelimiter);
1114
- replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
1113
+ const rewrittenPath = [];
1114
+ const leafArtifact = path.at(-1)._artifact;
1115
+ // Walk from left to right and search for first assoc. If assocs in filters become join relevant in the future,
1116
+ // i.e. not only fk-access, we need to revisit this
1117
+ for(let i = 0; i < path.length; i++) {
1118
+ const pathStep = path[i];
1119
+ if(pathStep._artifact?.foreignKeys) {
1120
+ const possibleNonAliasedFkName = path.slice(i).map(ps => ps.id).join(pathDelimiter);
1121
+ if(!pathStep._artifact.$flatSrcFKs)
1122
+ setProp(pathStep._artifact, '$flatSrcFKs', flattenElement(pathStep._artifact, true, pathStep._artifact.name.id, pathStep._artifact.name.id));
1123
+ const fk = pathStep._artifact.$flatSrcFKs.find(f => f._artifact === leafArtifact && f.acc.startsWith(possibleNonAliasedFkName));
1124
+ if(fk) {
1125
+ rewrittenPath.push(fk);
1126
+ i = path.length;
1127
+ continue;
1128
+ }
1129
+ }
1130
+
1131
+ rewrittenPath.push(pathStep);
1132
+ }
1133
+ replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: rewrittenPath.map(ps => ps.id).join(pathDelimiter), _artifact: pathNode._artifact } ]));
1115
1134
  }
1116
1135
  }
1117
1136
  } ]
@@ -1691,7 +1710,6 @@ function translateAssocsToJoins(model, inputOptions = {})
1691
1710
  const path = pathDict.path;
1692
1711
  const s = pathAsStr(path, '"');
1693
1712
  const me = env.lead && (env.lead.name.id || env.lead.op);
1694
- // eslint-disable-next-line no-console
1695
1713
  console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
1696
1714
  }
1697
1715
 
package/lib/utils/file.js CHANGED
@@ -49,22 +49,28 @@ function cdsFs( fileCache, enableTrace ) {
49
49
 
50
50
  return {
51
51
  /** @type {function(string, string)} */
52
- readFileAsync: util.promisify(readFile),
52
+ readFileAsync: util.promisify( readFile ),
53
53
  readFile,
54
54
  readFileSync,
55
- isFileAsync: util.promisify(isFile),
56
55
  isFile,
57
56
  isFileSync,
58
- realpathAsync: util.promisify(realpath),
59
- realpath,
57
+ realpath: fs.realpath,
58
+ realpathNative: fs.realpath.native,
60
59
  realpathSync,
60
+ realpathSyncNative,
61
61
  };
62
62
 
63
- function realpath( path, cb ) {
64
- return fs.realpath.native(path, cb);
65
- }
66
63
 
67
64
  function realpathSync( path, cb ) {
65
+ try {
66
+ cb(null, fs.realpathSync(path));
67
+ }
68
+ catch (err) {
69
+ cb(err, null);
70
+ }
71
+ }
72
+
73
+ function realpathSyncNative( path, cb ) {
68
74
  try {
69
75
  cb(null, fs.realpathSync.native(path));
70
76
  }
@@ -70,7 +70,7 @@ function makeModuleResolver( options, fileCache, messageFunctions ) {
70
70
  extensions,
71
71
  isFile: _fs.isFile,
72
72
  readFile: _fs.readFile,
73
- realpath: _fs.realpath,
73
+ realpath: _fs.realpathNative,
74
74
  lookupDirs: _getLookupDirectories( options, messageFunctions ),
75
75
  };
76
76
 
@@ -90,7 +90,20 @@ function makeModuleResolver( options, fileCache, messageFunctions ) {
90
90
  const body = fileCache[res];
91
91
  if (body === undefined || body === true) { // use fs if no or just temp entry
92
92
  dep.absname = res;
93
- _fs.realpath( res, cb );
93
+ _fs.realpath( res, function realPathResult(realpathErr, modulePath) {
94
+ if (realpathErr) {
95
+ cb(realpathErr, modulePath);
96
+ }
97
+ else {
98
+ _fs.realpathNative( res, function nativeRealPathResult(nativeErr, nativePath) {
99
+ if (!nativeErr)
100
+ checkFileCase( dep, modulePath, nativePath, messageFunctions );
101
+ // Pass the _native_ path to ensure that we use the actual
102
+ // file's path (include case-differences)
103
+ cb(realpathErr, nativePath);
104
+ });
105
+ }
106
+ } );
94
107
  }
95
108
  else if (body && typeof body === 'object' && body.realname) {
96
109
  // dep.absname = body.realname;
@@ -138,7 +151,7 @@ function makeModuleResolverSync( options, fileCache, messageFunctions ) {
138
151
  extensions,
139
152
  isFile: _fs.isFileSync,
140
153
  readFile: _fs.readFileSync,
141
- realpath: _fs.realpathSync,
154
+ realpath: _fs.realpathSyncNative,
142
155
  lookupDirs: _getLookupDirectories( options, messageFunctions ),
143
156
  };
144
157
 
@@ -166,11 +179,23 @@ function makeModuleResolverSync( options, fileCache, messageFunctions ) {
166
179
  const body = result ? fileCache[result] : undefined;
167
180
  if (body === undefined || body === true) { // use fs if no or just temp entry
168
181
  dep.absname = result;
169
- _fs.realpathSync( result, (err, modulePath) => {
170
- if (err)
171
- error = err;
172
- else
173
- result = modulePath;
182
+ _fs.realpathSync( result, function realPathResult(realpathErr, modulePath) {
183
+ if (realpathErr) {
184
+ error = realpathErr;
185
+ }
186
+ else {
187
+ _fs.realpathSyncNative( result, function nativeRealPathResult(nativeErr, nativePath) {
188
+ if (nativeErr) {
189
+ error = nativeErr;
190
+ }
191
+ else {
192
+ checkFileCase(dep, modulePath, nativePath, messageFunctions);
193
+ // Use the _native_ path to ensure that we use the actual
194
+ // file's path (include case-differences)
195
+ result = nativePath;
196
+ }
197
+ });
198
+ }
174
199
  });
175
200
  }
176
201
  else if (body && typeof body === 'object' && body.realname) {
@@ -533,6 +558,32 @@ function packageCdsMain( pkg ) {
533
558
  return null;
534
559
  }
535
560
 
561
+ /**
562
+ * Check if the given paths for case-differences. If there are case differences
563
+ * emit a warning. This can happen on systems with case-insensitive file
564
+ * systems. As that is a hard-to-debug issue, we help the user by emitting
565
+ * a corresponding warning.
566
+ *
567
+ * @param {object} dep
568
+ * @param {string} realpath
569
+ * @param {string} nativeRealpath
570
+ * @param {Function} warning
571
+ */
572
+ function checkFileCase( dep, realpath, nativeRealpath, { warning } ) {
573
+ if (realpath === nativeRealpath)
574
+ return;
575
+ if (realpath.toLowerCase() !== nativeRealpath.toLowerCase()) {
576
+ // safe-guard: in case realpath() resolved symlinks more deeply or sockets/pipes were used,
577
+ // which realpath.native() handles differently, don't report a possible false positive.
578
+ return;
579
+ }
580
+ for (const using of dep.usingFroms) {
581
+ warning('file-unexpected-case-mismatch', [ using.location, using ], {},
582
+ // eslint-disable-next-line max-len
583
+ 'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
584
+ }
585
+ }
586
+
536
587
  /**
537
588
  * @typedef {object} ResolveConfig
538
589
  * @property {string[]} lookupDirs
package/lib/utils/term.js CHANGED
@@ -57,8 +57,9 @@ function term( useColor = 'auto' ) {
57
57
  // > that, when present (regardless of its value), prevents the addition
58
58
  // > of ANSI color.
59
59
  // Note: To be able to disable colors in tests, we check the environment
60
- // variable here again.
61
- hasColor = hasColorShell && (process.env.NO_COLOR === undefined);
60
+ // variables again.
61
+ hasColor = (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') ||
62
+ (hasColorShell && process.env.NO_COLOR === undefined);
62
63
  break;
63
64
  }
64
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "4.9.4",
3
+ "version": "5.0.6",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -24,11 +24,15 @@
24
24
  "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",
25
25
  "deployHanaSql": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hana-sql.js",
26
26
  "deployHdiHdbcds": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hdi.hdbcds.js",
27
+ "deployHdi": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdi test3/test.deploy.hdi.hdbcds.js",
28
+ "deployHdbcds": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdbcds test3/test.deploy.hdi.hdbcds.js",
27
29
  "deployGitDiffs": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.git-diffs.js",
30
+ "deployHdbcdsGitDiffs": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdbcds test3/test.deploy.git-diffs.js",
31
+ "deployHdiGitDiffs": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdi test3/test.deploy.git-diffs.js",
28
32
  "gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
29
33
  "coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
30
34
  "coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
31
- "lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint . && cd ../../ && node scripts/check-changelog.js",
35
+ "lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint . && cd ../../ && node scripts/check-changelog.js",
32
36
  "tslint": "tsc --pretty -p .",
33
37
  "updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
34
38
  "updateTocs": "node scripts/update-toc.js",
@@ -60,6 +64,6 @@
60
64
  "LICENSE"
61
65
  ],
62
66
  "engines": {
63
- "node": ">=16"
67
+ "node": ">=18"
64
68
  }
65
69
  }
@@ -14,6 +14,8 @@
14
14
  "rewrite-not-supported",
15
15
  "syntax-expecting-unsigned-int",
16
16
  "type-missing-enum-value",
17
+ "type-unexpected-foreign-keys",
18
+ "type-unexpected-on-condition",
17
19
  "wildcard-excluding-one"
18
20
  ]
19
21
  }
@@ -0,0 +1,52 @@
1
+ # type-unexpected-foreign-keys
2
+
3
+ Foreign keys were specified in a composition-of-aspect.
4
+
5
+ Compositions of aspects are managed by the compiler.
6
+ Specifying a foreign key list is not supported.
7
+ If you need to specify foreign keys, use a composition
8
+ of an entity instead.
9
+
10
+ The message's severity is `Error`.
11
+
12
+ ## Example
13
+
14
+ Erroneous code example:
15
+
16
+ ```cds
17
+ aspect Item {
18
+ key ID : UUID;
19
+ field : String;
20
+ };
21
+ entity Model {
22
+ key ID : UUID;
23
+ Item : Composition of Item { ID }; // ❌
24
+ };
25
+ ```
26
+
27
+ `Item` is an aspect. Because an explicit list of foreign keys is specified,
28
+ the compiler rejects this CDS snippet. With an explicit foreign key list,
29
+ only entities can be used, but not aspects.
30
+
31
+ ## How to Fix
32
+
33
+ Either remove the explicit list of foreign keys and let the compiler handle
34
+ the composition, or use a composition of entity instead.
35
+
36
+ ```cds
37
+ aspect Item {
38
+ key ID : UUID;
39
+ field : String;
40
+ };
41
+ entity Model {
42
+ key ID : UUID;
43
+ Item : Composition of Model.Item { ID }; // ok
44
+ };
45
+ entity Model.Item : Item { };
46
+ ```
47
+
48
+ The snippet uses a user-defined entity, that includes the aspects.
49
+
50
+ ## Related Messages
51
+
52
+ - `type-unexpected-on-condition`
@@ -0,0 +1,52 @@
1
+ # type-unexpected-on-condition
2
+
3
+ An ON-condition was specified in a composition-of-aspect.
4
+
5
+ Compositions of aspects are managed by the compiler.
6
+ Specifying an ON-condition is not supported.
7
+ If you need to specify an ON-condition, use a composition
8
+ of an entity instead.
9
+
10
+ The message's severity is `Error`.
11
+
12
+ ## Example
13
+
14
+ Erroneous code example:
15
+
16
+ ```cds
17
+ aspect Item {
18
+ key ID : UUID;
19
+ field : String;
20
+ };
21
+ entity Model {
22
+ key ID : UUID;
23
+ Item : Composition of Item on Item.ID = ID; // ❌
24
+ };
25
+ ```
26
+
27
+ `Item` is an aspect. Because an ON-condition is specified, the compiler
28
+ rejects this CDS snippet. With an ON-condition, only entities can be used,
29
+ but not aspects.
30
+
31
+ ## How to Fix
32
+
33
+ Either remove the ON-condition and let the compiler handle
34
+ the composition, or use a composition of entity instead.
35
+
36
+ ```cds
37
+ aspect Item {
38
+ key ID : UUID;
39
+ field : String;
40
+ };
41
+ entity Model {
42
+ key ID : UUID;
43
+ Item : Composition of Model.Item on Item.ID = ID; // ok
44
+ };
45
+ entity Model.Item : Item { };
46
+ ```
47
+
48
+ The snippet uses a user-defined entity, that includes the aspects.
49
+
50
+ ## Related Messages
51
+
52
+ - `type-unexpected-foreign-keys`