diff --git a/lib/rules/no-property-in-node.js b/lib/rules/no-property-in-node.js index bd974af1..1f6563f6 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,33 @@ 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.', + 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 +98,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', + }, + ], + }, ], });