Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
424 changes: 391 additions & 33 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Bu klasörde projenin genel dokümanları ve üç ana bileşene ait dokümanlar

## Genel Proje Dokümanları

- [Kurulum ve Kullanım](../README.md#kurulum)
- [Proje Kapsamı](scope.md)
- [Beş Haftalık Proje Planı](project-plan.md)
- [GitHub Actions CI](ci.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ Doğrulanan mevcut sonuç:
CI workflow contract tests: 18 passed
Offline dependency CI tests: 21 passed
Python 3.11 compatibility tests: 1 passed
Complete test suite: 1044 passed
Complete test suite: 1065 passed
Combined statement/branch coverage: 98.6% (97.0% required)
Whole-project self-analysis check: passed (5 intentional findings)
Workflow YAML parse check: passed
Expand Down
65 changes: 60 additions & 5 deletions docs/components/static-analyzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ from static_analyzer.runner import main, run_cli
3. Hedef klasörü analiz eder.
4. Bulguları seçilen çıktı formatına dönüştürür.
5. Raporu standart çıktıya yazar.
6. Analiz sonucuna uygun exit code döndürür.
6. `--fail-on` severity eşiğine uygun exit code döndürür.

Temel kullanım:

Expand All @@ -808,6 +808,18 @@ exit_code = run_cli(
)
```

Severity eşiği:

```python
exit_code = run_cli(
[
"src",
"--fail-on",
"warning",
]
)
```

Runner mevcut bileşenleri kullanır:

```python
Expand Down Expand Up @@ -930,7 +942,7 @@ Bulgusuz analiz:
0
```

Bir veya daha fazla bulgu:
Seçilen `--fail-on` eşiğini karşılayan bir veya daha fazla bulgu:

```text
1
Expand All @@ -942,7 +954,8 @@ Beklenen operasyonel hata:
2
```

Bu sürümde bütün severity seviyeleri bulgu exit code değerini üretir:
Varsayılan `--fail-on any` bütün severity seviyelerinde bulgu exit code
değerini üretir:

```text
INFO
Expand All @@ -952,6 +965,19 @@ ERROR

Bir bulgu yalnızca `INFO` seviyesinde olsa bile exit code `1` olur.

Desteklenen eşikler:

```text
any
info
warning
error
```

`--fail-on warning`, `INFO` bulgularını raporda korur ancak yalnızca `WARNING`
ve `ERROR` bulgularında exit code `1` üretir. `--fail-on error` yalnızca
`ERROR` bulgularında başarısız olur.

#### Operasyonel Hatalar

`main()` aşağıdaki beklenen hataları yönetir:
Expand Down Expand Up @@ -1025,12 +1051,14 @@ bir veri sınıfıdır:
class CliArguments:
target: Path
output_format: str
fail_on: str = "any"
```

Alanlar:

- `target`: Analiz edilecek hedef klasörün `Path` karşılığı
- `output_format`: `text` veya `json`
- `fail_on`: `any`, `info`, `warning` veya `error`

#### Hedef yol

Expand Down Expand Up @@ -1072,6 +1100,26 @@ Geçersiz bir format standart `argparse` kullanım hatası üretir:
securecode-analyzer src --format xml
```

#### Fail-on eşiği

Varsayılan eşik:

```text
any
```

Severity gate örneği:

```powershell
securecode-analyzer src --fail-on warning
```

Eşik sırası:

```text
info < warning < error
```

#### Parser oluşturma

Her `build_parser()` çağrısı yeni bir `ArgumentParser` nesnesi üretir:
Expand Down Expand Up @@ -1101,11 +1149,18 @@ Argümanlar doğrudan bir liste üzerinden ayrıştırılabilir:

```python
arguments = parse_arguments(
["src", "--format", "json"]
[
"src",
"--format",
"json",
"--fail-on",
"warning",
]
)

assert arguments.target == Path("src")
assert arguments.output_format == "json"
assert arguments.fail_on == "warning"
```

`argv=None` kullanıldığında mevcut process argümanları ayrıştırılır.
Expand Down Expand Up @@ -1544,4 +1599,4 @@ Henüz tamamlanmayan çalışmalar:
## 17. Navigation

- [Tüm bileşenlere dön](../README.md)
- [Projenin ana sayfasına dön](../../../README.md)
- [Projenin ana sayfasına dön](../../../README.md)
4 changes: 4 additions & 0 deletions docs/project-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,10 @@ Sub-task’ler:
* API bağımlılığını yaz.
* Güvenli sürüm bilgisinin her zaman bulunmayabileceğini yaz.

**Durum:** Tamamlandı. Cross-platform kurulum, iki CLI'ın text/JSON ve
severity-gate kullanımı, canlı/yerel OSV ayrımı, exit code sözleşmeleri ve
bilinen sınırlamalar root [`README.md`](../README.md) içinde belgelenmiştir.

## Backlog 5.7 — Son teknik dokümantasyon

**Öncelik:** P1
Expand Down
2 changes: 1 addition & 1 deletion docs/self-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Dosya static-analysis ve integration test kapsamından çıkarılmaz.
eklenen testlerden sonra doğrulanan sonuç:

```text
Tests: 1044 passed
Tests: 1065 passed
Combined statement/branch coverage: 98.6%
Required CI floor: 97.0%
```
Expand Down
13 changes: 12 additions & 1 deletion src/static_analyzer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class CliArguments:

target: Path
output_format: str
fail_on: str = "any"


def build_parser() -> argparse.ArgumentParser:
Expand All @@ -40,6 +41,15 @@ def build_parser() -> argparse.ArgumentParser:
help="Output format to use. Available formats: text, json.",
)

parser.add_argument(
"--fail-on",
choices=("any", "info", "warning", "error"),
default="any",
help=(
"Return exit code 1 for findings at or above this severity."
),
)

return parser


Expand All @@ -54,4 +64,5 @@ def parse_arguments(
return CliArguments(
target=namespace.target,
output_format=namespace.output_format,
)
fail_on=namespace.fail_on,
)
43 changes: 41 additions & 2 deletions src/static_analyzer/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
format_findings_json,
format_findings_text,
)
from static_analyzer.models import Finding, Severity
from static_analyzer.project_analyzer import ProjectAnalyzer


