@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -107,9 +107,7 @@
107
107
 
108
108
  'use strict';
109
109
 
110
- const {
111
- makeMessageFunction, searchName, weakLocation,
112
- } = require('../base/messages');
110
+ const { searchName, weakLocation } = require('../base/messages');
113
111
  const {
114
112
  isDeprecatedEnabled, isBetaEnabled,
115
113
  setProp, forEachGeneric, forEachInOrder,
@@ -155,7 +153,7 @@ function getDefinerFunctions( model ) {
155
153
  // Get simplified "resolve" functionality and the message function:
156
154
  const {
157
155
  message, error, warning, info, messages,
158
- } = makeMessageFunction( model, model.options, 'compile' );
156
+ } = model.$messageFunctions;
159
157
  const {
160
158
  resolveUncheckedPath,
161
159
  resolvePath,
@@ -206,7 +204,7 @@ function getDefinerFunctions( model ) {
206
204
 
207
205
  mergeI18nBlocks( model );
208
206
 
209
- if (model.options && model.options.parseCdl) {
207
+ if (options.parseCdl) {
210
208
  initExtensionsWithoutApplying();
211
209
  // Check for redefinitions
212
210
  Object.keys( model.definitions ).forEach( preProcessArtifact );
@@ -218,7 +216,8 @@ function getDefinerFunctions( model ) {
218
216
  applyExtensions();
219
217
 
220
218
  Object.keys( model.definitions ).forEach( preProcessArtifact );
221
- const commonLanguagesEntity = isBetaEnabled( options, 'addTextsLanguageAssoc' ) &&
219
+ const commonLanguagesEntity // TODO: remove beta after a grace period
220
+ = (options.addTextsLanguageAssoc || isBetaEnabled( options, 'addTextsLanguageAssoc' )) &&
222
221
  model.definitions['sap.common.Languages'];
223
222
  addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
224
223
  commonLanguagesEntity.elements.code);
@@ -494,7 +493,7 @@ function getDefinerFunctions( model ) {
494
493
  initDollarSelf( art ); // $self
495
494
  if (art.params)
496
495
  initParams( art ); // $parameters
497
- if (art.includes && !(art.name.absolute in extensionsDict))
496
+ if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
498
497
  extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"
499
498
 
500
499
  if (!art.query)
@@ -609,14 +608,7 @@ function getDefinerFunctions( model ) {
609
608
  if (query.on)
610
609
  initExprForQuery( query.on, query );
611
610
  // TODO: MIXIN with name = ...subquery (not yet supported anyway)
612
- for (const elem of query.columns || []) {
613
- if (elem && (elem.value || elem.expand)) {
614
- setProp( elem, '_block', query._block );
615
- defineAnnotations( elem, elem, query._block );
616
- initExprForQuery( elem.value, query );
617
- initExpandInline( elem );
618
- }
619
- }
611
+ initSelectItems( query, query.columns );
620
612
  if (query.where)
621
613
  initExprForQuery( query.where, query );
622
614
  if (query.having)
@@ -624,22 +616,41 @@ function getDefinerFunctions( model ) {
624
616
  initMembers( query, query, query._block );
625
617
  }
626
618
 
627
- function initExpandInline( elem ) {
628
- // TODO: forbid with :param, global:true, in ref-where, outside queries (CSN), ...
629
- for (const sub of elem.expand || elem.inline || []) {
630
- if (!sub) // parse error
619
+ function initSelectItems( parent, columns ) {
620
+ // TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
621
+ let wildcard = null;
622
+ for (const col of columns || parent.expand || parent.inline || []) {
623
+ if (!col) // parse error
631
624
  continue;
632
- if (elem.value)
633
- setProp( sub, '_pathHead', elem ); // also set for '*' in expand/inline
634
- else if (elem._pathHead)
635
- setProp( sub, '_pathHead', elem._pathHead );
636
- if (sub.value || sub.expand) {
637
- setProp( sub, '_block', elem._block );
638
- defineAnnotations( sub, sub, elem._block ); // TODO: complain with inline
639
- initExpandInline( sub );
625
+ if (!columns) {
626
+ if (parent.value)
627
+ setProp( col, '_pathHead', parent ); // also set for '*' in expand/inline
628
+ else if (parent._pathHead)
629
+ setProp( col, '_pathHead', parent._pathHead );
630
+ }
631
+ if (col.val === '*') {
632
+ if (!wildcard) {
633
+ wildcard = col;
634
+ }
635
+ else {
636
+ // a late syntax error (this code also runs with parse-cdl), i.e.
637
+ // no semantic loc (wouldn't be available for expand/inline anyway)
638
+ error( 'syntax-duplicate-clause', [ col.location, null ],
639
+ { prop: '*', line: wildcard.location.line, col: wildcard.location.col },
640
+ 'You have provided a $(PROP) already at line $(LINE), column $(COL)' );
641
+ // TODO: extra text variants for expand/inline? - probably not
642
+ col.val = null; // do not consider it for expandWildcard()
643
+ }
644
+ }
645
+ else if (col.value || col.expand) {
646
+ setProp( col, '_block', parent._block );
647
+ defineAnnotations( col, col, parent._block ); // TODO: complain with inline
648
+ // TODO: allow sub queries? at least in top-level expand without parallel ref
649
+ if (columns)
650
+ initExprForQuery( col.value, parent );
651
+ initSelectItems( col );
640
652
  }
641
653
  }
642
- // TODO: allow sub queries in top-level expand without parallel ref
643
654
  }
644
655
 
645
656
  function initExprForQuery( expr, query ) {
@@ -658,9 +669,38 @@ function getDefinerFunctions( model ) {
658
669
  }
659
670
  else if (expr.path && expr.$expected === 'exists') {
660
671
  expr.$expected = 'approved-exists';
672
+ approveExistsInChildren(expr);
661
673
  }
662
674
  }
663
675
 
676
+ /**
677
+ * If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
678
+ * since we will have a top-level subquery after exists-processing in the forHanaNew.
679
+ *
680
+ * Recursively drill down into:
681
+ * - the .path
682
+ * - the .args
683
+ * - the .where.args
684
+ *
685
+ * Any $expected === 'exists' encountered along the way are turned into 'approved-exists'
686
+ *
687
+ * working: exists toE[exists toE] -> select from E where exists toE
688
+ * not working: toE[exists toE] -> we don't support subqueries in filters
689
+ *
690
+ * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
691
+ */
692
+ function approveExistsInChildren(exprOrPathElement) {
693
+ if (exprOrPathElement.$expected === 'exists')
694
+ exprOrPathElement.$expected = 'approved-exists';
695
+ // Drill down
696
+ if (exprOrPathElement.args)
697
+ exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
698
+ else if (exprOrPathElement.where && exprOrPathElement.where.args)
699
+ exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
700
+ else if (exprOrPathElement.path)
701
+ exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
702
+ }
703
+
664
704
  // table is table expression in FROM, becomes an alias
665
705
  function initTableExpression( table, query, joinParents ) {
666
706
  if (!table) // parse error
@@ -708,6 +748,8 @@ function getDefinerFunctions( model ) {
708
748
  // ? ta._joinParent.args[ta.$joinArgsIndex] // in JOIN
709
749
  // : ta._parent.from ) // directly in FROM
710
750
  // Note for --raw-output: _joinParent pointing to CROSS JOIN node has not name
751
+ if (!tab) // parse error; time for #6241
752
+ return; // (parser method to only add non-null to array)
711
753
  setProp( tab, '_joinParent', table );
712
754
  tab.$joinArgsIndex = index;
713
755
  initTableExpression( tab, query, joinParents );
@@ -847,7 +889,7 @@ function getDefinerFunctions( model ) {
847
889
  // TODO: error if CSN has both target.elements and targetAspect.elements -> delete target
848
890
  return true;
849
891
  }
850
- if (elem.targetAspect || !isDirectComposition( elem ))
892
+ if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
851
893
  return false;
852
894
  const name = resolveUncheckedPath( target, 'compositionTarget', elem );
853
895
  const aspect = name && model.definitions[name];
@@ -861,6 +903,7 @@ function getDefinerFunctions( model ) {
861
903
  * (which is basically the component name of the `parent` element plus a dot).
862
904
  */
863
905
  function initMembers( construct, parent, block, initExtensions = false ) {
906
+ // TODO: split extend from init
864
907
  const isQueryExtension = kindProperties[construct.kind].isExtension &&
865
908
  (parent._main || parent).query;
866
909
  let obj = construct;
@@ -916,7 +959,7 @@ function getDefinerFunctions( model ) {
916
959
  forEachInOrder( construct, 'params', init );
917
960
  const { returns } = construct;
918
961
  if (returns) {
919
- returns.kind = 'param';
962
+ returns.kind = (kindProperties[construct.kind].isExtension) ? construct.kind : 'param';
920
963
  init( returns, '' ); // '' is special name for returns parameter
921
964
  }
922
965
  return;
@@ -985,6 +1028,7 @@ function getDefinerFunctions( model ) {
985
1028
  }
986
1029
 
987
1030
  function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
1031
+ // TODO: do differently, see also annotateMembers() in resolver
988
1032
  // To have been checked by parsers:
989
1033
  // - artifacts (CDL-only anyway) only inside [extend] context|service
990
1034
  if (!dict)
@@ -1269,7 +1313,7 @@ function getDefinerFunctions( model ) {
1269
1313
  }
1270
1314
 
1271
1315
  function createTargetEntity( target, elem, keys, entityName, base ) {
1272
- const location = elem.target && elem.target.location || elem.location;
1316
+ const { location } = elem.targetAspect || elem.target || elem;
1273
1317
  elem.on = {
1274
1318
  location,
1275
1319
  op: { val: '=', location },
@@ -1314,7 +1358,7 @@ function getDefinerFunctions( model ) {
1314
1358
  // If 'up_' shall be rendered unmanaged, infer the parent
1315
1359
  // primary keys and add the ON condition
1316
1360
  if (isDeprecatedEnabled( options, 'unmanagedUpInComponent' )) {
1317
- addProxyElements( art, keys, 'aspect-composition', location,
1361
+ addProxyElements( art, keys, 'aspect-composition', target.name && location,
1318
1362
  'up__', '@odata.containment.ignore' );
1319
1363
  up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
1320
1364
  }
@@ -1328,7 +1372,7 @@ function getDefinerFunctions( model ) {
1328
1372
  setLink( art, '_base', base._base || base );
1329
1373
 
1330
1374
  dictAdd( art.elements, 'up_', up);
1331
- addProxyElements( art, target.elements, 'aspect-composition', location );
1375
+ addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
1332
1376
 
1333
1377
  setLink( art, '_block', model.$internal );
1334
1378
  model.definitions[entityName] = art;
@@ -1375,7 +1419,7 @@ function getDefinerFunctions( model ) {
1375
1419
  const exts = model.$lateExtensions[name];
1376
1420
  if (art && art.kind !== 'namespace') {
1377
1421
  if (art.builtin) {
1378
- for (const ext in exts)
1422
+ for (const ext of exts)
1379
1423
  info( 'anno-builtin', [ ext.name.location, ext ] );
1380
1424
  }
1381
1425
  // created texts entity, autoexposed entity
@@ -1423,6 +1467,12 @@ function getDefinerFunctions( model ) {
1423
1467
 
1424
1468
  model.extensions.push(annotationArtifact);
1425
1469
  extendArtifact( exts, annotationArtifact ); // also sets _artifact link in extensions
1470
+ // if one of the annotate statement mentions 'returns', assume it
1471
+ // TODO: with warning/info?
1472
+ for (const ext of exts) {
1473
+ if (ext.$syntax === 'returns')
1474
+ annotationArtifact.$syntax = 'returns';
1475
+ }
1426
1476
  }
1427
1477
  }
1428
1478
  }
@@ -1706,6 +1756,7 @@ function getDefinerFunctions( model ) {
1706
1756
  }
1707
1757
 
1708
1758
  function extendMembers( extensions, art, noExtend ) {
1759
+ // TODO: do the whole extension stuff lazily if the elements are requested
1709
1760
  const elemExtensions = [];
1710
1761
  extensions.sort( compareLayer );
1711
1762
  for (const ext of extensions) {
@@ -1751,7 +1802,12 @@ function getDefinerFunctions( model ) {
1751
1802
  [ 'elements', 'actions' ].forEach( (prop) => {
1752
1803
  const dict = art._extend && art._extend[prop];
1753
1804
  for (const name in dict) {
1754
- const validDict = art[prop] || prop === 'elements' && art.enum;
1805
+ let obj = art;
1806
+ if (obj.targetAspect)
1807
+ obj = obj.targetAspect;
1808
+ while (obj.items)
1809
+ obj = obj.items;
1810
+ const validDict = obj[prop] || prop === 'elements' && obj.enum;
1755
1811
  const member = validDict[name];
1756
1812
  if (!member)
1757
1813
  extendNothing( dict[name], prop, name, art, validDict );
@@ -1989,8 +2045,9 @@ function getDefinerFunctions( model ) {
1989
2045
  }
1990
2046
 
1991
2047
  if (isKey && isLocalized) { // key with localized is wrong - ignore localized
1992
- warning( 'localized-key', [ elem.localized.location, elem ], {},
1993
- 'Keyword "localized" is ignored for primary keys' );
2048
+ const errpos = elem.localized || elem.type || elem.name;
2049
+ warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
2050
+ 'Keyword $(KEYWORD) is ignored for primary keys' );
1994
2051
  }
1995
2052
  }
1996
2053
  if (textElems.length <= keys)
@@ -2132,9 +2189,9 @@ function getDefinerFunctions( model ) {
2132
2189
  });
2133
2190
  }
2134
2191
  }
2135
- else { // use location of LOCALIZED keyword
2192
+ if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
2136
2193
  const localized = orig.localized || orig.type || orig.name;
2137
- elem.localized = { val: false, $inferred: 'localized', location: localized.location };
2194
+ elem.localized = { val: null, $inferred: 'localized', location: localized.location };
2138
2195
  }
2139
2196
  }
2140
2197
  if (fioriEnabled)
@@ -2250,9 +2307,9 @@ function mergeI18nBlocks( model ) {
2250
2307
  model.i18n[langKey][textKey] = sourceVal;
2251
2308
  }
2252
2309
  else if (modelVal.val !== sourceVal.val) {
2253
- const { warning } = makeMessageFunction( model );
2254
- warning('i18n-different-value', [ sourceVal.location, null ],
2255
- { prop: textKey, otherprop: langKey });
2310
+ // TODO: put mergeI18nBlocks() into main function instead
2311
+ model.$messageFunctions.warning( 'i18n-different-value', sourceVal.location,
2312
+ { prop: textKey, otherprop: langKey } );
2256
2313
  }
2257
2314
  }
2258
2315
  }
@@ -15,7 +15,7 @@ const check = require('./checks');
15
15
 
16
16
 
17
17
  const { emptyWeakLocation } = require('../base/location');
18
- const { handleMessages, makeMessageFunction } = require('../base/messages');
18
+ const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
19
19
  const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
20
20
  const { cdsFs } = require('../utils/file');
21
21
 
@@ -50,25 +50,27 @@ class ArgumentError extends Error {
50
50
  * @param {string} source Source code of the file.
51
51
  * @param {string} filename Filename including its extension, e.g. "file.cds"
52
52
  * @param {object} options Compile options
53
+ * @param {object} messageFunctions If not provided, parse errors will not lead to an exception
53
54
  */
54
- function parseX( source, filename, options = {} ) {
55
+ function parseX( source, filename, options = {}, messageFunctions ) {
56
+ if (!messageFunctions)
57
+ messageFunctions = createMessageFunctions( options, 'parse' );
55
58
  const ext = path.extname( filename ).toLowerCase();
56
59
  if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
57
- return parseCsn.parse( source, filename, options );
60
+ return parseCsn.parse( source, filename, options, messageFunctions );
58
61
  if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
59
- return parseLanguage( source, filename, options );
62
+ return parseLanguage( source, filename, options, messageFunctions );
60
63
  if (options.fallbackParser === 'csn')
61
- return parseCsn.parse( source, filename, options );
64
+ return parseCsn.parse( source, filename, options, messageFunctions );
62
65
  if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
63
- return parseLanguage( source, filename, options );
66
+ return parseLanguage( source, filename, options, messageFunctions );
64
67
 
65
68
  const model = { location: { file: filename } };
66
- const { error } = makeMessageFunction( model, options, 'compile' );
67
- error( 'file-unknown-ext', emptyWeakLocation(filename),
68
- { file: ext && ext.slice(1), '#': !ext && 'none' }, {
69
- std: 'Unknown file extension $(FILE)',
70
- none: 'No file extension',
71
- } );
69
+ messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
70
+ { file: ext && ext.slice(1), '#': !ext && 'none' }, {
71
+ std: 'Unknown file extension $(FILE)',
72
+ none: 'No file extension',
73
+ } );
72
74
  return model;
73
75
  }
74
76
 
@@ -111,6 +113,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
111
113
  a.fileContentDict = Object.create(null);
112
114
 
113
115
  const model = { sources: a.sources, options };
116
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
114
117
  let all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) );
115
118
 
116
119
  all = all
@@ -147,7 +150,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
147
150
  else {
148
151
  try {
149
152
  a.fileContentDict[filename] = source;
150
- const ast = parseX( source, rel, options );
153
+ const ast = parseX( source, rel, options, model.$messageFunctions );
151
154
  a.sources[filename] = ast;
152
155
  ast.location = { file: rel };
153
156
  ast.dirname = path.dirname( filename );
@@ -187,8 +190,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
187
190
  }
188
191
  // create promises after all usingFroms have been collected, as the
189
192
  // Promise executor is called immediately with `new`:
190
- for (const module in dependencies)
191
- promises.push( resolveModule( dependencies[module], fileCache, options ) );
193
+ for (const module in dependencies) {
194
+ promises.push( resolveModule( dependencies[module], fileCache, options,
195
+ model.$messageFunctions ) );
196
+ }
192
197
  }
193
198
  if (!promises.length)
194
199
  return [];
@@ -218,6 +223,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
218
223
  a.fileContentDict = Object.create(null);
219
224
 
220
225
  const model = { sources: a.sources, options };
226
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
221
227
 
222
228
  let asts = [];
223
229
  const errors = [];
@@ -276,7 +282,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
276
282
  else {
277
283
  try {
278
284
  a.fileContentDict[filename] = source;
279
- const ast = parseX( source, rel, options );
285
+ const ast = parseX( source, rel, options, model.$messageFunctions );
280
286
  a.sources[filename] = ast;
281
287
  ast.location = { file: rel };
282
288
  ast.dirname = path.dirname( filename );
@@ -307,8 +313,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
307
313
  }
308
314
  // create promises after all usingFroms have been collected, as the
309
315
  // Promise executor is called immediately with `new`:
310
- for (const module in dependencies)
311
- fileNames.push( resolveModuleSync( dependencies[module], fileCache, options ) );
316
+ for (const module in dependencies) {
317
+ fileNames.push( resolveModuleSync( dependencies[module], fileCache, options,
318
+ model.$messageFunctions ) );
319
+ }
312
320
  }
313
321
  if (!fileNames.length)
314
322
  return [];
@@ -341,11 +349,12 @@ function compileSourcesX( sourcesDict, options = {} ) {
341
349
  sourcesDict = { '<stdin>.cds': sourcesDict };
342
350
  const sources = Object.create(null);
343
351
  const model = { sources, options };
352
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
344
353
 
345
354
  for (const filename in sourcesDict) {
346
355
  const source = sourcesDict[filename];
347
356
  if (typeof source === 'string') {
348
- const ast = parseX( source, filename, options );
357
+ const ast = parseX( source, filename, options, model.$messageFunctions );
349
358
  sources[filename] = ast;
350
359
  ast.location = { file: filename };
351
360
  assertConsistency( ast, options );
@@ -359,6 +368,38 @@ function compileSourcesX( sourcesDict, options = {} ) {
359
368
  return compileDoX( model );
360
369
  }
361
370
 
371
+ /**
372
+ * Recompile the given CSN
373
+ *
374
+ * @param {object} csn Input CSN to recompile to XSN
375
+ * @param {object} options Options
376
+ * @returns {object} XSN
377
+ *
378
+ * TODO: probaby issue message api-recompiled-csn there.
379
+ */
380
+ function recompileX( csn, options ) {
381
+ options = { ...options, parseCdl: false, $recompile: true };
382
+ // TODO: $recompile: true should be enough
383
+ // Explicitly set parseCdl to false because backends cannot handle it
384
+ // Explicitly delete all toCsn options:
385
+ delete options.toCsn;
386
+
387
+ const file = csn.$location && csn.$location.file &&
388
+ csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
389
+
390
+ const sources = Object.create(null);
391
+ const model = { sources, options };
392
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
393
+ // TODO: or use module which invokes the recompilation?
394
+
395
+ sources[file] = parseCsn.augment( csn, file, options, model.$messageFunctions );
396
+ moduleLayers.setLayers( sources );
397
+ const compiled = compileDoX( model ); // calls throwWithError()
398
+ if (options.messages) // does not help with exception in compileDoX()
399
+ deduplicateMessages(options.messages); // TODO: do better
400
+ return compiled;
401
+ }
402
+
362
403
  /**
363
404
  * On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
364
405
  * Creates an augmented CSN (XSN) and returns it.
@@ -368,24 +409,28 @@ function compileSourcesX( sourcesDict, options = {} ) {
368
409
  */
369
410
  function compileDoX( model ) {
370
411
  const { options } = model;
412
+ const { throwWithError } = model.$messageFunctions;
371
413
  if (!options.testMode)
372
414
  model.meta = {}; // provide initial central meta object
373
- if (options.parseOnly)
374
- return handleMessages( model, options );
375
-
415
+ if (options.parseOnly) {
416
+ throwWithError();
417
+ return model;
418
+ }
376
419
  define( model );
377
420
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
378
- if (options.parseCdl)
379
- return handleMessages( model, options );
380
-
421
+ // TODO: do not use this function for parseCdl anyway…
422
+ if (options.parseCdl) {
423
+ throwWithError();
424
+ return model;
425
+ }
381
426
  resolve( model );
382
427
  assertConsistency( model );
383
- handleMessages( model, options ); // stop compilation with errors
428
+ throwWithError();
384
429
  if (options.lintMode)
385
430
  return model;
386
431
 
387
432
  check(model);
388
- handleMessages( model, options );
433
+ throwWithError();
389
434
  return propagator.propagate( model );
390
435
  }
391
436
 
@@ -433,5 +478,6 @@ module.exports = {
433
478
  compileX,
434
479
  compileSyncX,
435
480
  compileSourcesX,
481
+ recompileX,
436
482
  InvocationError, // TODO: make it no error if same file name is provided twice
437
483
  };