sosukesuzuki.dev

October, 10 2020

9 月の OSS 活動 (Prettier とか Babel とか)

以前投稿した7 月と 8 月と 9 月前半の OSS 活動の中で、9 月前半までの記録を含めてしまったが、それ以降もそれなりに活動をしていたので改めて 9 月の OSS 活動として紹介したいと思う。

9 月は Prettier のメンテナーとしての活動を続ける傍らで Babel のパーサーの改善も行っていた。Prettier は JavaScript をパースするときにデフォルトでは @babel/parser を使用するので、Babel のパーサーの改善は間接的に Prettier を改善することにもつながる。実際、自分が今月行った改善のおかげでいままで Prettier がフォーマットできなかったコードが一部フォーマットできるようになるので、Babel がリリースされたらテストケースを追加しようと思う。

prettier/prettier

2.1.2 をリリースした。これは 2.1 で発生したリグレッションの修正のみを含むパッチリリースである。

Prettier のようなコードフォーマッターでは、何が破壊的変更なのかという判断が非常に難しいので、厳密に semver を守れている自信はないのだけど、できるだけ semver に従ったバージョニングをしていきたいと思っている。そのため、2.1 で発生したリグレッションの修正以外のコミットはリリースに含めたくなかった。しかしすでにmasterブランチにそういったコミットが入っていたので、手動でpatch-releaseブランチにcherry-pickしてそこからリリースした。ローカルの Git で行う作業が多いリリースは非常に緊張する。

2.1.2 に含まれる以下の変更もほとんど自分が行った。

GraphQL: Fix formatting for directives in fields(9116)

GraphQL でフィールドに対するディレクティブのインデントが崩れるというバグの修正だが、正直自分は GraphQL 一回も書いたことないので直してみてから有識者に聞いたところ挙動に問題はなさそうとのことだったのでマージした。

Fix line breaks for CSS in JS

styled-components などの CSS in JS で改行が一部の改行が壊れるというバグの修正。

// Input
styled.div`
${(props) => getSize(props.$size.xs)}
${(props) => getSize(props.$size.sm, "sm")}
${(props) => getSize(props.$size.md, "md")}
`
;

// Output
styled.div`
${(props) => getSize(props.$size.xs)}
${(props) => getSize(props.$size.sm, "sm")}
${(props) => getSize(props.$size.md, "md")}
`
;

YAML: Fix printing doubles a blank line before a comment

これは YAML のコメントと空行を組み合わせると奇妙なフォーマットが起こるというバグで、今でもよくわかっていない。YAML のシンタックスは自分には難しく、そしてほとんど書いたことがないので実は未だによくわかっていない。なので他のメンテナーに一部引き継いでもらった。

未だに不安定な気はするものの、リグレッションは修正できたのでその状態でリリースを行った。


まだリリースされていはいないいくつかの変更もここで紹介する。

  • Use better fonts in PlaygroundPlaygroundを等幅フォントに変更した。これによってスペースの数などを目視で確認するのが圧倒的に楽になった。
  • Release script: Commit and push after updating dependents counthttps://prettier.io には Prettier に依存しているパッケージの数が掲載されており、リリーススクリプト内でリリースと同時に更新している。しかし、実際には変更だけしてコミット・プッシュがされていなかったのでするように修正した。
  • Refactor: Use set instead of array。これはRefactorとタイトルに書いてあるが、実際にはリファクタリングというよりパフォーマンス改善にあたる。Set のような使い方をされていた配列を、Set と WeakMap を使って書き換えた。

sosukesuzuki/prettier-regression-testing

https://zenn.dev/sosukesuzuki/articles/753d7ec2d65e154599c3 に書いたが、GitHub Actions を使って Prettier にバグがないかを確認するツールを開発した。個人で開発したものだが、OSS 活動のために作った OSS なのでここに書いておく。

babel/babel

先月はなぜか Babel パーサーへの意欲が高まってしまい、10 個ほど Pull Request を作成した。すべてシンタックスエラーの改善だが、一部は Prettier の挙動に影響を及ぼす。

Babel は最近になって(7.9 から) errorRecovery 機能が導入されたこともあり、シンタックスエラーがあまりヒューマンフレンドリーではない傾向にある。そういった挙動を修正するのは比較的簡単だがあまり手がつけられていないようなので、もし Babel へのコントリビューションに興味がある人がいたらそこから挑戦していくと良いかもしれない。

