From de886ac531b4c57cbe7734786af937ebfb2ca78f Mon Sep 17 00:00:00 2001 From: JamesHenry Date: Sun, 9 Nov 2025 19:27:25 +0400 Subject: [PATCH] fix(angular-eslint): support eslint defineConfig types in addition to typescript-eslint config --- package.json | 1 + packages/angular-eslint/src/index.ts | 25 +++++--- .../tests/type-compatibility.test.ts | 64 +++++++++++++++++++ packages/angular-eslint/tsconfig.json | 3 + .../angular-eslint/tsconfig.type-compat.json | 15 +++++ pnpm-lock.yaml | 6 ++ pnpm-workspace.yaml | 1 + 7 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 packages/angular-eslint/tests/type-compatibility.test.ts create mode 100644 packages/angular-eslint/tsconfig.type-compat.json diff --git a/package.json b/package.json index a770f2068..589fc7eda 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@angular/compiler": "catalog:", "@commitlint/cli": "catalog:", "@commitlint/config-conventional": "catalog:", + "@eslint/core": "catalog:", "@mdn/browser-compat-data": "catalog:", "@nx/devkit": "catalog:", "@nx/esbuild": "catalog:", diff --git a/packages/angular-eslint/src/index.ts b/packages/angular-eslint/src/index.ts index 051fc5799..476955c01 100644 --- a/packages/angular-eslint/src/index.ts +++ b/packages/angular-eslint/src/index.ts @@ -1,6 +1,7 @@ import tsPluginBase from '@angular-eslint/eslint-plugin'; import templatePluginBase from '@angular-eslint/eslint-plugin-template'; import * as templateParserBase from '@angular-eslint/template-parser'; +import type { Plugin as ESLintPlugin } from '@eslint/core'; import type { TSESLint } from '@typescript-eslint/utils'; import { parser } from 'typescript-eslint'; @@ -16,7 +17,9 @@ const templateParser: TSESLint.FlatConfig.Parser = { }; /* -we could build a plugin object here without the `configs` key - but if we do +NOTE: Comment and approach taken from typescript-eslint: + +We could build a plugin object here without the `configs` key - but if we do that then we create a situation in which ``` require('angular-eslint').tsPlugin !== require('@angular-eslint/eslint-plugin') @@ -38,14 +41,18 @@ use our new package); however legacy configs consumed via `@eslint/eslintrc` would never be able to satisfy this constraint and thus users would be blocked from using them. */ -const tsPlugin: TSESLint.FlatConfig.Plugin = tsPluginBase as Omit< - typeof tsPluginBase, - 'configs' ->; -const templatePlugin: TSESLint.FlatConfig.Plugin = templatePluginBase as Omit< - typeof templatePluginBase, - 'configs' ->; + +/** + * Make the plugins compatible with both ESLint's Plugin type and typescript-eslint's + * FlatConfig.Plugin type through type assertion. + * + * This is covered by a type compatibility test in tests/type-compatibility.test.ts + */ +type CompatiblePlugin = Omit & { + configs?: never; +}; +const tsPlugin = tsPluginBase as unknown as CompatiblePlugin; +const templatePlugin = templatePluginBase as unknown as CompatiblePlugin; const configs = { tsAll: tsAllConfig(tsPlugin, parser), diff --git a/packages/angular-eslint/tests/type-compatibility.test.ts b/packages/angular-eslint/tests/type-compatibility.test.ts new file mode 100644 index 000000000..7d59aabf3 --- /dev/null +++ b/packages/angular-eslint/tests/type-compatibility.test.ts @@ -0,0 +1,64 @@ +/** + * This test file exists purely to be type-checked in CI to ensure that angular-eslint plugins and parsers are compatible with both: + * 1. ESLint's defineConfig function (uses ESLint.Plugin and Linter.ParserModule types) + * 2. typescript-eslint's config function (uses TSESLint.FlatConfig.Plugin and TSESLint.FlatConfig.Parser types) + */ + +import { defineConfig } from 'eslint/config'; +import { config } from 'typescript-eslint'; + +import angular from '../src/index'; + +/** + * Test 1: Verify compatibility with ESLint's defineConfig + */ +defineConfig([ + { + files: ['**/*.ts'], + plugins: { + '@angular-eslint': angular.tsPlugin, + }, + rules: { + '@angular-eslint/component-class-suffix': 'error', + }, + }, + { + files: ['**/*.html'], + plugins: { + '@angular-eslint/template': angular.templatePlugin, + }, + languageOptions: { + parser: angular.templateParser, + }, + rules: { + '@angular-eslint/template/banana-in-box': 'error', + }, + }, +]); + +/** + * Test 2: Verify compatibility with typescript-eslint's config function + */ +config( + { + files: ['**/*.ts'], + plugins: { + '@angular-eslint': angular.tsPlugin, + }, + rules: { + '@angular-eslint/component-class-suffix': 'error', + }, + }, + { + files: ['**/*.html'], + plugins: { + '@angular-eslint/template': angular.templatePlugin, + }, + languageOptions: { + parser: angular.templateParser, + }, + rules: { + '@angular-eslint/template/banana-in-box': 'error', + }, + }, +); diff --git a/packages/angular-eslint/tsconfig.json b/packages/angular-eslint/tsconfig.json index ae0e076b8..c1aef772a 100644 --- a/packages/angular-eslint/tsconfig.json +++ b/packages/angular-eslint/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.type-compat.json" } ] } diff --git a/packages/angular-eslint/tsconfig.type-compat.json b/packages/angular-eslint/tsconfig.type-compat.json new file mode 100644 index 000000000..dc77d79a4 --- /dev/null +++ b/packages/angular-eslint/tsconfig.type-compat.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/angular-eslint-type-compat", + "types": ["node"], + "sourceMap": true, + "skipLibCheck": false + }, + "include": ["tests/type-compatibility.test.ts"], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fe01c72b..746e8762b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ catalogs: '@commitlint/config-conventional': specifier: 20.0.0 version: 20.0.0 + '@eslint/core': + specifier: ^0.17.0 + version: 0.17.0 '@mdn/browser-compat-data': specifier: 7.1.18 version: 7.1.18 @@ -216,6 +219,9 @@ importers: '@commitlint/config-conventional': specifier: 'catalog:' version: 20.0.0 + '@eslint/core': + specifier: 'catalog:' + version: 0.17.0 '@mdn/browser-compat-data': specifier: 'catalog:' version: 7.1.18 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4f7bac2a7..8ea3f1d5b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,6 +6,7 @@ catalog: '@angular/compiler': 20.3.9 '@commitlint/cli': 20.1.0 '@commitlint/config-conventional': 20.0.0 + '@eslint/core': ^0.17.0 '@mdn/browser-compat-data': 7.1.18 '@nx/devkit': 22.1.0-beta.3 '@nx/esbuild': 22.1.0-beta.3