diff --git a/package.json b/package.json index df85983fd..218cb5ef8 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ ] }, "dependencies": { - "@typescript-eslint/utils": "^5.10.0" + "@typescript-eslint/utils": "^6.0.0" }, "devDependencies": { "@babel/cli": "^7.4.4", diff --git a/src/index.ts b/src/index.ts index 2396738e6..569ae64b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { readdirSync } from 'fs'; import { join, parse } from 'path'; -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint } from '@typescript-eslint/utils'; import { name as packageName, version as packageVersion, @@ -12,39 +12,6 @@ type RuleModule = TSESLint.RuleModule & { meta: Required, 'docs'>>; }; -// v5 of `@typescript-eslint/experimental-utils` removed this -declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { - export interface RuleMetaDataDocs { - category: 'Best Practices' | 'Possible Errors'; - } -} - -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[]; - } -} - // copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606 /* istanbul ignore next */ const interopRequireDefault = (obj: any): { default: any } => diff --git a/src/rules/__tests__/unbound-method.test.ts b/src/rules/__tests__/unbound-method.test.ts index c3484049d..1a926f1ba 100644 --- a/src/rules/__tests__/unbound-method.test.ts +++ b/src/rules/__tests__/unbound-method.test.ts @@ -1,5 +1,6 @@ import path from 'path'; -import { ESLintUtils, type TSESLint } from '@typescript-eslint/utils'; +import { version as rawTypeScriptESLintPluginVersion } from '@typescript-eslint/eslint-plugin/package.json'; +import { TSESLint } from '@typescript-eslint/utils'; import dedent from 'dedent'; import type { MessageIds, Options } from '../unbound-method'; @@ -9,8 +10,8 @@ function getFixturesRootDir(): string { const rootPath = getFixturesRootDir(); -const ruleTester = new ESLintUtils.RuleTester({ - parser: '@typescript-eslint/parser', +const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), parserOptions: { sourceType: 'module', tsconfigRootDir: rootPath, @@ -18,6 +19,26 @@ const ruleTester = new ESLintUtils.RuleTester({ }, }); +const fixtureFilename = path.join(rootPath, 'file.ts'); + +const withFixtureFilename = < + T extends Array< + | (TSESLint.ValidTestCase | string) + | TSESLint.InvalidTestCase + >, +>( + cases: T, +): T extends Array> + ? Array> + : Array> => { + // @ts-expect-error this is fine, and will go away later once we upgrade + return cases.map(code => { + const test = typeof code === 'string' ? { code } : code; + + return { filename: fixtureFilename, ...test }; + }); +}; + const ConsoleClassAndVariableCode = dedent` class Console { log(str) { @@ -164,8 +185,8 @@ describe('error handling', () => { }); describe('when @typescript-eslint/eslint-plugin is not available', () => { - const ruleTester = new ESLintUtils.RuleTester({ - parser: '@typescript-eslint/parser', + const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), parserOptions: { sourceType: 'module', tsconfigRootDir: rootPath, @@ -177,7 +198,9 @@ describe('error handling', () => { 'unbound-method jest edition without type service', requireRule(true), { - valid: validTestCases.concat(invalidTestCases.map(({ code }) => code)), + valid: withFixtureFilename( + validTestCases.concat(invalidTestCases.map(({ code }) => code)), + ), invalid: [], }, ); @@ -185,8 +208,8 @@ describe('error handling', () => { }); ruleTester.run('unbound-method jest edition', requireRule(false), { - valid: validTestCases, - invalid: invalidTestCases, + valid: withFixtureFilename(validTestCases), + invalid: withFixtureFilename(invalidTestCases), }); function addContainsMethodsClass(code: string): string { @@ -225,11 +248,14 @@ function addContainsMethodsClassInvalid( } ruleTester.run('unbound-method', requireRule(false), { - valid: [ + valid: withFixtureFilename([ 'Promise.resolve().then(console.log);', "['1', '2', '3'].map(Number.parseInt);", '[5.2, 7.1, 3.6].map(Math.floor);', 'const x = console.log;', + ...(parseInt(rawTypeScriptESLintPluginVersion.split('.')[0], 10) >= 6 + ? ['const x = Object.defineProperty;'] + : []), ...[ 'instance.bound();', 'instance.unbound();', @@ -455,8 +481,8 @@ class OtherClass extends BaseClass { const oc = new OtherClass(); oc.superLogThis(); `, - ], - invalid: [ + ]), + invalid: withFixtureFilename([ { code: ` class Console { @@ -762,5 +788,59 @@ class OtherClass extends BaseClass { }, ], }, - ], + { + code: ` +const values = { + a() {}, + b: () => {}, +}; + +const { a, b } = values; + `, + errors: [ + { + line: 7, + column: 9, + endColumn: 10, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, + { + code: ` +const values = { + a() {}, + b: () => {}, +}; + +const { a: c } = values; + `, + errors: [ + { + line: 7, + column: 9, + endColumn: 10, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, + { + code: ` +const values = { + a() {}, + b: () => {}, +}; + +const { b, a } = values; + `, + errors: [ + { + line: 7, + column: 12, + endColumn: 13, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, + ]), }); diff --git a/src/rules/consistent-test-it.ts b/src/rules/consistent-test-it.ts index 3fcd254e4..e0a68daaf 100644 --- a/src/rules/consistent-test-it.ts +++ b/src/rules/consistent-test-it.ts @@ -35,9 +35,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforce `test` and `it` usage conventions', - recommended: false, }, fixable: 'code', messages: { @@ -51,9 +49,11 @@ export default createRule< type: 'object', properties: { fn: { + type: 'string', enum: [TestCaseName.it, TestCaseName.test], }, withinDescribe: { + type: 'string', enum: [TestCaseName.it, TestCaseName.test], }, }, diff --git a/src/rules/expect-expect.ts b/src/rules/expect-expect.ts index 4bdd815cd..c4ec8198e 100644 --- a/src/rules/expect-expect.ts +++ b/src/rules/expect-expect.ts @@ -54,9 +54,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforce assertion to be made in a test body', - recommended: 'warn', }, messages: { noAssertions: 'Test has no assertions', diff --git a/src/rules/max-expects.ts b/src/rules/max-expects.ts index 3cc20e105..ce62a2bc1 100644 --- a/src/rules/max-expects.ts +++ b/src/rules/max-expects.ts @@ -10,9 +10,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforces a maximum number assertion calls in a test body', - recommended: false, }, messages: { exceededMaxAssertion: diff --git a/src/rules/max-nested-describe.ts b/src/rules/max-nested-describe.ts index b807a4591..31f603704 100644 --- a/src/rules/max-nested-describe.ts +++ b/src/rules/max-nested-describe.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforces a maximum depth to nested describe calls', - recommended: false, }, messages: { exceededMaxDepth: diff --git a/src/rules/no-alias-methods.ts b/src/rules/no-alias-methods.ts index dfd4a9629..e8c9f3b0f 100644 --- a/src/rules/no-alias-methods.ts +++ b/src/rules/no-alias-methods.ts @@ -9,9 +9,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow alias methods', - recommended: 'error', }, messages: { replaceAlias: `Replace {{ alias }}() with its canonical name of {{ canonical }}()`, diff --git a/src/rules/no-commented-out-tests.ts b/src/rules/no-commented-out-tests.ts index 1949c3fc4..cf09618f6 100644 --- a/src/rules/no-commented-out-tests.ts +++ b/src/rules/no-commented-out-tests.ts @@ -11,9 +11,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow commented out tests', - recommended: 'warn', }, messages: { commentedTests: 'Some tests seem to be commented', diff --git a/src/rules/no-conditional-expect.ts b/src/rules/no-conditional-expect.ts index 519c23523..31e7d3ada 100644 --- a/src/rules/no-conditional-expect.ts +++ b/src/rules/no-conditional-expect.ts @@ -20,8 +20,6 @@ export default createRule({ meta: { docs: { description: 'Disallow calling `expect` conditionally', - category: 'Best Practices', - recommended: 'error', }, messages: { conditionalExpect: 'Avoid calling `expect` conditionally`', diff --git a/src/rules/no-conditional-in-test.ts b/src/rules/no-conditional-in-test.ts index 6a58a4f77..0338fdf37 100644 --- a/src/rules/no-conditional-in-test.ts +++ b/src/rules/no-conditional-in-test.ts @@ -6,8 +6,6 @@ export default createRule({ meta: { docs: { description: 'Disallow conditional logic in tests', - category: 'Best Practices', - recommended: false, }, messages: { conditionalInTest: 'Avoid having conditionals in tests', diff --git a/src/rules/no-confusing-set-timeout.ts b/src/rules/no-confusing-set-timeout.ts index 52bb29606..f295a36fb 100644 --- a/src/rules/no-confusing-set-timeout.ts +++ b/src/rules/no-confusing-set-timeout.ts @@ -18,9 +18,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow confusing usages of jest.setTimeout', - recommended: false, }, messages: { globalSetTimeout: '`jest.setTimeout` should be call in `global` scope', diff --git a/src/rules/no-deprecated-functions.ts b/src/rules/no-deprecated-functions.ts index f41d65513..d83323d47 100644 --- a/src/rules/no-deprecated-functions.ts +++ b/src/rules/no-deprecated-functions.ts @@ -28,9 +28,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow use of deprecated functions', - recommended: 'error', }, messages: { deprecatedFunction: diff --git a/src/rules/no-disabled-tests.ts b/src/rules/no-disabled-tests.ts index af0c7547e..70d3ef180 100644 --- a/src/rules/no-disabled-tests.ts +++ b/src/rules/no-disabled-tests.ts @@ -10,9 +10,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow disabled tests', - recommended: 'warn', }, messages: { missingFunction: 'Test is missing function argument', diff --git a/src/rules/no-done-callback.ts b/src/rules/no-done-callback.ts index a227987c4..7647ec368 100644 --- a/src/rules/no-done-callback.ts +++ b/src/rules/no-done-callback.ts @@ -37,9 +37,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow using a callback in asynchronous tests and hooks', - recommended: 'error', }, messages: { noDoneCallback: diff --git a/src/rules/no-duplicate-hooks.ts b/src/rules/no-duplicate-hooks.ts index b47db957b..7305ad459 100644 --- a/src/rules/no-duplicate-hooks.ts +++ b/src/rules/no-duplicate-hooks.ts @@ -4,9 +4,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow duplicate setup and teardown hooks', - recommended: false, }, messages: { noDuplicateHook: 'Duplicate {{hook}} in describe block', diff --git a/src/rules/no-export.ts b/src/rules/no-export.ts index bd94acfbd..c1cbe0a80 100644 --- a/src/rules/no-export.ts +++ b/src/rules/no-export.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow using `exports` in files containing tests', - recommended: 'error', }, messages: { unexpectedExport: `Do not export from a test file`, diff --git a/src/rules/no-focused-tests.ts b/src/rules/no-focused-tests.ts index 0f740d94c..3d7d2ffd3 100644 --- a/src/rules/no-focused-tests.ts +++ b/src/rules/no-focused-tests.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow focused tests', - recommended: 'error', }, messages: { focusedTest: 'Unexpected focused test', diff --git a/src/rules/no-hooks.ts b/src/rules/no-hooks.ts index 7b08ef71c..88ddfae4d 100644 --- a/src/rules/no-hooks.ts +++ b/src/rules/no-hooks.ts @@ -7,9 +7,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow setup and teardown hooks', - recommended: false, }, messages: { unexpectedHook: "Unexpected '{{ hookName }}' hook", @@ -20,6 +18,7 @@ export default createRule< properties: { allow: { type: 'array', + // @ts-expect-error https://github.com/eslint/eslint/discussions/17573 contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'], }, }, diff --git a/src/rules/no-identical-title.ts b/src/rules/no-identical-title.ts index b8d6f7227..b2f26629e 100644 --- a/src/rules/no-identical-title.ts +++ b/src/rules/no-identical-title.ts @@ -21,9 +21,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow identical titles', - recommended: 'error', }, messages: { multipleTestTitle: diff --git a/src/rules/no-interpolation-in-snapshots.ts b/src/rules/no-interpolation-in-snapshots.ts index 4b7d87daf..1961d0db4 100644 --- a/src/rules/no-interpolation-in-snapshots.ts +++ b/src/rules/no-interpolation-in-snapshots.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow string interpolation inside snapshots', - recommended: 'error', }, messages: { noInterpolation: 'Do not use string interpolation inside of snapshots', diff --git a/src/rules/no-jasmine-globals.ts b/src/rules/no-jasmine-globals.ts index 89c51ba30..6d0d122dd 100644 --- a/src/rules/no-jasmine-globals.ts +++ b/src/rules/no-jasmine-globals.ts @@ -11,9 +11,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow Jasmine globals', - recommended: 'error', }, messages: { illegalGlobal: diff --git a/src/rules/no-large-snapshots.ts b/src/rules/no-large-snapshots.ts index 0f28dcfba..f5bd0474a 100644 --- a/src/rules/no-large-snapshots.ts +++ b/src/rules/no-large-snapshots.ts @@ -76,9 +76,7 @@ export default createRule<[RuleOptions], MessageId>({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow large snapshots', - recommended: false, }, messages: { noSnapshot: '`{{ lineCount }}`s should begin with lowercase', diff --git a/src/rules/no-mocks-import.ts b/src/rules/no-mocks-import.ts index 5fac9172e..69e54571b 100644 --- a/src/rules/no-mocks-import.ts +++ b/src/rules/no-mocks-import.ts @@ -17,9 +17,7 @@ export default createRule({ meta: { type: 'problem', docs: { - category: 'Best Practices', description: 'Disallow manually importing from `__mocks__`', - recommended: 'error', }, messages: { noManualImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path`, diff --git a/src/rules/no-restricted-jest-methods.ts b/src/rules/no-restricted-jest-methods.ts index 37947fce5..b50bf40e6 100644 --- a/src/rules/no-restricted-jest-methods.ts +++ b/src/rules/no-restricted-jest-methods.ts @@ -12,9 +12,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow specific `jest.` methods', - recommended: false, }, type: 'suggestion', schema: [ diff --git a/src/rules/no-restricted-matchers.ts b/src/rules/no-restricted-matchers.ts index f05cd4367..528704be4 100644 --- a/src/rules/no-restricted-matchers.ts +++ b/src/rules/no-restricted-matchers.ts @@ -23,9 +23,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow specific matchers & modifiers', - recommended: false, }, type: 'suggestion', schema: [ diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 5afcc977c..bb5c43fdc 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -60,9 +60,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow using `expect` outside of `it` or `test` blocks', - recommended: 'error', }, messages: { unexpectedExpect: 'Expect must be inside of a test block', @@ -70,6 +68,7 @@ export default createRule< type: 'suggestion', schema: [ { + type: 'object', properties: { additionalTestBlockFunctions: { type: 'array', diff --git a/src/rules/no-test-prefixes.ts b/src/rules/no-test-prefixes.ts index 64b5cc03f..f0a38ef8f 100644 --- a/src/rules/no-test-prefixes.ts +++ b/src/rules/no-test-prefixes.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Require using `.only` and `.skip` over `f` and `x`', - recommended: 'error', }, messages: { usePreferredName: 'Use "{{ preferredNodeName }}" instead', diff --git a/src/rules/no-test-return-statement.ts b/src/rules/no-test-return-statement.ts index 252c76e67..6cde8e75d 100644 --- a/src/rules/no-test-return-statement.ts +++ b/src/rules/no-test-return-statement.ts @@ -25,9 +25,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow explicitly returning from tests', - recommended: false, }, messages: { noReturnValue: 'Jest tests should not return a value', diff --git a/src/rules/no-untyped-mock-factory.ts b/src/rules/no-untyped-mock-factory.ts index a3abe55cc..9dceda78a 100644 --- a/src/rules/no-untyped-mock-factory.ts +++ b/src/rules/no-untyped-mock-factory.ts @@ -21,10 +21,8 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Disallow using `jest.mock()` factories without an explicit type parameter', - recommended: false, }, messages: { addTypeParameterToModuleMock: @@ -38,7 +36,12 @@ export default createRule({ create(context) { return { CallExpression(node: TSESTree.CallExpression): void { - const { callee, typeParameters } = node; + let { callee, typeArguments } = node; + + /* istanbul ignore next */ + if (!('typeArguments' in node)) { + typeArguments = (node as TSESTree.CallExpression).typeParameters; + } if (callee.type !== AST_NODE_TYPES.MemberExpression) { return; @@ -55,7 +58,7 @@ export default createRule({ const [nameNode, factoryNode] = node.arguments; const hasTypeParameter = - typeParameters !== undefined && typeParameters.params.length > 0; + typeArguments !== undefined && typeArguments.params.length > 0; const hasReturnType = isFunction(factoryNode) && factoryNode.returnType !== undefined; diff --git a/src/rules/prefer-called-with.ts b/src/rules/prefer-called-with.ts index b4b6709a2..646309cd2 100644 --- a/src/rules/prefer-called-with.ts +++ b/src/rules/prefer-called-with.ts @@ -4,10 +4,8 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()`', - recommended: false, }, messages: { preferCalledWith: 'Prefer {{ matcherName }}With(/* expected args */)', diff --git a/src/rules/prefer-comparison-matcher.ts b/src/rules/prefer-comparison-matcher.ts index 17b87ac4a..ec356f656 100644 --- a/src/rules/prefer-comparison-matcher.ts +++ b/src/rules/prefer-comparison-matcher.ts @@ -57,9 +57,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using the built-in comparison matchers', - recommended: false, }, messages: { useToBeComparison: 'Prefer using `{{ preferredMatcher }}` instead', diff --git a/src/rules/prefer-each.ts b/src/rules/prefer-each.ts index c337d71cc..141a9175b 100644 --- a/src/rules/prefer-each.ts +++ b/src/rules/prefer-each.ts @@ -5,9 +5,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Prefer using `.each` rather than manual loops', - recommended: false, }, messages: { preferEach: 'prefer using `{{ fn }}.each` rather than a manual loop', diff --git a/src/rules/prefer-equality-matcher.ts b/src/rules/prefer-equality-matcher.ts index 18ed9abb1..02932692e 100644 --- a/src/rules/prefer-equality-matcher.ts +++ b/src/rules/prefer-equality-matcher.ts @@ -14,9 +14,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using the built-in equality matchers', - recommended: false, }, messages: { useEqualityMatcher: 'Prefer using one of the equality matchers instead', diff --git a/src/rules/prefer-expect-assertions.ts b/src/rules/prefer-expect-assertions.ts index d7d3be5a0..8ec38c92d 100644 --- a/src/rules/prefer-expect-assertions.ts +++ b/src/rules/prefer-expect-assertions.ts @@ -64,10 +64,8 @@ export default createRule<[RuleOptions], MessageIds>({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `expect.assertions()` OR `expect.hasAssertions()`', - recommended: false, }, messages: { hasAssertionsTakesNoArguments: diff --git a/src/rules/prefer-expect-resolves.ts b/src/rules/prefer-expect-resolves.ts index cc9b09605..0c4719dc1 100644 --- a/src/rules/prefer-expect-resolves.ts +++ b/src/rules/prefer-expect-resolves.ts @@ -5,10 +5,8 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Prefer `await expect(...).resolves` over `expect(await ...)` syntax', - recommended: false, }, fixable: 'code', messages: { diff --git a/src/rules/prefer-hooks-in-order.ts b/src/rules/prefer-hooks-in-order.ts index 3f1f626d1..a5544db90 100644 --- a/src/rules/prefer-hooks-in-order.ts +++ b/src/rules/prefer-hooks-in-order.ts @@ -6,9 +6,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Prefer having hooks in a consistent order', - recommended: false, }, messages: { reorderHooks: `\`{{ currentHook }}\` hooks should be before any \`{{ previousHook }}\` hooks`, diff --git a/src/rules/prefer-hooks-on-top.ts b/src/rules/prefer-hooks-on-top.ts index 433e20a9b..0b68cd0ed 100644 --- a/src/rules/prefer-hooks-on-top.ts +++ b/src/rules/prefer-hooks-on-top.ts @@ -4,9 +4,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest having hooks before any test cases', - recommended: false, }, messages: { noHookOnTop: 'Hooks should come before test cases', diff --git a/src/rules/prefer-lowercase-title.ts b/src/rules/prefer-lowercase-title.ts index cf9a4f6bf..b87b72581 100644 --- a/src/rules/prefer-lowercase-title.ts +++ b/src/rules/prefer-lowercase-title.ts @@ -56,8 +56,6 @@ export default createRule< type: 'suggestion', docs: { description: 'Enforce lowercase test names', - category: 'Best Practices', - recommended: false, }, fixable: 'code', messages: { @@ -70,11 +68,14 @@ export default createRule< ignore: { type: 'array', items: { + type: 'string', + // for some reason TypeScript thinks this _must_ be a read-only + // array, so we have to explicitly cast it as a mutable array enum: [ DescribeAlias.describe, TestCaseName.test, TestCaseName.it, - ], + ] as string[], }, additionalItems: false, }, diff --git a/src/rules/prefer-mock-promise-shorthand.ts b/src/rules/prefer-mock-promise-shorthand.ts index 7007d816e..c32a456b7 100644 --- a/src/rules/prefer-mock-promise-shorthand.ts +++ b/src/rules/prefer-mock-promise-shorthand.ts @@ -32,9 +32,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Prefer mock resolved/rejected shorthands for promises', - recommended: false, }, messages: { useMockShorthand: 'Prefer {{ replacement }}', diff --git a/src/rules/prefer-snapshot-hint.ts b/src/rules/prefer-snapshot-hint.ts index 67621c101..b1882e298 100644 --- a/src/rules/prefer-snapshot-hint.ts +++ b/src/rules/prefer-snapshot-hint.ts @@ -43,9 +43,7 @@ export default createRule<[('always' | 'multi')?], keyof typeof messages>({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Prefer including a hint with external snapshots', - recommended: false, }, messages, type: 'suggestion', diff --git a/src/rules/prefer-spy-on.ts b/src/rules/prefer-spy-on.ts index ae1648bf6..c01ae1157 100644 --- a/src/rules/prefer-spy-on.ts +++ b/src/rules/prefer-spy-on.ts @@ -68,9 +68,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `jest.spyOn()`', - recommended: false, }, messages: { useJestSpyOn: 'Use jest.spyOn() instead', diff --git a/src/rules/prefer-strict-equal.ts b/src/rules/prefer-strict-equal.ts index 6586fddca..9fb594610 100644 --- a/src/rules/prefer-strict-equal.ts +++ b/src/rules/prefer-strict-equal.ts @@ -10,9 +10,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `toStrictEqual()`', - recommended: false, }, messages: { useToStrictEqual: 'Use `toStrictEqual()` instead', diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index f3fc235a0..7479d51e9 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -93,9 +93,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `toBe()` for primitive literals', - recommended: false, }, messages: { useToBe: 'Use `toBe` when expecting primitive literals', diff --git a/src/rules/prefer-to-contain.ts b/src/rules/prefer-to-contain.ts index 2e4f7e0ca..b062e1f8b 100644 --- a/src/rules/prefer-to-contain.ts +++ b/src/rules/prefer-to-contain.ts @@ -39,9 +39,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `toContain()`', - recommended: false, }, messages: { useToContain: 'Use toContain() instead', diff --git a/src/rules/prefer-to-have-length.ts b/src/rules/prefer-to-have-length.ts index a39fa47ce..683f5bb8f 100644 --- a/src/rules/prefer-to-have-length.ts +++ b/src/rules/prefer-to-have-length.ts @@ -11,9 +11,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `toHaveLength()`', - recommended: false, }, messages: { useToHaveLength: 'Use toHaveLength() instead', diff --git a/src/rules/prefer-todo.ts b/src/rules/prefer-todo.ts index 8e675e1e0..12df97844 100644 --- a/src/rules/prefer-todo.ts +++ b/src/rules/prefer-todo.ts @@ -55,9 +55,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Suggest using `test.todo`', - recommended: false, }, messages: { emptyTest: 'Prefer todo test case over empty test case', diff --git a/src/rules/require-hook.ts b/src/rules/require-hook.ts index 3eb0cbd9c..a60b8e2ee 100644 --- a/src/rules/require-hook.ts +++ b/src/rules/require-hook.ts @@ -65,9 +65,7 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Require setup and teardown code to be within a hook', - recommended: false, }, messages: { useHook: 'This should be done within a hook', diff --git a/src/rules/require-to-throw-message.ts b/src/rules/require-to-throw-message.ts index 9fdd6707c..e80c2f5c3 100644 --- a/src/rules/require-to-throw-message.ts +++ b/src/rules/require-to-throw-message.ts @@ -4,9 +4,7 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Require a message for `toThrow()`', - recommended: false, }, messages: { addErrorMessage: 'Add an error message to {{ matcherName }}()', diff --git a/src/rules/require-top-level-describe.ts b/src/rules/require-top-level-describe.ts index 876881e2b..d0b30cba3 100644 --- a/src/rules/require-top-level-describe.ts +++ b/src/rules/require-top-level-describe.ts @@ -15,10 +15,8 @@ export default createRule< name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Require test cases and hooks to be inside a `describe` block', - recommended: false, }, messages, type: 'suggestion', diff --git a/src/rules/unbound-method.ts b/src/rules/unbound-method.ts index 515c88258..eea17ccce 100644 --- a/src/rules/unbound-method.ts +++ b/src/rules/unbound-method.ts @@ -60,12 +60,12 @@ export default createRule({ type: 'problem', ...baseRule?.meta, docs: { - category: 'Best Practices', description: 'Enforce unbound methods are called with their expected scope', requiresTypeChecking: true, ...baseRule?.meta.docs, - recommended: false, + // mark this as not recommended + recommended: undefined, }, }, create(context) { diff --git a/src/rules/utils/__tests__/parseJestFnCall.test.ts b/src/rules/utils/__tests__/parseJestFnCall.test.ts index cdb3511b3..e119203f2 100644 --- a/src/rules/utils/__tests__/parseJestFnCall.test.ts +++ b/src/rules/utils/__tests__/parseJestFnCall.test.ts @@ -48,9 +48,7 @@ const rule = createRule({ name: __filename, meta: { docs: { - category: 'Possible Errors', description: 'Fake rule for testing parseJestFnCall', - recommended: false, }, messages: { details: '{{ data }}', diff --git a/src/rules/utils/misc.ts b/src/rules/utils/misc.ts index 9e025fcf5..e5e32a643 100644 --- a/src/rules/utils/misc.ts +++ b/src/rules/utils/misc.ts @@ -234,18 +234,14 @@ export const getFirstMatcherArg = ( export const getFilename = ( context: TSESLint.RuleContext, ) => { - return 'filename' in context - ? (context.filename as string) - : context.getFilename(); + return context.filename ?? context.getFilename(); }; /* istanbul ignore next */ export const getSourceCode = ( context: TSESLint.RuleContext, ) => { - return 'sourceCode' in context - ? (context.sourceCode as TSESLint.SourceCode) - : context.getSourceCode(); + return context.sourceCode ?? context.getSourceCode(); }; /* istanbul ignore next */ @@ -253,13 +249,7 @@ export const getScope = ( context: TSESLint.RuleContext, node: TSESTree.Node, ) => { - const sourceCode = getSourceCode(context); - - if ('getScope' in sourceCode) { - return sourceCode.getScope(node); - } - - return context.getScope(); + return getSourceCode(context).getScope?.(node) ?? context.getScope(); }; /* istanbul ignore next */ @@ -267,13 +257,7 @@ export const getAncestors = ( context: TSESLint.RuleContext, node: TSESTree.Node, ) => { - const sourceCode = getSourceCode(context); - - if ('getAncestors' in sourceCode) { - return sourceCode.getAncestors(node); - } - - return context.getAncestors(); + return getSourceCode(context).getAncestors?.(node) ?? context.getAncestors(); }; /* istanbul ignore next */ @@ -281,11 +265,8 @@ export const getDeclaredVariables = ( context: TSESLint.RuleContext, node: TSESTree.Node, ) => { - const sourceCode = getSourceCode(context); - - if ('getDeclaredVariables' in sourceCode) { - return sourceCode.getDeclaredVariables(node); - } - - return context.getDeclaredVariables(node); + return ( + getSourceCode(context).getDeclaredVariables?.(node) ?? + context.getDeclaredVariables(node) + ); }; diff --git a/src/rules/utils/parseJestFnCall.ts b/src/rules/utils/parseJestFnCall.ts index d05531248..0f5d65ebf 100644 --- a/src/rules/utils/parseJestFnCall.ts +++ b/src/rules/utils/parseJestFnCall.ts @@ -183,20 +183,21 @@ const ValidJestFnCallChains = [ 'xtest.failing.each', ]; -declare module '@typescript-eslint/utils/dist/ts-eslint' { - export interface SharedConfigurationSettings { - jest?: { - globalAliases?: Record; - version?: number | string; - }; - } +// todo: switch back to using declaration merging once https://github.com/typescript-eslint/typescript-eslint/pull/8485 +// is landed +interface SharedConfigurationSettings { + jest?: { + globalAliases?: Record; + version?: number | string; + }; } const resolvePossibleAliasedGlobal = ( global: string, context: TSESLint.RuleContext, ) => { - const globalAliases = context.settings.jest?.globalAliases ?? {}; + const globalAliases = + (context.settings as SharedConfigurationSettings).jest?.globalAliases ?? {}; const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(global), diff --git a/src/rules/valid-describe-callback.ts b/src/rules/valid-describe-callback.ts index 08eb70a00..874511b4c 100644 --- a/src/rules/valid-describe-callback.ts +++ b/src/rules/valid-describe-callback.ts @@ -23,9 +23,7 @@ export default createRule({ meta: { type: 'problem', docs: { - category: 'Possible Errors', description: 'Enforce valid `describe()` callback', - recommended: 'error', }, messages: { nameAndCallback: 'Describe requires name and callback arguments', diff --git a/src/rules/valid-expect-in-promise.ts b/src/rules/valid-expect-in-promise.ts index 04bf86c8e..8570f8ed5 100644 --- a/src/rules/valid-expect-in-promise.ts +++ b/src/rules/valid-expect-in-promise.ts @@ -344,10 +344,8 @@ export default createRule({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Require promises that have expectations in their chain to be valid', - recommended: 'error', }, messages: { expectInFloatingPromise: diff --git a/src/rules/valid-expect.ts b/src/rules/valid-expect.ts index 93b3ef20f..e5b30ea2f 100644 --- a/src/rules/valid-expect.ts +++ b/src/rules/valid-expect.ts @@ -115,9 +115,7 @@ export default createRule<[Options], MessageIds>({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforce valid `expect()` usage', - recommended: 'error', }, messages: { tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}', diff --git a/src/rules/valid-title.ts b/src/rules/valid-title.ts index 9ed6c7441..43dc09c95 100644 --- a/src/rules/valid-title.ts +++ b/src/rules/valid-title.ts @@ -74,7 +74,7 @@ const compileMatcherPatterns = ( type CompiledMatcherAndMessage = [matcher: RegExp, message?: string]; type MatcherAndMessage = [matcher: string, message?: string]; -const MatcherAndMessageSchema: JSONSchema.JSONSchema7 = { +const MatcherAndMessageSchema: JSONSchema.JSONSchema4 = { type: 'array', items: { type: 'string' }, minItems: 1, @@ -114,9 +114,7 @@ export default createRule<[Options], MessageIds>({ name: __filename, meta: { docs: { - category: 'Best Practices', description: 'Enforce valid titles', - recommended: 'error', }, messages: { titleMustBeString: 'Title must be a string', @@ -158,6 +156,7 @@ export default createRule<[Options], MessageIds>({ MatcherAndMessageSchema, { type: 'object', + // @ts-expect-error https://github.com/eslint/eslint/discussions/17573 propertyNames: { enum: ['describe', 'test', 'it'] }, additionalProperties: { oneOf: [{ type: 'string' }, MatcherAndMessageSchema], diff --git a/yarn.lock b/yarn.lock index 5613c1364..b622e010f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1650,7 +1650,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0": +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" dependencies: @@ -2844,7 +2844,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 97ed0cb44d4070aecea772b7b2e2ed971e10c81ec87dd4ecc160322ffa55ff330dace1793489540e3e318d90942064bb697cc0f8989391797792d919737b3b98 @@ -2902,10 +2902,10 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.3.12": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: ea6f5276f5b84c55921785a3a27a3cd37afee0111dfe2bcb3e03c31819c197c782598f17f0b150a69d453c9584cd14c4c4d7b9a55d2c5e6cacd4d66fdb3b3663 +"@types/semver@npm:^7.3.12, @types/semver@npm:^7.5.0": + version: 7.5.7 + resolution: "@types/semver@npm:7.5.7" + checksum: 5af9b13e3d74d86d4b618f6506ccbded801fb35dbc28608cd5a7bfb8bcac0021dd35ef305a72a0c2a8def0cff60acd706bfee16a9ed1c39a893d2a175e778ea7 languageName: node linkType: hard @@ -2990,6 +2990,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/scope-manager@npm:6.21.0" + dependencies: + "@typescript-eslint/types": 6.21.0 + "@typescript-eslint/visitor-keys": 6.21.0 + checksum: 71028b757da9694528c4c3294a96cc80bc7d396e383a405eab3bc224cda7341b88e0fc292120b35d3f31f47beac69f7083196c70616434072fbcd3d3e62d3376 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/type-utils@npm:5.62.0" @@ -3014,6 +3024,32 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/types@npm:6.21.0" + checksum: 9501b47d7403417af95fc1fb72b2038c5ac46feac0e1598a46bcb43e56a606c387e9dcd8a2a0abe174c91b509f2d2a8078b093786219eb9a01ab2fbf9ee7b684 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.21.0" + dependencies: + "@typescript-eslint/types": 6.21.0 + "@typescript-eslint/visitor-keys": 6.21.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + minimatch: 9.0.3 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependenciesMeta: + typescript: + optional: true + checksum: dec02dc107c4a541e14fb0c96148f3764b92117c3b635db3a577b5a56fc48df7a556fa853fb82b07c0663b4bf2c484c9f245c28ba3e17e5cb0918ea4cab2ea21 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:^5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -3050,7 +3086,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.38.1": +"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.38.1": version: 5.62.0 resolution: "@typescript-eslint/utils@npm:5.62.0" dependencies: @@ -3068,6 +3104,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^6.0.0": + version: 6.21.0 + resolution: "@typescript-eslint/utils@npm:6.21.0" + dependencies: + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.21.0 + "@typescript-eslint/types": 6.21.0 + "@typescript-eslint/typescript-estree": 6.21.0 + semver: ^7.5.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: b129b3a4aebec8468259f4589985cb59ea808afbfdb9c54f02fad11e17d185e2bf72bb332f7c36ec3c09b31f18fc41368678b076323e6e019d06f74ee93f7bf2 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -3078,6 +3131,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.21.0" + dependencies: + "@typescript-eslint/types": 6.21.0 + eslint-visitor-keys: ^3.4.1 + checksum: 67c7e6003d5af042d8703d11538fca9d76899f0119130b373402819ae43f0bc90d18656aa7add25a24427ccf1a0efd0804157ba83b0d4e145f06107d7d1b7433 + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -5148,7 +5211,7 @@ __metadata: "@types/node": ^14.18.26 "@typescript-eslint/eslint-plugin": ^5.0.0 "@typescript-eslint/parser": ^5.0.0 - "@typescript-eslint/utils": ^5.10.0 + "@typescript-eslint/utils": ^6.0.0 babel-jest: ^29.0.0 babel-plugin-replace-ts-export-assignment: ^0.0.2 dedent: ^1.5.0 @@ -8239,6 +8302,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:9.0.3, minimatch@npm:^9.0.0, minimatch@npm:^9.0.1, minimatch@npm:^9.0.3": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" + dependencies: + brace-expansion: ^2.0.1 + checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -8248,15 +8320,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.0, minimatch@npm:^9.0.1, minimatch@npm:^9.0.3": - version: 9.0.3 - resolution: "minimatch@npm:9.0.3" - dependencies: - brace-expansion: ^2.0.1 - checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 - languageName: node - linkType: hard - "minimist-options@npm:4.1.0": version: 4.1.0 resolution: "minimist-options@npm:4.1.0" @@ -10822,6 +10885,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.0.1": + version: 1.2.1 + resolution: "ts-api-utils@npm:1.2.1" + peerDependencies: + typescript: ">=4.2.0" + checksum: 17a2a4454d65a6765b9351304cfd516fcda3098f49d72bba90cb7f22b6a09a573b4a1993fd7de7d6b8046c408960c5f21a25e64ccb969d484b32ea3b3e19d6e4 + languageName: node + linkType: hard + "ts-node@npm:^10.2.1, ts-node@npm:^10.8.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2"