@sap/cds-compiler 3.1.2 → 3.4.0
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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/language/language.g4
CHANGED
|
@@ -721,16 +721,21 @@ aspectDef[ art, outer ] locals[ name = {} ]
|
|
|
721
721
|
)*
|
|
722
722
|
)?
|
|
723
723
|
)?
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
724
|
+
( // `aspect MyAspect {};`
|
|
725
|
+
'{' { $art.elements = this.createDict(); }
|
|
726
|
+
( elementDef[ $art ]* )
|
|
727
|
+
'}' { this.finalizeDictOrArray( $art.elements ); }
|
|
728
|
+
// TODO: action definitions in a specific section?
|
|
729
|
+
(
|
|
730
|
+
ACTIONS '{' { $art.actions = this.createDict(); }
|
|
731
|
+
actionFunctionDef[ $art ]*
|
|
732
|
+
'}' { this.finalizeDictOrArray( $art.actions ); }
|
|
733
|
+
)?
|
|
734
|
+
optionalSemi
|
|
735
|
+
| // `aspect MyAspect;`, e.g. for annotation aspects.
|
|
736
|
+
{ this.aspectWithoutElements( $art ); }
|
|
737
|
+
requiredSemi
|
|
738
|
+
)
|
|
734
739
|
;
|
|
735
740
|
|
|
736
741
|
typeDef[ art, outer ] locals[ name = {} ]
|
|
@@ -751,7 +756,7 @@ extendType[ art, outer ] locals[ name = {} ]
|
|
|
751
756
|
{ $art.expectedKind = 'type'; $art.name = $name;
|
|
752
757
|
this.addItem( $art, $outer, 'extensions', 'extend' );
|
|
753
758
|
}
|
|
754
|
-
|
|
759
|
+
extendWithOptElementsOrType[ $art, $art ]
|
|
755
760
|
;
|
|
756
761
|
|
|
757
762
|
annotationDef[ art, outer ] locals[ name = {} ]
|
|
@@ -840,11 +845,16 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
|
|
|
840
845
|
enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
|
|
841
846
|
'}' { this.finalizeDictOrArray( $art.enum ); }
|
|
842
847
|
optionalSemi
|
|
848
|
+
|
|
|
849
|
+
// extend Art with (length: 10);
|
|
850
|
+
// `with` is required, or we could have `extend String(length:10);`.
|
|
851
|
+
typeNamedArgList[ $art ]
|
|
852
|
+
requiredSemi
|
|
843
853
|
)
|
|
844
854
|
)
|
|
845
855
|
;
|
|
846
856
|
|
|
847
|
-
|
|
857
|
+
extendWithOptElementsOrType[ art ]
|
|
848
858
|
:
|
|
849
859
|
WITH { this.noSemicolonHere(); this.docComment( $art ); }
|
|
850
860
|
annotationAssignment_ll1[ $art ]*
|
|
@@ -857,6 +867,10 @@ extendWithOptElements[ art ]
|
|
|
857
867
|
'}' { this.finalizeDictOrArray( $art.elements ); }
|
|
858
868
|
{ this.checkExtensionDict( $art.elements ); }
|
|
859
869
|
optionalSemi
|
|
870
|
+
|
|
|
871
|
+
// extend type|element Art with (length: 10);
|
|
872
|
+
typeNamedArgList[ $art ]
|
|
873
|
+
requiredSemi
|
|
860
874
|
|
|
|
861
875
|
requiredSemi
|
|
862
876
|
)
|
|
@@ -1066,12 +1080,12 @@ mixinElementDef[ outer ] locals[ art = {} ]
|
|
|
1066
1080
|
|
|
|
1067
1081
|
typeRefOptArgs[ $art ]
|
|
1068
1082
|
( as='=' expression
|
|
1069
|
-
{ this.
|
|
1083
|
+
{ this.error( 'syntax-unsupported-field', $as ); }
|
|
1070
1084
|
)?
|
|
1071
1085
|
)
|
|
1072
1086
|
|
|
|
1073
1087
|
as='=' expression
|
|
1074
|
-
{ this.
|
|
1088
|
+
{ this.error( 'syntax-unsupported-field', $as ); }
|
|
1075
1089
|
)
|
|
1076
1090
|
requiredSemi
|
|
1077
1091
|
;
|
|
@@ -1090,8 +1104,7 @@ elementDefInner[ art, outer, allowEq ]
|
|
|
1090
1104
|
( masked=MASKED
|
|
1091
1105
|
{
|
|
1092
1106
|
$art.masked = this.valueWithTokenLocation( true, $masked ) ;
|
|
1093
|
-
this.message( 'syntax-
|
|
1094
|
-
'Keyword $(KEYWORD) not supported' );
|
|
1107
|
+
this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
|
|
1095
1108
|
}
|
|
1096
1109
|
)?
|
|
1097
1110
|
// TODO: order?
|
|
@@ -1203,7 +1216,7 @@ elementDefInner[ art, outer, allowEq ]
|
|
|
1203
1216
|
eq='=' e=expression // never introduce AS as syntax variant of '='
|
|
1204
1217
|
{
|
|
1205
1218
|
if (!$allowEq || $e.expr && !$e.expr.literal )
|
|
1206
|
-
this.
|
|
1219
|
+
this.error( 'syntax-unsupported-field', $eq );
|
|
1207
1220
|
else if ($e.expr)
|
|
1208
1221
|
$art.value = $e.expr;
|
|
1209
1222
|
}
|
|
@@ -1220,7 +1233,7 @@ extendElement[ art, outer ]
|
|
|
1220
1233
|
( expected=ELEMENT { $art.expectedKind = 'element'; } )?
|
|
1221
1234
|
name=ident['Element']
|
|
1222
1235
|
{ this.addDef( $art, $outer, 'elements', 'extend', $name.id ); }
|
|
1223
|
-
|
|
1236
|
+
extendWithOptElementsOrType[ $art, $art ]
|
|
1224
1237
|
;
|
|
1225
1238
|
|
|
1226
1239
|
selectItemDef[ outer ] locals[ art ]
|
|
@@ -1246,19 +1259,20 @@ selectItemDefBody[ art, outer ]
|
|
|
1246
1259
|
e=expression { $art.value = $e.expr; }
|
|
1247
1260
|
// we cannot use 'condition' instead, as long as we allow aliases without
|
|
1248
1261
|
// AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
|
|
1249
|
-
( AS n1=ident['Item'] { $art.name = $n1.id }
|
|
1262
|
+
( as=AS n1=ident['Item'] { $art.name = $n1.id }
|
|
1250
1263
|
| n2=ident['Item'] { $art.name = this.fragileAlias( $n2.id, true ); }
|
|
1251
1264
|
| { if (this.getCurrentToken().text !== '.') this.classifyImplicitName( 'Item', $e.expr ); }
|
|
1252
1265
|
)
|
|
1253
|
-
{ if ($art.value && !$art.value.path) this.excludeExpected( ["'.'", "'{'"] );
|
|
1266
|
+
{ if ($art.value && !$art.value.path) this.excludeExpected( ["'.'", "'{'"] );
|
|
1267
|
+
else if ($art.name) this.excludeExpected( ["'.'"] );
|
|
1268
|
+
}
|
|
1254
1269
|
(
|
|
1255
|
-
{
|
|
1270
|
+
{ this.reportExpandInline( $art, false ); }
|
|
1256
1271
|
selectItemInlineList[ $art, 'expand' ]
|
|
1257
1272
|
excludingClause[ $art ]?
|
|
1258
1273
|
// TODO: we might alternatively allow AS here
|
|
1259
1274
|
|
|
|
1260
|
-
|
|
1261
|
-
{ if ($art.value && !$art.value.path) this.reportExpandInline( 'inline' ); }
|
|
1275
|
+
{ this.reportExpandInline( $art, $as || this._input.LT(-1) ); }
|
|
1262
1276
|
DOTbeforeBRACE // ...orASTERISK
|
|
1263
1277
|
(
|
|
1264
1278
|
selectItemInlineList[ $art, 'inline' ]
|
|
@@ -1384,7 +1398,7 @@ elementProperties[ elem ]
|
|
|
1384
1398
|
nullability[ $elem ]?
|
|
1385
1399
|
|
|
|
1386
1400
|
eq='='
|
|
1387
|
-
{ this.
|
|
1401
|
+
{ this.error( 'syntax-unsupported-field', $eq ); }
|
|
1388
1402
|
expression
|
|
1389
1403
|
;
|
|
1390
1404
|
|
|
@@ -1799,6 +1813,16 @@ typeRefArgs[ art ]
|
|
|
1799
1813
|
')'{ this.finalizeDictOrArray( $art['$'+'typeArgs']); }
|
|
1800
1814
|
;
|
|
1801
1815
|
|
|
1816
|
+
typeNamedArgList[ art ]
|
|
1817
|
+
:
|
|
1818
|
+
paren='('
|
|
1819
|
+
typeNamedArg[ $art ]
|
|
1820
|
+
( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
|
|
1821
|
+
typeNamedArg[ $art ]
|
|
1822
|
+
)*
|
|
1823
|
+
')'
|
|
1824
|
+
;
|
|
1825
|
+
|
|
1802
1826
|
typeNamedArg[ art ] locals[ arg = '' ]
|
|
1803
1827
|
:
|
|
1804
1828
|
name=ident['paramname']
|
|
@@ -2206,7 +2230,7 @@ conditionTerm returns [ cond ]
|
|
|
2206
2230
|
{ $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
|
|
2207
2231
|
{ param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
|
|
2208
2232
|
] };
|
|
2209
|
-
this.csnParseOnly( '
|
|
2233
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
|
|
2210
2234
|
}
|
|
2211
2235
|
|
|
|
2212
2236
|
ep=valuePath[ 'ref' ]
|
|
@@ -2330,7 +2354,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2330
2354
|
ne=NEW nqp=valuePath[ 'ref', null] // token rewrite for NEW
|
|
2331
2355
|
// please note: there will be no compiler-supported code completion after NEW
|
|
2332
2356
|
{ $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
|
|
2333
|
-
this.
|
|
2357
|
+
this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
|
|
2334
2358
|
|
|
|
2335
2359
|
vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
|
|
2336
2360
|
{ this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
|
|
@@ -2343,7 +2367,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2343
2367
|
{ $expr = $vp.qp;; $expr.scope = 'param'; }
|
|
2344
2368
|
| pp=Number
|
|
2345
2369
|
{ $expr = { param: this.numberLiteral( $pp ), scope: 'param' };
|
|
2346
|
-
this.csnParseOnly( '
|
|
2370
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', name: ':' + $pp.text } );
|
|
2347
2371
|
}
|
|
2348
2372
|
)
|
|
2349
2373
|
|
|
|
@@ -2351,7 +2375,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2351
2375
|
// if we have an HideAlternatives here, we would block it to use it in
|
|
2352
2376
|
// parallel to an expression (would produce adaptivePredict() otherwise)
|
|
2353
2377
|
{ $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
|
|
2354
|
-
this.csnParseOnly( '
|
|
2378
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
|
|
2355
2379
|
}
|
|
2356
2380
|
|
|
|
2357
2381
|
open='('
|
|
@@ -2641,8 +2665,9 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2641
2665
|
const item = { literal: 'token', val: '...', location: this.tokenLocation($e) };
|
|
2642
2666
|
if ($ctx.upTo) item.upTo = $upTo.val;
|
|
2643
2667
|
$assignment.val.push( item );
|
|
2644
|
-
if ($seenEllipsis === true)
|
|
2645
|
-
this.error( 'syntax-unexpected-ellipsis', $e,
|
|
2668
|
+
if ($seenEllipsis === true)
|
|
2669
|
+
this.error( 'syntax-unexpected-ellipsis', $e,
|
|
2670
|
+
{ '#': 'duplicate', code: '...', keyword: 'up to' } );
|
|
2646
2671
|
else
|
|
2647
2672
|
$seenEllipsis = !$ctx.upTo || 'upTo';
|
|
2648
2673
|
}}
|
|
@@ -2652,9 +2677,8 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2652
2677
|
cb=']'
|
|
2653
2678
|
{
|
|
2654
2679
|
if ($seenEllipsis === 'upTo')
|
|
2655
|
-
this.error( 'syntax-
|
|
2656
|
-
{ code: '... up to', newcode: '...' }
|
|
2657
|
-
'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
|
|
2680
|
+
this.error( 'syntax-missing-ellipsis', $cb, // at closing bracket
|
|
2681
|
+
{ code: '... up to', newcode: '...' } );
|
|
2658
2682
|
}
|
|
2659
2683
|
|
|
|
2660
2684
|
v1=literalValue { Object.assign( $assignment, $v1.val ); }
|
|
@@ -25,9 +25,9 @@ const {
|
|
|
25
25
|
* of whitespace characters removed.
|
|
26
26
|
*/
|
|
27
27
|
function stripIndentation(str) {
|
|
28
|
-
if (str === '')
|
|
29
|
-
return ['', 0];
|
|
30
|
-
|
|
28
|
+
if (str === '')
|
|
29
|
+
return [ '', 0 ];
|
|
30
|
+
|
|
31
31
|
|
|
32
32
|
// Note: We have to check all newline characters, as the string is not normalized, yet.
|
|
33
33
|
const lines = str.split(cdlNewLineRegEx);
|
|
@@ -39,20 +39,19 @@ function stripIndentation(str) {
|
|
|
39
39
|
// If there is a trailing line break, it means that ``` is on newline and
|
|
40
40
|
// therefore the indentation to remove is 0.
|
|
41
41
|
// Remove the last newline, which may be CRLF.
|
|
42
|
-
return [lines.slice(0, -1).join('\n'), 0];
|
|
42
|
+
return [ lines.slice(0, -1).join('\n'), 0 ];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const minIndent = lines.reduce((min, line, index) => {
|
|
46
46
|
// Note: Last line is the line containing ```. There, we always count the indentation,
|
|
47
47
|
// even if blank. For all other lines, blank lines are ignored.
|
|
48
|
-
if (isWhitespaceOrNewLineOnly(line) && index !== (n-1))
|
|
48
|
+
if (isWhitespaceOrNewLineOnly(line) && index !== (n - 1))
|
|
49
49
|
return min;
|
|
50
50
|
|
|
51
51
|
let count = 0;
|
|
52
52
|
const length = Math.min(min, line.length);
|
|
53
|
-
while (count < length && isWhitespaceCharacterNoNewline(line[count]))
|
|
53
|
+
while (count < length && isWhitespaceCharacterNoNewline(line[count]))
|
|
54
54
|
count++;
|
|
55
|
-
}
|
|
56
55
|
return Math.min(min, count);
|
|
57
56
|
}, Number.MAX_SAFE_INTEGER);
|
|
58
57
|
|
|
@@ -63,10 +62,10 @@ function stripIndentation(str) {
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// Remove trailing last line, if there was nothing else in that line.
|
|
66
|
-
if (lines[n-1] === '')
|
|
65
|
+
if (lines[n - 1] === '')
|
|
67
66
|
lines.pop();
|
|
68
67
|
|
|
69
|
-
return [lines.join('\n'), minIndent];
|
|
68
|
+
return [ lines.join('\n'), minIndent ];
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
class MultiLineStringParser {
|
|
@@ -75,9 +74,9 @@ class MultiLineStringParser {
|
|
|
75
74
|
this.token = token;
|
|
76
75
|
this.str = token.text; // Copy because .text is a getter
|
|
77
76
|
|
|
78
|
-
if (this.str[0] !== '`' || this.str[this.str.length-1] !== '`')
|
|
77
|
+
if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
|
|
78
|
+
// eslint-disable-next-line max-len
|
|
79
79
|
throw new Error('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
|
|
80
|
-
}
|
|
81
80
|
|
|
82
81
|
this.output = [];
|
|
83
82
|
this.isTextBlock = this.str.startsWith('```');
|
|
@@ -90,7 +89,8 @@ class MultiLineStringParser {
|
|
|
90
89
|
if (this.isTextBlock) {
|
|
91
90
|
this.i = 3;
|
|
92
91
|
this.end = this.str.length - 3;
|
|
93
|
-
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
94
|
this.i = 1;
|
|
95
95
|
this.end = this.str.length - 1;
|
|
96
96
|
}
|
|
@@ -102,9 +102,8 @@ class MultiLineStringParser {
|
|
|
102
102
|
* @return {string}
|
|
103
103
|
*/
|
|
104
104
|
parse() {
|
|
105
|
-
if (this.str.length === 2)
|
|
105
|
+
if (this.str.length === 2)
|
|
106
106
|
return ''; // Nothing to do: ``
|
|
107
|
-
}
|
|
108
107
|
|
|
109
108
|
if (this.isTextBlock) {
|
|
110
109
|
// If there are no line breaks, emit an error as normal single-back-tick
|
|
@@ -112,7 +111,7 @@ class MultiLineStringParser {
|
|
|
112
111
|
// there is no text without at least one line break.
|
|
113
112
|
if (!cdlNewLineRegEx.test(this.str)) {
|
|
114
113
|
const loc = this._locationForCharacters(this.end, 1);
|
|
115
|
-
this.parser.
|
|
114
|
+
this.parser.error('syntax-invalid-text-block', loc);
|
|
116
115
|
return '';
|
|
117
116
|
}
|
|
118
117
|
this._skipOptionalLanguageIdentifierLine();
|
|
@@ -135,7 +134,7 @@ class MultiLineStringParser {
|
|
|
135
134
|
// Note: Index is at first character of string
|
|
136
135
|
|
|
137
136
|
do {
|
|
138
|
-
switch(this._current()) {
|
|
137
|
+
switch (this._current()) {
|
|
139
138
|
case this._matchLineBreakAtCurrentChar():
|
|
140
139
|
this.output.push('\n');
|
|
141
140
|
break;
|
|
@@ -146,8 +145,8 @@ class MultiLineStringParser {
|
|
|
146
145
|
case '$':
|
|
147
146
|
if (this._lookahead() === '{') {
|
|
148
147
|
const loc = this._locationForCharacters(this.i, 2);
|
|
149
|
-
this.parser.
|
|
150
|
-
|
|
148
|
+
this.parser.error('syntax-missing-escape', loc,
|
|
149
|
+
{ '#': 'placeholder', code: '${', newcode: '\\${' });
|
|
151
150
|
}
|
|
152
151
|
this.output.push(this.str[this.i]);
|
|
153
152
|
break;
|
|
@@ -155,7 +154,7 @@ class MultiLineStringParser {
|
|
|
155
154
|
this.output.push(this.str[this.i]);
|
|
156
155
|
break;
|
|
157
156
|
}
|
|
158
|
-
} while(this._move());
|
|
157
|
+
} while (this._move());
|
|
159
158
|
|
|
160
159
|
return this.output.join('');
|
|
161
160
|
}
|
|
@@ -166,27 +165,27 @@ class MultiLineStringParser {
|
|
|
166
165
|
* @private
|
|
167
166
|
*/
|
|
168
167
|
_innerEscape() {
|
|
169
|
-
switch(this._current()) {
|
|
168
|
+
switch (this._current()) {
|
|
170
169
|
case this._matchLineBreakAtCurrentChar():
|
|
171
170
|
// Don't add to output -> line break is escaped
|
|
172
171
|
break;
|
|
173
172
|
case 'b': // backspace
|
|
174
|
-
this.output.push(
|
|
173
|
+
this.output.push('\b');
|
|
175
174
|
break;
|
|
176
175
|
case 'f': // form feed
|
|
177
|
-
this.output.push(
|
|
176
|
+
this.output.push('\f');
|
|
178
177
|
break;
|
|
179
178
|
case 'v': // vertical tabulator
|
|
180
|
-
this.output.push(
|
|
179
|
+
this.output.push('\v');
|
|
181
180
|
break;
|
|
182
181
|
case 'r': // carriage return
|
|
183
|
-
this.output.push(
|
|
182
|
+
this.output.push('\r');
|
|
184
183
|
break;
|
|
185
184
|
case 'n': // line feed
|
|
186
|
-
this.output.push(
|
|
185
|
+
this.output.push('\n');
|
|
187
186
|
break;
|
|
188
187
|
case 't': // tab
|
|
189
|
-
this.output.push(
|
|
188
|
+
this.output.push('\t');
|
|
190
189
|
break;
|
|
191
190
|
case '\\':
|
|
192
191
|
case '"':
|
|
@@ -206,21 +205,23 @@ class MultiLineStringParser {
|
|
|
206
205
|
break;
|
|
207
206
|
case '0': // null terminator
|
|
208
207
|
if (!/^\d$/.test(this._lookahead())) {
|
|
209
|
-
this.output.push(
|
|
208
|
+
this.output.push('\0');
|
|
210
209
|
break;
|
|
211
210
|
}
|
|
212
211
|
// Let the default case handle octal representation.
|
|
213
212
|
// fallthrough
|
|
214
213
|
default: {
|
|
215
214
|
this.output.push(this._current());
|
|
216
|
-
const loc = this._locationForCharacters(this.i-1, 2);
|
|
215
|
+
const loc = this._locationForCharacters(this.i - 1, 2);
|
|
217
216
|
if (/\s/.test(this._current())) {
|
|
218
|
-
this.parser.
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'whitespace' });
|
|
218
|
+
}
|
|
219
|
+
else if (/\d/.test(this._current())) {
|
|
220
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'octal' });
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const code = this._makeCode(`\\${ this._current() }`);
|
|
224
|
+
this.parser.message('syntax-unknown-escape', loc, { '#': 'std', code });
|
|
224
225
|
}
|
|
225
226
|
break;
|
|
226
227
|
}
|
|
@@ -239,9 +240,9 @@ class MultiLineStringParser {
|
|
|
239
240
|
// syntax variant as the first invalid code point is \u{110000}
|
|
240
241
|
// and an empty `codePoint` is only possible with the braced variant.
|
|
241
242
|
const reportInvalidCodePoint = () => {
|
|
242
|
-
const code = this._makeCode(`\\u{${codePoint}}`);
|
|
243
|
-
const loc = this._locationForCharacters(this.i-codePoint.length, codePoint.length);
|
|
244
|
-
this.parser.
|
|
243
|
+
const code = this._makeCode(`\\u{${ codePoint }}`);
|
|
244
|
+
const loc = this._locationForCharacters(this.i - codePoint.length, codePoint.length);
|
|
245
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'codepoint', code });
|
|
245
246
|
};
|
|
246
247
|
|
|
247
248
|
const n = Number.parseInt(codePoint, 16);
|
|
@@ -252,7 +253,8 @@ class MultiLineStringParser {
|
|
|
252
253
|
|
|
253
254
|
try {
|
|
254
255
|
this.output.push(String.fromCodePoint(n));
|
|
255
|
-
}
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
256
258
|
// RangeError is thrown if number isn't a valid code point
|
|
257
259
|
reportInvalidCodePoint();
|
|
258
260
|
}
|
|
@@ -275,18 +277,20 @@ class MultiLineStringParser {
|
|
|
275
277
|
if (!this._eos() && /^[\dA-Fa-f]$/.test(this._lookahead())) {
|
|
276
278
|
this._move();
|
|
277
279
|
codePoint += this._current();
|
|
278
|
-
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
279
282
|
break;
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
if (codePoint.length === count) {
|
|
284
287
|
this._parseHexCodePoint(codePoint);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
this.
|
|
289
|
-
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const loc = this._locationForCharacters(this.i + 1, 1);
|
|
291
|
+
const code = this._eos(this.i + 1) ? `\\${ mode }${ codePoint }` : `\\${ mode }${ codePoint }${ this._lookahead() }`;
|
|
292
|
+
this.parser.error('syntax-invalid-escape', loc,
|
|
293
|
+
{ '#': 'hex-count', number: count, code: this._makeCode(code) });
|
|
290
294
|
}
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -305,14 +309,17 @@ class MultiLineStringParser {
|
|
|
305
309
|
if (/^[\dA-Fa-f]$/.test(this._lookahead())) {
|
|
306
310
|
this._move();
|
|
307
311
|
codePoint += this._current();
|
|
308
|
-
}
|
|
312
|
+
}
|
|
313
|
+
else if (this._lookahead() === '}') {
|
|
309
314
|
break;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
this.
|
|
315
|
+
}
|
|
316
|
+
else if (!this._eos(this.i + 1)) {
|
|
317
|
+
const loc = this._locationForCharacters(this.i + 1, 1); // Point to the exact character
|
|
318
|
+
const code = this._makeCode(`\\u{${ codePoint }${ this._lookahead() }…}`);
|
|
319
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'unicode-hex', code });
|
|
314
320
|
return;
|
|
315
|
-
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
316
323
|
break;
|
|
317
324
|
}
|
|
318
325
|
}
|
|
@@ -320,9 +327,10 @@ class MultiLineStringParser {
|
|
|
320
327
|
if (this._lookahead() === '}') {
|
|
321
328
|
this._move();
|
|
322
329
|
this._parseHexCodePoint(codePoint);
|
|
323
|
-
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
324
332
|
const loc = this._locationForCharacters(this.i, 1);
|
|
325
|
-
this.parser.
|
|
333
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'unicode-brace' });
|
|
326
334
|
}
|
|
327
335
|
}
|
|
328
336
|
|
|
@@ -333,7 +341,7 @@ class MultiLineStringParser {
|
|
|
333
341
|
* @private
|
|
334
342
|
*/
|
|
335
343
|
_skipOptionalLanguageIdentifierLine() {
|
|
336
|
-
while(!this._eos()) {
|
|
344
|
+
while (!this._eos()) {
|
|
337
345
|
switch (this._current()) {
|
|
338
346
|
case this._matchLineBreakAtCurrentChar():
|
|
339
347
|
this._move();
|
|
@@ -342,7 +350,7 @@ class MultiLineStringParser {
|
|
|
342
350
|
// Do not allow an escape in the language identifier. If at the line's end, users
|
|
343
351
|
// may expect the identifier to span more than the first line, which is _not_ the case.
|
|
344
352
|
const loc = this._locationForCharacters(this.i, 1);
|
|
345
|
-
this.parser.
|
|
353
|
+
this.parser.error('syntax-invalid-escape', loc, { '#': 'language-identifier' });
|
|
346
354
|
this._move();
|
|
347
355
|
break;
|
|
348
356
|
}
|
|
@@ -367,7 +375,7 @@ class MultiLineStringParser {
|
|
|
367
375
|
*/
|
|
368
376
|
_matchLineBreakAtCurrentChar() {
|
|
369
377
|
// Only increase line number for \n, because ANTLR does the same
|
|
370
|
-
switch(this._current()) {
|
|
378
|
+
switch (this._current()) {
|
|
371
379
|
case '\r':
|
|
372
380
|
if (this._lookahead() === '\n') {
|
|
373
381
|
this._move(); // \r\n is normalized
|
|
@@ -382,6 +390,7 @@ class MultiLineStringParser {
|
|
|
382
390
|
case '\u2028': // LS
|
|
383
391
|
case '\u2029': // PS
|
|
384
392
|
return this._current();
|
|
393
|
+
default: break;
|
|
385
394
|
}
|
|
386
395
|
return null;
|
|
387
396
|
}
|
|
@@ -419,7 +428,7 @@ class MultiLineStringParser {
|
|
|
419
428
|
* @returns {string}
|
|
420
429
|
*/
|
|
421
430
|
_lookahead() {
|
|
422
|
-
return this.str[this.i+1];
|
|
431
|
+
return this.str[this.i + 1];
|
|
423
432
|
}
|
|
424
433
|
|
|
425
434
|
/**
|
|
@@ -439,7 +448,7 @@ class MultiLineStringParser {
|
|
|
439
448
|
* @returns {string}
|
|
440
449
|
*/
|
|
441
450
|
_previous() {
|
|
442
|
-
return this.str[this.i-1];
|
|
451
|
+
return this.str[this.i - 1];
|
|
443
452
|
}
|
|
444
453
|
|
|
445
454
|
/**
|
|
@@ -455,8 +464,12 @@ class MultiLineStringParser {
|
|
|
455
464
|
file: this.parser.filename,
|
|
456
465
|
line: this.token.line + this._lineInString,
|
|
457
466
|
endLine: this.token.line + this._lineInString,
|
|
458
|
-
col: this._lineInString > 0
|
|
459
|
-
|
|
467
|
+
col: this._lineInString > 0
|
|
468
|
+
? i - this._currentLineBreakIndex + this._indentation
|
|
469
|
+
: this.token.column + i + 1,
|
|
470
|
+
endCol: this._lineInString > 0
|
|
471
|
+
? i - this._currentLineBreakIndex + width + this._indentation
|
|
472
|
+
: this.token.column + i + width + 1,
|
|
460
473
|
};
|
|
461
474
|
}
|
|
462
475
|
|
|
@@ -468,7 +481,7 @@ class MultiLineStringParser {
|
|
|
468
481
|
* @param {string} code
|
|
469
482
|
* @private
|
|
470
483
|
*/
|
|
471
|
-
_makeCode(code) {
|
|
484
|
+
_makeCode(code) { // eslint-disable-line class-methods-use-this
|
|
472
485
|
// For characters that may be rendered as newline,
|
|
473
486
|
// see <https://www.unicode.org/reports/tr14/tr14-32.html>.
|
|
474
487
|
//
|
|
@@ -484,9 +497,8 @@ class MultiLineStringParser {
|
|
|
484
497
|
//
|
|
485
498
|
// For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
|
|
486
499
|
// U+23CE: ⏎
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return code.replace(allPossibleNewLineCharacters, '\u{23CE}');
|
|
500
|
+
const allNewLineCharacters = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
|
|
501
|
+
return code.replace(allNewLineCharacters, '\u{23CE}');
|
|
490
502
|
}
|
|
491
503
|
}
|
|
492
504
|
|
package/lib/main.d.ts
CHANGED
|
@@ -786,15 +786,12 @@ declare namespace compiler {
|
|
|
786
786
|
*/
|
|
787
787
|
export type CdlResult = {
|
|
788
788
|
/**
|
|
789
|
-
* Rendered model,
|
|
789
|
+
* Rendered model, including extensions.
|
|
790
790
|
*/
|
|
791
791
|
model?: string
|
|
792
792
|
/**
|
|
793
|
-
* Rendered
|
|
794
|
-
|
|
795
|
-
unappliedExtensions?: string
|
|
796
|
-
/**
|
|
797
|
-
* Rendered csn.namespace property + using directives.
|
|
793
|
+
* Rendered `csn.namespace` property + using directives.
|
|
794
|
+
* Useful to keep the `csn.namespace` property when re-compiling the to.cdl() result.
|
|
798
795
|
*/
|
|
799
796
|
namespace?: string
|
|
800
797
|
}
|
package/lib/main.js
CHANGED
|
@@ -115,6 +115,7 @@ module.exports = {
|
|
|
115
115
|
functions: Object.freeze([ ...keywords.cdl_functions ] ),
|
|
116
116
|
}),
|
|
117
117
|
sql: Object.assign((...args) => snapi.sql(...args), {
|
|
118
|
+
migration: (...args) => snapi.sql.migration(...args),
|
|
118
119
|
sqlite: {
|
|
119
120
|
keywords: Object.freeze([ ...keywords.sqlite ] )
|
|
120
121
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"extends": "../../.eslintrc-ydkjsi.json",
|
|
4
|
+
"rules": {
|
|
5
|
+
"max-len": [ "error", {
|
|
6
|
+
"code": 180, // TODO: Remove
|
|
7
|
+
"tabWidth": 2,
|
|
8
|
+
"ignoreRegExpLiterals": false,
|
|
9
|
+
"ignoreStrings": false,
|
|
10
|
+
"ignoreTemplateLiterals": true
|
|
11
|
+
}]
|
|
12
|
+
}
|
|
13
|
+
}
|
package/lib/model/api.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// Do not change at will - they are in the compiler API!
|
|
4
4
|
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Dictionary of default traversal functions for function `traverseCsn`.
|
|
7
9
|
* It maps CSN property names to functions which are used by default
|
|
@@ -23,8 +25,8 @@ const defaultFunctions = {
|
|
|
23
25
|
actions: dictionary,
|
|
24
26
|
mixin: dictionary,
|
|
25
27
|
definitions: dictionary,
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
+
$: () => { /* do not traverse properties starting with '$' */ },
|
|
29
|
+
};
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Traverse the CSN node `csn`.
|