Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature align assignment #1030

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -2,6 +2,10 @@
# All notable changes to this project will be documented in this file.
# This project adheres to [Semantic Versioning](http://semver.org/).

## [0.41.1] 2022-08-30
### Added
- Add 4 new knobs to align assignment operators and dictionary colons. They are align_assignment, align_argument_assignment, align_dict_colon and new_alignment_after_commentline.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reflow this to 80-columns.


## [0.40.0] UNRELEASED
### Added
- Add a new Python parser to generate logical lines.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
@@ -15,3 +15,4 @@ Sam Clegg <sbc@google.com>
Łukasz Langa <ambv@fb.com>
Oleg Butuzov <butuzov@made.ua>
Mauricio Herrera Cuadra <mauricio@arareko.net>
Xiao Wang <lizawang87@gmail.com>
55 changes: 55 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -390,6 +390,61 @@ Options::
Knobs
=====

``ALIGN_ASSIGNMENT``
Align assignment or augmented assignment operators.
If there is a blank line or a newline comment or a multiline object
(e.g. a dictionary, a list, a function call) in between,
it will start new block alignment. Lines in the same block have the same
indentation level.

.. code-block:: python

a = 1
abc = 2
if condition == None:
var += ''
var_long -= 4
b = 3
bc = 4

``ALIGN_ARGUMENT_ASSIGNMENT``
Align assignment operators in the argument list if they are all split on newlines.
Arguments without assignment in between will initiate new block alignment calulation;
for example, a comment line.
Multiline objects in between will also initiate a new alignment block.

.. code-block:: python

rglist = test(
var_first = 0,
var_second = '',
var_dict = {
"key_1" : '',
"key_2" : 2,
"key_3" : True,
},
var_third = 1,
var_very_long = None )

``ALIGN_DICT_COLON``
Align the colons in the dictionary if all entries in dictionay are split on newlines
or 'EACH_DICT_ENTRY_ON_SEPERATE_LINE' is set True.
A commentline or multi-line object in between will start new alignment block.

.. code-block:: python

fields =
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This opening bracket should be on the first line.

"field" : "ediid",
"type" : "text",
# key: value
"required" : True,
}

``NEW_ALIGNMENT_AFTER_COMMENTLINE``
Make it optional to start a new alignmetn block for assignment
alignment and colon alignment after a comment line.

``ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT``
Align closing bracket with visual indentation.

98 changes: 53 additions & 45 deletions yapf/__init__.py
Original file line number Diff line number Diff line change
@@ -55,8 +55,8 @@ def main(argv):
Raises:
YapfError: if none of the supplied files were Python files.
"""
parser = _BuildParser()
args = parser.parse_args(argv[1:])
parser = _BuildParser()
args = parser.parse_args(argv[1:])
style_config = args.style

if args.style_help:
@@ -70,8 +70,9 @@ def main(argv):
if not args.files:
# No arguments specified. Read code from stdin.
if args.in_place or args.diff:
parser.error('cannot use --in-place or --diff flags when reading '
'from stdin')
parser.error(
'cannot use --in-place or --diff flags when reading '
'from stdin')

original_source = []
while True:
@@ -93,7 +94,7 @@ def main(argv):
if style_config is None and not args.no_local_style:
style_config = file_resources.GetDefaultStyleForDir(os.getcwd())

source = [line.rstrip() for line in original_source]
source = [line.rstrip() for line in original_source]
source[0] = py3compat.removeBOM(source[0])

try:
@@ -115,9 +116,9 @@ def main(argv):
exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir(
os.getcwd())

files = file_resources.GetCommandLineFiles(args.files, args.recursive,
(args.exclude or []) +
exclude_patterns_from_ignore_file)
files = file_resources.GetCommandLineFiles(
args.files, args.recursive,
(args.exclude or []) + exclude_patterns_from_ignore_file)
if not files:
raise errors.YapfError('input filenames did not match any python files')

@@ -152,16 +153,17 @@ def _PrintHelp(args):
print()


def FormatFiles(filenames,
lines,
style_config=None,
no_local_style=False,
in_place=False,
print_diff=False,
verify=False,
parallel=False,
quiet=False,
verbose=False):
def FormatFiles(
filenames,
lines,
style_config=None,
no_local_style=False,
in_place=False,
print_diff=False,
verify=False,
parallel=False,
quiet=False,
verbose=False):
"""Format a list of files.