メンテナーとしての責任や義務感がないので、全体的に楽しくやることができた。そして JavaScript や TypeScript について多少詳しくなれたような気がする。

Throw a syntax error for a declare function with a body

現在の Babel は次のような変換を行う。

// Input
declare function foo() {
};

// Output
function foo() {}

この入力は TypeScript のインバリッドなコードだが、このような変換がされるのはバグである。Babel のパーサーは ambient context の関数だろうが、ボディがあれば FunctionDeclaration としてパースするためこのようなバグが起こる。本来であれば TSDeclareFunction としてパースするべきで、例えば typescript-eslint/typescript-estree はそのようにパースする。また、このコードはインバリッドであるため、errorRecovery可能なシンタックスエラーを吐いてあげると人間に親切である。なので、そのように修正した。

Throw a syntax error for a parameter properties in not constructor

現在の @babel/parser は次のようなエラーをスローする。

// Input
class C {
not_constructor(readonly foo: string) {}
}

// Error
Unexpected token, expected "," (2:27)

この入力は constrcutor 以外に対してパラメータープロパティを使っているためインバリッドな TypeScript のコードだが、このエラーはあまりにも不親切である。そしてこのエラーは errorRecovery 不能なために、例えば次のようなコードを Prettier に入力するとエラーになる。

class C {
// o が足りない
constructr(readonly foo: string) {}
}

本来 Prettier は書きかけのコードであってもある程度フォーマットができてほしいので、これは修正する必要がある。なので、errorRecovery 可能なシンタックスエラーを吐きつつ、パースは成功するように修正した。

Throw a syntax error for a constructor with type parameters

現在の @babel/parser は次のようなコードに対してエラーをスローしない。

// Input
class C {
constructor<T>(foo: T) {}
}

しかしこのコードは constructor に型引数があるので TS 的にはインバリッドである。なのでシンタックスエラーをスローするように修正した。

Do not throw an error for optional binding pattern params in function declaration

現在の @babel/parser は次のようなコードに対してシンタックスエラーをスローする。

// Input
export declare function ohai({ foo }?: Args): string;

// Error
A binding pattern parameter cannot be optional in an implementation signature. (1:29)

implementation signature ではないのにこのエラーが出るのはバグで、実際 TypeScript コンパイラーではこのエラーは出ない。なので修正した。

Throw an error for a declare class field that have an initializer

現在の @babel/parser は次のようなコードに対してシンタックスエラーをスローしない。

// Input
class A {
declare bar: string = "test";
}

本来 TypeScript では declare なフィールドに初期値をもたせることはできないはずなので、シンタックスエラーを吐くべきである。なので修正した。

Add missing tests for TypeScript syntax errors

これは足りていなかったテストケースを追加した。よく使われている OSS でも意外とこういうことがある。

Improve syntax error for class fields in ambient context

「初期値を持つ declare なクラスフィールドにシンタックスエラーをスローしない」、というのは修正したが、ambient context なクラスフィールドの定義方法は他にも存在し、それらに対してもシンタックスエラーをスローするように修正した。

// Input
declare module m {
class C {
field = "field";
}
}

そして、エラーメッセージを TypeScript コンパイラと統一した。

[ts] Throw a syntax error for index signature with declare

現在の @babel/parser は次のようなコードに対してシンタックスエラーをスローしない。

// Input
class C {
[key: string]: string;
}

TypeScript では declare な index signature というのは定義できないはずなので、シンタックスエラーをスローするように修正した。

Throw a recoverable error for missing initializer in const declaration

現在の @babel/parser は次のようなコードに対して errorRecovery 不能なエラーをスローする。

// Input
const a;

// Error
Unexpected token (1:9)

これは人間にとってわかりにくいので、errorRecovery可能なエラーをスローし、パース自体は成功するように修正した。

Throw a syntax error for empty type parameter/argument

現在の @babel/parser は次のようなコードに対してシンタックスエラーをスローしない。

// Input
let a: Foo<>;

TypeScript コンパイラは空の型引数に対してシンタックスエラーをスローするので、それに合わせる形で修正した。