mirror of
https://github.com/langgenius/dify.git
synced 2026-11-10 21:35:45 +08:00
Co-authored-by: Codex <codex@openai.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
"""Reject new __new__ calls and references that bypass constructor checks.
|
|
|
|
Preconditions: Run from the repository root with Git and ast-grep available.
|
|
Use --staged for index contents or --base-rev for committed revisions.
|
|
Without either option, inspect local API changes against the main merge base.
|
|
|
|
Postconditions: Existing matching occurrences retain their Git baseline.
|
|
Identical moves retain that baseline. Copies and changed calls fail.
|
|
Allocation for the first parameter of a class's __new__ method is allowed.
|
|
Nested functions and lambdas do not inherit that allocation permission.
|
|
This syntactic rule does not resolve dynamic attribute access.
|
|
Exit codes are 0 for success, 1 for violations, and 2 for check failures.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
from ast_grep_guard import (
|
|
Violation,
|
|
filter_moved_matches,
|
|
match_key,
|
|
print_violations,
|
|
rule_path,
|
|
run_ast_grep,
|
|
)
|
|
from check_no_new_base_model import working_file_versions
|
|
from check_no_new_test_doubles import changed_file_versions
|
|
|
|
|
|
def find_new_constructor_bypasses(
|
|
versions: dict[str, tuple[str, str]],
|
|
) -> list[Violation]:
|
|
"""Compare constructor access in complete source versions.
|
|
|
|
Preconditions: Each key is a repository-relative path with old and new source.
|
|
Postconditions: Return API violations without changing files or source versions.
|
|
Each removed occurrence offsets one identical addition. Scan failures raise.
|
|
"""
|
|
rule = rule_path("no_new_constructor_bypass.yml")
|
|
removed: Counter[str] = Counter()
|
|
added = []
|
|
for path, (old_source, new_source) in versions.items():
|
|
if Path(path).is_relative_to("api"):
|
|
existing = Counter(match_key(match) for match in run_ast_grep(old_source, rule=rule))
|
|
for match in run_ast_grep(new_source, rule=rule):
|
|
key = match_key(match)
|
|
if existing[key]:
|
|
existing[key] -= 1
|
|
else:
|
|
added.append((path, match))
|
|
removed.update(existing)
|
|
return [
|
|
Violation(
|
|
path,
|
|
match.line_number,
|
|
"no-new-constructor-bypass: use the class constructor to preserve initialization and validation.",
|
|
)
|
|
for path, match in filter_moved_matches(added, removed)
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
"""Precondition: Git and ast-grep are available in the repository root.
|
|
|
|
Postconditions: Report violations without changing repository files.
|
|
Return 0 for success, 1 for violations, or 2 for check failures.
|
|
"""
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
source = parser.add_mutually_exclusive_group()
|
|
source.add_argument("--base-rev", help="Compare this revision with committed HEAD.")
|
|
source.add_argument("--staged", action="store_true", help="Compare staged changes with HEAD.")
|
|
args = parser.parse_args()
|
|
try:
|
|
versions = changed_file_versions(args) if args.base_rev or args.staged else working_file_versions()
|
|
violations = find_new_constructor_bypasses(versions)
|
|
except (OSError, RuntimeError) as error:
|
|
sys.stderr.write(f"{error}\n")
|
|
return 2
|
|
print_violations(sorted(violations, key=lambda item: (item.path, item.line_number)))
|
|
return int(bool(violations))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|