Repository: ota-meshi/eslint-plugin-astro
Pull request #371 (2024-04-28 03:23:37)

chore: minor refactor and add integration test

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 8950c6b3073fd4d89184ea5c06ca69a965a8d76c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/ota-meshi/eslint-plugin-astro/new/refactor?filename=.changeset/lazy-bulldogs-buy.md&value=---%0A%22eslint-plugin-astro%22%3A%20patch%0A---%0A%0Achore%3A%20minor%20refactor%20and%20add%20integration%20test%0A)



Patch:
diff --git a/.github/workflows/NodeCI.yml b/.github/workflows/NodeCI.yml
index 615d94b..9db5d5d 100644
--- a/.github/workflows/NodeCI.yml
+++ b/.github/workflows/NodeCI.yml
@@ -61,6 +61,15 @@ jobs:
         run: npm install -f
       - name: Test
         run: npm test
+  test-integration:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
+      - name: Install Packages
+        run: npm install -f
+      - name: Test
+        run: npm run test:integration
   test-and-coverage:
     runs-on: ubuntu-latest
     steps:
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
index 3776fb3..734be1f 100644
--- a/.github/workflows/Release.yml
+++ b/.github/workflows/Release.yml
@@ -22,9 +22,6 @@ jobs:
       - name: Install Dependencies
         run: npm install -f
 
-      - name: Build
-        run: npm run build:tsup
-
       - name: Create Release Pull Request or Publish to npm
         id: changesets
         uses: changesets/action@v1
diff --git a/.gitignore b/.gitignore
index 14e43c2..2e28b5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,3 +108,4 @@ dist
 /docs-build/shim/eslint.mjs
 /docs-build/shim/astro-eslint-parser.mjs
 /docs-build/shim/eslint-plugin-jsx-a11y.mjs
+/tests/fixtures/integration/**/actual.json
\ No newline at end of file
diff --git a/eslint.config.cjs b/eslint.config.cjs
index da65956..4dc6332 100644
--- a/eslint.config.cjs
+++ b/eslint.config.cjs
@@ -29,6 +29,8 @@ module.exports = [
       "tests/fixtures/rules/valid-compile/valid/babel/",
       "tests/fixtures/rules/valid-compile/valid/ts/",
       "tests/fixtures/rules/prefer-style-directive/",
+      "tests/fixtures/integration/**/actual.json",
+      "tests/fixtures/integration/*/src/",
       "build/",
     ],
   },
diff --git a/package.json b/package.json
index 0be5307..b5e916f 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,8 @@
     "build:tsup": "tsup",
     "clean": "rimraf .nyc_output dist coverage",
     "test": "npm run mocha -- \"tests/src/**/*.ts\" --reporter dot --timeout 60000 && npm run test:type",
+    "test:integration": "npm run build && npm run test:integration-wo-build",
+    "test:integration-wo-build": "npm run mocha -- \"tests/integration/**/*.ts\" --reporter dot --timeout 60000",
     "test:type": "tsc --noEmit",
     "cover": "nyc --reporter=lcov npm run test",
     "debug": "npm run mocha -- \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
diff --git a/src/plugin-config-builder.ts b/src/cjs-config-builder.ts
similarity index 56%
rename from src/plugin-config-builder.ts
rename to src/cjs-config-builder.ts
index 8670bd0..dc4180e 100644
--- a/src/plugin-config-builder.ts
+++ b/src/cjs-config-builder.ts
@@ -7,56 +7,6 @@ import flatAll from "./configs/flat/all"
 import { buildA11yFlatConfigs, buildA11yLegacyConfigs } from "./a11y"
 import type { Linter } from "eslint"
 
