@sap/cds-compiler 3.9.4 → 4.0.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 +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/json/to-csn.js
CHANGED
|
@@ -33,6 +33,7 @@ let universalCsn = false;
|
|
|
33
33
|
let strictMode = false; // whether to dump with unknown properties (in standard)
|
|
34
34
|
let projectionAsQuery = false;
|
|
35
35
|
let withLocations = false;
|
|
36
|
+
let withDocComments = false;
|
|
36
37
|
let structXpr = false;
|
|
37
38
|
let dictionaryPrototype = null;
|
|
38
39
|
|
|
@@ -54,7 +55,7 @@ const transformers = {
|
|
|
54
55
|
// early and modifiers (without null / not null) ---------------------------
|
|
55
56
|
kind,
|
|
56
57
|
id: n => n, // in path item
|
|
57
|
-
doc:
|
|
58
|
+
doc: docComment,
|
|
58
59
|
'@': anno,
|
|
59
60
|
virtual: value,
|
|
60
61
|
key: value,
|
|
@@ -159,7 +160,7 @@ const transformers = {
|
|
|
159
160
|
// which should appear at that place in order.
|
|
160
161
|
const csnPropertyNames = {
|
|
161
162
|
virtual: [ 'abstract' ], // abstract is compiler v1 CSN property
|
|
162
|
-
kind: [ 'annotate', 'extend', '$origin' ],
|
|
163
|
+
kind: [ 'annotate', 'extend', '$origin' ], // TODO: $origin better at the end? see addOrigin()
|
|
163
164
|
op: [ 'join', 'func', 'xpr' ], // TODO: 'func','xpr' into 'quantifier'? TODO: 'global'(scope)?
|
|
164
165
|
quantifier: [
|
|
165
166
|
'some', 'any', 'distinct', // 'all' explicitly listed
|
|
@@ -521,7 +522,7 @@ function targetAspect( val, csn, node ) {
|
|
|
521
522
|
if (universalCsn) {
|
|
522
523
|
if (val.$inferred)
|
|
523
524
|
return undefined;
|
|
524
|
-
if (node.target) {
|
|
525
|
+
if (node.target) { // TODO: use addOrigin() for this
|
|
525
526
|
csn.$origin = { target: (val.elements) ? standard( val ) : artifactRef( val, true ) };
|
|
526
527
|
return undefined;
|
|
527
528
|
}
|
|
@@ -536,13 +537,17 @@ function targetAspect( val, csn, node ) {
|
|
|
536
537
|
return undefined;
|
|
537
538
|
}
|
|
538
539
|
|
|
539
|
-
function target( val,
|
|
540
|
-
if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
|
|
541
|
-
val = node._origin.target;
|
|
540
|
+
function target( val, csn, node ) {
|
|
542
541
|
if (val.elements)
|
|
543
542
|
return standard( val ); // elements in target (parse-cdl)
|
|
543
|
+
// Mention user-provided target in $origin if outside query entity:
|
|
544
|
+
if (val.$inferred === '' && universalCsn && !gensrcFlavor && !node._main?.query) {
|
|
545
|
+
if (!csn.$origin)
|
|
546
|
+
csn.$origin = {};
|
|
547
|
+
csn.$origin.target = artifactRef( val, '.path' ); // TODO: to addOrigin()
|
|
548
|
+
}
|
|
544
549
|
if (!universalCsn || gensrcFlavor || node.on)
|
|
545
|
-
return artifactRef( val,
|
|
550
|
+
return artifactRef( val, !gensrcFlavor || val.$inferred !== '' || '.path' );
|
|
546
551
|
const tref = artifactRef( val, true );
|
|
547
552
|
const proto = node.type && !node.type.$inferred ? node.type._artifact : node._origin;
|
|
548
553
|
return (proto && proto.target && artifactRef( proto.target, true ) === tref)
|
|
@@ -668,8 +673,7 @@ function ignore() { /* no-op: ignore property */ }
|
|
|
668
673
|
|
|
669
674
|
function location( loc, csn, xsn ) {
|
|
670
675
|
if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
|
|
671
|
-
(!xsn.$inferred || !xsn._main)
|
|
672
|
-
xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
|
|
676
|
+
(!xsn.$inferred || !xsn._main)) { // TODO: also for 'select'
|
|
673
677
|
// Also include $location for elements in queries (if not via '*')
|
|
674
678
|
addLocation( xsn.name && xsn.name.location || loc, csn );
|
|
675
679
|
}
|
|
@@ -738,8 +742,6 @@ function foreignKeys( dict, csn, node ) {
|
|
|
738
742
|
return;
|
|
739
743
|
|
|
740
744
|
if (gensrcFlavor) {
|
|
741
|
-
if (node._origin?.$inferred === 'REDIRECTED')
|
|
742
|
-
dict = node._origin.foreignKeys;
|
|
743
745
|
if (dict[$inferred])
|
|
744
746
|
return;
|
|
745
747
|
}
|
|
@@ -1050,8 +1052,6 @@ function originRef( art, user ) {
|
|
|
1050
1052
|
}
|
|
1051
1053
|
|
|
1052
1054
|
function kind( k, csn, node ) {
|
|
1053
|
-
if (node.$inferred === 'REDIRECTED')
|
|
1054
|
-
return;
|
|
1055
1055
|
if (k === 'annotate' || k === 'extend') {
|
|
1056
1056
|
// We just use `name.absolute` because it is very likely a "constructed"
|
|
1057
1057
|
// extensions. The CSN parser must produce name.path like for other refs.
|
|
@@ -1077,18 +1077,11 @@ function kind( k, csn, node ) {
|
|
|
1077
1077
|
csn.$generated = generated;
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
|
-
function type( node
|
|
1080
|
+
function type( node ) {
|
|
1081
1081
|
if (!universalCsn)
|
|
1082
1082
|
return artifactRef( node, !node.$extra );
|
|
1083
1083
|
if (node.$inferred && node.$inferred !== 'cast')
|
|
1084
1084
|
return undefined;
|
|
1085
|
-
if (xsn._origin) {
|
|
1086
|
-
if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
|
|
1087
|
-
const $origin = definition( xsn._origin );
|
|
1088
|
-
if ($origin) // if not rendered as column
|
|
1089
|
-
csn.$origin = $origin;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
1085
|
return artifactRef( node, !node.$extra );
|
|
1093
1086
|
}
|
|
1094
1087
|
|
|
@@ -1106,73 +1099,39 @@ function artifactRef( node, terse ) {
|
|
|
1106
1099
|
if (node.$inferred && gensrcFlavor)
|
|
1107
1100
|
return undefined;
|
|
1108
1101
|
// Works also on XSN directly coming from parser and with XSN from CDL->CSN transformation
|
|
1109
|
-
|
|
1110
|
-
if (terse && node._artifact && !node._artifact._main)
|
|
1102
|
+
// Shortcut for many cases:
|
|
1103
|
+
if (terse && node._artifact && !node._artifact._main && terse !== '.path')
|
|
1111
1104
|
return node._artifact.name.absolute;
|
|
1105
|
+
let { path } = node;
|
|
1112
1106
|
if (!path)
|
|
1113
1107
|
return undefined; // TODO: complain with strict
|
|
1114
|
-
|
|
1108
|
+
if (!path.length)
|
|
1115
1109
|
return [];
|
|
1116
1110
|
|
|
1117
|
-
const
|
|
1118
|
-
const root =
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
|
|
1122
|
-
return renderArtifactPath( node, path, terse, node.scope );
|
|
1123
|
-
}
|
|
1124
|
-
const { absolute } = root.name;
|
|
1125
|
-
if (node.scope !== 'typeOf' && typeof node.scope !== 'number') {
|
|
1126
|
-
// CSN input or generated in compiler (XSN TODO: remove scope:'global')
|
|
1127
|
-
if (absolute === path[0].id) // normal case (no localization view)
|
|
1128
|
-
return renderArtifactPath( node, path, terse );
|
|
1129
|
-
// scope:param is not valid (and would be lost)
|
|
1130
|
-
const head = Object.assign( {}, path[0], { id: absolute } );
|
|
1131
|
-
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse );
|
|
1132
|
-
}
|
|
1133
|
-
if (node.scope === 'typeOf') { // TYPE OF without ':' in path
|
|
1134
|
-
// Root _artifact which is either element or main artifact for paths starting with $self.
|
|
1135
|
-
// To make the CDL->CSN transformation simpler, the _artifact for first item could be
|
|
1136
|
-
// a fake element with just a correct absolute name and _parent/_main links.
|
|
1137
|
-
if (!root._main || root.kind === 'select') { // $self/$projection
|
|
1138
|
-
// in query, only correct for leading query ->
|
|
1139
|
-
// TODO: forbid TYPE OF elem / TYPE OF $self.elem in queries
|
|
1140
|
-
return renderArtifactPath( node, [ { id: absolute }, ...path.slice(1) ], terse );
|
|
1141
|
-
}
|
|
1142
|
-
const parent = root._parent;
|
|
1143
|
-
const structs = parent.name.element ? parent.name.element.split('.') : [];
|
|
1144
|
-
return extra( { ref: [ absolute, ...structs, ...path.map( pathItem ) ] }, node );
|
|
1145
|
-
}
|
|
1146
|
-
let { scope } = node;
|
|
1147
|
-
if (!scope) { // no ':' in CDL path - try to be nice and guess it via links
|
|
1148
|
-
const { length } = path;
|
|
1149
|
-
for (; scope < length; ++scope) {
|
|
1150
|
-
const art = path[scope]._artifact;
|
|
1151
|
-
if (!art) {
|
|
1152
|
-
scope = 0; // unsuccessful, not all path items have links
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
if (art._main)
|
|
1156
|
-
break; // successful, found first element
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
const head = Object.assign( {}, path[0], { id: absolute } );
|
|
1160
|
-
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse, scope );
|
|
1161
|
-
}
|
|
1111
|
+
const head = path[0];
|
|
1112
|
+
const root = head._artifact;
|
|
1113
|
+
const id = root?.name.absolute;
|
|
1114
|
+
const scope = node.scope || path.length;
|
|
1162
1115
|
|
|
1163
|
-
function renderArtifactPath( node, path, terse, scope ) {
|
|
1164
|
-
if (scope === 0) {
|
|
1165
|
-
// try to find ':' position syntactically for FROM
|
|
1166
|
-
scope = !terse && path.findIndex( i => i.where || i.args || i.cardinality) + 1 ||
|
|
1167
|
-
path.length;
|
|
1168
|
-
}
|
|
1169
1116
|
if (typeof scope === 'number' && scope > 1) {
|
|
1170
1117
|
const item = path[scope - 1];
|
|
1171
|
-
const name = item._artifact
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1118
|
+
const name = item._artifact?.name;
|
|
1119
|
+
const absolute = name?.absolute ||
|
|
1120
|
+
`${ id || head.id }.${ path.slice( 1, scope ).map( i => i.id ).join('.') }`;
|
|
1121
|
+
path = [ Object.assign( {}, item, { id: absolute } ), ...path.slice( scope ) ];
|
|
1122
|
+
}
|
|
1123
|
+
else if (scope === 'typeOf') { // TYPE OF without ':' in path
|
|
1124
|
+
if (root) {
|
|
1125
|
+
const structs = root.name.element?.split('.').map( n => ({ id: n }) );
|
|
1126
|
+
// TODO: change (follow parents) if we introduce sparse names
|
|
1127
|
+
path = [ { id }, ...(structs || []), ...path.slice(1) ];
|
|
1128
|
+
}
|
|
1129
|
+
else if (strictMode) {
|
|
1130
|
+
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
else if (root && id !== head.id) {
|
|
1134
|
+
path = [ Object.assign( {}, head, { id } ), ...path.slice( 1 ) ];
|
|
1176
1135
|
}
|
|
1177
1136
|
const ref = path.map( pathItem );
|
|
1178
1137
|
return (!terse || ref.length !== 1 || typeof ref[0] !== 'string')
|
|
@@ -1183,6 +1142,10 @@ function renderArtifactPath( node, path, terse, scope ) {
|
|
|
1183
1142
|
function pathItem( item ) {
|
|
1184
1143
|
if (!item.args &&
|
|
1185
1144
|
!item.where &&
|
|
1145
|
+
!item.groupBy &&
|
|
1146
|
+
!item.having &&
|
|
1147
|
+
!item.limit &&
|
|
1148
|
+
!item.orderBy &&
|
|
1186
1149
|
!item.cardinality &&
|
|
1187
1150
|
!item.$extra &&
|
|
1188
1151
|
!item.$syntax)
|
|
@@ -1205,8 +1168,15 @@ function anno( node ) {
|
|
|
1205
1168
|
return value(node);
|
|
1206
1169
|
}
|
|
1207
1170
|
|
|
1171
|
+
function docComment( doc ) {
|
|
1172
|
+
// Value is `true` if options.docComment is falsey for CDL input.
|
|
1173
|
+
if (withDocComments && doc?.val !== true)
|
|
1174
|
+
return value( doc );
|
|
1175
|
+
return undefined;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1208
1178
|
function value( node ) {
|
|
1209
|
-
// "Short" value form, e.g. for annotation assignments
|
|
1179
|
+
// "Short" value form, e.g. for annotation assignments
|
|
1210
1180
|
if (!node)
|
|
1211
1181
|
return true; // `@aBool` short for `@aBool: true`
|
|
1212
1182
|
if (universalCsn && node.$inferred) {
|
|
@@ -1242,22 +1212,21 @@ function value( node ) {
|
|
|
1242
1212
|
function enumValueOrCalc( v, csn, node ) {
|
|
1243
1213
|
if (v.$inferred && (universalCsn || gensrcFlavor))
|
|
1244
1214
|
return undefined;
|
|
1245
|
-
// Enums can have values but if enums are extended, their kind is 'element'
|
|
1246
|
-
|
|
1215
|
+
// Enums can have values but if enums are extended, their kind is 'element'.
|
|
1216
|
+
// In v4, we don't check `node.$syntax === 'enum'` anymore.
|
|
1217
|
+
if (node.kind === 'enum') {
|
|
1247
1218
|
Object.assign( csn, expression( v ) );
|
|
1248
1219
|
}
|
|
1249
|
-
else if (node.$syntax === 'calc'
|
|
1220
|
+
else if (node.$syntax === 'calc' || node._parent?.kind === 'extend') {
|
|
1250
1221
|
const stored = v.stored ? { stored: value(v.stored) } : {};
|
|
1251
1222
|
return Object.assign( stored, expression( v ) );
|
|
1252
1223
|
}
|
|
1253
1224
|
return undefined;
|
|
1254
1225
|
}
|
|
1255
1226
|
|
|
1256
|
-
function onCondition( cond
|
|
1227
|
+
function onCondition( cond ) {
|
|
1257
1228
|
if (gensrcFlavor) {
|
|
1258
|
-
if (
|
|
1259
|
-
cond = node._origin.on;
|
|
1260
|
-
else if (cond.$inferred)
|
|
1229
|
+
if (cond.$inferred)
|
|
1261
1230
|
return undefined;
|
|
1262
1231
|
}
|
|
1263
1232
|
return condition( cond );
|
|
@@ -1475,6 +1444,7 @@ function addElementAsColumn( elem, cols ) {
|
|
|
1475
1444
|
try {
|
|
1476
1445
|
gensrcFlavor = gensrcFlavor || 'column';
|
|
1477
1446
|
set( 'virtual', col, elem );
|
|
1447
|
+
// TODO if (!elem.key?.$specifiedElement)
|
|
1478
1448
|
set( 'key', col, elem );
|
|
1479
1449
|
const expr = expression( elem.value );
|
|
1480
1450
|
Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) );
|
|
@@ -1533,7 +1503,7 @@ function cast( csn, node ) {
|
|
|
1533
1503
|
else
|
|
1534
1504
|
r.cast = {}; // TODO: what about $extra in cast?
|
|
1535
1505
|
for (const prop of typeProperties)
|
|
1536
|
-
set(
|
|
1506
|
+
set(prop, r.cast, node);
|
|
1537
1507
|
return r;
|
|
1538
1508
|
}
|
|
1539
1509
|
|
|
@@ -1596,6 +1566,7 @@ function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
|
|
|
1596
1566
|
? proto
|
|
1597
1567
|
: (proto) ? Object.prototype : null;
|
|
1598
1568
|
withLocations = options.withLocations;
|
|
1569
|
+
withDocComments = options.docComment !== false;
|
|
1599
1570
|
structXpr = options.structXpr;
|
|
1600
1571
|
projectionAsQuery = isDeprecatedEnabled( options, '_projectionAsQuery' );
|
|
1601
1572
|
}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
const antlr4 = require('antlr4');
|
|
12
12
|
|
|
13
13
|
const { CompileMessage } = require('../base/messages');
|
|
14
|
-
const { isBetaEnabled } = require('../base/model');
|
|
15
14
|
const errorStrategy = require('./errorStrategy');
|
|
16
15
|
|
|
17
16
|
const Parser = require('../gen/languageParser').default;
|
|
@@ -30,11 +29,6 @@ class ErrorListener extends antlr4.error.ErrorListener {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
33
|
-
constructor(lexer, v4newKeyword) {
|
|
34
|
-
super(lexer);
|
|
35
|
-
this.v4newKeyword = v4newKeyword;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
LT( k ) {
|
|
39
33
|
const t = super.LT(k);
|
|
40
34
|
if (!t || !t.type)
|
|
@@ -56,7 +50,7 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
|
56
50
|
else if (t.type === this.NEW) {
|
|
57
51
|
const n = super.LT(k + 1);
|
|
58
52
|
// TODO v4: rewrite token in grammar via `this.setLocalToken`
|
|
59
|
-
if (n?.type === this.Identifier
|
|
53
|
+
if (n?.type === this.Identifier) {
|
|
60
54
|
const o = super.LT(k + 2);
|
|
61
55
|
if (o?.type === this.PAREN)
|
|
62
56
|
return t;
|
|
@@ -118,8 +112,7 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
118
112
|
options = {}, messageFunctions = null,
|
|
119
113
|
rule = 'cdl' ) {
|
|
120
114
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
121
|
-
const
|
|
122
|
-
const tokenStream = new RewriteTypeTokenStream(lexer, v4newKeyword);
|
|
115
|
+
const tokenStream = new RewriteTypeTokenStream(lexer);
|
|
123
116
|
/** @type {object} */
|
|
124
117
|
const parser = new Parser( tokenStream );
|
|
125
118
|
const errorListener = new ErrorListener();
|
|
@@ -161,19 +154,30 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
161
154
|
|
|
162
155
|
|
|
163
156
|
const rulespec = rules[rule];
|
|
164
|
-
|
|
157
|
+
let tree;
|
|
158
|
+
try {
|
|
159
|
+
tree = rule && parser[rulespec.func]();
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
if (e instanceof RangeError && e.message.match(/Maximum.*exceeded$/i)) {
|
|
163
|
+
messageFunctions.error('syntax-invalid-source', { file: filename },
|
|
164
|
+
{ '#': 'cdl-stackoverflow' } );
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
165
170
|
const ast = tree && tree[rulespec.returns] || {};
|
|
166
171
|
ast.options = options;
|
|
167
172
|
if (rulespec.$frontend)
|
|
168
173
|
ast.$frontend = rulespec.$frontend;
|
|
169
174
|
|
|
170
|
-
// Warn about unused doc-comments.
|
|
171
175
|
// Do not warn if docComments are explicitly disabled.
|
|
172
176
|
if (options.docComment !== false) {
|
|
173
177
|
for (const token of tokenStream.tokens) {
|
|
174
178
|
if (token.type === parser.constructor.DocComment && !token.isUsed) {
|
|
175
|
-
messageFunctions.info(
|
|
176
|
-
|
|
179
|
+
messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
|
|
180
|
+
'Ignoring doc comment as it is not written at a defined position');
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
183
|
}
|
|
@@ -15,6 +15,12 @@ const hasContentOnFirstLineRegEx = /\/\*+\s*\S/;
|
|
|
15
15
|
* If the comment only contains whitespace it is seen as empty and `null` is returned
|
|
16
16
|
* which also stops doc comment propagation.
|
|
17
17
|
*
|
|
18
|
+
* Notes on escape sequences:
|
|
19
|
+
* - For `*\/`, the `\` is removed.
|
|
20
|
+
* - Nothing else is escaped, meaning `\n` will be `\` and `n` and not a newline character.
|
|
21
|
+
* - _If requested_, we could parse the doc comment similar to multiline string literals, but
|
|
22
|
+
* via an option.
|
|
23
|
+
*
|
|
18
24
|
* @param {string} comment Raw comment, e.g. '/** comment ... '.
|
|
19
25
|
* Must be a valid doc comment.
|
|
20
26
|
* @returns {string|null} Parsed contents or if the comment has an invalid format or
|
|
@@ -34,6 +40,7 @@ function parseDocComment( comment ) {
|
|
|
34
40
|
const content = lines[0]
|
|
35
41
|
.replace(/^\/[*]{2,}/, '')
|
|
36
42
|
.replace(/\*+\/$/, '')
|
|
43
|
+
.replace('*\\/', '*/') // escape sequence
|
|
37
44
|
.trim();
|
|
38
45
|
return isWhitespaceOrNewLineOnly(content) ? null : content;
|
|
39
46
|
}
|
|
@@ -66,7 +73,10 @@ function parseDocComment( comment ) {
|
|
|
66
73
|
const startIndex = (lines[0] === '') ? 1 : 0;
|
|
67
74
|
const endIndex = (lines[lines.length - 1] === '') ? lines.length - 1 : lines.length;
|
|
68
75
|
|
|
69
|
-
const content = lines
|
|
76
|
+
const content = lines
|
|
77
|
+
.slice(startIndex, endIndex)
|
|
78
|
+
.join('\n')
|
|
79
|
+
.replace('*\\/', '*/'); // escape sequence
|
|
70
80
|
return isWhitespaceOrNewLineOnly(content) ? null : content;
|
|
71
81
|
}
|
|
72
82
|
|
|
@@ -401,7 +401,8 @@ function checkExtensionDict( dict ) {
|
|
|
401
401
|
this.addAnnotation( def, prop, dup[prop] );
|
|
402
402
|
}
|
|
403
403
|
else if (prop === 'doc') {
|
|
404
|
-
|
|
404
|
+
// With explicit docComment:false, we don't emit a warning.
|
|
405
|
+
if (def.doc && this.options.docComment !== false) {
|
|
405
406
|
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
|
|
406
407
|
'Doc comment is overwritten by another one below' );
|
|
407
408
|
}
|
|
@@ -646,13 +647,15 @@ function docComment( node ) {
|
|
|
646
647
|
// This token is actually used by / assigned to an artifact.
|
|
647
648
|
token.isUsed = true;
|
|
648
649
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (node.doc) {
|
|
650
|
+
// With explicit docComment:false, we don't emit a warning.
|
|
651
|
+
if (node.doc && this.options.docComment !== false) {
|
|
652
652
|
this.warning( 'syntax-duplicate-doc-comment', node.doc.location, {},
|
|
653
653
|
'Doc comment is overwritten by another one below' );
|
|
654
654
|
}
|
|
655
|
-
|
|
655
|
+
|
|
656
|
+
// Either store the doc comment or a marker that there is one.
|
|
657
|
+
const val = !this.options.docComment ? true : parseDocComment( token.text );
|
|
658
|
+
node.doc = this.valueWithTokenLocation( val, token );
|
|
656
659
|
}
|
|
657
660
|
|
|
658
661
|
// Classify token (identifier category) for implicit names,
|
|
@@ -1099,7 +1102,7 @@ function aspectWithoutElements( art ) {
|
|
|
1099
1102
|
// TODO: Checking it here does not prevent aspect in CSN input having no elements!
|
|
1100
1103
|
art.elements = this.createDict();
|
|
1101
1104
|
if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
|
|
1102
|
-
this.error( null, [ art.name.location ], {},
|
|
1105
|
+
this.error( null, [ art.name.location, null ], {},
|
|
1103
1106
|
'Aspects without elements are not supported, yet' );
|
|
1104
1107
|
}
|
|
1105
1108
|
}
|
|
@@ -1189,7 +1192,7 @@ function setNullability( art, token1, token2 ) {
|
|
|
1189
1192
|
art.notNull = notNull;
|
|
1190
1193
|
}
|
|
1191
1194
|
|
|
1192
|
-
function reportDuplicateClause( prop,
|
|
1195
|
+
function reportDuplicateClause( prop, erroneous, chosen, keywords ) {
|
|
1193
1196
|
// probably easier for message linters not to use (?:) for the message id...?
|
|
1194
1197
|
const args = {
|
|
1195
1198
|
'#': prop,
|
|
@@ -1197,10 +1200,10 @@ function reportDuplicateClause( prop, errorneous, chosen, keywords ) {
|
|
|
1197
1200
|
line: chosen.location.line,
|
|
1198
1201
|
col: chosen.location.col,
|
|
1199
1202
|
};
|
|
1200
|
-
if (
|
|
1201
|
-
this.warning( 'syntax-duplicate-equal-clause',
|
|
1203
|
+
if (erroneous.val === chosen.val)
|
|
1204
|
+
this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
|
|
1202
1205
|
else
|
|
1203
|
-
this.message( 'syntax-duplicate-clause',
|
|
1206
|
+
this.message( 'syntax-duplicate-clause', erroneous.location, args );
|
|
1204
1207
|
}
|
|
1205
1208
|
|
|
1206
1209
|
const extensionsCode = {
|