From 27ad33b6b349c8c76fdef3bf0f707158ce7c275e Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 6 Jun 2025 15:15:42 +0100 Subject: kernel-doc: Fix symbol matching for dropped suffixes The support for dropping "_noprof" missed dropping the suffix from exported symbols. That meant that using the :export: feature would look for kernel-doc for (eg) krealloc_noprof() and not find the kernel-doc for krealloc(). Fixes: 51a7bf0238c2 (scripts/kernel-doc: drop "_noprof" on function prototypes) Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606141543.1285671-1-willy@infradead.org --- scripts/lib/kdoc/kdoc_parser.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 062453eefc7a..2c6143f7ca0f 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1171,16 +1171,24 @@ class KernelDoc: with a staticmethod decorator. """ + # We support documenting some exported symbols with different + # names. A horrible hack. + suffixes = [ '_noprof' ] + # Note: it accepts only one EXPORT_SYMBOL* per line, as having # multiple export lines would violate Kernel coding style. if export_symbol.search(line): symbol = export_symbol.group(2) + for suffix in suffixes: + symbol = symbol.removesuffix(suffix) function_set.add(symbol) return if export_symbol_ns.search(line): symbol = export_symbol_ns.group(2) + for suffix in suffixes: + symbol = symbol.removesuffix(suffix) function_set.add(symbol) def process_normal(self, ln, line): -- cgit v1.2.3 From e8f0303e8b8dce911536963c89eaf0a5ccb62d6a Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:30 -0600 Subject: docs: kdoc: simplify the PROTO continuation logic Remove the unneeded "cont" variable and tighten up the code slightly. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-2-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 2c6143f7ca0f..899d5446f95c 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1688,7 +1688,6 @@ class KernelDoc: Besides parsing kernel-doc tags, it also parses export symbols. """ - cont = False prev = "" prev_ln = None export_table = set() @@ -1704,18 +1703,14 @@ class KernelDoc: if self.state == state.PROTO: if line.endswith("\\"): prev += line.rstrip("\\") - cont = True - if not prev_ln: prev_ln = ln - continue - if cont: + if prev: ln = prev_ln line = prev + line prev = "" - cont = False prev_ln = None self.config.log.debug("%d %s%s: %s", -- cgit v1.2.3 From cef8c781ca71ddd0777d639775e66f8630359342 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:31 -0600 Subject: docs: kdoc: move the core dispatch into a state table Since all of the handlers already nicely have the same prototype, put them into a table and call them from there and take out the extended if-then-else series. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-3-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 899d5446f95c..1a6c6865b2c5 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1678,6 +1678,21 @@ class KernelDoc: return export_table + # + # The state/action table telling us which function to invoke in + # each state. + # + state_actions = { + state.NORMAL: process_normal, + state.NAME: process_name, + state.BODY: process_body, + state.BODY_MAYBE: process_body, + state.BODY_WITH_BLANK_LINE: process_body, + state.INLINE: process_inline, + state.PROTO: process_proto, + state.DOCBLOCK: process_docblock, + } + def parse_kdoc(self): """ Open and process each line of a C source file. @@ -1729,19 +1744,8 @@ class KernelDoc: self.process_export(export_table, line) # Hand this line to the appropriate state handler - if self.state == state.NORMAL: - self.process_normal(ln, line) - elif self.state == state.NAME: - self.process_name(ln, line) - elif self.state in [state.BODY, state.BODY_MAYBE, - state.BODY_WITH_BLANK_LINE]: - self.process_body(ln, line) - elif self.state == state.INLINE: # scanning for inline parameters - self.process_inline(ln, line) - elif self.state == state.PROTO: - self.process_proto(ln, line) - elif self.state == state.DOCBLOCK: - self.process_docblock(ln, line) + self.state_actions[self.state](self, ln, line) + except OSError: self.config.log.error(f"Error: Cannot open file {self.fname}") -- cgit v1.2.3 From 42592bd46dded5fab5af1d5e04c9b17cbb4bca6d Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:32 -0600 Subject: docs: kdoc: remove the section_intro variable It is only used in one place, so just put the constant string "Introduction" there so people don't have to go looking for it. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-4-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 1a6c6865b2c5..f8871f6a2638 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -203,7 +203,6 @@ class KernelDoc: # Section names - section_intro = "Introduction" section_context = "Context" section_return = "Return" @@ -1215,7 +1214,7 @@ class KernelDoc: self.entry.new_start_line = ln if not doc_block.group(1): - self.entry.section = self.section_intro + self.entry.section = "Introduction" else: self.entry.section = doc_block.group(1) -- cgit v1.2.3 From e76a1d2b2623e9f10e2ffd295ae2615bf3228561 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:33 -0600 Subject: docs: kdoc: simplify the kerneldoc recognition code process_name() looks for the first line of a kerneldoc comment. It contains two nearly identical regular expressions, the second of which only catches six cases in the kernel, all of the form: define SOME_MACRO_NAME - description Simply put the "define" into the regex and discard it, eliminating the loop and the code to remove it specially. Note that this still treats these defines as if they were functions, but that's a separate issue. There is no change in the generated output. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-5-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index f8871f6a2638..72919a5d71b2 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1238,26 +1238,18 @@ class KernelDoc: # Test for data declaration r = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef)\b\s*(\w*)") + r2 = KernRe(fr"^{decl_start}{fn_type}(?:define\s+)?(\w+)\s*{parenthesis}\s*{decl_end}?$") if r.search(line): self.entry.decl_type = r.group(1) self.entry.identifier = r.group(2) self.entry.is_kernel_comment = True - else: - # Look for foo() or static void foo() - description; - # or misspelt identifier - - r1 = KernRe(fr"^{decl_start}{fn_type}(\w+)\s*{parenthesis}\s*{decl_end}?$") - r2 = KernRe(fr"^{decl_start}{fn_type}(\w+[^-:]*){parenthesis}\s*{decl_end}$") - - for r in [r1, r2]: - if r.search(line): - self.entry.identifier = r.group(1) - self.entry.decl_type = "function" - - r = KernRe(r"define\s+") - self.entry.identifier = r.sub("", self.entry.identifier) - self.entry.is_kernel_comment = True - break + # + # Look for a function description + # + elif r2.search(line): + self.entry.identifier = r2.group(1) + self.entry.decl_type = "function" + self.entry.is_kernel_comment = True self.entry.identifier = self.entry.identifier.strip(" ") -- cgit v1.2.3 From 8f4650fe1a74e68f5c6715413a5a26aa1564780d Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:34 -0600 Subject: docs: kdoc: remove the KernelEntry::is_kernel_comment member entry::is_kernel_comment never had anything to do with the entry itself; it is a bit of local state in one branch of process_name(). It can, in fact, be removed entirely; rework the code slightly so that it is no longer needed. No change in the rendered output. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-6-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 72919a5d71b2..dffa3055adc1 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1224,7 +1224,6 @@ class KernelDoc: if doc_decl.search(line): self.entry.identifier = doc_decl.group(1) - self.entry.is_kernel_comment = False decl_start = str(doc_com) # comment block asterisk fn_type = r"(?:\w+\s*\*\s*)?" # type (for non-functions) @@ -1242,14 +1241,20 @@ class KernelDoc: if r.search(line): self.entry.decl_type = r.group(1) self.entry.identifier = r.group(2) - self.entry.is_kernel_comment = True # # Look for a function description # elif r2.search(line): self.entry.identifier = r2.group(1) self.entry.decl_type = "function" - self.entry.is_kernel_comment = True + # + # We struck out. + # + else: + self.emit_msg(ln, + f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") + self.state = state.NORMAL + return self.entry.identifier = self.entry.identifier.strip(" ") @@ -1271,11 +1276,6 @@ class KernelDoc: else: self.entry.declaration_purpose = "" - if not self.entry.is_kernel_comment: - self.emit_msg(ln, - f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") - self.state = state.NORMAL - if not self.entry.declaration_purpose and self.config.wshort_desc: self.emit_msg(ln, f"missing initial short description on line:\n{line}") -- cgit v1.2.3 From f9b4cf2e8518387d4c512d934137dc6968759ec4 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:35 -0600 Subject: docs: kdoc: remove the KernelEntry::descr pseudo member The entry.descr value used in process_name() is not actually a member of the KernelEntry class; it is a bit of local state. So just manage it locally. A trim_whitespace() helper was added to clean up the code slightly. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-7-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index dffa3055adc1..2d8a046499c7 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -60,6 +60,13 @@ export_symbol_ns = KernRe(r'^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+" type_param = KernRe(r"\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) +# +# A little helper to get rid of excess white space +# +multi_space = KernRe(r'\s\s+') +def trim_whitespace(s): + return multi_space.sub(' ', s.strip()) + class state: """ State machine enums @@ -1266,12 +1273,7 @@ class KernelDoc: r = KernRe("[-:](.*)") if r.search(line): - # strip leading/trailing/multiple spaces - self.entry.descr = r.group(1).strip(" ") - - r = KernRe(r"\s+") - self.entry.descr = r.sub(" ", self.entry.descr) - self.entry.declaration_purpose = self.entry.descr + self.entry.declaration_purpose = trim_whitespace(r.group(1)) self.state = state.BODY_MAYBE else: self.entry.declaration_purpose = "" -- cgit v1.2.3 From b23c71080b6cb0c12d4962321e5266814f980da1 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:36 -0600 Subject: docs: kdoc: remove some ineffective code The code testing for a pointer declaration in process_name() has no actual effect on subsequent actions; remove it. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-8-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 2d8a046499c7..575817387a32 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1237,11 +1237,6 @@ class KernelDoc: parenthesis = r"(?:\(\w*\))?" # optional parenthesis on function decl_end = r"(?:[-:].*)" # end of the name part - # test for pointer declaration type, foo * bar() - desc - r = KernRe(fr"^{decl_start}([\w\s]+?){parenthesis}?\s*{decl_end}?$") - if r.search(line): - self.entry.identifier = r.group(1) - # Test for data declaration r = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef)\b\s*(\w*)") r2 = KernRe(fr"^{decl_start}{fn_type}(?:define\s+)?(\w+)\s*{parenthesis}\s*{decl_end}?$") -- cgit v1.2.3 From 0682bde2c7f44320c621b765f31a0cf24e01b23f Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:37 -0600 Subject: docs: kdoc: move the declaration regexes out of process_name() Move two complex regexes up with the other patterns, decluttering this function and allowing the compilation to be done once rather than for every kerneldoc comment. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-9-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 575817387a32..d814e48f9f38 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -47,7 +47,6 @@ doc_sect = doc_com + \ flags=re.I, cache=False) doc_content = doc_com_body + KernRe(r'(.*)', cache=False) -doc_block = doc_com + KernRe(r'DOC:\s*(.*)?', cache=False) doc_inline_start = KernRe(r'^\s*/\*\*\s*$', cache=False) doc_inline_sect = KernRe(r'\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)', cache=False) doc_inline_end = KernRe(r'^\s*\*/\s*$', cache=False) @@ -60,6 +59,18 @@ export_symbol_ns = KernRe(r'^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+" type_param = KernRe(r"\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) +# +# Tests for the beginning of a kerneldoc block in its various forms. +# +doc_block = doc_com + KernRe(r'DOC:\s*(.*)?', cache=False) +doc_begin_data = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef)\b\s*(\w*)", cache = False) +doc_begin_func = KernRe(str(doc_com) + # initial " * ' + r"(?:\w+\s*\*\s*)?" + # type (not captured) + r'(?:define\s+)?' + # possible "define" (not captured) + r'(\w+)\s*(?:\(\w*\))?\s*' + # name and optional "(...)" + r'(?:[-:].*)?$', # description (not captured) + cache = False) + # # A little helper to get rid of excess white space # @@ -1232,22 +1243,15 @@ class KernelDoc: if doc_decl.search(line): self.entry.identifier = doc_decl.group(1) - decl_start = str(doc_com) # comment block asterisk - fn_type = r"(?:\w+\s*\*\s*)?" # type (for non-functions) - parenthesis = r"(?:\(\w*\))?" # optional parenthesis on function - decl_end = r"(?:[-:].*)" # end of the name part - # Test for data declaration - r = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef)\b\s*(\w*)") - r2 = KernRe(fr"^{decl_start}{fn_type}(?:define\s+)?(\w+)\s*{parenthesis}\s*{decl_end}?$") - if r.search(line): - self.entry.decl_type = r.group(1) - self.entry.identifier = r.group(2) + if doc_begin_data.search(line): + self.entry.decl_type = doc_begin_data.group(1) + self.entry.identifier = doc_begin_data.group(2) # # Look for a function description # - elif r2.search(line): - self.entry.identifier = r2.group(1) + elif doc_begin_func.search(line): + self.entry.identifier = doc_begin_func.group(1) self.entry.decl_type = "function" # # We struck out. -- cgit v1.2.3 From 8666a352dc1738f6302382d9d64611a44978d369 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 6 Jun 2025 10:34:38 -0600 Subject: docs: kdoc: some final touches for process_name() Add some comments to process_name() to cover its broad phases of operation, and slightly restructure the if/then/else structure to remove some early returns. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250606163438.229916-10-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index d814e48f9f38..42b2e0936b72 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1227,7 +1227,9 @@ class KernelDoc: """ STATE_NAME: Looking for the "name - description" line """ - + # + # Check for a DOC: block and handle them specially. + # if doc_block.search(line): self.entry.new_start_line = ln @@ -1238,9 +1240,10 @@ class KernelDoc: self.entry.identifier = self.entry.section self.state = state.DOCBLOCK - return - - if doc_decl.search(line): + # + # Otherwise we're looking for a normal kerneldoc declaration line. + # + elif doc_decl.search(line): self.entry.identifier = doc_decl.group(1) # Test for data declaration @@ -1261,15 +1264,19 @@ class KernelDoc: f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") self.state = state.NORMAL return - - self.entry.identifier = self.entry.identifier.strip(" ") - + # + # OK, set up for a new kerneldoc entry. + # self.state = state.BODY - + self.entry.identifier = self.entry.identifier.strip(" ") # if there's no @param blocks need to set up default section here self.entry.section = SECTION_DEFAULT self.entry.new_start_line = ln + 1 - + # + # Find the description portion, which *should* be there but + # isn't always. + # (We should be able to capture this from the previous parsing - someday) + # r = KernRe("[-:](.*)") if r.search(line): self.entry.declaration_purpose = trim_whitespace(r.group(1)) @@ -1290,11 +1297,11 @@ class KernelDoc: self.emit_msg(ln, f"Scanning doc for {self.entry.decl_type} {self.entry.identifier}", warning=False) - - return - + # # Failed to find an identifier. Emit a warning - self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") + # + else: + self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") def process_body(self, ln, line): """ -- cgit v1.2.3 From 823d6f956605cb2f009f75de138622fcd7e03817 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:04 -0600 Subject: docs: kdoc: Make body_with_blank_line parsing more flexible The regex in the BODY_WITH_BLANK_LINE case was looking for lines starting with " * ", where exactly one space was allowed before the following text. There are many kerneldoc comments where the authors have put multiple spaces instead, leading to mis-formatting of the documentation. Specifically, in this case, the description portion is associated with the last of the parameters. Allow multiple spaces in this context. See, for example, synchronize_hardirq() and how its documentation is formatted before and after the change. Acked-by: Mauro Carvalho Chehab Tested-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-2-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 42b2e0936b72..c46e1b6a7d4b 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1309,7 +1309,7 @@ class KernelDoc: """ if self.state == state.BODY_WITH_BLANK_LINE: - r = KernRe(r"\s*\*\s?\S") + r = KernRe(r"\s*\*\s*\S") if r.match(line): self.dump_section() self.entry.section = SECTION_DEFAULT -- cgit v1.2.3 From df2755269456d9ed02ad689aa8eaa50f7ac4217e Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:05 -0600 Subject: docs: kdoc: consolidate the "begin section" logic Pull the repeated "begin a section" logic into a single place and hide it within the KernelEntry class. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-3-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index c46e1b6a7d4b..d29a61a06f6d 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -169,6 +169,15 @@ class KernelEntry: self.warnings.append(log_msg) return + # + # Begin a new section. + # + def begin_section(self, line_no, title = SECTION_DEFAULT, dump = False): + if dump: + self.dump_section(start_new = True) + self.section = title + self.new_start_line = line_no + def dump_section(self, start_new=True): """ Dumps section contents to arrays/hashes intended for that purpose. @@ -1231,12 +1240,11 @@ class KernelDoc: # Check for a DOC: block and handle them specially. # if doc_block.search(line): - self.entry.new_start_line = ln if not doc_block.group(1): - self.entry.section = "Introduction" + self.entry.begin_section(ln, "Introduction") else: - self.entry.section = doc_block.group(1) + self.entry.begin_section(ln, doc_block.group(1)) self.entry.identifier = self.entry.section self.state = state.DOCBLOCK @@ -1270,8 +1278,7 @@ class KernelDoc: self.state = state.BODY self.entry.identifier = self.entry.identifier.strip(" ") # if there's no @param blocks need to set up default section here - self.entry.section = SECTION_DEFAULT - self.entry.new_start_line = ln + 1 + self.entry.begin_section(ln + 1) # # Find the description portion, which *should* be there but # isn't always. @@ -1312,8 +1319,7 @@ class KernelDoc: r = KernRe(r"\s*\*\s*\S") if r.match(line): self.dump_section() - self.entry.section = SECTION_DEFAULT - self.entry.new_start_line = ln + self.entry.begin_section(ln) self.entry.contents = "" if doc_sect.search(line): @@ -1340,8 +1346,7 @@ class KernelDoc: if self.entry.contents.strip("\n"): self.dump_section() - self.entry.new_start_line = ln - self.entry.section = newsection + self.entry.begin_section(ln, newsection) self.entry.leading_space = None self.entry.contents = newcontents.lstrip() @@ -1370,9 +1375,7 @@ class KernelDoc: if cont == "": if self.entry.section == self.section_context: - self.dump_section() - - self.entry.new_start_line = ln + self.entry.begin_section(ln, dump = True) self.state = state.BODY else: if self.entry.section != SECTION_DEFAULT: @@ -1427,8 +1430,7 @@ class KernelDoc: if self.inline_doc_state == state.INLINE_NAME and \ doc_inline_sect.search(line): - self.entry.section = doc_inline_sect.group(1) - self.entry.new_start_line = ln + self.entry.begin_section(ln, doc_inline_sect.group(1)) self.entry.contents = doc_inline_sect.group(2).lstrip() if self.entry.contents != "": @@ -1627,7 +1629,7 @@ class KernelDoc: """STATE_PROTO: reading a function/whatever prototype.""" if doc_inline_oneline.search(line): - self.entry.section = doc_inline_oneline.group(1) + self.entry.begin_section(ln, doc_inline_oneline.group(1)) self.entry.contents = doc_inline_oneline.group(2) if self.entry.contents != "": -- cgit v1.2.3 From e4153a2255b1a0f3398360895e79e7709a0600b2 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:06 -0600 Subject: docs: kdoc: separate out the handling of the declaration phase The BODY_MAYBE state really describes the "we are in a declaration" state. Rename it accordingly, and split the handling of this state out from that of the other BODY* states. This change introduces a fair amount of duplicated code that will be coalesced in a later patch. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-4-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 93 ++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 15 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index d29a61a06f6d..f1491f8c88e7 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -86,7 +86,7 @@ class state: # Parser states NORMAL = 0 # normal code NAME = 1 # looking for function name - BODY_MAYBE = 2 # body - or maybe more description + DECLARATION = 2 # We have seen a declaration which might not be done BODY = 3 # the body of the comment BODY_WITH_BLANK_LINE = 4 # the body which has a blank line PROTO = 5 # scanning prototype @@ -96,7 +96,7 @@ class state: name = [ "NORMAL", "NAME", - "BODY_MAYBE", + "DECLARATION", "BODY", "BODY_WITH_BLANK_LINE", "PROTO", @@ -1287,7 +1287,7 @@ class KernelDoc: r = KernRe("[-:](.*)") if r.search(line): self.entry.declaration_purpose = trim_whitespace(r.group(1)) - self.state = state.BODY_MAYBE + self.state = state.DECLARATION else: self.entry.declaration_purpose = "" @@ -1310,9 +1310,82 @@ class KernelDoc: else: self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") + def process_decl(self, ln, line): + """ + STATE_DECLARATION: We've seen the beginning of a declaration + """ + if doc_sect.search(line): + self.entry.in_doc_sect = True + newsection = doc_sect.group(1) + + if newsection.lower() in ["description", "context"]: + newsection = newsection.title() + + # Special case: @return is a section, not a param description + if newsection.lower() in ["@return", "@returns", + "return", "returns"]: + newsection = "Return" + + # Perl kernel-doc has a check here for contents before sections. + # the logic there is always false, as in_doc_sect variable is + # always true. So, just don't implement Wcontents_before_sections + + # .title() + newcontents = doc_sect.group(2) + if not newcontents: + newcontents = "" + + if self.entry.contents.strip("\n"): + self.dump_section() + + self.entry.begin_section(ln, newsection) + self.entry.leading_space = None + + self.entry.contents = newcontents.lstrip() + if self.entry.contents: + self.entry.contents += "\n" + + self.state = state.BODY + return + + if doc_end.search(line): + self.dump_section() + + # Look for doc_com + + doc_end: + r = KernRe(r'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') + if r.match(line): + self.emit_msg(ln, f"suspicious ending line: {line}") + + self.entry.prototype = "" + self.entry.new_start_line = ln + 1 + + self.state = state.PROTO + return + + if doc_content.search(line): + cont = doc_content.group(1) + + if cont == "": + self.state = state.BODY + self.entry.contents += "\n" # needed? + + else: + # Continued declaration purpose + self.entry.declaration_purpose = self.entry.declaration_purpose.rstrip() + self.entry.declaration_purpose += " " + cont + + r = KernRe(r"\s+") + self.entry.declaration_purpose = r.sub(' ', + self.entry.declaration_purpose) + return + + # Unknown line, ignore + self.emit_msg(ln, f"bad line: {line}") + + def process_body(self, ln, line): """ - STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. + STATE_BODY: the bulk of a kerneldoc comment. """ if self.state == state.BODY_WITH_BLANK_LINE: @@ -1385,16 +1458,6 @@ class KernelDoc: self.entry.contents += "\n" - elif self.state == state.BODY_MAYBE: - - # Continued declaration purpose - self.entry.declaration_purpose = self.entry.declaration_purpose.rstrip() - self.entry.declaration_purpose += " " + cont - - r = KernRe(r"\s+") - self.entry.declaration_purpose = r.sub(' ', - self.entry.declaration_purpose) - else: if self.entry.section.startswith('@') or \ self.entry.section == self.section_context: @@ -1687,7 +1750,7 @@ class KernelDoc: state.NORMAL: process_normal, state.NAME: process_name, state.BODY: process_body, - state.BODY_MAYBE: process_body, + state.DECLARATION: process_decl, state.BODY_WITH_BLANK_LINE: process_body, state.INLINE: process_inline, state.PROTO: process_proto, -- cgit v1.2.3 From 74cee0dfc2fc50e0d53629c289dc9b2954d31b1c Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:07 -0600 Subject: docs: kdoc: split out the special-section state The state known as BODY_WITH_BLANK_LINE really, in a convoluted way, indicates a "special section" that is terminated by a blank line or the beginning of a new section. That is either "@param: desc" sections, or the weird "context" section that plays by the same rules. Rename the state to SPECIAL_SECTION and split its processing into a separate function; no real changes to the logic yet. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-5-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index f1491f8c88e7..185ffe4e1469 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -88,7 +88,7 @@ class state: NAME = 1 # looking for function name DECLARATION = 2 # We have seen a declaration which might not be done BODY = 3 # the body of the comment - BODY_WITH_BLANK_LINE = 4 # the body which has a blank line + SPECIAL_SECTION = 4 # doc section ending with a blank line PROTO = 5 # scanning prototype DOCBLOCK = 6 # documentation block INLINE = 7 # gathering doc outside main block @@ -98,7 +98,7 @@ class state: "NAME", "DECLARATION", "BODY", - "BODY_WITH_BLANK_LINE", + "SPECIAL_SECTION", "PROTO", "DOCBLOCK", "INLINE", @@ -1383,18 +1383,18 @@ class KernelDoc: self.emit_msg(ln, f"bad line: {line}") + def process_special(self, ln, line): + """ + STATE_SPECIAL_SECTION: a section ending with a blank line + """ + if KernRe(r"\s*\*\s*\S").match(line): + self.entry.begin_section(ln, dump = True) + self.process_body(ln, line) + def process_body(self, ln, line): """ STATE_BODY: the bulk of a kerneldoc comment. """ - - if self.state == state.BODY_WITH_BLANK_LINE: - r = KernRe(r"\s*\*\s*\S") - if r.match(line): - self.dump_section() - self.entry.begin_section(ln) - self.entry.contents = "" - if doc_sect.search(line): self.entry.in_doc_sect = True newsection = doc_sect.group(1) @@ -1452,7 +1452,7 @@ class KernelDoc: self.state = state.BODY else: if self.entry.section != SECTION_DEFAULT: - self.state = state.BODY_WITH_BLANK_LINE + self.state = state.SPECIAL_SECTION else: self.state = state.BODY @@ -1751,7 +1751,7 @@ class KernelDoc: state.NAME: process_name, state.BODY: process_body, state.DECLARATION: process_decl, - state.BODY_WITH_BLANK_LINE: process_body, + state.SPECIAL_SECTION: process_special, state.INLINE: process_inline, state.PROTO: process_proto, state.DOCBLOCK: process_docblock, -- cgit v1.2.3 From 99327067e1974e83cd8a60cf8445ce49086de46e Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:08 -0600 Subject: docs: kdoc: coalesce the new-section handling Merge the duplicated code back into a single implementation. Code movement only, no logic changes. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-6-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 49 +++++++++++------------------------------ 1 file changed, 13 insertions(+), 36 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 185ffe4e1469..a336d543e72b 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1310,10 +1310,10 @@ class KernelDoc: else: self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") - def process_decl(self, ln, line): - """ - STATE_DECLARATION: We've seen the beginning of a declaration - """ + # + # Helper function to determine if a new section is being started. + # + def is_new_section(self, ln, line): if doc_sect.search(line): self.entry.in_doc_sect = True newsection = doc_sect.group(1) @@ -1346,6 +1346,14 @@ class KernelDoc: self.entry.contents += "\n" self.state = state.BODY + return True + return False + + def process_decl(self, ln, line): + """ + STATE_DECLARATION: We've seen the beginning of a declaration + """ + if self.is_new_section(ln, line): return if doc_end.search(line): @@ -1395,38 +1403,7 @@ class KernelDoc: """ STATE_BODY: the bulk of a kerneldoc comment. """ - if doc_sect.search(line): - self.entry.in_doc_sect = True - newsection = doc_sect.group(1) - - if newsection.lower() in ["description", "context"]: - newsection = newsection.title() - - # Special case: @return is a section, not a param description - if newsection.lower() in ["@return", "@returns", - "return", "returns"]: - newsection = "Return" - - # Perl kernel-doc has a check here for contents before sections. - # the logic there is always false, as in_doc_sect variable is - # always true. So, just don't implement Wcontents_before_sections - - # .title() - newcontents = doc_sect.group(2) - if not newcontents: - newcontents = "" - - if self.entry.contents.strip("\n"): - self.dump_section() - - self.entry.begin_section(ln, newsection) - self.entry.leading_space = None - - self.entry.contents = newcontents.lstrip() - if self.entry.contents: - self.entry.contents += "\n" - - self.state = state.BODY + if self.is_new_section(ln, line): return if doc_end.search(line): -- cgit v1.2.3 From e65d54e19149601b96a19790ee9ba9ed04c59abe Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:09 -0600 Subject: docs: kdoc: rework the handling of SPECIAL_SECTION Move the recognition of this state to when we enter it, rather than when we exit, eliminating some twisty logic along the way. Some changes in output do result from this shift, generally for kerneldoc comments that do not quite fit the format. See, for example, struct irqdomain. As far as I can tell, the new behavior is more correct in each case. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-7-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 48 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index a336d543e72b..5998b02ca3a0 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1316,21 +1316,25 @@ class KernelDoc: def is_new_section(self, ln, line): if doc_sect.search(line): self.entry.in_doc_sect = True + self.state = state.BODY + # + # Pick out the name of our new section, tweaking it if need be. + # newsection = doc_sect.group(1) - - if newsection.lower() in ["description", "context"]: - newsection = newsection.title() - - # Special case: @return is a section, not a param description - if newsection.lower() in ["@return", "@returns", - "return", "returns"]: + if newsection.lower() == 'description': + newsection = 'Description' + elif newsection.lower() == 'context': + newsection = 'Context' + self.state = state.SPECIAL_SECTION + elif newsection.lower() in ["@return", "@returns", + "return", "returns"]: newsection = "Return" - - # Perl kernel-doc has a check here for contents before sections. - # the logic there is always false, as in_doc_sect variable is - # always true. So, just don't implement Wcontents_before_sections - - # .title() + self.state = state.SPECIAL_SECTION + elif newsection[0] == '@': + self.state = state.SPECIAL_SECTION + # + # Initialize the contents, and get the new section going. + # newcontents = doc_sect.group(2) if not newcontents: newcontents = "" @@ -1344,8 +1348,6 @@ class KernelDoc: self.entry.contents = newcontents.lstrip() if self.entry.contents: self.entry.contents += "\n" - - self.state = state.BODY return True return False @@ -1395,8 +1397,9 @@ class KernelDoc: """ STATE_SPECIAL_SECTION: a section ending with a blank line """ - if KernRe(r"\s*\*\s*\S").match(line): + if KernRe(r"\s*\*\s*$").match(line): self.entry.begin_section(ln, dump = True) + self.state = state.BODY self.process_body(ln, line) def process_body(self, ln, line): @@ -1424,20 +1427,9 @@ class KernelDoc: cont = doc_content.group(1) if cont == "": - if self.entry.section == self.section_context: - self.entry.begin_section(ln, dump = True) - self.state = state.BODY - else: - if self.entry.section != SECTION_DEFAULT: - self.state = state.SPECIAL_SECTION - else: - self.state = state.BODY - self.entry.contents += "\n" - else: - if self.entry.section.startswith('@') or \ - self.entry.section == self.section_context: + if self.state == state.SPECIAL_SECTION: if self.entry.leading_space is None: r = KernRe(r'^(\s+)') if r.match(cont): -- cgit v1.2.3 From 2ad02b94914ab47b3b94274856e1b56cd94d3e31 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:10 -0600 Subject: docs: kdoc: coalesce the end-of-comment processing Separate out the end-of-comment logic into its own helper and remove the duplicated code introduced earlier. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-8-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 5998b02ca3a0..f7a5b85a8ed7 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1351,13 +1351,10 @@ class KernelDoc: return True return False - def process_decl(self, ln, line): - """ - STATE_DECLARATION: We've seen the beginning of a declaration - """ - if self.is_new_section(ln, line): - return - + # + # Helper function to detect (and effect) the end of a kerneldoc comment. + # + def is_comment_end(self, ln, line): if doc_end.search(line): self.dump_section() @@ -1370,6 +1367,15 @@ class KernelDoc: self.entry.new_start_line = ln + 1 self.state = state.PROTO + return True + return False + + + def process_decl(self, ln, line): + """ + STATE_DECLARATION: We've seen the beginning of a declaration + """ + if self.is_new_section(ln, line) or self.is_comment_end(ln, line): return if doc_content.search(line): @@ -1406,21 +1412,7 @@ class KernelDoc: """ STATE_BODY: the bulk of a kerneldoc comment. """ - if self.is_new_section(ln, line): - return - - if doc_end.search(line): - self.dump_section() - - # Look for doc_com + + doc_end: - r = KernRe(r'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') - if r.match(line): - self.emit_msg(ln, f"suspicious ending line: {line}") - - self.entry.prototype = "" - self.entry.new_start_line = ln + 1 - - self.state = state.PROTO + if self.is_new_section(ln, line) or self.is_comment_end(ln, line): return if doc_content.search(line): -- cgit v1.2.3 From ccad65a494657e899f9139174fcc74c64316c10a Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:11 -0600 Subject: docs: kdoc: Add some comments to process_decl() Now that the function can actually fit into a human brain, add a few comments. While I was at it, I switched to the trim_whitespace() helper rather than open-coding it. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-9-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index f7a5b85a8ed7..a6ee8bac378d 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1377,26 +1377,28 @@ class KernelDoc: """ if self.is_new_section(ln, line) or self.is_comment_end(ln, line): return - + # + # Look for anything with the " * " line beginning. + # if doc_content.search(line): cont = doc_content.group(1) - + # + # A blank line means that we have moved out of the declaration + # part of the comment (without any "special section" parameter + # descriptions). + # if cont == "": self.state = state.BODY self.entry.contents += "\n" # needed? - + # + # Otherwise we have more of the declaration section to soak up. + # else: - # Continued declaration purpose - self.entry.declaration_purpose = self.entry.declaration_purpose.rstrip() - self.entry.declaration_purpose += " " + cont - - r = KernRe(r"\s+") - self.entry.declaration_purpose = r.sub(' ', - self.entry.declaration_purpose) - return - - # Unknown line, ignore - self.emit_msg(ln, f"bad line: {line}") + self.entry.declaration_purpose = \ + trim_whitespace(self.entry.declaration_purpose + ' ' + cont) + else: + # Unknown line, ignore + self.emit_msg(ln, f"bad line: {line}") def process_special(self, ln, line): -- cgit v1.2.3 From 07e04d8e7dceae9822377abcb2dd07aae5747e7d Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Sat, 21 Jun 2025 14:35:12 -0600 Subject: docs: kdoc: finish disentangling the BODY and SPECIAL_SECTION states Move the last SPECIAL_SECTION special case into the proper handler function, getting rid of more if/then/else logic. The leading-space tracking was tightened up a bit in the move. Add some comments describing what is going on. No changes to the generated output. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/20250621203512.223189-10-corbet@lwn.net --- scripts/lib/kdoc/kdoc_parser.py | 80 ++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 32 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index a6ee8bac378d..3557c512c85a 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1405,10 +1405,53 @@ class KernelDoc: """ STATE_SPECIAL_SECTION: a section ending with a blank line """ + # + # If we have hit a blank line (only the " * " marker), then this + # section is done. + # if KernRe(r"\s*\*\s*$").match(line): self.entry.begin_section(ln, dump = True) + self.entry.contents += '\n' self.state = state.BODY - self.process_body(ln, line) + return + # + # Not a blank line, look for the other ways to end the section. + # + if self.is_new_section(ln, line) or self.is_comment_end(ln, line): + return + # + # OK, we should have a continuation of the text for this section. + # + if doc_content.search(line): + cont = doc_content.group(1) + # + # If the lines of text after the first in a special section have + # leading white space, we need to trim it out or Sphinx will get + # confused. For the second line (the None case), see what we + # find there and remember it. + # + if self.entry.leading_space is None: + r = KernRe(r'^(\s+)') + if r.match(cont): + self.entry.leading_space = len(r.group(1)) + else: + self.entry.leading_space = 0 + # + # Otherwise, before trimming any leading chars, be *sure* + # that they are white space. We should maybe warn if this + # isn't the case. + # + for i in range(0, self.entry.leading_space): + if cont[i] != " ": + self.entry.leading_space = i + break + # + # Add the trimmed result to the section and we're done. + # + self.entry.contents += cont[self.entry.leading_space:] + '\n' + else: + # Unknown line, ignore + self.emit_msg(ln, f"bad line: {line}") def process_body(self, ln, line): """ @@ -1419,37 +1462,10 @@ class KernelDoc: if doc_content.search(line): cont = doc_content.group(1) - - if cont == "": - self.entry.contents += "\n" - else: - if self.state == state.SPECIAL_SECTION: - if self.entry.leading_space is None: - r = KernRe(r'^(\s+)') - if r.match(cont): - self.entry.leading_space = len(r.group(1)) - else: - self.entry.leading_space = 0 - - # Double-check if leading space are realy spaces - pos = 0 - for i in range(0, self.entry.leading_space): - if cont[i] != " ": - break - pos += 1 - - cont = cont[pos:] - - # NEW LOGIC: - # In case it is different, update it - if self.entry.leading_space != pos: - self.entry.leading_space = pos - - self.entry.contents += cont + "\n" - return - - # Unknown line, ignore - self.emit_msg(ln, f"bad line: {line}") + self.entry.contents += cont + "\n" + else: + # Unknown line, ignore + self.emit_msg(ln, f"bad line: {line}") def process_inline(self, ln, line): """STATE_INLINE: docbook comments within a prototype.""" -- cgit v1.2.3 From d982828d08b63c2c56f83c09b33cb71929fd4c22 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 25 Jun 2025 14:08:40 -0600 Subject: docs: kdoc: remove KernelEntry::in_doc_sect This field is not used for anything, just get rid of it. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 3557c512c85a..f3970ffbf402 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -151,8 +151,6 @@ class KernelEntry: # State flags self.brcount = 0 - - self.in_doc_sect = False self.declaration_start_line = ln + 1 # TODO: rename to emit_message after removal of kernel-doc.pl @@ -1227,7 +1225,6 @@ class KernelDoc: # start a new entry self.reset_state(ln) - self.entry.in_doc_sect = False # next line is always the function name self.state = state.NAME @@ -1315,7 +1312,6 @@ class KernelDoc: # def is_new_section(self, ln, line): if doc_sect.search(line): - self.entry.in_doc_sect = True self.state = state.BODY # # Pick out the name of our new section, tweaking it if need be. -- cgit v1.2.3 From d6699d5f601670176bd03f95d1680914bd65b2a9 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 25 Jun 2025 14:51:11 -0600 Subject: docs: kdoc: Move content handling into KernelEntry Rather than having other code mucking around with this bit of internal state, encapsulate it internally. Accumulate the description as a list of strings, joining them at the end, which is a more efficient way of building the text. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 62 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 32 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index f3970ffbf402..f87355b63c19 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -128,7 +128,7 @@ class KernelEntry: def __init__(self, config, ln): self.config = config - self.contents = "" + self._contents = [] self.function = "" self.sectcheck = "" self.struct_actual = "" @@ -153,6 +153,15 @@ class KernelEntry: self.brcount = 0 self.declaration_start_line = ln + 1 + # + # Management of section contents + # + def add_text(self, text): + self._contents.append(text) + + def contents(self): + return '\n'.join(self._contents) + '\n' + # TODO: rename to emit_message after removal of kernel-doc.pl def emit_msg(self, log_msg, warning=True): """Emit a message""" @@ -180,9 +189,14 @@ class KernelEntry: """ Dumps section contents to arrays/hashes intended for that purpose. """ - + # + # If we have accumulated no contents in the default ("description") + # section, don't bother. + # + if self.section == SECTION_DEFAULT and not self._contents: + return name = self.section - contents = self.contents + contents = self.contents() if type_param.match(name): name = type_param.group(1) @@ -206,7 +220,8 @@ class KernelEntry: if name != SECTION_DEFAULT: self.emit_msg(self.new_start_line, f"duplicate section name '{name}'\n") - self.sections[name] += contents + # Treat as a new paragraph - add a blank line + self.sections[name] += '\n' + contents else: self.sections[name] = contents self.sectionlist.append(name) @@ -217,7 +232,7 @@ class KernelEntry: if start_new: self.section = SECTION_DEFAULT - self.contents = "" + self._contents = [] class KernelDoc: @@ -1334,16 +1349,11 @@ class KernelDoc: newcontents = doc_sect.group(2) if not newcontents: newcontents = "" - - if self.entry.contents.strip("\n"): - self.dump_section() - + self.dump_section() self.entry.begin_section(ln, newsection) self.entry.leading_space = None - self.entry.contents = newcontents.lstrip() - if self.entry.contents: - self.entry.contents += "\n" + self.entry.add_text(newcontents.lstrip()) return True return False @@ -1385,7 +1395,6 @@ class KernelDoc: # if cont == "": self.state = state.BODY - self.entry.contents += "\n" # needed? # # Otherwise we have more of the declaration section to soak up. # @@ -1407,7 +1416,6 @@ class KernelDoc: # if KernRe(r"\s*\*\s*$").match(line): self.entry.begin_section(ln, dump = True) - self.entry.contents += '\n' self.state = state.BODY return # @@ -1444,7 +1452,7 @@ class KernelDoc: # # Add the trimmed result to the section and we're done. # - self.entry.contents += cont[self.entry.leading_space:] + '\n' + self.entry.add_text(cont[self.entry.leading_space:]) else: # Unknown line, ignore self.emit_msg(ln, f"bad line: {line}") @@ -1458,7 +1466,7 @@ class KernelDoc: if doc_content.search(line): cont = doc_content.group(1) - self.entry.contents += cont + "\n" + self.entry.add_text(cont) else: # Unknown line, ignore self.emit_msg(ln, f"bad line: {line}") @@ -1470,27 +1478,20 @@ class KernelDoc: doc_inline_sect.search(line): self.entry.begin_section(ln, doc_inline_sect.group(1)) - self.entry.contents = doc_inline_sect.group(2).lstrip() - if self.entry.contents != "": - self.entry.contents += "\n" - + self.entry.add_text(doc_inline_sect.group(2).lstrip()) self.inline_doc_state = state.INLINE_TEXT # Documentation block end */ return if doc_inline_end.search(line): - if self.entry.contents not in ["", "\n"]: - self.dump_section() - + self.dump_section() self.state = state.PROTO self.inline_doc_state = state.INLINE_NA return if doc_content.search(line): if self.inline_doc_state == state.INLINE_TEXT: - self.entry.contents += doc_content.group(1) + "\n" - if not self.entry.contents.strip(" ").rstrip("\n"): - self.entry.contents = "" + self.entry.add_text(doc_content.group(1)) elif self.inline_doc_state == state.INLINE_NAME: self.emit_msg(ln, @@ -1668,11 +1669,8 @@ class KernelDoc: if doc_inline_oneline.search(line): self.entry.begin_section(ln, doc_inline_oneline.group(1)) - self.entry.contents = doc_inline_oneline.group(2) - - if self.entry.contents != "": - self.entry.contents += "\n" - self.dump_section(start_new=False) + self.entry.add_text(doc_inline_oneline.group(2)) + self.dump_section() elif doc_inline_start.search(line): self.state = state.INLINE @@ -1696,7 +1694,7 @@ class KernelDoc: self.reset_state(ln) elif doc_content.search(line): - self.entry.contents += doc_content.group(1) + "\n" + self.entry.add_text(doc_content.group(1)) def parse_export(self): """ -- cgit v1.2.3 From 1550a409e778673a63a6957718b802050f98359a Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 25 Jun 2025 15:43:37 -0600 Subject: docs: kdoc: remove a bit of dead code The type_param regex matches "@..." just fine, so the special-case branch for that in dump_section() is never executed. Just remove it. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index f87355b63c19..9e46cfa20978 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -207,13 +207,6 @@ class KernelEntry: self.sectcheck += name + " " self.new_start_line = 0 - elif name == "@...": - name = "..." - self.parameterdescs[name] = contents - self.sectcheck += name + " " - self.parameterdesc_start_lines[name] = self.new_start_line - self.new_start_line = 0 - else: if name in self.sections and self.sections[name] != "": # Only warn on user-specified duplicate section names -- cgit v1.2.3 From f61e404f5b6124905025dbda58afa1fd3171100f Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 25 Jun 2025 16:58:55 -0600 Subject: docs: kdoc: remove KernelEntry::function This member is unused, to take it out. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 1 - 1 file changed, 1 deletion(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 9e46cfa20978..224dea5f7c2e 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -129,7 +129,6 @@ class KernelEntry: self.config = config self._contents = [] - self.function = "" self.sectcheck = "" self.struct_actual = "" self.prototype = "" -- cgit v1.2.3 From 473734e086ccdd50af9d0abf81c0b70085dcf625 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 25 Jun 2025 17:19:40 -0600 Subject: docs: kdoc: rework process_export() slightly Reorganize process_export() to eliminate duplicated code, don't look for exports in states where we don't expect them, and don't bother with normal state-machine processing if an export declaration has been found. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 224dea5f7c2e..734b908579c3 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1211,16 +1211,17 @@ class KernelDoc: if export_symbol.search(line): symbol = export_symbol.group(2) - for suffix in suffixes: - symbol = symbol.removesuffix(suffix) - function_set.add(symbol) - return - - if export_symbol_ns.search(line): + elif export_symbol_ns.search(line): symbol = export_symbol_ns.group(2) - for suffix in suffixes: - symbol = symbol.removesuffix(suffix) - function_set.add(symbol) + else: + return False + # + # Found an export, trim out any special suffixes + # + for suffix in suffixes: + symbol = symbol.removesuffix(suffix) + function_set.add(symbol) + return True def process_normal(self, ln, line): """ @@ -1767,13 +1768,10 @@ class KernelDoc: # it was read twice. Here, we use the already-existing # loop to parse exported symbols as well. # - # TODO: It should be noticed that not all states are - # needed here. On a future cleanup, process export only - # at the states that aren't handling comment markups. - self.process_export(export_table, line) - - # Hand this line to the appropriate state handler - self.state_actions[self.state](self, ln, line) + if (self.state != state.NORMAL) or \ + not self.process_export(export_table, line): + # Hand this line to the appropriate state handler + self.state_actions[self.state](self, ln, line) except OSError: self.config.log.error(f"Error: Cannot open file {self.fname}") -- cgit v1.2.3 From dd49aae52b5e03bc151c65f0e8ee1731fdd73c0a Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Thu, 26 Jun 2025 13:38:05 -0600 Subject: docs: kdoc: remove the INLINE_END state It is never used, so just get rid of it. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 734b908579c3..03a0e44707a7 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -108,8 +108,7 @@ class state: INLINE_NA = 0 # not applicable ($state != INLINE) INLINE_NAME = 1 # looking for member name (@foo:) INLINE_TEXT = 2 # looking for member documentation - INLINE_END = 3 # done - INLINE_ERROR = 4 # error - Comment without header was found. + INLINE_ERROR = 3 # error - Comment without header was found. # Spit a warning as it's not # proper kernel-doc and ignore the rest. @@ -117,7 +116,6 @@ class state: "", "_NAME", "_TEXT", - "_END", "_ERROR", ] -- cgit v1.2.3 From 096f73ab01b95aaeaa7f678c56257d2e4c8490d3 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 27 Jun 2025 11:33:18 -0600 Subject: docs: kdoc: remove the inline states-within-a-state The processing of inline kerneldoc comments is a state like the rest, but it was implemented as a set of separate substates. Just remove the substate logic and make the inline states normal ones like the rest. INLINE_ERROR was never actually used for anything, so just take it out. No changes to the generated output. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 43 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 30 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 03a0e44707a7..a931c1471fa8 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -91,7 +91,8 @@ class state: SPECIAL_SECTION = 4 # doc section ending with a blank line PROTO = 5 # scanning prototype DOCBLOCK = 6 # documentation block - INLINE = 7 # gathering doc outside main block + INLINE_NAME = 7 # gathering doc outside main block + INLINE_TEXT = 8 # reading the body of inline docs name = [ "NORMAL", @@ -101,23 +102,10 @@ class state: "SPECIAL_SECTION", "PROTO", "DOCBLOCK", - "INLINE", + "INLINE_NAME", + "INLINE_TEXT", ] - # Inline documentation state - INLINE_NA = 0 # not applicable ($state != INLINE) - INLINE_NAME = 1 # looking for member name (@foo:) - INLINE_TEXT = 2 # looking for member documentation - INLINE_ERROR = 3 # error - Comment without header was found. - # Spit a warning as it's not - # proper kernel-doc and ignore the rest. - - inline_name = [ - "", - "_NAME", - "_TEXT", - "_ERROR", - ] SECTION_DEFAULT = "Description" # default section @@ -246,7 +234,6 @@ class KernelDoc: # Initial state for the state machines self.state = state.NORMAL - self.inline_doc_state = state.INLINE_NA # Store entry currently being processed self.entry = None @@ -323,7 +310,6 @@ class KernelDoc: # State flags self.state = state.NORMAL - self.inline_doc_state = state.INLINE_NA def push_parameter(self, ln, decl_type, param, dtype, org_arg, declaration_name): @@ -1465,30 +1451,28 @@ class KernelDoc: def process_inline(self, ln, line): """STATE_INLINE: docbook comments within a prototype.""" - if self.inline_doc_state == state.INLINE_NAME and \ + if self.state == state.INLINE_NAME and \ doc_inline_sect.search(line): self.entry.begin_section(ln, doc_inline_sect.group(1)) self.entry.add_text(doc_inline_sect.group(2).lstrip()) - self.inline_doc_state = state.INLINE_TEXT + self.state = state.INLINE_TEXT # Documentation block end */ return if doc_inline_end.search(line): self.dump_section() self.state = state.PROTO - self.inline_doc_state = state.INLINE_NA return if doc_content.search(line): - if self.inline_doc_state == state.INLINE_TEXT: + if self.state == state.INLINE_TEXT: self.entry.add_text(doc_content.group(1)) - elif self.inline_doc_state == state.INLINE_NAME: + elif self.state == state.INLINE_NAME: self.emit_msg(ln, f"Incorrect use of kernel-doc format: {line}") - - self.inline_doc_state = state.INLINE_ERROR + self.state = state.PROTO def syscall_munge(self, ln, proto): # pylint: disable=W0613 """ @@ -1664,8 +1648,7 @@ class KernelDoc: self.dump_section() elif doc_inline_start.search(line): - self.state = state.INLINE - self.inline_doc_state = state.INLINE_NAME + self.state = state.INLINE_NAME elif self.entry.decl_type == 'function': self.process_proto_function(ln, line) @@ -1716,7 +1699,8 @@ class KernelDoc: state.BODY: process_body, state.DECLARATION: process_decl, state.SPECIAL_SECTION: process_special, - state.INLINE: process_inline, + state.INLINE_NAME: process_inline, + state.INLINE_TEXT: process_inline, state.PROTO: process_proto, state.DOCBLOCK: process_docblock, } @@ -1756,9 +1740,8 @@ class KernelDoc: prev = "" prev_ln = None - self.config.log.debug("%d %s%s: %s", + self.config.log.debug("%d %s: %s", ln, state.name[self.state], - state.inline_name[self.inline_doc_state], line) # This is an optimization over the original script. -- cgit v1.2.3 From c7eedb09417e4372183bf1843676d2008da340d5 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 27 Jun 2025 12:23:05 -0600 Subject: docs: kdoc: split the processing of the two remaining inline states Now that "inline_*" are just ordinary parser states, split them into two separate functions, getting rid of some nested conditional logic. The original process_inline() would simply ignore lines that didn't match any of the regexes (those lacking the initial " * " marker). I have preserved that behavior, but we should perhaps emit a warning instead. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index a931c1471fa8..93938155fce2 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1448,31 +1448,30 @@ class KernelDoc: # Unknown line, ignore self.emit_msg(ln, f"bad line: {line}") - def process_inline(self, ln, line): - """STATE_INLINE: docbook comments within a prototype.""" + def process_inline_name(self, ln, line): + """STATE_INLINE_NAME: beginning of docbook comments within a prototype.""" - if self.state == state.INLINE_NAME and \ - doc_inline_sect.search(line): + if doc_inline_sect.search(line): self.entry.begin_section(ln, doc_inline_sect.group(1)) - self.entry.add_text(doc_inline_sect.group(2).lstrip()) self.state = state.INLINE_TEXT - # Documentation block end */ - return - - if doc_inline_end.search(line): + elif doc_inline_end.search(line): self.dump_section() self.state = state.PROTO - return + elif doc_content.search(line): + self.emit_msg(ln, f"Incorrect use of kernel-doc format: {line}") + self.state = state.PROTO + # else ... ?? - if doc_content.search(line): - if self.state == state.INLINE_TEXT: - self.entry.add_text(doc_content.group(1)) + def process_inline_text(self, ln, line): + """STATE_INLINE_TEXT: docbook comments within a prototype.""" - elif self.state == state.INLINE_NAME: - self.emit_msg(ln, - f"Incorrect use of kernel-doc format: {line}") - self.state = state.PROTO + if doc_inline_end.search(line): + self.dump_section() + self.state = state.PROTO + elif doc_content.search(line): + self.entry.add_text(doc_content.group(1)) + # else ... ?? def syscall_munge(self, ln, proto): # pylint: disable=W0613 """ @@ -1699,8 +1698,8 @@ class KernelDoc: state.BODY: process_body, state.DECLARATION: process_decl, state.SPECIAL_SECTION: process_special, - state.INLINE_NAME: process_inline, - state.INLINE_TEXT: process_inline, + state.INLINE_NAME: process_inline_name, + state.INLINE_TEXT: process_inline_text, state.PROTO: process_proto, state.DOCBLOCK: process_docblock, } -- cgit v1.2.3 From 362ec251a6aba32c8d950f0278c75aaa8c1b0b10 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Fri, 27 Jun 2025 13:08:20 -0600 Subject: docs: kdoc: don't reinvent string.strip() process_proto_type() and process_proto_function() reinventing the strip() string method with a whole series of separate regexes; take all that out and just use strip(). The previous implementation also (in process_proto_type()) removed C++ comments *after* the above dance, leaving trailing whitespace in that case; now we do the stripping afterward. This results in exactly one output change: the removal of a spurious space in the definition of BACKLIGHT_POWER_REDUCED - see https://docs.kernel.org/gpu/backlight.html#c.backlight_properties. I note that we are putting semicolons after #define lines that really shouldn't be there - a task for another day. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 93938155fce2..d9ff2d066160 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1567,17 +1567,9 @@ class KernelDoc: self.entry.prototype += r.group(1) + " " if '{' in line or ';' in line or KernRe(r'\s*#\s*define').match(line): - # strip comments - r = KernRe(r'/\*.*?\*/') - self.entry.prototype = r.sub('', self.entry.prototype) - - # strip newlines/cr's - r = KernRe(r'[\r\n]+') - self.entry.prototype = r.sub(' ', self.entry.prototype) - - # strip leading spaces - r = KernRe(r'^\s+') - self.entry.prototype = r.sub('', self.entry.prototype) + # strip comments and surrounding spaces + r = KernRe(r'/\*.*\*/') + self.entry.prototype = r.sub('', self.entry.prototype).strip() # Handle self.entry.prototypes for function pointers like: # int (*pcs_config)(struct foo) @@ -1600,17 +1592,8 @@ class KernelDoc: def process_proto_type(self, ln, line): """Ancillary routine to process a type""" - # Strip newlines/cr's. - line = KernRe(r'[\r\n]+', re.S).sub(' ', line) - - # Strip leading spaces - line = KernRe(r'^\s+', re.S).sub('', line) - - # Strip trailing spaces - line = KernRe(r'\s+$', re.S).sub('', line) - - # Strip C99-style comments to the end of the line - line = KernRe(r"\/\/.*$", re.S).sub('', line) + # Strip C99-style comments and surrounding whitespace + line = KernRe(r"//.*$", re.S).sub('', line).strip() # To distinguish preprocessor directive from regular declaration later. if line.startswith('#'): -- cgit v1.2.3 From bfa5bb3d104b0f2ffd25daa3b4900d54fe060285 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 30 Jun 2025 10:03:28 -0600 Subject: docs: kdoc: remove the brcount floor in process_proto_type() Putting the floor under brcount does not change the output in any way, just remove it. Change the termination test from ==0 to <=0 to prevent infinite loops in case somebody does something truly wacko in the code. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index d9ff2d066160..935f2a3c4b47 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1609,9 +1609,7 @@ class KernelDoc: self.entry.brcount += r.group(2).count('{') self.entry.brcount -= r.group(2).count('}') - self.entry.brcount = max(self.entry.brcount, 0) - - if r.group(2) == ';' and self.entry.brcount == 0: + if r.group(2) == ';' and self.entry.brcount <= 0: self.dump_declaration(ln, self.entry.prototype) self.reset_state(ln) break -- cgit v1.2.3 From 1aeb8099d053af79d50f4ffee740c29cc10d56fc Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 30 Jun 2025 11:08:32 -0600 Subject: docs: kdoc: rework type prototype parsing process_proto_type() is using a complex regex and a "while True" loop to split a declaration into chunks and, in the end, count brackets. Switch to using a simpler regex to just do the split directly, and handle each chunk as it comes. The result is, IMO, easier to understand and reason about. The old algorithm would occasionally elide the space between function parameters; see struct rng_alg->generate(), foe example. The only output difference is to not elide that space, which is more correct. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 43 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 935f2a3c4b47..61da297df623 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1594,30 +1594,37 @@ class KernelDoc: # Strip C99-style comments and surrounding whitespace line = KernRe(r"//.*$", re.S).sub('', line).strip() + if not line: + return # nothing to see here # To distinguish preprocessor directive from regular declaration later. if line.startswith('#'): line += ";" - - r = KernRe(r'([^\{\};]*)([\{\};])(.*)') - while True: - if r.search(line): - if self.entry.prototype: - self.entry.prototype += " " - self.entry.prototype += r.group(1) + r.group(2) - - self.entry.brcount += r.group(2).count('{') - self.entry.brcount -= r.group(2).count('}') - - if r.group(2) == ';' and self.entry.brcount <= 0: + # + # Split the declaration on any of { } or ;, and accumulate pieces + # until we hit a semicolon while not inside {brackets} + # + r = KernRe(r'(.*?)([{};])') + for chunk in r.split(line): + if chunk: # Ignore empty matches + self.entry.prototype += chunk + # + # This cries out for a match statement ... someday after we can + # drop Python 3.9 ... + # + if chunk == '{': + self.entry.brcount += 1 + elif chunk == '}': + self.entry.brcount -= 1 + elif chunk == ';' and self.entry.brcount <= 0: self.dump_declaration(ln, self.entry.prototype) self.reset_state(ln) - break - - line = r.group(3) - else: - self.entry.prototype += line - break + return + # + # We hit the end of the line while still in the declaration; put + # in a space to represent the newline. + # + self.entry.prototype += ' ' def process_proto(self, ln, line): """STATE_PROTO: reading a function/whatever prototype.""" -- cgit v1.2.3 From 901f506945b8d0a9386c126a2af6bec52354f7b3 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 30 Jun 2025 11:38:42 -0600 Subject: docs: kdoc: some tweaks to process_proto_function() Add a set of comments to process_proto_function and reorganize the logic slightly; no functional change. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 43 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 61da297df623..d5ef3ce87438 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1553,39 +1553,44 @@ class KernelDoc: """Ancillary routine to process a function prototype""" # strip C99-style comments to end of line - r = KernRe(r"\/\/.*$", re.S) - line = r.sub('', line) - + line = KernRe(r"\/\/.*$", re.S).sub('', line) + # + # Soak up the line's worth of prototype text, stopping at { or ; if present. + # if KernRe(r'\s*#\s*define').match(line): self.entry.prototype = line - elif line.startswith('#'): - # Strip other macros like #ifdef/#ifndef/#endif/... - pass - else: + elif not line.startswith('#'): # skip other preprocessor stuff r = KernRe(r'([^\{]*)') if r.match(line): self.entry.prototype += r.group(1) + " " - + # + # If we now have the whole prototype, clean it up and declare victory. + # if '{' in line or ';' in line or KernRe(r'\s*#\s*define').match(line): # strip comments and surrounding spaces - r = KernRe(r'/\*.*\*/') - self.entry.prototype = r.sub('', self.entry.prototype).strip() - + self.entry.prototype = KernRe(r'/\*.*\*/').sub('', self.entry.prototype).strip() + # # Handle self.entry.prototypes for function pointers like: # int (*pcs_config)(struct foo) - + # by turning it into + # int pcs_config(struct foo) + # r = KernRe(r'^(\S+\s+)\(\s*\*(\S+)\)') self.entry.prototype = r.sub(r'\1\2', self.entry.prototype) - + # + # Handle special declaration syntaxes + # if 'SYSCALL_DEFINE' in self.entry.prototype: self.entry.prototype = self.syscall_munge(ln, self.entry.prototype) - - r = KernRe(r'TRACE_EVENT|DEFINE_EVENT|DEFINE_SINGLE_EVENT') - if r.search(self.entry.prototype): - self.entry.prototype = self.tracepoint_munge(ln, - self.entry.prototype) - + else: + r = KernRe(r'TRACE_EVENT|DEFINE_EVENT|DEFINE_SINGLE_EVENT') + if r.search(self.entry.prototype): + self.entry.prototype = self.tracepoint_munge(ln, + self.entry.prototype) + # + # ... and we're done + # self.dump_function(ln, self.entry.prototype) self.reset_state(ln) -- cgit v1.2.3 From d1af2889682e83acc791e2a2191687958b548da1 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Tue, 1 Jul 2025 13:02:54 -0600 Subject: docs: kdoc: pretty up dump_enum() Add some comments to dump_enum to help the next person who has to figure out what it is actually doing. Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index d5ef3ce87438..831f061f61b8 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -860,39 +860,48 @@ class KernelDoc: # Strip #define macros inside enums proto = KernRe(r'#\s*((define|ifdef|if)\s+|endif)[^;]*;', flags=re.S).sub('', proto) - members = None - declaration_name = None - + # + # Parse out the name and members of the enum. Typedef form first. + # r = KernRe(r'typedef\s+enum\s*\{(.*)\}\s*(\w*)\s*;') if r.search(proto): declaration_name = r.group(2) members = r.group(1).rstrip() + # + # Failing that, look for a straight enum + # else: r = KernRe(r'enum\s+(\w*)\s*\{(.*)\}') if r.match(proto): declaration_name = r.group(1) members = r.group(2).rstrip() - - if not members: - self.emit_msg(ln, f"{proto}: error: Cannot parse enum!") - return - + # + # OK, this isn't going to work. + # + else: + self.emit_msg(ln, f"{proto}: error: Cannot parse enum!") + return + # + # Make sure we found what we were expecting. + # if self.entry.identifier != declaration_name: if self.entry.identifier == "": self.emit_msg(ln, f"{proto}: wrong kernel-doc identifier on prototype") else: self.emit_msg(ln, - f"expecting prototype for enum {self.entry.identifier}. Prototype was for enum {declaration_name} instead") + f"expecting prototype for enum {self.entry.identifier}. " + f"Prototype was for enum {declaration_name} instead") return if not declaration_name: declaration_name = "(anonymous)" - + # + # Parse out the name of each enum member, and verify that we + # have a description for it. + # member_set = set() - - members = KernRe(r'\([^;]*?[\)]').sub('', members) - + members = KernRe(r'\([^;)]*\)').sub('', members) for arg in members.split(','): if not arg: continue @@ -903,7 +912,9 @@ class KernelDoc: self.emit_msg(ln, f"Enum value '{arg}' not described in enum '{declaration_name}'") member_set.add(arg) - + # + # Ensure that every described member actually exists in the enum. + # for k in self.entry.parameterdescs: if k not in member_set: self.emit_msg(ln, -- cgit v1.2.3 From 60016e0116b8d33f95e797b011799e717766ec13 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Tue, 1 Jul 2025 15:31:11 -0600 Subject: docs: kdoc; Add a rudimentary class to represent output items This class is intended to replace the unstructured dict used to accumulate an entry to pass to an output module. For now, it remains unstructured, but it works well enough that the output classes don't notice the difference. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_item.py | 26 ++++++++++++++++++++++++++ scripts/lib/kdoc/kdoc_parser.py | 30 +++++++++--------------------- 2 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 scripts/lib/kdoc/kdoc_item.py (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_item.py b/scripts/lib/kdoc/kdoc_item.py new file mode 100644 index 000000000000..add2cc772fec --- /dev/null +++ b/scripts/lib/kdoc/kdoc_item.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# A class that will, eventually, encapsulate all of the parsed data that we +# then pass into the output modules. +# + +class KdocItem: + def __init__(self, name, type, start_line, **other_stuff): + self.name = name + self.type = type + self.declaration_start_line = start_line + # + # Just save everything else into our own dict so that the output + # side can grab it directly as before. As we move things into more + # structured data, this will, hopefully, fade away. + # + self.other_stuff = other_stuff + + def get(self, key, default = None): + ret = self.other_stuff.get(key, default) + if ret == default: + return self.__dict__.get(key, default) + return ret + + def __getitem__(self, key): + return self.get(key) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 831f061f61b8..a5a59b97a444 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -16,7 +16,7 @@ import re from pprint import pformat from kdoc_re import NestedMatch, KernRe - +from kdoc_item import KdocItem # # Regular expressions used to parse kernel-doc markups at KernelDoc class. @@ -271,32 +271,20 @@ class KernelDoc: The actual output and output filters will be handled elsewhere """ - # The implementation here is different than the original kernel-doc: - # instead of checking for output filters or actually output anything, - # it just stores the declaration content at self.entries, as the - # output will happen on a separate class. - # - # For now, we're keeping the same name of the function just to make - # easier to compare the source code of both scripts - - args["declaration_start_line"] = self.entry.declaration_start_line - args["type"] = dtype - args["warnings"] = self.entry.warnings - - # TODO: use colletions.OrderedDict to remove sectionlist + item = KdocItem(name, dtype, self.entry.declaration_start_line, **args) + item.warnings = self.entry.warnings - sections = args.get('sections', {}) - sectionlist = args.get('sectionlist', []) + sections = item.get('sections', {}) + sectionlist = item.get('sectionlist', []) # Drop empty sections # TODO: improve empty sections logic to emit warnings for section in ["Description", "Return"]: - if section in sectionlist: - if not sections[section].rstrip(): - del sections[section] - sectionlist.remove(section) + if section in sectionlist and not sections[section].rstrip(): + del sections[section] + sectionlist.remove(section) - self.entries.append((name, args)) + self.entries.append((name, item)) self.config.log.debug("Output: %s:%s = %s", dtype, name, pformat(args)) -- cgit v1.2.3 From 703f9074a8e10ac3fe939025233acb7c47529608 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Tue, 1 Jul 2025 15:54:09 -0600 Subject: docs: kdoc: simplify the output-item passing Since our output items contain their name, we don't need to pass it separately. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_files.py | 4 ++-- scripts/lib/kdoc/kdoc_parser.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 9be4a64df71d..9e09b45b02fa 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -275,8 +275,8 @@ class KernelFiles(): self.config.log.warning("No kernel-doc for file %s", fname) continue - for name, arg in self.results[fname]: - m = self.out_msg(fname, name, arg) + for arg in self.results[fname]: + m = self.out_msg(fname, arg.name, arg) if m is None: ln = arg.get("ln", 0) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index a5a59b97a444..97380ff30a0d 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -284,7 +284,7 @@ class KernelDoc: del sections[section] sectionlist.remove(section) - self.entries.append((name, item)) + self.entries.append(item) self.config.log.debug("Output: %s:%s = %s", dtype, name, pformat(args)) -- cgit v1.2.3 From 8d9d122915492ea6984f32e5df30cef5c582f062 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Tue, 1 Jul 2025 16:21:24 -0600 Subject: docs: kdoc: drop "sectionlist" Python dicts (as of 3.7) are guaranteed to remember the insertion order of items, so we do not need a separate list for that purpose. Drop the per-entry sectionlist variable and just rely on native dict ordering. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_output.py | 18 ++++++------------ scripts/lib/kdoc/kdoc_parser.py | 13 +------------ 2 files changed, 7 insertions(+), 24 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 86102e628d91..4895c80e4b81 100644 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -339,11 +339,10 @@ class RestFormat(OutputFormat): tends to duplicate a header already in the template file. """ - sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) section_start_lines = args.get('section_start_lines', {}) - for section in sectionlist: + for section in sections: # Skip sections that are in the nosymbol_table if section in self.nosymbol: continue @@ -636,7 +635,6 @@ class ManFormat(OutputFormat): self.data += line + "\n" def out_doc(self, fname, name, args): - sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) if not self.check_doc(name, args): @@ -644,7 +642,7 @@ class ManFormat(OutputFormat): self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" - for section in sectionlist: + for section in sections: self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) @@ -653,7 +651,6 @@ class ManFormat(OutputFormat): parameterlist = args.get('parameterlist', []) parameterdescs = args.get('parameterdescs', {}) - sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" @@ -695,7 +692,7 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name, "")) - for section in sectionlist: + for section in sections: self.data += f'.SH "{section.upper()}"' + "\n" self.output_highlight(sections[section]) @@ -703,7 +700,6 @@ class ManFormat(OutputFormat): name = args.get('enum', '') parameterlist = args.get('parameterlist', []) - sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -731,7 +727,7 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(args['parameterdescs'].get(parameter_name, "")) - for section in sectionlist: + for section in sections: self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections[section]) @@ -739,7 +735,6 @@ class ManFormat(OutputFormat): module = self.modulename typedef = args.get('typedef') purpose = args.get('purpose') - sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -747,7 +742,7 @@ class ManFormat(OutputFormat): self.data += ".SH NAME\n" self.data += f"typedef {typedef} \\- {purpose}\n" - for section in sectionlist: + for section in sections: self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) @@ -757,7 +752,6 @@ class ManFormat(OutputFormat): struct_name = args.get('struct') purpose = args.get('purpose') definition = args.get('definition') - sectionlist = args.get('sectionlist', []) parameterlist = args.get('parameterlist', []) sections = args.get('sections', {}) parameterdescs = args.get('parameterdescs', {}) @@ -788,6 +782,6 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name)) - for section in sectionlist: + for section in sections: self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 97380ff30a0d..2e00c8b3a5f2 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -127,7 +127,6 @@ class KernelEntry: self.parameterdesc_start_lines = {} self.section_start_lines = {} - self.sectionlist = [] self.sections = {} self.anon_struct_union = False @@ -202,7 +201,6 @@ class KernelEntry: self.sections[name] += '\n' + contents else: self.sections[name] = contents - self.sectionlist.append(name) self.section_start_lines[name] = self.new_start_line self.new_start_line = 0 @@ -275,14 +273,12 @@ class KernelDoc: item.warnings = self.entry.warnings sections = item.get('sections', {}) - sectionlist = item.get('sectionlist', []) # Drop empty sections # TODO: improve empty sections logic to emit warnings for section in ["Description", "Return"]: - if section in sectionlist and not sections[section].rstrip(): + if section in sections and not sections[section].rstrip(): del sections[section] - sectionlist.remove(section) self.entries.append(item) @@ -828,7 +824,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) @@ -913,7 +908,6 @@ class KernelDoc: parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) @@ -1085,7 +1079,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose, @@ -1099,7 +1092,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose, @@ -1145,7 +1137,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) @@ -1168,7 +1159,6 @@ class KernelDoc: self.output_declaration('typedef', declaration_name, typedef=declaration_name, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) @@ -1653,7 +1643,6 @@ class KernelDoc: if doc_end.search(line): self.dump_section() self.output_declaration("doc", self.entry.identifier, - sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines) self.reset_state(ln) -- cgit v1.2.3 From 8d7338752d76c3854a5c54cf7df976c539baab5b Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Tue, 1 Jul 2025 16:47:59 -0600 Subject: docs: kdoc: Centralize handling of the item section list The section list always comes directly from the under-construction entry and is used uniformly. Formalize section handling in the KdocItem class, and have output_declaration() load the sections directly from the entry, eliminating a lot of duplicated, verbose parameters. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_item.py | 9 +++++++++ scripts/lib/kdoc/kdoc_output.py | 36 +++++++++++++----------------------- scripts/lib/kdoc/kdoc_parser.py | 20 +++----------------- 3 files changed, 25 insertions(+), 40 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_item.py b/scripts/lib/kdoc/kdoc_item.py index add2cc772fec..f0b2b9082c56 100644 --- a/scripts/lib/kdoc/kdoc_item.py +++ b/scripts/lib/kdoc/kdoc_item.py @@ -9,6 +9,8 @@ class KdocItem: self.name = name self.type = type self.declaration_start_line = start_line + self.sections = {} + self.sections_start_lines = {} # # Just save everything else into our own dict so that the output # side can grab it directly as before. As we move things into more @@ -24,3 +26,10 @@ class KdocItem: def __getitem__(self, key): return self.get(key) + + # + # Tracking of section information. + # + def set_sections(self, sections, start_lines): + self.sections = sections + self.section_start_lines = start_lines diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 4895c80e4b81..15cb89f91987 100644 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -338,11 +338,7 @@ class RestFormat(OutputFormat): starts by putting out the name of the doc section itself, but that tends to duplicate a header already in the template file. """ - - sections = args.get('sections', {}) - section_start_lines = args.get('section_start_lines', {}) - - for section in sections: + for section, text in args.sections.items(): # Skip sections that are in the nosymbol_table if section in self.nosymbol: continue @@ -354,8 +350,8 @@ class RestFormat(OutputFormat): else: self.data += f'{self.lineprefix}**{section}**\n\n' - self.print_lineno(section_start_lines.get(section, 0)) - self.output_highlight(sections[section]) + self.print_lineno(args.section_start_lines.get(section, 0)) + self.output_highlight(text) self.data += "\n" self.data += "\n" @@ -635,23 +631,20 @@ class ManFormat(OutputFormat): self.data += line + "\n" def out_doc(self, fname, name, args): - sections = args.get('sections', {}) - if not self.check_doc(name, args): return self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" - for section in sections: + for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" - self.output_highlight(sections.get(section)) + self.output_highlight(text) def out_function(self, fname, name, args): """output function in man""" parameterlist = args.get('parameterlist', []) parameterdescs = args.get('parameterdescs', {}) - sections = args.get('sections', {}) self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" @@ -692,15 +685,14 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name, "")) - for section in sections: + for section, text in args.sections.items(): self.data += f'.SH "{section.upper()}"' + "\n" - self.output_highlight(sections[section]) + self.output_highlight(text) def out_enum(self, fname, name, args): name = args.get('enum', '') parameterlist = args.get('parameterlist', []) - sections = args.get('sections', {}) self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -727,24 +719,23 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(args['parameterdescs'].get(parameter_name, "")) - for section in sections: + for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" - self.output_highlight(sections[section]) + self.output_highlight(text) def out_typedef(self, fname, name, args): module = self.modulename typedef = args.get('typedef') purpose = args.get('purpose') - sections = args.get('sections', {}) self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" self.data += ".SH NAME\n" self.data += f"typedef {typedef} \\- {purpose}\n" - for section in sections: + for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" - self.output_highlight(sections.get(section)) + self.output_highlight(text) def out_struct(self, fname, name, args): module = self.modulename @@ -753,7 +744,6 @@ class ManFormat(OutputFormat): purpose = args.get('purpose') definition = args.get('definition') parameterlist = args.get('parameterlist', []) - sections = args.get('sections', {}) parameterdescs = args.get('parameterdescs', {}) self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -782,6 +772,6 @@ class ManFormat(OutputFormat): self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name)) - for section in sections: + for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" - self.output_highlight(sections.get(section)) + self.output_highlight(text) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 2e00c8b3a5f2..608f3a1045dc 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -272,13 +272,13 @@ class KernelDoc: item = KdocItem(name, dtype, self.entry.declaration_start_line, **args) item.warnings = self.entry.warnings - sections = item.get('sections', {}) - # Drop empty sections # TODO: improve empty sections logic to emit warnings + sections = self.entry.sections for section in ["Description", "Return"]: if section in sections and not sections[section].rstrip(): del sections[section] + item.set_sections(sections, self.entry.section_start_lines) self.entries.append(item) @@ -824,8 +824,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) def dump_enum(self, ln, proto): @@ -908,8 +906,6 @@ class KernelDoc: parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) def dump_declaration(self, ln, prototype): @@ -1079,8 +1075,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose, func_macro=func_macro) else: @@ -1092,8 +1086,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose, func_macro=func_macro) @@ -1137,8 +1129,6 @@ class KernelDoc: parameterdescs=self.entry.parameterdescs, parametertypes=self.entry.parametertypes, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) return @@ -1159,8 +1149,6 @@ class KernelDoc: self.output_declaration('typedef', declaration_name, typedef=declaration_name, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, purpose=self.entry.declaration_purpose) return @@ -1642,9 +1630,7 @@ class KernelDoc: if doc_end.search(line): self.dump_section() - self.output_declaration("doc", self.entry.identifier, - sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines) + self.output_declaration("doc", self.entry.identifier) self.reset_state(ln) elif doc_content.search(line): -- cgit v1.2.3 From 172bee3376ab29fbf38b09bf01d6f06f7f6c39e1 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 11:04:43 -0600 Subject: docs: kdoc: remove the "struct_actual" machinery The code goes out of its way to create a special list of parameters in entry.struct_actual that is just like entry.parameterlist, but with extra junk. The only use of that information, in check_sections(), promptly strips all the extra junk back out. Drop all that extra work and just use parameterlist. No output changes. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 608f3a1045dc..b28f056365cb 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -116,7 +116,6 @@ class KernelEntry: self._contents = [] self.sectcheck = "" - self.struct_actual = "" self.prototype = "" self.warnings = [] @@ -366,15 +365,6 @@ class KernelDoc: org_arg = KernRe(r'\s\s+').sub(' ', org_arg) self.entry.parametertypes[param] = org_arg - def save_struct_actual(self, actual): - """ - Strip all spaces from the actual param so that it looks like - one string item. - """ - - actual = KernRe(r'\s*').sub("", actual, count=1) - - self.entry.struct_actual += actual + " " def create_parameter_list(self, ln, decl_type, args, splitter, declaration_name): @@ -420,7 +410,6 @@ class KernelDoc: param = arg dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) - self.save_struct_actual(param) self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name) @@ -437,7 +426,6 @@ class KernelDoc: dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) - self.save_struct_actual(param) self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name) @@ -470,7 +458,6 @@ class KernelDoc: param = r.group(1) - self.save_struct_actual(r.group(2)) self.push_parameter(ln, decl_type, r.group(2), f"{dtype} {r.group(1)}", arg, declaration_name) @@ -482,12 +469,10 @@ class KernelDoc: continue if dtype != "": # Skip unnamed bit-fields - self.save_struct_actual(r.group(1)) self.push_parameter(ln, decl_type, r.group(1), f"{dtype}:{r.group(2)}", arg, declaration_name) else: - self.save_struct_actual(param) self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name) @@ -499,24 +484,11 @@ class KernelDoc: sects = sectcheck.split() prms = prmscheck.split() - err = False for sx in range(len(sects)): # pylint: disable=C0200 err = True for px in range(len(prms)): # pylint: disable=C0200 - prm_clean = prms[px] - prm_clean = KernRe(r'\[.*\]').sub('', prm_clean) - prm_clean = attribute.sub('', prm_clean) - - # ignore array size in a parameter string; - # however, the original param string may contain - # spaces, e.g.: addr[6 + 2] - # and this appears in @prms as "addr[6" since the - # parameter list is split at spaces; - # hence just ignore "[..." for the sections check; - prm_clean = KernRe(r'\[.*').sub('', prm_clean) - - if prm_clean == sects[sx]: + if prms[px] == sects[sx]: err = False break @@ -782,7 +754,7 @@ class KernelDoc: self.create_parameter_list(ln, decl_type, members, ';', declaration_name) self.check_sections(ln, declaration_name, decl_type, - self.entry.sectcheck, self.entry.struct_actual) + self.entry.sectcheck, ' '.join(self.entry.parameterlist)) # Adjust declaration for better display declaration = KernRe(r'([\{;])').sub(r'\1\n', declaration) -- cgit v1.2.3 From efacdf85135ae02a8c25452e40547b773bb1b6b3 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 11:12:27 -0600 Subject: docs: kdoc: use self.entry.parameterlist directly in check_sections() Callers of check_sections() join parameterlist into a single string, which is then immediately split back into the original list. Rather than do all that, just use parameterlist directly in check_sections(). Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index b28f056365cb..ffd49f9395ae 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -476,19 +476,18 @@ class KernelDoc: self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name) - def check_sections(self, ln, decl_name, decl_type, sectcheck, prmscheck): + def check_sections(self, ln, decl_name, decl_type, sectcheck): """ Check for errors inside sections, emitting warnings if not found parameters are described. """ sects = sectcheck.split() - prms = prmscheck.split() for sx in range(len(sects)): # pylint: disable=C0200 err = True - for px in range(len(prms)): # pylint: disable=C0200 - if prms[px] == sects[sx]: + for param in self.entry.parameterlist: + if param == sects[sx]: err = False break @@ -753,8 +752,7 @@ class KernelDoc: self.create_parameter_list(ln, decl_type, members, ';', declaration_name) - self.check_sections(ln, declaration_name, decl_type, - self.entry.sectcheck, ' '.join(self.entry.parameterlist)) + self.check_sections(ln, declaration_name, decl_type, self.entry.sectcheck) # Adjust declaration for better display declaration = KernRe(r'([\{;])').sub(r'\1\n', declaration) @@ -1032,9 +1030,7 @@ class KernelDoc: f"expecting prototype for {self.entry.identifier}(). Prototype was for {declaration_name}() instead") return - prms = " ".join(self.entry.parameterlist) - self.check_sections(ln, declaration_name, "function", - self.entry.sectcheck, prms) + self.check_sections(ln, declaration_name, "function", self.entry.sectcheck) self.check_return_section(ln, declaration_name, return_type) -- cgit v1.2.3 From de6f7ac91a08d723a6eaa9c5bbce30c5a126c861 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 13:05:56 -0600 Subject: docs: kdoc: Coalesce parameter-list handling Callers to output_declaration() always pass the parameter information from self.entry; remove all of the boilerplate arguments and just get at that information directly. Formalize its placement in the KdocItem class. It would be nice to get rid of parameterlist as well, but that has the effect of reordering the output of function parameters and struct fields to match the order in the kerneldoc comment rather than in the declaration. One could argue about which is more correct, but the ordering has been left unchanged for now. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_item.py | 12 ++++++- scripts/lib/kdoc/kdoc_output.py | 75 ++++++++++++++++------------------------- scripts/lib/kdoc/kdoc_parser.py | 23 ++----------- 3 files changed, 43 insertions(+), 67 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_item.py b/scripts/lib/kdoc/kdoc_item.py index f0b2b9082c56..ccabb448b5d4 100644 --- a/scripts/lib/kdoc/kdoc_item.py +++ b/scripts/lib/kdoc/kdoc_item.py @@ -11,6 +11,10 @@ class KdocItem: self.declaration_start_line = start_line self.sections = {} self.sections_start_lines = {} + self.parameterlist = [] + self.parameterdesc_start_lines = [] + self.parameterdescs = {} + self.parametertypes = {} # # Just save everything else into our own dict so that the output # side can grab it directly as before. As we move things into more @@ -28,8 +32,14 @@ class KdocItem: return self.get(key) # - # Tracking of section information. + # Tracking of section and parameter information. # def set_sections(self, sections, start_lines): self.sections = sections self.section_start_lines = start_lines + + def set_params(self, names, descs, types, starts): + self.parameterlist = names + self.parameterdescs = descs + self.parametertypes = types + self.parameterdesc_start_lines = starts diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 15cb89f91987..d6f4d9e7173b 100644 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -373,18 +373,13 @@ class RestFormat(OutputFormat): signature = args['functiontype'] + " " signature += args['function'] + " (" - parameterlist = args.get('parameterlist', []) - parameterdescs = args.get('parameterdescs', {}) - parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) - ln = args.get('declaration_start_line', 0) - count = 0 - for parameter in parameterlist: + for parameter in args.parameterlist: if count != 0: signature += ", " count += 1 - dtype = args['parametertypes'].get(parameter, "") + dtype = args.parametertypes.get(parameter, "") if function_pointer.search(dtype): signature += function_pointer.group(1) + parameter + function_pointer.group(3) @@ -419,26 +414,26 @@ class RestFormat(OutputFormat): # function prototypes apart self.lineprefix = " " - if parameterlist: + if args.parameterlist: self.data += ".. container:: kernelindent\n\n" self.data += f"{self.lineprefix}**Parameters**\n\n" - for parameter in parameterlist: + for parameter in args.parameterlist: parameter_name = KernRe(r'\[.*').sub('', parameter) - dtype = args['parametertypes'].get(parameter, "") + dtype = args.parametertypes.get(parameter, "") if dtype: self.data += f"{self.lineprefix}``{dtype}``\n" else: self.data += f"{self.lineprefix}``{parameter}``\n" - self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) + self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) self.lineprefix = " " - if parameter_name in parameterdescs and \ - parameterdescs[parameter_name] != KernelDoc.undescribed: + if parameter_name in args.parameterdescs and \ + args.parameterdescs[parameter_name] != KernelDoc.undescribed: - self.output_highlight(parameterdescs[parameter_name]) + self.output_highlight(args.parameterdescs[parameter_name]) self.data += "\n" else: self.data += f"{self.lineprefix}*undescribed*\n\n" @@ -451,8 +446,6 @@ class RestFormat(OutputFormat): oldprefix = self.lineprefix name = args.get('enum', '') - parameterlist = args.get('parameterlist', []) - parameterdescs = args.get('parameterdescs', {}) ln = args.get('declaration_start_line', 0) self.data += f"\n\n.. c:enum:: {name}\n\n" @@ -467,11 +460,11 @@ class RestFormat(OutputFormat): self.lineprefix = outer + " " self.data += f"{outer}**Constants**\n\n" - for parameter in parameterlist: + for parameter in args.parameterlist: self.data += f"{outer}``{parameter}``\n" - if parameterdescs.get(parameter, '') != KernelDoc.undescribed: - self.output_highlight(parameterdescs[parameter]) + if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed: + self.output_highlight(args.parameterdescs[parameter]) else: self.data += f"{self.lineprefix}*undescribed*\n\n" self.data += "\n" @@ -505,10 +498,6 @@ class RestFormat(OutputFormat): dtype = args.get('type', "struct") ln = args.get('declaration_start_line', 0) - parameterlist = args.get('parameterlist', []) - parameterdescs = args.get('parameterdescs', {}) - parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) - self.data += f"\n\n.. c:{dtype}:: {name}\n\n" self.print_lineno(ln) @@ -531,21 +520,21 @@ class RestFormat(OutputFormat): self.lineprefix = " " self.data += f"{self.lineprefix}**Members**\n\n" - for parameter in parameterlist: + for parameter in args.parameterlist: if not parameter or parameter.startswith("#"): continue parameter_name = parameter.split("[", maxsplit=1)[0] - if parameterdescs.get(parameter_name) == KernelDoc.undescribed: + if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: continue - self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) + self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) self.data += f"{self.lineprefix}``{parameter}``\n" self.lineprefix = " " - self.output_highlight(parameterdescs[parameter_name]) + self.output_highlight(args.parameterdescs[parameter_name]) self.lineprefix = " " self.data += "\n" @@ -643,9 +632,6 @@ class ManFormat(OutputFormat): def out_function(self, fname, name, args): """output function in man""" - parameterlist = args.get('parameterlist', []) - parameterdescs = args.get('parameterdescs', {}) - self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" self.data += ".SH NAME\n" @@ -661,11 +647,11 @@ class ManFormat(OutputFormat): parenth = "(" post = "," - for parameter in parameterlist: - if count == len(parameterlist) - 1: + for parameter in args.parameterlist: + if count == len(args.parameterlist) - 1: post = ");" - dtype = args['parametertypes'].get(parameter, "") + dtype = args.parametertypes.get(parameter, "") if function_pointer.match(dtype): # Pointer-to-function self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" @@ -676,14 +662,14 @@ class ManFormat(OutputFormat): count += 1 parenth = "" - if parameterlist: + if args.parameterlist: self.data += ".SH ARGUMENTS\n" - for parameter in parameterlist: + for parameter in args.parameterlist: parameter_name = re.sub(r'\[.*', '', parameter) self.data += f'.IP "{parameter}" 12' + "\n" - self.output_highlight(parameterdescs.get(parameter_name, "")) + self.output_highlight(args.parameterdescs.get(parameter_name, "")) for section, text in args.sections.items(): self.data += f'.SH "{section.upper()}"' + "\n" @@ -692,7 +678,6 @@ class ManFormat(OutputFormat): def out_enum(self, fname, name, args): name = args.get('enum', '') - parameterlist = args.get('parameterlist', []) self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -703,9 +688,9 @@ class ManFormat(OutputFormat): self.data += f"enum {args['enum']}" + " {\n" count = 0 - for parameter in parameterlist: + for parameter in args.parameterlist: self.data += f'.br\n.BI " {parameter}"' + "\n" - if count == len(parameterlist) - 1: + if count == len(args.parameterlist) - 1: self.data += "\n};\n" else: self.data += ", \n.br\n" @@ -714,10 +699,10 @@ class ManFormat(OutputFormat): self.data += ".SH Constants\n" - for parameter in parameterlist: + for parameter in args.parameterlist: parameter_name = KernRe(r'\[.*').sub('', parameter) self.data += f'.IP "{parameter}" 12' + "\n" - self.output_highlight(args['parameterdescs'].get(parameter_name, "")) + self.output_highlight(args.parameterdescs.get(parameter_name, "")) for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" @@ -743,8 +728,6 @@ class ManFormat(OutputFormat): struct_name = args.get('struct') purpose = args.get('purpose') definition = args.get('definition') - parameterlist = args.get('parameterlist', []) - parameterdescs = args.get('parameterdescs', {}) self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" @@ -760,17 +743,17 @@ class ManFormat(OutputFormat): self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" self.data += ".SH Members\n" - for parameter in parameterlist: + for parameter in args.parameterlist: if parameter.startswith("#"): continue parameter_name = re.sub(r"\[.*", "", parameter) - if parameterdescs.get(parameter_name) == KernelDoc.undescribed: + if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: continue self.data += f'.IP "{parameter}" 12' + "\n" - self.output_highlight(parameterdescs.get(parameter_name)) + self.output_highlight(args.parameterdescs.get(parameter_name)) for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index ffd49f9395ae..298abd260264 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -278,7 +278,9 @@ class KernelDoc: if section in sections and not sections[section].rstrip(): del sections[section] item.set_sections(sections, self.entry.section_start_lines) - + item.set_params(self.entry.parameterlist, self.entry.parameterdescs, + self.entry.parametertypes, + self.entry.parameterdesc_start_lines) self.entries.append(item) self.config.log.debug("Output: %s:%s = %s", dtype, name, pformat(args)) @@ -790,10 +792,6 @@ class KernelDoc: self.output_declaration(decl_type, declaration_name, struct=declaration_name, definition=declaration, - parameterlist=self.entry.parameterlist, - parameterdescs=self.entry.parameterdescs, - parametertypes=self.entry.parametertypes, - parameterdesc_start_lines=self.entry.parameterdesc_start_lines, purpose=self.entry.declaration_purpose) def dump_enum(self, ln, proto): @@ -873,9 +871,6 @@ class KernelDoc: self.output_declaration('enum', declaration_name, enum=declaration_name, - parameterlist=self.entry.parameterlist, - parameterdescs=self.entry.parameterdescs, - parameterdesc_start_lines=self.entry.parameterdesc_start_lines, purpose=self.entry.declaration_purpose) def dump_declaration(self, ln, prototype): @@ -1039,10 +1034,6 @@ class KernelDoc: function=declaration_name, typedef=True, functiontype=return_type, - parameterlist=self.entry.parameterlist, - parameterdescs=self.entry.parameterdescs, - parametertypes=self.entry.parametertypes, - parameterdesc_start_lines=self.entry.parameterdesc_start_lines, purpose=self.entry.declaration_purpose, func_macro=func_macro) else: @@ -1050,10 +1041,6 @@ class KernelDoc: function=declaration_name, typedef=False, functiontype=return_type, - parameterlist=self.entry.parameterlist, - parameterdescs=self.entry.parameterdescs, - parametertypes=self.entry.parametertypes, - parameterdesc_start_lines=self.entry.parameterdesc_start_lines, purpose=self.entry.declaration_purpose, func_macro=func_macro) @@ -1093,10 +1080,6 @@ class KernelDoc: function=declaration_name, typedef=True, functiontype=return_type, - parameterlist=self.entry.parameterlist, - parameterdescs=self.entry.parameterdescs, - parametertypes=self.entry.parametertypes, - parameterdesc_start_lines=self.entry.parameterdesc_start_lines, purpose=self.entry.declaration_purpose) return -- cgit v1.2.3 From a0db2051d7e1fca9a63a8643f1f187ff0b5931f1 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 13:17:59 -0600 Subject: docs: kdoc: Regularize the use of the declaration name Each declaration type passes through the name in a unique field of the "args" blob - even though we have always just passed the name separately. Get rid of all the weird names and just use the common version. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_output.py | 39 +++++++++++++++------------------------ scripts/lib/kdoc/kdoc_parser.py | 6 ------ 2 files changed, 15 insertions(+), 30 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index d6f4d9e7173b..8a31b637ffd2 100644 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -367,11 +367,11 @@ class RestFormat(OutputFormat): func_macro = args.get('func_macro', False) if func_macro: - signature = args['function'] + signature = name else: if args.get('functiontype'): signature = args['functiontype'] + " " - signature += args['function'] + " (" + signature += name + " (" ln = args.get('declaration_start_line', 0) count = 0 @@ -391,7 +391,7 @@ class RestFormat(OutputFormat): self.print_lineno(ln) if args.get('typedef') or not args.get('functiontype'): - self.data += f".. c:macro:: {args['function']}\n\n" + self.data += f".. c:macro:: {name}\n\n" if args.get('typedef'): self.data += " **Typedef**: " @@ -445,7 +445,6 @@ class RestFormat(OutputFormat): def out_enum(self, fname, name, args): oldprefix = self.lineprefix - name = args.get('enum', '') ln = args.get('declaration_start_line', 0) self.data += f"\n\n.. c:enum:: {name}\n\n" @@ -475,7 +474,6 @@ class RestFormat(OutputFormat): def out_typedef(self, fname, name, args): oldprefix = self.lineprefix - name = args.get('typedef', '') ln = args.get('declaration_start_line', 0) self.data += f"\n\n.. c:type:: {name}\n\n" @@ -492,7 +490,6 @@ class RestFormat(OutputFormat): def out_struct(self, fname, name, args): - name = args.get('struct', "") purpose = args.get('purpose', "") declaration = args.get('definition', "") dtype = args.get('type', "struct") @@ -632,16 +629,16 @@ class ManFormat(OutputFormat): def out_function(self, fname, name, args): """output function in man""" - self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" + self.data += f'.TH "{name}" 9 "{name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" self.data += ".SH NAME\n" - self.data += f"{args['function']} \\- {args['purpose']}\n" + self.data += f"{name} \\- {args['purpose']}\n" self.data += ".SH SYNOPSIS\n" if args.get('functiontype', ''): - self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n" + self.data += f'.B "{args["functiontype"]}" {name}' + "\n" else: - self.data += f'.B "{args["function"]}' + "\n" + self.data += f'.B "{name}' + "\n" count = 0 parenth = "(" @@ -676,16 +673,13 @@ class ManFormat(OutputFormat): self.output_highlight(text) def out_enum(self, fname, name, args): - - name = args.get('enum', '') - - self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" + self.data += f'.TH "{self.modulename}" 9 "enum {name}" "{self.man_date}" "API Manual" LINUX' + "\n" self.data += ".SH NAME\n" - self.data += f"enum {args['enum']} \\- {args['purpose']}\n" + self.data += f"enum {name} \\- {args['purpose']}\n" self.data += ".SH SYNOPSIS\n" - self.data += f"enum {args['enum']}" + " {\n" + self.data += f"enum {name}" + " {\n" count = 0 for parameter in args.parameterlist: @@ -710,13 +704,12 @@ class ManFormat(OutputFormat): def out_typedef(self, fname, name, args): module = self.modulename - typedef = args.get('typedef') purpose = args.get('purpose') - self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" + self.data += f'.TH "{module}" 9 "{name}" "{self.man_date}" "API Manual" LINUX' + "\n" self.data += ".SH NAME\n" - self.data += f"typedef {typedef} \\- {purpose}\n" + self.data += f"typedef {name} \\- {purpose}\n" for section, text in args.sections.items(): self.data += f'.SH "{section}"' + "\n" @@ -724,22 +717,20 @@ class ManFormat(OutputFormat): def out_struct(self, fname, name, args): module = self.modulename - struct_type = args.get('type') - struct_name = args.get('struct') purpose = args.get('purpose') definition = args.get('definition') - self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" + self.data += f'.TH "{module}" 9 "{args.type} {name}" "{self.man_date}" "API Manual" LINUX' + "\n" self.data += ".SH NAME\n" - self.data += f"{struct_type} {struct_name} \\- {purpose}\n" + self.data += f"{args.type} {name} \\- {purpose}\n" # Replace tabs with two spaces and handle newlines declaration = definition.replace("\t", " ") declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) self.data += ".SH SYNOPSIS\n" - self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n" + self.data += f"{args.type} {name} " + "{" + "\n.br\n" self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" self.data += ".SH Members\n" diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 298abd260264..6e35e508608b 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -790,7 +790,6 @@ class KernelDoc: level += 1 self.output_declaration(decl_type, declaration_name, - struct=declaration_name, definition=declaration, purpose=self.entry.declaration_purpose) @@ -870,7 +869,6 @@ class KernelDoc: f"Excess enum value '%{k}' description in '{declaration_name}'") self.output_declaration('enum', declaration_name, - enum=declaration_name, purpose=self.entry.declaration_purpose) def dump_declaration(self, ln, prototype): @@ -1031,14 +1029,12 @@ class KernelDoc: if 'typedef' in return_type: self.output_declaration(decl_type, declaration_name, - function=declaration_name, typedef=True, functiontype=return_type, purpose=self.entry.declaration_purpose, func_macro=func_macro) else: self.output_declaration(decl_type, declaration_name, - function=declaration_name, typedef=False, functiontype=return_type, purpose=self.entry.declaration_purpose, @@ -1077,7 +1073,6 @@ class KernelDoc: self.create_parameter_list(ln, decl_type, args, ',', declaration_name) self.output_declaration(decl_type, declaration_name, - function=declaration_name, typedef=True, functiontype=return_type, purpose=self.entry.declaration_purpose) @@ -1099,7 +1094,6 @@ class KernelDoc: return self.output_declaration('typedef', declaration_name, - typedef=declaration_name, purpose=self.entry.declaration_purpose) return -- cgit v1.2.3 From 08b8dc43d18d5d0c4791cc630d5cddf98eaa51ea Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 13:34:40 -0600 Subject: docs: kdoc: straighten up dump_declaration() Get rid of the excess "return" statements in dump_declaration(), along with a line of never-executed dead code. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 6e35e508608b..7191fa94e17a 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -878,18 +878,13 @@ class KernelDoc: if self.entry.decl_type == "enum": self.dump_enum(ln, prototype) - return - - if self.entry.decl_type == "typedef": + elif self.entry.decl_type == "typedef": self.dump_typedef(ln, prototype) - return - - if self.entry.decl_type in ["union", "struct"]: + elif self.entry.decl_type in ["union", "struct"]: self.dump_struct(ln, prototype) - return - - self.output_declaration(self.entry.decl_type, prototype, - entry=self.entry) + else: + # This would be a bug + self.emit_message(ln, f'Unknown declaration type: {self.entry.decl_type}') def dump_function(self, ln, prototype): """ -- cgit v1.2.3 From 636d4d9ec641025b98e8df4623a77ecc09026209 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Wed, 2 Jul 2025 14:53:32 -0600 Subject: docs: kdoc: clean up check_sections() entry.sectcheck is just a duplicate of our list of sections that is only passed to check_sections(); its main purpose seems to be to avoid checking the special named sections. Rework check_sections() to not use that field (which is then deleted), tocheck for the known sections directly, and tighten up the logic in general. Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 7191fa94e17a..fdde14b045fe 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -42,9 +42,11 @@ doc_decl = doc_com + KernRe(r'(\w+)', cache=False) # @{section-name}: # while trying to not match literal block starts like "example::" # +known_section_names = 'description|context|returns?|notes?|examples?' +known_sections = KernRe(known_section_names, flags = re.I) doc_sect = doc_com + \ - KernRe(r'\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$', - flags=re.I, cache=False) + KernRe(r'\s*(\@[.\w]+|\@\.\.\.|' + known_section_names + r')\s*:([^:].*)?$', + flags=re.I, cache=False) doc_content = doc_com_body + KernRe(r'(.*)', cache=False) doc_inline_start = KernRe(r'^\s*/\*\*\s*$', cache=False) @@ -115,7 +117,6 @@ class KernelEntry: self.config = config self._contents = [] - self.sectcheck = "" self.prototype = "" self.warnings = [] @@ -187,7 +188,6 @@ class KernelEntry: self.parameterdescs[name] = contents self.parameterdesc_start_lines[name] = self.new_start_line - self.sectcheck += name + " " self.new_start_line = 0 else: @@ -478,29 +478,20 @@ class KernelDoc: self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name) - def check_sections(self, ln, decl_name, decl_type, sectcheck): + def check_sections(self, ln, decl_name, decl_type): """ Check for errors inside sections, emitting warnings if not found parameters are described. """ - - sects = sectcheck.split() - - for sx in range(len(sects)): # pylint: disable=C0200 - err = True - for param in self.entry.parameterlist: - if param == sects[sx]: - err = False - break - - if err: + for section in self.entry.sections: + if section not in self.entry.parameterlist and \ + not known_sections.search(section): if decl_type == 'function': dname = f"{decl_type} parameter" else: dname = f"{decl_type} member" - self.emit_msg(ln, - f"Excess {dname} '{sects[sx]}' description in '{decl_name}'") + f"Excess {dname} '{section}' description in '{decl_name}'") def check_return_section(self, ln, declaration_name, return_type): """ @@ -754,7 +745,7 @@ class KernelDoc: self.create_parameter_list(ln, decl_type, members, ';', declaration_name) - self.check_sections(ln, declaration_name, decl_type, self.entry.sectcheck) + self.check_sections(ln, declaration_name, decl_type) # Adjust declaration for better display declaration = KernRe(r'([\{;])').sub(r'\1\n', declaration) @@ -1018,7 +1009,7 @@ class KernelDoc: f"expecting prototype for {self.entry.identifier}(). Prototype was for {declaration_name}() instead") return - self.check_sections(ln, declaration_name, "function", self.entry.sectcheck) + self.check_sections(ln, declaration_name, "function") self.check_return_section(ln, declaration_name, return_type) -- cgit v1.2.3 From 40020fe8e3a4038ed6fb4b3115ad4c60fd354ab3 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Thu, 10 Jul 2025 17:24:07 -0600 Subject: docs: kdoc: emit a warning for ancient versions of Python Versions of Python prior to 3.7 do not guarantee to remember the insertion order of dicts; since kernel-doc depends on that guarantee, running with such older versions could result in output with reordered sections. Python 3.9 is the minimum for the kernel as a whole, so this should not be a problem, but put in a warning just in case somebody tries to use something older. Suggested-by: Mauro Carvalho Chehab Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- scripts/lib/kdoc/kdoc_parser.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index fdde14b045fe..06f55f38d4a7 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -12,6 +12,7 @@ Read a C language source or header FILE and extract embedded documentation comments """ +import sys import re from pprint import pformat @@ -238,6 +239,14 @@ class KernelDoc: # Place all potential outputs into an array self.entries = [] + # + # We need Python 3.7 for its "dicts remember the insertion + # order" guarantee + # + if sys.version_info.major == 3 and sys.version_info.minor < 7: + self.emit_msg(0, + 'Python 3.7 or later is required for correct results') + def emit_msg(self, ln, msg, warning=True): """Emit a message""" -- cgit v1.2.3 From 39e39af70d066029c788800ee07e0491e07eb081 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Fri, 11 Jul 2025 09:27:09 +0200 Subject: scripts: kdoc: make it backward-compatible with Python 3.7 There was a change at kdoc that ended breaking compatibility with Python 3.7: str.removesuffix() was introduced on version 3.9. Restore backward compatibility. Reported-by: Akira Yokosawa Closes: https://lore.kernel.org/linux-doc/57be9f77-9a94-4cde-aacb-184cae111506@gmail.com/ Fixes: 27ad33b6b349 ("kernel-doc: Fix symbol matching for dropped suffixes") Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/d13058d285838ac2bc04c492e60531c013a8a919.1752218291.git.mchehab+huawei@kernel.org --- scripts/lib/kdoc/kdoc_parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'scripts/lib/kdoc/kdoc_parser.py') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 06f55f38d4a7..c3fe4bd5eab4 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1120,7 +1120,9 @@ class KernelDoc: # Found an export, trim out any special suffixes # for suffix in suffixes: - symbol = symbol.removesuffix(suffix) + # Be backward compatible with Python < 3.9 + if symbol.endswith(suffix): + symbol = symbol[:-len(suffix)] function_set.add(symbol) return True -- cgit v1.2.3