checker: add suggestions for misspelled `mod.func_name()` calls

pull/13927/head
Delyan Angelov 2022-04-03 18:23:35 +03:00
parent 51c1d666c2
commit 44603f8e59
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
5 changed files with 51 additions and 4 deletions

View File

@ -662,6 +662,20 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool)
} }
if !found { if !found {
continue_check = false continue_check = false
if dot_index := fn_name.index('.') {
if !fn_name[0].is_capital() {
mod_name := fn_name#[..dot_index]
mut mod_func_names := []string{}
for ctfnk, ctfnv in c.table.fns {
if ctfnv.is_pub && ctfnk.starts_with(mod_name) {
mod_func_names << ctfnk
}
}
suggestion := util.new_suggestion(fn_name, mod_func_names)
c.error(suggestion.say('unknown function: $fn_name '), node.pos)
return ast.void_type
}
}
c.error('unknown function: $fn_name', node.pos) c.error('unknown function: $fn_name', node.pos)
return ast.void_type return ast.void_type
} }

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.vv:3:9: error: unknown function: os.read_fil .
Did you mean `os.read_file`?
1 | import os
2 |
3 | dump(os.read_fil('abc'))
| ~~~~~~~~~~~~~~~

View File

@ -0,0 +1,3 @@
import os
dump(os.read_fil('abc'))

View File

@ -1,4 +1,5 @@
vlib/v/checker/tests/unknown_function.vv:4:15: error: unknown function: math.max_i64 vlib/v/checker/tests/unknown_function.vv:4:15: error: unknown function: math.max_i64 .
Did you mean `math.max`?
2 | 2 |
3 | fn main() { 3 | fn main() {
4 | println(math.max_i64()) 4 | println(math.max_i64())

View File

@ -1,14 +1,20 @@
module util module util
import term
import strings import strings
// Possibility is a simple pair of a string, with a similarity coefficient
// determined by the editing distance to a wanted value.
struct Possibility { struct Possibility {
value string value string
svalue string svalue string
mut: mut:
similarity f32 similarity f32 // Note: 0.0 for *equal* strings.
} }
// Suggestion is set of known possibilities and a wanted string.
// It has helper methods for making educated guesses based on the possibilities,
// on which of them match best the wanted string.
struct Suggestion { struct Suggestion {
mut: mut:
known []Possibility known []Possibility
@ -16,6 +22,7 @@ mut:
swanted string swanted string
} }
// new_suggestion creates a new Suggestion, given a wanted value and a list of possibilities.
pub fn new_suggestion(wanted string, possibilities []string) Suggestion { pub fn new_suggestion(wanted string, possibilities []string) Suggestion {
mut s := Suggestion{ mut s := Suggestion{
wanted: wanted wanted: wanted
@ -26,6 +33,8 @@ pub fn new_suggestion(wanted string, possibilities []string) Suggestion {
return s return s
} }
// add adds the `val` to the list of known possibilities of the suggestion.
// It calculates the similarity metric towards the wanted value.
pub fn (mut s Suggestion) add(val string) { pub fn (mut s Suggestion) add(val string) {
if val in [s.wanted, s.swanted] { if val in [s.wanted, s.swanted] {
return return
@ -43,16 +52,21 @@ pub fn (mut s Suggestion) add(val string) {
} }
} }
// add adds all of the `many` to the list of known possibilities of the suggestion
pub fn (mut s Suggestion) add_many(many []string) { pub fn (mut s Suggestion) add_many(many []string) {
for x in many { for x in many {
s.add(x) s.add(x)
} }
} }
// sort sorts the list of known possibilities, based on their similarity metric.
// Equal strings will be first, followed by less similar ones, very distinct ones will be last.
pub fn (mut s Suggestion) sort() { pub fn (mut s Suggestion) sort() {
s.known.sort(a.similarity < b.similarity) s.known.sort(a.similarity < b.similarity)
} }
// say produces a final suggestion message, based on the preset `wanted` and
// `possibilities` fields, accumulated in the Suggestion.
pub fn (s Suggestion) say(msg string) string { pub fn (s Suggestion) say(msg string) string {
mut res := msg mut res := msg
mut found := false mut found := false
@ -61,14 +75,14 @@ pub fn (s Suggestion) say(msg string) string {
if top_posibility.similarity > 0.5 { if top_posibility.similarity > 0.5 {
val := top_posibility.value val := top_posibility.value
if !val.starts_with('[]') { if !val.starts_with('[]') {
res += '.\nDid you mean `$val`?' res += '.\nDid you mean `${highlight_suggestion(val)}`?'
found = true found = true
} }
} }
} }
if !found { if !found {
if s.known.len > 0 { if s.known.len > 0 {
mut values := s.known.map('`$it.svalue`') mut values := s.known.map('`${highlight_suggestion(it.svalue)}`')
values.sort() values.sort()
if values.len == 1 { if values.len == 1 {
res += '.\n1 possibility: ${values[0]}.' res += '.\n1 possibility: ${values[0]}.'
@ -81,6 +95,8 @@ pub fn (s Suggestion) say(msg string) string {
return res return res
} }
// short_module_name returns a shortened version of the fully qualified `name`,
// i.e. `xyz.def.abc.symname` -> `abc.symname`
pub fn short_module_name(name string) string { pub fn short_module_name(name string) string {
if !name.contains('.') { if !name.contains('.') {
return name return name
@ -93,3 +109,10 @@ pub fn short_module_name(name string) string {
symname := vals[vals.len - 1] symname := vals[vals.len - 1]
return '${mname}.$symname' return '${mname}.$symname'
} }
// highlight_suggestion returns a colorfull/highlighted version of `message`,
// but only if the standart error output allows for color messages, otherwise
// the plain message will be returned.
pub fn highlight_suggestion(message string) string {
return term.ecolorize(term.bright_blue, message)
}