[PATCH v3] scripts/kernel-doc: Suggest possible names for excess descriptions

Ryszard Knop posted 1 patch 1 week ago
tools/lib/python/kdoc/kdoc_parser.py | 51 ++++++++++++++++++++++++++--
1 file changed, 49 insertions(+), 2 deletions(-)
[PATCH v3] scripts/kernel-doc: Suggest possible names for excess descriptions
Posted by Ryszard Knop 1 week ago
Recent check_sections() change added a warning if a documentation tag
member name does not match the detected struct/union member names. Since
the checker knows all possible names, we can suggest known names, so
that it's more obvious how to deal with the warning.

Signed-off-by: Ryszard Knop <ryszard.knop@intel.com>
---
Changes in v1:
- Added the suggestion hint in the warning, with basic name substring checks
- Link: https://lore.kernel.org/linux-doc/20260714111208.323108-1-ryszard.knop@intel.com/

Changes in v2:
- Strip trailing whitespace from the warning when the generated hint is empty
- Link: https://lore.kernel.org/linux-doc/20260715111726.394565-1-ryszard.knop@intel.com/

v3:
- Use difflib to generate suggestions even if the tag member is mistyped
- Suggest names based on the nested struct members too
---
 tools/lib/python/kdoc/kdoc_parser.py | 51 ++++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 2 deletions(-)

diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py
index 2dedda215c22..884f42584667 100644
--- a/tools/lib/python/kdoc/kdoc_parser.py
+++ b/tools/lib/python/kdoc/kdoc_parser.py
@@ -11,6 +11,7 @@ and extract embedded documentation comments from it.
 
 import sys
 import re
+import difflib
 from pprint import pformat
 
 from kdoc.c_lex import CTokenizer, tokenizer_set_log
@@ -558,6 +559,50 @@ class KernelDoc:
                         self.push_parameter(ln, decl_type, param, dtype,
                                             arg, declaration_name)
 
+    def get_suggestions_hint(self, decl_name, possible_names):
+        # For decl name 'flags' or 'flgas', suggests 'substruct.flags'
+        submember_exact = []
+        submember_substrings = []
+        submember_suggestions = []
+        for possible_name in possible_names:
+            parts = possible_name.strip().split('.')
+            if len(parts) < 2:
+                continue
+
+            final_part = parts[-1]
+            if decl_name == final_part:
+                submember_exact.append(possible_name)
+            elif decl_name in final_part:
+                submember_substrings.append(possible_name)
+            elif difflib.get_close_matches(decl_name, [final_part]):
+                submember_suggestions.append(possible_name)
+
+        # For decl name 'flgas', suggests 'flags'
+        full_suggestions = difflib.get_close_matches(decl_name, possible_names)
+
+        # For decl name 'member', suggests 'longer_member'
+        full_substrings = [name for name in possible_names if decl_name in name]
+
+        ordered_lists = [
+            submember_exact,
+            submember_substrings,
+            submember_suggestions,
+            full_suggestions,
+            full_substrings,
+        ]
+
+        # Deduplicate but maintain order from most to least likely:
+        unique_suggestions = {}
+        for suggestion_list in ordered_lists:
+            for suggestion in suggestion_list:
+                unique_suggestions[suggestion] = None
+
+        suggestions = list(unique_suggestions.keys())
+        if not suggestions:
+            return ""
+
+        return f"(did you mean one of: '{"', '".join(suggestions)}')"
+
     def check_sections(self, ln, decl_name, decl_type):
         """
         Check for errors inside sections, emitting warnings if not found
@@ -566,12 +611,13 @@ class KernelDoc:
         for section in self.entry.sections:
             if section not in self.entry.parameterlist and \
                not known_sections.search(section):
+                hint = self.get_suggestions_hint(section, self.entry.parameterlist)
                 if decl_type == 'function':
                     dname = f"{decl_type} parameter"
                 else:
                     dname = f"{decl_type} member"
                 self.emit_msg(ln,
-                              f"Excess {dname} '{section}' description in '{decl_name}'")
+                              f"Excess {dname} '{section}' description in '{decl_name}' {hint}".strip())
 
         #
         # Check that documented parameter names (from doc comments, including
@@ -591,12 +637,13 @@ class KernelDoc:
             if param_name in self.entry.parameterlist:
                 continue
 
+            hint = self.get_suggestions_hint(param_name, self.entry.parameterlist)
             if decl_type == 'function':
                 dname = f"{decl_type} parameter"
             else:
                 dname = f"{decl_type} member"
             self.emit_msg(ln,
-                          f"Excess {dname} '{param_name}' description in '{decl_name}'")
+                          f"Excess {dname} '{param_name}' description in '{decl_name}' {hint}".strip())
 
     def check_return_section(self, ln, declaration_name, return_type):
         """
-- 
2.55.0
Re: [PATCH v3] scripts/kernel-doc: Suggest possible names for excess descriptions
Posted by Jonathan Corbet 2 days, 12 hours ago
Ryszard Knop <ryszard.knop@intel.com> writes:

> Recent check_sections() change added a warning if a documentation tag
> member name does not match the detected struct/union member names. Since
> the checker knows all possible names, we can suggest known names, so
> that it's more obvious how to deal with the warning.
>
> Signed-off-by: Ryszard Knop <ryszard.knop@intel.com>
> ---
> Changes in v1:
> - Added the suggestion hint in the warning, with basic name substring checks
> - Link: https://lore.kernel.org/linux-doc/20260714111208.323108-1-ryszard.knop@intel.com/
>
> Changes in v2:
> - Strip trailing whitespace from the warning when the generated hint is empty
> - Link: https://lore.kernel.org/linux-doc/20260715111726.394565-1-ryszard.knop@intel.com/
>
> v3:
> - Use difflib to generate suggestions even if the tag member is mistyped
> - Suggest names based on the nested struct members too
> ---
>  tools/lib/python/kdoc/kdoc_parser.py | 51 ++++++++++++++++++++++++++--
>  1 file changed, 49 insertions(+), 2 deletions(-)

