@weborigami/language 0.4.2 → 0.5.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/main.js +0 -1
- package/package.json +6 -6
- package/src/compiler/compile.js +2 -4
- package/src/compiler/optimize.js +7 -2
- package/src/compiler/origami.pegjs +13 -9
- package/src/compiler/parse.js +254 -220
- package/src/compiler/parserHelpers.js +10 -8
- package/src/runtime/errors.js +4 -0
- package/src/runtime/handlers.js +1 -0
- package/src/runtime/jsGlobals.js +3 -0
- package/src/runtime/ops.js +54 -10
- package/test/compiler/compile.test.js +4 -5
- package/test/compiler/optimize.test.js +4 -3
- package/test/compiler/parse.test.js +145 -147
- package/test/runtime/ops.test.js +8 -1
- package/src/runtime/templateIndent.js +0 -120
- package/test/runtime/taggedTemplateIndent.test.js +0 -44
|
@@ -72,16 +72,11 @@ describe("Origami parser", () => {
|
|
|
72
72
|
[ops.literal, "src/"],
|
|
73
73
|
[ops.literal, "assets"],
|
|
74
74
|
]);
|
|
75
|
-
assertParse(
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
[
|
|
79
|
-
|
|
80
|
-
[ops.literal, "example.com/"],
|
|
81
|
-
[ops.literal, "data.yaml"],
|
|
82
|
-
],
|
|
83
|
-
"jse"
|
|
84
|
-
);
|
|
75
|
+
assertParse("angleBracketLiteral", "<https://example.com/data.yaml>", [
|
|
76
|
+
[markers.global, "https:"],
|
|
77
|
+
[ops.literal, "example.com/"],
|
|
78
|
+
[ops.literal, "data.yaml"],
|
|
79
|
+
]);
|
|
85
80
|
});
|
|
86
81
|
});
|
|
87
82
|
|
|
@@ -313,6 +308,7 @@ describe("Origami parser", () => {
|
|
|
313
308
|
[ops.literal, "assets"],
|
|
314
309
|
]);
|
|
315
310
|
assertParse("callExpression", "<node:process>.env", [
|
|
311
|
+
ops.property,
|
|
316
312
|
[
|
|
317
313
|
[markers.global, "node:"],
|
|
318
314
|
[ops.literal, "process"],
|
|
@@ -475,17 +471,21 @@ Body`,
|
|
|
475
471
|
|
|
476
472
|
test("property acccess", () => {
|
|
477
473
|
assertParse("expression", "(foo).bar", [
|
|
474
|
+
ops.property,
|
|
478
475
|
[markers.traverse, [markers.reference, "foo"]],
|
|
479
476
|
[ops.literal, "bar"],
|
|
480
477
|
]);
|
|
481
478
|
assertParse("expression", "(foo).bar.baz", [
|
|
479
|
+
ops.property,
|
|
482
480
|
[
|
|
481
|
+
ops.property,
|
|
483
482
|
[markers.traverse, [markers.reference, "foo"]],
|
|
484
483
|
[ops.literal, "bar"],
|
|
485
484
|
],
|
|
486
485
|
[ops.literal, "baz"],
|
|
487
486
|
]);
|
|
488
487
|
assertParse("expression", "foo[bar]", [
|
|
488
|
+
ops.property,
|
|
489
489
|
[markers.traverse, [markers.reference, "foo"]],
|
|
490
490
|
[markers.traverse, [markers.reference, "bar"]],
|
|
491
491
|
]);
|
|
@@ -500,7 +500,7 @@ Body`,
|
|
|
500
500
|
"expression",
|
|
501
501
|
"x //comment",
|
|
502
502
|
[markers.traverse, [markers.reference, "x"]],
|
|
503
|
-
"
|
|
503
|
+
"program",
|
|
504
504
|
false
|
|
505
505
|
);
|
|
506
506
|
});
|
|
@@ -557,7 +557,7 @@ Body`,
|
|
|
557
557
|
[
|
|
558
558
|
ops.lambda,
|
|
559
559
|
[[ops.literal, "_"]],
|
|
560
|
-
[ops.
|
|
560
|
+
[ops.templateText, [ops.literal, ["x"]]],
|
|
561
561
|
],
|
|
562
562
|
]);
|
|
563
563
|
assertParse("expression", "copy app.js(formulas), files:snapshot", [
|
|
@@ -577,7 +577,7 @@ Body`,
|
|
|
577
577
|
ops.lambda,
|
|
578
578
|
[[ops.literal, "_"]],
|
|
579
579
|
[
|
|
580
|
-
ops.
|
|
580
|
+
ops.templateText,
|
|
581
581
|
[ops.literal, ["<li>", "</li>"]],
|
|
582
582
|
[markers.traverse, [markers.reference, "_"]],
|
|
583
583
|
],
|
|
@@ -679,7 +679,7 @@ Body`,
|
|
|
679
679
|
[[ops.literal, "name"]],
|
|
680
680
|
[[markers.traverse, [markers.reference, "_template"]], undefined],
|
|
681
681
|
],
|
|
682
|
-
"
|
|
682
|
+
"program",
|
|
683
683
|
false
|
|
684
684
|
);
|
|
685
685
|
});
|
|
@@ -709,8 +709,8 @@ Body`,
|
|
|
709
709
|
});
|
|
710
710
|
|
|
711
711
|
test("identifier", () => {
|
|
712
|
-
assertParse("identifier", "foo", "foo", "
|
|
713
|
-
assertParse("identifier", "$Δelta", "$Δelta", "
|
|
712
|
+
assertParse("identifier", "foo", "foo", "program", false);
|
|
713
|
+
assertParse("identifier", "$Δelta", "$Δelta", "program", false);
|
|
714
714
|
assertThrows(
|
|
715
715
|
"identifier",
|
|
716
716
|
"1stCharacterIsNumber",
|
|
@@ -841,7 +841,7 @@ Body`,
|
|
|
841
841
|
"multiLineComment",
|
|
842
842
|
"/*\nHello, world!\n*/",
|
|
843
843
|
null,
|
|
844
|
-
"
|
|
844
|
+
"program",
|
|
845
845
|
false
|
|
846
846
|
);
|
|
847
847
|
});
|
|
@@ -895,110 +895,87 @@ Body`,
|
|
|
895
895
|
assertParse("numericLiteral", "123.45", [ops.literal, 123.45]);
|
|
896
896
|
});
|
|
897
897
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
|
|
921
|
-
["b", [ops.literal, 2]],
|
|
922
|
-
]);
|
|
923
|
-
assertParse(
|
|
924
|
-
"objectLiteral",
|
|
925
|
-
`{
|
|
926
|
-
a = b
|
|
927
|
-
b = 2
|
|
928
|
-
}`,
|
|
929
|
-
[
|
|
898
|
+
describe("objectLiteral", () => {
|
|
899
|
+
describe("basic objects", () => {
|
|
900
|
+
assertParse("objectLiteral", "{}", [ops.object]);
|
|
901
|
+
assertParse("objectLiteral", "{ a: 1, b }", [
|
|
902
|
+
ops.object,
|
|
903
|
+
["a", [ops.literal, 1]],
|
|
904
|
+
["b", [markers.traverse, [markers.reference, "b"]]],
|
|
905
|
+
]);
|
|
906
|
+
assertParse("objectLiteral", "{ sub: { a: 1 } }", [
|
|
907
|
+
ops.object,
|
|
908
|
+
["sub", [ops.object, ["a", [ops.literal, 1]]]],
|
|
909
|
+
]);
|
|
910
|
+
assertParse("objectLiteral", "{ sub: { a/: 1 } }", [
|
|
911
|
+
ops.object,
|
|
912
|
+
["sub", [ops.object, ["a/", [ops.literal, 1]]]],
|
|
913
|
+
]);
|
|
914
|
+
assertParse("objectLiteral", `{ "a": 1, "b": 2 }`, [
|
|
915
|
+
ops.object,
|
|
916
|
+
["a", [ops.literal, 1]],
|
|
917
|
+
["b", [ops.literal, 2]],
|
|
918
|
+
]);
|
|
919
|
+
assertParse("objectLiteral", "{ a = b, b = 2 }", [
|
|
930
920
|
ops.object,
|
|
931
921
|
["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
|
|
932
922
|
["b", [ops.literal, 2]],
|
|
933
|
-
]
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
ops.object,
|
|
941
|
-
["a", [ops.object, ["b", [ops.literal, 1]]]],
|
|
942
|
-
]);
|
|
943
|
-
assertParse("objectLiteral", "{ a: { b = fn() } }", [
|
|
944
|
-
ops.object,
|
|
945
|
-
[
|
|
946
|
-
"a",
|
|
923
|
+
]);
|
|
924
|
+
assertParse(
|
|
925
|
+
"objectLiteral",
|
|
926
|
+
`{
|
|
927
|
+
a = b
|
|
928
|
+
b = 2
|
|
929
|
+
}`,
|
|
947
930
|
[
|
|
948
931
|
ops.object,
|
|
932
|
+
["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
|
|
933
|
+
["b", [ops.literal, 2]],
|
|
934
|
+
]
|
|
935
|
+
);
|
|
936
|
+
assertParse("objectLiteral", "{ a: { b: 1 } }", [
|
|
937
|
+
ops.object,
|
|
938
|
+
["a", [ops.object, ["b", [ops.literal, 1]]]],
|
|
939
|
+
]);
|
|
940
|
+
assertParse("objectLiteral", "{ a: { b = 1 } }", [
|
|
941
|
+
ops.object,
|
|
942
|
+
["a", [ops.object, ["b", [ops.literal, 1]]]],
|
|
943
|
+
]);
|
|
944
|
+
assertParse("objectLiteral", "{ a: { b = fn() } }", [
|
|
945
|
+
ops.object,
|
|
946
|
+
[
|
|
947
|
+
"a",
|
|
949
948
|
[
|
|
950
|
-
|
|
949
|
+
ops.object,
|
|
951
950
|
[
|
|
952
|
-
|
|
953
|
-
[
|
|
951
|
+
"b",
|
|
952
|
+
[
|
|
953
|
+
ops.getter,
|
|
954
|
+
[[markers.traverse, [markers.reference, "fn"]], undefined],
|
|
955
|
+
],
|
|
954
956
|
],
|
|
955
957
|
],
|
|
956
958
|
],
|
|
957
|
-
]
|
|
958
|
-
|
|
959
|
-
assertParse("objectLiteral", "{ x = fn.js('a') }", [
|
|
960
|
-
ops.object,
|
|
961
|
-
[
|
|
962
|
-
"x",
|
|
963
|
-
[
|
|
964
|
-
ops.getter,
|
|
965
|
-
[
|
|
966
|
-
[markers.traverse, [markers.reference, "fn.js"]],
|
|
967
|
-
[ops.literal, "a"],
|
|
968
|
-
],
|
|
969
|
-
],
|
|
970
|
-
],
|
|
971
|
-
]);
|
|
972
|
-
assertParse("objectLiteral", "{ a: 1, ...more, c: a }", [
|
|
973
|
-
[
|
|
959
|
+
]);
|
|
960
|
+
assertParse("objectLiteral", "{ x = fn.js('a') }", [
|
|
974
961
|
ops.object,
|
|
975
|
-
["a", [ops.literal, 1]],
|
|
976
|
-
["c", [markers.traverse, [markers.reference, "a"]]],
|
|
977
962
|
[
|
|
978
|
-
"
|
|
963
|
+
"x",
|
|
979
964
|
[
|
|
980
|
-
ops.
|
|
981
|
-
[
|
|
982
|
-
|
|
983
|
-
|
|
965
|
+
ops.getter,
|
|
966
|
+
[
|
|
967
|
+
[markers.traverse, [markers.reference, "fn.js"]],
|
|
968
|
+
[ops.literal, "a"],
|
|
969
|
+
],
|
|
984
970
|
],
|
|
985
971
|
],
|
|
986
|
-
]
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
]);
|
|
994
|
-
assertParse("objectLiteral", "{ (a): 1 }", [
|
|
995
|
-
ops.object,
|
|
996
|
-
["(a)", [ops.literal, 1]],
|
|
997
|
-
]);
|
|
998
|
-
assertParse(
|
|
999
|
-
"objectLiteral",
|
|
1000
|
-
"{ <path/to/file.txt> }",
|
|
1001
|
-
[
|
|
972
|
+
]);
|
|
973
|
+
|
|
974
|
+
assertParse("objectLiteral", "{ (a): 1 }", [
|
|
975
|
+
ops.object,
|
|
976
|
+
["(a)", [ops.literal, 1]],
|
|
977
|
+
]);
|
|
978
|
+
assertParse("objectLiteral", "{ <path/to/file.txt> }", [
|
|
1002
979
|
ops.object,
|
|
1003
980
|
[
|
|
1004
981
|
"file.txt",
|
|
@@ -1009,9 +986,37 @@ Body`,
|
|
|
1009
986
|
[ops.literal, "file.txt"],
|
|
1010
987
|
],
|
|
1011
988
|
],
|
|
1012
|
-
]
|
|
1013
|
-
|
|
1014
|
-
|
|
989
|
+
]);
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
describe("spreads", () => {
|
|
993
|
+
assertParse("objectLiteral", "{ ...x }", [
|
|
994
|
+
ops.unpack,
|
|
995
|
+
[markers.traverse, [markers.reference, "x"]],
|
|
996
|
+
]);
|
|
997
|
+
assertParse("objectLiteral", "{ a: 1, ...more, c: a }", [
|
|
998
|
+
[
|
|
999
|
+
ops.object,
|
|
1000
|
+
["a", [ops.literal, 1]],
|
|
1001
|
+
["c", [markers.traverse, [markers.reference, "a"]]],
|
|
1002
|
+
[
|
|
1003
|
+
"_result",
|
|
1004
|
+
[
|
|
1005
|
+
ops.merge,
|
|
1006
|
+
[ops.object, ["a", [ops.getter, [[ops.context, 1], "a"]]]],
|
|
1007
|
+
[markers.traverse, [markers.reference, "more"]],
|
|
1008
|
+
[ops.object, ["c", [ops.getter, [[ops.context, 1], "c"]]]],
|
|
1009
|
+
],
|
|
1010
|
+
],
|
|
1011
|
+
],
|
|
1012
|
+
"_result",
|
|
1013
|
+
]);
|
|
1014
|
+
assertParse("objectLiteral", "{ a: 1, ...{ b: 2 } }", [
|
|
1015
|
+
ops.object,
|
|
1016
|
+
["a", [ops.literal, 1]],
|
|
1017
|
+
["b", [ops.literal, 2]],
|
|
1018
|
+
]);
|
|
1019
|
+
});
|
|
1015
1020
|
});
|
|
1016
1021
|
|
|
1017
1022
|
test("objectEntry", () => {
|
|
@@ -1027,20 +1032,15 @@ Body`,
|
|
|
1027
1032
|
"a",
|
|
1028
1033
|
[markers.traverse, [markers.reference, "a"]],
|
|
1029
1034
|
]);
|
|
1030
|
-
assertParse(
|
|
1031
|
-
"
|
|
1032
|
-
"<path/to/file.txt>",
|
|
1035
|
+
assertParse("objectEntry", "<path/to/file.txt>", [
|
|
1036
|
+
"file.txt",
|
|
1033
1037
|
[
|
|
1034
|
-
|
|
1035
|
-
[
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
[ops.literal, "to/"],
|
|
1039
|
-
[ops.literal, "file.txt"],
|
|
1040
|
-
],
|
|
1038
|
+
markers.traverse,
|
|
1039
|
+
[markers.external, "path/"],
|
|
1040
|
+
[ops.literal, "to/"],
|
|
1041
|
+
[ops.literal, "file.txt"],
|
|
1041
1042
|
],
|
|
1042
|
-
|
|
1043
|
-
);
|
|
1043
|
+
]);
|
|
1044
1044
|
assertParse("objectEntry", "a: (a) => a", [
|
|
1045
1045
|
"a",
|
|
1046
1046
|
[
|
|
@@ -1252,7 +1252,7 @@ Body`,
|
|
|
1252
1252
|
'Hello'
|
|
1253
1253
|
`,
|
|
1254
1254
|
[ops.literal, "Hello"],
|
|
1255
|
-
"
|
|
1255
|
+
"program",
|
|
1256
1256
|
false
|
|
1257
1257
|
);
|
|
1258
1258
|
});
|
|
@@ -1312,7 +1312,7 @@ Body`,
|
|
|
1312
1312
|
ops.lambda,
|
|
1313
1313
|
[[ops.literal, "_"]],
|
|
1314
1314
|
[
|
|
1315
|
-
ops.
|
|
1315
|
+
ops.templateText,
|
|
1316
1316
|
[ops.literal, ["Hello, ", "."]],
|
|
1317
1317
|
[markers.traverse, [markers.reference, "name"]],
|
|
1318
1318
|
],
|
|
@@ -1328,7 +1328,13 @@ Body`,
|
|
|
1328
1328
|
});
|
|
1329
1329
|
|
|
1330
1330
|
test("singleLineComment", () => {
|
|
1331
|
-
assertParse(
|
|
1331
|
+
assertParse(
|
|
1332
|
+
"singleLineComment",
|
|
1333
|
+
"// Hello, world!",
|
|
1334
|
+
null,
|
|
1335
|
+
"program",
|
|
1336
|
+
false
|
|
1337
|
+
);
|
|
1332
1338
|
});
|
|
1333
1339
|
|
|
1334
1340
|
test("spreadElement", () => {
|
|
@@ -1336,10 +1342,6 @@ Body`,
|
|
|
1336
1342
|
ops.spread,
|
|
1337
1343
|
[markers.traverse, [markers.reference, "a"]],
|
|
1338
1344
|
]);
|
|
1339
|
-
assertParse("spreadElement", "…a", [
|
|
1340
|
-
ops.spread,
|
|
1341
|
-
[markers.traverse, [markers.reference, "a"]],
|
|
1342
|
-
]);
|
|
1343
1345
|
});
|
|
1344
1346
|
|
|
1345
1347
|
test("stringLiteral", () => {
|
|
@@ -1386,7 +1388,6 @@ Body text`,
|
|
|
1386
1388
|
[
|
|
1387
1389
|
ops.object,
|
|
1388
1390
|
["title", [ops.literal, "Title goes here"]],
|
|
1389
|
-
["(@text)", [ops.templateIndent, [ops.literal, ["Body text"]]]],
|
|
1390
1391
|
["_body", [ops.templateIndent, [ops.literal, ["Body text"]]]],
|
|
1391
1392
|
]
|
|
1392
1393
|
);
|
|
@@ -1398,7 +1399,7 @@ Body text`,
|
|
|
1398
1399
|
`---
|
|
1399
1400
|
{
|
|
1400
1401
|
title: "Title"
|
|
1401
|
-
|
|
1402
|
+
_body: _template()
|
|
1402
1403
|
}
|
|
1403
1404
|
---
|
|
1404
1405
|
<h1>\${ title }</h1>
|
|
@@ -1407,7 +1408,7 @@ Body text`,
|
|
|
1407
1408
|
ops.object,
|
|
1408
1409
|
["title", [ops.literal, "Title"]],
|
|
1409
1410
|
[
|
|
1410
|
-
"
|
|
1411
|
+
"_body",
|
|
1411
1412
|
[
|
|
1412
1413
|
ops.templateIndent,
|
|
1413
1414
|
[ops.literal, ["<h1>", "</h1>\n"]],
|
|
@@ -1441,34 +1442,31 @@ Body text`,
|
|
|
1441
1442
|
[markers.traverse, [markers.reference, "title"]],
|
|
1442
1443
|
],
|
|
1443
1444
|
],
|
|
1444
|
-
]
|
|
1445
|
-
"jse"
|
|
1445
|
+
]
|
|
1446
1446
|
);
|
|
1447
1447
|
});
|
|
1448
1448
|
|
|
1449
1449
|
test("templateLiteral", () => {
|
|
1450
1450
|
assertParse("templateLiteral", "`Hello, world.`", [
|
|
1451
|
-
ops.
|
|
1451
|
+
ops.templateText,
|
|
1452
|
+
[ops.literal, ["Hello, world."]],
|
|
1453
|
+
]);
|
|
1454
|
+
assertParse("templateLiteral", "`Hello, world.`", [
|
|
1455
|
+
ops.templateText,
|
|
1452
1456
|
[ops.literal, ["Hello, world."]],
|
|
1453
1457
|
]);
|
|
1454
|
-
assertParse(
|
|
1455
|
-
"templateLiteral",
|
|
1456
|
-
"`Hello, world.`",
|
|
1457
|
-
[ops.templateTree, [ops.literal, ["Hello, world."]]],
|
|
1458
|
-
"jse"
|
|
1459
|
-
);
|
|
1460
1458
|
assertParse("templateLiteral", "`foo ${x} bar`", [
|
|
1461
|
-
ops.
|
|
1459
|
+
ops.templateText,
|
|
1462
1460
|
[ops.literal, ["foo ", " bar"]],
|
|
1463
1461
|
[markers.traverse, [markers.reference, "x"]],
|
|
1464
1462
|
]);
|
|
1465
1463
|
assertParse("templateLiteral", "`${`nested`}`", [
|
|
1466
|
-
ops.
|
|
1464
|
+
ops.templateText,
|
|
1467
1465
|
[ops.literal, ["", ""]],
|
|
1468
|
-
[ops.
|
|
1466
|
+
[ops.templateText, [ops.literal, ["nested"]]],
|
|
1469
1467
|
]);
|
|
1470
1468
|
assertParse("templateLiteral", "`${ map(people, =`${name}`) }`", [
|
|
1471
|
-
ops.
|
|
1469
|
+
ops.templateText,
|
|
1472
1470
|
[ops.literal, ["", ""]],
|
|
1473
1471
|
[
|
|
1474
1472
|
[markers.traverse, [markers.reference, "map"]],
|
|
@@ -1477,7 +1475,7 @@ Body text`,
|
|
|
1477
1475
|
ops.lambda,
|
|
1478
1476
|
[[ops.literal, "_"]],
|
|
1479
1477
|
[
|
|
1480
|
-
ops.
|
|
1478
|
+
ops.templateText,
|
|
1481
1479
|
[ops.literal, ["", ""]],
|
|
1482
1480
|
[markers.traverse, [markers.reference, "name"]],
|
|
1483
1481
|
],
|
|
@@ -1562,7 +1560,7 @@ Body text`,
|
|
|
1562
1560
|
// Second comment
|
|
1563
1561
|
`,
|
|
1564
1562
|
null,
|
|
1565
|
-
"
|
|
1563
|
+
"program",
|
|
1566
1564
|
false
|
|
1567
1565
|
);
|
|
1568
1566
|
});
|
|
@@ -1596,7 +1594,7 @@ function assertParse(
|
|
|
1596
1594
|
assertCodeEqual(code, expected);
|
|
1597
1595
|
}
|
|
1598
1596
|
|
|
1599
|
-
function assertThrows(startRule, source, message, position, mode = "
|
|
1597
|
+
function assertThrows(startRule, source, message, position, mode = "program") {
|
|
1600
1598
|
// @ts-ignore We declare this so we can inspect it in debugger
|
|
1601
1599
|
let code;
|
|
1602
1600
|
try {
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -246,7 +246,7 @@ describe("ops", () => {
|
|
|
246
246
|
test("ops.merge", async () => {
|
|
247
247
|
// {
|
|
248
248
|
// a: 1
|
|
249
|
-
//
|
|
249
|
+
// ...more
|
|
250
250
|
// c: a
|
|
251
251
|
// }
|
|
252
252
|
const scope = new ObjectTree({
|
|
@@ -340,6 +340,13 @@ describe("ops", () => {
|
|
|
340
340
|
assert.deepEqual(result, ["Hello", 1, "WORLD"]);
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
+
test("ops.property returns a property if defined, otherwise traverses", async () => {
|
|
344
|
+
assert.equal(await ops.property({ a: 1 }, "a"), 1);
|
|
345
|
+
assert.equal(await ops.property({ a: 1 }, "b"), undefined);
|
|
346
|
+
const uppercase = (text) => text.toUpperCase();
|
|
347
|
+
assert.equal(await ops.property(uppercase, "a"), "A");
|
|
348
|
+
});
|
|
349
|
+
|
|
343
350
|
test("ops.remainder calculates the remainder of two numbers", async () => {
|
|
344
351
|
assert.strictEqual(ops.remainder(13, 5), 3);
|
|
345
352
|
assert.strictEqual(ops.remainder(-13, 5), -3);
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { deepText, toString, Tree } from "@weborigami/async-tree";
|
|
2
|
-
|
|
3
|
-
const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
|
|
4
|
-
|
|
5
|
-
const mapStringsToModifications = new Map();
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Normalize indentation in a tagged template string
|
|
9
|
-
*
|
|
10
|
-
* @param {TemplateStringsArray} strings
|
|
11
|
-
* @param {...any} values
|
|
12
|
-
* @returns {Promise<string>}
|
|
13
|
-
*/
|
|
14
|
-
export default async function indent(strings, ...values) {
|
|
15
|
-
let modified = mapStringsToModifications.get(strings);
|
|
16
|
-
if (!modified) {
|
|
17
|
-
modified = modifyStrings(strings);
|
|
18
|
-
mapStringsToModifications.set(strings, modified);
|
|
19
|
-
}
|
|
20
|
-
const { blockIndentations, strings: modifiedStrings } = modified;
|
|
21
|
-
const valueTexts = await Promise.all(
|
|
22
|
-
values.map((value) => (Tree.isTreelike(value) ? deepText(value) : value))
|
|
23
|
-
);
|
|
24
|
-
return joinBlocks(modifiedStrings, valueTexts, blockIndentations);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Join strings and values, applying the given block indentation to the lines of
|
|
28
|
-
// values for block placholders.
|
|
29
|
-
function joinBlocks(strings, values, blockIndentations) {
|
|
30
|
-
let result = strings[0];
|
|
31
|
-
for (let i = 0; i < values.length; i++) {
|
|
32
|
-
let text = toString(values[i]);
|
|
33
|
-
if (text) {
|
|
34
|
-
const blockIndentation = blockIndentations[i];
|
|
35
|
-
if (blockIndentation) {
|
|
36
|
-
const lines = text.split("\n");
|
|
37
|
-
text = "";
|
|
38
|
-
if (lines.at(-1) === "") {
|
|
39
|
-
// Drop empty last line
|
|
40
|
-
lines.pop();
|
|
41
|
-
}
|
|
42
|
-
for (let line of lines) {
|
|
43
|
-
text += blockIndentation + line + "\n";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
result += text;
|
|
47
|
-
}
|
|
48
|
-
result += strings[i + 1];
|
|
49
|
-
}
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Given an array of template boilerplate strings, return an object { modified,
|
|
54
|
-
// blockIndentations } where `strings` is the array of strings with indentation
|
|
55
|
-
// removed, and `blockIndentations` is an array of indentation strings for each
|
|
56
|
-
// block placeholder.
|
|
57
|
-
function modifyStrings(strings) {
|
|
58
|
-
// Phase one: Identify the indentation based on the first real line of the
|
|
59
|
-
// first string (skipping the initial newline), and remove this indentation
|
|
60
|
-
// from all lines of all strings.
|
|
61
|
-
let indent;
|
|
62
|
-
if (strings.length > 0 && strings[0].startsWith("\n")) {
|
|
63
|
-
// Look for indenttation
|
|
64
|
-
const firstLineWhitespaceRegex = /^\n(?<indent>[ \t]*)/;
|
|
65
|
-
const match = strings[0].match(firstLineWhitespaceRegex);
|
|
66
|
-
indent = match?.groups.indent;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Determine the modified strings. If this invoked as a JS tagged template
|
|
70
|
-
// literal, the `strings` argument will be an odd array-ish object that we'll
|
|
71
|
-
// want to convert to a real array.
|
|
72
|
-
let modified;
|
|
73
|
-
if (indent) {
|
|
74
|
-
// De-indent the strings.
|
|
75
|
-
const indentationRegex = new RegExp(`\n${indent}`, "g");
|
|
76
|
-
// The `replaceAll` also converts strings to a real array.
|
|
77
|
-
modified = strings.map((string) =>
|
|
78
|
-
string.replaceAll(indentationRegex, "\n")
|
|
79
|
-
);
|
|
80
|
-
// Remove indentation from last line of last string
|
|
81
|
-
modified[modified.length - 1] = modified
|
|
82
|
-
.at(-1)
|
|
83
|
-
.replace(lastLineWhitespaceRegex, "\n");
|
|
84
|
-
} else {
|
|
85
|
-
// No indentation; just copy the strings so we have a real array
|
|
86
|
-
modified = strings.slice();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Phase two: Identify any block placholders, identify and remove their
|
|
90
|
-
// preceding indentation, and remove the following newline. Work backward from
|
|
91
|
-
// the end towards the start because we're modifying the strings in place and
|
|
92
|
-
// our pattern matching won't work going forward from start to end.
|
|
93
|
-
let blockIndentations = [];
|
|
94
|
-
for (let i = modified.length - 2; i >= 0; i--) {
|
|
95
|
-
// Get the modified before and after substitution with index `i`
|
|
96
|
-
const beforeString = modified[i];
|
|
97
|
-
const afterString = modified[i + 1];
|
|
98
|
-
const match = beforeString.match(lastLineWhitespaceRegex);
|
|
99
|
-
if (match && afterString.startsWith("\n")) {
|
|
100
|
-
// The substitution between these strings is a block substitution
|
|
101
|
-
let blockIndentation = match.groups.indent;
|
|
102
|
-
blockIndentations[i] = blockIndentation;
|
|
103
|
-
// Trim the before and after strings
|
|
104
|
-
if (blockIndentation) {
|
|
105
|
-
modified[i] = beforeString.slice(0, -blockIndentation.length);
|
|
106
|
-
}
|
|
107
|
-
modified[i + 1] = afterString.slice(1);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Remove newline from start of first string *after* removing indentation.
|
|
112
|
-
if (modified[0].startsWith("\n")) {
|
|
113
|
-
modified[0] = modified[0].slice(1);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
blockIndentations,
|
|
118
|
-
strings: modified,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import indent from "../../src/runtime/templateIndent.js";
|
|
4
|
-
|
|
5
|
-
describe("taggedTemplateIndent", () => {
|
|
6
|
-
test("joins strings and values together if template isn't a block template", async () => {
|
|
7
|
-
const result = await indent`a ${"b"} c`;
|
|
8
|
-
assert.equal(result, "a b c");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("removes first and last lines if template is a block template", async () => {
|
|
12
|
-
const actual = await indent`
|
|
13
|
-
<p>
|
|
14
|
-
Hello, ${"Alice"}!
|
|
15
|
-
</p>
|
|
16
|
-
`;
|
|
17
|
-
const expected = `
|
|
18
|
-
<p>
|
|
19
|
-
Hello, Alice!
|
|
20
|
-
</p>
|
|
21
|
-
`.trimStart();
|
|
22
|
-
assert.equal(actual, expected);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("indents all lines in a block substitution", async () => {
|
|
26
|
-
const lines = `
|
|
27
|
-
Line 1
|
|
28
|
-
Line 2
|
|
29
|
-
Line 3`.trimStart();
|
|
30
|
-
const actual = await indent`
|
|
31
|
-
<main>
|
|
32
|
-
${lines}
|
|
33
|
-
</main>
|
|
34
|
-
`;
|
|
35
|
-
const expected = `
|
|
36
|
-
<main>
|
|
37
|
-
Line 1
|
|
38
|
-
Line 2
|
|
39
|
-
Line 3
|
|
40
|
-
</main>
|
|
41
|
-
`.trimStart();
|
|
42
|
-
assert.equal(actual, expected);
|
|
43
|
-
});
|
|
44
|
-
});
|