feat: also ensure correct file structure
This commit is contained in:
parent
d0f2df9bae
commit
95992d85d6
1 changed files with 120 additions and 0 deletions
|
|
@ -50,6 +50,7 @@ SECTION_NAMES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
RULE_COLOURS = {
|
RULE_COLOURS = {
|
||||||
|
"file-structure": RED,
|
||||||
"import-order": GREEN,
|
"import-order": GREEN,
|
||||||
"section-order": YELLOW,
|
"section-order": YELLOW,
|
||||||
"missing-section-separator": CYAN,
|
"missing-section-separator": CYAN,
|
||||||
|
|
@ -175,6 +176,123 @@ def fix_imports(lines: list[str]) -> list[str]:
|
||||||
return lines[:first] + sorted_imports + lines[last + 1 :]
|
return lines[:first] + sorted_imports + lines[last + 1 :]
|
||||||
|
|
||||||
|
|
||||||
|
def check_file_structure(lines: list[str], rel: str) -> list[Violation]:
|
||||||
|
"""Check file-level structure: pragmas, then imports, then content."""
|
||||||
|
violations = []
|
||||||
|
pragma_indices: list[int] = []
|
||||||
|
import_indices: list[int] = []
|
||||||
|
content_start: int | None = None
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped:
|
||||||
|
continue
|
||||||
|
if stripped.startswith("pragma "):
|
||||||
|
pragma_indices.append(i)
|
||||||
|
elif IMPORT_RE.match(stripped):
|
||||||
|
import_indices.append(i)
|
||||||
|
else:
|
||||||
|
content_start = i
|
||||||
|
break
|
||||||
|
|
||||||
|
# Pragmas must come before imports
|
||||||
|
if pragma_indices and import_indices:
|
||||||
|
if pragma_indices[-1] > import_indices[0]:
|
||||||
|
violations.append(
|
||||||
|
Violation(rel, pragma_indices[-1] + 1, "file-structure", "pragmas should appear before imports")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Separator between pragmas and imports
|
||||||
|
if pragma_indices and import_indices:
|
||||||
|
gap = import_indices[0] - pragma_indices[-1] - 1
|
||||||
|
if gap == 0:
|
||||||
|
violations.append(
|
||||||
|
Violation(
|
||||||
|
rel, import_indices[0] + 1, "file-structure", "blank line expected between pragmas and imports"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif gap > 1:
|
||||||
|
violations.append(
|
||||||
|
Violation(
|
||||||
|
rel,
|
||||||
|
pragma_indices[-1] + 3,
|
||||||
|
"file-structure",
|
||||||
|
"only one blank line expected between pragmas and imports",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# No blank lines within imports
|
||||||
|
for j in range(1, len(import_indices)):
|
||||||
|
if import_indices[j] != import_indices[j - 1] + 1:
|
||||||
|
for gap_line in range(import_indices[j - 1] + 1, import_indices[j]):
|
||||||
|
if not lines[gap_line].strip():
|
||||||
|
violations.append(
|
||||||
|
Violation(rel, gap_line + 1, "file-structure", "no blank lines expected within imports")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Separator between imports/pragmas and content
|
||||||
|
last_header = import_indices[-1] if import_indices else (pragma_indices[-1] if pragma_indices else None)
|
||||||
|
if last_header is not None and content_start is not None:
|
||||||
|
gap = content_start - last_header - 1
|
||||||
|
label = "imports" if import_indices else "pragmas"
|
||||||
|
if gap == 0:
|
||||||
|
violations.append(
|
||||||
|
Violation(
|
||||||
|
rel, content_start + 1, "file-structure", f"blank line expected between {label} and content"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif gap > 1:
|
||||||
|
violations.append(
|
||||||
|
Violation(
|
||||||
|
rel,
|
||||||
|
last_header + 3,
|
||||||
|
"file-structure",
|
||||||
|
f"only one blank line expected between {label} and content",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return violations
|
||||||
|
|
||||||
|
|
||||||
|
def fix_file_structure(lines: list[str]) -> list[str]:
|
||||||
|
"""Ensure correct file structure: pragmas, blank, imports (no gaps), blank, content."""
|
||||||
|
pragmas: list[str] = []
|
||||||
|
imports: list[str] = []
|
||||||
|
content_start: int | None = None
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped:
|
||||||
|
continue
|
||||||
|
if stripped.startswith("pragma "):
|
||||||
|
pragmas.append(line)
|
||||||
|
elif IMPORT_RE.match(stripped):
|
||||||
|
imports.append(line)
|
||||||
|
else:
|
||||||
|
content_start = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if content_start is None:
|
||||||
|
content_start = len(lines)
|
||||||
|
|
||||||
|
# Skip any blank lines at the start of content
|
||||||
|
while content_start < len(lines) and not lines[content_start].strip():
|
||||||
|
content_start += 1
|
||||||
|
|
||||||
|
result: list[str] = []
|
||||||
|
has_content = content_start < len(lines)
|
||||||
|
if pragmas:
|
||||||
|
result.extend(pragmas)
|
||||||
|
if imports or has_content:
|
||||||
|
result.append("")
|
||||||
|
if imports:
|
||||||
|
result.extend(imports)
|
||||||
|
if has_content:
|
||||||
|
result.append("")
|
||||||
|
result.extend(lines[content_start:])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def fix_section_separators(lines: list[str]) -> list[str]:
|
def fix_section_separators(lines: list[str]) -> list[str]:
|
||||||
"""Insert blank lines between different sections and return modified lines."""
|
"""Insert blank lines between different sections and return modified lines."""
|
||||||
insertions: list[int] = []
|
insertions: list[int] = []
|
||||||
|
|
@ -265,6 +383,7 @@ def fix_file(filepath: Path) -> bool:
|
||||||
|
|
||||||
lines = text.splitlines()
|
lines = text.splitlines()
|
||||||
lines = fix_imports(lines)
|
lines = fix_imports(lines)
|
||||||
|
lines = fix_file_structure(lines)
|
||||||
lines = fix_section_separators(lines)
|
lines = fix_section_separators(lines)
|
||||||
new_text = "\n".join(lines)
|
new_text = "\n".join(lines)
|
||||||
if text.endswith("\n"):
|
if text.endswith("\n"):
|
||||||
|
|
@ -358,6 +477,7 @@ def check_file(filepath: Path) -> list[Violation]:
|
||||||
except (OSError, UnicodeDecodeError):
|
except (OSError, UnicodeDecodeError):
|
||||||
return violations
|
return violations
|
||||||
|
|
||||||
|
violations.extend(check_file_structure(lines, rel))
|
||||||
violations.extend(check_imports(filepath, lines, rel))
|
violations.extend(check_imports(filepath, lines, rel))
|
||||||
|
|
||||||
scopes: dict[str, ScopeTracker] = {} # indent -> tracker
|
scopes: dict[str, ScopeTracker] = {} # indent -> tracker
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue