mirror of
https://github.com/MontFerret/ferret.git
synced 2025-08-13 19:52:52 +02:00
Enhance diagnostics for unclosed string literals: improve error messages and hints for missing opening and closing quotes, and add comprehensive test cases for better clarity and coverage.
This commit is contained in:
@@ -106,6 +106,22 @@ func isQuote(input string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidString(input string) bool {
|
||||
if input == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if isQuote(input) {
|
||||
return true
|
||||
}
|
||||
|
||||
if isQuote(input[0:1]) && isQuote(input[len(input)-1:]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func is(node *TokenNode, expected string) bool {
|
||||
if node == nil {
|
||||
return false
|
||||
@@ -182,3 +198,14 @@ func extractExtraneousInput(msg string) string {
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func extractMismatchedInput(msg string) string {
|
||||
re := regexp.MustCompile(`mismatched input\s+(?P<input>.+?)\s+expecting`)
|
||||
match := re.FindStringSubmatch(msg)
|
||||
|
||||
input := match[re.SubexpIndex("input")]
|
||||
input = strings.TrimPrefix(input, "'")
|
||||
input = strings.TrimSuffix(input, "'")
|
||||
|
||||
return input
|
||||
}
|
||||
|
@@ -42,23 +42,41 @@ func matchCommonErrors(src *file.Source, err *CompilationError, offending *Token
|
||||
input := extractNoAlternativeInputs(err.Message)
|
||||
token := input[len(input)-1]
|
||||
|
||||
if isQuote(token) {
|
||||
span := spanFromTokenSafe(offending.Token(), src)
|
||||
inputRaw := extractNoAlternativeInput(err.Message)
|
||||
spaces := strings.Count(inputRaw, " ") + 1
|
||||
span.Start += spaces
|
||||
span.End += spaces
|
||||
isMissingClosingQuote := isQuote(token)
|
||||
isMissingOpeningQuote := isKeyword(offending.Prev()) && isQuote(token[len(token)-1:]) && !isValidString(token)
|
||||
|
||||
if isMissingClosingQuote || isMissingOpeningQuote {
|
||||
var span file.Span
|
||||
var typeOfQuote string
|
||||
var quote string
|
||||
|
||||
if isMissingClosingQuote {
|
||||
quote = token
|
||||
typeOfQuote = "closing"
|
||||
span = spanFromTokenSafe(offending.Token(), src)
|
||||
inputRaw := extractNoAlternativeInput(err.Message)
|
||||
spaces := strings.Count(inputRaw, " ") + 1
|
||||
span.Start += spaces
|
||||
span.End += spaces
|
||||
} else {
|
||||
quote = token[len(token)-1:]
|
||||
typeOfQuote = "opening"
|
||||
span = spanFromTokenSafe(offending.Prev().Token(), src)
|
||||
span.Start += 2
|
||||
span.End += 2
|
||||
}
|
||||
|
||||
err.Message = "Unclosed string literal"
|
||||
|
||||
if token == "'" {
|
||||
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", token)
|
||||
if quote == "'" {
|
||||
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", quote)
|
||||
err.Spans = []ErrorSpan{
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing closing \"%s\"", token)),
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing %s \"%s\"", typeOfQuote, quote)),
|
||||
}
|
||||
} else {
|
||||
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", token)
|
||||
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", quote)
|
||||
err.Spans = []ErrorSpan{
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing closing '%s'", token)),
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing %s '%s'", typeOfQuote, quote)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,25 +240,42 @@ func matchCommonErrors(src *file.Source, err *CompilationError, offending *Token
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
token := extractExtraneousInput(err.Message)
|
||||
if isExtraneous(err.Message) || isMismatched(err.Message) {
|
||||
var token string
|
||||
|
||||
if isExtraneous(err.Message) {
|
||||
token = extractExtraneousInput(err.Message)
|
||||
} else {
|
||||
token = extractMismatchedInput(err.Message)
|
||||
}
|
||||
|
||||
if isQuote(token) {
|
||||
span := spanFromTokenSafe(offending.Token(), src)
|
||||
var span file.Span
|
||||
var typeOfQuote string
|
||||
|
||||
if isKeyword(offending) {
|
||||
span = spanFromTokenSafe(offending.Token(), src)
|
||||
typeOfQuote = "closing"
|
||||
} else {
|
||||
span = spanFromTokenSafe(offending.Prev().Token(), src)
|
||||
typeOfQuote = "opening"
|
||||
}
|
||||
|
||||
span.Start += 2
|
||||
span.End += 2
|
||||
|
||||
err.Message = "Unclosed string literal"
|
||||
|
||||
if token == "'" {
|
||||
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", token)
|
||||
err.Spans = []ErrorSpan{
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing closing \"%s\"", token)),
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing %s \"%s\"", typeOfQuote, token)),
|
||||
}
|
||||
} else {
|
||||
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", token)
|
||||
err.Spans = []ErrorSpan{
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing closing '%s'", token)),
|
||||
NewMainErrorSpan(span, fmt.Sprintf("missing %s '%s'", typeOfQuote, token)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,37 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete string"),
|
||||
}, "Incomplete string (closing quote missing)"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = "foo bar
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete multi-string (closing quote missing)"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = foo"
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete string (opening quote missing)"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = foo bar"
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete multi-string (opening quote missing)"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
@@ -55,7 +85,37 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete string 2"),
|
||||
}, "Incomplete string (closing quote missing) 2"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = 'foo bar
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete multi-string (closing quote missing) 2"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = foo'
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete string (opening quote missing) 2"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = foo bar'
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete multi-string (opening quote missing) 2"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = `foo "+
|
||||
@@ -63,7 +123,31 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '`' to close the string.",
|
||||
}, "Incomplete string 3"),
|
||||
}, "Incomplete string (closing quote missing) 3"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = `foo bar"+
|
||||
"RETURN i", E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '`' to close the string.",
|
||||
}, "Incomplete multi-string (closing quote missing) 3"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = foo` "+
|
||||
"RETURN i", E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '`' to close the string.",
|
||||
}, "Incomplete string (opening quote missing) 3"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = foo bar` "+
|
||||
"RETURN i", E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '`' to close the string.",
|
||||
}, "Incomplete multi-string (opening quote missing) 3"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
@@ -73,7 +157,37 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete string 4"),
|
||||
}, "Incomplete string (closing quote missing) 4"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = { "foo bar: }
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete multi-string (closing quote missing) 4"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = { foo": }
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string.",
|
||||
}, "Incomplete string (opening quote missing) 4"),
|
||||
|
||||
SkipErrorCase(
|
||||
`
|
||||
LET i = { foo bar": }
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching '\"' to close the string",
|
||||
}, "Incomplete multi-string (opening quote missing) 4"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
@@ -83,7 +197,27 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete string 5"),
|
||||
}, "Incomplete string (closing quote missing) 5"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
LET i = { foo': }
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete string (opening quote missing) 5"),
|
||||
|
||||
SkipErrorCase(
|
||||
`
|
||||
LET i = { foo bar': }
|
||||
RETURN i
|
||||
`, E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete multi-string (opening quote missing) 5"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = { 'foo: }"+
|
||||
@@ -91,7 +225,15 @@ func TestSyntaxErrors(t *testing.T) {
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete string 6"),
|
||||
}, "Incomplete string (closing quote missing) 6"),
|
||||
|
||||
ErrorCase(
|
||||
"LET i = { 'foo bar: }"+
|
||||
"RETURN i", E{
|
||||
Kind: compiler.SyntaxError,
|
||||
Message: "Unclosed string literal",
|
||||
Hint: "Add a matching \"'\" to close the string.",
|
||||
}, "Incomplete multi-string (closing quote missing) 6"),
|
||||
|
||||
ErrorCase(
|
||||
`
|
||||
|
Reference in New Issue
Block a user