"""
String transformers that can split and merge strings.
"""
+
import re
-import sys
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass
ClassVar,
Collection,
Dict,
+ Final,
Iterable,
Iterator,
List,
+ Literal,
Optional,
Sequence,
Set,
Union,
)
-if sys.version_info < (3, 8):
- from typing_extensions import Final, Literal
-else:
- from typing import Literal, Final
-
from mypy_extensions import trait
from black.comments import contains_pragma_comment
"""
Returns:
* Ok(string_indices) such that for each index, `line.leaves[index]`
- is our target string if a match was able to be made. For
- transformers that don't result in more lines (e.g. StringMerger,
- StringParenStripper), multiple matches and transforms are done at
- once to reduce the complexity.
- OR
+ is our target string if a match was able to be made. For
+ transformers that don't result in more lines (e.g. StringMerger,
+ StringParenStripper), multiple matches and transforms are done at
+ once to reduce the complexity.
+ OR
* Err(CannotTransform), if no match could be made.
"""
"""
Yields:
* Ok(new_line) where new_line is the new transformed line.
- OR
+ OR
* Err(CannotTransform) if the transformation failed for some reason. The
- `do_match(...)` template method should usually be used to reject
- the form of the given Line, but in some cases it is difficult to
- know whether or not a Line meets the StringTransformer's
- requirements until the transformation is already midway.
+ `do_match(...)` template method should usually be used to reject
+ the form of the given Line, but in some cases it is difficult to
+ know whether or not a Line meets the StringTransformer's
+ requirements until the transformation is already midway.
Side Effects:
This method should NOT mutate @line directly, but it MAY mutate the
Returns:
* A list of the custom splits that are mapped to @string, if any
- exist.
- OR
+ exist.
+ OR
* [], otherwise.
Side Effects:
Requirements:
(A) The line contains adjacent strings such that ALL of the validation checks
listed in StringMerger._validate_msg(...)'s docstring pass.
- OR
+ OR
(B) The line contains a string which uses line continuation backslashes.
Transformations:
Depending on which of the two requirements above where met, either:
(A) The string group associated with the target string is merged.
- OR
+ OR
(B) All line-continuation backslashes are removed from the target string.
Collaborations:
LL[lpar_or_rpar_idx].remove() # Remove lpar.
replace_child(LL[idx], string_leaf)
new_line.append(string_leaf)
+ # replace comments
+ old_comments = new_line.comments.pop(id(LL[idx]), [])
+ new_line.comments.setdefault(id(string_leaf), []).extend(old_comments)
else:
LL[lpar_or_rpar_idx].remove() # This is a rpar.
Requirements:
* The target string value is responsible for the line going over the
- line length limit. It follows that after all of black's other line
- split methods have been exhausted, this line (or one of the resulting
- lines after all line splits are performed) would still be over the
- line_length limit unless we split this string.
- AND
+ line length limit. It follows that after all of black's other line
+ split methods have been exhausted, this line (or one of the resulting
+ lines after all line splits are performed) would still be over the
+ line_length limit unless we split this string.
+ AND
+
* The target string is NOT a "pointless" string (i.e. a string that has
- no parent or siblings).
- AND
+ no parent or siblings).
+ AND
+
* The target string is not followed by an inline comment that appears
- to be a pragma.
- AND
+ to be a pragma.
+ AND
+
* The target string is not a multiline (i.e. triple-quote) string.
"""
Returns:
* Ok(None), if ALL of the requirements are met.
- OR
+ OR
* Err(CannotTransform), if ANY of the requirements are NOT met.
"""
LL = line.leaves
if LL[0].type != token.STRING:
return None
- # If the string is surrounded by commas (or is the first/last child)...
- prev_sibling = LL[0].prev_sibling
- next_sibling = LL[0].next_sibling
- if not prev_sibling and not next_sibling and parent_type(LL[0]) == syms.atom:
- # If it's an atom string, we need to check the parent atom's siblings.
- parent = LL[0].parent
- assert parent is not None # For type checkers.
- prev_sibling = parent.prev_sibling
- next_sibling = parent.next_sibling
- if (not prev_sibling or prev_sibling.type == token.COMMA) and (
- not next_sibling or next_sibling.type == token.COMMA
+ matching_nodes = [
+ syms.listmaker,
+ syms.dictsetmaker,
+ syms.testlist_gexp,
+ ]
+ # If the string is an immediate child of a list/set/tuple literal...
+ if (
+ parent_type(LL[0]) in matching_nodes
+ or parent_type(LL[0].parent) in matching_nodes
):
- return 0
+ # And the string is surrounded by commas (or is the first/last child)...
+ prev_sibling = LL[0].prev_sibling
+ next_sibling = LL[0].next_sibling
+ if (
+ not prev_sibling
+ and not next_sibling
+ and parent_type(LL[0]) == syms.atom
+ ):
+ # If it's an atom string, we need to check the parent atom's siblings.
+ parent = LL[0].parent
+ assert parent is not None # For type checkers.
+ prev_sibling = parent.prev_sibling
+ next_sibling = parent.next_sibling
+ if (not prev_sibling or prev_sibling.type == token.COMMA) and (
+ not next_sibling or next_sibling.type == token.COMMA
+ ):
+ return 0
return None
Requirements:
* The line consists ONLY of a single string (possibly prefixed by a
- string operator [e.g. '+' or '==']), MAYBE a string trailer, and MAYBE
- a trailing comma.
- AND
+ string operator [e.g. '+' or '==']), MAYBE a string trailer, and MAYBE
+ a trailing comma.
+ AND
* All of the requirements listed in BaseStringSplitter's docstring.
Transformations:
addition to the requirements listed below:
* The line is a return/yield statement, which returns/yields a string.
- OR
+ OR
* The line is part of a ternary expression (e.g. `x = y if cond else
- z`) such that the line starts with `else <string>`, where <string> is
- some string.
- OR
+ z`) such that the line starts with `else <string>`, where <string> is
+ some string.
+ OR
* The line is an assert statement, which ends with a string.
- OR
+ OR
* The line is an assignment statement (e.g. `x = <string>` or `x +=
- <string>`) such that the variable is being assigned the value of some
- string.
- OR
+ <string>`) such that the variable is being assigned the value of some
+ string.
+ OR
* The line is a dictionary key assignment where some valid key is being
- assigned the value of some string.
- OR
+ assigned the value of some string.
+ OR
* The line is an lambda expression and the value is a string.
- OR
+ OR
* The line starts with an "atom" string that prefers to be wrapped in
- parens. It's preferred to be wrapped when the string is surrounded by
- commas (or is the first/last child).
+ parens. It's preferred to be wrapped when it's is an immediate child of
+ a list/set/tuple literal, AND the string is surrounded by commas (or is
+ the first/last child).
Transformations:
The chosen string is wrapped in parentheses and then split at the LPAR.
Returns:
The index directly after the last leaf which is apart of the string
trailer, if a "trailer" exists.
- OR
+ OR
@string_idx + 1, if no string "trailer" exists.
"""
assert leaves[string_idx].type == token.STRING
"""
Pre-conditions:
* On the first call to this function, @leaf MUST be the leaf that
- was directly after the string leaf in question (e.g. if our target
- string is `line.leaves[i]` then the first call to this method must
- be `line.leaves[i + 1]`).
+ was directly after the string leaf in question (e.g. if our target
+ string is `line.leaves[i]` then the first call to this method must
+ be `line.leaves[i + 1]`).
* On the next call to this function, the leaf parameter passed in
- MUST be the leaf directly following @leaf.
+ MUST be the leaf directly following @leaf.
Returns:
True iff @leaf is apart of the string's trailer.