Parses incomplete import statements that are suitable for autocomplete suggestions. Examples: - import foo -> Result(from_name=None, name='foo') - import foo. -> Result(from_name=None, name='foo.') - from foo -> Result(from_name='foo'
| 302 | |
| 303 | |
| 304 | class ImportParser: |
| 305 | """ |
| 306 | Parses incomplete import statements that are |
| 307 | suitable for autocomplete suggestions. |
| 308 | |
| 309 | Examples: |
| 310 | - import foo -> Result(from_name=None, name='foo') |
| 311 | - import foo. -> Result(from_name=None, name='foo.') |
| 312 | - from foo -> Result(from_name='foo', name=None) |
| 313 | - from foo import bar -> Result(from_name='foo', name='bar') |
| 314 | - from .foo import ( -> Result(from_name='.foo', name='') |
| 315 | |
| 316 | Note that the parser works in reverse order, starting from the |
| 317 | last token in the input string. This makes the parser more robust |
| 318 | when parsing multiple statements. |
| 319 | """ |
| 320 | _ignored_tokens = { |
| 321 | token.INDENT, token.DEDENT, token.COMMENT, |
| 322 | token.NL, token.NEWLINE, token.ENDMARKER |
| 323 | } |
| 324 | _keywords = {'import', 'from', 'as'} |
| 325 | |
| 326 | def __init__(self, code: str) -> None: |
| 327 | self.code = code |
| 328 | tokens = [] |
| 329 | try: |
| 330 | for t in tokenize.generate_tokens(StringIO(code).readline): |
| 331 | if t.type not in self._ignored_tokens: |
| 332 | tokens.append(t) |
| 333 | except tokenize.TokenError as e: |
| 334 | if 'unexpected EOF' not in str(e): |
| 335 | # unexpected EOF is fine, since we're parsing an |
| 336 | # incomplete statement, but other errors are not |
| 337 | # because we may not have all the tokens so it's |
| 338 | # safer to bail out |
| 339 | tokens = [] |
| 340 | except SyntaxError: |
| 341 | tokens = [] |
| 342 | self.tokens = TokenQueue(tokens[::-1]) |
| 343 | |
| 344 | def parse(self) -> tuple[str | None, str | None] | None: |
| 345 | if not (res := self._parse()): |
| 346 | return None |
| 347 | return res.from_name, res.name |
| 348 | |
| 349 | def _parse(self) -> Result | None: |
| 350 | with self.tokens.save_state(): |
| 351 | return self.parse_from_import() |
| 352 | with self.tokens.save_state(): |
| 353 | return self.parse_import() |
| 354 | |
| 355 | def parse_import(self) -> Result: |
| 356 | if self.code.rstrip().endswith('import') and self.code.endswith(' '): |
| 357 | return Result(name='') |
| 358 | if self.tokens.peek_string(','): |
| 359 | name = '' |
| 360 | else: |
| 361 | if self.code.endswith(' '): |
no outgoing calls
searching dependent graphs…