diff --git a/docs/rules/no-property-in-node.md b/docs/rules/no-property-in-node.md index 7d986ad0..75876214 100644 --- a/docs/rules/no-property-in-node.md +++ b/docs/rules/no-property-in-node.md @@ -13,9 +13,7 @@ Instead, checking a node's `type` property is generally considered preferable. Examples of **incorrect** code for this rule: -```ts -/* eslint eslint-plugin/no-property-in-node: error */ - +```js /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -25,7 +23,7 @@ module.exports = { return { 'ClassDeclaration, FunctionDeclaration'(node) { if ('superClass' in node) { - console.log('This is a class declaration:', node); + // This is a class declaration } }, }; @@ -35,9 +33,7 @@ module.exports = { Examples of **correct** code for this rule: -```ts -/* eslint eslint-plugin/no-property-in-node: error */ - +```js /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -47,7 +43,7 @@ module.exports = { return { 'ClassDeclaration, FunctionDeclaration'(node) { if (node.type === 'ClassDeclaration') { - console.log('This is a class declaration:', node); + // This is a class declaration; } }, }; diff --git a/eslint.config.ts b/eslint.config.ts index ad8f1362..6ff5a59a 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -2,9 +2,9 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import js from '@eslint/js'; import { FlatCompat } from '@eslint/eslintrc'; -import { defineConfig } from 'eslint/config'; import markdown from 'eslint-plugin-markdown'; import pluginN from 'eslint-plugin-n'; +import tseslint from 'typescript-eslint'; import eslintPlugin from './lib/index.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -13,14 +13,14 @@ const compat = new FlatCompat({ recommendedConfig: js.configs.recommended, }); -export default defineConfig([ +export default tseslint.config([ // Global ignores { - ignores: ['node_modules', 'coverage', 'dist'], + ignores: ['node_modules', 'coverage', 'dist', 'tests/lib/fixtures'], }, // Global settings { - languageOptions: { sourceType: 'module' }, + languageOptions: { parser: tseslint.parser, sourceType: 'module' }, }, ...compat.extends( 'not-an-aardvark/node', @@ -41,11 +41,18 @@ export default defineConfig([ 'unicorn/no-null': 'off', 'unicorn/prefer-module': 'off', 'unicorn/prevent-abbreviations': 'off', + 'unicorn/no-nested-ternary': 'off', }, }, + // TypeScript rules + tseslint.configs.recommended.map((config) => ({ + files: ['**/*.ts', '**/*.mts', '**/*.cts'], + ...config, + rules: { ...config.rules, 'n/no-missing-import': 'off' }, + })), { // Apply eslint-plugin rules to our own rules/tests (but not docs). - files: ['lib/**/*.js', 'tests/**/*.js'], + files: ['lib/**/*.ts', 'tests/**/*.ts'], plugins: { 'eslint-plugin': eslintPlugin }, rules: { ...eslintPlugin.configs.all.rules, @@ -67,12 +74,14 @@ export default defineConfig([ }, { // Markdown JS code samples in documentation: - files: ['**/*.md/*.js'], + files: ['**/*.md/*.js', '**/*.md/*.ts'], plugins: { markdown }, linterOptions: { noInlineConfig: true }, rules: { 'no-undef': 'off', 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + strict: 'off', '@eslint-community/eslint-comments/require-description': 'off', diff --git a/lib/index.ts b/lib/index.ts index 1fbcb0af..89ea9f0c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -41,24 +41,14 @@ import testCaseShorthandStrings from './rules/test-case-shorthand-strings.js'; const require = createRequire(import.meta.url); -const packageMetadata = require("../package.json") as { - name: string; - version: string; +const packageMetadata = require('../package.json') as { + name: string; + version: string; }; const PLUGIN_NAME = packageMetadata.name.replace(/^eslint-plugin-/, ''); -const CONFIG_NAMES = [ - 'all', - 'all-type-checked', - 'recommended', - 'rules', - 'tests', - 'rules-recommended', - 'tests-recommended', -] as const; -type ConfigName = (typeof CONFIG_NAMES)[number]; -const configFilters: Record boolean> = { +const configFilters: Record boolean> = { all: (rule: Rule.RuleModule) => !( rule.meta?.docs && @@ -75,7 +65,7 @@ const configFilters: Record boolean> = { configFilters.recommended(rule) && configFilters.tests(rule), }; -const createConfig = (configName: ConfigName): Linter.Config => ({ +const createConfig = (configName: string): Linter.Config => ({ name: `${PLUGIN_NAME}/${configName}`, plugins: { get [PLUGIN_NAME](): ESLint.Plugin { diff --git a/lib/rules/no-meta-schema-default.ts b/lib/rules/no-meta-schema-default.ts index 4cf540f0..db9905d8 100644 --- a/lib/rules/no-meta-schema-default.ts +++ b/lib/rules/no-meta-schema-default.ts @@ -83,12 +83,9 @@ const rule: Rule.RuleModule = { case 'properties': { if ('properties' in value && Array.isArray(value.properties)) { - for (const property of value.properties) { - if ( - 'value' in property && - property.value.type === 'ObjectExpression' - ) { - checkSchemaElement(property.value); + for (const prop of value.properties) { + if ('value' in prop && prop.value.type === 'ObjectExpression') { + checkSchemaElement(prop.value); } } } diff --git a/lib/rules/no-missing-message-ids.ts b/lib/rules/no-missing-message-ids.ts index 3ece7a2a..74c594cb 100644 --- a/lib/rules/no-missing-message-ids.ts +++ b/lib/rules/no-missing-message-ids.ts @@ -72,7 +72,7 @@ const rule: Rule.RuleModule = { collectReportViolationAndSuggestionData(reportInfo); for (const messageId of reportMessagesAndDataArray .map((obj) => obj.messageId) - .filter((messageId) => !!messageId)) { + .filter((id) => !!id)) { const values = messageId.type === 'Literal' ? [messageId] diff --git a/lib/rules/no-missing-placeholders.ts b/lib/rules/no-missing-placeholders.ts index fa39d453..d6047320 100644 --- a/lib/rules/no-missing-placeholders.ts +++ b/lib/rules/no-missing-placeholders.ts @@ -111,7 +111,7 @@ const rule: Rule.RuleModule = { let match: RegExpExecArray | null; const messageText: string = - // @ts-expect-error + // @ts-expect-error -- Property 'value' does not exist on type 'ArrayExpression'.ts(2339) message.value || messageStaticValue.value; while ((match = PLACEHOLDER_MATCHER.exec(messageText))) { const matchingProperty = diff --git a/lib/rules/no-only-tests.ts b/lib/rules/no-only-tests.ts index 6ed93ce5..2a59cc3d 100644 --- a/lib/rules/no-only-tests.ts +++ b/lib/rules/no-only-tests.ts @@ -56,15 +56,19 @@ const rule: Rule.RuleModule = { sourceCode.getTokenBefore(onlyProperty); const tokenAfter = sourceCode.getTokenAfter(onlyProperty); - if ((tokenBefore && tokenAfter) && + if ( + tokenBefore && + tokenAfter && ((isCommaToken(tokenBefore) && isCommaToken(tokenAfter)) || // In middle of properties - (isOpeningBraceToken(tokenBefore) && - isCommaToken(tokenAfter))) // At beginning of properties + (isOpeningBraceToken(tokenBefore) && + isCommaToken(tokenAfter))) // At beginning of properties ) { yield fixer.remove(tokenAfter); // Remove extra comma. } - if ((tokenBefore && tokenAfter) && + if ( + tokenBefore && + tokenAfter && isCommaToken(tokenBefore) && isClosingBraceToken(tokenAfter) ) { diff --git a/lib/rules/no-unused-message-ids.ts b/lib/rules/no-unused-message-ids.ts index e260e5ce..601daa3b 100644 --- a/lib/rules/no-unused-message-ids.ts +++ b/lib/rules/no-unused-message-ids.ts @@ -103,7 +103,7 @@ const rule: Rule.RuleModule = { collectReportViolationAndSuggestionData(reportInfo); for (const messageId of reportMessagesAndDataArray .map((obj) => obj.messageId) - .filter((messageId) => !!messageId)) { + .filter((id) => !!id)) { const values = messageId.type === 'Literal' ? [messageId] diff --git a/lib/rules/no-unused-placeholders.ts b/lib/rules/no-unused-placeholders.ts index a9dc14cd..cee772c7 100644 --- a/lib/rules/no-unused-placeholders.ts +++ b/lib/rules/no-unused-placeholders.ts @@ -103,7 +103,7 @@ const rule: Rule.RuleModule = { data.type === 'ObjectExpression' ) { const messageValue: string = - // @ts-expect-error + // @ts-expect-error -- Property 'value' does not exist on type 'SimpleCallExpression'.ts(2339) message.value || messageStaticValue.value; // https://github.com/eslint/eslint/blob/2874d75ed8decf363006db25aac2d5f8991bd969/lib/linter.js#L986 const PLACEHOLDER_MATCHER = /{{\s*([^{}]+?)\s*}}/g; diff --git a/lib/rules/no-useless-token-range.ts b/lib/rules/no-useless-token-range.ts index 653ef53d..a17e516b 100644 --- a/lib/rules/no-useless-token-range.ts +++ b/lib/rules/no-useless-token-range.ts @@ -3,14 +3,7 @@ * @author Teddy Katz */ import type { Rule } from 'eslint'; -import type { - CallExpression, - Expression, - MemberExpression, - Node, - Property, - SpreadElement, -} from 'estree'; +import type { Expression, MemberExpression, SpreadElement } from 'estree'; import { getKeyName, getSourceCodeIdentifiers } from '../utils.js'; diff --git a/lib/rules/require-meta-default-options.ts b/lib/rules/require-meta-default-options.ts index a8bc4374..ac836c4a 100644 --- a/lib/rules/require-meta-default-options.ts +++ b/lib/rules/require-meta-default-options.ts @@ -67,7 +67,7 @@ const rule: Rule.RuleModule = { } if (!metaDefaultOptions) { - metaNode && + if (metaNode) { context.report({ node: metaNode, messageId: 'missingDefaultOptions', @@ -78,6 +78,7 @@ const rule: Rule.RuleModule = { ); }, }); + } return {}; } @@ -93,7 +94,7 @@ const rule: Rule.RuleModule = { schemaProperty.type === 'ObjectExpression' && schemaProperty.properties .filter((property) => property.type === 'Property') - // @ts-expect-error + // @ts-expect-error -- Property 'name' does not exist on type 'ArrayExpression'.ts(2339) .find((property) => property.key.name === 'type')?.value.value === 'array'; diff --git a/lib/rules/require-meta-docs-url.ts b/lib/rules/require-meta-docs-url.ts index ae09e8f1..4a94764e 100644 --- a/lib/rules/require-meta-docs-url.ts +++ b/lib/rules/require-meta-docs-url.ts @@ -120,7 +120,7 @@ const rule: Rule.RuleModule = { // eslint-disable-next-line unicorn/no-negated-condition -- actually more clear like this messageId: !urlPropNode ? 'missing' - : // eslint-disable-next-line unicorn/no-nested-ternary,unicorn/no-negated-condition -- this is fine for now + : // eslint-disable-next-line unicorn/no-negated-condition -- this is fine for now !expectedUrl ? 'wrongType' : /* otherwise */ 'mismatch', diff --git a/lib/rules/require-meta-schema-description.ts b/lib/rules/require-meta-schema-description.ts index e4167bca..2ffb8eea 100644 --- a/lib/rules/require-meta-schema-description.ts +++ b/lib/rules/require-meta-schema-description.ts @@ -74,7 +74,7 @@ const rule: Rule.RuleModule = { continue; } - // @ts-expect-error + // @ts-expect-error == Property 'name' does not exist on type 'ClassExpression'.ts(2339) switch (key.name ?? key.value) { case 'description': { hadDescription = true; diff --git a/lib/rules/test-case-shorthand-strings.ts b/lib/rules/test-case-shorthand-strings.ts index 64352316..f88db02e 100644 --- a/lib/rules/test-case-shorthand-strings.ts +++ b/lib/rules/test-case-shorthand-strings.ts @@ -79,24 +79,29 @@ const rule: Rule.RuleModule = { let caseInfoFilter: (caseInfo: (typeof caseInfoList)[number]) => boolean; switch (shorthandOption) { - case 'as-needed': + case 'as-needed': { caseInfoFilter = (caseInfo) => !caseInfo.shorthand && !caseInfo.needsLongform; break; - case 'never': + } + case 'never': { caseInfoFilter = (caseInfo) => caseInfo.shorthand; break; - case 'consistent': + } + case 'consistent': { caseInfoFilter = isConsistent ? () => false : (caseInfo) => caseInfo.shorthand; break; - case 'consistent-as-needed': + } + case 'consistent-as-needed': { caseInfoFilter = (caseInfo) => caseInfo.shorthand === hasCaseNeedingLongform; break; - default: - return; // invalid option + } + default: { + return; + } // invalid option } caseInfoList.filter(caseInfoFilter).forEach((badCaseInfo) => { @@ -112,7 +117,7 @@ const rule: Rule.RuleModule = { badCaseInfo.node, badCaseInfo.shorthand ? `{code: ${sourceCode.getText(badCaseInfo.node)}}` - : // @ts-expect-error + : // @ts-expect-error -- Property 'properties' does not exist on type 'SimpleLiteral'.ts(2339) sourceCode.getText(badCaseInfo.node.properties[0].value), ); }, diff --git a/lib/utils.ts b/lib/utils.ts index 75355270..a4ad085d 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -12,7 +12,6 @@ import type { Identifier, MaybeNamedClassDeclaration, MaybeNamedFunctionDeclaration, - MemberExpression, ModuleDeclaration, Node, ObjectExpression, @@ -359,7 +358,7 @@ function getRuleExportsCJS( */ function findObjectPropertyValueByKeyName( obj: ObjectExpression, - keyName: String, + keyName: string, ): Property['value'] | undefined { const property = obj.properties.find( (prop) => diff --git a/package.json b/package.json index 0101ff80..6e1dd65d 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "@types/estree": "^1.0.8", "@types/lodash": "^4.17.18", "@types/node": "^20.19.0", - "@typescript-eslint/parser": "^8.38.0", - "@typescript-eslint/utils": "^8.38.0", + "@typescript-eslint/parser": "^8.39.0", + "@typescript-eslint/utils": "^8.39.0", "@vitest/coverage-istanbul": "^3.2.4", "eslint": "^9.31.0", "eslint-config-not-an-aardvark": "^2.1.0", @@ -83,6 +83,7 @@ "release-it": "^17.2.0", "tsup": "^8.5.0", "typescript": "^5.9.2", + "typescript-eslint": "^8.39.0", "vitest": "^3.2.4" }, "peerDependencies": { diff --git a/tests/lib/rule-setup.ts b/tests/lib/rule-setup.ts index dfa0fbba..9006b490 100644 --- a/tests/lib/rule-setup.ts +++ b/tests/lib/rule-setup.ts @@ -47,7 +47,7 @@ describe('rule setup is correct', () => { const file = readFileSync(filePath, 'utf8'); assert.ok( - file.includes("const rule: Rule.RuleModule"), + file.includes('const rule: Rule.RuleModule'), 'is defined as type RuleModule', ); }); @@ -68,7 +68,13 @@ describe('rule setup is correct', () => { }); it('should have documentation for all rules', () => { - const filePath = path.join(import.meta.dirname, '..', '..', 'docs', 'rules'); + const filePath = path.join( + import.meta.dirname, + '..', + '..', + 'docs', + 'rules', + ); const files = readdirSync(filePath); assert.deepStrictEqual( diff --git a/tests/lib/rules/require-meta-type.ts b/tests/lib/rules/require-meta-type.ts index a2326db2..645bf7ec 100644 --- a/tests/lib/rules/require-meta-type.ts +++ b/tests/lib/rules/require-meta-type.ts @@ -76,15 +76,13 @@ ruleTester.run('require-meta-type', rule, { `, 'module.exports = {};', // No rule. // No `create` function. - { - code: ` + ` const create = {}; module.exports = { meta: {}, create, }; `, - }, ], invalid: [ diff --git a/tests/lib/utils.ts b/tests/lib/utils.ts index 292e392d..143b6eee 100644 --- a/tests/lib/utils.ts +++ b/tests/lib/utils.ts @@ -36,9 +36,11 @@ type MockRuleInfo = { id?: { name: string }; type: string; }; - meta?: { - type: string; - } | undefined; + meta?: + | { + type: string; + } + | undefined; isNewStyle: boolean; }; @@ -1794,9 +1796,7 @@ describe('utils', () => { }, { code: `module.exports = { meta: FOO, create(context) {} };`, - getResult() { - return undefined; - }, // returns undefined + getResult() {}, // returns undefined }, { code: `module.exports = { create(context) {} };`, diff --git a/types/estree.d.ts b/types/estree.d.ts index 93339cfa..2c8d6462 100644 --- a/types/estree.d.ts +++ b/types/estree.d.ts @@ -1,4 +1,4 @@ -import { Program as EstreeProgram } from 'estree'; +import type { Expression, Identifier, Node } from 'estree'; /** * This file augments the `estree` types to include a couple of types that are not built-in to `estree` that we're using.