-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(experimental-utils): expose our RuleTester extension (#1948)
- Loading branch information
1 parent
383f931
commit 2dd1638
Showing
4 changed files
with
120 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TSESLint.RuleTesterConfig, 'parser'> & { | ||
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TSESLint.RuleTesterConfig, 'parser'> & { | ||
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<TMessageIds extends string, TOptions extends Readonly<unknown[]>>( | ||
name: string, | ||
rule: TSESLint.RuleModule<TMessageIds, TOptions>, | ||
tests: TSESLint.RunTests<TMessageIds, TOptions>, | ||
): 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 }; |
109 changes: 109 additions & 0 deletions
109
packages/experimental-utils/src/eslint-utils/RuleTester.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import * as TSESLint from '../ts-eslint'; | ||
import * as path from 'path'; | ||
|
||
const parser = '@typescript-eslint/parser'; | ||
|
||
type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & { | ||
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<TMessageIds extends string, TOptions extends Readonly<unknown[]>>( | ||
name: string, | ||
rule: TSESLint.RuleModule<TMessageIds, TOptions>, | ||
tests: TSESLint.RunTests<TMessageIds, TOptions>, | ||
): 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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters