Closed5

no-restricted-syntax でお手軽にカスタムルールを追加する

nissy-devnissy-dev

そもそも AST とは?

AST = Abstract Syntax Tree、抽象構文木

文字列であるソースコードを解析して、 ツリー状に表現したもの。プログラムに本質的に関係のないコメントとかスペースとかはなくなることが多い。

https://speakerdeck.com/brn/how-javascript-works?slide=10

(brn さんのスライドは大変素晴らしいので読んでみると良いよ)

ソースコードの AST を確認したいときは AST explorer を使う

https://astexplorer.net/

nissy-devnissy-dev

no-restricted-syntax

AST に対する query とエラー文言を定義すると、query にマッチしたものに error を報告するというルール。

https://eslint.org/docs/latest/rules/no-restricted-syntax

見てもらったほうが速そうなので、早速今回実装したものを見せる

  • スタイルの確認は VRT でしたいので、toHaveCSS 系のメソッドを使わないようにしたい
  • lib/date.ts に日付操作用の関数を集約しているので、Intl.DateFormat を使えわないようにしたい
    'no-restricted-syntax': [
      'error',
      {
        selector: 'MemberExpression[property.name=/toHaveCSS|toHaveStyle/]',
        message: '見た目を確認するには、CSS のチェックではなく VRT などを検討してください。',
      },
      {
        selector: "MemberExpression[object.name='Intl'][property.name='DateTimeFormat']",
        message:
          '日付操作を行う場合は、基本的に date-fns を使用してください。src/lib に日付操作の関数も定義されています。',
      },
    ],

AST に慣れてないと selector の定義見ても、こんな感じになるよね

nissy-devnissy-dev

selector の実装方法

禁止したい構文の ASTをチェックする

test('test', async ({ page }) => {
  // これを禁止したい
  await expect(page.locator('....')).toHaveCSS('font-size', '16px');
});

↑のコードを AST explorer に貼り付ける

AST explorer を見るときのポイント

  • parser の設定は確認しよう
    • ESLint のルールを書くときは ESLint 系の parser を選ぼう
  • チェックしたい箇所をダブルクリックすると便利だよ
    • ダブルクリックした箇所に対する AST にフォーカスが移動するよ

構文に対するクエリを書いてみる

AST を見てわかったこと

  • "toHaveCSS" が格納されているノードは MemberExpression になっている
    • ノードはただの object だと思って貰えればよい
  • "toHaveCSS" が格納されているフィールドは "property" になっている
  • "toHaveCSS" は Identifier というノードの name フィールドに格納されている

これを query に変換すると次のようになる

MemberExpression[property.name=/toHaveCSS|toHaveStyle/]

クエリの詳細の仕様は、次のドキュメントにかかれている。基本的になんでもできる。
https://eslint.org/docs/latest/extend/selectors

クエリがうまくいくかどうかは次の playground でも確認できる

http://estools.github.io/esquery/

nissy-devnissy-dev

練習

  • Intl.DateFormat の利用を禁止する
  • グローバル空間での定数宣言は必ず大文字 or undescore で定義されることを強制する
    • const AAA_BBB = 1 みたいなこと
2つ目の解答

以下のクエリで大文字 or undescore 以外を使った場合にマッチできる

Program > VariableDeclaration > VariableDeclarator[id.name=/^[^A-Z_]+$/][init.type="Literal"]

以下だとグローバル空間でないのも拾ってしまうのでだめ

VariableDeclaration > VariableDeclarator[id.name=/^[^A-Z_]+$/][init.type="Literal"]

以下だと Literal 以外の arrow 関数とかも拾ってしまうのでだめ

Program > VariableDeclaration > VariableDeclarator[id.name=/^[^A-Z_]+$/]
このスクラップは2024/02/26にクローズされました