East Asian な全角文字をキーに使った場合のアラインメントについて考える
掲題のとおりである。 ここでいうアラインメントはメモリの話じゃなくて、字面上の整列のことを指す。
自分の中でもどうあるべきかまだ結論が出せてないので、考えをまとめるために書く。
問題
いま、 PSScriptAnalyzer で全角文字をキーに持つ hashtable を整形したとき期待どおりにならなのだけど、どう解決するのがいいのだろう?と悩んでる。
一応弁解しておくと、わたしの気持ちとしては「そんなところに全角文字使うとか危なげなことやめようや」だ。
ところが、今やってる仕事でスプレッドシートのデータをシステムに取り込むにあたり、それらをいくつかの CSV に分解・再構築する必要があって、その中でこのテーマに直面した。 PSCustomObject を CSV に変換する形でスクリプトを作ってるので、全角文字が識別子になるのだ。
普通に自分が書く範囲だとこんなの書かないので、最近まで気づかなかった。
VS Code で PowerShell を書くと、自動整形には PSScriptAnalyzer の Invoke-Formatter
が使われる。この戻り値が整形後のコードになるのだけど、ここで今回のテーマに直面する。
問題は以下のコードで再現できる。
$settings = @{
IncludeRules = @(
'PSAlignAssignmentStatement'
)
Rules = @{
PSAlignAssignmentStatement = @{
Enable = $true
# PSAlignAssignmentStatement.CheckHashtable が真だと、
# hashtable の要素の並びを整形してくれる。
CheckHashtable = $true
}
}
}
$script = @'
$test = @{
A = 0
ABC = 0
ABCDE = 0
A = 0
ABC = 0
ABCDE = 0
}
'@
Invoke-Formatter -ScriptDefinition $script -Settings $settings
出力はこうなる。これは期待のとおりではない。
$test = @{
A = 0
ABC = 0
ABCDE = 0
A = 0
ABC = 0
ABCDE = 0
}
わたしはこうなってほしいと考える。
$test = @{
A = 0
ABC = 0
ABCDE = 0
A = 0
ABC = 0
ABCDE = 0
}
PSScriptAnalyzer のコードを調べて、 PSScriptAnalyzer 的には PowerShell のパーサが返した列番号(EndColumnNumber
)の通りに整形してるのがわかった。
PSScriptAnalyzer/AlignAssignmentStatement.cs GetHashtableCorrections の L194
次に PowerShell のパーサを直接調べる。
$script = @'
$test = @{
A = 0
ABC = 0
ABCDE = 0
A = 0
ABC = 0
ABCDE = 0
}
'@
function getColumnNumberString {
param (
$Extent
)
"start $($Extent.StartColumnNumber.ToString().PadLeft(2)) end $($Extent.EndColumnNumber.ToString().PadLeft(2))"
}
$ast = [System.Management.Automation.Language.Parser]::ParseInput($script, [ref]$null, [ref]$null)
$hashAst = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $true)
$hashAst.KeyValuePairs | ForEach-Object {
# Item1 がキーの情報、 Item2 は '=' の情報
$e1, $e2 = $_.Item1.Extent, $_.Item2.Extent
"key $(getColumnNumberString($e1)) | '=' $(getColumnNumberString($e2))"
}
出力はこうなって、全角文字の表示幅は考慮されてないのがわかる。
key start 5 end 6 | '=' start 9 end 10
key start 5 end 8 | '=' start 11 end 12
key start 5 end 10 | '=' start 13 end 14
key start 5 end 6 | '=' start 9 end 10
key start 5 end 8 | '=' start 11 end 12
key start 5 end 10 | '=' start 13 end 14
じゃあこれは PowerShell のバグなのか?とまで考えを至らせると、いやパーサは表示する文字の幅を意識する必要あるか?と思えるので、これは誰が解決する問題なんや...と思っているのが、今の状況。
他の言語を調べる
他の事例を調べて、全角文字を含むキーや識別子をアラインメントするとどうなるか比べてみる。
とはいえ、他の言語でアラインメントするようなフォーマットかける言語何があったっけ? 少なくとも Python は違ったし、思いついたのは Go だけ。 Go 以外にも思いついたら追加したい。
Go
こんな中身の test.go
があるとする。
package main
type person struct {
A int
ABC int
ABCDE int
A int
ABC int
ABCDE int
}
これに gofmt ./test.go
すると次の通り。
package main
type person struct {
A int
ABC int
ABCDE int
A int
ABC int
ABCDE int
}
やっぱり! PowerShell の結果と同じ。
現時点の理解
現実的には、パーサやフォーマッタがフォントの文字幅を考慮しないし、アラインメントしないのが妥当な落とし所なのかな。 気持ちとしては、全角文字を含んだとしても綺麗にアラインメントしてほしいが、それもエッジケースなのでそんなに困ることがない。
とはいえ Unicode 文字の演算子とかポツポツあるし、幅の規定が厳格であればフォーマッタあたりで解消したいテーマではある気がする。
なんか締まらない締めになった。
とりあえず自分用の覚書としては、 PSScriptAnalyzer は PSAlignAssignmentStatement.CheckHashtable=$false
で利用すればこの問題に悩まされることもないので、オススメする。