Skip to content

Syntax gate

syntax_gate

Refuse syntax regressions in Python, TOML and JSON before writing.

The gate compares the file as it is on disk with the content the batch would write, both in memory, so a refusal happens before a single byte is written. It only guards against a regression: a file that did not parse before the batch is never blocked, so an edit repairing it stays possible. New Python and TOML files must parse; new JSON files are unchecked. JSON comments and trailing commas are tolerated. Data parser recursion limits produce no verdict and the content is considered parseable.

Python parsing uses :func:ast.parse of the running interpreter, which must be at least as recent as the edited project's syntax.

syntax_regression(file_rel, before, after)

Report a batch turning a supported file into unparseable content.

Parameters:

Name Type Description Default
file_rel str

Path of the file, relative to the batch root.

required
before str | None

Current content, None when the file does not exist yet.

required
after str

Content the batch would write.

required

Returns:

Type Description
ValidationError | None

A validation error naming the first syntax error of after, or

ValidationError | None

None for unsupported formats, valid content, already-broken files,

ValidationError | None

or new files whose format policy skips checking.

Source code in packages/axm-edit/src/axm_edit/core/syntax_gate.py
Python
def syntax_regression(
    file_rel: str,
    before: str | None,
    after: str,
) -> ValidationError | None:
    """Report a batch turning a supported file into unparseable content.

    Args:
        file_rel: Path of the file, relative to the batch root.
        before: Current content, ``None`` when the file does not exist yet.
        after: Content the batch would write.

    Returns:
        A validation error naming the first syntax error of *after*, or
        ``None`` for unsupported formats, valid content, already-broken files,
        or new files whose format policy skips checking.
    """
    format_spec = next(
        (spec for suffix, spec in _FORMATS.items() if file_rel.endswith(suffix)), None
    )
    if format_spec is None:
        return None
    if before is None:
        if not format_spec.check_new:
            return None
    elif format_spec.checker(before) is not None:
        return None
    failure = format_spec.checker(after)
    if failure is None:
        return None
    where = f" at line {failure.line}" if failure.line else ""
    return ValidationError(
        file=file_rel,
        line=failure.line,
        error=(
            f"Batch would break {format_spec.name} syntax{where}: {failure.reason}."
            f" Nothing was written; {format_spec.hint}"
        ),
    )