diff --git a/lib/python/pyflyby/_file.py b/lib/python/pyflyby/_file.py index 9dba9013..b8467445 100644 --- a/lib/python/pyflyby/_file.py +++ b/lib/python/pyflyby/_file.py @@ -4,13 +4,13 @@ -from functools import total_ordering +from functools import total_ordering, cached_property import io import os import re import sys -from pyflyby._util import cached_attribute, cmp, memoize +from pyflyby._util import cmp, memoize class UnsafeFilenameError(ValueError): @@ -28,6 +28,8 @@ class Filename(object): Filename('/etc/passwd') """ + _filename: str + def __new__(cls, arg): if isinstance(arg, cls): return arg @@ -85,7 +87,7 @@ def __cmp__(self, o): return NotImplemented return cmp(self._filename, o._filename) - @cached_attribute + @cached_property def ext(self): """ Returns the extension of this filename, including the dot. @@ -99,15 +101,15 @@ def ext(self): return None return dot + rhs - @cached_attribute + @cached_property def base(self): return os.path.basename(self._filename) - @cached_attribute + @cached_property def dir(self): return type(self)(os.path.dirname(self._filename)) - @cached_attribute + @cached_property def real(self): return type(self)(os.path.realpath(self._filename)) @@ -338,6 +340,8 @@ class FileText: Represents a contiguous sequence of lines from a file. """ + filename: Filename + def __new__(cls, arg, filename=None, startpos=None): """ Return a new ``FileText`` instance. @@ -394,7 +398,7 @@ def _from_lines(cls, lines, filename, startpos): self.startpos = startpos return self - @cached_attribute + @cached_property def lines(self): r""" Lines that have been split by newline. @@ -414,7 +418,7 @@ def lines(self): # (or requires extra work to process if we use splitlines(True)). return tuple(self.joined.split('\n')) - @cached_attribute + @cached_property def joined(self): # used if only initialized with 'lines' return '\n'.join(self.lines) @@ -441,7 +445,7 @@ def alter(self, filename=None, startpos=None): result.startpos = startpos return result - @cached_attribute + @cached_property def endpos(self): """ The position after the last character in the text. diff --git a/lib/python/pyflyby/_imports2s.py b/lib/python/pyflyby/_imports2s.py index bcc88787..1a05a08f 100644 --- a/lib/python/pyflyby/_imports2s.py +++ b/lib/python/pyflyby/_imports2s.py @@ -200,12 +200,10 @@ def insert_new_blocks_after_comments(self, blocks): # insert in the middle. self.blocks[:1] = ( [SourceToSourceTransformation( - PythonBlock.concatenate(statements[:idx], - assume_contiguous=True))] + + PythonBlock.concatenate(statements[:idx]))] + blocks + [SourceToSourceTransformation( - PythonBlock.concatenate(statements[idx:], - assume_contiguous=True))]) + PythonBlock.concatenate(statements[idx:]))]) break else: # First block is entirely comments, so just insert after it. diff --git a/lib/python/pyflyby/_parse.py b/lib/python/pyflyby/_parse.py index be74b2df..50f28373 100644 --- a/lib/python/pyflyby/_parse.py +++ b/lib/python/pyflyby/_parse.py @@ -1,7 +1,7 @@ # pyflyby/_parse.py. # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT - +from __future__ import annotations import ast @@ -13,6 +13,8 @@ import sys from textwrap import dedent import types +from typing import Optional +import warnings from pyflyby._file import FilePos, FileText, Filename from pyflyby._flags import CompilerFlags @@ -22,6 +24,8 @@ from ast import TypeIgnore, AsyncFunctionDef +_sentinel = object() + def _is_comment_or_blank(line): """ @@ -741,7 +745,7 @@ class _DummyAst_Node(object): pass -class PythonStatement(object): +class PythonStatement: r""" Representation of a top-level Python statement or consecutive comments/blank lines. @@ -753,7 +757,9 @@ class PythonStatement(object): top-level AST node. """ - def __new__(cls, arg, filename=None, startpos=None, flags=None): + block: PythonBlock + + def __new__(cls, arg:PythonStatement, filename=None, startpos=None, flags=None): if isinstance(arg, cls): if filename is startpos is flags is None: return arg @@ -773,7 +779,8 @@ def __new__(cls, arg, filename=None, startpos=None, flags=None): raise TypeError("PythonStatement: unexpected %s" % (type(arg).__name__,)) @classmethod - def _construct_from_block(cls, block): + def _construct_from_block(cls, block:PythonBlock): + assert isinstance(block, PythonBlock), repr(block) # Only to be used by PythonBlock. assert isinstance(block, PythonBlock) self = object.__new__(cls) @@ -781,7 +788,7 @@ def _construct_from_block(cls, block): return self @property - def text(self): + def text(self) -> FileText: """ :rtype: `FileText` @@ -789,7 +796,7 @@ def text(self): return self.block.text @property - def filename(self): + def filename(self) -> Optional[str]: """ :rtype: `Filename` @@ -828,9 +835,17 @@ def ast_node(self): return ast_nodes[0] raise AssertionError("More than one AST node in block") + @property + def is_blank(self): + return self.ast_node is None and self.text.joined.strip() == '' + + @property + def is_comment(self): + return self.ast_node is None and self.text.joined.strip() != '' + @property def is_comment_or_blank(self): - return self.ast_node is None + return self.is_comment or self.is_blank @property def is_comment_or_blank_or_string_literal(self): @@ -898,7 +913,7 @@ def __hash__(self): @total_ordering -class PythonBlock(object): +class PythonBlock: r""" Representation of a sequence of consecutive top-level `PythonStatement` (s). @@ -993,17 +1008,21 @@ def __construct_from_annotated_ast(cls, annotated_ast_nodes, text, flags): return self @classmethod - def concatenate(cls, blocks, assume_contiguous=False): + def concatenate(cls, blocks, assume_contiguous=_sentinel): """ Concatenate a bunch of blocks into one block. :type blocks: sequence of `PythonBlock` s and/or `PythonStatement` s :param assume_contiguous: + Deprecated, always True Whether to assume, without checking, that the input blocks were originally all contiguous. This must be set to True to indicate the caller understands the assumption; False is not implemented. """ + if assume_contiguous is not _sentinel: + warnings.warn('`assume_continuous` is deprecated and considered always `True`') + assume_contiguous = True if not assume_contiguous: raise NotImplementedError blocks = [PythonBlock(b) for b in blocks] @@ -1248,7 +1267,7 @@ def groupby(self, predicate): cls = type(self) for pred, stmts in groupby(self.statements, predicate): blocks = [s.block for s in stmts] - yield pred, cls.concatenate(blocks, assume_contiguous=True) + yield pred, cls.concatenate(blocks) def string_literals(self): r"""