Skip to content

Add Psalm plugin to detect runtime errors at static analysis time #60

Description

@koriym

背景

Be Framework では以下の2種類のエラーが実行時まで検出できない。いずれも Psalm プラグインで静的解析時に検出可能。

ルール1: Being コンストラクタのパラメータ属性チェック

問題

#[Be(...)] の変換先クラスのコンストラクタパラメータに #[Input] も #[Inject] もない場合、実行時に MissingParameterAttribute 例外が発生する。

// NG: $storage に属性がない → 実行時クラッシュ
final readonly class WeightLogged
{
    public function __construct(
        #[Input] string $recordedAt,
        WeightStorageInterface $storage, // ← 実行するまでわからない
    ) {}
}

期待する挙動

Psalm が静的解析時にエラーを報告する。

ERROR: MissingBeingParameterAttribute
Parameter $storage in WeightLogged::__construct() must have #[Input] or #[Inject]

検出ロジック案

  • コンストラクタに #[Input] を持つパラメータが1つでも存在する → Being クラスと判定
  • 同コンストラクタの全パラメータに #[Input] または #[Inject] がなければエラー

ルール2: #[Validate] メソッドの例外型チェック

問題

#[Validate] メソッドが DomainException 以外の例外を throw しても、SemanticValidator の catch 節で捕捉されず素通りする。

// NG: RuntimeException は catch (DomainException) で捕捉されない
final class WeightKg
{
    #[Validate]
    public function validate(float $weightKg): void
    {
        throw new InvalidWeightException($weightKg); // RuntimeException を継承 → 素通り
    }
}

期待する挙動

ERROR: InvalidValidateException
Exception InvalidWeightException thrown in #[Validate] method must extend DomainException

検出ロジック案

  • #[Validate] 属性を持つメソッドのボディを走査
  • throw 文の例外型が DomainException を継承していなければエラー

実装方針

  • Psalm Plugin として実装(vimeo/psalm ^6)
  • AfterClassLikeVisitInterface を使い AST から属性を検出
  • カスタム Issue 型: MissingBeingParameterAttribute, InvalidValidateException
  • composer require --dev be-framework/psalm-plugin で導入できる形が理想

参考

  • 発見の経緯: be-semantic スキル を使った実装中に両エラーに遭遇
  • SemanticValidator::validateWithAttributes が catch (DomainException $exception) のみ捕捉している箇所が起点

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions