Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions docs/rules/no-property-in-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ 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 */

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
/* ... */
Expand All @@ -25,20 +22,17 @@ module.exports = {
return {
'ClassDeclaration, FunctionDeclaration'(node) {
if ('superClass' in node) {
console.log('This is a class declaration:', node);
// This is a class declaration
}
},
};
},
};
} satisfies Rule.RuleModule;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we're mixing commonjs syntax and TypeScript in this example, which is a bit odd. Can we choose one:

  • commonjs with the jsdoc types
  • or ESM TypeScript and include any imports for the types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely agree with you there. The docs are a bit all over the place, with different people writing them at different points in time. It would benefit from someone going through all of them, with a focus on making them consistent (which I'm also happy to do). I don't think that's really in the scope of this change, though, which is just focused on enabling the eslint plugin. I had to touch this file, to address a violation. Would you prefer I change it to js here, and remove the satisfies?

Copy link
Member

@bmish bmish Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving it as cjs would be best then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. I changed the examples to js and put the type annotation back in.

```

Examples of **correct** code for this rule:

```ts
/* eslint eslint-plugin/no-property-in-node: error */

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
/* ... */
Expand All @@ -47,12 +41,12 @@ module.exports = {
return {
'ClassDeclaration, FunctionDeclaration'(node) {
if (node.type === 'ClassDeclaration') {
console.log('This is a class declaration:', node);
// This is a class declaration;
}
},
};
},
};
} satisfies Rule.RuleModule;
```

## Options
Expand Down
21 changes: 15 additions & 6 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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',
Expand All @@ -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,
Expand All @@ -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',
Expand Down
20 changes: 5 additions & 15 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfigName, (rule: Rule.RuleModule) => boolean> = {
const configFilters: Record<string, (rule: Rule.RuleModule) => boolean> = {
all: (rule: Rule.RuleModule) =>
!(
rule.meta?.docs &&
Expand All @@ -75,7 +65,7 @@ const configFilters: Record<ConfigName, (rule: Rule.RuleModule) => 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 {
Expand Down
9 changes: 3 additions & 6 deletions lib/rules/no-meta-schema-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-missing-message-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-missing-placeholders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
12 changes: 8 additions & 4 deletions lib/rules/no-only-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
) {
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-unused-message-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-unused-placeholders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 1 addition & 8 deletions lib/rules/no-useless-token-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
5 changes: 3 additions & 2 deletions lib/rules/require-meta-default-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const rule: Rule.RuleModule = {
}

if (!metaDefaultOptions) {
metaNode &&
if (metaNode) {
context.report({
node: metaNode,
messageId: 'missingDefaultOptions',
Expand All @@ -78,6 +78,7 @@ const rule: Rule.RuleModule = {
);
},
});
}
return {};
}

Expand All @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion lib/rules/require-meta-docs-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/require-meta-schema-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 12 additions & 7 deletions lib/rules/test-case-shorthand-strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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),
);
},
Expand Down
3 changes: 1 addition & 2 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {
Identifier,
MaybeNamedClassDeclaration,
MaybeNamedFunctionDeclaration,
MemberExpression,
ModuleDeclaration,
Node,
ObjectExpression,
Expand Down Expand Up @@ -359,7 +358,7 @@ function getRuleExportsCJS(
*/
function findObjectPropertyValueByKeyName(
obj: ObjectExpression,
keyName: String,
keyName: string,
): Property['value'] | undefined {
const property = obj.properties.find(
(prop) =>
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
10 changes: 8 additions & 2 deletions tests/lib/rule-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
});
Expand All @@ -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(
Expand Down
Loading