🤖

Deno で eslint を動かす

に公開

deno lint はさすがにルールが足りないので、 eslint も動かせるように確かめたログ。

tl;dr

  • deno run -A npm:eslint で、型チェックを除く構文周りの lint は動く
  • deno-lsp の型リゾルバーがないので、tseslint.configs.strictTypeChecked は動かない

動かしてみる

ある程度 Node/Deno 互換な範囲の型定義を置く

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "noEmit": true,
    "moduleResolution": "bundler",
    "strict": true,
    "importsNotUsedAsValues": "error",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "allowImportingTsExtensions": true
  }
}

node 互換モードで依存をいれる。

$ deno init
$ deno add npm:eslint npm:@eslint/js npm:typescript-eslint npm:eslint-plugin-unused-imports

設定ファイル

eslint.config.ts
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
// フラットコンフィグ対応なら動く
import unusedImports from "eslint-plugin-unused-imports";

// class を禁止するカスタムルール
import { ESLintUtils } from "@typescript-eslint/utils";
export const noClass = createRule({
  name: "no-class",
  meta: {
    docs: {
      description: "Disallow the use of class",
    },
    messages: {
      dontUseClass: "Do not use class",
    },
    type: "suggestion",
    schema: [],
  },
  defaultOptions: [],
  create(context) {
    return {
      ClassExpression(node) {
        if (node.id != null) {
          context.report({
            messageId: "dontUseClass",
            node: node.id,
          });
        }
      },
      ClassDeclaration(node) {
        if (node.id != null) {
          context.report({
            messageId: "dontUseClass",
            node: node.id,
          });
        }
      },
    };
  },
});


export default tseslint.config(
  eslint.configs.recommended,
  /// deno lsp がないので、型チェックは不整合になる
  // ...tseslint.configs.strictTypeChecked,
  ...tseslint.configs.stylistic,
  /// immport 解決ルールが違うので, eslint-plugin-import もだめだった
  // importPlugin.flatConfigs!.recommended,
  {
    plugins: {
      // これは動いた
      "unused-imports": unusedImports,
      local: {
        rules: {
          "no-class": noClass,
        },
      },
    },
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "local/no-class": "error",
    },
  },
  {
    rules: {
      "@typescript-eslint/no-redeclare": "error",
      "@typescript-eslint/prefer-ts-expect-error": "error",
      "unused-imports/no-unused-imports": "error",
    },
  },
);

動かす。

$ deno run -A npm:eslint

/home/mizchi/mizchi/bp/_experimental/deno-eslint/src/fixtures/1.ts
   1:1   warning  Unused eslint-disable directive (no problems were reported from '@typescript-eslint/no-unused-vars')
   2:15  error    '_' is defined but never used                                                                                                                                       no-unused-vars
   2:15  error    '_' is defined but never used

Node 互換 API から叩く

// https://eslint.org/docs/latest/integrate/nodejs-api
import path from "node:path";
import { loadESLint } from "eslint";

const FlatESLint = await loadESLint({ useFlatConfig: true });
const eslint = new FlatESLint({ cwd: Deno.cwd() });

if (import.meta.main) {
  const target = Deno.args[0];
  const files: string[] = target
    ? [path.resolve(Deno.cwd(), target)]
    : ["src/**/*.ts"];
  const results = await eslint.lintFiles(files);
  const formatter = await eslint.loadFormatter("stylish");
  const resultText = formatter.format(results);
  console.log(resultText);
}

動いた。

$ deno run -A run.ts target.ts

感想

構文のみだが、使えなくもない。

型の解決は自分で deno-graph や LSP を繋ぎこみにいってもいいけど、tsgo 前提でエコシステム変わりそうだから、それまで待つ

Discussion