|
|
|
|
|
from typing import NamedTuple, Callable, List, Tuple, Optional |
|
|
import re |
|
|
|
|
|
class LintIssue(NamedTuple): |
|
|
code: str |
|
|
msg: str |
|
|
span: Tuple[int,int] |
|
|
autofix: Optional[Callable[[str], str]] = None |
|
|
severity: str = "warning" |
|
|
|
|
|
_LINT_RULES: List[Callable[[str], List[LintIssue]]] = [] |
|
|
|
|
|
def register_rule(fn): |
|
|
_LINT_RULES.append(fn) |
|
|
return fn |
|
|
|
|
|
def run_lints(text: str) -> List[LintIssue]: |
|
|
issues: List[LintIssue] = [] |
|
|
for rule in _LINT_RULES: |
|
|
try: |
|
|
issues.extend(rule(text)) |
|
|
except Exception: |
|
|
|
|
|
continue |
|
|
return issues |
|
|
|
|
|
@register_rule |
|
|
def r_break_spacing(s: str): |
|
|
issues = [] |
|
|
for m in re.finditer(r'(?<!\s)(BREAK)(?!\s)', s): |
|
|
def fix(src, i=m.start(), j=m.end()): |
|
|
return src[:i] + " BREAK " + src[j:] |
|
|
issues.append(LintIssue("BREAK_SPACE","Добавьте пробелы вокруг BREAK",(m.start(),m.end()), |
|
|
autofix=fix, severity="info")) |
|
|
return issues |
|
|
|
|
|
@register_rule |
|
|
def r_top_level_terminator(s: str): |
|
|
|
|
|
if "::" in s and "!!" not in s: |
|
|
def fix(src): return src.rstrip() + " !!" |
|
|
return [LintIssue("TOPLEVEL_TERM","Top-level без '!!'", (len(s),len(s)), fix, "warning")] |
|
|
return [] |
|
|
|
|
|
@register_rule |
|
|
def r_numbered_marker(s: str): |
|
|
|
|
|
pat = re.compile(r'(\d+)([!_])\s*\{([^}]*)\}') |
|
|
issues = [] |
|
|
for m in pat.finditer(s): |
|
|
options = [t.strip() for t in m.group(3).split("|") if t.strip()] |
|
|
if len(options) < 2: |
|
|
issues.append(LintIssue("NUM_MARKER_TOO_FEW","Маркер distinct при <2 опций",(m.start(),m.end()), None,"warning")) |
|
|
return issues |
|
|
|