@sap/cds-compiler 2.4.4 → 2.10.2

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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. 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,17 +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 && (sub.value || sub.expand)) {
631
- setProp( sub, '_block', elem._block );
632
- setProp( sub, '_pathHead', elem );
633
- defineAnnotations( sub, sub, elem._block );
634
- initExpandInline( sub );
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
624
+ continue;
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 );
635
652
  }
636
653
  }
637
- // TODO: allow sub queries in top-level expand without parallel ref
638
654
  }
639
655
 
640
656
  function initExprForQuery( expr, query ) {
@@ -653,9 +669,38 @@ function getDefinerFunctions( model ) {
653
669
  }
654
670
  else if (expr.path && expr.$expected === 'exists') {
655
671
  expr.$expected = 'approved-exists';
672
+ approveExistsInChildren(expr);
656
673
  }
657
674
  }
658
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
+
659
704
  // table is table expression in FROM, becomes an alias
660
705
  function initTableExpression( table, query, joinParents ) {
661
706
  if (!table) // parse error
@@ -703,6 +748,8 @@ function getDefinerFunctions( model ) {
703
748
  // ? ta._joinParent.args[ta.$joinArgsIndex] // in JOIN
704
749
  // : ta._parent.from ) // directly in FROM
705
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)
706
753
  setProp( tab, '_joinParent', table );
707
754
  tab.$joinArgsIndex = index;
708
755
  initTableExpression( tab, query, joinParents );
@@ -842,7 +889,7 @@ function getDefinerFunctions( model ) {
842
889
  // TODO: error if CSN has both target.elements and targetAspect.elements -> delete target
843
890
  return true;
844
891
  }
845
- if (elem.targetAspect || !isDirectComposition( elem ))
892
+ if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
846
893
  return false;
847
894
  const name = resolveUncheckedPath( target, 'compositionTarget', elem );
848
895
  const aspect = name && model.definitions[name];
@@ -856,6 +903,7 @@ function getDefinerFunctions( model ) {
856
903
  * (which is basically the component name of the `parent` element plus a dot).
857
904
  */
858
905
  function initMembers( construct, parent, block, initExtensions = false ) {
906
+ // TODO: split extend from init
859
907
  const isQueryExtension = kindProperties[construct.kind].isExtension &&
860
908
  (parent._main || parent).query;
861
909
  let obj = construct;
@@ -911,7 +959,7 @@ function getDefinerFunctions( model ) {
911
959
  forEachInOrder( construct, 'params', init );
912
960
  const { returns } = construct;
913
961
  if (returns) {
914
- returns.kind = 'param';
962
+ returns.kind = (kindProperties[construct.kind].isExtension) ? construct.kind : 'param';
915
963
  init( returns, '' ); // '' is special name for returns parameter
916
964
  }
917
965
  return;
@@ -980,6 +1028,7 @@ function getDefinerFunctions( model ) {
980
1028
  }
981
1029
 
982
1030
  function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
1031
+ // TODO: do differently, see also annotateMembers() in resolver
983
1032
  // To have been checked by parsers:
984
1033
  // - artifacts (CDL-only anyway) only inside [extend] context|service
985
1034
  if (!dict)
@@ -1264,7 +1313,7 @@ function getDefinerFunctions( model ) {
1264
1313
  }
1265
1314
 
1266
1315
  function createTargetEntity( target, elem, keys, entityName, base ) {
1267
- const location = elem.target && elem.target.location || elem.location;
1316
+ const { location } = elem.targetAspect || elem.target || elem;
1268
1317
  elem.on = {
1269
1318
  location,
1270
1319
  op: { val: '=', location },
@@ -1309,7 +1358,7 @@ function getDefinerFunctions( model ) {
1309
1358
  // If 'up_' shall be rendered unmanaged, infer the parent
1310
1359
  // primary keys and add the ON condition
1311
1360
  if (isDeprecatedEnabled( options, 'unmanagedUpInComponent' )) {
1312
- addProxyElements( art, keys, 'aspect-composition', location,
1361
+ addProxyElements( art, keys, 'aspect-composition', target.name && location,
1313
1362
  'up__', '@odata.containment.ignore' );
1314
1363
  up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
1315
1364
  }
@@ -1323,7 +1372,7 @@ function getDefinerFunctions( model ) {
1323
1372
  setLink( art, '_base', base._base || base );
1324
1373
 
1325
1374
  dictAdd( art.elements, 'up_', up);
1326
- addProxyElements( art, target.elements, 'aspect-composition', location );
1375
+ addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
1327
1376
 
1328
1377
  setLink( art, '_block', model.$internal );
1329
1378
  model.definitions[entityName] = art;
@@ -1370,7 +1419,7 @@ function getDefinerFunctions( model ) {
1370
1419
  const exts = model.$lateExtensions[name];
1371
1420
  if (art && art.kind !== 'namespace') {
1372
1421
  if (art.builtin) {
1373
- for (const ext in exts)
1422
+ for (const ext of exts)
1374
1423
  info( 'anno-builtin', [ ext.name.location, ext ] );
1375
1424
  }
1376
1425
  // created texts entity, autoexposed entity
@@ -1418,6 +1467,12 @@ function getDefinerFunctions( model ) {
1418
1467
 
1419
1468
  model.extensions.push(annotationArtifact);
1420
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
+ }
1421
1476
  }
1422
1477
  }
1423
1478
  }
@@ -1559,7 +1614,7 @@ function getDefinerFunctions( model ) {
1559
1614
  else {
1560
1615
  const fake = { name: { absolute: artifact.name.absolute } };
1561
1616
  // to-csn just needs a fake element whose absolute name and _parent/_main links are correct
1562
- setLink( fake, '_parent', artifact._parent, '_parent' );
1617
+ setLink( fake, '_parent', artifact._parent );
1563
1618
  setLink( fake, '_main', artifact._main ); // value does not matter...
1564
1619
  setLink( root, '_artifact', fake );
1565
1620
  }
@@ -1701,6 +1756,7 @@ function getDefinerFunctions( model ) {
1701
1756
  }
1702
1757
 
1703
1758
  function extendMembers( extensions, art, noExtend ) {
1759
+ // TODO: do the whole extension stuff lazily if the elements are requested
1704
1760
  const elemExtensions = [];
1705
1761
  extensions.sort( compareLayer );
1706
1762
  for (const ext of extensions) {
@@ -1746,7 +1802,12 @@ function getDefinerFunctions( model ) {
1746
1802
  [ 'elements', 'actions' ].forEach( (prop) => {
1747
1803
  const dict = art._extend && art._extend[prop];
1748
1804
  for (const name in dict) {
1749
- 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;
1750
1811
  const member = validDict[name];
1751
1812
  if (!member)
1752
1813
  extendNothing( dict[name], prop, name, art, validDict );
@@ -1984,8 +2045,9 @@ function getDefinerFunctions( model ) {
1984
2045
  }
1985
2046
 
1986
2047
  if (isKey && isLocalized) { // key with localized is wrong - ignore localized
1987
- warning( 'localized-key', [ elem.localized.location, elem ], {},
1988
- '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' );
1989
2051
  }
1990
2052
  }
1991
2053
  if (textElems.length <= keys)
@@ -2127,9 +2189,9 @@ function getDefinerFunctions( model ) {
2127
2189
  });
2128
2190
  }
2129
2191
  }
2130
- else { // use location of LOCALIZED keyword
2192
+ if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
2131
2193
  const localized = orig.localized || orig.type || orig.name;
2132
- elem.localized = { val: false, $inferred: 'localized', location: localized.location };
2194
+ elem.localized = { val: null, $inferred: 'localized', location: localized.location };
2133
2195
  }
2134
2196
  }
2135
2197
  if (fioriEnabled)
@@ -2245,9 +2307,9 @@ function mergeI18nBlocks( model ) {
2245
2307
  model.i18n[langKey][textKey] = sourceVal;
2246
2308
  }
2247
2309
  else if (modelVal.val !== sourceVal.val) {
2248
- const { warning } = makeMessageFunction( model );
2249
- warning('i18n-different-value', [ sourceVal.location, null ],
2250
- { 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 } );
2251
2313
  }
2252
2314
  }
2253
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,28 +50,28 @@ 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
- if ([ '.json', '.csn' ].includes(ext) || (options.fallbackParser === 'csn')) {
57
- return parseCsn.parse( source, filename, options );
58
- }
59
- else if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext) || options.fallbackParser) {
60
- // Note: Historically, all truthy values for options.fallbackParser were interpreted as 'cdl'.
61
- // To not break existing programs, we do the same if it's not set to `csn`.
62
- return parseLanguage( source, filename, options );
63
- }
64
- // eslint-disable-next-line no-else-return
65
- else {
66
- const model = { location: { file: filename } };
67
- const { error } = makeMessageFunction( model, options, 'compile' );
68
- error( 'file-unknown-ext', emptyWeakLocation(filename),
69
- { file: ext && ext.slice(1), '#': !ext && 'none' }, {
70
- std: 'Unknown file extension $(FILE)',
71
- none: 'No file extension',
72
- } );
73
- return model;
74
- }
59
+ if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
60
+ return parseCsn.parse( source, filename, options, messageFunctions );
61
+ if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
62
+ return parseLanguage( source, filename, options, messageFunctions );
63
+ if (options.fallbackParser === 'csn')
64
+ return parseCsn.parse( source, filename, options, messageFunctions );
65
+ if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
66
+ return parseLanguage( source, filename, options, messageFunctions );
67
+
68
+ const model = { location: { file: filename } };
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
+ } );
74
+ return model;
75
75
  }
76
76
 
77
77
  // Main function: Compile the sources from the files given by the array of
@@ -83,7 +83,8 @@ function parseX( source, filename, options = {} ) {
83
83
  // - Truthy `lintMode`: do not do checks and propagation
84
84
  // - many others - TODO
85
85
 
86
- // This function returns a Promise. See ../bin/cdsv.js for an example usage.
86
+ // This function returns a Promise and can be used with `await`. For an example
87
+ // see `examples/api-usage/`.
87
88
  // See function `compileSyncX` or `compileSourcesX` for alternative compile
88
89
  // functions.
89
90
  //
@@ -112,6 +113,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
112
113
  a.fileContentDict = Object.create(null);
113
114
 
114
115
  const model = { sources: a.sources, options };
116
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
115
117
  let all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) );
116
118
 
117
119
  all = all
@@ -148,7 +150,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
148
150
  else {
149
151
  try {
150
152
  a.fileContentDict[filename] = source;
151
- const ast = parseX( source, rel, options );
153
+ const ast = parseX( source, rel, options, model.$messageFunctions );
152
154
  a.sources[filename] = ast;
153
155
  ast.location = { file: rel };
154
156
  ast.dirname = path.dirname( filename );
@@ -188,8 +190,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
188
190
  }
189
191
  // create promises after all usingFroms have been collected, as the
190
192
  // Promise executor is called immediately with `new`:
191
- for (const module in dependencies)
192
- 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
+ }
193
197
  }
194
198
  if (!promises.length)
195
199
  return [];
@@ -219,6 +223,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
219
223
  a.fileContentDict = Object.create(null);
220
224
 
221
225
  const model = { sources: a.sources, options };
226
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
222
227
 
223
228
  let asts = [];
224
229
  const errors = [];
@@ -277,7 +282,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
277
282
  else {
278
283
  try {
279
284
  a.fileContentDict[filename] = source;
280
- const ast = parseX( source, rel, options );
285
+ const ast = parseX( source, rel, options, model.$messageFunctions );
281
286
  a.sources[filename] = ast;
282
287
  ast.location = { file: rel };
283
288
  ast.dirname = path.dirname( filename );
@@ -308,8 +313,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
308
313
  }
309
314
  // create promises after all usingFroms have been collected, as the
310
315
  // Promise executor is called immediately with `new`:
311
- for (const module in dependencies)
312
- 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
+ }
313
320
  }
314
321
  if (!fileNames.length)
315
322
  return [];
@@ -331,23 +338,23 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
331
338
  * are parse or other compilation errors, throw an exception CompilationError
332
339
  * containing a vector of individual errors.
333
340
  *
341
+ * TODO: re-check `using from` dependencies.
342
+ *
334
343
  * @param {string|object} sourcesDict Files to compile.
335
344
  * @param {object} [options={}] Compilation options.
336
345
  * @returns {XSN.Model} Augmented CSN
337
346
  */
338
347
  function compileSourcesX( sourcesDict, options = {} ) {
339
- // eslint-disable-next-line no-nested-ternary
340
- const content = sourcesDict.sources
341
- ? sourcesDict.sources
342
- : (typeof sourcesDict === 'string') ? { '<stdin>.cds': sourcesDict } : sourcesDict;
343
-
348
+ if (typeof sourcesDict === 'string')
349
+ sourcesDict = { '<stdin>.cds': sourcesDict };
344
350
  const sources = Object.create(null);
345
351
  const model = { sources, options };
352
+ model.$messageFunctions = createMessageFunctions( options, 'compile', model );
346
353
 
347
- for (const filename in content) {
348
- const source = content[filename];
354
+ for (const filename in sourcesDict) {
355
+ const source = sourcesDict[filename];
349
356
  if (typeof source === 'string') {
350
- const ast = parseX( source, filename, options );
357
+ const ast = parseX( source, filename, options, model.$messageFunctions );
351
358
  sources[filename] = ast;
352
359
  ast.location = { file: filename };
353
360
  assertConsistency( ast, options );
@@ -356,26 +363,43 @@ function compileSourcesX( sourcesDict, options = {} ) {
356
363
  sources[filename] = source;
357
364
  }
358
365
  }
359
-
360
- // add dependencies to AST
361
- for (const filename in sourcesDict.dependencies) {
362
- const dependency = sourcesDict.dependencies[filename];
363
- for (const val in dependency) {
364
- const dep = {
365
- literal: 'string', val, realname: dependency[val], // location ?
366
- };
367
- const arr = sources[filename].dependencies;
368
- if (arr)
369
- arr.push( dep );
370
- else
371
- sources[filename].dependencies = [ dep ];
372
- }
373
- }
374
366
  moduleLayers.setLayers( sources );
375
367
 
376
368
  return compileDoX( model );
377
369
  }
378
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
+
379
403
  /**
380
404
  * On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
381
405
  * Creates an augmented CSN (XSN) and returns it.
@@ -385,24 +409,28 @@ function compileSourcesX( sourcesDict, options = {} ) {
385
409
  */
386
410
  function compileDoX( model ) {
387
411
  const { options } = model;
412
+ const { throwWithError } = model.$messageFunctions;
388
413
  if (!options.testMode)
389
414
  model.meta = {}; // provide initial central meta object
390
- if (options.parseOnly)
391
- return handleMessages( model, options );
392
-
415
+ if (options.parseOnly) {
416
+ throwWithError();
417
+ return model;
418
+ }
393
419
  define( model );
394
420
  // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
395
- if (options.parseCdl)
396
- return handleMessages( model, options );
397
-
421
+ // TODO: do not use this function for parseCdl anyway…
422
+ if (options.parseCdl) {
423
+ throwWithError();
424
+ return model;
425
+ }
398
426
  resolve( model );
399
427
  assertConsistency( model );
400
- handleMessages( model, options ); // stop compilation with errors
428
+ throwWithError();
401
429
  if (options.lintMode)
402
430
  return model;
403
431
 
404
432
  check(model);
405
- handleMessages( model, options );
433
+ throwWithError();
406
434
  return propagator.propagate( model );
407
435
  }
408
436
 
@@ -450,5 +478,6 @@ module.exports = {
450
478
  compileX,
451
479
  compileSyncX,
452
480
  compileSourcesX,
481
+ recompileX,
453
482
  InvocationError, // TODO: make it no error if same file name is provided twice
454
483
  };