# fp-checker

`fp-checker` is a Rust-powered checker for Python codebases that reports functions which violate functional-programming-oriented rules. It provides both a CLI and a Python API, and can output results in text, JSON, or SARIF. In addition to built-in rules, you can add custom rules through configuration only.

## English Summary

- What it does: checks Python functions for side effects, hidden mutation, non-determinism, deprecated APIs, and other patterns that make code less functional
- Interfaces: CLI and Python API
- Output formats: `text`, `json`, `sarif`
- Extensibility: built-in rules plus config-driven custom rules
- Package: `pip install fp-checker`

## Where To Start

- Feature overview: [docs/features.md](docs/features.md)
- Rule reference: [docs/rules.md](docs/rules.md)
- CLI / Python API usage: [docs/usage.md](docs/usage.md)
- Configuration: [docs/configuration.md](docs/configuration.md)
- Development setup: [docs/development.md](docs/development.md)
- CI / security: [docs/quality-and-security.md](docs/quality-and-security.md)

## 日本語

Python コードを解析し、関数型プログラミングのルールに反した関数を検査する Rust 製ツールです。CLI と Python API の両方を提供し、結果は text / JSON / SARIF の各形式で出力できます。組み込みルールに加えて、設定ファイルだけで custom rule を追加できます。

## どこを見れば何が分かるか

- 機能概要: [docs/features.md](docs/features.md)
- ルール仕様: [docs/rules.md](docs/rules.md)
- CLI / Python API の使い方: [docs/usage.md](docs/usage.md)
- 設定ファイル仕様: [docs/configuration.md](docs/configuration.md)
- 開発方法と Dev Container: [docs/development.md](docs/development.md)
- CI / セキュリティ方針: [docs/quality-and-security.md](docs/quality-and-security.md)

## 現在の実装範囲

- Rust workspace: `fp_checker_core`, `fp_checker_cli`, `fp_checker_py`
- Python 構文解析: `rustpython-parser` ベースの adapter
- ignore デコレータ: `@fp_ignore` を既定値としてサポート
- MVP ルール:
  - 可変グローバル状態
  - 代入の多用
  - 直接 I/O
  - 非決定的呼び出し
  - 外部状態への副作用
  - ミュータブルなデフォルト引数
  - 直接的な effectful call
  - `global` / `nonlocal`
- 追加ルール:
  - hidden mutation
  - loop 内副作用
  - 例外制御フロー
  - class method 内副作用
  - 参照透過性を壊す暗黙依存
  - 非推奨 API 使用
- 出力形式: text / JSON / SARIF
- AI 向けの structured `fix_hint` を JSON に含める
- CLI / Python API / `pyproject.toml` 設定対応
- 設定スキーマ: unknown key を reject する strict TOML
- config 駆動の custom rule: `call` / `name` / `attribute_write`
- サポート方針: Rust 1.94+, Python 3.12+
- CI 上の Python smoke test: 3.12 / 3.13 / 3.14
- JSON schema 検証: `tests/fixtures/report.schema.json`
- リリース自動化: tag push で GitHub Release と PyPI publish
- benchmark: `cargo bench -p fp_checker_core`
- pre-commit: `.pre-commit-config.yaml`

## クイックスタート

### PyPI からインストール

```bash
pip install fp-checker
```

```bash
fp-checker --help
```

```bash
fp-checker check path/to/project
```

```bash
python -m fp_checker --help
```

### CLI

```bash
cargo run -p fp_checker_cli -- check tests/fixtures/sample_project
```

```bash
cargo run -p fp_checker_cli -- check tests/fixtures/sample_project --format json
```

```bash
cargo run -p fp_checker_cli -- check tests/fixtures/sample_project --format sarif
```

### Python API

```bash
maturin develop --manifest-path crates/fp_checker_py/Cargo.toml
python3 -c "import fp_checker; print(fp_checker.available_rules())"
```

```python
import fp_checker

print(fp_checker.check_code("def f(items=[]):\n    return items", format="json"))
```

## 具体的な実装例と出力サンプル

