diff --git a/packages/eslint-plugin-internal/tests/RuleTester.ts b/packages/eslint-plugin-internal/tests/RuleTester.ts index daef7ec9a8b..3b7d3afc554 100644 --- a/packages/eslint-plugin-internal/tests/RuleTester.ts +++ b/packages/eslint-plugin-internal/tests/RuleTester.ts @@ -1,22 +1,10 @@ -import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; +import path from 'path'; -const { batchedSingleLineTests } = ESLintUtils; - -const parser = '@typescript-eslint/parser'; - -type RuleTesterConfig = Omit & { - parser: typeof parser; -}; -class RuleTester extends TSESLint.RuleTester { - // as of eslint 6 you have to provide an absolute path to the parser - // but that's not as clean to type, this saves us trying to manually enforce - // that contributors require.resolve everything - constructor(options: RuleTesterConfig) { - super({ - ...options, - parser: require.resolve(options.parser), - }); - } +function getFixturesRootDir(): string { + return path.join(__dirname, 'fixtures'); } -export { RuleTester, batchedSingleLineTests }; +const { batchedSingleLineTests, RuleTester } = ESLintUtils; + +export { RuleTester, batchedSingleLineTests, getFixturesRootDir }; diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 1774200db32..dad454369c1 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,104 +1,10 @@ -import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; -import { clearCaches } from '@typescript-eslint/parser'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import * as path from 'path'; -const parser = '@typescript-eslint/parser'; - -type RuleTesterConfig = Omit & { - parser: typeof parser; -}; -class RuleTester extends TSESLint.RuleTester { - // as of eslint 6 you have to provide an absolute path to the parser - // but that's not as clean to type, this saves us trying to manually enforce - // that contributors require.resolve everything - constructor(private readonly options: RuleTesterConfig) { - super({ - ...options, - parser: require.resolve(options.parser), - }); - } - private getFilename(options?: TSESLint.ParserOptions): string { - if (options) { - const filename = `file.ts${ - options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' - }`; - if (options.project) { - return path.join(getFixturesRootDir(), filename); - } - - return filename; - } else if (this.options.parserOptions) { - return this.getFilename(this.options.parserOptions); - } - - return 'file.ts'; - } - - // as of eslint 6 you have to provide an absolute path to the parser - // If you don't do that at the test level, the test will fail somewhat cryptically... - // This is a lot more explicit - run>( - name: string, - rule: TSESLint.RuleModule, - tests: TSESLint.RunTests, - ): void { - const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; - - // standardize the valid tests as objects - tests.valid = tests.valid.map(test => { - if (typeof test === 'string') { - return { - code: test, - }; - } - return test; - }); - - tests.valid.forEach(test => { - if (typeof test !== 'string') { - if (test.parser === parser) { - throw new Error(errorMessage); - } - if (!test.filename) { - test.filename = this.getFilename(test.parserOptions); - } - } - }); - tests.invalid.forEach(test => { - if (test.parser === parser) { - throw new Error(errorMessage); - } - if (!test.filename) { - test.filename = this.getFilename(test.parserOptions); - } - }); - - super.run(name, rule, tests); - } -} - function getFixturesRootDir(): string { - return path.join(process.cwd(), 'tests/fixtures/'); + return path.join(__dirname, 'fixtures'); } -const { batchedSingleLineTests } = ESLintUtils; - -// 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 -afterAll(() => { - clearCaches(); -}); - -/** - * Simple no-op tag to mark code samples as "should not format with prettier" - * for the internal/plugin-test-formatting lint rule - */ -function noFormat(strings: TemplateStringsArray, ...keys: string[]): string { - const lastIndex = strings.length - 1; - return ( - strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + - strings[lastIndex] - ); -} +const { batchedSingleLineTests, RuleTester, noFormat } = ESLintUtils; export { batchedSingleLineTests, getFixturesRootDir, noFormat, RuleTester }; diff --git a/packages/experimental-utils/src/eslint-utils/RuleTester.ts b/packages/experimental-utils/src/eslint-utils/RuleTester.ts new file mode 100644 index 00000000000..07b3a4cf0cf --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/RuleTester.ts @@ -0,0 +1,109 @@ +import * as TSESLint from '../ts-eslint'; +import * as path from 'path'; + +const parser = '@typescript-eslint/parser'; + +type RuleTesterConfig = Omit & { + parser: typeof parser; +}; + +class RuleTester extends TSESLint.RuleTester { + // as of eslint 6 you have to provide an absolute path to the parser + // but that's not as clean to type, this saves us trying to manually enforce + // that contributors require.resolve everything + constructor(private readonly options: RuleTesterConfig) { + super({ + ...options, + parser: require.resolve(options.parser), + }); + + // 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 + afterAll(() => { + try { + // instead of creating a hard dependency, just use a soft require + // a bit weird, but if they're using this tooling, it'll be installed + require(parser).clearCaches(); + } catch { + // ignored + } + }); + } + private getFilename(options?: TSESLint.ParserOptions): string { + if (options) { + const filename = `file.ts${ + options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' + }`; + if (options.project) { + return path.join( + options.tsconfigRootDir != null + ? options.tsconfigRootDir + : process.cwd(), + filename, + ); + } + + return filename; + } else if (this.options.parserOptions) { + return this.getFilename(this.options.parserOptions); + } + + return 'file.ts'; + } + + // as of eslint 6 you have to provide an absolute path to the parser + // If you don't do that at the test level, the test will fail somewhat cryptically... + // This is a lot more explicit + run>( + name: string, + rule: TSESLint.RuleModule, + tests: TSESLint.RunTests, + ): void { + const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; + + // standardize the valid tests as objects + tests.valid = tests.valid.map(test => { + if (typeof test === 'string') { + return { + code: test, + }; + } + return test; + }); + + tests.valid.forEach(test => { + if (typeof test !== 'string') { + if (test.parser === parser) { + throw new Error(errorMessage); + } + if (!test.filename) { + test.filename = this.getFilename(test.parserOptions); + } + } + }); + tests.invalid.forEach(test => { + if (test.parser === parser) { + throw new Error(errorMessage); + } + if (!test.filename) { + test.filename = this.getFilename(test.parserOptions); + } + }); + + super.run(name, rule, tests); + } +} + +/** + * Simple no-op tag to mark code samples as "should not format with prettier" + * for the internal/plugin-test-formatting lint rule + */ +function noFormat(strings: TemplateStringsArray, ...keys: string[]): string { + const lastIndex = strings.length - 1; + return ( + strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex] + ); +} + +export { noFormat, RuleTester }; diff --git a/packages/experimental-utils/src/eslint-utils/index.ts b/packages/experimental-utils/src/eslint-utils/index.ts index 72977de2003..60452e9aca4 100644 --- a/packages/experimental-utils/src/eslint-utils/index.ts +++ b/packages/experimental-utils/src/eslint-utils/index.ts @@ -2,4 +2,5 @@ export * from './applyDefault'; export * from './batchedSingleLineTests'; export * from './getParserServices'; export * from './RuleCreator'; +export * from './RuleTester'; export * from './deepMerge';