Skip to content

Commit

Permalink
Add '--use-spaces=<int>' option to formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
Scony committed Nov 19, 2023
1 parent 138e999 commit 8b66d13
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Added support for `breakpoint` statement
- Added support for function-level annotations
- Added support for typed `for` loop iterator (#241)
- Added the `--use-spaces=<int>` option to `gdformat` so that space-based indentations can be used instead of tab-based ones

### Changed
- Fixed `max-public-methods` linter check disabling (#222)
Expand Down
3 changes: 3 additions & 0 deletions gdtoolkit/formatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
)


# pylint: disable-next=too-many-arguments
def check_formatting_safety(
given_code: str,
formatted_code: str,
max_line_length: int,
given_code_parse_tree: Optional[Tree] = None,
given_code_comment_parse_tree: Optional[Tree] = None,
spaces_for_indent: Optional[int] = None,
) -> None:
if given_code == formatted_code:
return
Expand All @@ -40,4 +42,5 @@ def check_formatting_safety(
max_line_length,
parse_tree=formatted_code_parse_tree,
comment_parse_tree=formatted_code_comment_parse_tree,
spaces_for_indent=spaces_for_indent,
)
54 changes: 41 additions & 13 deletions gdtoolkit/formatter/__main__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""GDScript formatter
Uncompromising GDScript code formatter. The only configurable thing is
max line length allowed. The rest will be taken care of by gdformat in a one,
consistent way.
max line length allowed and tabs/spaces indent. The rest will be taken
care of by gdformat in a one, consistent way.
Usage:
gdformat <path>... [options]
Expand All @@ -16,6 +16,7 @@
-f --fast Skip safety checks.
-l --line-length=<int> How many characters per line to allow.
[default: 100]
-s --use-spaces=<int> Use spaces for indent instead of tabs.
-h --help Show this screen.
--version Show version.
Expand All @@ -24,7 +25,7 @@
"""
import sys
import difflib
from typing import List, Tuple
from typing import List, Tuple, Optional
import pkg_resources

from docopt import docopt
Expand Down Expand Up @@ -57,29 +58,45 @@ def main():
arguments["--check"] = True

line_length = int(arguments["--line-length"])
spaces_for_indent = (
int(arguments["--use-spaces"])
if arguments["--use-spaces"] is not None
else None
)
safety_checks = not arguments["--fast"]
files: List[str] = find_gd_files_from_paths(
arguments["<path>"], excluded_directories=set(".git")
)

if files == ["-"]:
_format_stdin(line_length, safety_checks)
_format_stdin(line_length, spaces_for_indent, safety_checks)
elif arguments["--check"]:
_check_files_formatting(files, line_length, arguments["--diff"], safety_checks)
_check_files_formatting(
files, line_length, spaces_for_indent, arguments["--diff"], safety_checks
)
else:
_format_files(files, line_length, safety_checks)
_format_files(files, line_length, spaces_for_indent, safety_checks)


def _format_stdin(line_length: int, safety_checks: bool) -> None:
def _format_stdin(
line_length: int, spaces_for_indent: Optional[int], safety_checks: bool
) -> None:
code = sys.stdin.read()
success, _, formatted_code = _format_code(code, line_length, "STDIN", safety_checks)
success, _, formatted_code = _format_code(
code, line_length, spaces_for_indent, "STDIN", safety_checks
)
if not success:
sys.exit(1)
print(formatted_code, end="")


# pylint: disable-next=too-many-locals
def _check_files_formatting(
files: List[str], line_length: int, print_diff: bool, safety_checks: bool
files: List[str],
line_length: int,
spaces_for_indent: Optional[int],
print_diff: bool,
safety_checks: bool,
) -> None:
formattable_files = set()
failed_files = set()
Expand All @@ -88,7 +105,7 @@ def _check_files_formatting(
with open(file_path, "r", encoding="utf-8") as handle:
code = handle.read()
success, actually_formatted, formatted_code = _format_code(
code, line_length, file_path, safety_checks
code, line_length, spaces_for_indent, file_path, safety_checks
)
if success and actually_formatted:
print(f"would reformat {file_path}", file=sys.stderr)
Expand Down Expand Up @@ -135,15 +152,20 @@ def _check_files_formatting(
sys.exit(1)


def _format_files(files: List[str], line_length: int, safety_checks: bool) -> None:
def _format_files(
files: List[str],
line_length: int,
spaces_for_indent: Optional[int],
safety_checks: bool,
) -> None:
formatted_files = set()
failed_files = set()
for file_path in files:
try:
with open(file_path, "r+", encoding="utf-8") as handle:
code = handle.read()
success, actually_formatted, formatted_code = _format_code(
code, line_length, file_path, safety_checks
code, line_length, spaces_for_indent, file_path, safety_checks
)
if success and actually_formatted:
print(f"reformatted {file_path}")
Expand Down Expand Up @@ -173,7 +195,11 @@ def _format_files(files: List[str], line_length: int, safety_checks: bool) -> No


def _format_code(
code: str, line_length: int, file_path: str, safety_checks: bool
code: str,
line_length: int,
spaces_for_indent: Optional[int],
file_path: str,
safety_checks: bool,
) -> Tuple[bool, bool, str]:
success = True
actually_formatted = False
Expand All @@ -185,6 +211,7 @@ def _format_code(
formatted_code = format_code(
gdscript_code=code,
max_line_length=line_length,
spaces_for_indent=spaces_for_indent,
parse_tree=code_parse_tree,
comment_parse_tree=comment_parse_tree,
)
Expand All @@ -195,6 +222,7 @@ def _format_code(
code,
formatted_code,
max_line_length=line_length,
spaces_for_indent=spaces_for_indent,
given_code_parse_tree=code_parse_tree,
given_code_comment_parse_tree=comment_parse_tree,
)
Expand Down
4 changes: 2 additions & 2 deletions gdtoolkit/formatter/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .types import Outcome, FormattedLines
from .context import Context
from .constants import (
INDENT_SIZE,
TAB_INDENT_SIZE,
DEFAULT_SURROUNDING_EMPTY_LINES_TABLE as DEFAULT_SURROUNDINGS_TABLE,
)
from .annotation import (
Expand Down Expand Up @@ -109,7 +109,7 @@ def _find_dedent_line_number(
if (
line.startswith("\t")
and re.search(
r"^\t{0,%d}[^\t]+" % ((context.indent / INDENT_SIZE) - 1), line
r"^\t{0,%d}[^\t]+" % ((context.indent / TAB_INDENT_SIZE) - 1), line
)
is not None
):
Expand Down
3 changes: 1 addition & 2 deletions gdtoolkit/formatter/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from types import MappingProxyType


INDENT_STRING = "\t"
INDENT_SIZE = 4
TAB_INDENT_SIZE = 4
INLINE_COMMENT_OFFSET = 2

DEFAULT_SURROUNDING_EMPTY_LINES_TABLE = MappingProxyType(
Expand Down
18 changes: 13 additions & 5 deletions gdtoolkit/formatter/context.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
import re
from typing import List, Optional
from dataclasses import dataclass

from lark import Tree

from .constants import INDENT_STRING, INDENT_SIZE


# pylint: disable=too-many-arguments
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-few-public-methods
class Context:
def __init__(
self,
indent: int,
previously_processed_line_number: int,
single_indent_size: int,
single_indent_string: str,
max_line_length: int,
gdscript_code_lines: List[str],
standalone_comments: List[Optional[str]],
inline_comments: List[Optional[str]],
indent: int = 0,
):
self.single_indent = single_indent_size
self.single_indent_string = single_indent_string
self.indent = indent
self.indent_string = self.single_indent_string * (
self.indent // self.single_indent
)
self.indent_regex = re.compile(f"^{self.single_indent_string[0]}+")
self.previously_processed_line_number = previously_processed_line_number
self.max_line_length = max_line_length
self.gdscript_code_lines = gdscript_code_lines
self.standalone_comments = standalone_comments
self.inline_comments = inline_comments
self.indent_string = INDENT_STRING * (self.indent // INDENT_SIZE)
self.annotations = [] # type: List[Tree]

def create_child_context(self, previously_processed_line_number: int):
return Context(
indent=self.indent + INDENT_SIZE,
single_indent_size=self.single_indent,
single_indent_string=self.single_indent_string,
previously_processed_line_number=previously_processed_line_number,
max_line_length=self.max_line_length,
gdscript_code_lines=self.gdscript_code_lines,
standalone_comments=self.standalone_comments,
inline_comments=self.inline_comments,
indent=self.indent + self.single_indent,
)


Expand Down
6 changes: 3 additions & 3 deletions gdtoolkit/formatter/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
is_trailing_comma,
)
from .expression_to_str import expression_to_str
from .constants import INDENT_SIZE
from .constants import TAB_INDENT_SIZE


def format_expression(
Expand Down Expand Up @@ -91,7 +91,7 @@ def _format_foldable(
single_line_number, single_line = _format_concrete_expression_to_single_line(
expression, expression_context, context
)[0]
single_line_length = len(single_line.replace("\t", " " * INDENT_SIZE))
single_line_length = len(single_line.replace("\t", " " * TAB_INDENT_SIZE))
if single_line_length <= context.max_line_length:
return [(single_line_number, single_line)]
return _format_foldable_to_multiple_lines(expression, expression_context, context)
Expand Down Expand Up @@ -821,7 +821,7 @@ def _format_dot_chain_to_multiple_lines(
dot_chain, expression_context, context
)
if all(
len(line.replace("\t", " " * INDENT_SIZE)) <= context.max_line_length
len(line.replace("\t", " " * TAB_INDENT_SIZE)) <= context.max_line_length
for line_number, line in lines_formatted_bottom_up
):
return lines_formatted_bottom_up
Expand Down
32 changes: 22 additions & 10 deletions gdtoolkit/formatter/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

from ..parser import parser
from .context import Context
from .constants import INLINE_COMMENT_OFFSET, GLOBAL_SCOPE_SURROUNDING_EMPTY_LINES_TABLE
from .constants import (
TAB_INDENT_SIZE,
INLINE_COMMENT_OFFSET,
GLOBAL_SCOPE_SURROUNDING_EMPTY_LINES_TABLE,
)
from .types import FormattedLines
from .block import format_block
from .class_statement import format_class_statement
Expand All @@ -14,12 +18,11 @@
gather_inline_comments,
)

INDENT_REGEX = re.compile(r"^\t+")


def format_code(
gdscript_code: str,
max_line_length: int,
spaces_for_indent: Optional[int] = None,
parse_tree: Optional[Tree] = None,
comment_parse_tree: Optional[Tree] = None,
) -> str:
Expand All @@ -38,8 +41,15 @@ def format_code(
*gdscript_code.splitlines(),
] # type: List[str]
formatted_lines = [] # type: FormattedLines
single_indent_size = (
TAB_INDENT_SIZE if spaces_for_indent is None else spaces_for_indent
)
single_indent_string = (
"\t" if spaces_for_indent is None else " " * spaces_for_indent
)
context = Context(
indent=0,
single_indent_size=single_indent_size,
single_indent_string=single_indent_string,
previously_processed_line_number=0,
max_line_length=max_line_length,
gdscript_code_lines=gdscript_code_lines,
Expand All @@ -57,7 +67,7 @@ def format_code(
formatted_lines.append((None, ""))
formatted_lines = _add_inline_comments(formatted_lines, context.inline_comments)
formatted_lines = _add_standalone_comments(
formatted_lines, context.standalone_comments
formatted_lines, context.standalone_comments, context.indent_regex
)
return "\n".join([line for _, line in formatted_lines])

Expand Down Expand Up @@ -87,7 +97,9 @@ def _add_inline_comments(


def _add_standalone_comments(
formatted_lines: FormattedLines, standalone_comments: List[Optional[str]]
formatted_lines: FormattedLines,
standalone_comments: List[Optional[str]],
indent_regex: re.Pattern,
) -> FormattedLines:
remaining_comments = standalone_comments[:]
postprocessed_lines = [] # type: FormattedLines
Expand All @@ -106,7 +118,7 @@ def _add_standalone_comments(
continue
comments = remaining_comments[line_no:last_experssion_line_no]
remaining_comments = remaining_comments[:line_no]
indent = _get_greater_indent(line, postprocessed_lines[-1][1])
indent = _get_greater_indent(line, postprocessed_lines[-1][1], indent_regex)
postprocessed_lines += [
(None, f"{indent}{comment}")
for comment in reversed(comments)
Expand All @@ -117,9 +129,9 @@ def _add_standalone_comments(
return list(reversed(postprocessed_lines))


def _get_greater_indent(line_a: str, line_b: str):
line_a_match = INDENT_REGEX.search(line_a)
line_b_match = INDENT_REGEX.search(line_b)
def _get_greater_indent(line_a: str, line_b: str, indent_regex: re.Pattern):
line_a_match = indent_regex.search(line_a)
line_b_match = indent_regex.search(line_b)
line_a_indent = "" if line_a_match is None else line_a_match.group(0)
line_b_indent = "" if line_b_match is None else line_b_match.group(0)
return line_a_indent if len(line_a_indent) > len(line_b_indent) else line_b_indent
2 changes: 2 additions & 0 deletions gdtoolkit/formatter/safety_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,14 @@ def check_formatting_stability(
max_line_length: int,
parse_tree: Optional[Tree] = None,
comment_parse_tree: Optional[Tree] = None,
spaces_for_indent: Optional[int] = None,
) -> None:
code_formatted_again = format_code(
formatted_code,
max_line_length,
parse_tree=parse_tree,
comment_parse_tree=comment_parse_tree,
spaces_for_indent=spaces_for_indent,
)
if formatted_code != code_formatted_again:
diff = "\n".join(
Expand Down
3 changes: 2 additions & 1 deletion gdtoolkit/gd2py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ def convert_code(gdscript_code: str) -> str:
gdscript_code, gather_metadata=True
) # TODO: is metadata needed?
context = Context(
indent=0,
single_indent_size=1,
single_indent_string="\t",
previously_processed_line_number=-1,
max_line_length=-1,
gdscript_code_lines=[],
Expand Down
Loading

0 comments on commit 8b66d13

Please sign in to comment.