diff --git a/packages/utils/src/eslint-utils/getParserServices.ts b/packages/utils/src/eslint-utils/getParserServices.ts index 0fd908f4a2ec..f105fbf9902a 100644 --- a/packages/utils/src/eslint-utils/getParserServices.ts +++ b/packages/utils/src/eslint-utils/getParserServices.ts @@ -3,7 +3,7 @@ import type { ParserServices, ParserServicesWithTypeInformation, } from '../ts-estree'; -import { parserPathSeemsToBeTSESLint } from './parserPathSeemsToBeTSESLint'; +import { parserSeemsToBeTSESLint } from './parserSeemsToBeTSESLint'; const ERROR_MESSAGE_REQUIRES_PARSER_SERVICES = 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'; @@ -60,6 +60,9 @@ function getParserServices( context: Readonly>, allowWithoutFullTypeInformation = false, ): ParserServices { + const parser = + context.parserPath || context.languageOptions.parser?.meta?.name; + // This check is unnecessary if the user is using the latest version of our parser. // // However the world isn't perfect: @@ -74,7 +77,7 @@ function getParserServices( context.sourceCode.parserServices?.esTreeNodeToTSNodeMap == null || context.sourceCode.parserServices.tsNodeToESTreeNodeMap == null ) { - throwError(context.parserPath); + throwError(parser); } // if a rule requires full type information, then hard fail if it doesn't exist @@ -83,21 +86,20 @@ function getParserServices( context.sourceCode.parserServices.program == null && !allowWithoutFullTypeInformation ) { - throwError(context.parserPath); + throwError(parser); } return context.sourceCode.parserServices as ParserServices; } /* eslint-enable @typescript-eslint/unified-signatures */ -function throwError(parserPath: string): never { +function throwError(parser: string | undefined): never { const messages = [ ERROR_MESSAGE_REQUIRES_PARSER_SERVICES, - `Parser: ${parserPath}`, - ]; - if (!parserPathSeemsToBeTSESLint(parserPath)) { - messages.push(ERROR_MESSAGE_UNKNOWN_PARSER); - } + `Parser: ${parser || '(unknown)'}`, + !parserSeemsToBeTSESLint(parser) && ERROR_MESSAGE_UNKNOWN_PARSER, + ].filter(Boolean); + throw new Error(messages.join('\n')); } diff --git a/packages/utils/src/eslint-utils/parserPathSeemsToBeTSESLint.ts b/packages/utils/src/eslint-utils/parserPathSeemsToBeTSESLint.ts deleted file mode 100644 index ab68b4367ab7..000000000000 --- a/packages/utils/src/eslint-utils/parserPathSeemsToBeTSESLint.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function parserPathSeemsToBeTSESLint(parserPath: string): boolean { - return /(?:typescript-eslint|\.\.)[\w/\\]*parser/.test(parserPath); -} diff --git a/packages/utils/src/eslint-utils/parserSeemsToBeTSESLint.ts b/packages/utils/src/eslint-utils/parserSeemsToBeTSESLint.ts new file mode 100644 index 000000000000..386384b0d331 --- /dev/null +++ b/packages/utils/src/eslint-utils/parserSeemsToBeTSESLint.ts @@ -0,0 +1,3 @@ +export function parserSeemsToBeTSESLint(parser: string | undefined): boolean { + return !!parser && /(?:typescript-eslint|\.\.)[\w/\\]*parser/.test(parser); +} diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 8ca4c7de2a72..d7e3562c8181 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -203,9 +203,9 @@ export interface RuleContext< */ options: Options; /** - * The name of the parser from configuration. + * The name of the parser from configuration, if in eslintrc (legacy) config. */ - parserPath: string; + parserPath: string | undefined; /** * The language options configured for this run */ diff --git a/packages/utils/tests/eslint-utils/getParserServices.test.ts b/packages/utils/tests/eslint-utils/getParserServices.test.ts index b247727786ae..2fe3c0c04c3c 100644 --- a/packages/utils/tests/eslint-utils/getParserServices.test.ts +++ b/packages/utils/tests/eslint-utils/getParserServices.test.ts @@ -3,6 +3,7 @@ import type * as ts from 'typescript'; import type { ParserServices, TSESLint, TSESTree } from '../../src'; import { ESLintUtils } from '../../src'; +import type { FlatConfig } from '../../src/ts-eslint'; type UnknownRuleContext = Readonly>; @@ -25,18 +26,20 @@ const createMockRuleContext = ( ...overrides, }) as unknown as UnknownRuleContext; -const requiresParserServicesMessageTemplate = +const requiresParserServicesMessageTemplate = (parser = '\\S*'): string => 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.\n' + - 'Parser: \\S*'; -const baseErrorRegex = new RegExp(requiresParserServicesMessageTemplate); -const unknownParserErrorRegex = new RegExp( - requiresParserServicesMessageTemplate + - '\n' + - 'Note: detected a parser other than @typescript-eslint/parser. Make sure the parser is configured to forward "parserOptions.project" to @typescript-eslint/parser.', -); + `Parser: ${parser}`; +const baseErrorRegex = (parser?: string): RegExp => + new RegExp(requiresParserServicesMessageTemplate(parser)); +const unknownParserErrorRegex = (parser?: string): RegExp => + new RegExp( + requiresParserServicesMessageTemplate(parser) + + '\n' + + 'Note: detected a parser other than @typescript-eslint/parser. Make sure the parser is configured to forward "parserOptions.project" to @typescript-eslint/parser.', + ); describe('getParserServices', () => { - it('throws a standard error when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is known', () => { + it('throws a standard error with the parser when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is typescript-eslint', () => { const context = createMockRuleContext({ sourceCode: { ...defaults.sourceCode, @@ -48,7 +51,69 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - baseErrorRegex, + baseErrorRegex('@typescript-eslint[\\/]parser[\\/]dist[\\/]index\\.js'), + ); + }); + + it('throws a standard error with the parser when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is custom', () => { + const context = createMockRuleContext({ + languageOptions: { + parser: { + meta: { + name: 'custom-parser', + }, + } as FlatConfig.Parser, + }, + parserPath: undefined, + sourceCode: { + ...defaults.sourceCode, + parserServices: { + ...defaults.sourceCode.parserServices, + esTreeNodeToTSNodeMap: undefined as any, + }, + }, + }); + + expect(() => ESLintUtils.getParserServices(context)).toThrow( + baseErrorRegex('custom-parser'), + ); + }); + + it('throws a standard error with an unknown parser when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is missing', () => { + const context = createMockRuleContext({ + languageOptions: {}, + parserPath: undefined, + sourceCode: { + ...defaults.sourceCode, + parserServices: { + ...defaults.sourceCode.parserServices, + esTreeNodeToTSNodeMap: undefined as any, + }, + }, + }); + + expect(() => ESLintUtils.getParserServices(context)).toThrow( + baseErrorRegex('\\(unknown\\)'), + ); + }); + + it('throws a standard error with an unknown parser when parserOptions.esTreeNodeToTSNodeMap is missing and the parser is unknown', () => { + const context = createMockRuleContext({ + languageOptions: { + parser: {} as FlatConfig.Parser, + }, + parserPath: undefined, + sourceCode: { + ...defaults.sourceCode, + parserServices: { + ...defaults.sourceCode.parserServices, + esTreeNodeToTSNodeMap: undefined as any, + }, + }, + }); + + expect(() => ESLintUtils.getParserServices(context)).toThrow( + baseErrorRegex('\\(unknown\\)'), ); }); @@ -64,7 +129,7 @@ describe('getParserServices', () => { }, }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - unknownParserErrorRegex, + unknownParserErrorRegex(), ); }); @@ -80,7 +145,7 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - baseErrorRegex, + baseErrorRegex(), ); }); @@ -96,7 +161,7 @@ describe('getParserServices', () => { }); expect(() => ESLintUtils.getParserServices(context)).toThrow( - baseErrorRegex, + baseErrorRegex(), ); }); diff --git a/packages/utils/tests/eslint-utils/parserPathSeemsToBeTSESLint.test.ts b/packages/utils/tests/eslint-utils/parserSeemsToBeTSESLint.test.ts similarity index 79% rename from packages/utils/tests/eslint-utils/parserPathSeemsToBeTSESLint.test.ts rename to packages/utils/tests/eslint-utils/parserSeemsToBeTSESLint.test.ts index 0c11c1664005..fb70c5e644e6 100644 --- a/packages/utils/tests/eslint-utils/parserPathSeemsToBeTSESLint.test.ts +++ b/packages/utils/tests/eslint-utils/parserSeemsToBeTSESLint.test.ts @@ -1,7 +1,9 @@ -import { parserPathSeemsToBeTSESLint } from '../../src/eslint-utils/parserPathSeemsToBeTSESLint'; +import { parserSeemsToBeTSESLint } from '../../src/eslint-utils/parserSeemsToBeTSESLint'; -describe('parserPathSeemsToBeTSESLint', () => { +describe('parserSeemsToBeTSESLint', () => { test.each([ + [undefined, false], + ['espree', false], ['local.js', false], ['../other.js', false], ['@babel/eslint-parser/lib/index.cjs', false], @@ -19,6 +21,6 @@ describe('parserPathSeemsToBeTSESLint', () => { ['/path/to/@typescript-eslint/packages/parser/dist/index.js', true], ['/path/to/@typescript-eslint/packages/parser/index.js', true], ])('%s', (parserPath, expected) => { - expect(parserPathSeemsToBeTSESLint(parserPath)).toBe(expected); + expect(parserSeemsToBeTSESLint(parserPath)).toBe(expected); }); });