@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.
- package/CHANGELOG.md +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/compiler/definer.js
CHANGED
|
@@ -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
|
-
} =
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
628
|
-
// TODO: forbid with :param, global:true, in ref-where, outside queries (CSN), ...
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
2192
|
+
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
2131
2193
|
const localized = orig.localized || orig.type || orig.name;
|
|
2132
|
-
elem.localized = { val:
|
|
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
|
-
|
|
2249
|
-
warning('i18n-different-value',
|
|
2250
|
-
|
|
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
|
}
|
package/lib/compiler/index.js
CHANGED
|
@@ -15,7 +15,7 @@ const check = require('./checks');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
const { emptyWeakLocation } = require('../base/location');
|
|
18
|
-
const {
|
|
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) ||
|
|
57
|
-
return parseCsn.parse( source, filename, options );
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
340
|
-
|
|
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
|
|
348
|
-
const source =
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
-
|
|
428
|
+
throwWithError();
|
|
401
429
|
if (options.lintMode)
|
|
402
430
|
return model;
|
|
403
431
|
|
|
404
432
|
check(model);
|
|
405
|
-
|
|
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
|
};
|