【Psalm/phar】Notice: Uninitialized string offset: 0 in ... で Cannot locate エラー

# `Psalm` で `Cannot locate` エラー

> `composer require psalm/phar` でインストールした `phar` 版 `psalm` を実行すると、`Notice: Uninitialized string offset: 0 ... command_functions.php ...` と `Cannot locate` エラーが出るようになりました。

以前までは動いていたと思ったのですが、気付いたらエラーが出ていました。特に [Psalm の issues](https://github.com/vimeo/psalm/issues?q=is%3Aissue+Cannot+locate) にも上がっておらず、ググりまくっても解決策が見つからなかったので、自分のググラビリティ/備忘録として。

## TL; DR

> `psalm` のコマンド引数で、空のオプションを渡していませんか。

```shell-session:空の引数を渡している
$ use_alter=''
$ ./vendor/bin/psalm \
  --config="$(pwd)/tests/conf/psalm.xml" \
  --root="$(pwd)" \
  "$use_alter"   # <--- !! w/quote
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 178
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 184
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 202
Cannot locate 
```

**クォートで変数を囲うと、変数の中身が空の場合でもオプションとして渡してしまう**ので、クォートせずに指定するか `if` 文で分岐します。

```shell-session
$ use_alter=''
$ ./vendor/bin/psalm \
  --config="$(pwd)/tests/conf/psalm.xml" \
  --root="$(pwd)" \
  $use_alter   # <--- !! w/out quote
Scanning files...
Analyzing files...

░░░
------------------------------
No errors found!
------------------------------

Checks took 5.92 seconds and used 171.358MB of memory
Psalm was able to infer types for 100% of the codebase
```

## TS; DR

各種テストを実行するシェル・スクリプトで、`psalm` の自動補完/修正機能オプション `--alter` を条件によって付けたり付けなかったりしたかったので、変数にオプションを入れて指定していました。

```bash:run-tests.sh
#!/bin/bash
...
# psalm の追加オプション指定
use_alter=''
isFlagSet 'psalter' && {
  use_alter='--alter'
}

# psalm の実行
./vendor/bin/psalm \
  --config=$path_file_conf_psalm \
  --root=$path_dir_parent \
  $use_alter || {
    echo >&2 'Psalm check failed.'
  }
...
```

あるときから、シェル・スクリプトの静的解析ツールの「[ShellCheck](https://www.shellcheck.net/)」を使うようになりました。

この `shellcheck` の解析内容には「変数をダブル・クォーテーションで囲う」（[SC2086](https://github.com/koalaman/shellcheck/wiki/SC2086)）と言うルールがあります。これはシェルの場合、**変数内にスペースが含まれていた場合、クォーテーションで囲わないと複数の引数として展開してしまうため**です。

```shell-session
$ hoge='fuga piyo'
$ ./myCommand $hoge    #<- ./myCommand 'fuga' 'piyo' と同等
$ ./myCommand "$hoge"  #<- ./myCommand 'fuga piyo' と同等
```

このことは、「スペース入りディレクトリのあるパス」を扱う時など意図しない（見つけづらい）エラーを起こします。

「まぁ、そうだよな」と、盲目的に修正して（変数をクォートで囲って）いたのですが、逆の**「引数の値が空でも『空の値』として渡してしまう」と言うパターン**を失念しておりました。このことが原因でエラーが発生していたのですが、気づくのに時間がかなりかかってしまいました。

```shell-session:$user_alterは空
$ # NG
$ ./vendor/bin/psalm \
  --config="${path_file_conf_psalm}" \
  --root="${path_dir_app_root}" \
  "$use_alter"
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 178
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 184
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 202
Cannot locate

$ # OK
$ ./vendor/bin/psalm \
  --config="${path_file_conf_psalm}" \
  --root="${path_dir_app_root}" \
  $use_alter
Scanning files...
Analyzing files...

░░░
------------------------------
No errors found!
------------------------------

Checks took 5.92 seconds and used 171.358MB of memory
Psalm was able to infer types for 100% of the codebase
```

```shell-session:バージョン情報
$ ./vendor/bin/psalm --version
Psalm 4.3.1@2feba22a005a18bf31d4c7b9bdb9252c73897476

$ php -v
PHP 7.3.25 (cli) (built: Dec 11 2020 09:22:30) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.25, Copyright (c) 1998-2018 Zend Technologies
    with Xdebug v3.0.1, Copyright (c) 2002-2020, by Derick Rethans
```