Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(experimental-utils): upgrade eslint types for v7 #2023

Merged
merged 1 commit into from May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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';