From f6809950f11d47a1587ba8dac5dd8b502f25bca0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 23 Jun 2024 14:34:20 -0400 Subject: [PATCH 1/2] feat: [no-property-in-node] add additionalNodeTypeFiles option --- lib/rules/no-property-in-node.js | 30 +++++++++++++++++--- tests/lib/rules/no-property-in-node.js | 39 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-property-in-node.js b/lib/rules/no-property-in-node.js index bd974af1..f3ac0e72 100644 --- a/lib/rules/no-property-in-node.js +++ b/lib/rules/no-property-in-node.js @@ -1,6 +1,6 @@ 'use strict'; -const typedNodeSourceFileTesters = [ +const defaultTypedNodeSourceFileTesters = [ /@types[/\\]estree[/\\]index\.d\.ts/, /@typescript-eslint[/\\]types[/\\]dist[/\\]generated[/\\]ast-spec\.d\.ts/, ]; @@ -26,9 +26,10 @@ const typedNodeSourceFileTesters = [ * ``` * * @param {import('typescript').Type} type + * @param {RegExp[]} typedNodeSourceFileTesters * @returns Whether the type seems to include a known ESTree or TSESTree AST node. */ -function isAstNodeType(type) { +function isAstNodeType(type, typedNodeSourceFileTesters) { return (type.types || [type]) .filter((typePart) => typePart.getProperty('type')) .flatMap( @@ -55,13 +56,34 @@ module.exports = { requiresTypeChecking: true, url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-property-in-node.md', }, - schema: [], + schema: [ + { + type: 'object', + properties: { + additionalNodeTypeFiles: { + description: + 'Any additional regular expressions to consider source files defining AST Node types.', + default: '^(enforce|require|disallow)', + elements: { type: 'string' }, + type: 'array', + }, + }, + additionalProperties: false, + }, + ], messages: { in: 'Prefer checking specific node properties instead of a broad `in`.', }, }, create(context) { + const typedNodeSourceFileTesters = [ + ...defaultTypedNodeSourceFileTesters, + ...(context.options[0]?.additionalNodeTypeFiles?.map( + (filePath) => new RegExp(filePath), + ) ?? []), + ]; + return { 'BinaryExpression[operator=in]'(node) { // TODO: Switch this to ESLintUtils.getParserServices with typescript-eslint@>=6 @@ -77,7 +99,7 @@ module.exports = { const tsNode = services.esTreeNodeToTSNodeMap.get(node.right); const type = checker.getTypeAtLocation(tsNode); - if (isAstNodeType(type)) { + if (isAstNodeType(type, typedNodeSourceFileTesters)) { context.report({ messageId: 'in', node }); } }, diff --git a/tests/lib/rules/no-property-in-node.js b/tests/lib/rules/no-property-in-node.js index bc5387c1..62181a3b 100644 --- a/tests/lib/rules/no-property-in-node.js +++ b/tests/lib/rules/no-property-in-node.js @@ -93,6 +93,21 @@ ruleTester.run('no-property-in-node', rule, { }, }; `, + { + code: ` + interface Node { + type: string; + } + declare const node: Node; + 'a' in node; + export {}; + `, + options: [ + { + additionalNodeTypeFiles: [/not-found/], + }, + ], + }, ], invalid: [ { @@ -163,5 +178,29 @@ ruleTester.run('no-property-in-node', rule, { }, ], }, + { + code: ` + interface Node { + type: string; + } + declare const node: Node; + 'a' in node; + export {}; + `, + options: [ + { + additionalNodeTypeFiles: [/lib[/\\]fixtures[/\\]estree\.ts/], + }, + ], + errors: [ + { + column: 9, + line: 6, + endColumn: 20, + endLine: 6, + messageId: 'in', + }, + ], + }, ], }); From 594d084b8e5d0e4826cf1516a677068bcad90b08 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Jun 2024 13:28:59 -0400 Subject: [PATCH 2/2] Remove default --- lib/rules/no-property-in-node.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rules/no-property-in-node.js b/lib/rules/no-property-in-node.js index f3ac0e72..1f6563f6 100644 --- a/lib/rules/no-property-in-node.js +++ b/lib/rules/no-property-in-node.js @@ -63,7 +63,6 @@ module.exports = { additionalNodeTypeFiles: { description: 'Any additional regular expressions to consider source files defining AST Node types.', - default: '^(enforce|require|disallow)', elements: { type: 'string' }, type: 'array', },