@@ -719,6 +719,16 @@ func TestExprSemanticsCheckOK(t *testing.T) {
719
719
input : "!!('foo' || 10) && 20" ,
720
720
expected : NumberType {},
721
721
},
722
+ {
723
+ what : "escaped braces in format string" ,
724
+ input : "format('hello {{1}} {0}', 42)" ,
725
+ expected : StringType {},
726
+ },
727
+ {
728
+ what : "format specifier is escaped" ,
729
+ input : "format('hello {{{0}', 'world')" , // First {{ is escaped. {0} is not escaped
730
+ expected : StringType {},
731
+ },
722
732
}
723
733
724
734
allSPFuncs := []string {}
@@ -1023,6 +1033,20 @@ func TestExprSemanticsCheckError(t *testing.T) {
1023
1033
`format string "{0}" does not contain placeholder {1}` ,
1024
1034
},
1025
1035
},
1036
+ {
1037
+ what : "format specifier is escaped" ,
1038
+ input : "format('hello {{0}}', 'world')" ,
1039
+ expected : []string {
1040
+ "does not contain placeholder {0}" ,
1041
+ },
1042
+ },
1043
+ {
1044
+ what : "format specifier is still escaped" ,
1045
+ input : "format('hello {{{{0}}', 'world')" , // First {{ is escaped. {{0}} is still escaped
1046
+ expected : []string {
1047
+ "does not contain placeholder {0}" ,
1048
+ },
1049
+ },
1026
1050
{
1027
1051
what : "undefined matrix value" ,
1028
1052
input : "matrix.bar" ,
@@ -1660,3 +1684,95 @@ func TestBuiltinGlobalVariableTypesValidation(t *testing.T) {
1660
1684
testObjectPropertiesAreInLowerCase (t , ty )
1661
1685
}
1662
1686
}
1687
+
1688
+ func TestParseFormatSpecifiers (t * testing.T ) {
1689
+ tests := []struct {
1690
+ what string
1691
+ in string
1692
+ want []int // Specifiers in the `in` string
1693
+ }{
1694
+ {
1695
+ what : "empty input" ,
1696
+ in : "" ,
1697
+ },
1698
+ {
1699
+ what : "no specifier" ,
1700
+ in : "hello, world!" ,
1701
+ },
1702
+ {
1703
+ what : "single specifier" ,
1704
+ in : "Hello{0}specifier" ,
1705
+ want : []int {0 },
1706
+ },
1707
+ {
1708
+ what : "mutliple specifiers" ,
1709
+ in : "{0} {1}{2}x{3}}{4}!" ,
1710
+ want : []int {0 , 1 , 2 , 3 , 4 },
1711
+ },
1712
+ {
1713
+ what : "many specifiers" ,
1714
+ in : "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}!" ,
1715
+ want : []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 },
1716
+ },
1717
+ {
1718
+ what : "unordered" ,
1719
+ in : "{2}foo{4} {0}{3}" ,
1720
+ want : []int {2 , 4 , 0 , 3 },
1721
+ },
1722
+ {
1723
+ what : "uncontiguous" ,
1724
+ in : "{0} {2}foo{5} {1}" ,
1725
+ want : []int {0 , 2 , 5 , 1 },
1726
+ },
1727
+ {
1728
+ what : "unclosed" ,
1729
+ in : "{12foo" ,
1730
+ },
1731
+ {
1732
+ what : "not digit" ,
1733
+ in : "{hello}" ,
1734
+ },
1735
+ {
1736
+ what : "space in digits" ,
1737
+ in : "{1 2}" ,
1738
+ },
1739
+ {
1740
+ what : "empty" ,
1741
+ in : "{}" ,
1742
+ },
1743
+ {
1744
+ what : "specifier inside specifier" ,
1745
+ in : "{1{0}2}" ,
1746
+ want : []int {0 },
1747
+ },
1748
+ {
1749
+ what : "escaped" ,
1750
+ in : "{{hello{{0}{{{{1}world}}" ,
1751
+ },
1752
+ {
1753
+ what : "after escaped" ,
1754
+ in : "{{{{{0}" ,
1755
+ want : []int {0 },
1756
+ },
1757
+ {
1758
+ what : "kuma-" ,
1759
+ in : "{・{ᴥ}・}" ,
1760
+ },
1761
+ }
1762
+
1763
+ for _ , tc := range tests {
1764
+ t .Run (tc .what , func (t * testing.T ) {
1765
+ want := map [int ]struct {}{}
1766
+ for _ , i := range tc .want {
1767
+ want [i ] = struct {}{}
1768
+ }
1769
+ have := parseFormatFuncSpecifiers (tc .in , len (tc .want ))
1770
+
1771
+ if ! cmp .Equal (want , have ) {
1772
+ t .Fatal (cmp .Diff (want , have ))
1773
+ }
1774
+ })
1775
+ }
1776
+ }
1777
+
1778
+ // vim: nofoldenable
0 commit comments