From d96351ea53afb613f352fc262b4a7b8f626ef1f6 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 20 Oct 2025 13:42:02 -0400 Subject: [PATCH 01/19] Initial architecture and code for aws-cli-linter. --- awsclilinter/.gitignore | 27 ++++ awsclilinter/Makefile | 21 +++ awsclilinter/README.md | 104 ++++++++++++ awsclilinter/awsclilinter/__init__.py | 1 + awsclilinter/awsclilinter/cli.py | 108 +++++++++++++ awsclilinter/awsclilinter/linter.py | 33 ++++ awsclilinter/awsclilinter/rules/__init__.py | 3 + .../awsclilinter/rules/base64_rule.py | 52 ++++++ awsclilinter/awsclilinter/rules_base.py | 36 +++++ awsclilinter/examples/upload_s3_files.sh | 13 ++ awsclilinter/pyproject.toml | 29 ++++ awsclilinter/requirements-dev.lock | 11 ++ awsclilinter/requirements-dev.txt | 4 + awsclilinter/requirements.lock | 1 + awsclilinter/requirements.txt | 1 + awsclilinter/tests/__init__.py | 0 awsclilinter/tests/test_cli.py | 150 ++++++++++++++++++ awsclilinter/tests/test_linter.py | 43 +++++ awsclilinter/tests/test_rules.py | 41 +++++ 19 files changed, 678 insertions(+) create mode 100644 awsclilinter/.gitignore create mode 100644 awsclilinter/Makefile create mode 100644 awsclilinter/README.md create mode 100644 awsclilinter/awsclilinter/__init__.py create mode 100644 awsclilinter/awsclilinter/cli.py create mode 100644 awsclilinter/awsclilinter/linter.py create mode 100644 awsclilinter/awsclilinter/rules/__init__.py create mode 100644 awsclilinter/awsclilinter/rules/base64_rule.py create mode 100644 awsclilinter/awsclilinter/rules_base.py create mode 100644 awsclilinter/examples/upload_s3_files.sh create mode 100644 awsclilinter/pyproject.toml create mode 100644 awsclilinter/requirements-dev.lock create mode 100644 awsclilinter/requirements-dev.txt create mode 100644 awsclilinter/requirements.lock create mode 100644 awsclilinter/requirements.txt create mode 100644 awsclilinter/tests/__init__.py create mode 100644 awsclilinter/tests/test_cli.py create mode 100644 awsclilinter/tests/test_linter.py create mode 100644 awsclilinter/tests/test_rules.py diff --git a/awsclilinter/.gitignore b/awsclilinter/.gitignore new file mode 100644 index 000000000000..c491fb8c56a9 --- /dev/null +++ b/awsclilinter/.gitignore @@ -0,0 +1,27 @@ +*.py[co] +*.DS_Store + +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ +.pytest_cache/ + +# Unit test / coverage reports +.coverage +htmlcov/ + +# Virtualenvs +.venv/ +venv/ +env/ + +# Keep lockfiles +!*.lock + +# Pyenv +.python-version diff --git a/awsclilinter/Makefile b/awsclilinter/Makefile new file mode 100644 index 000000000000..d880ce1bfce4 --- /dev/null +++ b/awsclilinter/Makefile @@ -0,0 +1,21 @@ +.PHONY: setup test format lint clean + +setup: + ./setup.sh + +test: + pytest tests/ -v + +format: + black awsclilinter tests + isort awsclilinter tests + +lint: + black --check awsclilinter tests + isort --check awsclilinter tests + +clean: + rm -rf venv + rm -rf build dist *.egg-info + find . -type d -name __pycache__ -exec rm -rf {} + + find . -type f -name "*.pyc" -delete diff --git a/awsclilinter/README.md b/awsclilinter/README.md new file mode 100644 index 000000000000..6dc8ad722eac --- /dev/null +++ b/awsclilinter/README.md @@ -0,0 +1,104 @@ +# AWS CLI Linter + +A CLI tool that lints bash scripts for AWS CLI v1 usage and updates them to avoid breaking changes introduced in AWS CLI v2. + +## Installation + +1. Create a virtual environment: +```bash +python3.12 -m venv venv +source venv/bin/activate +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +# Or use lockfile for reproducible builds: +pip install -r requirements.lock +``` + +3. Install the package in development mode: +```bash +pip install -e . +``` + +## Usage + +### Dry-run mode (default) +Display issues without modifying the script: +```bash +upgrade-aws-cli --script upload_s3_files.sh +``` + +### Fix mode +Automatically update the input script: +```bash +upgrade-aws-cli --script upload_s3_files.sh --fix +``` + +### Output mode +Create a new fixed script without modifying the original: +```bash +upgrade-aws-cli --script upload_s3_files.sh --output upload_s3_files_v2.sh +``` + +### Interactive mode +Review and accept/reject each change individually: +```bash +upgrade-aws-cli --script upload_s3_files.sh --interactive --output upload_s3_files_v2.sh +``` + +In interactive mode, you can: +- Press `y` to accept the current change +- Press `n` to skip the current change +- Press `u` to accept all remaining changes +- Press `x` to cancel and exit + +Note: `--interactive` requires either `--output` to specify where to write changes, or no output flag for dry-run. It cannot be used with `--fix`. + +## Development + +### Running tests +```bash +pytest +``` + +### Code formatting +```bash +black awsclilinter tests +isort awsclilinter tests +``` + +## Adding New Linting Rules + +To add a new linting rule: + +1. Create a new rule class in `awsclilinter/rules/` that inherits from `LintRule` +2. Implement the required methods: `name`, `description`, and `check` +3. Add the rule to the rules list in `awsclilinter/cli.py` + +Example: +```python +from awsclilinter.rules_base import LintRule, LintFinding + +class MyCustomRule(LintRule): + @property + def name(self) -> str: + return "my-custom-rule" + + @property + def description(self) -> str: + return "Description of what this rule checks" + + def check(self, root) -> List[LintFinding]: + # Implementation using ast-grep + pass +``` + +## Architecture + +- `rules_base.py`: Base classes for linting rules (`LintRule`, `LintFinding`) +- `rules/`: Directory containing individual linting rule implementations +- `linter.py`: Main `ScriptLinter` class that orchestrates rule checking +- `cli.py`: CLI interface using argparse +- `tests/`: Unit tests using pytest diff --git a/awsclilinter/awsclilinter/__init__.py b/awsclilinter/awsclilinter/__init__.py new file mode 100644 index 000000000000..3dc1f76bc69e --- /dev/null +++ b/awsclilinter/awsclilinter/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py new file mode 100644 index 000000000000..9ed039598a5c --- /dev/null +++ b/awsclilinter/awsclilinter/cli.py @@ -0,0 +1,108 @@ +import argparse +import sys +from pathlib import Path +from typing import List + +from awsclilinter.linter import ScriptLinter +from awsclilinter.rules.base64_rule import Base64BinaryFormatRule +from awsclilinter.rules_base import LintFinding + + +def get_user_choice(prompt: str) -> str: + """Get user input for interactive mode.""" + while True: + choice = input(prompt).lower().strip() + if choice in ["y", "n", "u", "x"]: + return choice + print("Invalid choice. Please enter y, n, u, or x.") + + +def display_finding(finding: LintFinding, index: int, total: int): + """Display a finding to the user.""" + print(f"\n[{index}/{total}] {finding.rule_name}") + print(f"Lines {finding.line_start}-{finding.line_end}: {finding.description}") + print(f"\nOriginal:\n {finding.original_text}") + print(f"\nSuggested:\n {finding.suggested_fix}") + + +def interactive_mode(findings: List[LintFinding]) -> List[LintFinding]: + """Run interactive mode and return accepted findings.""" + accepted = [] + for i, finding in enumerate(findings, 1): + display_finding(finding, i, len(findings)) + choice = get_user_choice("\nApply this fix? [y]es, [n]o, [u]pdate all, [x] cancel: ") + + if choice == "y": + accepted.append(finding) + elif choice == "u": + accepted.extend(findings[i - 1 :]) + break + elif choice == "x": + print("Cancelled.") + sys.exit(0) + + return accepted + + +def main(): + """Main entry point for the CLI tool.""" + parser = argparse.ArgumentParser( + description="Lint and upgrade bash scripts from AWS CLI v1 to v2" + ) + parser.add_argument("--script", required=True, help="Path to the bash script to lint") + parser.add_argument( + "--fix", action="store_true", help="Apply fixes to the script (modifies in place)" + ) + parser.add_argument("--output", help="Output path for the fixed script") + parser.add_argument( + "-i", + "--interactive", + action="store_true", + help="Interactive mode to review each change", + ) + + args = parser.parse_args() + + if args.fix and args.output: + print("Error: Cannot use both --fix and --output") + sys.exit(1) + + if args.fix and args.interactive: + print("Error: Cannot use both --fix and --interactive") + sys.exit(1) + + script_path = Path(args.script) + if not script_path.exists(): + print(f"Error: Script not found: {args.script}") + sys.exit(1) + + script_content = script_path.read_text() + + rules = [Base64BinaryFormatRule()] + linter = ScriptLinter(rules) + findings = linter.lint(script_content) + + if not findings: + print("No issues found.") + return + + if args.interactive: + findings = interactive_mode(findings) + if not findings: + print("No changes accepted.") + return + + if args.fix or args.output: + fixed_content = linter.apply_fixes(script_content, findings) + output_path = Path(args.output) if args.output else script_path + output_path.write_text(fixed_content) + print(f"Fixed script written to: {output_path}") + else: + print(f"\nFound {len(findings)} issue(s):\n") + for i, finding in enumerate(findings, 1): + display_finding(finding, i, len(findings)) + print("\n\nRun with --fix to apply changes or --interactive to review each change.") + + +if __name__ == "__main__": + main() diff --git a/awsclilinter/awsclilinter/linter.py b/awsclilinter/awsclilinter/linter.py new file mode 100644 index 000000000000..ceacf7bafd15 --- /dev/null +++ b/awsclilinter/awsclilinter/linter.py @@ -0,0 +1,33 @@ +from typing import List + +from ast_grep_py import SgRoot + +from awsclilinter.rules_base import LintFinding, LintRule + + +class ScriptLinter: + """Linter for bash scripts to detect AWS CLI v1 to v2 migration issues.""" + + def __init__(self, rules: List[LintRule]): + self.rules = rules + + def lint(self, script_content: str) -> List[LintFinding]: + """Lint the script and return all findings.""" + root = SgRoot(script_content, "bash") + findings = [] + for rule in self.rules: + findings.extend(rule.check(root)) + return sorted(findings, key=lambda f: (f.line_start, f.line_end)) + + def apply_fixes(self, script_content: str, findings: List[LintFinding]) -> str: + """Apply fixes to the script content.""" + root = SgRoot(script_content, "bash") + node = root.root() + + edits = [] + for finding in findings: + matches = node.find_all(pattern=finding.original_text) + for match in matches: + edits.append(match.replace(finding.suggested_fix)) + + return node.commit_edits(edits) diff --git a/awsclilinter/awsclilinter/rules/__init__.py b/awsclilinter/awsclilinter/rules/__init__.py new file mode 100644 index 000000000000..323cd7a7e44a --- /dev/null +++ b/awsclilinter/awsclilinter/rules/__init__.py @@ -0,0 +1,3 @@ +from awsclilinter.rules_base import LintFinding, LintRule + +__all__ = ["LintRule", "LintFinding"] diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py new file mode 100644 index 000000000000..a6c46dc42958 --- /dev/null +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -0,0 +1,52 @@ +from typing import List + +from awsclilinter.rules_base import LintFinding, LintRule + + +class Base64BinaryFormatRule(LintRule): + """Detects AWS CLI commands with file:// that need --cli-binary-format.""" + + @property + def name(self) -> str: + return "base64-binary-format" + + @property + def description(self) -> str: + return ( + "AWS CLI v2 requires --cli-binary-format raw-in-base64-out " + "for commands using file:// protocol" + ) + + def check(self, root) -> List[LintFinding]: + """Check for AWS CLI commands with file:// missing --cli-binary-format.""" + node = root.root() + base64_broken_nodes = node.find_all( + all=[ + {"kind": "command"}, + {"pattern": "aws $SERVICE $OPERATION $$$ARGS"}, + {"has": {"kind": "word", "regex": r"\Afile://"}}, + {"not": {"has": {"kind": "word", "pattern": "--cli-binary-format"}}}, + ] + ) + + findings = [] + for stmt in base64_broken_nodes: + service = stmt.get_match("SERVICE").text() + operation = stmt.get_match("OPERATION").text() + args = " ".join([match.text() for match in stmt.get_multiple_matches("ARGS")]) + + original = stmt.text() + suggested = f"aws {service} {operation} {args} --cli-binary-format raw-in-base64-out" + + findings.append( + LintFinding( + line_start=stmt.range().start.line, + line_end=stmt.range().end.line, + original_text=original, + suggested_fix=suggested, + rule_name=self.name, + description=self.description, + ) + ) + + return findings diff --git a/awsclilinter/awsclilinter/rules_base.py b/awsclilinter/awsclilinter/rules_base.py new file mode 100644 index 000000000000..cb175b4cc441 --- /dev/null +++ b/awsclilinter/awsclilinter/rules_base.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import List + + +@dataclass +class LintFinding: + """Represents a linting issue found in the script.""" + + line_start: int + line_end: int + original_text: str + suggested_fix: str + rule_name: str + description: str + + +class LintRule(ABC): + """Base class for all linting rules.""" + + @property + @abstractmethod + def name(self) -> str: + """Return the name of the rule.""" + pass + + @property + @abstractmethod + def description(self) -> str: + """Return a description of what the rule checks.""" + pass + + @abstractmethod + def check(self, root) -> List[LintFinding]: + """Check the AST root for violations and return findings.""" + pass diff --git a/awsclilinter/examples/upload_s3_files.sh b/awsclilinter/examples/upload_s3_files.sh new file mode 100644 index 000000000000..3fd428cf721a --- /dev/null +++ b/awsclilinter/examples/upload_s3_files.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Example script with AWS CLI v1 patterns + +# TODO update examples to commands that specify file:// for blob-type params + +# This command needs --cli-binary-format flag +aws s3api put-object --bucket mybucket --key mykey --body file://data.json + +# This command also needs the flag +aws dynamodb put-item --table-name mytable --item file://item.json + +# This command doesn't use file:// so it's fine +aws s3 ls s3://mybucket diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml new file mode 100644 index 000000000000..41a6d043212b --- /dev/null +++ b/awsclilinter/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "awsclilinter" +version = "0.1.0" +description = "CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2" +requires-python = ">=3.12" +dependencies = [ + "ast-grep-py>=0.39.6", +] + +[project.scripts] +upgrade-aws-cli = "awsclilinter.cli:main" + +[tool.black] +line-length = 100 +target-version = ['py312'] + +[tool.isort] +profile = "black" +line_length = 100 + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] diff --git a/awsclilinter/requirements-dev.lock b/awsclilinter/requirements-dev.lock new file mode 100644 index 000000000000..ffbcaf6b35e4 --- /dev/null +++ b/awsclilinter/requirements-dev.lock @@ -0,0 +1,11 @@ +ast-grep-py==0.39.6 +black==24.1.1 +click==8.3.0 +iniconfig==2.3.0 +isort==5.13.2 +mypy_extensions==1.1.0 +packaging==25.0 +pathspec==0.12.1 +platformdirs==4.5.0 +pluggy==1.6.0 +pytest==8.0.0 diff --git a/awsclilinter/requirements-dev.txt b/awsclilinter/requirements-dev.txt new file mode 100644 index 000000000000..c5abd4669027 --- /dev/null +++ b/awsclilinter/requirements-dev.txt @@ -0,0 +1,4 @@ +-r requirements.txt +pytest==8.0.0 +black==24.1.1 +isort==5.13.2 diff --git a/awsclilinter/requirements.lock b/awsclilinter/requirements.lock new file mode 100644 index 000000000000..63578da3ceea --- /dev/null +++ b/awsclilinter/requirements.lock @@ -0,0 +1 @@ +ast-grep-py==0.39.6 diff --git a/awsclilinter/requirements.txt b/awsclilinter/requirements.txt new file mode 100644 index 000000000000..63578da3ceea --- /dev/null +++ b/awsclilinter/requirements.txt @@ -0,0 +1 @@ +ast-grep-py==0.39.6 diff --git a/awsclilinter/tests/__init__.py b/awsclilinter/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/awsclilinter/tests/test_cli.py b/awsclilinter/tests/test_cli.py new file mode 100644 index 000000000000..f59d5d963b81 --- /dev/null +++ b/awsclilinter/tests/test_cli.py @@ -0,0 +1,150 @@ +from unittest.mock import mock_open, patch + +import pytest + +from awsclilinter.cli import main + + +class TestCLI: + """Test cases for CLI interface.""" + + def test_script_not_found(self, capsys): + """Test error when script file doesn't exist.""" + with patch("sys.argv", ["upgrade-aws-cli", "--script", "nonexistent.sh"]): + with pytest.raises(SystemExit) as exc_info: + main() + assert exc_info.value.code == 1 + + def test_fix_and_output_conflict(self, capsys): + """Test error when both --fix and --output are provided.""" + with patch( + "sys.argv", ["upgrade-aws-cli", "--script", "test.sh", "--fix", "--output", "out.sh"] + ): + with pytest.raises(SystemExit) as exc_info: + main() + assert exc_info.value.code == 1 + + def test_fix_and_interactive_conflict(self, capsys): + """Test error when both --fix and --interactive are provided.""" + with patch( + "sys.argv", ["upgrade-aws-cli", "--script", "test.sh", "--fix", "--interactive"] + ): + with pytest.raises(SystemExit) as exc_info: + main() + assert exc_info.value.code == 1 + + def test_no_issues_found(self, tmp_path, capsys): + """Test output when no issues are found.""" + script_file = tmp_path / "test.sh" + script_file.write_text("echo 'hello world'") + + with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file)]): + main() + captured = capsys.readouterr() + assert "No issues found" in captured.out + + def test_dry_run_mode(self, tmp_path, capsys): + """Test dry run mode displays findings.""" + script_file = tmp_path / "test.sh" + script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + + with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file)]): + main() + captured = capsys.readouterr() + assert "Found" in captured.out + assert "issue" in captured.out + + def test_fix_mode(self, tmp_path): + """Test fix mode modifies the script.""" + script_file = tmp_path / "test.sh" + script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + + with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--fix"]): + main() + fixed_content = script_file.read_text() + assert "--cli-binary-format" in fixed_content + + def test_output_mode(self, tmp_path): + """Test output mode creates new file.""" + script_file = tmp_path / "test.sh" + output_file = tmp_path / "output.sh" + script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + + with patch( + "sys.argv", + ["upgrade-aws-cli", "--script", str(script_file), "--output", str(output_file)], + ): + main() + assert output_file.exists() + assert "--cli-binary-format" in output_file.read_text() + + def test_interactive_mode_accept_all(self, tmp_path): + """Test interactive mode with 'y' to accept all changes.""" + script_file = tmp_path / "test.sh" + output_file = tmp_path / "output.sh" + script_file.write_text( + "aws s3api put-object --bucket mybucket --body file://data.json\naws dynamodb put-item --table mytable --item file://item.json" + ) + + with patch( + "sys.argv", + [ + "upgrade-aws-cli", + "--script", + str(script_file), + "--interactive", + "--output", + str(output_file), + ], + ): + with patch("builtins.input", side_effect=["y", "y"]): + main() + fixed_content = output_file.read_text() + assert fixed_content.count("--cli-binary-format") == 2 + + def test_interactive_mode_reject_all(self, tmp_path, capsys): + """Test interactive mode with 'n' to reject all changes.""" + script_file = tmp_path / "test.sh" + original = "aws s3api put-object --bucket mybucket --body file://data.json" + script_file.write_text(original) + + with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): + with patch("builtins.input", return_value="n"): + main() + captured = capsys.readouterr() + assert "No changes accepted" in captured.out + + def test_interactive_mode_update_all(self, tmp_path): + """Test interactive mode with 'u' to accept remaining changes.""" + script_file = tmp_path / "test.sh" + output_file = tmp_path / "output.sh" + script_file.write_text( + "aws s3api put-object --bucket mybucket --body file://data.json\naws dynamodb put-item --table mytable --item file://item.json" + ) + + with patch( + "sys.argv", + [ + "upgrade-aws-cli", + "--script", + str(script_file), + "--interactive", + "--output", + str(output_file), + ], + ): + with patch("builtins.input", return_value="u"): + main() + fixed_content = output_file.read_text() + assert fixed_content.count("--cli-binary-format") == 2 + + def test_interactive_mode_cancel(self, tmp_path): + """Test interactive mode with 'x' to cancel.""" + script_file = tmp_path / "test.sh" + script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + + with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): + with patch("builtins.input", return_value="x"): + with pytest.raises(SystemExit) as exc_info: + main() + assert exc_info.value.code == 0 diff --git a/awsclilinter/tests/test_linter.py b/awsclilinter/tests/test_linter.py new file mode 100644 index 000000000000..57ba93934e40 --- /dev/null +++ b/awsclilinter/tests/test_linter.py @@ -0,0 +1,43 @@ +from awsclilinter.linter import ScriptLinter +from awsclilinter.rules.base64_rule import Base64BinaryFormatRule + + +class TestScriptLinter: + """Test cases for ScriptLinter.""" + + def test_lint_finds_issues(self): + """Test that linter finds issues in script.""" + script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json" + linter = ScriptLinter([Base64BinaryFormatRule()]) + findings = linter.lint(script) + + assert len(findings) == 1 + assert findings[0].rule_name == "base64-binary-format" + assert "file://" in findings[0].original_text + + def test_lint_no_issues(self): + """Test that linter returns no findings for compliant script.""" + script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json --cli-binary-format raw-in-base64-out" + linter = ScriptLinter([Base64BinaryFormatRule()]) + findings = linter.lint(script) + + assert len(findings) == 0 + + def test_apply_fixes(self): + """Test that fixes are applied correctly.""" + script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json" + linter = ScriptLinter([Base64BinaryFormatRule()]) + findings = linter.lint(script) + fixed = linter.apply_fixes(script, findings) + + assert "--cli-binary-format raw-in-base64-out" in fixed + assert "file://data.json" in fixed + + def test_multiple_issues(self): + """Test linter with multiple issues.""" + script = """aws s3api put-object --bucket mybucket --key mykey --body file://data.json +aws dynamodb put-item --table-name mytable --item file://item.json""" + linter = ScriptLinter([Base64BinaryFormatRule()]) + findings = linter.lint(script) + + assert len(findings) == 2 diff --git a/awsclilinter/tests/test_rules.py b/awsclilinter/tests/test_rules.py new file mode 100644 index 000000000000..ca7e4a49d394 --- /dev/null +++ b/awsclilinter/tests/test_rules.py @@ -0,0 +1,41 @@ +from ast_grep_py import SgRoot + +from awsclilinter.rules.base64_rule import Base64BinaryFormatRule + + +class TestBase64BinaryFormatRule: + """Test cases for Base64BinaryFormatRule.""" + + def test_rule_properties(self): + """Test rule name and description.""" + rule = Base64BinaryFormatRule() + assert rule.name == "base64-binary-format" + assert "cli-binary-format" in rule.description + + def test_detects_missing_flag(self): + """Test detection of missing --cli-binary-format flag.""" + script = "aws s3api put-object --bucket mybucket --body file://data.json" + root = SgRoot(script, "bash") + rule = Base64BinaryFormatRule() + findings = rule.check(root) + + assert len(findings) == 1 + assert "--cli-binary-format" in findings[0].suggested_fix + + def test_no_detection_with_flag(self): + """Test no detection when flag is present.""" + script = "aws s3api put-object --bucket mybucket --body file://data.json --cli-binary-format raw-in-base64-out" + root = SgRoot(script, "bash") + rule = Base64BinaryFormatRule() + findings = rule.check(root) + + assert len(findings) == 0 + + def test_no_detection_without_file_protocol(self): + """Test no detection when file:// is not used.""" + script = "aws s3api put-object --bucket mybucket --body data.json" + root = SgRoot(script, "bash") + rule = Base64BinaryFormatRule() + findings = rule.check(root) + + assert len(findings) == 0 From 1ed785aa67f43109d1d4d1933a98d273f24f1f62 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 11:02:19 -0400 Subject: [PATCH 02/19] Progress. --- awsclilinter/Makefile | 17 +++- awsclilinter/README.md | 52 +++--------- awsclilinter/awsclilinter/cli.py | 79 +++++++++++++++---- awsclilinter/awsclilinter/linter.py | 9 +-- .../awsclilinter/rules/base64_rule.py | 21 ++--- awsclilinter/awsclilinter/rules_base.py | 5 +- awsclilinter/examples/upload_s3_files.sh | 13 ++- awsclilinter/pyproject.toml | 4 +- awsclilinter/requirements-dev.lock | 11 --- awsclilinter/requirements.lock | 1 - awsclilinter/tests/test_cli.py | 4 +- awsclilinter/tests/test_linter.py | 2 +- awsclilinter/tests/test_rules.py | 2 +- 13 files changed, 118 insertions(+), 102 deletions(-) delete mode 100644 awsclilinter/requirements-dev.lock delete mode 100644 awsclilinter/requirements.lock diff --git a/awsclilinter/Makefile b/awsclilinter/Makefile index d880ce1bfce4..ebd7170e2265 100644 --- a/awsclilinter/Makefile +++ b/awsclilinter/Makefile @@ -1,7 +1,22 @@ .PHONY: setup test format lint clean setup: - ./setup.sh + @echo "Setting up AWS CLI Linter..." + @if [ ! -d "venv" ]; then \ + echo "Creating virtual environment..."; \ + python3.12 -m venv venv; \ + fi + @echo "Installing dependencies..." + @. venv/bin/activate && pip install --upgrade pip + @if [ -f "requirements-dev-lock.txt" ]; then \ + echo "Using lockfile for reproducible builds..."; \ + . venv/bin/activate && pip install -r requirements-dev-lock.txt; \ + else \ + . venv/bin/activate && pip install -r requirements-dev.txt; \ + fi + @echo "Installing package..." + @. venv/bin/activate && pip install -e . + @echo "Setup complete! Activate the virtual environment with: source venv/bin/activate" test: pytest tests/ -v diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 6dc8ad722eac..65345e0572c9 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -1,6 +1,11 @@ # AWS CLI Linter -A CLI tool that lints bash scripts for AWS CLI v1 usage and updates them to avoid breaking changes introduced in AWS CLI v2. +A CLI tool that lints bash scripts for AWS CLI v1 usage and updates them to avoid breaking +changes introduced in AWS CLI v2. Not all of the breaking changes can be detected statically, +thus not all of them are supported by this tool. + +For a full list of the breaking changes introduced with AWS CLI v2, see +[Breaking changes between AWS CLI version 1 and AWS CLI version 2](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-changes-breaking). ## Installation @@ -13,8 +18,6 @@ source venv/bin/activate 2. Install dependencies: ```bash pip install -r requirements.txt -# Or use lockfile for reproducible builds: -pip install -r requirements.lock ``` 3. Install the package in development mode: @@ -52,53 +55,16 @@ In interactive mode, you can: - Press `y` to accept the current change - Press `n` to skip the current change - Press `u` to accept all remaining changes -- Press `x` to cancel and exit - -Note: `--interactive` requires either `--output` to specify where to write changes, or no output flag for dry-run. It cannot be used with `--fix`. +- Press `q` to cancel and quit ## Development ### Running tests ```bash -pytest +make test ``` ### Code formatting ```bash -black awsclilinter tests -isort awsclilinter tests -``` - -## Adding New Linting Rules - -To add a new linting rule: - -1. Create a new rule class in `awsclilinter/rules/` that inherits from `LintRule` -2. Implement the required methods: `name`, `description`, and `check` -3. Add the rule to the rules list in `awsclilinter/cli.py` - -Example: -```python -from awsclilinter.rules_base import LintRule, LintFinding - -class MyCustomRule(LintRule): - @property - def name(self) -> str: - return "my-custom-rule" - - @property - def description(self) -> str: - return "Description of what this rule checks" - - def check(self, root) -> List[LintFinding]: - # Implementation using ast-grep - pass +make format ``` - -## Architecture - -- `rules_base.py`: Base classes for linting rules (`LintRule`, `LintFinding`) -- `rules/`: Directory containing individual linting rule implementations -- `linter.py`: Main `ScriptLinter` class that orchestrates rule checking -- `cli.py`: CLI interface using argparse -- `tests/`: Unit tests using pytest diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 9ed039598a5c..d1ef881e70ed 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -7,37 +7,84 @@ from awsclilinter.rules.base64_rule import Base64BinaryFormatRule from awsclilinter.rules_base import LintFinding +# ANSI color codes +RED = "\033[31m" +GREEN = "\033[32m" +CYAN = "\033[36m" +RESET = "\033[0m" + def get_user_choice(prompt: str) -> str: """Get user input for interactive mode.""" while True: choice = input(prompt).lower().strip() - if choice in ["y", "n", "u", "x"]: + if choice in ["y", "n", "u", "q"]: return choice - print("Invalid choice. Please enter y, n, u, or x.") - + print("Invalid choice. Please enter y, n, u, or q.") + + +def display_finding(finding: LintFinding, index: int, total: int, script_content: str): + """Display a finding to the user with context.""" + lines = script_content.splitlines() + dest_lines = finding.edit.inserted_text.splitlines() + start_line = finding.line_start + end_line = finding.line_end + src_lines_removed = end_line - start_line + 1 + new_lines_added = len(dest_lines) + + # Create a map from line numbers to their indices within the full script file + line_positions = [] + pos = 0 + for i, line in enumerate(lines): + line_positions.append((pos, pos + len(line))) + pos += len(line) + 1 + + # Get context lines (3 before and 3 after) + context_start = max(0, start_line - 3) + context_end = min(len(lines), end_line + 4) + src_context_size = context_end - context_start + dest_context_size = src_context_size + (new_lines_added - src_lines_removed) -def display_finding(finding: LintFinding, index: int, total: int): - """Display a finding to the user.""" print(f"\n[{index}/{total}] {finding.rule_name}") - print(f"Lines {finding.line_start}-{finding.line_end}: {finding.description}") - print(f"\nOriginal:\n {finding.original_text}") - print(f"\nSuggested:\n {finding.suggested_fix}") + print(f"{finding.description}") + print( + f"\n{CYAN}@@ -{context_start + 1},{src_context_size} " + f"+{context_start + 1},{dest_context_size} @@{RESET}" + ) + + for i in range(context_start, context_end): + line = lines[i] if i < len(lines) else "" + + if start_line <= i <= end_line: + # This line is actually being modified + print(f"{RED}-{line}{RESET}") + + if i == end_line: + line_start_pos, _ = line_positions[i] + start_pos_in_line = max(0, finding.edit.start_pos - line_start_pos) + end_pos_in_line = min(len(line), finding.edit.end_pos - line_start_pos) + new_line = line[:start_pos_in_line] + finding.suggested_fix + line[end_pos_in_line:] + # Print the new line suggestion. The line number will always be the start line + # returned by ast-grep. + print(f"{GREEN}+{new_line}{RESET}") + else: + # Context line + print(f"{line}") -def interactive_mode(findings: List[LintFinding]) -> List[LintFinding]: +def interactive_mode(findings: List[LintFinding], script_content: str) -> List[LintFinding]: """Run interactive mode and return accepted findings.""" accepted = [] for i, finding in enumerate(findings, 1): - display_finding(finding, i, len(findings)) - choice = get_user_choice("\nApply this fix? [y]es, [n]o, [u]pdate all, [x] cancel: ") + display_finding(finding, i, len(findings), script_content) + choice = get_user_choice("\nApply this fix? [y]es, [n]o, [u]pdate all, [q]uit: ") if choice == "y": accepted.append(finding) elif choice == "u": accepted.extend(findings[i - 1 :]) break - elif choice == "x": + elif choice == "q": print("Cancelled.") sys.exit(0) @@ -87,12 +134,14 @@ def main(): return if args.interactive: - findings = interactive_mode(findings) + findings = interactive_mode(findings, script_content) if not findings: print("No changes accepted.") return - if args.fix or args.output: + if args.fix or args.output or args.interactive: + # Interactive mode is functionally equivalent to --fix, except the user + # can select a subset of the changes to apply. fixed_content = linter.apply_fixes(script_content, findings) output_path = Path(args.output) if args.output else script_path output_path.write_text(fixed_content) @@ -100,7 +149,7 @@ def main(): else: print(f"\nFound {len(findings)} issue(s):\n") for i, finding in enumerate(findings, 1): - display_finding(finding, i, len(findings)) + display_finding(finding, i, len(findings), script_content) print("\n\nRun with --fix to apply changes or --interactive to review each change.") diff --git a/awsclilinter/awsclilinter/linter.py b/awsclilinter/awsclilinter/linter.py index ceacf7bafd15..f9f5885d0b09 100644 --- a/awsclilinter/awsclilinter/linter.py +++ b/awsclilinter/awsclilinter/linter.py @@ -23,11 +23,4 @@ def apply_fixes(self, script_content: str, findings: List[LintFinding]) -> str: """Apply fixes to the script content.""" root = SgRoot(script_content, "bash") node = root.root() - - edits = [] - for finding in findings: - matches = node.find_all(pattern=finding.original_text) - for match in matches: - edits.append(match.replace(finding.suggested_fix)) - - return node.commit_edits(edits) + return node.commit_edits([f.edit for f in findings]) diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py index a6c46dc42958..728433e4ac4b 100644 --- a/awsclilinter/awsclilinter/rules/base64_rule.py +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -1,5 +1,7 @@ from typing import List +from ast_grep_py.ast_grep_py import SgRoot + from awsclilinter.rules_base import LintFinding, LintRule @@ -8,16 +10,17 @@ class Base64BinaryFormatRule(LintRule): @property def name(self) -> str: - return "base64-binary-format" + return "binary-params-base64" @property def description(self) -> str: return ( - "AWS CLI v2 requires --cli-binary-format raw-in-base64-out " - "for commands using file:// protocol" + "In AWS CLI v2, an input parameter typed as binary large object (BLOB) expects " + "the input to be base64-encoded. To retain v1 behavior after upgrading to AWS CLI v2, " + "add `--cli-binary-format raw-in-base64-out`." ) - def check(self, root) -> List[LintFinding]: + def check(self, root: SgRoot) -> List[LintFinding]: """Check for AWS CLI commands with file:// missing --cli-binary-format.""" node = root.root() base64_broken_nodes = node.find_all( @@ -31,17 +34,17 @@ def check(self, root) -> List[LintFinding]: findings = [] for stmt in base64_broken_nodes: - service = stmt.get_match("SERVICE").text() - operation = stmt.get_match("OPERATION").text() - args = " ".join([match.text() for match in stmt.get_multiple_matches("ARGS")]) - original = stmt.text() - suggested = f"aws {service} {operation} {args} --cli-binary-format raw-in-base64-out" + # To retain v1 behavior after migrating to v2, append + # --cli-binary-format raw-in-base64-out + suggested = original + " --cli-binary-format raw-in-base64-out" + edit = stmt.replace(suggested) findings.append( LintFinding( line_start=stmt.range().start.line, line_end=stmt.range().end.line, + edit=edit, original_text=original, suggested_fix=suggested, rule_name=self.name, diff --git a/awsclilinter/awsclilinter/rules_base.py b/awsclilinter/awsclilinter/rules_base.py index cb175b4cc441..991ae5b99e58 100644 --- a/awsclilinter/awsclilinter/rules_base.py +++ b/awsclilinter/awsclilinter/rules_base.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import List +from ast_grep_py.ast_grep_py import Edit, SgRoot + @dataclass class LintFinding: @@ -9,6 +11,7 @@ class LintFinding: line_start: int line_end: int + edit: Edit original_text: str suggested_fix: str rule_name: str @@ -31,6 +34,6 @@ def description(self) -> str: pass @abstractmethod - def check(self, root) -> List[LintFinding]: + def check(self, root: SgRoot) -> List[LintFinding]: """Check the AST root for violations and return findings.""" pass diff --git a/awsclilinter/examples/upload_s3_files.sh b/awsclilinter/examples/upload_s3_files.sh index 3fd428cf721a..ed38ffff9a60 100644 --- a/awsclilinter/examples/upload_s3_files.sh +++ b/awsclilinter/examples/upload_s3_files.sh @@ -1,13 +1,12 @@ #!/bin/bash # Example script with AWS CLI v1 patterns -# TODO update examples to commands that specify file:// for blob-type params +aws secretsmanager put-secret-value --secret-id secret1213 \ + --secret-binary file://data.json -# This command needs --cli-binary-format flag -aws s3api put-object --bucket mybucket --key mykey --body file://data.json +if + aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json ; then + echo "command succeeded." +fi -# This command also needs the flag -aws dynamodb put-item --table-name mytable --item file://item.json - -# This command doesn't use file:// so it's fine aws s3 ls s3://mybucket diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 41a6d043212b..454ea39e7c68 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta" [project] name = "awsclilinter" -version = "0.1.0" +version = "1.0.0" description = "CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2" -requires-python = ">=3.12" +requires-python = ">=3.9" dependencies = [ "ast-grep-py>=0.39.6", ] diff --git a/awsclilinter/requirements-dev.lock b/awsclilinter/requirements-dev.lock deleted file mode 100644 index ffbcaf6b35e4..000000000000 --- a/awsclilinter/requirements-dev.lock +++ /dev/null @@ -1,11 +0,0 @@ -ast-grep-py==0.39.6 -black==24.1.1 -click==8.3.0 -iniconfig==2.3.0 -isort==5.13.2 -mypy_extensions==1.1.0 -packaging==25.0 -pathspec==0.12.1 -platformdirs==4.5.0 -pluggy==1.6.0 -pytest==8.0.0 diff --git a/awsclilinter/requirements.lock b/awsclilinter/requirements.lock deleted file mode 100644 index 63578da3ceea..000000000000 --- a/awsclilinter/requirements.lock +++ /dev/null @@ -1 +0,0 @@ -ast-grep-py==0.39.6 diff --git a/awsclilinter/tests/test_cli.py b/awsclilinter/tests/test_cli.py index f59d5d963b81..fb7c72653a8e 100644 --- a/awsclilinter/tests/test_cli.py +++ b/awsclilinter/tests/test_cli.py @@ -1,4 +1,4 @@ -from unittest.mock import mock_open, patch +from unittest.mock import patch import pytest @@ -144,7 +144,7 @@ def test_interactive_mode_cancel(self, tmp_path): script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): - with patch("builtins.input", return_value="x"): + with patch("builtins.input", return_value="q"): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 0 diff --git a/awsclilinter/tests/test_linter.py b/awsclilinter/tests/test_linter.py index 57ba93934e40..d405cbeb8eb0 100644 --- a/awsclilinter/tests/test_linter.py +++ b/awsclilinter/tests/test_linter.py @@ -12,7 +12,7 @@ def test_lint_finds_issues(self): findings = linter.lint(script) assert len(findings) == 1 - assert findings[0].rule_name == "base64-binary-format" + assert findings[0].rule_name == "binary-params-base64" assert "file://" in findings[0].original_text def test_lint_no_issues(self): diff --git a/awsclilinter/tests/test_rules.py b/awsclilinter/tests/test_rules.py index ca7e4a49d394..fa69f3f9bb33 100644 --- a/awsclilinter/tests/test_rules.py +++ b/awsclilinter/tests/test_rules.py @@ -9,7 +9,7 @@ class TestBase64BinaryFormatRule: def test_rule_properties(self): """Test rule name and description.""" rule = Base64BinaryFormatRule() - assert rule.name == "base64-binary-format" + assert rule.name == "binary-params-base64" assert "cli-binary-format" in rule.description def test_detects_missing_flag(self): From e7d2f15913dfab51ea19d64233a8c9906fbf2fa4 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 11:34:16 -0400 Subject: [PATCH 03/19] Progress --- awsclilinter/README.md | 6 +- awsclilinter/awsclilinter/__init__.py | 2 +- awsclilinter/awsclilinter/cli.py | 20 ++-- .../awsclilinter/rules/base64_rule.py | 4 +- awsclilinter/examples/upload_s3_files.sh | 2 +- awsclilinter/requirements-dev-lock.txt | 97 +++++++++++++++++++ awsclilinter/tests/test_cli.py | 18 ++-- awsclilinter/tests/test_linter.py | 16 +-- awsclilinter/tests/test_rules.py | 13 +-- 9 files changed, 140 insertions(+), 38 deletions(-) create mode 100644 awsclilinter/requirements-dev-lock.txt diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 65345e0572c9..7fd78c2d19ce 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -1,7 +1,7 @@ # AWS CLI Linter A CLI tool that lints bash scripts for AWS CLI v1 usage and updates them to avoid breaking -changes introduced in AWS CLI v2. Not all of the breaking changes can be detected statically, +changes introduced in AWS CLI v2. Not all breaking changes can be detected statically, thus not all of them are supported by this tool. For a full list of the breaking changes introduced with AWS CLI v2, see @@ -9,6 +9,9 @@ For a full list of the breaking changes introduced with AWS CLI v2, see ## Installation +Run `make setup` to set up a virtual environment with the linter installed. Alternatively, +you can follow the steps below. + 1. Create a virtual environment: ```bash python3.12 -m venv venv @@ -18,6 +21,7 @@ source venv/bin/activate 2. Install dependencies: ```bash pip install -r requirements.txt +pip install -r requirements-dev-lock.txt ``` 3. Install the package in development mode: diff --git a/awsclilinter/awsclilinter/__init__.py b/awsclilinter/awsclilinter/__init__.py index 3dc1f76bc69e..5becc17c04a9 100644 --- a/awsclilinter/awsclilinter/__init__.py +++ b/awsclilinter/awsclilinter/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "1.0.0" diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index d1ef881e70ed..9a37b51c51ba 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -13,6 +13,9 @@ CYAN = "\033[36m" RESET = "\033[0m" +# The number of lines to show before an after a fix suggestion, for context within the script +CONTEXT_SIZE = 3 + def get_user_choice(prompt: str) -> str: """Get user input for interactive mode.""" @@ -25,7 +28,7 @@ def get_user_choice(prompt: str) -> str: def display_finding(finding: LintFinding, index: int, total: int, script_content: str): """Display a finding to the user with context.""" - lines = script_content.splitlines() + src_lines = script_content.splitlines() dest_lines = finding.edit.inserted_text.splitlines() start_line = finding.line_start end_line = finding.line_end @@ -35,13 +38,13 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content # Create a map from line numbers to their indices within the full script file line_positions = [] pos = 0 - for i, line in enumerate(lines): + for i, line in enumerate(src_lines): line_positions.append((pos, pos + len(line))) pos += len(line) + 1 - # Get context lines (3 before and 3 after) - context_start = max(0, start_line - 3) - context_end = min(len(lines), end_line + 4) + # Get context lines + context_start = max(0, start_line - CONTEXT_SIZE) + context_end = min(len(src_lines), end_line + CONTEXT_SIZE + 1) src_context_size = context_end - context_start dest_context_size = src_context_size + (new_lines_added - src_lines_removed) @@ -53,10 +56,10 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content ) for i in range(context_start, context_end): - line = lines[i] if i < len(lines) else "" + line = src_lines[i] if i < len(src_lines) else "" if start_line <= i <= end_line: - # This line is actually being modified + # This line is being modified print(f"{RED}-{line}{RESET}") if i == end_line: @@ -64,8 +67,7 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content start_pos_in_line = max(0, finding.edit.start_pos - line_start_pos) end_pos_in_line = min(len(line), finding.edit.end_pos - line_start_pos) new_line = line[:start_pos_in_line] + finding.suggested_fix + line[end_pos_in_line:] - # Print the new line suggestion. The line number will always be the start line - # returned by ast-grep. + # Print the new line suggestion. print(f"{GREEN}+{new_line}{RESET}") else: # Context line diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py index 728433e4ac4b..674b3568c893 100644 --- a/awsclilinter/awsclilinter/rules/base64_rule.py +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -6,7 +6,9 @@ class Base64BinaryFormatRule(LintRule): - """Detects AWS CLI commands with file:// that need --cli-binary-format.""" + """Detects AWS CLI commands with file:// that need --cli-binary-format. This is a best-effort + attempt at statically detecting the breaking change with how AWS CLI v2 treats binary + parameters.""" @property def name(self) -> str: diff --git a/awsclilinter/examples/upload_s3_files.sh b/awsclilinter/examples/upload_s3_files.sh index ed38ffff9a60..81a24d17278d 100644 --- a/awsclilinter/examples/upload_s3_files.sh +++ b/awsclilinter/examples/upload_s3_files.sh @@ -5,7 +5,7 @@ aws secretsmanager put-secret-value --secret-id secret1213 \ --secret-binary file://data.json if - aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json ; then + aws kinesis put-record --stream-name samplestream --data file://data --partition-key samplepartitionkey ; then echo "command succeeded." fi diff --git a/awsclilinter/requirements-dev-lock.txt b/awsclilinter/requirements-dev-lock.txt new file mode 100644 index 000000000000..2938acef36e3 --- /dev/null +++ b/awsclilinter/requirements-dev-lock.txt @@ -0,0 +1,97 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes --output-file=requirements-dev-lock.txt requirements-dev.txt +# +ast-grep-py==0.39.6 \ + --hash=sha256:01d3e4a7dfea92ee43ff712843f795a9f9a821c4bd0139fd2ce773851dee263d \ + --hash=sha256:292a2eb0cd76b0ed39ef512f4489bb9936978ef54b5d601918bc697f263d1357 \ + --hash=sha256:2a7fffe7dcc55ea7678628072b511ea0252433748f6f3b19b6f2483f11874e3c \ + --hash=sha256:3358b5d26d90cb928951923b4a7ac3973f7d7303d1e372321279023447b1dd33 \ + --hash=sha256:3ef4cc66c228654b2fb41e72d90bd5276e1cf947f8300ffc78abd67274136b1b \ + --hash=sha256:3f61646794e1c74023e8881ad93fd8b13778bfe5e50b61a4e4f8d3e8802bc914 \ + --hash=sha256:4380389a83fd7f06fe4c30e6c68ac786b99a8ce513ac0742148d27a1c987c0c0 \ + --hash=sha256:46de5659378e2c1f1915629eb4f7e9f2add7b1a69ad774835c263c6e4b61613c \ + --hash=sha256:4fc47c76d12f03407616ae60a4e210e4e337fcfd278ad24d6cf17f81cdb2b338 \ + --hash=sha256:4fd970464f63af98e66cceabee836ac0304a612ff060e9461cd026c2193c809f \ + --hash=sha256:63cce05fa68dd9d626711ed46a775f1f091fc6e075e685471c02d492b2d77a8a \ + --hash=sha256:65172cf9514f633d5922ba4074cd2e34470ee08abb29e6d8eb4059ac370ec45f \ + --hash=sha256:678a68502ea4887e3724842429b5adc0da8a719cb4720e2bdd95a1d4f9f208ed \ + --hash=sha256:74a2e7fab3da96e403c7c257652218cbe7e5fc15055842215a91f14d158c9589 \ + --hash=sha256:828dd474c2504fc7544733b8f200245be0d2ae67060f6e3d0fe7c5852d0bf9cf \ + --hash=sha256:931229ec21c3b116f1bffb65fca8f67bddf7b31f3b89d02230ae331e30997768 \ + --hash=sha256:a77156ea53c6e6efaf7cfb8cb45f8731b198dc0ef2ea1e5b31b1c92fe281c203 \ + --hash=sha256:b70440bfdbc1ed71b26430dd05ee4fc19bb97b43b1d1f0fea8ee073f5a4e1bec \ + --hash=sha256:c11245346a78deedb6b7bc65cca6a6c22783f2a33e1e999f3ba7d7bf00d89db8 \ + --hash=sha256:c5194c8ec3bf05fc25dda5a1f9d77a47c7ad08f997c1d579bdfbb0bc12afa683 \ + --hash=sha256:ca8e8cd36aa81f89448cdadf00875e5ac0e14cff0cc11526dd1a1821a35cdae9 \ + --hash=sha256:cb804e6e048c35c873a396737f616124fb54343d392e87390e3cd515d44d281c \ + --hash=sha256:d540136365e95b767cbc2772fd08b8968bd151e1aaaafaa51d91303659367120 \ + --hash=sha256:dc18747c0f2614984c855636e2f7430998bd83c952cb964aa115eca096bfcb8b \ + --hash=sha256:e473ef786fb3e12192ef53682f68af634b30716859dbc44764164981be777fcd \ + --hash=sha256:f4227d320719de840ed0bb32a281de3b9d2fa33897dcb3f93d2ae3391affc70e \ + --hash=sha256:fbe02b082474831fc2716cf8e8b312c5da97a992967e50ac5e37f83de385fe18 + # via -r /Users/aemous/GitHub/aws-cli2/awsclilinter/requirements.txt +black==24.1.1 \ + --hash=sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8 \ + --hash=sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6 \ + --hash=sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62 \ + --hash=sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445 \ + --hash=sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c \ + --hash=sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a \ + --hash=sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9 \ + --hash=sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2 \ + --hash=sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6 \ + --hash=sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b \ + --hash=sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4 \ + --hash=sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168 \ + --hash=sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d \ + --hash=sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5 \ + --hash=sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024 \ + --hash=sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e \ + --hash=sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b \ + --hash=sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161 \ + --hash=sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717 \ + --hash=sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8 \ + --hash=sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac \ + --hash=sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7 + # via -r requirements-dev.txt +click==8.3.0 \ + --hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \ + --hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4 + # via black +iniconfig==2.3.0 \ + --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ + --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via -r requirements-dev.txt +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via black +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pytest +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +platformdirs==4.5.0 \ + --hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \ + --hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 + # via black +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pytest==8.0.0 \ + --hash=sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c \ + --hash=sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6 + # via -r requirements-dev.txt diff --git a/awsclilinter/tests/test_cli.py b/awsclilinter/tests/test_cli.py index fb7c72653a8e..b86dc83549a7 100644 --- a/awsclilinter/tests/test_cli.py +++ b/awsclilinter/tests/test_cli.py @@ -46,7 +46,7 @@ def test_no_issues_found(self, tmp_path, capsys): def test_dry_run_mode(self, tmp_path, capsys): """Test dry run mode displays findings.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file)]): main() @@ -57,7 +57,7 @@ def test_dry_run_mode(self, tmp_path, capsys): def test_fix_mode(self, tmp_path): """Test fix mode modifies the script.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--fix"]): main() @@ -68,7 +68,7 @@ def test_output_mode(self, tmp_path): """Test output mode creates new file.""" script_file = tmp_path / "test.sh" output_file = tmp_path / "output.sh" - script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") with patch( "sys.argv", @@ -83,7 +83,9 @@ def test_interactive_mode_accept_all(self, tmp_path): script_file = tmp_path / "test.sh" output_file = tmp_path / "output.sh" script_file.write_text( - "aws s3api put-object --bucket mybucket --body file://data.json\naws dynamodb put-item --table mytable --item file://item.json" + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json\n" + "aws kinesis put-record --stream-name samplestream --data file://data " + "--partition-key samplepartitionkey" ) with patch( @@ -105,7 +107,7 @@ def test_interactive_mode_accept_all(self, tmp_path): def test_interactive_mode_reject_all(self, tmp_path, capsys): """Test interactive mode with 'n' to reject all changes.""" script_file = tmp_path / "test.sh" - original = "aws s3api put-object --bucket mybucket --body file://data.json" + original = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" script_file.write_text(original) with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): @@ -119,7 +121,9 @@ def test_interactive_mode_update_all(self, tmp_path): script_file = tmp_path / "test.sh" output_file = tmp_path / "output.sh" script_file.write_text( - "aws s3api put-object --bucket mybucket --body file://data.json\naws dynamodb put-item --table mytable --item file://item.json" + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json\n" + "aws kinesis put-record --stream-name samplestream --data file://data " + "--partition-key samplepartitionkey" ) with patch( @@ -141,7 +145,7 @@ def test_interactive_mode_update_all(self, tmp_path): def test_interactive_mode_cancel(self, tmp_path): """Test interactive mode with 'x' to cancel.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws s3api put-object --bucket mybucket --body file://data.json") + script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): with patch("builtins.input", return_value="q"): diff --git a/awsclilinter/tests/test_linter.py b/awsclilinter/tests/test_linter.py index d405cbeb8eb0..d2ae6e047a24 100644 --- a/awsclilinter/tests/test_linter.py +++ b/awsclilinter/tests/test_linter.py @@ -7,7 +7,7 @@ class TestScriptLinter: def test_lint_finds_issues(self): """Test that linter finds issues in script.""" - script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json" + script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" linter = ScriptLinter([Base64BinaryFormatRule()]) findings = linter.lint(script) @@ -15,17 +15,9 @@ def test_lint_finds_issues(self): assert findings[0].rule_name == "binary-params-base64" assert "file://" in findings[0].original_text - def test_lint_no_issues(self): - """Test that linter returns no findings for compliant script.""" - script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json --cli-binary-format raw-in-base64-out" - linter = ScriptLinter([Base64BinaryFormatRule()]) - findings = linter.lint(script) - - assert len(findings) == 0 - def test_apply_fixes(self): """Test that fixes are applied correctly.""" - script = "aws s3api put-object --bucket mybucket --key mykey --body file://data.json" + script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" linter = ScriptLinter([Base64BinaryFormatRule()]) findings = linter.lint(script) fixed = linter.apply_fixes(script, findings) @@ -35,8 +27,8 @@ def test_apply_fixes(self): def test_multiple_issues(self): """Test linter with multiple issues.""" - script = """aws s3api put-object --bucket mybucket --key mykey --body file://data.json -aws dynamodb put-item --table-name mytable --item file://item.json""" + script = """aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json + aws kinesis put-record --stream-name samplestream --data file://data --partition-key samplepartitionkey""" linter = ScriptLinter([Base64BinaryFormatRule()]) findings = linter.lint(script) diff --git a/awsclilinter/tests/test_rules.py b/awsclilinter/tests/test_rules.py index fa69f3f9bb33..bdc9cb605be4 100644 --- a/awsclilinter/tests/test_rules.py +++ b/awsclilinter/tests/test_rules.py @@ -7,14 +7,13 @@ class TestBase64BinaryFormatRule: """Test cases for Base64BinaryFormatRule.""" def test_rule_properties(self): - """Test rule name and description.""" + """Test rule description.""" rule = Base64BinaryFormatRule() - assert rule.name == "binary-params-base64" assert "cli-binary-format" in rule.description def test_detects_missing_flag(self): """Test detection of missing --cli-binary-format flag.""" - script = "aws s3api put-object --bucket mybucket --body file://data.json" + script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" root = SgRoot(script, "bash") rule = Base64BinaryFormatRule() findings = rule.check(root) @@ -24,7 +23,7 @@ def test_detects_missing_flag(self): def test_no_detection_with_flag(self): """Test no detection when flag is present.""" - script = "aws s3api put-object --bucket mybucket --body file://data.json --cli-binary-format raw-in-base64-out" + script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json --cli-binary-format raw-in-base64-out" root = SgRoot(script, "bash") rule = Base64BinaryFormatRule() findings = rule.check(root) @@ -32,8 +31,10 @@ def test_no_detection_with_flag(self): assert len(findings) == 0 def test_no_detection_without_file_protocol(self): - """Test no detection when file:// is not used.""" - script = "aws s3api put-object --bucket mybucket --body data.json" + """Test no detection when file:// is not used. Even though the breaking change may + still occur without the use of file://, only the case where file:// is used can be detected + statically.""" + script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-string secret123" root = SgRoot(script, "bash") rule = Base64BinaryFormatRule() findings = rule.check(root) From c8ca3eeaa147c7aefbc7afc894b8f4fdc640f7f3 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 11:49:36 -0400 Subject: [PATCH 04/19] Inject + at start of each line in inserted test. --- awsclilinter/awsclilinter/cli.py | 5 ++++- awsclilinter/awsclilinter/rules/base64_rule.py | 4 +++- awsclilinter/awsclilinter/rules_base.py | 1 - awsclilinter/tests/test_rules.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 9a37b51c51ba..ba34424d3712 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -66,7 +66,10 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content line_start_pos, _ = line_positions[i] start_pos_in_line = max(0, finding.edit.start_pos - line_start_pos) end_pos_in_line = min(len(line), finding.edit.end_pos - line_start_pos) - new_line = line[:start_pos_in_line] + finding.suggested_fix + line[end_pos_in_line:] + new_line = line[:start_pos_in_line] + finding.edit.inserted_text + line[end_pos_in_line:] + # In case the inserted text takes up multiple lines, + # inject a + at the start of each line. + new_line = new_line.replace("\n", "\n+") # Print the new line suggestion. print(f"{GREEN}+{new_line}{RESET}") else: diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py index 674b3568c893..acafa5c0756e 100644 --- a/awsclilinter/awsclilinter/rules/base64_rule.py +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -42,13 +42,15 @@ def check(self, root: SgRoot) -> List[LintFinding]: suggested = original + " --cli-binary-format raw-in-base64-out" edit = stmt.replace(suggested) + print(f"suggested fix: {suggested}") + print(f"edit text: {edit.inserted_text}") + findings.append( LintFinding( line_start=stmt.range().start.line, line_end=stmt.range().end.line, edit=edit, original_text=original, - suggested_fix=suggested, rule_name=self.name, description=self.description, ) diff --git a/awsclilinter/awsclilinter/rules_base.py b/awsclilinter/awsclilinter/rules_base.py index 991ae5b99e58..5900a2c09ff8 100644 --- a/awsclilinter/awsclilinter/rules_base.py +++ b/awsclilinter/awsclilinter/rules_base.py @@ -13,7 +13,6 @@ class LintFinding: line_end: int edit: Edit original_text: str - suggested_fix: str rule_name: str description: str diff --git a/awsclilinter/tests/test_rules.py b/awsclilinter/tests/test_rules.py index bdc9cb605be4..0248a44b7a12 100644 --- a/awsclilinter/tests/test_rules.py +++ b/awsclilinter/tests/test_rules.py @@ -19,7 +19,7 @@ def test_detects_missing_flag(self): findings = rule.check(root) assert len(findings) == 1 - assert "--cli-binary-format" in findings[0].suggested_fix + assert "--cli-binary-format" in findings[0].edit.inserted_text def test_no_detection_with_flag(self): """Test no detection when flag is present.""" From a6ff73da42fd299a74893d78853f84560eeab5dc Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 11:50:30 -0400 Subject: [PATCH 05/19] Remove printlines. --- awsclilinter/awsclilinter/rules/base64_rule.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py index acafa5c0756e..8672c5e9b2f5 100644 --- a/awsclilinter/awsclilinter/rules/base64_rule.py +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -42,9 +42,6 @@ def check(self, root: SgRoot) -> List[LintFinding]: suggested = original + " --cli-binary-format raw-in-base64-out" edit = stmt.replace(suggested) - print(f"suggested fix: {suggested}") - print(f"edit text: {edit.inserted_text}") - findings.append( LintFinding( line_start=stmt.range().start.line, From b52cc70e8b2ad0c3fffc1cbb4dd1bcb3eba4c984 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 13:09:43 -0400 Subject: [PATCH 06/19] Add awsclilinter to setuptools exclude param. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 300d0084cb93..723d269901e7 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ def find_version(*file_paths): scripts=['bin/aws', 'bin/aws.cmd', 'bin/aws_completer', 'bin/aws_zsh_completer.sh', 'bin/aws_bash_completer'], - packages=find_packages(exclude=['tests*']), + packages=find_packages(exclude=['tests*', 'awsclilinter']), include_package_data=True, install_requires=install_requires, extras_require={}, From 4ae8d0a043822d6abf847e187b0c785c2f553020 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 13:20:01 -0400 Subject: [PATCH 07/19] Prune awsclilinter in MANIFEST.in. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 34fb540e8276..98ea78d55fa6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,3 +6,4 @@ include .pre-commit-config.yaml recursive-include awscli/examples *.rst *.txt recursive-include awscli/data *.json recursive-include awscli/topics *.rst *.json +prune awsclilinter \ No newline at end of file From 66dcb9a9e636bb7a5ba2f8cb56b1ebe4ba99dcb1 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 14:36:28 -0400 Subject: [PATCH 08/19] Add pyproject.toml, setup.py, and MANIFEST.in. --- awsclilinter/MANIFEST.in | 7 +++++ awsclilinter/pyproject.toml | 30 +++++++++++++++++++ awsclilinter/setup.py | 59 +++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 awsclilinter/MANIFEST.in create mode 100644 awsclilinter/setup.py diff --git a/awsclilinter/MANIFEST.in b/awsclilinter/MANIFEST.in new file mode 100644 index 000000000000..777927b30491 --- /dev/null +++ b/awsclilinter/MANIFEST.in @@ -0,0 +1,7 @@ +include README.md +include requirements.txt +include requirements-dev.txt +include requirements-dev-lock.txt +recursive-include awsclilinter *.py +recursive-exclude tests * +recursive-exclude examples * diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 454ea39e7c68..9e29ba0ccfcc 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -6,11 +6,41 @@ build-backend = "setuptools.build_meta" name = "awsclilinter" version = "1.0.0" description = "CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2" +readme = "README.md" requires-python = ">=3.9" +license = "Apache-2.0" +keywords = ["aws", "cli", "linter", "bash", "script", "migration", "v1", "v2"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Utilities", +] dependencies = [ "ast-grep-py>=0.39.6", ] +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "black>=24.1.1", + "isort>=5.13.2", +] + +[project.urls] +Homepage = "https://github.com/aws/aws-cli" +"Bug Tracker" = "https://github.com/aws/aws-cli/issues" +Documentation = "https://github.com/aws/aws-cli" +"Source Code" = "https://github.com/aws/aws-cli" + [project.scripts] upgrade-aws-cli = "awsclilinter.cli:main" diff --git a/awsclilinter/setup.py b/awsclilinter/setup.py new file mode 100644 index 000000000000..85fa3e2f6f05 --- /dev/null +++ b/awsclilinter/setup.py @@ -0,0 +1,59 @@ +"""Setup configuration for awsclilinter package.""" + +from setuptools import find_packages, setup + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="awsclilinter", + version="1.0.0", + author="Amazon Web Services", + description="CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/aws/aws-cli", + project_urls={ + "Bug Tracker": "https://github.com/aws/aws-cli/issues", + "Documentation": "https://github.com/aws/aws-cli", + "Source Code": "https://github.com/aws/aws-cli", + }, + packages=find_packages(exclude=["tests", "tests.*"]), + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", + "Topic :: Utilities", + ], + python_requires=">=3.9", + install_requires=[ + "ast-grep-py>=0.39.6", + ], + extras_require={ + "dev": [ + "pytest>=8.0.0", + "black>=24.1.1", + "isort>=5.13.2", + ], + }, + entry_points={ + "console_scripts": [ + "upgrade-aws-cli=awsclilinter.cli:main", + ], + }, + license="Apache-2.0", + keywords="aws cli linter bash script migration v1 v2", + zip_safe=False, +) From 46e1ed6617944b3da06eae085f9b18c8cb00c27c Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 14:37:51 -0400 Subject: [PATCH 09/19] Update project name in readme. --- awsclilinter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 7fd78c2d19ce..e34a4f6dd1f6 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -1,4 +1,4 @@ -# AWS CLI Linter +# AWS CLI v1-to-v2 Upgrade Linter A CLI tool that lints bash scripts for AWS CLI v1 usage and updates them to avoid breaking changes introduced in AWS CLI v2. Not all breaking changes can be detected statically, From b6b55d2eba24f8cfb95bff5c631842ed217b0d33 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 21 Oct 2025 15:47:04 -0400 Subject: [PATCH 10/19] Fix typoe in license. --- awsclilinter/LICENSE.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 awsclilinter/LICENSE.txt diff --git a/awsclilinter/LICENSE.txt b/awsclilinter/LICENSE.txt new file mode 100644 index 000000000000..3d176f2a1677 --- /dev/null +++ b/awsclilinter/LICENSE.txt @@ -0,0 +1,12 @@ +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"). You +may not use this file except in compliance with the License. A copy of +the License is located at + + http://aws.amazon.com/apache2.0/ + +or in the "license" file accompanying this file. This file is +distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. From bd95147a61cf0b3ea8b98af63b6d08ae852dfafc Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 10 Nov 2025 16:21:57 -0500 Subject: [PATCH 11/19] Address basic PR feedback. --- awsclilinter/README.md | 62 +++++++++++++++++++++++++------- awsclilinter/awsclilinter/cli.py | 8 +++-- awsclilinter/setup.py | 59 ------------------------------ 3 files changed, 55 insertions(+), 74 deletions(-) delete mode 100644 awsclilinter/setup.py diff --git a/awsclilinter/README.md b/awsclilinter/README.md index e34a4f6dd1f6..1cd420bade34 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -9,26 +9,34 @@ For a full list of the breaking changes introduced with AWS CLI v2, see ## Installation -Run `make setup` to set up a virtual environment with the linter installed. Alternatively, -you can follow the steps below. +Most users should install AWS CLI Linter via `pip` in a `virtualenv`: -1. Create a virtual environment: -```bash -python3.12 -m venv venv -source venv/bin/activate +```shell +$ python3 -m pip install awsclilinter ``` -2. Install dependencies: -```bash -pip install -r requirements.txt -pip install -r requirements-dev-lock.txt +or, if you are not installing in a `virtualenv`, to install globally: + +```shell +$ sudo python3 -m pip install awsclilinter ``` -3. Install the package in development mode: -```bash -pip install -e . +or for your user: + +```shell +$ python3 -m pip install --user awsclilinter ``` +If you have the `awsclilinter` package installed and want to upgrade to the latest version, you can run: + +```shell +$ python3 -m pip install --upgrade awscli +``` + +This will install the `awsclilinter` package as well as all dependencies. + +If you want to run `awsclilinter` from source, see the [Installing development versions](#installing-development-versions) section. + ## Usage ### Dry-run mode (default) @@ -63,6 +71,34 @@ In interactive mode, you can: ## Development +### Installing development versions + +If you are interested in using the latest released version of the AWS CLI Linter, please see the [Installation](#installation) section. +This section is for anyone who wants to install the development version of the AWS CLI Linter. You might need to do this if: + +* You are developing a feature for the AWS CLI Linter and plan on submitting a Pull Request. +* You want to test the latest changes of the AWS CLI Linter before they make it into an official release. + +Run `make setup` to set up a virtual environment with the linter installed. Alternatively, +you can follow the steps below. + +1. Create a virtual environment: +```bash +python3.12 -m venv venv +source venv/bin/activate +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +pip install -r requirements-dev-lock.txt +``` + +3. Install the package in development mode: +```bash +pip install -e . +``` + ### Running tests ```bash make test diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index ba34424d3712..22455a0b6ac5 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -29,11 +29,15 @@ def get_user_choice(prompt: str) -> str: def display_finding(finding: LintFinding, index: int, total: int, script_content: str): """Display a finding to the user with context.""" src_lines = script_content.splitlines() - dest_lines = finding.edit.inserted_text.splitlines() start_line = finding.line_start end_line = finding.line_end src_lines_removed = end_line - start_line + 1 - new_lines_added = len(dest_lines) + new_lines_added = end_line - start_line + 1 + + if src_lines_removed != new_lines_added: + raise RuntimeError( + f"Number of lines removed ({src_lines_removed}) does not match number of lines added ({new_lines_added})" + ) # Create a map from line numbers to their indices within the full script file line_positions = [] diff --git a/awsclilinter/setup.py b/awsclilinter/setup.py deleted file mode 100644 index 85fa3e2f6f05..000000000000 --- a/awsclilinter/setup.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Setup configuration for awsclilinter package.""" - -from setuptools import find_packages, setup - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setup( - name="awsclilinter", - version="1.0.0", - author="Amazon Web Services", - description="CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/aws/aws-cli", - project_urls={ - "Bug Tracker": "https://github.com/aws/aws-cli/issues", - "Documentation": "https://github.com/aws/aws-cli", - "Source Code": "https://github.com/aws/aws-cli", - }, - packages=find_packages(exclude=["tests", "tests.*"]), - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Software Development :: Quality Assurance", - "Topic :: Software Development :: Testing", - "Topic :: Utilities", - ], - python_requires=">=3.9", - install_requires=[ - "ast-grep-py>=0.39.6", - ], - extras_require={ - "dev": [ - "pytest>=8.0.0", - "black>=24.1.1", - "isort>=5.13.2", - ], - }, - entry_points={ - "console_scripts": [ - "upgrade-aws-cli=awsclilinter.cli:main", - ], - }, - license="Apache-2.0", - keywords="aws cli linter bash script migration v1 v2", - zip_safe=False, -) From b7fcd4b8ba41829ebd3f8f885a5e094e6f70b414 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 10 Nov 2025 16:45:40 -0500 Subject: [PATCH 12/19] More changes based on feedback. --- awsclilinter/MANIFEST.in | 3 - awsclilinter/Makefile | 36 ---------- awsclilinter/README.md | 30 ++++---- awsclilinter/awsclilinter/cli.py | 7 +- awsclilinter/pyproject.toml | 16 +++-- awsclilinter/requirements-dev-lock.txt | 97 -------------------------- awsclilinter/requirements-dev.txt | 4 -- awsclilinter/requirements.txt | 1 - awsclilinter/tests/test_cli.py | 16 +++-- awsclilinter/tests/test_linter.py | 8 ++- awsclilinter/tests/test_rules.py | 9 ++- 11 files changed, 55 insertions(+), 172 deletions(-) delete mode 100644 awsclilinter/Makefile delete mode 100644 awsclilinter/requirements-dev-lock.txt delete mode 100644 awsclilinter/requirements-dev.txt delete mode 100644 awsclilinter/requirements.txt diff --git a/awsclilinter/MANIFEST.in b/awsclilinter/MANIFEST.in index 777927b30491..5de61f385a65 100644 --- a/awsclilinter/MANIFEST.in +++ b/awsclilinter/MANIFEST.in @@ -1,7 +1,4 @@ include README.md -include requirements.txt -include requirements-dev.txt -include requirements-dev-lock.txt recursive-include awsclilinter *.py recursive-exclude tests * recursive-exclude examples * diff --git a/awsclilinter/Makefile b/awsclilinter/Makefile deleted file mode 100644 index ebd7170e2265..000000000000 --- a/awsclilinter/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -.PHONY: setup test format lint clean - -setup: - @echo "Setting up AWS CLI Linter..." - @if [ ! -d "venv" ]; then \ - echo "Creating virtual environment..."; \ - python3.12 -m venv venv; \ - fi - @echo "Installing dependencies..." - @. venv/bin/activate && pip install --upgrade pip - @if [ -f "requirements-dev-lock.txt" ]; then \ - echo "Using lockfile for reproducible builds..."; \ - . venv/bin/activate && pip install -r requirements-dev-lock.txt; \ - else \ - . venv/bin/activate && pip install -r requirements-dev.txt; \ - fi - @echo "Installing package..." - @. venv/bin/activate && pip install -e . - @echo "Setup complete! Activate the virtual environment with: source venv/bin/activate" - -test: - pytest tests/ -v - -format: - black awsclilinter tests - isort awsclilinter tests - -lint: - black --check awsclilinter tests - isort --check awsclilinter tests - -clean: - rm -rf venv - rm -rf build dist *.egg-info - find . -type d -name __pycache__ -exec rm -rf {} + - find . -type f -name "*.pyc" -delete diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 1cd420bade34..a9c5ded86d67 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -79,32 +79,34 @@ This section is for anyone who wants to install the development version of the A * You are developing a feature for the AWS CLI Linter and plan on submitting a Pull Request. * You want to test the latest changes of the AWS CLI Linter before they make it into an official release. -Run `make setup` to set up a virtual environment with the linter installed. Alternatively, -you can follow the steps below. +Install [uv](https://docs.astral.sh/uv/) if you haven't already, then set up the development environment: -1. Create a virtual environment: ```bash -python3.12 -m venv venv -source venv/bin/activate +uv sync --extra dev ``` -2. Install dependencies: +This will create a virtual environment, install all dependencies, and install the package in development mode. + +### Running tests ```bash -pip install -r requirements.txt -pip install -r requirements-dev-lock.txt +uv run pytest tests/ -v ``` -3. Install the package in development mode: +### Code formatting ```bash -pip install -e . +uv run ruff format awsclilinter tests +uv run ruff check --select I --fix awsclilinter tests ``` -### Running tests +### Code linting ```bash -make test +uv run ruff format --check awsclilinter tests +uv run ruff check awsclilinter tests ``` -### Code formatting +### Clean local workspace ```bash -make format +rm -rf .venv build dist *.egg-info +find . -type d -name __pycache__ -exec rm -rf {} + +find . -type f -name "*.pyc" -delete ``` diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 22455a0b6ac5..4ce220b3081e 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -36,7 +36,8 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content if src_lines_removed != new_lines_added: raise RuntimeError( - f"Number of lines removed ({src_lines_removed}) does not match number of lines added ({new_lines_added})" + f"Number of lines removed ({src_lines_removed}) does not match " + f"number of lines added ({new_lines_added})" ) # Create a map from line numbers to their indices within the full script file @@ -70,7 +71,9 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content line_start_pos, _ = line_positions[i] start_pos_in_line = max(0, finding.edit.start_pos - line_start_pos) end_pos_in_line = min(len(line), finding.edit.end_pos - line_start_pos) - new_line = line[:start_pos_in_line] + finding.edit.inserted_text + line[end_pos_in_line:] + new_line = ( + line[:start_pos_in_line] + finding.edit.inserted_text + line[end_pos_in_line:] + ) # In case the inserted text takes up multiple lines, # inject a + at the start of each line. new_line = new_line.replace("\n", "\n+") diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 9e29ba0ccfcc..425b9974e9fe 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -31,8 +31,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "pytest>=8.0.0", - "black>=24.1.1", - "isort>=5.13.2", + "ruff>=0.8.0", ] [project.urls] @@ -44,13 +43,16 @@ Documentation = "https://github.com/aws/aws-cli" [project.scripts] upgrade-aws-cli = "awsclilinter.cli:main" -[tool.black] +[tool.ruff] line-length = 100 -target-version = ['py312'] +target-version = "py312" -[tool.isort] -profile = "black" -line_length = 100 +[tool.ruff.lint] +select = ["E", "F", "I"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/awsclilinter/requirements-dev-lock.txt b/awsclilinter/requirements-dev-lock.txt deleted file mode 100644 index 2938acef36e3..000000000000 --- a/awsclilinter/requirements-dev-lock.txt +++ /dev/null @@ -1,97 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes --output-file=requirements-dev-lock.txt requirements-dev.txt -# -ast-grep-py==0.39.6 \ - --hash=sha256:01d3e4a7dfea92ee43ff712843f795a9f9a821c4bd0139fd2ce773851dee263d \ - --hash=sha256:292a2eb0cd76b0ed39ef512f4489bb9936978ef54b5d601918bc697f263d1357 \ - --hash=sha256:2a7fffe7dcc55ea7678628072b511ea0252433748f6f3b19b6f2483f11874e3c \ - --hash=sha256:3358b5d26d90cb928951923b4a7ac3973f7d7303d1e372321279023447b1dd33 \ - --hash=sha256:3ef4cc66c228654b2fb41e72d90bd5276e1cf947f8300ffc78abd67274136b1b \ - --hash=sha256:3f61646794e1c74023e8881ad93fd8b13778bfe5e50b61a4e4f8d3e8802bc914 \ - --hash=sha256:4380389a83fd7f06fe4c30e6c68ac786b99a8ce513ac0742148d27a1c987c0c0 \ - --hash=sha256:46de5659378e2c1f1915629eb4f7e9f2add7b1a69ad774835c263c6e4b61613c \ - --hash=sha256:4fc47c76d12f03407616ae60a4e210e4e337fcfd278ad24d6cf17f81cdb2b338 \ - --hash=sha256:4fd970464f63af98e66cceabee836ac0304a612ff060e9461cd026c2193c809f \ - --hash=sha256:63cce05fa68dd9d626711ed46a775f1f091fc6e075e685471c02d492b2d77a8a \ - --hash=sha256:65172cf9514f633d5922ba4074cd2e34470ee08abb29e6d8eb4059ac370ec45f \ - --hash=sha256:678a68502ea4887e3724842429b5adc0da8a719cb4720e2bdd95a1d4f9f208ed \ - --hash=sha256:74a2e7fab3da96e403c7c257652218cbe7e5fc15055842215a91f14d158c9589 \ - --hash=sha256:828dd474c2504fc7544733b8f200245be0d2ae67060f6e3d0fe7c5852d0bf9cf \ - --hash=sha256:931229ec21c3b116f1bffb65fca8f67bddf7b31f3b89d02230ae331e30997768 \ - --hash=sha256:a77156ea53c6e6efaf7cfb8cb45f8731b198dc0ef2ea1e5b31b1c92fe281c203 \ - --hash=sha256:b70440bfdbc1ed71b26430dd05ee4fc19bb97b43b1d1f0fea8ee073f5a4e1bec \ - --hash=sha256:c11245346a78deedb6b7bc65cca6a6c22783f2a33e1e999f3ba7d7bf00d89db8 \ - --hash=sha256:c5194c8ec3bf05fc25dda5a1f9d77a47c7ad08f997c1d579bdfbb0bc12afa683 \ - --hash=sha256:ca8e8cd36aa81f89448cdadf00875e5ac0e14cff0cc11526dd1a1821a35cdae9 \ - --hash=sha256:cb804e6e048c35c873a396737f616124fb54343d392e87390e3cd515d44d281c \ - --hash=sha256:d540136365e95b767cbc2772fd08b8968bd151e1aaaafaa51d91303659367120 \ - --hash=sha256:dc18747c0f2614984c855636e2f7430998bd83c952cb964aa115eca096bfcb8b \ - --hash=sha256:e473ef786fb3e12192ef53682f68af634b30716859dbc44764164981be777fcd \ - --hash=sha256:f4227d320719de840ed0bb32a281de3b9d2fa33897dcb3f93d2ae3391affc70e \ - --hash=sha256:fbe02b082474831fc2716cf8e8b312c5da97a992967e50ac5e37f83de385fe18 - # via -r /Users/aemous/GitHub/aws-cli2/awsclilinter/requirements.txt -black==24.1.1 \ - --hash=sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8 \ - --hash=sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6 \ - --hash=sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62 \ - --hash=sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445 \ - --hash=sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c \ - --hash=sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a \ - --hash=sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9 \ - --hash=sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2 \ - --hash=sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6 \ - --hash=sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b \ - --hash=sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4 \ - --hash=sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168 \ - --hash=sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d \ - --hash=sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5 \ - --hash=sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024 \ - --hash=sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e \ - --hash=sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b \ - --hash=sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161 \ - --hash=sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717 \ - --hash=sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8 \ - --hash=sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac \ - --hash=sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7 - # via -r requirements-dev.txt -click==8.3.0 \ - --hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \ - --hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4 - # via black -iniconfig==2.3.0 \ - --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ - --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - # via pytest -isort==5.13.2 \ - --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ - --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 - # via -r requirements-dev.txt -mypy-extensions==1.1.0 \ - --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ - --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 - # via black -packaging==25.0 \ - --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ - --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f - # via - # black - # pytest -pathspec==0.12.1 \ - --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ - --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 - # via black -platformdirs==4.5.0 \ - --hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \ - --hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 - # via black -pluggy==1.6.0 \ - --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ - --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via pytest -pytest==8.0.0 \ - --hash=sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c \ - --hash=sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6 - # via -r requirements-dev.txt diff --git a/awsclilinter/requirements-dev.txt b/awsclilinter/requirements-dev.txt deleted file mode 100644 index c5abd4669027..000000000000 --- a/awsclilinter/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ --r requirements.txt -pytest==8.0.0 -black==24.1.1 -isort==5.13.2 diff --git a/awsclilinter/requirements.txt b/awsclilinter/requirements.txt deleted file mode 100644 index 63578da3ceea..000000000000 --- a/awsclilinter/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -ast-grep-py==0.39.6 diff --git a/awsclilinter/tests/test_cli.py b/awsclilinter/tests/test_cli.py index b86dc83549a7..dd3570fde671 100644 --- a/awsclilinter/tests/test_cli.py +++ b/awsclilinter/tests/test_cli.py @@ -46,7 +46,9 @@ def test_no_issues_found(self, tmp_path, capsys): def test_dry_run_mode(self, tmp_path, capsys): """Test dry run mode displays findings.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") + script_file.write_text( + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" + ) with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file)]): main() @@ -57,7 +59,9 @@ def test_dry_run_mode(self, tmp_path, capsys): def test_fix_mode(self, tmp_path): """Test fix mode modifies the script.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") + script_file.write_text( + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" + ) with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--fix"]): main() @@ -68,7 +72,9 @@ def test_output_mode(self, tmp_path): """Test output mode creates new file.""" script_file = tmp_path / "test.sh" output_file = tmp_path / "output.sh" - script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") + script_file.write_text( + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" + ) with patch( "sys.argv", @@ -145,7 +151,9 @@ def test_interactive_mode_update_all(self, tmp_path): def test_interactive_mode_cancel(self, tmp_path): """Test interactive mode with 'x' to cancel.""" script_file = tmp_path / "test.sh" - script_file.write_text("aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json") + script_file.write_text( + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" + ) with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): with patch("builtins.input", return_value="q"): diff --git a/awsclilinter/tests/test_linter.py b/awsclilinter/tests/test_linter.py index d2ae6e047a24..1f3afb274259 100644 --- a/awsclilinter/tests/test_linter.py +++ b/awsclilinter/tests/test_linter.py @@ -27,8 +27,12 @@ def test_apply_fixes(self): def test_multiple_issues(self): """Test linter with multiple issues.""" - script = """aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json - aws kinesis put-record --stream-name samplestream --data file://data --partition-key samplepartitionkey""" + script = ( + "aws secretsmanager put-secret-value --secret-id secret1213 " + "--secret-binary file://data.json\n" + " aws kinesis put-record --stream-name samplestream " + "--data file://data --partition-key samplepartitionkey" + ) linter = ScriptLinter([Base64BinaryFormatRule()]) findings = linter.lint(script) diff --git a/awsclilinter/tests/test_rules.py b/awsclilinter/tests/test_rules.py index 0248a44b7a12..dc886b4b6fb0 100644 --- a/awsclilinter/tests/test_rules.py +++ b/awsclilinter/tests/test_rules.py @@ -23,7 +23,10 @@ def test_detects_missing_flag(self): def test_no_detection_with_flag(self): """Test no detection when flag is present.""" - script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json --cli-binary-format raw-in-base64-out" + script = ( + "aws secretsmanager put-secret-value --secret-id secret1213 " + "--secret-binary file://data.json --cli-binary-format raw-in-base64-out" + ) root = SgRoot(script, "bash") rule = Base64BinaryFormatRule() findings = rule.check(root) @@ -34,7 +37,9 @@ def test_no_detection_without_file_protocol(self): """Test no detection when file:// is not used. Even though the breaking change may still occur without the use of file://, only the case where file:// is used can be detected statically.""" - script = "aws secretsmanager put-secret-value --secret-id secret1213 --secret-string secret123" + script = ( + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-string secret123" + ) root = SgRoot(script, "bash") rule = Base64BinaryFormatRule() findings = rule.check(root) From 9ae1483378e1421a21327f98b4e906caa2ad2956 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 09:15:19 -0500 Subject: [PATCH 13/19] Replace q to quit with save and exit. --- awsclilinter/README.md | 2 +- awsclilinter/awsclilinter/cli.py | 11 +- awsclilinter/tests/test_cli.py | 30 +++- awsclilinter/uv.lock | 264 +++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+), 15 deletions(-) create mode 100644 awsclilinter/uv.lock diff --git a/awsclilinter/README.md b/awsclilinter/README.md index a9c5ded86d67..756e6e1243b5 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -67,7 +67,7 @@ In interactive mode, you can: - Press `y` to accept the current change - Press `n` to skip the current change - Press `u` to accept all remaining changes -- Press `q` to cancel and quit +- Press `s` to save and exit (applies all accepted changes so far) ## Development diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 4ce220b3081e..d62178e44154 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -21,9 +21,9 @@ def get_user_choice(prompt: str) -> str: """Get user input for interactive mode.""" while True: choice = input(prompt).lower().strip() - if choice in ["y", "n", "u", "q"]: + if choice in ["y", "n", "u", "s"]: return choice - print("Invalid choice. Please enter y, n, u, or q.") + print("Invalid choice. Please enter y, n, u, or s.") def display_finding(finding: LintFinding, index: int, total: int, script_content: str): @@ -89,16 +89,15 @@ def interactive_mode(findings: List[LintFinding], script_content: str) -> List[L accepted = [] for i, finding in enumerate(findings, 1): display_finding(finding, i, len(findings), script_content) - choice = get_user_choice("\nApply this fix? [y]es, [n]o, [u]pdate all, [q]uit: ") + choice = get_user_choice("\nApply this fix? [y]es, [n]o, [u]pdate all, [s]ave and exit: ") if choice == "y": accepted.append(finding) elif choice == "u": accepted.extend(findings[i - 1 :]) break - elif choice == "q": - print("Cancelled.") - sys.exit(0) + elif choice == "s": + break return accepted diff --git a/awsclilinter/tests/test_cli.py b/awsclilinter/tests/test_cli.py index dd3570fde671..8e507a382008 100644 --- a/awsclilinter/tests/test_cli.py +++ b/awsclilinter/tests/test_cli.py @@ -148,15 +148,29 @@ def test_interactive_mode_update_all(self, tmp_path): fixed_content = output_file.read_text() assert fixed_content.count("--cli-binary-format") == 2 - def test_interactive_mode_cancel(self, tmp_path): - """Test interactive mode with 'x' to cancel.""" + def test_interactive_mode_save_and_exit(self, tmp_path): + """Test interactive mode with 's' to save and exit.""" script_file = tmp_path / "test.sh" + output_file = tmp_path / "output.sh" script_file.write_text( - "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json" + "aws secretsmanager put-secret-value --secret-id secret1213 --secret-binary file://data.json\n" + "aws kinesis put-record --stream-name samplestream --data file://data " + "--partition-key samplepartitionkey" ) - with patch("sys.argv", ["upgrade-aws-cli", "--script", str(script_file), "--interactive"]): - with patch("builtins.input", return_value="q"): - with pytest.raises(SystemExit) as exc_info: - main() - assert exc_info.value.code == 0 + with patch( + "sys.argv", + [ + "upgrade-aws-cli", + "--script", + str(script_file), + "--interactive", + "--output", + str(output_file), + ], + ): + with patch("builtins.input", side_effect=["y", "s"]): + main() + fixed_content = output_file.read_text() + # Only first change should be applied since we pressed 's' on the second + assert fixed_content.count("--cli-binary-format") == 1 diff --git a/awsclilinter/uv.lock b/awsclilinter/uv.lock new file mode 100644 index 000000000000..6f2e5a971f66 --- /dev/null +++ b/awsclilinter/uv.lock @@ -0,0 +1,264 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "ast-grep-py" +version = "0.39.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/b2/dbd082539aaf58a95ff3f97783dc3df51bc1ac54ed4ae195b00054ee23c3/ast_grep_py-0.39.9.tar.gz", hash = "sha256:1260d16e5bb25e486b9e9263d1c9d8ed819fa2940527169718e35e91d9c7c717", size = 135854, upload-time = "2025-11-07T02:22:23.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/13/c06895922aebda036bd1f3e336e184acbb5af620cc6eeb73fb4b7a484646/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a5d3ce635ebc85db35a20f89b69afbeabfc19cc47e3f2c95ee49e85231851fa5", size = 4807686, upload-time = "2025-11-07T02:21:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/44/43/884caa27108b371609f3bde11b040b88fd24af41dfc398bf31250da8d9a5/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:757660bc42b487b70398e6f0f0ea5dd28e3394e32ac996ca92b3dea7733b89af", size = 4911988, upload-time = "2025-11-07T02:21:41.998Z" }, + { url = "https://files.pythonhosted.org/packages/8e/da/4168d4ab5b7b3905381fb2ff0e29045f45091adef2e731e5841e4b2f42e5/ast_grep_py-0.39.9-cp310-cp310-win32.whl", hash = "sha256:9e6abe38e12ac95111acef1057cab99f21e90211c40ac41974f8d04eb028ce17", size = 4531742, upload-time = "2025-11-07T02:21:43.706Z" }, + { url = "https://files.pythonhosted.org/packages/66/54/7476056e2348b35bcaac412f5662914963a94b5e78abc1e7d69012aae1e0/ast_grep_py-0.39.9-cp310-cp310-win_amd64.whl", hash = "sha256:d5e942f27750084697afc464be93e808b847f62cfde9b4950cefe8b30fbead33", size = 4658311, upload-time = "2025-11-07T02:21:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ea/b62cf84eb71eda7841c96eea1c360d1528cbaf0505ec7e1810710d60f5a6/ast_grep_py-0.39.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:340acb957af5e732744b549bbee44eaf3d2573313e0c4e831bc7aa7cf0ade0c7", size = 4839283, upload-time = "2025-11-07T02:21:47.123Z" }, + { url = "https://files.pythonhosted.org/packages/89/03/1838657ce742e031cae16b17a4c7a844610510abb6726b468e92226f71da/ast_grep_py-0.39.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:caf056615b633ceb8666a63e5244404887c48ffd51567ca8e3fe1ead65fb7fd3", size = 4993450, upload-time = "2025-11-07T02:21:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/823984158b9d4ea41a30f49e4c47fd241379e9168d1de3e890cd3f40ec17/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5cd5f89c86974b6a082ff171c87c363ea2fd6903572930f64694248e96cb249b", size = 4807926, upload-time = "2025-11-07T02:21:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/fe/93/6e9c24864f7df05cdba4eafb6dd6037899744b6465b64a744a2700952820/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de73719089e2277188873c544a29bf248904d2c6e33112827342847c96cabea5", size = 4912214, upload-time = "2025-11-07T02:21:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/f4baa0adc1d26bdd6a8db1d34626166aa033aa753b9c06afd2361f3b4c35/ast_grep_py-0.39.9-cp311-cp311-win32.whl", hash = "sha256:83b3aae87ad75259fcafbac45f428414887cf455bbdb73426db056a456f7f5f4", size = 4532694, upload-time = "2025-11-07T02:21:53.233Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3e/7466a698b004d8349883b6528645f9539a4c35f06f59f9eeb94c105da229/ast_grep_py-0.39.9-cp311-cp311-win_amd64.whl", hash = "sha256:5cbb0d626a2cbb4221fb9813c2cd9826d48ae243079217b94b6177f6c5536f65", size = 4658015, upload-time = "2025-11-07T02:21:54.943Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ae/3ecf8cb071e56f22ed750d86431f992eca37f3948dee71a4660d3584c673/ast_grep_py-0.39.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b0993314fcadf0114fe635921edcc25754572cf889bf5c80ccaac7c2603b6ade", size = 4836643, upload-time = "2025-11-07T02:21:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e2/1f1491e21f8d1248ff970ff43e0221e3ac37acd8dbdb629b34829611edf6/ast_grep_py-0.39.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9633229808150ab34dea25203f209d3cfe317e8da06d897bac087587169ef2b8", size = 4985618, upload-time = "2025-11-07T02:21:57.819Z" }, + { url = "https://files.pythonhosted.org/packages/f9/90/4c469d34d274c1e97a7870f92b2288fccaa14fa88d5edab760e5acbdfb50/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d485f1bbe5db8f800d8a3c60c85e24839f52cac33bc72874a060d17831774b97", size = 4809030, upload-time = "2025-11-07T02:21:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/76/ce/74fb305ba75b1750e79c20e58f81403264da9dc86034240ee3943b4c4a18/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b2a214ba114ea39325752f83e43968f8177538c91cebe37b7a5a399fda29acb", size = 4914929, upload-time = "2025-11-07T02:22:01.46Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e8/07b1f56e0de405f8869976a46d0c6918d3d69fc252ef3146eee1c97920af/ast_grep_py-0.39.9-cp312-cp312-win32.whl", hash = "sha256:43e87cc3315c77571c0fc6c02b5e2fab0554fc4492a35e69240183879e246538", size = 4532460, upload-time = "2025-11-07T02:22:02.689Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bf/88e7dafb65d0f8a613d6558751ea072aa306c30f67224318be68bc0e47cf/ast_grep_py-0.39.9-cp312-cp312-win_amd64.whl", hash = "sha256:20cff26eee0e79b6679a5a7d5a875753b604eabadb85d5ed0e6f48fbc8d870e5", size = 4659784, upload-time = "2025-11-07T02:22:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3f/1f325a0b3870888e09304baff29496d26347aaccdc2cfc7bd9942e6607d0/ast_grep_py-0.39.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:27f7d040e0f2520be0d3a840b9d7b2bb213eb2fdbff57538774504ac9ed2bda7", size = 4838805, upload-time = "2025-11-07T02:22:05.924Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/7f26dde476dca33d19897ad2c2627a568ae0b47ac005c126b25fb658f41a/ast_grep_py-0.39.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10fde4d654be5f5d9e91220877d4af4d801a17a37bc812d442e0af04cc5d69a1", size = 4989079, upload-time = "2025-11-07T02:22:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/6e/12/4830e54fbd6f9ffe8943de96a64f39a58539b43314a0a46ce3f76cd52577/ast_grep_py-0.39.9-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fee0b8480f02658903d7c42ff57c9ee9e09946a027e0b165324f3cb0ee4814e0", size = 4808951, upload-time = "2025-11-07T02:22:08.85Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e6/c17bcfd8e99c08ab55c9f723b7668d2939269d4515d77fec88dae4547ac1/ast_grep_py-0.39.9-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4fed1f6b5242525151babd1e982bb21306500834eb48f32d104905c3980ae20b", size = 4914653, upload-time = "2025-11-07T02:22:10.738Z" }, + { url = "https://files.pythonhosted.org/packages/59/2b/987e3e85368ad1381fedeef0664814ab909564c2ef5c713c24f245290f9f/ast_grep_py-0.39.9-cp313-cp313-win32.whl", hash = "sha256:f582a896f1e23051a91e64e17de6d90c7252b25070bdb47a397e30679ba25b63", size = 4535662, upload-time = "2025-11-07T02:22:12.119Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ae/3277b26a4b718b52bf29b85aaee3da92b7fc14fdd8e278cd9f71a5c67e24/ast_grep_py-0.39.9-cp313-cp313-win_amd64.whl", hash = "sha256:940040754520112d52d9051c4c3be7c47a56d693ae19d0bda3f267bcdff38561", size = 4659232, upload-time = "2025-11-07T02:22:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/7aac11346a663cccc625afde110d0489dc260b79167523276ad571562a94/ast_grep_py-0.39.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3dcca009886492009dad65e5de5a67627bc288ae7ac6ee74648fc29faac4cd18", size = 4838784, upload-time = "2025-11-07T02:22:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/4f/50/9cf49354cd41f458a629744b0ed34f244b05306dbbd847b5b6731fada61d/ast_grep_py-0.39.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77f5f9c9d91969e2eb0d164135006639f07ad956cf334e20aa3b7c2c6b44d0f2", size = 4989891, upload-time = "2025-11-07T02:22:16.251Z" }, + { url = "https://files.pythonhosted.org/packages/8e/27/22bb880a59c9908489bd3edfe6b169b3db202209cb3b167404f38d27a02b/ast_grep_py-0.39.9-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0aece3d37fab65ff76ce86f1e8c43d5481ab02faa10d5dc8248c5723f68fa211", size = 4808826, upload-time = "2025-11-07T02:22:17.602Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/1e13e149ee3ed2d20e597c3389b4fa489f7e79cb507ee8e089479a250f51/ast_grep_py-0.39.9-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:633b1aef5cadf7cfd5a521297264de1977832a4ba0350c3d7383e851a514f171", size = 4913960, upload-time = "2025-11-07T02:22:18.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/49/bf120661c474967f4cdb4934da19793d12ee179d3549a34364567caf409f/ast_grep_py-0.39.9-cp314-cp314-win32.whl", hash = "sha256:0d0b80fba4531d025effe5a60c64525b797c4a3f76c5661cfe814576e8d96c99", size = 4536334, upload-time = "2025-11-07T02:22:20.794Z" }, + { url = "https://files.pythonhosted.org/packages/90/b4/833fbc038eddad03ca0ff5de1c4a959da2390c94258f6dae541c38007370/ast_grep_py-0.39.9-cp314-cp314-win_amd64.whl", hash = "sha256:248c1825b211dce645a36f3f0c8d33bddc3ed3703f21c003cb2c12baf1955540", size = 4660331, upload-time = "2025-11-07T02:22:22.105Z" }, +] + +[[package]] +name = "awsclilinter" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "ast-grep-py" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "ast-grep-py", specifier = ">=0.39.6" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, +] +provides-extras = ["dev"] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764, upload-time = "2025-11-08T17:25:33.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/99/cafef234114a3b6d9f3aaed0723b437c40c57bdb7b3e4c3a575bc4890052/pytest-9.0.0-py3-none-any.whl", hash = "sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96", size = 373364, upload-time = "2025-11-08T17:25:31.811Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/55/cccfca45157a2031dcbb5a462a67f7cf27f8b37d4b3b1cd7438f0f5c1df6/ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3", size = 5587844, upload-time = "2025-11-06T22:07:45.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/b9/67240254166ae1eaa38dec32265e9153ac53645a6c6670ed36ad00722af8/ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518", size = 12606781, upload-time = "2025-11-06T22:07:01.841Z" }, + { url = "https://files.pythonhosted.org/packages/46/c8/09b3ab245d8652eafe5256ab59718641429f68681ee713ff06c5c549f156/ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4", size = 12946765, upload-time = "2025-11-06T22:07:05.858Z" }, + { url = "https://files.pythonhosted.org/packages/14/bb/1564b000219144bf5eed2359edc94c3590dd49d510751dad26202c18a17d/ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33", size = 11928120, upload-time = "2025-11-06T22:07:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/a3/92/d5f1770e9988cc0742fefaa351e840d9aef04ec24ae1be36f333f96d5704/ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2", size = 12370877, upload-time = "2025-11-06T22:07:10.015Z" }, + { url = "https://files.pythonhosted.org/packages/e2/29/e9282efa55f1973d109faf839a63235575519c8ad278cc87a182a366810e/ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5", size = 12408538, upload-time = "2025-11-06T22:07:13.085Z" }, + { url = "https://files.pythonhosted.org/packages/8e/01/930ed6ecfce130144b32d77d8d69f5c610e6d23e6857927150adf5d7379a/ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e", size = 13141942, upload-time = "2025-11-06T22:07:15.386Z" }, + { url = "https://files.pythonhosted.org/packages/6a/46/a9c89b42b231a9f487233f17a89cbef9d5acd538d9488687a02ad288fa6b/ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8", size = 14544306, upload-time = "2025-11-06T22:07:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/78/96/9c6cf86491f2a6d52758b830b89b78c2ae61e8ca66b86bf5a20af73d20e6/ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649", size = 14210427, upload-time = "2025-11-06T22:07:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/71/f4/0666fe7769a54f63e66404e8ff698de1dcde733e12e2fd1c9c6efb689cb5/ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850", size = 13658488, upload-time = "2025-11-06T22:07:22.32Z" }, + { url = "https://files.pythonhosted.org/packages/ee/79/6ad4dda2cfd55e41ac9ed6d73ef9ab9475b1eef69f3a85957210c74ba12c/ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5", size = 13354908, upload-time = "2025-11-06T22:07:24.347Z" }, + { url = "https://files.pythonhosted.org/packages/b5/60/f0b6990f740bb15c1588601d19d21bcc1bd5de4330a07222041678a8e04f/ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132", size = 13587803, upload-time = "2025-11-06T22:07:26.327Z" }, + { url = "https://files.pythonhosted.org/packages/c9/da/eaaada586f80068728338e0ef7f29ab3e4a08a692f92eb901a4f06bbff24/ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67", size = 12279654, upload-time = "2025-11-06T22:07:28.46Z" }, + { url = "https://files.pythonhosted.org/packages/66/d4/b1d0e82cf9bf8aed10a6d45be47b3f402730aa2c438164424783ac88c0ed/ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469", size = 12357520, upload-time = "2025-11-06T22:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/04/f4/53e2b42cc82804617e5c7950b7079d79996c27e99c4652131c6a1100657f/ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde", size = 12719431, upload-time = "2025-11-06T22:07:33.831Z" }, + { url = "https://files.pythonhosted.org/packages/a2/94/80e3d74ed9a72d64e94a7b7706b1c1ebaa315ef2076fd33581f6a1cd2f95/ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349", size = 13464394, upload-time = "2025-11-06T22:07:35.905Z" }, + { url = "https://files.pythonhosted.org/packages/54/1a/a49f071f04c42345c793d22f6cf5e0920095e286119ee53a64a3a3004825/ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff", size = 12493429, upload-time = "2025-11-06T22:07:38.43Z" }, + { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, + { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From 0b11fcd3d9f22c99a2f5470a07c2b7eb18a3dfd6 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 10:00:46 -0500 Subject: [PATCH 14/19] Progress on difflib. --- awsclilinter/README.md | 10 +++ awsclilinter/awsclilinter/cli.py | 72 ++++++---------- awsclilinter/pyproject.toml | 2 +- awsclilinter/uv.lock | 140 ++----------------------------- 4 files changed, 45 insertions(+), 179 deletions(-) diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 756e6e1243b5..85680930aa0e 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -87,6 +87,16 @@ uv sync --extra dev This will create a virtual environment, install all dependencies, and install the package in development mode. +Activate the virtual environment: +```bash +source .venv/bin/activate +``` + +### Running the CLI +```bash +upgrade-aws-cli --script +``` + ### Running tests ```bash uv run pytest tests/ -v diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index d62178e44154..ad4608a95da5 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -1,4 +1,5 @@ import argparse +import difflib import sys from pathlib import Path from typing import List @@ -28,60 +29,41 @@ def get_user_choice(prompt: str) -> str: def display_finding(finding: LintFinding, index: int, total: int, script_content: str): """Display a finding to the user with context.""" - src_lines = script_content.splitlines() + src_lines = script_content.splitlines(keepends=True) + + # Apply the edit to get the fixed content + fixed_content = ( + script_content[: finding.edit.start_pos] + + finding.edit.inserted_text + + script_content[finding.edit.end_pos :] + ) + dest_lines = fixed_content.splitlines(keepends=True) + start_line = finding.line_start end_line = finding.line_end - src_lines_removed = end_line - start_line + 1 - new_lines_added = end_line - start_line + 1 - - if src_lines_removed != new_lines_added: - raise RuntimeError( - f"Number of lines removed ({src_lines_removed}) does not match " - f"number of lines added ({new_lines_added})" - ) - - # Create a map from line numbers to their indices within the full script file - line_positions = [] - pos = 0 - for i, line in enumerate(src_lines): - line_positions.append((pos, pos + len(line))) - pos += len(line) + 1 - - # Get context lines context_start = max(0, start_line - CONTEXT_SIZE) context_end = min(len(src_lines), end_line + CONTEXT_SIZE + 1) - src_context_size = context_end - context_start - dest_context_size = src_context_size + (new_lines_added - src_lines_removed) + + src_context = src_lines[context_start:context_end] + dest_context = dest_lines[context_start:context_end] print(f"\n[{index}/{total}] {finding.rule_name}") print(f"{finding.description}") - print( - f"\n{CYAN}@@ -{context_start + 1},{src_context_size} " - f"+{context_start + 1},{dest_context_size} @@{RESET}" - ) - for i in range(context_start, context_end): - line = src_lines[i] if i < len(src_lines) else "" - - if start_line <= i <= end_line: - # This line is being modified - print(f"{RED}-{line}{RESET}") - - if i == end_line: - line_start_pos, _ = line_positions[i] - start_pos_in_line = max(0, finding.edit.start_pos - line_start_pos) - end_pos_in_line = min(len(line), finding.edit.end_pos - line_start_pos) - new_line = ( - line[:start_pos_in_line] + finding.edit.inserted_text + line[end_pos_in_line:] - ) - # In case the inserted text takes up multiple lines, - # inject a + at the start of each line. - new_line = new_line.replace("\n", "\n+") - # Print the new line suggestion. - print(f"{GREEN}+{new_line}{RESET}") + diff = difflib.unified_diff(src_context, dest_context, lineterm="") + for line_num, line in enumerate(diff): + if line_num < 2: + # First 2 lines are the --- and +++ lines, we don't print those. + continue + elif line_num == 2: + # The 3rd line is the context control line. + print(f"\n{CYAN}{line}{RESET}") + elif line.startswith("-"): + print(f"{RED}{line}{RESET}", end="") + elif line.startswith("+"): + print(f"{GREEN}{line}{RESET}", end="") else: - # Context line - print(f"{line}") + print(line, end="") def interactive_mode(findings: List[LintFinding], script_content: str) -> List[LintFinding]: diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 425b9974e9fe..577337509b47 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -7,7 +7,7 @@ name = "awsclilinter" version = "1.0.0" description = "CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.12" license = "Apache-2.0" keywords = ["aws", "cli", "linter", "bash", "script", "migration", "v1", "v2"] classifiers = [ diff --git a/awsclilinter/uv.lock b/awsclilinter/uv.lock index 6f2e5a971f66..c7607cdb0a1a 100644 --- a/awsclilinter/uv.lock +++ b/awsclilinter/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] +requires-python = ">=3.12" [[package]] name = "ast-grep-py" @@ -12,16 +8,6 @@ version = "0.39.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/b2/dbd082539aaf58a95ff3f97783dc3df51bc1ac54ed4ae195b00054ee23c3/ast_grep_py-0.39.9.tar.gz", hash = "sha256:1260d16e5bb25e486b9e9263d1c9d8ed819fa2940527169718e35e91d9c7c717", size = 135854, upload-time = "2025-11-07T02:22:23.416Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/13/c06895922aebda036bd1f3e336e184acbb5af620cc6eeb73fb4b7a484646/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a5d3ce635ebc85db35a20f89b69afbeabfc19cc47e3f2c95ee49e85231851fa5", size = 4807686, upload-time = "2025-11-07T02:21:39.558Z" }, - { url = "https://files.pythonhosted.org/packages/44/43/884caa27108b371609f3bde11b040b88fd24af41dfc398bf31250da8d9a5/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:757660bc42b487b70398e6f0f0ea5dd28e3394e32ac996ca92b3dea7733b89af", size = 4911988, upload-time = "2025-11-07T02:21:41.998Z" }, - { url = "https://files.pythonhosted.org/packages/8e/da/4168d4ab5b7b3905381fb2ff0e29045f45091adef2e731e5841e4b2f42e5/ast_grep_py-0.39.9-cp310-cp310-win32.whl", hash = "sha256:9e6abe38e12ac95111acef1057cab99f21e90211c40ac41974f8d04eb028ce17", size = 4531742, upload-time = "2025-11-07T02:21:43.706Z" }, - { url = "https://files.pythonhosted.org/packages/66/54/7476056e2348b35bcaac412f5662914963a94b5e78abc1e7d69012aae1e0/ast_grep_py-0.39.9-cp310-cp310-win_amd64.whl", hash = "sha256:d5e942f27750084697afc464be93e808b847f62cfde9b4950cefe8b30fbead33", size = 4658311, upload-time = "2025-11-07T02:21:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ea/b62cf84eb71eda7841c96eea1c360d1528cbaf0505ec7e1810710d60f5a6/ast_grep_py-0.39.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:340acb957af5e732744b549bbee44eaf3d2573313e0c4e831bc7aa7cf0ade0c7", size = 4839283, upload-time = "2025-11-07T02:21:47.123Z" }, - { url = "https://files.pythonhosted.org/packages/89/03/1838657ce742e031cae16b17a4c7a844610510abb6726b468e92226f71da/ast_grep_py-0.39.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:caf056615b633ceb8666a63e5244404887c48ffd51567ca8e3fe1ead65fb7fd3", size = 4993450, upload-time = "2025-11-07T02:21:48.775Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/823984158b9d4ea41a30f49e4c47fd241379e9168d1de3e890cd3f40ec17/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5cd5f89c86974b6a082ff171c87c363ea2fd6903572930f64694248e96cb249b", size = 4807926, upload-time = "2025-11-07T02:21:50.367Z" }, - { url = "https://files.pythonhosted.org/packages/fe/93/6e9c24864f7df05cdba4eafb6dd6037899744b6465b64a744a2700952820/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de73719089e2277188873c544a29bf248904d2c6e33112827342847c96cabea5", size = 4912214, upload-time = "2025-11-07T02:21:51.923Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/f4baa0adc1d26bdd6a8db1d34626166aa033aa753b9c06afd2361f3b4c35/ast_grep_py-0.39.9-cp311-cp311-win32.whl", hash = "sha256:83b3aae87ad75259fcafbac45f428414887cf455bbdb73426db056a456f7f5f4", size = 4532694, upload-time = "2025-11-07T02:21:53.233Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3e/7466a698b004d8349883b6528645f9539a4c35f06f59f9eeb94c105da229/ast_grep_py-0.39.9-cp311-cp311-win_amd64.whl", hash = "sha256:5cbb0d626a2cbb4221fb9813c2cd9826d48ae243079217b94b6177f6c5536f65", size = 4658015, upload-time = "2025-11-07T02:21:54.943Z" }, { url = "https://files.pythonhosted.org/packages/2d/ae/3ecf8cb071e56f22ed750d86431f992eca37f3948dee71a4660d3584c673/ast_grep_py-0.39.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b0993314fcadf0114fe635921edcc25754572cf889bf5c80ccaac7c2603b6ade", size = 4836643, upload-time = "2025-11-07T02:21:56.343Z" }, { url = "https://files.pythonhosted.org/packages/ee/e2/1f1491e21f8d1248ff970ff43e0221e3ac37acd8dbdb629b34829611edf6/ast_grep_py-0.39.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9633229808150ab34dea25203f209d3cfe317e8da06d897bac087587169ef2b8", size = 4985618, upload-time = "2025-11-07T02:21:57.819Z" }, { url = "https://files.pythonhosted.org/packages/f9/90/4c469d34d274c1e97a7870f92b2288fccaa14fa88d5edab760e5acbdfb50/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d485f1bbe5db8f800d8a3c60c85e24839f52cac33bc72874a060d17831774b97", size = 4809030, upload-time = "2025-11-07T02:21:59.898Z" }, @@ -52,8 +38,7 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "ruff" }, ] @@ -74,37 +59,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "exceptiongroup" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -137,42 +95,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - [[package]] name = "pytest" version = "9.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764, upload-time = "2025-11-08T17:25:33.34Z" } wheels = [ @@ -204,61 +136,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, ] - -[[package]] -name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] From 932d205bfdc728c739c83ae682c314f497817a6c Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 10:10:13 -0500 Subject: [PATCH 15/19] Finalize usage of difflib. --- awsclilinter/awsclilinter/cli.py | 3 +++ awsclilinter/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index ad4608a95da5..5414711532ed 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -59,10 +59,13 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content # The 3rd line is the context control line. print(f"\n{CYAN}{line}{RESET}") elif line.startswith("-"): + # Removed line print(f"{RED}{line}{RESET}", end="") elif line.startswith("+"): + # Added line print(f"{GREEN}{line}{RESET}", end="") else: + # Context (unchanged) lines always start with whitespace. print(line, end="") diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 577337509b47..425b9974e9fe 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -7,7 +7,7 @@ name = "awsclilinter" version = "1.0.0" description = "CLI tool to lint and upgrade bash scripts from AWS CLI v1 to v2" readme = "README.md" -requires-python = ">=3.12" +requires-python = ">=3.9" license = "Apache-2.0" keywords = ["aws", "cli", "linter", "bash", "script", "migration", "v1", "v2"] classifiers = [ From 7b99a78732a09bb3b5bf6d234d95c392a03ad16c Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 10:11:17 -0500 Subject: [PATCH 16/19] Formatting. --- awsclilinter/awsclilinter/cli.py | 2 +- awsclilinter/uv.lock | 140 +++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 8 deletions(-) diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 5414711532ed..410f30b351e9 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -30,7 +30,7 @@ def get_user_choice(prompt: str) -> str: def display_finding(finding: LintFinding, index: int, total: int, script_content: str): """Display a finding to the user with context.""" src_lines = script_content.splitlines(keepends=True) - + # Apply the edit to get the fixed content fixed_content = ( script_content[: finding.edit.start_pos] diff --git a/awsclilinter/uv.lock b/awsclilinter/uv.lock index c7607cdb0a1a..6f2e5a971f66 100644 --- a/awsclilinter/uv.lock +++ b/awsclilinter/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.12" +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] [[package]] name = "ast-grep-py" @@ -8,6 +12,16 @@ version = "0.39.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/b2/dbd082539aaf58a95ff3f97783dc3df51bc1ac54ed4ae195b00054ee23c3/ast_grep_py-0.39.9.tar.gz", hash = "sha256:1260d16e5bb25e486b9e9263d1c9d8ed819fa2940527169718e35e91d9c7c717", size = 135854, upload-time = "2025-11-07T02:22:23.416Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/13/c06895922aebda036bd1f3e336e184acbb5af620cc6eeb73fb4b7a484646/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a5d3ce635ebc85db35a20f89b69afbeabfc19cc47e3f2c95ee49e85231851fa5", size = 4807686, upload-time = "2025-11-07T02:21:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/44/43/884caa27108b371609f3bde11b040b88fd24af41dfc398bf31250da8d9a5/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:757660bc42b487b70398e6f0f0ea5dd28e3394e32ac996ca92b3dea7733b89af", size = 4911988, upload-time = "2025-11-07T02:21:41.998Z" }, + { url = "https://files.pythonhosted.org/packages/8e/da/4168d4ab5b7b3905381fb2ff0e29045f45091adef2e731e5841e4b2f42e5/ast_grep_py-0.39.9-cp310-cp310-win32.whl", hash = "sha256:9e6abe38e12ac95111acef1057cab99f21e90211c40ac41974f8d04eb028ce17", size = 4531742, upload-time = "2025-11-07T02:21:43.706Z" }, + { url = "https://files.pythonhosted.org/packages/66/54/7476056e2348b35bcaac412f5662914963a94b5e78abc1e7d69012aae1e0/ast_grep_py-0.39.9-cp310-cp310-win_amd64.whl", hash = "sha256:d5e942f27750084697afc464be93e808b847f62cfde9b4950cefe8b30fbead33", size = 4658311, upload-time = "2025-11-07T02:21:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ea/b62cf84eb71eda7841c96eea1c360d1528cbaf0505ec7e1810710d60f5a6/ast_grep_py-0.39.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:340acb957af5e732744b549bbee44eaf3d2573313e0c4e831bc7aa7cf0ade0c7", size = 4839283, upload-time = "2025-11-07T02:21:47.123Z" }, + { url = "https://files.pythonhosted.org/packages/89/03/1838657ce742e031cae16b17a4c7a844610510abb6726b468e92226f71da/ast_grep_py-0.39.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:caf056615b633ceb8666a63e5244404887c48ffd51567ca8e3fe1ead65fb7fd3", size = 4993450, upload-time = "2025-11-07T02:21:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/823984158b9d4ea41a30f49e4c47fd241379e9168d1de3e890cd3f40ec17/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5cd5f89c86974b6a082ff171c87c363ea2fd6903572930f64694248e96cb249b", size = 4807926, upload-time = "2025-11-07T02:21:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/fe/93/6e9c24864f7df05cdba4eafb6dd6037899744b6465b64a744a2700952820/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de73719089e2277188873c544a29bf248904d2c6e33112827342847c96cabea5", size = 4912214, upload-time = "2025-11-07T02:21:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/f4baa0adc1d26bdd6a8db1d34626166aa033aa753b9c06afd2361f3b4c35/ast_grep_py-0.39.9-cp311-cp311-win32.whl", hash = "sha256:83b3aae87ad75259fcafbac45f428414887cf455bbdb73426db056a456f7f5f4", size = 4532694, upload-time = "2025-11-07T02:21:53.233Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3e/7466a698b004d8349883b6528645f9539a4c35f06f59f9eeb94c105da229/ast_grep_py-0.39.9-cp311-cp311-win_amd64.whl", hash = "sha256:5cbb0d626a2cbb4221fb9813c2cd9826d48ae243079217b94b6177f6c5536f65", size = 4658015, upload-time = "2025-11-07T02:21:54.943Z" }, { url = "https://files.pythonhosted.org/packages/2d/ae/3ecf8cb071e56f22ed750d86431f992eca37f3948dee71a4660d3584c673/ast_grep_py-0.39.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b0993314fcadf0114fe635921edcc25754572cf889bf5c80ccaac7c2603b6ade", size = 4836643, upload-time = "2025-11-07T02:21:56.343Z" }, { url = "https://files.pythonhosted.org/packages/ee/e2/1f1491e21f8d1248ff970ff43e0221e3ac37acd8dbdb629b34829611edf6/ast_grep_py-0.39.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9633229808150ab34dea25203f209d3cfe317e8da06d897bac087587169ef2b8", size = 4985618, upload-time = "2025-11-07T02:21:57.819Z" }, { url = "https://files.pythonhosted.org/packages/f9/90/4c469d34d274c1e97a7870f92b2288fccaa14fa88d5edab760e5acbdfb50/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d485f1bbe5db8f800d8a3c60c85e24839f52cac33bc72874a060d17831774b97", size = 4809030, upload-time = "2025-11-07T02:21:59.898Z" }, @@ -38,7 +52,8 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "ruff" }, ] @@ -59,10 +74,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -95,16 +137,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + [[package]] name = "pytest" version = "9.0.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764, upload-time = "2025-11-08T17:25:33.34Z" } wheels = [ @@ -136,3 +204,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, ] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From 81bf9c0c0c666c5f4194920ed009dfca605bf85f Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 10:17:55 -0500 Subject: [PATCH 17/19] Rules refactoring. --- awsclilinter/awsclilinter/cli.py | 2 +- awsclilinter/awsclilinter/linter.py | 2 +- awsclilinter/awsclilinter/rules/__init__.py | 2 +- awsclilinter/awsclilinter/rules/base.py | 38 +++++++++++++++++++ .../awsclilinter/rules/base64_rule.py | 2 +- 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 awsclilinter/awsclilinter/rules/base.py diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 410f30b351e9..2463befe4180 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -5,8 +5,8 @@ from typing import List from awsclilinter.linter import ScriptLinter +from awsclilinter.rules import LintFinding from awsclilinter.rules.base64_rule import Base64BinaryFormatRule -from awsclilinter.rules_base import LintFinding # ANSI color codes RED = "\033[31m" diff --git a/awsclilinter/awsclilinter/linter.py b/awsclilinter/awsclilinter/linter.py index f9f5885d0b09..25b7c311e26b 100644 --- a/awsclilinter/awsclilinter/linter.py +++ b/awsclilinter/awsclilinter/linter.py @@ -2,7 +2,7 @@ from ast_grep_py import SgRoot -from awsclilinter.rules_base import LintFinding, LintRule +from awsclilinter.rules import LintFinding, LintRule class ScriptLinter: diff --git a/awsclilinter/awsclilinter/rules/__init__.py b/awsclilinter/awsclilinter/rules/__init__.py index 323cd7a7e44a..fc4132589762 100644 --- a/awsclilinter/awsclilinter/rules/__init__.py +++ b/awsclilinter/awsclilinter/rules/__init__.py @@ -1,3 +1,3 @@ -from awsclilinter.rules_base import LintFinding, LintRule +from awsclilinter.rules.base import LintFinding, LintRule __all__ = ["LintRule", "LintFinding"] diff --git a/awsclilinter/awsclilinter/rules/base.py b/awsclilinter/awsclilinter/rules/base.py new file mode 100644 index 000000000000..5900a2c09ff8 --- /dev/null +++ b/awsclilinter/awsclilinter/rules/base.py @@ -0,0 +1,38 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import List + +from ast_grep_py.ast_grep_py import Edit, SgRoot + + +@dataclass +class LintFinding: + """Represents a linting issue found in the script.""" + + line_start: int + line_end: int + edit: Edit + original_text: str + rule_name: str + description: str + + +class LintRule(ABC): + """Base class for all linting rules.""" + + @property + @abstractmethod + def name(self) -> str: + """Return the name of the rule.""" + pass + + @property + @abstractmethod + def description(self) -> str: + """Return a description of what the rule checks.""" + pass + + @abstractmethod + def check(self, root: SgRoot) -> List[LintFinding]: + """Check the AST root for violations and return findings.""" + pass diff --git a/awsclilinter/awsclilinter/rules/base64_rule.py b/awsclilinter/awsclilinter/rules/base64_rule.py index 8672c5e9b2f5..6da9ad61fcf8 100644 --- a/awsclilinter/awsclilinter/rules/base64_rule.py +++ b/awsclilinter/awsclilinter/rules/base64_rule.py @@ -2,7 +2,7 @@ from ast_grep_py.ast_grep_py import SgRoot -from awsclilinter.rules_base import LintFinding, LintRule +from awsclilinter.rules import LintFinding, LintRule class Base64BinaryFormatRule(LintRule): From e632312640d620e80dbcd25b5b5369a4993a5072 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 10:18:32 -0500 Subject: [PATCH 18/19] Rules refactor complete. --- awsclilinter/awsclilinter/rules_base.py | 38 ------------------------- 1 file changed, 38 deletions(-) delete mode 100644 awsclilinter/awsclilinter/rules_base.py diff --git a/awsclilinter/awsclilinter/rules_base.py b/awsclilinter/awsclilinter/rules_base.py deleted file mode 100644 index 5900a2c09ff8..000000000000 --- a/awsclilinter/awsclilinter/rules_base.py +++ /dev/null @@ -1,38 +0,0 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import List - -from ast_grep_py.ast_grep_py import Edit, SgRoot - - -@dataclass -class LintFinding: - """Represents a linting issue found in the script.""" - - line_start: int - line_end: int - edit: Edit - original_text: str - rule_name: str - description: str - - -class LintRule(ABC): - """Base class for all linting rules.""" - - @property - @abstractmethod - def name(self) -> str: - """Return the name of the rule.""" - pass - - @property - @abstractmethod - def description(self) -> str: - """Return a description of what the rule checks.""" - pass - - @abstractmethod - def check(self, root: SgRoot) -> List[LintFinding]: - """Check the AST root for violations and return findings.""" - pass From 7854fa9aa1042b650c3bc92852562c3cf3e2995a Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 11 Nov 2025 11:27:37 -0500 Subject: [PATCH 19/19] Iterate on PR feedback. --- awsclilinter/README.md | 2 +- awsclilinter/awsclilinter/cli.py | 6 ++ awsclilinter/pyproject.toml | 6 +- awsclilinter/uv.lock | 156 ++++++++++++------------------- 4 files changed, 70 insertions(+), 100 deletions(-) diff --git a/awsclilinter/README.md b/awsclilinter/README.md index 85680930aa0e..a9636356d408 100644 --- a/awsclilinter/README.md +++ b/awsclilinter/README.md @@ -30,7 +30,7 @@ $ python3 -m pip install --user awsclilinter If you have the `awsclilinter` package installed and want to upgrade to the latest version, you can run: ```shell -$ python3 -m pip install --upgrade awscli +$ python3 -m pip install --upgrade awsclilinter ``` This will install the `awsclilinter` package as well as all dependencies. diff --git a/awsclilinter/awsclilinter/cli.py b/awsclilinter/awsclilinter/cli.py index 2463befe4180..5bc7d26ac981 100644 --- a/awsclilinter/awsclilinter/cli.py +++ b/awsclilinter/awsclilinter/cli.py @@ -47,6 +47,12 @@ def display_finding(finding: LintFinding, index: int, total: int, script_content src_context = src_lines[context_start:context_end] dest_context = dest_lines[context_start:context_end] + if len(src_context) != len(dest_context): + raise RuntimeError( + f"Original and new context lengths must be equal. " + f"{len(src_context)} != {len(dest_context)}." + ) + print(f"\n[{index}/{total}] {finding.rule_name}") print(f"{finding.description}") diff --git a/awsclilinter/pyproject.toml b/awsclilinter/pyproject.toml index 425b9974e9fe..3b070b8cfe53 100644 --- a/awsclilinter/pyproject.toml +++ b/awsclilinter/pyproject.toml @@ -25,13 +25,13 @@ classifiers = [ "Topic :: Utilities", ] dependencies = [ - "ast-grep-py>=0.39.6", + "ast-grep-py==0.39.6", ] [project.optional-dependencies] dev = [ - "pytest>=8.0.0", - "ruff>=0.8.0", + "pytest>=8.0.0,<8.2.0", + "ruff>=0.8.0,<0.14.0", ] [project.urls] diff --git a/awsclilinter/uv.lock b/awsclilinter/uv.lock index 6f2e5a971f66..a87bc73a6c88 100644 --- a/awsclilinter/uv.lock +++ b/awsclilinter/uv.lock @@ -8,38 +8,36 @@ resolution-markers = [ [[package]] name = "ast-grep-py" -version = "0.39.9" +version = "0.39.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/b2/dbd082539aaf58a95ff3f97783dc3df51bc1ac54ed4ae195b00054ee23c3/ast_grep_py-0.39.9.tar.gz", hash = "sha256:1260d16e5bb25e486b9e9263d1c9d8ed819fa2940527169718e35e91d9c7c717", size = 135854, upload-time = "2025-11-07T02:22:23.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/d1/542b01bd11990e136ce1a67f4fd7e0e201e792754771594e481d08843307/ast_grep_py-0.39.6.tar.gz", hash = "sha256:931229ec21c3b116f1bffb65fca8f67bddf7b31f3b89d02230ae331e30997768", size = 136502, upload-time = "2025-10-05T04:15:48.777Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/13/c06895922aebda036bd1f3e336e184acbb5af620cc6eeb73fb4b7a484646/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a5d3ce635ebc85db35a20f89b69afbeabfc19cc47e3f2c95ee49e85231851fa5", size = 4807686, upload-time = "2025-11-07T02:21:39.558Z" }, - { url = "https://files.pythonhosted.org/packages/44/43/884caa27108b371609f3bde11b040b88fd24af41dfc398bf31250da8d9a5/ast_grep_py-0.39.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:757660bc42b487b70398e6f0f0ea5dd28e3394e32ac996ca92b3dea7733b89af", size = 4911988, upload-time = "2025-11-07T02:21:41.998Z" }, - { url = "https://files.pythonhosted.org/packages/8e/da/4168d4ab5b7b3905381fb2ff0e29045f45091adef2e731e5841e4b2f42e5/ast_grep_py-0.39.9-cp310-cp310-win32.whl", hash = "sha256:9e6abe38e12ac95111acef1057cab99f21e90211c40ac41974f8d04eb028ce17", size = 4531742, upload-time = "2025-11-07T02:21:43.706Z" }, - { url = "https://files.pythonhosted.org/packages/66/54/7476056e2348b35bcaac412f5662914963a94b5e78abc1e7d69012aae1e0/ast_grep_py-0.39.9-cp310-cp310-win_amd64.whl", hash = "sha256:d5e942f27750084697afc464be93e808b847f62cfde9b4950cefe8b30fbead33", size = 4658311, upload-time = "2025-11-07T02:21:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ea/b62cf84eb71eda7841c96eea1c360d1528cbaf0505ec7e1810710d60f5a6/ast_grep_py-0.39.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:340acb957af5e732744b549bbee44eaf3d2573313e0c4e831bc7aa7cf0ade0c7", size = 4839283, upload-time = "2025-11-07T02:21:47.123Z" }, - { url = "https://files.pythonhosted.org/packages/89/03/1838657ce742e031cae16b17a4c7a844610510abb6726b468e92226f71da/ast_grep_py-0.39.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:caf056615b633ceb8666a63e5244404887c48ffd51567ca8e3fe1ead65fb7fd3", size = 4993450, upload-time = "2025-11-07T02:21:48.775Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/823984158b9d4ea41a30f49e4c47fd241379e9168d1de3e890cd3f40ec17/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5cd5f89c86974b6a082ff171c87c363ea2fd6903572930f64694248e96cb249b", size = 4807926, upload-time = "2025-11-07T02:21:50.367Z" }, - { url = "https://files.pythonhosted.org/packages/fe/93/6e9c24864f7df05cdba4eafb6dd6037899744b6465b64a744a2700952820/ast_grep_py-0.39.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de73719089e2277188873c544a29bf248904d2c6e33112827342847c96cabea5", size = 4912214, upload-time = "2025-11-07T02:21:51.923Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/f4baa0adc1d26bdd6a8db1d34626166aa033aa753b9c06afd2361f3b4c35/ast_grep_py-0.39.9-cp311-cp311-win32.whl", hash = "sha256:83b3aae87ad75259fcafbac45f428414887cf455bbdb73426db056a456f7f5f4", size = 4532694, upload-time = "2025-11-07T02:21:53.233Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3e/7466a698b004d8349883b6528645f9539a4c35f06f59f9eeb94c105da229/ast_grep_py-0.39.9-cp311-cp311-win_amd64.whl", hash = "sha256:5cbb0d626a2cbb4221fb9813c2cd9826d48ae243079217b94b6177f6c5536f65", size = 4658015, upload-time = "2025-11-07T02:21:54.943Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ae/3ecf8cb071e56f22ed750d86431f992eca37f3948dee71a4660d3584c673/ast_grep_py-0.39.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b0993314fcadf0114fe635921edcc25754572cf889bf5c80ccaac7c2603b6ade", size = 4836643, upload-time = "2025-11-07T02:21:56.343Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e2/1f1491e21f8d1248ff970ff43e0221e3ac37acd8dbdb629b34829611edf6/ast_grep_py-0.39.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9633229808150ab34dea25203f209d3cfe317e8da06d897bac087587169ef2b8", size = 4985618, upload-time = "2025-11-07T02:21:57.819Z" }, - { url = "https://files.pythonhosted.org/packages/f9/90/4c469d34d274c1e97a7870f92b2288fccaa14fa88d5edab760e5acbdfb50/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d485f1bbe5db8f800d8a3c60c85e24839f52cac33bc72874a060d17831774b97", size = 4809030, upload-time = "2025-11-07T02:21:59.898Z" }, - { url = "https://files.pythonhosted.org/packages/76/ce/74fb305ba75b1750e79c20e58f81403264da9dc86034240ee3943b4c4a18/ast_grep_py-0.39.9-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b2a214ba114ea39325752f83e43968f8177538c91cebe37b7a5a399fda29acb", size = 4914929, upload-time = "2025-11-07T02:22:01.46Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e8/07b1f56e0de405f8869976a46d0c6918d3d69fc252ef3146eee1c97920af/ast_grep_py-0.39.9-cp312-cp312-win32.whl", hash = "sha256:43e87cc3315c77571c0fc6c02b5e2fab0554fc4492a35e69240183879e246538", size = 4532460, upload-time = "2025-11-07T02:22:02.689Z" }, - { url = "https://files.pythonhosted.org/packages/a6/bf/88e7dafb65d0f8a613d6558751ea072aa306c30f67224318be68bc0e47cf/ast_grep_py-0.39.9-cp312-cp312-win_amd64.whl", hash = "sha256:20cff26eee0e79b6679a5a7d5a875753b604eabadb85d5ed0e6f48fbc8d870e5", size = 4659784, upload-time = "2025-11-07T02:22:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/d7/3f/1f325a0b3870888e09304baff29496d26347aaccdc2cfc7bd9942e6607d0/ast_grep_py-0.39.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:27f7d040e0f2520be0d3a840b9d7b2bb213eb2fdbff57538774504ac9ed2bda7", size = 4838805, upload-time = "2025-11-07T02:22:05.924Z" }, - { url = "https://files.pythonhosted.org/packages/09/eb/7f26dde476dca33d19897ad2c2627a568ae0b47ac005c126b25fb658f41a/ast_grep_py-0.39.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10fde4d654be5f5d9e91220877d4af4d801a17a37bc812d442e0af04cc5d69a1", size = 4989079, upload-time = "2025-11-07T02:22:07.482Z" }, - { url = "https://files.pythonhosted.org/packages/6e/12/4830e54fbd6f9ffe8943de96a64f39a58539b43314a0a46ce3f76cd52577/ast_grep_py-0.39.9-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fee0b8480f02658903d7c42ff57c9ee9e09946a027e0b165324f3cb0ee4814e0", size = 4808951, upload-time = "2025-11-07T02:22:08.85Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e6/c17bcfd8e99c08ab55c9f723b7668d2939269d4515d77fec88dae4547ac1/ast_grep_py-0.39.9-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4fed1f6b5242525151babd1e982bb21306500834eb48f32d104905c3980ae20b", size = 4914653, upload-time = "2025-11-07T02:22:10.738Z" }, - { url = "https://files.pythonhosted.org/packages/59/2b/987e3e85368ad1381fedeef0664814ab909564c2ef5c713c24f245290f9f/ast_grep_py-0.39.9-cp313-cp313-win32.whl", hash = "sha256:f582a896f1e23051a91e64e17de6d90c7252b25070bdb47a397e30679ba25b63", size = 4535662, upload-time = "2025-11-07T02:22:12.119Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ae/3277b26a4b718b52bf29b85aaee3da92b7fc14fdd8e278cd9f71a5c67e24/ast_grep_py-0.39.9-cp313-cp313-win_amd64.whl", hash = "sha256:940040754520112d52d9051c4c3be7c47a56d693ae19d0bda3f267bcdff38561", size = 4659232, upload-time = "2025-11-07T02:22:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7b/7aac11346a663cccc625afde110d0489dc260b79167523276ad571562a94/ast_grep_py-0.39.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3dcca009886492009dad65e5de5a67627bc288ae7ac6ee74648fc29faac4cd18", size = 4838784, upload-time = "2025-11-07T02:22:14.707Z" }, - { url = "https://files.pythonhosted.org/packages/4f/50/9cf49354cd41f458a629744b0ed34f244b05306dbbd847b5b6731fada61d/ast_grep_py-0.39.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77f5f9c9d91969e2eb0d164135006639f07ad956cf334e20aa3b7c2c6b44d0f2", size = 4989891, upload-time = "2025-11-07T02:22:16.251Z" }, - { url = "https://files.pythonhosted.org/packages/8e/27/22bb880a59c9908489bd3edfe6b169b3db202209cb3b167404f38d27a02b/ast_grep_py-0.39.9-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0aece3d37fab65ff76ce86f1e8c43d5481ab02faa10d5dc8248c5723f68fa211", size = 4808826, upload-time = "2025-11-07T02:22:17.602Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b5/1e13e149ee3ed2d20e597c3389b4fa489f7e79cb507ee8e089479a250f51/ast_grep_py-0.39.9-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:633b1aef5cadf7cfd5a521297264de1977832a4ba0350c3d7383e851a514f171", size = 4913960, upload-time = "2025-11-07T02:22:18.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/49/bf120661c474967f4cdb4934da19793d12ee179d3549a34364567caf409f/ast_grep_py-0.39.9-cp314-cp314-win32.whl", hash = "sha256:0d0b80fba4531d025effe5a60c64525b797c4a3f76c5661cfe814576e8d96c99", size = 4536334, upload-time = "2025-11-07T02:22:20.794Z" }, - { url = "https://files.pythonhosted.org/packages/90/b4/833fbc038eddad03ca0ff5de1c4a959da2390c94258f6dae541c38007370/ast_grep_py-0.39.9-cp314-cp314-win_amd64.whl", hash = "sha256:248c1825b211dce645a36f3f0c8d33bddc3ed3703f21c003cb2c12baf1955540", size = 4660331, upload-time = "2025-11-07T02:22:22.105Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/6e410f976f9731b25d85dbeb68833d0f31a4ddba847305c371692e26afc5/ast_grep_py-0.39.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:828dd474c2504fc7544733b8f200245be0d2ae67060f6e3d0fe7c5852d0bf9cf", size = 4814178, upload-time = "2025-10-05T04:14:56.043Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7f/39e577bf63f026320634289baa70607c2ad6021891a6936ec41edbea0b86/ast_grep_py-0.39.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:63cce05fa68dd9d626711ed46a775f1f091fc6e075e685471c02d492b2d77a8a", size = 4916531, upload-time = "2025-10-05T04:14:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/b2c7278499f81e33fd0b37eb864dea661d69b8e435da5e33e8bd4d776cd8/ast_grep_py-0.39.6-cp310-cp310-win32.whl", hash = "sha256:3358b5d26d90cb928951923b4a7ac3973f7d7303d1e372321279023447b1dd33", size = 4532245, upload-time = "2025-10-05T04:15:01.368Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d3/dbe75875f0b2946dc4bf84062128ef1ec602112284bce0414c661322d1ec/ast_grep_py-0.39.6-cp310-cp310-win_amd64.whl", hash = "sha256:c5194c8ec3bf05fc25dda5a1f9d77a47c7ad08f997c1d579bdfbb0bc12afa683", size = 4655916, upload-time = "2025-10-05T04:15:03.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/36/f615e3fc9dd97ee375ed583934570e0c1b5aff151fd0952b18c935e6c275/ast_grep_py-0.39.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:292a2eb0cd76b0ed39ef512f4489bb9936978ef54b5d601918bc697f263d1357", size = 4848770, upload-time = "2025-10-05T04:15:05.621Z" }, + { url = "https://files.pythonhosted.org/packages/7e/73/a650fab3a19b9aae99a6f60a74a5841ab32a4163c9ff2c66fcf4ec435d1d/ast_grep_py-0.39.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01d3e4a7dfea92ee43ff712843f795a9f9a821c4bd0139fd2ce773851dee263d", size = 4999485, upload-time = "2025-10-05T04:15:07.618Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b0/e605ddd32e7750280caa8529cd1ceb1f38ee2d6e553d836acc94f554aba2/ast_grep_py-0.39.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fbe02b082474831fc2716cf8e8b312c5da97a992967e50ac5e37f83de385fe18", size = 4814292, upload-time = "2025-10-05T04:15:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/79/9e/54a69b08555e48f7cab13596a3c5ebbb08ce009f3559965421579b6825f3/ast_grep_py-0.39.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4fd970464f63af98e66cceabee836ac0304a612ff060e9461cd026c2193c809f", size = 4917639, upload-time = "2025-10-05T04:15:11.954Z" }, + { url = "https://files.pythonhosted.org/packages/86/cb/d13ba0e86d2fa58ef86ebe542f6f77c3b541decf8fde43a1d4281439059a/ast_grep_py-0.39.6-cp311-cp311-win32.whl", hash = "sha256:4380389a83fd7f06fe4c30e6c68ac786b99a8ce513ac0742148d27a1c987c0c0", size = 4532060, upload-time = "2025-10-05T04:15:14.52Z" }, + { url = "https://files.pythonhosted.org/packages/b1/26/ec04132de1d2dc2c3224d57f3d48ed9aec961fd5be284bf3b032ffc1a83b/ast_grep_py-0.39.6-cp311-cp311-win_amd64.whl", hash = "sha256:b70440bfdbc1ed71b26430dd05ee4fc19bb97b43b1d1f0fea8ee073f5a4e1bec", size = 4656022, upload-time = "2025-10-05T04:15:16.484Z" }, + { url = "https://files.pythonhosted.org/packages/9f/fe/09127b2bc6c1981ab346e736f2dd5df17e81d6fed09e43b777e862ea84dc/ast_grep_py-0.39.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3ef4cc66c228654b2fb41e72d90bd5276e1cf947f8300ffc78abd67274136b1b", size = 4848540, upload-time = "2025-10-05T04:15:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/96/e6/d43412548357f6119877690b923ddcf29c836d4d9eb8bca00c87a26b701e/ast_grep_py-0.39.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:65172cf9514f633d5922ba4074cd2e34470ee08abb29e6d8eb4059ac370ec45f", size = 4994646, upload-time = "2025-10-05T04:15:20.327Z" }, + { url = "https://files.pythonhosted.org/packages/b1/37/b9d9e1cba73ec2b512be6414465a29a1751f754e004b78ed3a23d8c8b275/ast_grep_py-0.39.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:678a68502ea4887e3724842429b5adc0da8a719cb4720e2bdd95a1d4f9f208ed", size = 4814179, upload-time = "2025-10-05T04:15:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fe/bd0535b659a1c3328c51e8b5300390a03238248162d75b2d1b49e40c4ca3/ast_grep_py-0.39.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4fc47c76d12f03407616ae60a4e210e4e337fcfd278ad24d6cf17f81cdb2b338", size = 4917932, upload-time = "2025-10-05T04:15:24.51Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0b/af64a8bcd98ccf24aef052c0ec82831546550ea864c94cd43736acb7cde1/ast_grep_py-0.39.6-cp312-cp312-win32.whl", hash = "sha256:2a7fffe7dcc55ea7678628072b511ea0252433748f6f3b19b6f2483f11874e3c", size = 4536390, upload-time = "2025-10-05T04:15:26.782Z" }, + { url = "https://files.pythonhosted.org/packages/76/11/c73fe8f4dfe8701c53124ccf915253e695c22d7a78711bb0105f18a25381/ast_grep_py-0.39.6-cp312-cp312-win_amd64.whl", hash = "sha256:d540136365e95b767cbc2772fd08b8968bd151e1aaaafaa51d91303659367120", size = 4658301, upload-time = "2025-10-05T04:15:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/f0ec566f2bfa356bdca8e2e12c6ee21b86fb8cf340ff386317fc45c6a822/ast_grep_py-0.39.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:cb804e6e048c35c873a396737f616124fb54343d392e87390e3cd515d44d281c", size = 4849750, upload-time = "2025-10-05T04:15:30.488Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e6/f59d92e69569ff6d9fd299c814a58385f3c691178c6c9157ee5591029397/ast_grep_py-0.39.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:46de5659378e2c1f1915629eb4f7e9f2add7b1a69ad774835c263c6e4b61613c", size = 4996290, upload-time = "2025-10-05T04:15:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/0f/89/8f27e86d3e30dd01d349033d02184125e176b8549a30a87eecca219ba2d2/ast_grep_py-0.39.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3f61646794e1c74023e8881ad93fd8b13778bfe5e50b61a4e4f8d3e8802bc914", size = 4814171, upload-time = "2025-10-05T04:15:34.516Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/5ab318861edb3b6863c8b0d77a0274b26fa6da623f51642f7a074abda18a/ast_grep_py-0.39.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ca8e8cd36aa81f89448cdadf00875e5ac0e14cff0cc11526dd1a1821a35cdae9", size = 4917194, upload-time = "2025-10-05T04:15:36.513Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/62ef817a5536467732fc9f9f492741b3224c72994dff96deef4a9baaf68c/ast_grep_py-0.39.6-cp313-cp313-win32.whl", hash = "sha256:f4227d320719de840ed0bb32a281de3b9d2fa33897dcb3f93d2ae3391affc70e", size = 4539296, upload-time = "2025-10-05T04:15:38.551Z" }, + { url = "https://files.pythonhosted.org/packages/03/68/b02ca991581d831ec0a8cd7d944ed591d5f43dfd743d7d32effda00f6b3d/ast_grep_py-0.39.6-cp313-cp313-win_amd64.whl", hash = "sha256:74a2e7fab3da96e403c7c257652218cbe7e5fc15055842215a91f14d158c9589", size = 4658417, upload-time = "2025-10-05T04:15:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/89/83/56f6bb739e9fb37b34f677744c1ad34872ff6fbbf679715a11e3910c6d38/ast_grep_py-0.39.6-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:e473ef786fb3e12192ef53682f68af634b30716859dbc44764164981be777fcd", size = 4815592, upload-time = "2025-10-05T04:15:41.972Z" }, + { url = "https://files.pythonhosted.org/packages/c7/53/dbe3a1d51abf3fab4e1e56b9fcacf35afcd7cc8469a730a656aa5c7c924b/ast_grep_py-0.39.6-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dc18747c0f2614984c855636e2f7430998bd83c952cb964aa115eca096bfcb8b", size = 4919011, upload-time = "2025-10-05T04:15:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/ddb9928a8ae7c828cf22e7b7e645fbfd6fa0636270b1cc73bf0fa2e5a76b/ast_grep_py-0.39.6-cp39-cp39-win32.whl", hash = "sha256:a77156ea53c6e6efaf7cfb8cb45f8731b198dc0ef2ea1e5b31b1c92fe281c203", size = 4533582, upload-time = "2025-10-05T04:15:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/30/7d/89ae9941735d362fb0302d5ade69f26b401e476d1c365fb69fda9d6c6a57/ast_grep_py-0.39.6-cp39-cp39-win_amd64.whl", hash = "sha256:c11245346a78deedb6b7bc65cca6a6c22783f2a33e1e999f3ba7d7bf00d89db8", size = 4656717, upload-time = "2025-10-05T04:15:47.361Z" }, ] [[package]] @@ -52,16 +50,15 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ - { name = "ast-grep-py", specifier = ">=0.39.6" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, + { name = "ast-grep-py", specifier = "==0.39.6" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0,<8.2.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0,<0.14.0" }, ] provides-extras = ["dev"] @@ -128,81 +125,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - [[package]] name = "pytest" -version = "8.4.2" +version = "8.1.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764, upload-time = "2025-11-08T17:25:33.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/1f/dd3960a2369182720e8cbd580523a4f75292c0c75197dd0254c95f4a0add/pytest-8.1.2.tar.gz", hash = "sha256:f3c45d1d5eed96b01a2aea70dee6a4a366d51d38f9957768083e4fecfc77f3ef", size = 1410060, upload-time = "2024-04-26T18:05:17.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/99/cafef234114a3b6d9f3aaed0723b437c40c57bdb7b3e4c3a575bc4890052/pytest-9.0.0-py3-none-any.whl", hash = "sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96", size = 373364, upload-time = "2025-11-08T17:25:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d7/a812a0bd0f5160823e647524488cc9734f93782f2c273b8f77e8afc60a37/pytest-8.1.2-py3-none-any.whl", hash = "sha256:6c06dc309ff46a05721e6fd48e492a775ed8165d2ecdf57f156a80c7e95bb142", size = 337456, upload-time = "2024-04-26T18:05:15.452Z" }, ] [[package]] name = "ruff" -version = "0.14.4" +version = "0.13.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/55/cccfca45157a2031dcbb5a462a67f7cf27f8b37d4b3b1cd7438f0f5c1df6/ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3", size = 5587844, upload-time = "2025-11-06T22:07:45.033Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/8e/f9f9ca747fea8e3ac954e3690d4698c9737c23b51731d02df999c150b1c9/ruff-0.13.3.tar.gz", hash = "sha256:5b0ba0db740eefdfbcce4299f49e9eaefc643d4d007749d77d047c2bab19908e", size = 5438533, upload-time = "2025-10-02T19:29:31.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/b9/67240254166ae1eaa38dec32265e9153ac53645a6c6670ed36ad00722af8/ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518", size = 12606781, upload-time = "2025-11-06T22:07:01.841Z" }, - { url = "https://files.pythonhosted.org/packages/46/c8/09b3ab245d8652eafe5256ab59718641429f68681ee713ff06c5c549f156/ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4", size = 12946765, upload-time = "2025-11-06T22:07:05.858Z" }, - { url = "https://files.pythonhosted.org/packages/14/bb/1564b000219144bf5eed2359edc94c3590dd49d510751dad26202c18a17d/ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33", size = 11928120, upload-time = "2025-11-06T22:07:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/a3/92/d5f1770e9988cc0742fefaa351e840d9aef04ec24ae1be36f333f96d5704/ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2", size = 12370877, upload-time = "2025-11-06T22:07:10.015Z" }, - { url = "https://files.pythonhosted.org/packages/e2/29/e9282efa55f1973d109faf839a63235575519c8ad278cc87a182a366810e/ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5", size = 12408538, upload-time = "2025-11-06T22:07:13.085Z" }, - { url = "https://files.pythonhosted.org/packages/8e/01/930ed6ecfce130144b32d77d8d69f5c610e6d23e6857927150adf5d7379a/ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e", size = 13141942, upload-time = "2025-11-06T22:07:15.386Z" }, - { url = "https://files.pythonhosted.org/packages/6a/46/a9c89b42b231a9f487233f17a89cbef9d5acd538d9488687a02ad288fa6b/ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8", size = 14544306, upload-time = "2025-11-06T22:07:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/78/96/9c6cf86491f2a6d52758b830b89b78c2ae61e8ca66b86bf5a20af73d20e6/ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649", size = 14210427, upload-time = "2025-11-06T22:07:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/71/f4/0666fe7769a54f63e66404e8ff698de1dcde733e12e2fd1c9c6efb689cb5/ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850", size = 13658488, upload-time = "2025-11-06T22:07:22.32Z" }, - { url = "https://files.pythonhosted.org/packages/ee/79/6ad4dda2cfd55e41ac9ed6d73ef9ab9475b1eef69f3a85957210c74ba12c/ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5", size = 13354908, upload-time = "2025-11-06T22:07:24.347Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/f0b6990f740bb15c1588601d19d21bcc1bd5de4330a07222041678a8e04f/ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132", size = 13587803, upload-time = "2025-11-06T22:07:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/c9/da/eaaada586f80068728338e0ef7f29ab3e4a08a692f92eb901a4f06bbff24/ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67", size = 12279654, upload-time = "2025-11-06T22:07:28.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b1d0e82cf9bf8aed10a6d45be47b3f402730aa2c438164424783ac88c0ed/ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469", size = 12357520, upload-time = "2025-11-06T22:07:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/04/f4/53e2b42cc82804617e5c7950b7079d79996c27e99c4652131c6a1100657f/ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde", size = 12719431, upload-time = "2025-11-06T22:07:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/a2/94/80e3d74ed9a72d64e94a7b7706b1c1ebaa315ef2076fd33581f6a1cd2f95/ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349", size = 13464394, upload-time = "2025-11-06T22:07:35.905Z" }, - { url = "https://files.pythonhosted.org/packages/54/1a/a49f071f04c42345c793d22f6cf5e0920095e286119ee53a64a3a3004825/ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff", size = 12493429, upload-time = "2025-11-06T22:07:38.43Z" }, - { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, - { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, + { url = "https://files.pythonhosted.org/packages/d2/33/8f7163553481466a92656d35dea9331095122bb84cf98210bef597dd2ecd/ruff-0.13.3-py3-none-linux_armv6l.whl", hash = "sha256:311860a4c5e19189c89d035638f500c1e191d283d0cc2f1600c8c80d6dcd430c", size = 12484040, upload-time = "2025-10-02T19:28:49.199Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b5/4a21a4922e5dd6845e91896b0d9ef493574cbe061ef7d00a73c61db531af/ruff-0.13.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2bdad6512fb666b40fcadb65e33add2b040fc18a24997d2e47fee7d66f7fcae2", size = 13122975, upload-time = "2025-10-02T19:28:52.446Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/15649af836d88c9f154e5be87e64ae7d2b1baa5a3ef317cb0c8fafcd882d/ruff-0.13.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fc6fa4637284708d6ed4e5e970d52fc3b76a557d7b4e85a53013d9d201d93286", size = 12346621, upload-time = "2025-10-02T19:28:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/bcbccb8141305f9a6d3f72549dd82d1134299177cc7eaf832599700f95a7/ruff-0.13.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c9e6469864f94a98f412f20ea143d547e4c652f45e44f369d7b74ee78185838", size = 12574408, upload-time = "2025-10-02T19:28:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/ce/19/0f3681c941cdcfa2d110ce4515624c07a964dc315d3100d889fcad3bfc9e/ruff-0.13.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5bf62b705f319476c78891e0e97e965b21db468b3c999086de8ffb0d40fd2822", size = 12285330, upload-time = "2025-10-02T19:28:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/f8/387976bf00d126b907bbd7725219257feea58650e6b055b29b224d8cb731/ruff-0.13.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cc1abed87ce40cb07ee0667ce99dbc766c9f519eabfd948ed87295d8737c60", size = 13980815, upload-time = "2025-10-02T19:29:01.577Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a6/7c8ec09d62d5a406e2b17d159e4817b63c945a8b9188a771193b7e1cc0b5/ruff-0.13.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4fb75e7c402d504f7a9a259e0442b96403fa4a7310ffe3588d11d7e170d2b1e3", size = 14987733, upload-time = "2025-10-02T19:29:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/97/e5/f403a60a12258e0fd0c2195341cfa170726f254c788673495d86ab5a9a9d/ruff-0.13.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b951f9d9afb39330b2bdd2dd144ce1c1335881c277837ac1b50bfd99985ed3", size = 14439848, upload-time = "2025-10-02T19:29:06.684Z" }, + { url = "https://files.pythonhosted.org/packages/39/49/3de381343e89364c2334c9f3268b0349dc734fc18b2d99a302d0935c8345/ruff-0.13.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6052f8088728898e0a449f0dde8fafc7ed47e4d878168b211977e3e7e854f662", size = 13421890, upload-time = "2025-10-02T19:29:08.767Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b5/c0feca27d45ae74185a6bacc399f5d8920ab82df2d732a17213fb86a2c4c/ruff-0.13.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc742c50f4ba72ce2a3be362bd359aef7d0d302bf7637a6f942eaa763bd292af", size = 13444870, upload-time = "2025-10-02T19:29:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/50/a1/b655298a1f3fda4fdc7340c3f671a4b260b009068fbeb3e4e151e9e3e1bf/ruff-0.13.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8e5640349493b378431637019366bbd73c927e515c9c1babfea3e932f5e68e1d", size = 13691599, upload-time = "2025-10-02T19:29:13.353Z" }, + { url = "https://files.pythonhosted.org/packages/32/b0/a8705065b2dafae007bcae21354e6e2e832e03eb077bb6c8e523c2becb92/ruff-0.13.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b139f638a80eae7073c691a5dd8d581e0ba319540be97c343d60fb12949c8d0", size = 12421893, upload-time = "2025-10-02T19:29:15.668Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/cbe7082588d025cddbb2f23e6dfef08b1a2ef6d6f8328584ad3015b5cebd/ruff-0.13.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6b547def0a40054825de7cfa341039ebdfa51f3d4bfa6a0772940ed351d2746c", size = 12267220, upload-time = "2025-10-02T19:29:17.583Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/4086f9c43f85e0755996d09bdcb334b6fee9b1eabdf34e7d8b877fadf964/ruff-0.13.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9cc48a3564423915c93573f1981d57d101e617839bef38504f85f3677b3a0a3e", size = 13177818, upload-time = "2025-10-02T19:29:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/7b5db7e39947d9dc1c5f9f17b838ad6e680527d45288eeb568e860467010/ruff-0.13.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1a993b17ec03719c502881cb2d5f91771e8742f2ca6de740034433a97c561989", size = 13618715, upload-time = "2025-10-02T19:29:22.527Z" }, + { url = "https://files.pythonhosted.org/packages/28/d3/bb25ee567ce2f61ac52430cf99f446b0e6d49bdfa4188699ad005fdd16aa/ruff-0.13.3-py3-none-win32.whl", hash = "sha256:f14e0d1fe6460f07814d03c6e32e815bff411505178a1f539a38f6097d3e8ee3", size = 12334488, upload-time = "2025-10-02T19:29:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/cf/49/12f5955818a1139eed288753479ba9d996f6ea0b101784bb1fe6977ec128/ruff-0.13.3-py3-none-win_amd64.whl", hash = "sha256:621e2e5812b691d4f244638d693e640f188bacbb9bc793ddd46837cea0503dd2", size = 13455262, upload-time = "2025-10-02T19:29:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/fe/72/7b83242b26627a00e3af70d0394d68f8f02750d642567af12983031777fc/ruff-0.13.3-py3-none-win_arm64.whl", hash = "sha256:9e9e9d699841eaf4c2c798fa783df2fabc680b72059a02ca0ed81c460bc58330", size = 12538484, upload-time = "2025-10-02T19:29:28.951Z" }, ] [[package]]