json: parsing of keywords
This commit is contained in:
parent
f754328a91
commit
f6bd122f4b
20
zcl/json/didyoumean.go
Normal file
20
zcl/json/didyoumean.go
Normal file
@ -0,0 +1,20 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/agext/levenshtein"
|
||||
)
|
||||
|
||||
var keywords = []string{"false", "true", "null"}
|
||||
|
||||
// keywordSuggestion tries to find a valid JSON keyword that is close to the
|
||||
// given string and returns it if found. If no keyword is close enough, returns
|
||||
// the empty string.
|
||||
func keywordSuggestion(given string) string {
|
||||
for _, kw := range keywords {
|
||||
dist := levenshtein.Distance(given, kw, nil)
|
||||
if dist < 3 { // threshold determined experimentally
|
||||
return kw
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
49
zcl/json/didyoumean_test.go
Normal file
49
zcl/json/didyoumean_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package json
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestKeywordSuggestion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input, Want string
|
||||
}{
|
||||
{"true", "true"},
|
||||
{"false", "false"},
|
||||
{"null", "null"},
|
||||
{"bananas", ""},
|
||||
{"NaN", ""},
|
||||
{"Inf", ""},
|
||||
{"Infinity", ""},
|
||||
{"void", ""},
|
||||
{"undefined", ""},
|
||||
|
||||
{"ture", "true"},
|
||||
{"tru", "true"},
|
||||
{"tre", "true"},
|
||||
{"treu", "true"},
|
||||
{"rtue", "true"},
|
||||
|
||||
{"flase", "false"},
|
||||
{"fales", "false"},
|
||||
{"flse", "false"},
|
||||
{"fasle", "false"},
|
||||
{"fasel", "false"},
|
||||
{"flue", "false"},
|
||||
|
||||
{"nil", "null"},
|
||||
{"nul", "null"},
|
||||
{"unll", "null"},
|
||||
{"nll", "null"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
got := keywordSuggestion(test.Input)
|
||||
if got != test.Want {
|
||||
t.Errorf(
|
||||
"wrong result\ninput: %q\ngot: %q\nwant: %q",
|
||||
test.Input, got, test.Want,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/apparentlymart/go-zcl/zcl"
|
||||
)
|
||||
|
||||
@ -180,5 +182,46 @@ func parseString(p *peeker) (node, zcl.Diagnostics) {
|
||||
}
|
||||
|
||||
func parseKeyword(p *peeker) (node, zcl.Diagnostics) {
|
||||
return nil, nil
|
||||
tok := p.Read()
|
||||
s := string(tok.Bytes)
|
||||
|
||||
switch s {
|
||||
case "true":
|
||||
return &booleanVal{
|
||||
Value: true,
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "false":
|
||||
return &booleanVal{
|
||||
Value: false,
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "null":
|
||||
return &nullVal{
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "undefined", "NaN", "Infinity":
|
||||
return nil, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid JSON keyword",
|
||||
Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
default:
|
||||
var dym string
|
||||
if suggest := keywordSuggestion(s); suggest != "" {
|
||||
dym = fmt.Sprintf(" Did you mean %q?", suggest)
|
||||
}
|
||||
|
||||
return nil, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid JSON keyword",
|
||||
Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
80
zcl/json/parser_test.go
Normal file
80
zcl/json/parser_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/apparentlymart/go-zcl/zcl"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input string
|
||||
Want node
|
||||
DiagCount int
|
||||
}{
|
||||
{
|
||||
`true`,
|
||||
&booleanVal{
|
||||
Value: true,
|
||||
SrcRange: zcl.Range{
|
||||
Filename: "",
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`false`,
|
||||
&booleanVal{
|
||||
Value: false,
|
||||
SrcRange: zcl.Range{
|
||||
Filename: "",
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`null`,
|
||||
&nullVal{
|
||||
SrcRange: zcl.Range{
|
||||
Filename: "",
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`undefined`,
|
||||
nil,
|
||||
1,
|
||||
},
|
||||
{
|
||||
`flase`,
|
||||
nil,
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
got, diag := parseFileContent([]byte(test.Input), "")
|
||||
|
||||
if len(diag) != test.DiagCount {
|
||||
t.Errorf("got %d diagnostics; want %d\n%s", len(diag), test.DiagCount, spew.Sdump(diag))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.Want) {
|
||||
t.Errorf(
|
||||
"wrong result\ninput: %s\ngot: %#v\nwant: %#v",
|
||||
test.Input, got, test.Want,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user