Skip to content

Commit

Permalink
feat(experimental-utils): upgrade eslint types for v7 (#2023)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed May 21, 2020
1 parent 208de71 commit 06869c9
Show file tree
Hide file tree
Showing 22 changed files with 1,311 additions and 322 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Expand Up @@ -40,6 +40,7 @@
"ASTs",
"autofix",
"autofixers",
"autofixes",
"backticks",
"bigint",
"bivariant",
Expand Down Expand Up @@ -70,6 +71,7 @@
"performant",
"pluggable",
"postprocess",
"postprocessor",
"Premade",
"prettier's",
"recurse",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin/src/rules/array-type.ts
Expand Up @@ -147,7 +147,7 @@ export default util.createRule<Options, MessageIds>({
}

const nextToken = sourceCode.getTokenAfter(prevToken);
if (nextToken && sourceCode.isSpaceBetweenTokens(prevToken, nextToken)) {
if (nextToken && sourceCode.isSpaceBetween(prevToken, nextToken)) {
return false;
}

Expand Down
Expand Up @@ -273,9 +273,11 @@ export class OffsetStorage {
* Gets the first token that the given token's indentation is dependent on
* @returns The token that the given token depends on, or `null` if the given token is at the top level
*/
getFirstDependency(
token: Exclude<TSESTree.Token, TSESTree.Comment>,
): Exclude<TSESTree.Token, TSESTree.Comment> | null;
getFirstDependency(token: TSESTree.Token): TSESTree.Token | null;
getFirstDependency(token: TokenOrComment): TokenOrComment | null;
getFirstDependency(token: TokenOrComment): TokenOrComment | null {
getFirstDependency(token: TSESTree.Token): TSESTree.Token | null {
return this.getOffsetDescriptor(token).from;
}
}
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/rules/no-unused-expressions.ts
Expand Up @@ -2,7 +2,10 @@ import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/no-unused-expressions';
import * as util from '../util';

export default util.createRule({
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
name: 'no-unused-expressions',
meta: {
type: 'suggestion',
Expand Down
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/rules/no-unused-vars.ts
Expand Up @@ -5,7 +5,10 @@ import {
import baseRule from 'eslint/lib/rules/no-unused-vars';
import * as util from '../util';

export default util.createRule({
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
name: 'no-unused-vars',
meta: {
type: 'problem',
Expand Down
Expand Up @@ -145,7 +145,7 @@ export default util.createRule<Options, MessageIds>({
rightToken = sourceCode.getFirstToken(node, util.isOpeningParenToken)!;
leftToken = sourceCode.getTokenBefore(rightToken)!;
}
const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
const hasSpacing = sourceCode.isSpaceBetween(leftToken, rightToken);

if (hasSpacing && functionConfig === 'never') {
context.report({
Expand Down
14 changes: 13 additions & 1 deletion packages/eslint-plugin/src/util/index.ts
Expand Up @@ -15,4 +15,16 @@ const {
isObjectNotArray,
getParserServices,
} = ESLintUtils;
export { applyDefault, deepMerge, isObjectNotArray, getParserServices };
type InferMessageIdsTypeFromRule<T> = ESLintUtils.InferMessageIdsTypeFromRule<
T
>;
type InferOptionsTypeFromRule<T> = ESLintUtils.InferOptionsTypeFromRule<T>;

export {
applyDefault,
deepMerge,
isObjectNotArray,
getParserServices,
InferMessageIdsTypeFromRule,
InferOptionsTypeFromRule,
};
28 changes: 0 additions & 28 deletions packages/eslint-plugin/src/util/misc.ts
Expand Up @@ -22,32 +22,6 @@ function upperCaseFirst(str: string): string {
return str[0].toUpperCase() + str.slice(1);
}

type InferOptionsTypeFromRuleNever<T> = T extends TSESLint.RuleModule<
never,
infer TOptions
>
? TOptions
: unknown;
/**
* Uses type inference to fetch the TOptions type from the given RuleModule
*/
type InferOptionsTypeFromRule<T> = T extends TSESLint.RuleModule<
string,
infer TOptions
>
? TOptions
: InferOptionsTypeFromRuleNever<T>;

/**
* Uses type inference to fetch the TMessageIds type from the given RuleModule
*/
type InferMessageIdsTypeFromRule<T> = T extends TSESLint.RuleModule<
infer TMessageIds,
unknown[]
>
? TMessageIds
: unknown;

/** Return true if both parameters are equal. */
type Equal<T> = (a: T, b: T) => boolean;

Expand Down Expand Up @@ -136,8 +110,6 @@ export {
getEnumNames,
getNameFromIndexSignature,
getNameFromMember,
InferMessageIdsTypeFromRule,
InferOptionsTypeFromRule,
isDefinitionFile,
RequireKeys,
upperCaseFirst,
Expand Down
Expand Up @@ -23,12 +23,15 @@ const hasExport = /^export/m;
function makeExternalModule<
T extends ValidTestCase<Options> | InvalidTestCase<MessageIds, Options>
>(tests: T[]): T[] {
tests.forEach(t => {
return tests.map(t => {
if (!hasExport.test(t.code)) {
t.code = `${t.code}\nexport const __externalModule = 1;`;
return {
...t,
code: `${t.code}\nexport const __externalModule = 1;`,
};
}
return t;
});
return tests;
}

const DEFAULT_IGNORED_REGEX = new RegExp(
Expand Down
26 changes: 26 additions & 0 deletions packages/experimental-utils/src/eslint-utils/InferTypesFromRule.ts
@@ -0,0 +1,26 @@
import { RuleModule } from '../ts-eslint';

type InferOptionsTypeFromRuleNever<T> = T extends RuleModule<
never,
infer TOptions
>
? TOptions
: unknown;
/**
* Uses type inference to fetch the TOptions type from the given RuleModule
*/
type InferOptionsTypeFromRule<T> = T extends RuleModule<string, infer TOptions>
? TOptions
: InferOptionsTypeFromRuleNever<T>;

/**
* Uses type inference to fetch the TMessageIds type from the given RuleModule
*/
type InferMessageIdsTypeFromRule<T> = T extends RuleModule<
infer TMessageIds,
unknown[]
>
? TMessageIds
: unknown;

export { InferOptionsTypeFromRule, InferMessageIdsTypeFromRule };
16 changes: 9 additions & 7 deletions packages/experimental-utils/src/eslint-utils/RuleCreator.ts
Expand Up @@ -7,7 +7,7 @@ import {
} from '../ts-eslint/Rule';
import { applyDefault } from './applyDefault';

// we'll automatically add the url + tslint description for people.
// we automatically add the url
type CreateRuleMetaDocs = Omit<RuleMetaDataDocs, 'url'>;
type CreateRuleMeta<TMessageIds extends string> = {
docs: CreateRuleMetaDocs;
Expand All @@ -25,15 +25,15 @@ function RuleCreator(urlCreator: (ruleName: string) => string) {
meta,
defaultOptions,
create,
}: {
}: Readonly<{
name: string;
meta: CreateRuleMeta<TMessageIds>;
defaultOptions: TOptions;
defaultOptions: Readonly<TOptions>;
create: (
context: RuleContext<TMessageIds, TOptions>,
optionsWithDefault: TOptions,
context: Readonly<RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>,
) => TRuleListener;
}): RuleModule<TMessageIds, TOptions, TRuleListener> {
}>): RuleModule<TMessageIds, TOptions, TRuleListener> {
return {
meta: {
...meta,
Expand All @@ -42,7 +42,9 @@ function RuleCreator(urlCreator: (ruleName: string) => string) {
url: urlCreator(name),
},
},
create(context): TRuleListener {
create(
context: Readonly<RuleContext<TMessageIds, TOptions>>,
): TRuleListener {
const optionsWithDefault = applyDefault(
defaultOptions,
context.options,
Expand Down
32 changes: 23 additions & 9 deletions packages/experimental-utils/src/eslint-utils/RuleTester.ts
@@ -1,5 +1,5 @@
import * as TSESLint from '../ts-eslint';
import * as path from 'path';
import * as TSESLint from '../ts-eslint';

const parser = '@typescript-eslint/parser';

Expand All @@ -8,10 +8,12 @@ type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & {
};

class RuleTester extends TSESLint.RuleTester {
readonly #options: RuleTesterConfig;

// 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) {
constructor(options: RuleTesterConfig) {
super({
...options,
parserOptions: {
Expand All @@ -22,6 +24,8 @@ class RuleTester extends TSESLint.RuleTester {
parser: require.resolve(options.parser),
});

this.#options = options;

// 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(() => {
Expand Down Expand Up @@ -49,8 +53,8 @@ class RuleTester extends TSESLint.RuleTester {
}

return filename;
} else if (this.options.parserOptions) {
return this.getFilename(this.options.parserOptions);
} else if (this.#options.parserOptions) {
return this.getFilename(this.#options.parserOptions);
}

return 'file.ts';
Expand All @@ -62,10 +66,12 @@ class RuleTester extends TSESLint.RuleTester {
run<TMessageIds extends string, TOptions extends Readonly<unknown[]>>(
name: string,
rule: TSESLint.RuleModule<TMessageIds, TOptions>,
tests: TSESLint.RunTests<TMessageIds, TOptions>,
testsReadonly: 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}`;

const tests = { ...testsReadonly };

// standardize the valid tests as objects
tests.valid = tests.valid.map(test => {
if (typeof test === 'string') {
Expand All @@ -76,23 +82,31 @@ class RuleTester extends TSESLint.RuleTester {
return test;
});

tests.valid.forEach(test => {
tests.valid = tests.valid.map(test => {
if (typeof test !== 'string') {
if (test.parser === parser) {
throw new Error(errorMessage);
}
if (!test.filename) {
test.filename = this.getFilename(test.parserOptions);
return {
...test,
filename: this.getFilename(test.parserOptions),
};
}
}
return test;
});
tests.invalid.forEach(test => {
tests.invalid = tests.invalid.map(test => {
if (test.parser === parser) {
throw new Error(errorMessage);
}
if (!test.filename) {
test.filename = this.getFilename(test.parserOptions);
return {
...test,
filename: this.getFilename(test.parserOptions),
};
}
return test;
});

super.run(name, rule, tests);
Expand Down
1 change: 1 addition & 0 deletions packages/experimental-utils/src/eslint-utils/index.ts
@@ -1,6 +1,7 @@
export * from './applyDefault';
export * from './batchedSingleLineTests';
export * from './getParserServices';
export * from './InferTypesFromRule';
export * from './RuleCreator';
export * from './RuleTester';
export * from './deepMerge';

0 comments on commit 06869c9

Please sign in to comment.