Applied, thanks.

jon
Re: [PATCH v3] scripts/kernel-doc: Suggest possible names for excess descriptions
Posted by Randy Dunlap 1 week ago

On 7/17/26 5:57 AM, Ryszard Knop wrote:
> Recent check_sections() change added a warning if a documentation tag
> member name does not match the detected struct/union member names. Since
> the checker knows all possible names, we can suggest known names, so
> that it's more obvious how to deal with the warning.
> 
> Signed-off-by: Ryszard Knop <ryszard.knop@intel.com>

Tested-by: Randy Dunlap <rdunlap@infradead.org>
Acked-by: Randy Dunlap <rdunlap@infradead.org>

Thanks.

> ---
> Changes in v1:
> - Added the suggestion hint in the warning, with basic name substring checks
> - Link: https://lore.kernel.org/linux-doc/20260714111208.323108-1-ryszard.knop@intel.com/
> 
> Changes in v2:
> - Strip trailing whitespace from the warning when the generated hint is empty
> - Link: https://lore.kernel.org/linux-doc/20260715111726.394565-1-ryszard.knop@intel.com/
> 
> v3:
> - Use difflib to generate suggestions even if the tag member is mistyped
> - Suggest names based on the nested struct members too
> ---
>  tools/lib/python/kdoc/kdoc_parser.py | 51 ++++++++++++++++++++++++++--
>  1 file changed, 49 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py
> index 2dedda215c22..884f42584667 100644
> --- a/tools/lib/python/kdoc/kdoc_parser.py
> +++ b/tools/lib/python/kdoc/kdoc_parser.py
> @@ -11,6 +11,7 @@ and extract embedded documentation comments from it.
>  
>  import sys
>  import re
> +import difflib
>  from pprint import pformat
>  
>  from kdoc.c_lex import CTokenizer, tokenizer_set_log
> @@ -558,6 +559,50 @@ class KernelDoc:
>                          self.push_parameter(ln, decl_type, param, dtype,
>                                              arg, declaration_name)
>  
> +    def get_suggestions_hint(self, decl_name, possible_names):
> +        # For decl name 'flags' or 'flgas', suggests 'substruct.flags'
> +        submember_exact = []
> +        submember_substrings = []
> +        submember_suggestions = []
> +        for possible_name in possible_names:
> +            parts = possible_name.strip().split('.')
> +            if len(parts) < 2:
> +                continue
> +
> +            final_part = parts[-1]
> +            if decl_name == final_part:
> +                submember_exact.append(possible_name)
> +            elif decl_name in final_part:
> +                submember_substrings.append(possible_name)
> +            elif difflib.get_close_matches(decl_name, [final_part]):
> +                submember_suggestions.append(possible_name)
> +
> +        # For decl name 'flgas', suggests 'flags'
> +        full_suggestions = difflib.get_close_matches(decl_name, possible_names)
> +
> +        # For decl name 'member', suggests 'longer_member'
> +        full_substrings = [name for name in possible_names if decl_name in name]
> +
> +        ordered_lists = [
> +            submember_exact,
> +            submember_substrings,
> +            submember_suggestions,
> +            full_suggestions,
> +            full_substrings,
> +        ]
> +
> +        # Deduplicate but maintain order from most to least likely:
> +        unique_suggestions = {}
> +        for suggestion_list in ordered_lists:
> +            for suggestion in suggestion_list:
> +                unique_suggestions[suggestion] = None
> +
> +        suggestions = list(unique_suggestions.keys())
> +        if not suggestions:
> +            return ""
> +
> +        return f"(did you mean one of: '{"', '".join(suggestions)}')"
> +
>      def check_sections(self, ln, decl_name, decl_type):
>          """
>          Check for errors inside sections, emitting warnings if not found
> @@ -566,12 +611,13 @@ class KernelDoc:
>          for section in self.entry.sections:
>              if section not in self.entry.parameterlist and \
>                 not known_sections.search(section):
> +                hint = self.get_suggestions_hint(section, self.entry.parameterlist)
>                  if decl_type == 'function':
>                      dname = f"{decl_type} parameter"
>                  else:
>                      dname = f"{decl_type} member"
>                  self.emit_msg(ln,
> -                              f"Excess {dname} '{section}' description in '{decl_name}'")
> +                              f"Excess {dname} '{section}' description in '{decl_name}' {hint}".strip())
>  
>          #
>          # Check that documented parameter names (from doc comments, including
> @@ -591,12 +637,13 @@ class KernelDoc:
>              if param_name in self.entry.parameterlist:
>                  continue
>  
> +            hint = self.get_suggestions_hint(param_name, self.entry.parameterlist)
>              if decl_type == 'function':
>                  dname = f"{decl_type} parameter"
>              else:
>                  dname = f"{decl_type} member"
>              self.emit_msg(ln,
> -                          f"Excess {dname} '{param_name}' description in '{decl_name}'")
> +                          f"Excess {dname} '{param_name}' description in '{decl_name}' {hint}".strip())
>  
>      def check_return_section(self, ln, declaration_name, return_type):
>          """

-- 
~Randy