diff --git a/CHANGELOG b/CHANGELOG index 0e5958d8..f70654f3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,9 @@ Bug Fixes * Recognize ``MATERIALIZED`` as a keyword so it is parsed and formatted consistently in ``CREATE MATERIALIZED VIEW`` statements (issue752). * Fix ``get_real_name`` for names with more than two dotted parts (issue332). +* Fix ``TypeError`` in ``TokenList.token_matching`` (and other + ``_token_matching`` callers) when passed a ``None`` index; a ``None`` index + now yields "no match" instead of crashing (issue747). Release 0.5.5 (Dec 19, 2025) diff --git a/sqlparse/sql.py b/sqlparse/sql.py index ec44a6da..64ffab44 100644 --- a/sqlparse/sql.py +++ b/sqlparse/sql.py @@ -235,7 +235,7 @@ def _groupable_tokens(self): def _token_matching(self, funcs, start=0, end=None, reverse=False): """next token that match functions""" if start is None: - return None + return None, None if not isinstance(funcs, (list, tuple)): funcs = (funcs,) diff --git a/tests/test_regressions.py b/tests/test_regressions.py index a4629378..88851f47 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -477,6 +477,18 @@ def test_materialized_view_issue752(): assert formatted == 'CREATE MATERIALIZED VIEW v AS SELECT 1' +def test_token_matching_none_idx_issue747(): + # token_matching (and other helpers) delegate to _token_matching, which + # used to return a bare None when given a None start index. Callers then + # tried to subscript that None and crashed with a TypeError. A None index + # should simply yield "no match". + p = sqlparse.parse('SELECT id FROM table')[0] + assert p.token_matching( + [lambda tk: isinstance(tk, sql.Token)], None) is None + assert p._token_matching(lambda tk: True, None) == (None, None) + assert p.token_not_matching([lambda tk: False], None) == (None, None) + + @pytest.fixture def limit_recursion(): curr_limit = sys.getrecursionlimit()