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

Issue 1079: Enable deactivation of style check for individual files #1083

Merged
merged 3 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Memory management in sessions to avoid use of heap (#629)
- Setting of single message fields (#1067)

Specification:

- Enable deactivation of style checks for individual files (#1079)

CLI:

- `rflx` option `--max-errors NUM` (#748)
Expand Down
20 changes: 19 additions & 1 deletion doc/User-Guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,25 @@
:toc:
:numbered:

== Integration files
== Specification Files

=== Style Checks

By default, the style of specification files is checked. Style checks can be disabled for individual files by adding a pragma to the first line of the file. Besides the deactivation of specific checks, it is also possible to disable all checks by using `all`.

*Example*

[source,ada,rflx]
----
-- style: disable = line-length, blank-lines

package P is

...
----


== Integration Files

For each RecordFlux specification file with the `.rflx` file extension, users
may provide a file with the same name but the `.rfi` file extension. This is
Expand Down
7 changes: 6 additions & 1 deletion rflx/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ def create_specifications(self) -> Dict[ID, str]:
def write_specification_files(self, output_dir: Path) -> None:
"""Write corresponding specification files (one per package) into given directory."""
for package, specification in self.create_specifications().items():
(output_dir / f"{package.flat.lower()}.rflx").write_text(specification)
header = (
"-- style: disable = line-length\n\n"
if any(len(l) > 120 for l in specification.split("\n"))
else ""
)
(output_dir / f"{package.flat.lower()}.rflx").write_text(f"{header}{specification}")

def _add_missing_types_and_validate(self) -> None:
error = self._check_duplicates()
Expand Down
116 changes: 96 additions & 20 deletions rflx/specification/style.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import re
from enum import Enum
from pathlib import Path

from rflx.error import Location, RecordFluxError, Severity, Subsystem
Expand All @@ -26,6 +27,16 @@
]


class Check(Enum):
ALL = "all"
BLANK_LINES = "blank-lines"
CHARACTERS = "characters"
INDENTATION = "indentation"
LINE_LENGTH = "line-length"
TOKEN_SPACING = "token-spacing"
TRAILING_SPACES = "trailing-spaces"


def check(spec_file: Path) -> RecordFluxError:
error = RecordFluxError()

Expand All @@ -35,25 +46,43 @@ def check(spec_file: Path) -> RecordFluxError:
if not specification:
return error

blank_lines = 0
lines = specification.split("\n")
enabled_checks = _determine_enabled_checks(error, lines[0], spec_file)

if not enabled_checks:
return error

blank_lines = 0

for i, l in enumerate(lines, start=1):
blank_lines = _check_blank_lines(error, l, i, spec_file, blank_lines, len(lines))
_check_characters(error, l, i, spec_file)
_check_indentation(error, l, i, spec_file)
_check_line_length(error, l, i, spec_file)
_check_token_spacing(error, l, i, spec_file)
_check_trailing_spaces(error, l, i, spec_file)
if Check.BLANK_LINES in enabled_checks:
blank_lines = _check_blank_lines(error, l, i, spec_file, blank_lines, len(lines))
if Check.CHARACTERS in enabled_checks:
_check_characters(error, l, i, spec_file)
if Check.INDENTATION in enabled_checks:
_check_indentation(error, l, i, spec_file)
if Check.LINE_LENGTH in enabled_checks:
_check_line_length(error, l, i, spec_file)
if Check.TOKEN_SPACING in enabled_checks:
_check_token_spacing(error, l, i, spec_file)
if Check.TRAILING_SPACES in enabled_checks:
_check_trailing_spaces(error, l, i, spec_file)

return error


def _append(error: RecordFluxError, message: str, row: int, col: int, spec_file: Path) -> None:
def _append(
error: RecordFluxError,
message: str,
row: int,
col: int,
spec_file: Path,
check_type: Check = None,
) -> None:
error.extend(
[
(
message,
message + (f" [{check_type.value}]" if check_type else ""),
Subsystem.STYLE,
Severity.ERROR,
Location((row, col), spec_file),
Expand All @@ -62,18 +91,36 @@ def _append(error: RecordFluxError, message: str, row: int, col: int, spec_file:
)


def _determine_enabled_checks(error: RecordFluxError, line: str, spec_file: Path) -> set[Check]:
checks = {c.value for c in Check.__members__.values()}
disabled_checks = set()

m = re.match(r"^\s*--\s*style\s*:\s*disable\s*=\s*([^.]*)$", line)
if m:
disabled_checks = {c.strip() for c in m.group(1).split(",")}
for c in disabled_checks - checks:
_append(error, f'invalid check "{c}"', 1, 1, spec_file)
else:
return set(Check.__members__.values())

if Check.ALL.value in disabled_checks:
return set()

return {Check(c) for c in checks - disabled_checks}


def _check_blank_lines(
error: RecordFluxError, line: str, row: int, spec_file: Path, blank_lines: int, row_count: int
) -> int:
if line == "":
if row == 1:
_append(error, "leading blank line", row, 1, spec_file)
_append(error, "leading blank line", row, 1, spec_file, Check.BLANK_LINES)
if blank_lines > 0 and row == row_count:
_append(error, "trailing blank line", row - 1, 1, spec_file)
_append(error, "trailing blank line", row - 1, 1, spec_file, Check.BLANK_LINES)
blank_lines += 1
else:
if blank_lines > 1:
_append(error, "multiple blank lines", row - 1, 1, spec_file)
_append(error, "multiple blank lines", row - 1, 1, spec_file, Check.BLANK_LINES)
blank_lines = 0

return blank_lines
Expand All @@ -83,10 +130,10 @@ def _check_characters(error: RecordFluxError, line: str, row: int, spec_file: Pa
for j, c in enumerate(line, start=1):
if c == INCORRECT_LINE_TERMINATORS:
s = repr(c).replace("'", '"')
_append(error, f"incorrect line terminator {s}", row, j, spec_file)
_append(error, f"incorrect line terminator {s}", row, j, spec_file, Check.CHARACTERS)
if c in ILLEGAL_WHITESPACE_CHARACTERS:
s = repr(c).replace("'", '"')
_append(error, f"illegal whitespace character {s}", row, j, spec_file)
_append(error, f"illegal whitespace character {s}", row, j, spec_file, Check.CHARACTERS)


def _check_indentation(error: RecordFluxError, line: str, row: int, spec_file: Path) -> None:
Expand All @@ -107,6 +154,7 @@ def _check_indentation(error: RecordFluxError, line: str, row: int, spec_file: P
row,
match.end(),
spec_file,
Check.INDENTATION,
)


Expand Down Expand Up @@ -143,23 +191,51 @@ def _check_token_spacing(error: RecordFluxError, line: str, row: int, spec_file:
if space_before:
assert token != ";"
if match.start() > 1 and line[match.start() - 1] not in " (":
_append(error, f'missing space before "{token}"', row, match.start() + 1, spec_file)
_append(
error,
f'missing space before "{token}"',
row,
match.start() + 1,
spec_file,
Check.TOKEN_SPACING,
)
else:
if match.start() > 1 and line[match.start() - 1] == " ":
_append(error, f'space before "{token}"', row, match.start() + 1, spec_file)
_append(
error,
f'space before "{token}"',
row,
match.start() + 1,
spec_file,
Check.TOKEN_SPACING,
)
if space_after:
if match.end() < len(line) and line[match.end()] not in " ;\n":
_append(error, f'missing space after "{token}"', row, match.end() + 1, spec_file)
_append(
error,
f'missing space after "{token}"',
row,
match.end() + 1,
spec_file,
Check.TOKEN_SPACING,
)
else:
if match.end() < len(line) and line[match.end()] == " ":
_append(error, f'space after "{token}"', row, match.end() + 2, spec_file)
_append(
error,
f'space after "{token}"',
row,
match.end() + 2,
spec_file,
Check.TOKEN_SPACING,
)


def _check_trailing_spaces(error: RecordFluxError, line: str, row: int, spec_file: Path) -> None:
if line.endswith(" "):
_append(error, "trailing whitespace", row, len(line), spec_file)
_append(error, "trailing whitespace", row, len(line), spec_file, Check.TRAILING_SPACES)


def _check_line_length(error: RecordFluxError, line: str, row: int, spec_file: Path) -> None:
if len(line) > 120:
_append(error, f"line too long ({len(line)}/120)", row, 121, spec_file)
_append(error, f"line too long ({len(line)}/120)", row, 121, spec_file, Check.LINE_LENGTH)
8 changes: 8 additions & 0 deletions tests/unit/model/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,11 @@ def test_write_specification_file_multiple_packages_missing_deps(tmp_path: Path)

end R;"""
)


def test_write_specification_files_line_too_long(tmp_path: Path) -> None:
t = ModularInteger("P::" + "T" * 120, Number(256))
Model([t]).write_specification_files(tmp_path)
expected_path = tmp_path / Path("p.rflx")
assert list(tmp_path.glob("*.rflx")) == [expected_path]
assert expected_path.read_text().startswith("-- style: disable = line-length\n\npackage P is")
Loading