-type ESMConfigs = {
-  base: Linter.FlatConfig[]
-  recommended: Linter.FlatConfig[]
-  all: Linter.FlatConfig[]
-  "jsx-a11y-strict": Linter.FlatConfig[]
-  "jsx-a11y-recommended": Linter.FlatConfig[]
-  // For backward compatibility
-  "flat/base": Linter.FlatConfig[]
-  "flat/recommended": Linter.FlatConfig[]
-  "flat/all": Linter.FlatConfig[]
-  "flat/jsx-a11y-strict": Linter.FlatConfig[]
-  "flat/jsx-a11y-recommended": Linter.FlatConfig[]
-}
-/**
- * Build configs for ESM Module
- */
-export function buildEsmConfigs(): ESMConfigs {
-  const esmConfigs: ESMConfigs = {
-    base: flatBase as Linter.FlatConfig[],
-    recommended: flatRecommended as Linter.FlatConfig[],
-    all: flatAll as Linter.FlatConfig[],
-    "jsx-a11y-strict": null as never,
-    "jsx-a11y-recommended": null as never,
-    // For backward compatibility
-    "flat/base": flatBase as Linter.FlatConfig[],
-    "flat/recommended": flatRecommended as Linter.FlatConfig[],
-    "flat/all": flatAll as Linter.FlatConfig[],
-    "flat/jsx-a11y-strict": null as never,
-    "flat/jsx-a11y-recommended": null as never,
-  }
-
-  const a11yFlatConfigs = buildA11yFlatConfigs()
-  for (const configName of Object.keys(a11yFlatConfigs)) {
-    Object.defineProperty(esmConfigs, configName, {
-      enumerable: true,
-      get() {
-        return a11yFlatConfigs[configName]
-      },
-    })
-    Object.defineProperty(esmConfigs, `flat/${configName}`, {
-      enumerable: true,
-      get() {
-        return a11yFlatConfigs[configName]
-      },
-    })
-  }
-
-  return esmConfigs
-}
-
 type CJSConfigs = {
   base: Linter.Config
   recommended: Linter.Config
diff --git a/src/configs/flat/base.ts b/src/configs/flat/base.ts
index 00be547..7d549e5 100644
--- a/src/configs/flat/base.ts
+++ b/src/configs/flat/base.ts
@@ -14,8 +14,8 @@ export default [
   {
     plugins: {
       get astro(): ESLint.Plugin {
-        // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
-        return (plugin ??= require("../../plugin-without-config"))
+        // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- ignore
+        return (plugin ??= require("../../plugin").plugin)
       },
     },
   },
diff --git a/src/esm-config-builder.ts b/src/esm-config-builder.ts
new file mode 100644
index 0000000..98311e4
--- /dev/null
+++ b/src/esm-config-builder.ts
@@ -0,0 +1,55 @@
+import flatBase from "./configs/flat/base"
+import flatRecommended from "./configs/flat/recommended"
+import flatAll from "./configs/flat/all"
+import { buildA11yFlatConfigs } from "./a11y"
+import type { Linter } from "eslint"
+
+type ESMConfigs = {
+  base: Linter.FlatConfig[]
+  recommended: Linter.FlatConfig[]
+  all: Linter.FlatConfig[]
+  "jsx-a11y-strict": Linter.FlatConfig[]
+  "jsx-a11y-recommended": Linter.FlatConfig[]
+  // For backward compatibility
+  "flat/base": Linter.FlatConfig[]
+  "flat/recommended": Linter.FlatConfig[]
+  "flat/all": Linter.FlatConfig[]
+  "flat/jsx-a11y-strict": Linter.FlatConfig[]
+  "flat/jsx-a11y-recommended": Linter.FlatConfig[]
+}
+/**
+ * Build configs for ESM Module
+ */
+export function buildEsmConfigs(): ESMConfigs {
+  const esmConfigs: ESMConfigs = {
+    base: flatBase as Linter.FlatConfig[],
+    recommended: flatRecommended as Linter.FlatConfig[],
+    all: flatAll as Linter.FlatConfig[],
+    "jsx-a11y-strict": null as never,
+    "jsx-a11y-recommended": null as never,
+    // For backward compatibility
+    "flat/base": flatBase as Linter.FlatConfig[],
+    "flat/recommended": flatRecommended as Linter.FlatConfig[],
+    "flat/all": flatAll as Linter.FlatConfig[],
+    "flat/jsx-a11y-strict": null as never,
+    "flat/jsx-a11y-recommended": null as never,
+  }
+
+  const a11yFlatConfigs = buildA11yFlatConfigs()
+  for (const configName of Object.keys(a11yFlatConfigs)) {
+    Object.defineProperty(esmConfigs, configName, {
+      enumerable: true,
+      get() {
+        return a11yFlatConfigs[configName]
+      },
+    })
+    Object.defineProperty(esmConfigs, `flat/${configName}`, {
+      enumerable: true,
+      get() {
+        return a11yFlatConfigs[configName]
+      },
+    })
+  }
+
+  return esmConfigs
+}
diff --git a/src/index.cts b/src/index.cts
index 1db9245..0b317d9 100644
--- a/src/index.cts
+++ b/src/index.cts
@@ -1,4 +1,4 @@
-import { buildCjsConfigs } from "./plugin-config-builder"
-import { meta, rules, processors, environments } from "./plugin-without-config"
+import { buildCjsConfigs } from "./cjs-config-builder"
+import { plugin } from "./plugin"
 const configs = buildCjsConfigs()
-export = { meta, configs, rules, processors, environments }
+export = Object.assign(plugin, { configs })
diff --git a/src/index.mts b/src/index.mts
index 8e2cd25..8e6466b 100644
--- a/src/index.mts
+++ b/src/index.mts
@@ -1,5 +1,5 @@
-import { buildEsmConfigs } from "./plugin-config-builder"
-import { meta, rules, processors, environments } from "./plugin-without-config"
-const configs = buildEsmConfigs()
-export default { meta, configs, rules, processors, environments }
-export { meta, configs, rules, processors, environments }
+import { buildEsmConfigs } from "./esm-config-builder"
+import { plugin } from "./plugin"
+export const configs = buildEsmConfigs()
+export default Object.assign(plugin, { configs })
+export const { meta, rules, processors, environments } = plugin
diff --git a/src/plugin-without-config.ts b/src/plugin.ts
similarity index 67%
rename from src/plugin-without-config.ts
rename to src/plugin.ts
index 26fd0ca..3b3354a 100644
--- a/src/plugin-without-config.ts
+++ b/src/plugin.ts
@@ -1,20 +1,24 @@
 import { rules as ruleList } from "./utils/rules"
 import * as processorsDefines from "./processor"
 import type { Rule } from "eslint"
+import * as meta from "./meta"
+import { environments } from "./environments"
 
-export * as meta from "./meta"
-export { environments } from "./environments"
-
-export const rules = ruleList.reduce(
+const rules = ruleList.reduce(
   (obj, r) => {
     obj[r.meta.docs.ruleName] = r as never
     return obj
   },
   {} as { [key: string]: Rule.RuleModule },
 )
-
-export const processors = {
+const processors = {
   ".astro": processorsDefines.astroProcessor,
   astro: processorsDefines.astroProcessor,
   "client-side-ts": processorsDefines.clientSideTsProcessor,
 }
+export const plugin = {
+  meta,
+  environments,
+  rules,
+  processors,
+}
diff --git a/tools/update-rulesets.ts b/tools/update-rulesets.ts
index 8f70db7..0504692 100644
--- a/tools/update-rulesets.ts
+++ b/tools/update-rulesets.ts
@@ -117,7 +117,7 @@ export default [
     plugins: {
       get astro(): ESLint.Plugin {
         // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
-        return (plugin ??= require("../../plugin-without-config"))
+        return (plugin ??= require("../../plugin").plugin)
       },
     },
   },
diff --git a/tsconfig.json b/tsconfig.json
index 69c7a38..f84ad29 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -33,6 +33,7 @@
   "include": [
     "src/**/*",
     "tests/src/**/*",
+    "tests/integration/**/*",
     "tests/utils/**/*",
     "tests/typing.test.ts",
     "tools/**/*",


Test patch:
diff --git a/tests/fixtures/integration/config/.npmrc b/tests/fixtures/integration/config/.npmrc
new file mode 100644
index 0000000..9cf9495
--- /dev/null
+++ b/tests/fixtures/integration/config/.npmrc
@@ -0,0 +1,1 @@
+package-lock=false
\ No newline at end of file
diff --git a/tests/fixtures/integration/config/eslint.config.mjs b/tests/fixtures/integration/config/eslint.config.mjs
new file mode 100644
index 0000000..122e8f1
--- /dev/null
+++ b/tests/fixtures/integration/config/eslint.config.mjs
@@ -0,0 +1,6 @@
+import js from "@eslint/js"
+import eslintPluginAstro from "eslint-plugin-astro"
+export default [
+  js.configs.recommended,
+  ...eslintPluginAstro.configs.recommended,
+]
diff --git a/tests/fixtures/integration/config/expected.json b/tests/fixtures/integration/config/expected.json
new file mode 100644
index 0000000..874e226
--- /dev/null
+++ b/tests/fixtures/integration/config/expected.json
@@ -0,0 +1,26 @@
+[
+  {
+    "filePath": "src/index.astro",
+    "messages": [
+      {
+        "ruleId": "no-unused-vars",
+        "severity": 2,
+        "message": "'foo' is assigned a value but never used.",
+        "line": 2,
+        "column": 7,
+        "nodeType": "Identifier",
+        "messageId": "unusedVar",
+        "endLine": 2,
+        "endColumn": 10
+      }
+    ],
+    "suppressedMessages": [],
+    "errorCount": 1,
+    "fatalErrorCount": 0,
+    "warningCount": 0,
+    "fixableErrorCount": 0,
+    "fixableWarningCount": 0,
+    "source": "---\nconst foo = {}\n---\n\n<a></a>\n",
+    "usedDeprecatedRules": []
+  }
+]
diff --git a/tests/fixtures/integration/config/package.json b/tests/fixtures/integration/config/package.json
new file mode 100644
index 0000000..b939d08
--- /dev/null
+++ b/tests/fixtures/integration/config/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "eslint-plugin-astro-integration-test-for-legacy-config",
+  "version": "1.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "test": "eslint . --format json"
+  },
+  "license": "MIT",
+  "devDependencies": {
+    "@eslint/js": "latest",
+    "eslint": "^9.1.1",
+    "eslint-plugin-astro": "file:../../../.."
+  }
+}
diff --git a/tests/fixtures/integration/config/src/index.astro b/tests/fixtures/integration/config/src/index.astro
new file mode 100644
index 0000000..f1ef7c7
--- /dev/null
+++ b/tests/fixtures/integration/config/src/index.astro
@@ -0,0 +1,5 @@
+---
+const foo = {}
+---
+
+<a></a>
diff --git a/tests/fixtures/integration/legacy-config/.eslintrc.js b/tests/fixtures/integration/legacy-config/.eslintrc.js
new file mode 100644
index 0000000..fb4a47d
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict"
+
+module.exports = {
+  root: true,
+  extends: ["eslint:recommended", "plugin:astro/recommended"],
+}
diff --git a/tests/fixtures/integration/legacy-config/.npmrc b/tests/fixtures/integration/legacy-config/.npmrc
new file mode 100644
index 0000000..9cf9495
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/.npmrc
@@ -0,0 +1,1 @@
+package-lock=false
\ No newline at end of file
diff --git a/tests/fixtures/integration/legacy-config/expected.json b/tests/fixtures/integration/legacy-config/expected.json
new file mode 100644
index 0000000..874e226
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/expected.json
@@ -0,0 +1,26 @@
+[
+  {
+    "filePath": "src/index.astro",
+    "messages": [
+      {
+        "ruleId": "no-unused-vars",
+        "severity": 2,
+        "message": "'foo' is assigned a value but never used.",
+        "line": 2,
+        "column": 7,
+        "nodeType": "Identifier",
+        "messageId": "unusedVar",
+        "endLine": 2,
+        "endColumn": 10
+      }
+    ],
+    "suppressedMessages": [],
+    "errorCount": 1,
+    "fatalErrorCount": 0,
+    "warningCount": 0,
+    "fixableErrorCount": 0,
+    "fixableWarningCount": 0,
+    "source": "---\nconst foo = {}\n---\n\n<a></a>\n",
+    "usedDeprecatedRules": []
+  }
+]
diff --git a/tests/fixtures/integration/legacy-config/package.json b/tests/fixtures/integration/legacy-config/package.json
new file mode 100644
index 0000000..a5d9864
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "eslint-plugin-astro-integration-test-for-legacy-config",
+  "version": "1.0.0",
+  "private": true,
+  "scripts": {
+    "test": "eslint . --format json"
+  },
+  "license": "MIT",
+  "devDependencies": {
+    "eslint": "^9.1.1",
+    "eslint-plugin-astro": "file:../../../.."
+  }
+}
diff --git a/tests/fixtures/integration/legacy-config/src/index.astro b/tests/fixtures/integration/legacy-config/src/index.astro
new file mode 100644
index 0000000..f1ef7c7
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/src/index.astro
@@ -0,0 +1,5 @@
+---
+const foo = {}
+---
+
+<a></a>
diff --git a/tests/fixtures/integration/legacy-config/test-env.json b/tests/fixtures/integration/legacy-config/test-env.json
new file mode 100644
index 0000000..de094cb
--- /dev/null
+++ b/tests/fixtures/integration/legacy-config/test-env.json
@@ -0,0 +1,3 @@
+{
+  "ESLINT_USE_FLAT_CONFIG": false
+}
diff --git a/tests/integration/test.ts b/tests/integration/test.ts
new file mode 100644
index 0000000..b79c162
--- /dev/null
+++ b/tests/integration/test.ts
@@ -0,0 +1,69 @@
+import cp from "child_process"
+import assert from "assert"
+import fs from "fs"
+import path from "path"
+
+const TEST_FIXTURES_ROOT = path.join(__dirname, "../fixtures/integration")
+
+for (const dirent of fs.readdirSync(TEST_FIXTURES_ROOT, {
+  withFileTypes: true,
+})) {
+  if (!dirent.isDirectory()) continue
+  const TEST_CWD = path.join(TEST_FIXTURES_ROOT, dirent.name)
+
+  describe(`Integration for ${dirent.name}`, () => {
+    let originalCwd: string
+
+    before(() => {
+      originalCwd = process.cwd()
+      process.chdir(TEST_CWD)
+      cp.execSync("npm i -f", { stdio: "inherit" })
+    })
+    after(() => {
+      process.chdir(originalCwd)
+    })
+
+    it("should lint errors", () => {
+      const envFile = path.join(TEST_CWD, "test-env.json")
+      const expectedFile = path.join(TEST_CWD, "expected.json")
+      const actualFile = path.join(TEST_CWD, "actual.json")
+      const env = {
+        // eslint-disable-next-line no-process-env -- test
+        ...process.env,
+        ...(fs.existsSync(envFile)
+          ? // eslint-disable-next-line @typescript-eslint/no-require-imports -- test
+            require(envFile)
+          : {}),
+      }
+      try {
+        const res = cp.execSync(`npx eslint src --format json`, {
+          env,
+          cwd: TEST_CWD,
+        })
+        fs.writeFileSync(actualFile, `no error:${res}`)
+        assert.fail("Expect error")
+      } catch (e: any) {
+        const output = `${e.stdout}`
+        try {
+          fs.writeFileSync(actualFile, output)
+          const results = JSON.parse(output)
+          fs.writeFileSync(actualFile, JSON.stringify(output, null, 2))
+          assert.ok(Array.isArray(results), "output is not array")
+          const result = results.map((r) => ({
+            ...r,
+            filePath: path.relative(TEST_CWD, r.filePath),
+          }))
+          fs.writeFileSync(actualFile, JSON.stringify(result, null, 2))
+
+          const expected =
+            // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- test
+            require(expectedFile)
+          assert.deepStrictEqual(result, expected)
+        } catch (e) {
+          console.error(e)
+          throw e
+        }
+      }
+    })
+  })
+}
diff --git a/tests/src/meta.ts b/tests/src/meta.ts
index fbd0383..a28d3ea 100644
--- a/tests/src/meta.ts
+++ b/tests/src/meta.ts
@@ -1,5 +1,5 @@
 import assert from "assert"
-import * as plugin from "../../src/plugin-without-config"
+import { plugin } from "../../src/plugin"
 import { version } from "../../package.json"
 const expectedMeta = {
   name: "eslint-plugin-astro",
diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts
index db63041..277c069 100644
--- a/tests/utils/utils.ts
+++ b/tests/utils/utils.ts
@@ -3,7 +3,7 @@ import path from "path"
 import type { RuleTester } from "eslint"
 import { getLinter as getCompatLinter } from "eslint-compat-utils/linter"
 import * as astroESLintParser from "astro-eslint-parser"
-import * as plugin from "../../src/plugin-without-config"
+import { plugin } from "../../src/plugin"
 import { applyFixes } from "./source-code-fixer"
 // eslint-disable-next-line @typescript-eslint/naming-convention -- class name
 const Linter = getCompatLinter()