このリポジトリには、すぐ試せるサンプルとして `tests/fixtures/sample_project` が含まれています。`origin/main` の現在内容もこの fixture と期待値 JSON を前提にしているため、README の例もこれに合わせています。

### 解析対象のサンプルコード

`tests/fixtures/sample_project/sample.py`

```python
import random
import time

STATE = []


def pure_add(x, y):
    return x + y


def uses_mutable_global():
    return STATE


def mutates_global():
    global STATE
    STATE.append(1)
    return STATE


def direct_io(path):
    print(path)
    return open(path)


def nondeterministic():
    return random.random() + time.time()


def external_state(obj, items=[]):
    obj.value = 10
    obj.save()
    return items
```

### CLI での実行例

```bash
fp-checker check tests/fixtures/sample_project
```

text 出力例（抜粋）:

```text
fp-checker report
files=2 checked_functions=7 ignored_functions=1 diagnostics=4

tests/fixtures/sample_project/broken.py (checked=0, ignored=0, diagnostics=1)
  [error] tests/fixtures/sample_project/broken.py:1:12 invalid syntax. Got unexpected token ':' at byte offset 11 (parse_error)
    hint: Fix the syntax error and run the checker again.

tests/fixtures/sample_project/sample.py (checked=7, ignored=1, diagnostics=3)
  function pure_add purity=pure[info] style=no_reassignment[info]
  function uses_mutable_global purity=impure_reads_external_state[warning] style=no_reassignment[info]
  function mutates_global purity=impure_writes_external_state[error] style=has_mutation[info]
  function direct_io purity=impure_direct_io[error] style=no_reassignment[info]
  function nondeterministic purity=impure_nondeterministic[error] style=no_reassignment[info]
  function external_state purity=impure_writes_external_state[error] style=has_mutation[info]
  [warning] tests/fixtures/sample_project/sample.py:18:12 References mutable global state `STATE`. (mutable_global_state)
    function: uses_mutable_global
    hint: Pass the value in as an argument and manage state outside the function.
```

JSON 出力例（抜粋）:

```bash
fp-checker check tests/fixtures/sample_project --format json
```

```json
{
  "summary": {
    "files": 2,
    "checked_functions": 7,
    "ignored_functions": 1,
    "diagnostics": 4
  },
  "files": [
    {
      "path": "tests/fixtures/sample_project/broken.py",
      "diagnostics": [
        {
          "rule_id": "parse_error",
          "severity": "error",
          "message": "invalid syntax. Got unexpected token ':' at byte offset 11"
        }
      ]
    },
    {
      "path": "tests/fixtures/sample_project/sample.py",
      "function_assessments": [
        {
          "qualified_name": "mutates_global",
          "external_purity": {
            "primary": "impure_writes_external_state",
            "severity": "error"
          },
          "implementation_style": {
            "primary": "has_mutation",
            "severity": "info"
          }
        }
      ],
      "diagnostics": [
        {
          "rule_id": "mutable_global_state",
          "severity": "warning",
          "function": "uses_mutable_global",
          "message": "References mutable global state `STATE`."
        },
        {
          "rule_id": "mutable_default_argument",
          "severity": "warning",
          "function": "external_state",
          "message": "Parameter `items` has a mutable default value."
        }
      ]
    }
  ]
}
```

### Python API での実装例

```python
import fp_checker

report = fp_checker.check_path("tests/fixtures/sample_project", format="json")
print(report)
```

出力例（抜粋）:

```json
{
  "summary": {
    "files": 2,
    "checked_functions": 7,
    "ignored_functions": 1,
    "diagnostics": 4
  }
}
```

## 開発コマンド

```bash
cargo check
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
```

## リポジトリ構成

```text
.
├── crates/
│   ├── fp_checker_cli/
│   ├── fp_checker_core/
│   └── fp_checker_py/
├── docs/
├── python/
│   └── examples/
├── tests/
│   └── fixtures/
├── .devcontainer/
├── .github/
│   ├── dependabot.yml
│   └── workflows/
├── Cargo.toml
├── pyproject.toml
├── CONTRIBUTING.md
├── SECURITY.md
└── CHANGELOG.md
```
