|
|
| 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 |
|
|