From 5c83d70ded937fcc83cbbdd65c6061d9b001a5d0 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:06:58 +1200 Subject: [PATCH 1/8] fix: introduce utilities for v9 compatability --- lib/utils/compat.ts | 84 +++++++++++++++++++++++++++++++++++++++++++++ lib/utils/index.ts | 1 + 2 files changed, 85 insertions(+) create mode 100644 lib/utils/compat.ts diff --git a/lib/utils/compat.ts b/lib/utils/compat.ts new file mode 100644 index 00000000..8fb34608 --- /dev/null +++ b/lib/utils/compat.ts @@ -0,0 +1,84 @@ +import { type TSESLint, type TSESTree } from '@typescript-eslint/utils'; + +declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + export interface RuleContext<TMessageIds extends string, TOptions extends readonly unknown[]> { + /** + * The filename associated with the source. + */ + filename: string; + + /** + * A SourceCode object that you can use to work with the source that + * was passed to ESLint. + */ + sourceCode: Readonly<TSESLint.SourceCode>; + } +} + +declare module '@typescript-eslint/utils/dist/ts-eslint/SourceCode' { + export interface SourceCode { + /** + * Returns the scope of the given node. + * This information can be used track references to variables. + * @since 8.37.0 + */ + getScope(node: TSESTree.Node): TSESLint.Scope.Scope; + /** + * Returns an array of the ancestors of the given node, starting at + * the root of the AST and continuing through the direct parent of the current node. + * This array does not include the currently-traversed node itself. + * @since 8.38.0 + */ + getAncestors(node: TSESTree.Node): TSESTree.Node[]; + /** + * Returns a list of variables declared by the given node. + * This information can be used to track references to variables. + * @since 8.38.0 + */ + getDeclaredVariables( + node: TSESTree.Node, + ): readonly TSESLint.Scope.Variable[]; + } +} + +/* istanbul ignore next */ +export const getFilename = ( + context: TSESLint.RuleContext<string, unknown[]>, +) => { + return context.filename ?? context.getFilename(); +}; + +/* istanbul ignore next */ +export const getSourceCode = ( + context: TSESLint.RuleContext<string, unknown[]>, +) => { + return context.sourceCode ?? context.getSourceCode(); +}; + +/* istanbul ignore next */ +export const getScope = ( + context: TSESLint.RuleContext<string, unknown[]>, + node: TSESTree.Node, +) => { + return getSourceCode(context).getScope?.(node) ?? context.getScope(); +}; + +/* istanbul ignore next */ +export const getAncestors = ( + context: TSESLint.RuleContext<string, unknown[]>, + node: TSESTree.Node, +) => { + return getSourceCode(context).getAncestors?.(node) ?? context.getAncestors(); +}; + +/* istanbul ignore next */ +export const getDeclaredVariables = ( + context: TSESLint.RuleContext<string, unknown[]>, + node: TSESTree.Node, +) => { + return ( + getSourceCode(context).getDeclaredVariables?.(node) ?? + context.getDeclaredVariables(node) + ); +}; diff --git a/lib/utils/index.ts b/lib/utils/index.ts index b0ed31f7..cb0e8e03 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -1,3 +1,4 @@ +export * from './compat'; export * from './file-import'; export * from './types'; From 15e4f44bdb27137e2250db2f36a2d3d4477c7a0d Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:12:01 +1200 Subject: [PATCH 2/8] fix: replace uses of `context.getFilename` --- lib/rules/consistent-data-testid.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rules/consistent-data-testid.ts b/lib/rules/consistent-data-testid.ts index 2536706e..f8b7ff6e 100644 --- a/lib/rules/consistent-data-testid.ts +++ b/lib/rules/consistent-data-testid.ts @@ -1,5 +1,6 @@ import { createTestingLibraryRule } from '../create-testing-library-rule'; import { isJSXAttribute, isLiteral } from '../node-utils'; +import { getFilename } from '../utils'; export const RULE_NAME = 'consistent-data-testid'; export type MessageIds = @@ -77,11 +78,10 @@ export default createTestingLibraryRule<Options, MessageIds>({ }, create: (context, [options]) => { - const { getFilename } = context; const { testIdPattern, testIdAttribute: attr, customMessage } = options; function getFileNameData() { - const splitPath = getFilename().split('/'); + const splitPath = getFilename(context).split('/'); const fileNameWithExtension = splitPath.pop() ?? ''; if ( fileNameWithExtension.includes('[') || From 142bb23be3e62822f8fbc4c129ee94195409fdd9 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:12:23 +1200 Subject: [PATCH 3/8] fix: replace uses of `context.getSourceCode` --- lib/rules/prefer-find-by.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index f9d951e0..632790a2 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -9,6 +9,7 @@ import { isObjectPattern, isProperty, } from '../node-utils'; +import { getSourceCode } from '../utils'; export const RULE_NAME = 'prefer-find-by'; export type MessageIds = 'preferFindBy'; @@ -69,7 +70,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ defaultOptions: [], create(context, _, helpers) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); /** * Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario From 49449033a90ac5a70b218106511abcba3c95e798 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:15:46 +1200 Subject: [PATCH 4/8] fix: replace uses of `context.getScope` --- lib/node-utils/index.ts | 4 +++- lib/rules/no-promise-in-fire-event.ts | 3 ++- lib/rules/prefer-find-by.ts | 28 +++++++++++++++------------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index 0f748c5c..a47dd642 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -6,6 +6,8 @@ import { TSESTree, } from '@typescript-eslint/utils'; +import { getScope } from '../utils'; + import { isArrayExpression, isArrowFunctionExpression, @@ -305,7 +307,7 @@ export function getInnermostFunctionScope( asyncQueryNode: TSESTree.Identifier ): InnermostFunctionScope | null { const innermostScope = ASTUtils.getInnermostScope( - context.getScope(), + getScope(context, asyncQueryNode), asyncQueryNode ); diff --git a/lib/rules/no-promise-in-fire-event.ts b/lib/rules/no-promise-in-fire-event.ts index c3c7eb4d..f10bfc77 100644 --- a/lib/rules/no-promise-in-fire-event.ts +++ b/lib/rules/no-promise-in-fire-event.ts @@ -8,6 +8,7 @@ import { isNewExpression, isPromiseIdentifier, } from '../node-utils'; +import { getScope } from '../utils'; export const RULE_NAME = 'no-promise-in-fire-event'; export type MessageIds = 'noPromiseInFireEvent'; @@ -76,7 +77,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ if (ASTUtils.isIdentifier(node)) { const nodeVariable = ASTUtils.findVariable( - context.getScope(), + getScope(context, node), node.name ); if (!nodeVariable) { diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 632790a2..7e48993b 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -9,7 +9,7 @@ import { isObjectPattern, isProperty, } from '../node-utils'; -import { getSourceCode } from '../utils'; +import { getScope, getSourceCode } from '../utils'; export const RULE_NAME = 'prefer-find-by'; export type MessageIds = 'preferFindBy'; @@ -119,7 +119,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ isCallExpression(node.body.callee.object.arguments[0]) && ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) ) { - return node.body.callee.object.arguments[0].callee.name; + return node.body.callee.object.arguments[0].callee; } if (!ASTUtils.isIdentifier(node.body.callee.property)) { @@ -135,7 +135,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ node.body.callee.object.arguments[0].callee.property ) ) { - return node.body.callee.object.arguments[0].callee.property.name; + return node.body.callee.object.arguments[0].callee.property; } // expect(screen.getByText).not shape @@ -150,7 +150,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ node.body.callee.object.object.arguments[0].callee.property ) ) { - return node.body.callee.object.object.arguments[0].callee.property.name; + return node.body.callee.object.object.arguments[0].callee.property; } // expect(getByText).not shape @@ -162,10 +162,10 @@ export default createTestingLibraryRule<Options, MessageIds>({ node.body.callee.object.object.arguments[0].callee ) ) { - return node.body.callee.object.object.arguments[0].callee.name; + return node.body.callee.object.object.arguments[0].callee; } - return node.body.callee.property.name; + return node.body.callee.property; } function getWrongQueryName(node: TSESTree.ArrowFunctionExpression) { @@ -178,7 +178,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ ASTUtils.isIdentifier(node.body.callee) && helpers.isSyncQuery(node.body.callee) ) { - return node.body.callee.name; + return node.body.callee; } return getWrongQueryNameInAssertion(node); @@ -354,12 +354,14 @@ export default createTestingLibraryRule<Options, MessageIds>({ } // shape of () => screen.getByText - const fullQueryMethod = getWrongQueryName(argument); + const fullQueryMethodNode = getWrongQueryName(argument); - if (!fullQueryMethod) { + if (!fullQueryMethodNode) { return; } + const fullQueryMethod = fullQueryMethodNode.name; + // if there is a second argument to AwaitExpression, it is the options const waitOptions = node.arguments[1]; let waitOptionsSourceCode = ''; @@ -401,12 +403,14 @@ export default createTestingLibraryRule<Options, MessageIds>({ } // shape of () => getByText - const fullQueryMethod = getWrongQueryName(argument); + const fullQueryMethodNode = getWrongQueryName(argument); - if (!fullQueryMethod) { + if (!fullQueryMethodNode) { return; } + const fullQueryMethod = fullQueryMethodNode.name; + const queryMethod = fullQueryMethod.split('By')[1]; const queryVariant = getFindByQueryVariant(fullQueryMethod); const callArguments = getQueryArguments(argument.body); @@ -435,7 +439,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ // this adds the findBy* declaration - adding it to the list of destructured variables { findBy* } = render() const definition = findRenderDefinitionDeclaration( - context.getScope(), + getScope(context, fullQueryMethodNode), fullQueryMethod ); // I think it should always find it, otherwise code should not be valid (it'd be using undeclared variables) From 8cc367621d46a7d4bdba2217cedf780f6091f113 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:17:28 +1200 Subject: [PATCH 5/8] fix: replace uses of `context.getDeclaredVariables` --- lib/node-utils/index.ts | 4 ++-- lib/rules/no-debugging-utils.ts | 4 ++-- lib/rules/no-manual-cleanup.ts | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index a47dd642..9e9e9b04 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -6,7 +6,7 @@ import { TSESTree, } from '@typescript-eslint/utils'; -import { getScope } from '../utils'; +import { getDeclaredVariables, getScope } from '../utils'; import { isArrayExpression, @@ -289,7 +289,7 @@ export function getVariableReferences( ): TSESLint.Scope.Reference[] { if (ASTUtils.isVariableDeclarator(node)) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return context.getDeclaredVariables(node)[0]?.references?.slice(1) ?? []; + return getDeclaredVariables(context, node)[0]?.references?.slice(1) ?? []; } return []; diff --git a/lib/rules/no-debugging-utils.ts b/lib/rules/no-debugging-utils.ts index d9ed3040..3978fbde 100644 --- a/lib/rules/no-debugging-utils.ts +++ b/lib/rules/no-debugging-utils.ts @@ -11,7 +11,7 @@ import { isObjectPattern, isProperty, } from '../node-utils'; -import { DEBUG_UTILS } from '../utils'; +import { DEBUG_UTILS, getDeclaredVariables } from '../utils'; type DebugUtilsToCheckForConfig = Record<(typeof DEBUG_UTILS)[number], boolean>; type DebugUtilsToCheckFor = Partial<DebugUtilsToCheckForConfig>; @@ -175,7 +175,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ const isVariableFromBuiltInConsole = builtInConsoleNodes.some( (variableDeclarator) => { - const variables = context.getDeclaredVariables(variableDeclarator); + const variables = getDeclaredVariables(context, variableDeclarator); return variables.some( ({ name }) => name === callExpressionIdentifier.name && diff --git a/lib/rules/no-manual-cleanup.ts b/lib/rules/no-manual-cleanup.ts index 9074d669..ee65ee71 100644 --- a/lib/rules/no-manual-cleanup.ts +++ b/lib/rules/no-manual-cleanup.ts @@ -12,6 +12,7 @@ import { isObjectPattern, isProperty, } from '../node-utils'; +import { getDeclaredVariables } from '../utils'; export const RULE_NAME = 'no-manual-cleanup'; export type MessageIds = 'noManualCleanup'; @@ -65,7 +66,7 @@ export default createTestingLibraryRule<Options, MessageIds>({ if (isImportDeclaration(moduleNode)) { // case: import utils from 'testing-library-module' if (isImportDefaultSpecifier(moduleNode.specifiers[0])) { - const { references } = context.getDeclaredVariables(moduleNode)[0]; + const { references } = getDeclaredVariables(context, moduleNode)[0]; reportImportReferences(references); } From 83538d31ea5dcac482ee528852a8ef1bdcee3dd5 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:37:59 +1200 Subject: [PATCH 6/8] refactor: remove unneeded compat utility --- lib/utils/compat.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/utils/compat.ts b/lib/utils/compat.ts index 8fb34608..a8068cea 100644 --- a/lib/utils/compat.ts +++ b/lib/utils/compat.ts @@ -64,14 +64,6 @@ export const getScope = ( return getSourceCode(context).getScope?.(node) ?? context.getScope(); }; -/* istanbul ignore next */ -export const getAncestors = ( - context: TSESLint.RuleContext<string, unknown[]>, - node: TSESTree.Node, -) => { - return getSourceCode(context).getAncestors?.(node) ?? context.getAncestors(); -}; - /* istanbul ignore next */ export const getDeclaredVariables = ( context: TSESLint.RuleContext<string, unknown[]>, From 10705729e8322fd19d5cbeec2b2e0bf6140425a8 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 07:38:22 +1200 Subject: [PATCH 7/8] refactor: apply prettier --- lib/utils/compat.ts | 99 +++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/lib/utils/compat.ts b/lib/utils/compat.ts index a8068cea..0520d843 100644 --- a/lib/utils/compat.ts +++ b/lib/utils/compat.ts @@ -1,76 +1,79 @@ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils'; declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - export interface RuleContext<TMessageIds extends string, TOptions extends readonly unknown[]> { - /** - * The filename associated with the source. - */ - filename: string; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + export interface RuleContext< + TMessageIds extends string, + TOptions extends readonly unknown[], + > { + /** + * The filename associated with the source. + */ + filename: string; - /** - * A SourceCode object that you can use to work with the source that - * was passed to ESLint. - */ - sourceCode: Readonly<TSESLint.SourceCode>; - } + /** + * A SourceCode object that you can use to work with the source that + * was passed to ESLint. + */ + sourceCode: Readonly<TSESLint.SourceCode>; + } } declare module '@typescript-eslint/utils/dist/ts-eslint/SourceCode' { - export interface SourceCode { - /** - * Returns the scope of the given node. - * This information can be used track references to variables. - * @since 8.37.0 - */ - getScope(node: TSESTree.Node): TSESLint.Scope.Scope; - /** - * Returns an array of the ancestors of the given node, starting at - * the root of the AST and continuing through the direct parent of the current node. - * This array does not include the currently-traversed node itself. - * @since 8.38.0 - */ - getAncestors(node: TSESTree.Node): TSESTree.Node[]; - /** - * Returns a list of variables declared by the given node. - * This information can be used to track references to variables. - * @since 8.38.0 - */ - getDeclaredVariables( - node: TSESTree.Node, - ): readonly TSESLint.Scope.Variable[]; - } + export interface SourceCode { + /** + * Returns the scope of the given node. + * This information can be used track references to variables. + * @since 8.37.0 + */ + getScope(node: TSESTree.Node): TSESLint.Scope.Scope; + /** + * Returns an array of the ancestors of the given node, starting at + * the root of the AST and continuing through the direct parent of the current node. + * This array does not include the currently-traversed node itself. + * @since 8.38.0 + */ + getAncestors(node: TSESTree.Node): TSESTree.Node[]; + /** + * Returns a list of variables declared by the given node. + * This information can be used to track references to variables. + * @since 8.38.0 + */ + getDeclaredVariables( + node: TSESTree.Node + ): readonly TSESLint.Scope.Variable[]; + } } /* istanbul ignore next */ export const getFilename = ( - context: TSESLint.RuleContext<string, unknown[]>, + context: TSESLint.RuleContext<string, unknown[]> ) => { - return context.filename ?? context.getFilename(); + return context.filename ?? context.getFilename(); }; /* istanbul ignore next */ export const getSourceCode = ( - context: TSESLint.RuleContext<string, unknown[]>, + context: TSESLint.RuleContext<string, unknown[]> ) => { - return context.sourceCode ?? context.getSourceCode(); + return context.sourceCode ?? context.getSourceCode(); }; /* istanbul ignore next */ export const getScope = ( - context: TSESLint.RuleContext<string, unknown[]>, - node: TSESTree.Node, + context: TSESLint.RuleContext<string, unknown[]>, + node: TSESTree.Node ) => { - return getSourceCode(context).getScope?.(node) ?? context.getScope(); + return getSourceCode(context).getScope?.(node) ?? context.getScope(); }; /* istanbul ignore next */ export const getDeclaredVariables = ( - context: TSESLint.RuleContext<string, unknown[]>, - node: TSESTree.Node, + context: TSESLint.RuleContext<string, unknown[]>, + node: TSESTree.Node ) => { - return ( - getSourceCode(context).getDeclaredVariables?.(node) ?? - context.getDeclaredVariables(node) - ); + return ( + getSourceCode(context).getDeclaredVariables?.(node) ?? + context.getDeclaredVariables(node) + ); }; From 88ecc281b31a1773608082200c88a78c758ab368 Mon Sep 17 00:00:00 2001 From: Gareth Jones <jones258@gmail.com> Date: Thu, 15 Aug 2024 08:47:32 +1200 Subject: [PATCH 8/8] chore: adjust eslint inline disables --- lib/utils/compat.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/compat.ts b/lib/utils/compat.ts index 0520d843..2216dd8d 100644 --- a/lib/utils/compat.ts +++ b/lib/utils/compat.ts @@ -1,9 +1,10 @@ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils'; declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { - // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface RuleContext< + // eslint-disable-next-line @typescript-eslint/no-unused-vars TMessageIds extends string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars TOptions extends readonly unknown[], > { /**