| 171 | |
| 172 | |
| 173 | def getstatementrange_ast( |
| 174 | lineno: int, |
| 175 | source: Source, |
| 176 | assertion: bool = False, |
| 177 | astnode: Optional[ast.AST] = None, |
| 178 | ) -> Tuple[ast.AST, int, int]: |
| 179 | if astnode is None: |
| 180 | content = str(source) |
| 181 | # See #4260: |
| 182 | # Don't produce duplicate warnings when compiling source to find AST. |
| 183 | with warnings.catch_warnings(): |
| 184 | warnings.simplefilter("ignore") |
| 185 | astnode = ast.parse(content, "source", "exec") |
| 186 | |
| 187 | start, end = get_statement_startend2(lineno, astnode) |
| 188 | # We need to correct the end: |
| 189 | # - ast-parsing strips comments |
| 190 | # - there might be empty lines |
| 191 | # - we might have lesser indented code blocks at the end |
| 192 | if end is None: |
| 193 | end = len(source.lines) |
| 194 | |
| 195 | if end > start + 1: |
| 196 | # Make sure we don't span differently indented code blocks |
| 197 | # by using the BlockFinder helper used which inspect.getsource() uses itself. |
| 198 | block_finder = inspect.BlockFinder() |
| 199 | # If we start with an indented line, put blockfinder to "started" mode. |
| 200 | block_finder.started = source.lines[start][0].isspace() |
| 201 | it = ((x + "\n") for x in source.lines[start:end]) |
| 202 | try: |
| 203 | for tok in tokenize.generate_tokens(lambda: next(it)): |
| 204 | block_finder.tokeneater(*tok) |
| 205 | except (inspect.EndOfBlock, IndentationError): |
| 206 | end = block_finder.last + start |
| 207 | except Exception: |
| 208 | pass |
| 209 | |
| 210 | # The end might still point to a comment or empty line, correct it. |
| 211 | while end: |
| 212 | line = source.lines[end - 1].lstrip() |
| 213 | if line.startswith("#") or not line: |
| 214 | end -= 1 |
| 215 | else: |
| 216 | break |
| 217 | return astnode, start, end |