Arguments:
@@ -191,28 +193,31 @@ def FormatFiles(filenames,
workers = min(multiprocessing.cpu_count(), len(filenames))
with concurrent.futures.ProcessPoolExecutor(workers) as executor:
future_formats = [
executor.submit(_FormatFile, filename, lines, style_config,
no_local_style, in_place, print_diff, verify, quiet,
verbose) for filename in filenames
executor.submit(
_FormatFile, filename, lines, style_config, no_local_style,
in_place, print_diff, verify, quiet, verbose)
for filename in filenames
]
for future in concurrent.futures.as_completed(future_formats):
changed |= future.result()
else:
for filename in filenames:
changed |= _FormatFile(filename, lines, style_config, no_local_style,
in_place, print_diff, verify, quiet, verbose)
changed |= _FormatFile(
filename, lines, style_config, no_local_style, in_place, print_diff,
verify, quiet, verbose)
return changed


def _FormatFile(filename,
lines,
style_config=None,
no_local_style=False,
in_place=False,
print_diff=False,
verify=False,
quiet=False,
verbose=False):
def _FormatFile(
filename,
lines,
style_config=None,
no_local_style=False,
in_place=False,
print_diff=False,
verify=False,
quiet=False,
verbose=False):
"""Format an individual file."""
if verbose and not quiet:
print('Reformatting %s' % filename)
@@ -236,8 +241,8 @@ def _FormatFile(filename,
raise errors.YapfError(errors.FormatErrorMsg(e))

if not in_place and not quiet and reformatted_code:
file_resources.WriteReformattedCode(filename, reformatted_code, encoding,
in_place)
file_resources.WriteReformattedCode(
filename, reformatted_code, encoding, in_place)
return has_change


@@ -321,18 +326,20 @@ def _BuildParser():
parser.add_argument(
'--style',
action='store',
help=('specify formatting style: either a style name (for example "pep8" '
'or "google"), or the name of a file with style settings. The '
'default is pep8 unless a %s or %s or %s file located in the same '
'directory as the source or one of its parent directories '
'(for stdin, the current directory is used).' %
(style.LOCAL_STYLE, style.SETUP_CONFIG, style.PYPROJECT_TOML)))
help=(
'specify formatting style: either a style name (for example "pep8" '
'or "google"), or the name of a file with style settings. The '
'default is pep8 unless a %s or %s or %s file located in the same '
'directory as the source or one of its parent directories '
'(for stdin, the current directory is used).' %
(style.LOCAL_STYLE, style.SETUP_CONFIG, style.PYPROJECT_TOML)))
parser.add_argument(
'--style-help',
action='store_true',
help=('show style settings and exit; this output can be '
'saved to .style.yapf to make your settings '
'permanent'))
help=(
'show style settings and exit; this output can be '
'saved to .style.yapf to make your settings '
'permanent'))
parser.add_argument(
'--no-local-style',
action='store_true',
@@ -342,8 +349,9 @@ def _BuildParser():
'-p',
'--parallel',
action='store_true',
help=('run YAPF in parallel when formatting multiple files. Requires '
'concurrent.futures in Python 2.X'))
help=(
'run YAPF in parallel when formatting multiple files. Requires '
'concurrent.futures in Python 2.X'))
parser.add_argument(
'-vv',
'--verbose',
22 changes: 11 additions & 11 deletions yapf/pyparser/pyparser.py
Original file line number Diff line number Diff line change
@@ -68,7 +68,7 @@ def ParseCode(unformatted_source, filename='<unknown>'):
ast_tree = ast.parse(unformatted_source, filename)
ast.fix_missing_locations(ast_tree)
readline = py3compat.StringIO(unformatted_source).readline
tokens = tokenize.generate_tokens(readline)
tokens = tokenize.generate_tokens(readline)
except Exception:
raise

@@ -89,18 +89,18 @@ def _CreateLogicalLines(tokens):
Returns:
A list of LogicalLines.
"""
logical_lines = []
logical_lines = []
cur_logical_line = []
prev_tok = None
depth = 0
prev_tok = None
depth = 0

for tok in tokens:
tok = py3compat.TokenInfo(*tok)
if tok.type == tokenize.NEWLINE:
# End of a logical line.
logical_lines.append(logical_line.LogicalLine(depth, cur_logical_line))
cur_logical_line = []
prev_tok = None
prev_tok = None
elif tok.type == tokenize.INDENT:
depth += 1
elif tok.type == tokenize.DEDENT:
@@ -117,29 +117,29 @@ def _CreateLogicalLines(tokens):
line=prev_tok.line)
ctok.lineno = ctok.start[0]
ctok.column = ctok.start[1]
ctok.value = '\\'
ctok.value = '\\'
cur_logical_line.append(format_token.FormatToken(ctok, 'CONTINUATION'))
tok.lineno = tok.start[0]
tok.column = tok.start[1]
tok.value = tok.string
tok.value = tok.string
cur_logical_line.append(
format_token.FormatToken(tok, token.tok_name[tok.type]))
prev_tok = tok

# Link the FormatTokens in each line together to for a doubly linked list.
for line in logical_lines:
previous = line.first
previous = line.first
bracket_stack = [previous] if previous.OpensScope() else []
for tok in line.tokens[1:]:
tok.previous_token = previous
tok.previous_token = previous
previous.next_token = tok
previous = tok
previous = tok

# Set up the "matching_bracket" attribute.
if tok.OpensScope():
bracket_stack.append(tok)
elif tok.ClosesScope():
bracket_stack[-1].matching_bracket = tok
tok.matching_bracket = bracket_stack.pop()
tok.matching_bracket = bracket_stack.pop()

return logical_lines
8 changes: 4 additions & 4 deletions yapf/pyparser/pyparser_utils.py
Original file line number Diff line number Diff line change
@@ -31,8 +31,8 @@

def GetTokens(logical_lines, node):
"""Get a list of tokens within the node's range from the logical lines."""
start = TokenStart(node)
end = TokenEnd(node)
start = TokenStart(node)
end = TokenEnd(node)
tokens = []

for line in logical_lines:
@@ -46,8 +46,8 @@ def GetTokens(logical_lines, node):

def GetTokensInSubRange(tokens, node):
"""Get a subset of tokens representing the node."""
start = TokenStart(node)
end = TokenEnd(node)
start = TokenStart(node)
end = TokenEnd(node)
tokens_in_range = []

for tok in tokens:
Loading