From 0d972ebe093ab6cdcfb9d9c51ae2c5789df023e9 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Fri, 11 Oct 2024 16:37:28 -0600 Subject: [PATCH 1/3] rethrow exceptions in RuleTester to avoid circular JSON issue --- packages/rule-tester/src/RuleTester.ts | 121 +++++++++++++++---------- 1 file changed, 72 insertions(+), 49 deletions(-) diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index 6a1bd8fb0c42..151f0eaad437 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -184,10 +184,37 @@ export class RuleTester extends TestFramework { rules: { [`${RULE_TESTER_PLUGIN_PREFIX}validate-ast`]: 'error' }, }); - this.#linter = new Linter({ - configType: 'flat', - cwd: this.#testerConfig.languageOptions.parserOptions?.tsconfigRootDir, - }); + // This proxy nonsense is a workaround for https://github.com/jestjs/jest/issues/14840 + // see also https://github.com/typescript-eslint/typescript-eslint/issues/8942 + // + // For some reason rethrowing exceptions skirts around the circular JSON error. + this.#linter = new Proxy( + new Linter({ + configType: 'flat', + cwd: this.#testerConfig.languageOptions.parserOptions?.tsconfigRootDir, + }), + { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + get: (target, prop, receiver) => { + if (prop === 'verify') { + return ( + ...args: Parameters + ): ReturnType => { + try { + return target.verify(...args); + } catch (error) { + throw new Error('Caught an error while linting', { + cause: error, + }); + } + }; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Reflect.get(target, prop, receiver); + }, + }, + ); // make sure that the parser doesn't hold onto file handles between tests // on linux (i.e. our CI env), there can be very a limited number of watch handles available @@ -775,51 +802,6 @@ export class RuleTester extends TestFramework { * Check if the template is valid or not * all valid cases go through this */ - #testValidTemplate< - MessageIds extends string, - Options extends readonly unknown[], - >( - ruleName: string, - rule: RuleModule, - itemIn: ValidTestCase | string, - seenValidTestCases: Set, - ): void { - const item: ValidTestCase = - typeof itemIn === 'object' ? itemIn : { code: itemIn }; - - assert.ok( - typeof item.code === 'string', - "Test case must specify a string value for 'code'", - ); - if (item.name) { - assert.ok( - typeof item.name === 'string', - "Optional test case property 'name' must be a string", - ); - } - - checkDuplicateTestCase(item, seenValidTestCases); - - const result = this.runRuleForItem(ruleName, rule, item); - const messages = result.messages; - - assert.strictEqual( - messages.length, - 0, - util.format( - 'Should have no errors but had %d: %s', - messages.length, - util.inspect(messages), - ), - ); - - assertASTDidntChange(result.beforeAST, result.afterAST); - } - - /** - * Check if the template is invalid or not - * all invalid cases go through this. - */ #testInvalidTemplate< MessageIds extends string, Options extends readonly unknown[], @@ -1284,6 +1266,47 @@ export class RuleTester extends TestFramework { assertASTDidntChange(result.beforeAST, result.afterAST); } + + #testValidTemplate< + MessageIds extends string, + Options extends readonly unknown[], + >( + ruleName: string, + rule: RuleModule, + itemIn: ValidTestCase | string, + seenValidTestCases: Set, + ): void { + const item: ValidTestCase = + typeof itemIn === 'object' ? itemIn : { code: itemIn }; + + assert.ok( + typeof item.code === 'string', + "Test case must specify a string value for 'code'", + ); + if (item.name) { + assert.ok( + typeof item.name === 'string', + "Optional test case property 'name' must be a string", + ); + } + + checkDuplicateTestCase(item, seenValidTestCases); + + const result = this.runRuleForItem(ruleName, rule, item); + const messages = result.messages; + + assert.strictEqual( + messages.length, + 0, + util.format( + 'Should have no errors but had %d: %s', + messages.length, + util.inspect(messages), + ), + ); + + assertASTDidntChange(result.beforeAST, result.afterAST); + } } /** From c799e6985237aab1ea1f3c7ac5a54a5980a1ce59 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Fri, 11 Oct 2024 16:41:48 -0600 Subject: [PATCH 2/3] didn't mean to move stuff --- packages/rule-tester/src/RuleTester.ts | 86 ++++++++++++++------------ 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index 151f0eaad437..ddaccfeef285 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -802,6 +802,51 @@ export class RuleTester extends TestFramework { * Check if the template is valid or not * all valid cases go through this */ + #testValidTemplate< + MessageIds extends string, + Options extends readonly unknown[], + >( + ruleName: string, + rule: RuleModule, + itemIn: ValidTestCase | string, + seenValidTestCases: Set, + ): void { + const item: ValidTestCase = + typeof itemIn === 'object' ? itemIn : { code: itemIn }; + + assert.ok( + typeof item.code === 'string', + "Test case must specify a string value for 'code'", + ); + if (item.name) { + assert.ok( + typeof item.name === 'string', + "Optional test case property 'name' must be a string", + ); + } + + checkDuplicateTestCase(item, seenValidTestCases); + + const result = this.runRuleForItem(ruleName, rule, item); + const messages = result.messages; + + assert.strictEqual( + messages.length, + 0, + util.format( + 'Should have no errors but had %d: %s', + messages.length, + util.inspect(messages), + ), + ); + + assertASTDidntChange(result.beforeAST, result.afterAST); + } + + /** + * Check if the template is invalid or not + * all invalid cases go through this. + */ #testInvalidTemplate< MessageIds extends string, Options extends readonly unknown[], @@ -1266,47 +1311,6 @@ export class RuleTester extends TestFramework { assertASTDidntChange(result.beforeAST, result.afterAST); } - - #testValidTemplate< - MessageIds extends string, - Options extends readonly unknown[], - >( - ruleName: string, - rule: RuleModule, - itemIn: ValidTestCase | string, - seenValidTestCases: Set, - ): void { - const item: ValidTestCase = - typeof itemIn === 'object' ? itemIn : { code: itemIn }; - - assert.ok( - typeof item.code === 'string', - "Test case must specify a string value for 'code'", - ); - if (item.name) { - assert.ok( - typeof item.name === 'string', - "Optional test case property 'name' must be a string", - ); - } - - checkDuplicateTestCase(item, seenValidTestCases); - - const result = this.runRuleForItem(ruleName, rule, item); - const messages = result.messages; - - assert.strictEqual( - messages.length, - 0, - util.format( - 'Should have no errors but had %d: %s', - messages.length, - util.inspect(messages), - ), - ); - - assertASTDidntChange(result.beforeAST, result.afterAST); - } } /** From 416d69bafed8bc287d9fd9ad0dff4862cd85a89d Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 14 Oct 2024 18:32:57 -0600 Subject: [PATCH 3/3] switch to simpler approach --- packages/rule-tester/src/RuleTester.ts | 50 ++++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index ddaccfeef285..47e740d7c9b9 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -184,37 +184,31 @@ export class RuleTester extends TestFramework { rules: { [`${RULE_TESTER_PLUGIN_PREFIX}validate-ast`]: 'error' }, }); - // This proxy nonsense is a workaround for https://github.com/jestjs/jest/issues/14840 - // see also https://github.com/typescript-eslint/typescript-eslint/issues/8942 - // - // For some reason rethrowing exceptions skirts around the circular JSON error. - this.#linter = new Proxy( - new Linter({ + this.#linter = (() => { + const linter = new Linter({ configType: 'flat', cwd: this.#testerConfig.languageOptions.parserOptions?.tsconfigRootDir, - }), - { - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - get: (target, prop, receiver) => { - if (prop === 'verify') { - return ( - ...args: Parameters - ): ReturnType => { - try { - return target.verify(...args); - } catch (error) { - throw new Error('Caught an error while linting', { - cause: error, - }); - } - }; - } + }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return Reflect.get(target, prop, receiver); - }, - }, - ); + // This nonsense is a workaround for https://github.com/jestjs/jest/issues/14840 + // see also https://github.com/typescript-eslint/typescript-eslint/issues/8942 + // + // For some reason rethrowing exceptions skirts around the circular JSON error. + const oldVerify = linter.verify.bind(linter); + linter.verify = ( + ...args: Parameters + ): ReturnType => { + try { + return oldVerify(...args); + } catch (error) { + throw new Error('Caught an error while linting', { + cause: error, + }); + } + }; + + return linter; + })(); // make sure that the parser doesn't hold onto file handles between tests // on linux (i.e. our CI env), there can be very a limited number of watch handles available