summaryrefslogtreecommitdiff
path: root/isort/parse.py
diff options
context:
space:
mode:
Diffstat (limited to 'isort/parse.py')
-rw-r--r--isort/parse.py116
1 files changed, 38 insertions, 78 deletions
diff --git a/isort/parse.py b/isort/parse.py
index 51dbc96f..a87461ff 100644
--- a/isort/parse.py
+++ b/isort/parse.py
@@ -1,5 +1,6 @@
"""Defines parsing functions used by isort for parsing import definitions"""
from collections import OrderedDict, defaultdict, namedtuple
+from io import StringIO
from itertools import chain
from typing import TYPE_CHECKING, Any, Dict, Generator, Iterator, List, NamedTuple, Optional, Tuple
from warnings import warn
@@ -10,6 +11,8 @@ from isort.settings import DEFAULT_CONFIG, Config
from .comments import parse as parse_comments
from .finders import FindersManager
+IMPORT_START_IDENTIFIERS = ("from ", "from.import", "import ", "import*")
+
if TYPE_CHECKING:
from mypy_extensions import TypedDict
@@ -37,6 +40,18 @@ def _infer_line_separator(file_contents: str) -> str:
return "\n"
+def _normalize_line(raw_line: str) -> Tuple[str, str]:
+ """Normalizes import related statements in the provided line.
+
+ Returns (normalized_line: str, raw_line: str)
+ """
+ line = raw_line.replace("from.import ", "from . import ")
+ line = line.replace("import*", "import *")
+ line = line.replace(" .import ", " . import ")
+ line = line.replace("\t", " ")
+ return (line, raw_line)
+
+
def import_type(line: str) -> Optional[str]:
"""If the current line is an import line it will return its type (from or straight)"""
if "isort:skip" in line or "NOQA" in line:
@@ -62,45 +77,24 @@ def _strip_syntax(import_string: str) -> str:
def skip_line(
- line: str,
- in_quote: str,
- in_top_comment: bool,
- index: int,
- section_comments: List[str],
- first_comment_index_start: int,
- first_comment_index_end: int,
-) -> Tuple[bool, str, bool, int, int]:
+ line: str, in_quote: str, index: int, section_comments: List[str]
+) -> Tuple[bool, str]:
"""Determine if a given line should be skipped.
Returns back a tuple containing:
(skip_line: bool,
- in_quote: str,
- in_top_comment: bool,
- first_comment_index_start: int,
- first_comment_index_end: int)
+ in_quote: str,)
"""
skip_line = bool(in_quote)
- if index == 1 and line.startswith("#"):
- in_top_comment = True
- return (True, in_quote, in_top_comment, first_comment_index_start, first_comment_index_end)
- elif in_top_comment:
- if not line.startswith("#") or line in section_comments:
- in_top_comment = False
- first_comment_index_end = index - 1
-
if '"' in line or "'" in line:
char_index = 0
- if first_comment_index_start == -1 and (line.startswith('"') or line.startswith("'")):
- first_comment_index_start = index
while char_index < len(line):
if line[char_index] == "\\":
char_index += 1
elif in_quote:
if line[char_index : char_index + len(in_quote)] == in_quote:
in_quote = ""
- if first_comment_index_end < first_comment_index_start:
- first_comment_index_end = index
elif line[char_index] in ("'", '"'):
long_quote = line[char_index : char_index + 3]
if long_quote in ('"""', "'''"):
@@ -112,13 +106,12 @@ def skip_line(
break
char_index += 1
- return (
- bool(skip_line or in_quote or in_top_comment),
- in_quote,
- in_top_comment,
- first_comment_index_start,
- first_comment_index_end,
- )
+ if ";" in line:
+ for part in (part.strip() for part in line.split(";")):
+ if part and not part.startswith("from ") and not part.startswith("import "):
+ skip_line = True
+
+ return (bool(skip_line or in_quote), in_quote)
class ParsedContent(NamedTuple):
@@ -130,8 +123,6 @@ class ParsedContent(NamedTuple):
as_map: Dict[str, List[str]]
imports: Dict[str, Dict[str, Any]]
categorized_comments: "CommentsDict"
- first_comment_index_start: int
- first_comment_index_end: int
change_count: int
original_line_count: int
line_separator: str
@@ -142,16 +133,12 @@ class ParsedContent(NamedTuple):
def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedContent:
"""Parses a python file taking out and categorizing imports."""
line_separator: str = config.line_ending or _infer_line_separator(contents)
- add_imports = (format_natural(addition) for addition in config.add_imports)
in_lines = contents.split(line_separator)
out_lines = []
original_line_count = len(in_lines)
section_comments = [f"# {heading}" for heading in config.import_headings.values()]
finder = FindersManager(config=config)
- if original_line_count > 1 or in_lines[:1] not in ([], [""]) or config.force_adds:
- in_lines.extend(add_imports)
-
line_count = len(in_lines)
place_imports: Dict[str, List[str]] = {}
@@ -170,31 +157,12 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
index = 0
import_index = -1
in_quote = ""
- in_top_comment = False
- first_comment_index_start = -1
- first_comment_index_end = -1
while index < line_count:
- raw_line = line = in_lines[index]
- line = line.replace("from.import ", "from . import ")
- line = line.replace("\t", " ").replace("import*", "import *")
- line = line.replace(" .import ", " . import ")
+ line = in_lines[index]
index += 1
statement_index = index
-
- (
- skipping_line,
- in_quote,
- in_top_comment,
- first_comment_index_start,
- first_comment_index_end,
- ) = skip_line(
- line,
- in_quote=in_quote,
- in_top_comment=in_top_comment,
- index=index,
- section_comments=section_comments,
- first_comment_index_start=first_comment_index_start,
- first_comment_index_end=first_comment_index_end,
+ (skipping_line, in_quote) = skip_line(
+ line, in_quote=in_quote, index=index, section_comments=section_comments
)
if line in section_comments and not skipping_line:
@@ -207,20 +175,17 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
place_imports[section] = []
import_placements[line] = section
- if ";" in line:
- for part in (part.strip() for part in line.split(";")):
- if part and not part.startswith("from ") and not part.startswith("import "):
- skipping_line = True
-
- type_of_import: str = import_type(line) or ""
- if not type_of_import or skipping_line:
- out_lines.append(raw_line)
+ if skipping_line:
+ out_lines.append(line)
continue
- for line in (line.strip() for line in line.split(";")):
+ for line in (
+ (line.strip() for line in line.split(";")) if ";" in line else (line,) # type: ignore
+ ):
+ line, raw_line = _normalize_line(line)
type_of_import = import_type(line) or ""
if not type_of_import:
- out_lines.append(line)
+ out_lines.append(raw_line)
continue
if import_index == -1:
@@ -353,7 +318,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if comments:
categorized_comments["from"].setdefault(import_from, []).extend(comments)
- if len(out_lines) > max(import_index, first_comment_index_end + 1, 1) - 1:
+ if len(out_lines) > max(import_index, 1) - 1:
last = out_lines and out_lines[-1].rstrip() or ""
while (
last.startswith("#")
@@ -364,10 +329,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
categorized_comments["above"]["from"].setdefault(import_from, []).insert(
0, out_lines.pop(-1)
)
- if (
- len(out_lines)
- > max(import_index - 1, first_comment_index_end + 1, 1) - 1
- ):
+ if len(out_lines) > max(import_index - 1, 1) - 1:
last = out_lines[-1].rstrip()
else:
last = ""
@@ -391,7 +353,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
categorized_comments["straight"][module] = comments
comments = []
- if len(out_lines) > max(import_index, first_comment_index_end + 1, 1) - 1:
+ if len(out_lines) > max(import_index, +1, 1) - 1:
last = out_lines and out_lines[-1].rstrip() or ""
while (
@@ -403,7 +365,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
categorized_comments["above"]["straight"].setdefault(module, []).insert(
0, out_lines.pop(-1)
)
- if len(out_lines) > 0 and len(out_lines) != first_comment_index_end:
+ if len(out_lines) > 0 and len(out_lines):
last = out_lines[-1].rstrip()
else:
last = ""
@@ -435,8 +397,6 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
as_map=as_map,
imports=imports,
categorized_comments=categorized_comments,
- first_comment_index_start=first_comment_index_start,
- first_comment_index_end=first_comment_index_end,
change_count=change_count,
original_line_count=original_line_count,
line_separator=line_separator,