Expand All @@ -24,6 +25,12 @@
UnicodeDecodeError,
)

_SEVERITY_RANK = {
Severity.INFO: 1,
Severity.WARNING: 2,
Severity.ERROR: 3,
}


def run_cli(
argv: Sequence[str] | None = None,
Expand Down Expand Up @@ -62,7 +69,10 @@ def run_cli(
output_stream.write(output)
output_stream.write("\n")

return 1 if findings else 0
return _calculate_exit_code(
findings,
arguments.fail_on,
)


def main(
Expand All @@ -88,4 +98,33 @@ def main(
)
except _OPERATIONAL_ERRORS as error:
error_stream.write(f"Error: {error}\n")
return 2
return 2


def _calculate_exit_code(
findings: list[Finding],
fail_on: str,
) -> int:
"""Return whether findings meet the configured severity threshold."""

if not findings:
return 0

if fail_on == "any":
return 1

try:
minimum_severity = Severity(fail_on)
minimum_rank = _SEVERITY_RANK[minimum_severity]
except (ValueError, KeyError) as error:
raise ValueError(
f"Unsupported fail-on severity: {fail_on}"
) from error

return int(
any(
_SEVERITY_RANK[finding.severity]
>= minimum_rank
for finding in findings
)
)
37 changes: 36 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ def test_cli_arguments_store_expected_fields() -> None:
arguments = CliArguments(
target=Path("src"),
output_format="json",
fail_on="warning",
)

assert arguments.target == Path("src")
assert arguments.output_format == "json"
assert arguments.fail_on == "warning"


def test_cli_arguments_are_immutable() -> None:
Expand All @@ -43,6 +45,7 @@ def test_cli_arguments_use_slots() -> None:
assert CliArguments.__slots__ == (
"target",
"output_format",
"fail_on",
)


Expand Down Expand Up @@ -154,6 +157,26 @@ def test_json_format_is_accepted() -> None:
assert arguments.output_format == "json"


def test_default_fail_on_level_is_any() -> None:
"""Every finding should fail unless a severity floor is selected."""

assert parse_arguments(["src"]).fail_on == "any"


@pytest.mark.parametrize(
"fail_on",
["any", "info", "warning", "error"],
)
def test_supported_fail_on_level_is_accepted(fail_on: str) -> None:
"""All documented static severity thresholds should parse."""

arguments = parse_arguments(
["src", "--fail-on", fail_on]
)

assert arguments.fail_on == fail_on


def test_format_option_can_appear_before_target() -> None:
"""Optional arguments should work before the target."""

Expand Down Expand Up @@ -207,6 +230,15 @@ def test_invalid_output_format_exits_with_usage_error() -> None:
assert error.value.code == 2


def test_invalid_fail_on_level_exits_with_usage_error() -> None:
"""An unsupported static severity threshold should be rejected."""

with pytest.raises(SystemExit) as error:
parse_arguments(["src", "--fail-on", "critical"])

assert error.value.code == 2


def test_unknown_argument_exits_with_usage_error() -> None:
"""Unknown arguments should be rejected."""

Expand Down Expand Up @@ -247,9 +279,12 @@ def test_help_output_contains_expected_information(
assert "securecode-analyzer" in output
assert "target" in output
assert "--format" in output
assert "--fail-on" in output
assert "text" in output
assert "json" in output
assert "warning" in output
assert "error" in output
assert (
"Analyze Python source code for quality and security findings."
in output
)
)
Loading