From c8eb6dbdcec270163542588b32bc4ed42b2ebe01 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Sat, 13 Jun 2026 11:33:48 -0300 Subject: [PATCH] parser: drain all leftover buffers between blocks A comment or 'def' block peeks the line that terminates it and returns it as a leftover buffer for the main loop to re-process. When that leftover is itself a comment/'def' block, re-processing produces a second leftover -- but the loop only drained one level ('if nextbuf:'), so the second leftover was glued onto the next raw line instead. In practice this misparsed an 'addhandler' line followed by its 'python ...() {}' event handler when the handler was preceded by two or more 'def' helper blocks: the handler merged with the 'addhandler' line and its body spilled out as loose Items instead of a single Function. Drain the leftover buffer fully ('while nextbuf:'). Adds a regression test covering a handler that follows two 'def' helper blocks. Signed-off-by: Otavio Salvador --- oelint_parser/parser.py | 9 ++++++++- tests/test_snippet.py | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/oelint_parser/parser.py b/oelint_parser/parser.py index c5bc662..1f47ade 100644 --- a/oelint_parser/parser.py +++ b/oelint_parser/parser.py @@ -215,7 +215,14 @@ def prepare_lines(_file: str, lineOffset: int = 0, negative: bool = False) -> Li _iter, lineOffset, num, line, negative=negative) num += length prep_lines.append(item) - if nextbuf: + # A comment or 'def' block peeks the line that terminates it and + # hands it back as a leftover buffer. That leftover can itself be + # another such block, peeking yet another terminating line, so the + # buffer must be drained fully here. Stopping after a single pass + # leaves the second leftover to be glued onto the next raw line + # above, which misparses e.g. an 'addhandler' followed by its + # 'python ...() {}' event handler. + while nextbuf: item, nextbuf, length = prepare_lines_subparser( _iter, lineOffset, num, nextbuf, negative=negative) num += length diff --git a/tests/test_snippet.py b/tests/test_snippet.py index 16564b6..3306e89 100644 --- a/tests/test_snippet.py +++ b/tests/test_snippet.py @@ -64,3 +64,47 @@ def test_item_classification(self, input_): _stash = self.__stash.GetItemsFor() assert len(_stash.data) == 1 + + def test_handler_after_def_blocks(self): + # 'python ...() {}' event handlers following one or more 'def' helper + # blocks must still parse as a single Function with the complete body, + # not be merged with the preceding 'addhandler' line and have the body + # spill out as loose Items. + from oelint_parser.cls_item import Function, Item, PythonBlock + from oelint_parser.cls_stash import Stash + + path = self.create_tempfile( + 'test-handler.bb', + ''' + def _helper_a(d): + return d.getVar('A') + + def _helper_b(d): + return d.getVar('B') + + addhandler myclass_eventhandler + python myclass_eventhandler() { + if bb.event.getName(e) == "ConfigParsed": + _helper_a(e.data) + } + ''', + ) + + self.__stash = Stash() + self.__stash.AddFile(path) + + _funcs = self.__stash.GetItemsFor(classifier=Function.CLASSIFIER) + assert len(_funcs) == 1 + assert _funcs[0].FuncName == 'myclass_eventhandler' + assert _funcs[0].IsPython + # the whole body, up to the closing brace, belongs to the handler + assert 'bb.event.getName' in _funcs[0].RealRaw + assert _funcs[0].RealRaw.rstrip().endswith('}') + + # both helpers stay recognised as Python blocks + assert len(self.__stash.GetItemsFor(classifier=PythonBlock.CLASSIFIER)) == 2 + + # the handler body must not leak out as loose base Items + _loose = [x for x in self.__stash.GetItemsFor() + if type(x) is Item and 'python' in x.RealRaw] + assert not _loose