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

Export rule utilities #546

Closed
viestat opened this issue Mar 20, 2020 · 5 comments
Closed

Export rule utilities #546

viestat opened this issue Mar 20, 2020 · 5 comments

Comments

@viestat
Copy link
Contributor

viestat commented Mar 20, 2020

Motivation

While working on creating some internal rules for the company I currently work for I realised that in this repo there are a lot of cool utilities and types that could be super useful for creating new rules for jest that might not exactly fit in this repository.

@SimenB
Copy link
Member

SimenB commented Mar 20, 2020

we can export them, sure. @G-Rath looked into it at some point I believe

@G-Rath
Copy link
Collaborator

G-Rath commented Mar 21, 2020

Glad you find the utils & types useful!

You should already be able to use the runtime utils by just importing them with their path relative to the plugin, but you'd need to provide a .d.ts file if you want to use them in TypeScript.

You can generate one by cloning the repo and running:

tsc src/rules/utils.ts --declaration --emitDeclarationOnly --resolveJsonModule

This will give you the following d.ts:

import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
export declare const createRule: <TOptions extends unknown[], TMessageIds extends string, TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener>({ name, meta, defaultOptions, create, }: {
    name: string;
    meta: {
        docs: Pick<TSESLint.RuleMetaDataDocs, "category" | "description" | "recommended" | "requiresTypeChecking" | "extendsBaseRule">;
    } & Pick<TSESLint.RuleMetaData<TMessageIds>, "type" | "deprecated" | "fixable" | "messages" | "replacedBy" | "schema">;
    defaultOptions: TOptions;
    create: (context: TSESLint.RuleContext<TMessageIds, TOptions>, optionsWithDefault: TOptions) => TRuleListener;
}) => TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>;
export declare type MaybeTypeCast<Expression extends TSESTree.Expression> = TSTypeCastExpression<Expression> | Expression;
declare type TSTypeCastExpression<Expression extends TSESTree.Expression = TSESTree.Expression> = AsExpressionChain<Expression> | TypeAssertionChain<Expression>;
interface AsExpressionChain<Expression extends TSESTree.Expression = TSESTree.Expression> extends TSESTree.TSAsExpression {
    expression: AsExpressionChain<Expression> | Expression;
}
interface TypeAssertionChain<Expression extends TSESTree.Expression = TSESTree.Expression> extends TSESTree.TSTypeAssertion {
    expression: TypeAssertionChain<Expression> | Expression;
}
export declare const followTypeAssertionChain: <Expression extends TSESTree.Expression>(expression: MaybeTypeCast<Expression>) => Expression;
/**
 * A `Literal` with a `value` of type `string`.
 */
interface StringLiteral<Value extends string = string> extends TSESTree.StringLiteral {
    value: Value;
}
interface TemplateLiteral<Value extends string = string> extends TSESTree.TemplateLiteral {
    quasis: [TSESTree.TemplateElement & {
        value: {
            raw: Value;
            cooked: Value;
        };
    }];
}
export declare type StringNode<S extends string = string> = StringLiteral<S> | TemplateLiteral<S>;
/**
 * Checks if the given `node` is a {@link StringNode}.
 *
 * @param {Node} node
 * @param {V?} specifics
 *
 * @return {node is StringNode}
 *
 * @template V
 */
export declare const isStringNode: <V extends string>(node: TSESTree.Node, specifics?: V) => node is StringNode<V>;
/**
 * Gets the value of the given `StringNode`.
 *
 * If the `node` is a `TemplateLiteral`, the `raw` value is used;
 * otherwise, `value` is returned instead.
 *
 * @param {StringNode<S>} node
 *
 * @return {S}
 *
 * @template S
 */
export declare const getStringValue: <S extends string>(node: StringNode<S>) => S;
/**
 * Represents a `MemberExpression` with a "known" `property`.
 */
interface KnownMemberExpression<Name extends string = string> extends TSESTree.MemberExpressionComputedName {
    property: AccessorNode<Name>;
}
/**
 * Represents a `CallExpression` with a "known" `property` accessor.
 *
 * i.e `KnownCallExpression<'includes'>` represents `.includes()`.
 */
export interface KnownCallExpression<Name extends string = string> extends TSESTree.CallExpression {
    callee: CalledKnownMemberExpression<Name>;
}
/**
 * Represents a `MemberExpression` with a "known" `property`, that is called.
 *
 * This is `KnownCallExpression` from the perspective of the `MemberExpression` node.
 */
export interface CalledKnownMemberExpression<Name extends string = string> extends KnownMemberExpression<Name> {
    parent: KnownCallExpression<Name>;
}
/**
 * Represents a `CallExpression` with a single argument.
 */
export interface CallExpressionWithSingleArgument<Argument extends TSESTree.Expression = TSESTree.Expression> extends TSESTree.CallExpression {
    arguments: [Argument];
}
/**
 * Guards that the given `call` has only one `argument`.
 *
 * @param {CallExpression} call
 *
 * @return {call is CallExpressionWithSingleArgument}
 */
export declare const hasOnlyOneArgument: (call: TSESTree.CallExpression) => call is CallExpressionWithSingleArgument<TSESTree.Expression>;
/**
 * An `Identifier` with a known `name` value - i.e `expect`.
 */
interface KnownIdentifier<Name extends string> extends TSESTree.Identifier {
    name: Name;
}
/**
 * Checks if the given `node` is a "supported accessor".
 *
 * This means that it's a node can be used to access properties,
 * and who's "value" can be statically determined.
 *
 * `MemberExpression` nodes most commonly contain accessors,
 * but it's possible for other nodes to contain them.
 *
 * If a `value` is provided & the `node` is an `AccessorNode`,
 * the `value` will be compared to that of the `AccessorNode`.
 *
 * Note that `value` here refers to the normalised value.
 * The property that holds the value is not always called `name`.
 *
 * @param {Node} node
 * @param {V?} value
 *
 * @return {node is AccessorNode<V>}
 *
 * @template V
 */
export declare const isSupportedAccessor: <V extends string>(node: TSESTree.Node, value?: V) => node is AccessorNode<V>;
/**
 * Gets the value of the given `AccessorNode`,
 * account for the different node types.
 *
 * @param {AccessorNode<S>} accessor
 *
 * @return {S}
 *
 * @template S
 */
export declare const getAccessorValue: <S extends string = string>(accessor: AccessorNode<S>) => S;
declare type AccessorNode<Specifics extends string = string> = StringNode<Specifics> | KnownIdentifier<Specifics>;
interface ExpectCall extends TSESTree.CallExpression {
    callee: AccessorNode<'expect'>;
    parent: TSESTree.Node;
}
/**
 * Checks if the given `node` is a valid `ExpectCall`.
 *
 * In order to be an `ExpectCall`, the `node` must:
 *  * be a `CallExpression`,
 *  * have an accessor named 'expect',
 *  * have a `parent`.
 *
 * @param {Node} node
 *
 * @return {node is ExpectCall}
 */
export declare const isExpectCall: (node: TSESTree.Node) => node is ExpectCall;
interface ParsedExpectMember<Name extends ExpectPropertyName = ExpectPropertyName, Node extends ExpectMember<Name> = ExpectMember<Name>> {
    name: Name;
    node: Node;
}
/**
 * Represents a `MemberExpression` that comes after an `ExpectCall`.
 */
interface ExpectMember<PropertyName extends ExpectPropertyName = ExpectPropertyName, Parent extends TSESTree.Node | undefined = TSESTree.Node | undefined> extends KnownMemberExpression<PropertyName> {
    object: ExpectCall | ExpectMember;
    parent: Parent;
}
export declare const isExpectMember: <Name extends string = string>(node: TSESTree.Node, name?: Name) => node is ExpectMember<Name, TSESTree.Node>;
/**
 * Represents all the jest matchers.
 */
declare type MatcherName = string;
declare type ExpectPropertyName = ModifierName | MatcherName;
export declare type ParsedEqualityMatcherCall<Argument extends TSESTree.Expression = TSESTree.Expression, Matcher extends EqualityMatcher = EqualityMatcher> = Omit<ParsedExpectMatcher<Matcher>, 'arguments'> & {
    arguments: [Argument];
};
export declare enum ModifierName {
    not = "not",
    rejects = "rejects",
    resolves = "resolves"
}
export declare enum EqualityMatcher {
    toBe = "toBe",
    toEqual = "toEqual",
    toStrictEqual = "toStrictEqual"
}
export declare const isParsedEqualityMatcherCall: <MatcherName_1 extends EqualityMatcher = EqualityMatcher>(matcher: ParsedExpectMatcher<string, ExpectMember<string, TSESTree.Node>>, name?: MatcherName_1) => matcher is ParsedEqualityMatcherCall<TSESTree.Expression, MatcherName_1>;
/**
 * Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
 */
export interface ParsedExpectMatcher<Matcher extends MatcherName = MatcherName, Node extends ExpectMember<Matcher> = ExpectMember<Matcher>> extends ParsedExpectMember<Matcher, Node> {
    /**
     * The arguments being passed to the matcher.
     * A value of `null` means the matcher isn't being called.
     */
    arguments: TSESTree.CallExpression['arguments'] | null;
}
declare type BaseParsedModifier<Modifier extends ModifierName = ModifierName> = ParsedExpectMember<Modifier>;
declare type NegatableModifierName = ModifierName.rejects | ModifierName.resolves;
declare type NotNegatableModifierName = ModifierName.not;
/**
 * Represents a parsed modifier that can be followed by a `not` negation modifier.
 */
interface NegatableParsedModifier<Modifier extends NegatableModifierName = NegatableModifierName> extends BaseParsedModifier<Modifier> {
    negation?: ExpectMember<ModifierName.not>;
}
/**
 * Represents a parsed modifier that cannot be followed by a `not` negation modifier.
 */
export interface NotNegatableParsedModifier<Modifier extends NotNegatableModifierName = NotNegatableModifierName> extends BaseParsedModifier<Modifier> {
    negation?: never;
}
declare type ParsedExpectModifier = NotNegatableParsedModifier | NegatableParsedModifier;
interface Expectation<ExpectNode extends ExpectCall = ExpectCall> {
    expect: ExpectNode;
    modifier?: ParsedExpectModifier;
    matcher?: ParsedExpectMatcher;
}
export declare const parseExpectCall: <ExpectNode extends ExpectCall>(expect: ExpectNode) => Expectation<ExpectNode>;
export declare enum DescribeAlias {
    'describe' = "describe",
    'fdescribe' = "fdescribe",
    'xdescribe' = "xdescribe"
}
export declare enum TestCaseName {
    'fit' = "fit",
    'it' = "it",
    'test' = "test",
    'xit' = "xit",
    'xtest' = "xtest"
}
export declare enum HookName {
    'beforeAll' = "beforeAll",
    'beforeEach' = "beforeEach",
    'afterAll' = "afterAll",
    'afterEach' = "afterEach"
}
export declare enum DescribeProperty {
    'each' = "each",
    'only' = "only",
    'skip' = "skip"
}
export declare enum TestCaseProperty {
    'each' = "each",
    'concurrent' = "concurrent",
    'only' = "only",
    'skip' = "skip",
    'todo' = "todo"
}
declare type JestFunctionName = DescribeAlias | TestCaseName | HookName;
declare type JestPropertyName = DescribeProperty | TestCaseProperty;
interface JestFunctionIdentifier<FunctionName extends JestFunctionName> extends TSESTree.Identifier {
    name: FunctionName;
}
interface JestFunctionMemberExpression<FunctionName extends JestFunctionName, PropertyName extends JestPropertyName = JestPropertyName> extends KnownMemberExpression<PropertyName> {
    object: JestFunctionIdentifier<FunctionName>;
}
interface JestFunctionCallExpressionWithMemberExpressionCallee<FunctionName extends JestFunctionName, PropertyName extends JestPropertyName = JestPropertyName> extends TSESTree.CallExpression {
    callee: JestFunctionMemberExpression<FunctionName, PropertyName>;
}
export interface JestFunctionCallExpressionWithIdentifierCallee<FunctionName extends JestFunctionName> extends TSESTree.CallExpression {
    callee: JestFunctionIdentifier<FunctionName>;
}
export declare type JestFunctionCallExpression<FunctionName extends JestFunctionName = JestFunctionName> = JestFunctionCallExpressionWithMemberExpressionCallee<FunctionName> | JestFunctionCallExpressionWithIdentifierCallee<FunctionName>;
export declare function getNodeName(node: JestFunctionMemberExpression<JestFunctionName> | JestFunctionIdentifier<JestFunctionName>): string;
export declare function getNodeName(node: TSESTree.Node): string | null;
export declare type FunctionExpression = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
export declare const isFunction: (node: TSESTree.Node) => node is FunctionExpression;
export declare const isHook: (node: TSESTree.CallExpression) => node is JestFunctionCallExpressionWithIdentifierCallee<HookName>;
export declare const getTestCallExpressionsFromDeclaredVariables: (declaredVariables: TSESLint.Scope.Variable[]) => JestFunctionCallExpression<TestCaseName>[];
export declare const isTestCase: (node: TSESTree.CallExpression) => node is JestFunctionCallExpression<TestCaseName>;
export declare const isDescribe: (node: TSESTree.CallExpression) => node is JestFunctionCallExpression<DescribeAlias>;
/**
 * Checks if the given `describe` is a call to `describe.each`.
 *
 * @param {JestFunctionCallExpression<DescribeAlias>} node
 * @return {node is JestFunctionCallExpression<DescribeAlias, DescribeProperty.each>}
 */
export declare const isDescribeEach: (node: JestFunctionCallExpression<DescribeAlias>) => node is JestFunctionCallExpressionWithMemberExpressionCallee<DescribeAlias, DescribeProperty.each>;
/**
 * Gets the arguments of the given `JestFunctionCallExpression`.
 *
 * If the `node` is an `each` call, then the arguments of the actual suite
 * are returned, rather then the `each` array argument.
 *
 * @param {JestFunctionCallExpression<DescribeAlias | TestCaseName>} node
 *
 * @return {Expression[]}
 */
export declare const getJestFunctionArguments: (node: JestFunctionCallExpression<DescribeAlias | TestCaseName>) => TSESTree.Expression[];
export declare const scopeHasLocalReference: (scope: TSESLint.Scope.Scope, referenceName: string) => boolean;
export {};

To use this locally you have to wrap it in declare module 'eslint-plugin-jest/lib/rules/utils', and remove all the existing declares like so:

`eslint-plugin-jest.d.ts`
declare module 'eslint-plugin-jest/lib/rules/utils' {
  import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
  export const createRule: <
    TOptions extends unknown[],
    TMessageIds extends string,
    TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
  >({
    name,
    meta,
    defaultOptions,
    create
  }: {
    name: string;
    meta: {
      docs: Pick<
        TSESLint.RuleMetaDataDocs,
        | 'category'
        | 'description'
        | 'recommended'
        | 'requiresTypeChecking'
        | 'extendsBaseRule'
      >;
    } & Pick<
      TSESLint.RuleMetaData<TMessageIds>,
      'type' | 'deprecated' | 'fixable' | 'messages' | 'replacedBy' | 'schema'
    >;
    defaultOptions: TOptions;
    create: (
      context: TSESLint.RuleContext<TMessageIds, TOptions>,
      optionsWithDefault: TOptions
    ) => TRuleListener;
  }) => TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>;
  export type MaybeTypeCast<Expression extends TSESTree.Expression> =
    | TSTypeCastExpression<Expression>
    | Expression;
  type TSTypeCastExpression<
    Expression extends TSESTree.Expression = TSESTree.Expression
  > = AsExpressionChain<Expression> | TypeAssertionChain<Expression>;
  interface AsExpressionChain<
    Expression extends TSESTree.Expression = TSESTree.Expression
  > extends TSESTree.TSAsExpression {
    expression: AsExpressionChain<Expression> | Expression;
  }
  interface TypeAssertionChain<
    Expression extends TSESTree.Expression = TSESTree.Expression
  > extends TSESTree.TSTypeAssertion {
    expression: TypeAssertionChain<Expression> | Expression;
  }
  export const followTypeAssertionChain: <Expression extends TSESTree.Expression>(
    expression: MaybeTypeCast<Expression>
  ) => Expression;
  /**
   * A `Literal` with a `value` of type `string`.
   */
  interface StringLiteral<Value extends string = string>
    extends TSESTree.StringLiteral {
    value: Value;
  }
  interface TemplateLiteral<Value extends string = string>
    extends TSESTree.TemplateLiteral {
    quasis: [
      TSESTree.TemplateElement & {
        value: {
          raw: Value;
          cooked: Value;
        };
      }
    ];
  }
  export type StringNode<S extends string = string> =
    | StringLiteral<S>
    | TemplateLiteral<S>;
  /**
   * Checks if the given `node` is a {@link StringNode}.
   *
   * @param {Node} node
   * @param {V?} specifics
   *
   * @return {node is StringNode}
   *
   * @template V
   */
  export const isStringNode: <V extends string>(
    node: TSESTree.Node,
    specifics?: V
  ) => node is StringNode<V>;
  /**
   * Gets the value of the given `StringNode`.
   *
   * If the `node` is a `TemplateLiteral`, the `raw` value is used;
   * otherwise, `value` is returned instead.
   *
   * @param {StringNode<S>} node
   *
   * @return {S}
   *
   * @template S
   */
  export const getStringValue: <S extends string>(
    node: StringNode<S>
  ) => S;
  /**
   * Represents a `MemberExpression` with a "known" `property`.
   */
  interface KnownMemberExpression<Name extends string = string>
    extends TSESTree.MemberExpressionComputedName {
    property: AccessorNode<Name>;
  }
  /**
   * Represents a `CallExpression` with a "known" `property` accessor.
   *
   * i.e `KnownCallExpression<'includes'>` represents `.includes()`.
   */
  export interface KnownCallExpression<Name extends string = string>
    extends TSESTree.CallExpression {
    callee: CalledKnownMemberExpression<Name>;
  }
  /**
   * Represents a `MemberExpression` with a "known" `property`, that is called.
   *
   * This is `KnownCallExpression` from the perspective of the `MemberExpression` node.
   */
  export interface CalledKnownMemberExpression<Name extends string = string>
    extends KnownMemberExpression<Name> {
    parent: KnownCallExpression<Name>;
  }
  /**
   * Represents a `CallExpression` with a single argument.
   */
  export interface CallExpressionWithSingleArgument<
    Argument extends TSESTree.Expression = TSESTree.Expression
  > extends TSESTree.CallExpression {
    arguments: [Argument];
  }
  /**
   * Guards that the given `call` has only one `argument`.
   *
   * @param {CallExpression} call
   *
   * @return {call is CallExpressionWithSingleArgument}
   */
  export const hasOnlyOneArgument: (
    call: TSESTree.CallExpression
  ) => call is CallExpressionWithSingleArgument<TSESTree.Expression>;
  /**
   * An `Identifier` with a known `name` value - i.e `expect`.
   */
  interface KnownIdentifier<Name extends string> extends TSESTree.Identifier {
    name: Name;
  }
  /**
   * Checks if the given `node` is a "supported accessor".
   *
   * This means that it's a node can be used to access properties,
   * and who's "value" can be statically determined.
   *
   * `MemberExpression` nodes most commonly contain accessors,
   * but it's possible for other nodes to contain them.
   *
   * If a `value` is provided & the `node` is an `AccessorNode`,
   * the `value` will be compared to that of the `AccessorNode`.
   *
   * Note that `value` here refers to the normalised value.
   * The property that holds the value is not always called `name`.
   *
   * @param {Node} node
   * @param {V?} value
   *
   * @return {node is AccessorNode<V>}
   *
   * @template V
   */
  export const isSupportedAccessor: <V extends string>(
    node: TSESTree.Node,
    value?: V
  ) => node is AccessorNode<V>;
  /**
   * Gets the value of the given `AccessorNode`,
   * account for the different node types.
   *
   * @param {AccessorNode<S>} accessor
   *
   * @return {S}
   *
   * @template S
   */
  export const getAccessorValue: <S extends string = string>(
    accessor: AccessorNode<S>
  ) => S;
  type AccessorNode<Specifics extends string = string> =
    | StringNode<Specifics>
    | KnownIdentifier<Specifics>;
  interface ExpectCall extends TSESTree.CallExpression {
    callee: AccessorNode<'expect'>;
    parent: TSESTree.Node;
  }
  /**
   * Checks if the given `node` is a valid `ExpectCall`.
   *
   * In order to be an `ExpectCall`, the `node` must:
   *  * be a `CallExpression`,
   *  * have an accessor named 'expect',
   *  * have a `parent`.
   *
   * @param {Node} node
   *
   * @return {node is ExpectCall}
   */
  export const isExpectCall: (
    node: TSESTree.Node
  ) => node is ExpectCall;
  interface ParsedExpectMember<
    Name extends ExpectPropertyName = ExpectPropertyName,
    Node extends ExpectMember<Name> = ExpectMember<Name>
  > {
    name: Name;
    node: Node;
  }
  /**
   * Represents a `MemberExpression` that comes after an `ExpectCall`.
   */
  interface ExpectMember<
    PropertyName extends ExpectPropertyName = ExpectPropertyName,
    Parent extends TSESTree.Node | undefined = TSESTree.Node | undefined
  > extends KnownMemberExpression<PropertyName> {
    object: ExpectCall | ExpectMember;
    parent: Parent;
  }
  export const isExpectMember: <Name extends string = string>(
    node: TSESTree.Node,
    name?: Name
  ) => node is ExpectMember<Name, TSESTree.Node>;
  /**
   * Represents all the jest matchers.
   */
  type MatcherName = string;
  type ExpectPropertyName = ModifierName | MatcherName;
  export type ParsedEqualityMatcherCall<
    Argument extends TSESTree.Expression = TSESTree.Expression,
    Matcher extends EqualityMatcher = EqualityMatcher
  > = Omit<ParsedExpectMatcher<Matcher>, 'arguments'> & {
    arguments: [Argument];
  };
  export enum ModifierName {
    not = 'not',
    rejects = 'rejects',
    resolves = 'resolves'
  }
  export enum EqualityMatcher {
    toBe = 'toBe',
    toEqual = 'toEqual',
    toStrictEqual = 'toStrictEqual'
  }
  export const isParsedEqualityMatcherCall: <MatcherName_1 extends EqualityMatcher = EqualityMatcher>(
    matcher: ParsedExpectMatcher<string, ExpectMember<string, TSESTree.Node>>,
    name?: MatcherName_1
  ) => matcher is ParsedEqualityMatcherCall<TSESTree.Expression, MatcherName_1>;
  /**
   * Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
   */
  export interface ParsedExpectMatcher<
    Matcher extends MatcherName = MatcherName,
    Node extends ExpectMember<Matcher> = ExpectMember<Matcher>
  > extends ParsedExpectMember<Matcher, Node> {
    /**
     * The arguments being passed to the matcher.
     * A value of `null` means the matcher isn't being called.
     */
    arguments: TSESTree.CallExpression['arguments'] | null;
  }
  type BaseParsedModifier<
    Modifier extends ModifierName = ModifierName
  > = ParsedExpectMember<Modifier>;
  type NegatableModifierName =
    | ModifierName.rejects
    | ModifierName.resolves;
  type NotNegatableModifierName = ModifierName.not;
  /**
   * Represents a parsed modifier that can be followed by a `not` negation modifier.
   */
  interface NegatableParsedModifier<
    Modifier extends NegatableModifierName = NegatableModifierName
  > extends BaseParsedModifier<Modifier> {
    negation?: ExpectMember<ModifierName.not>;
  }
  /**
   * Represents a parsed modifier that cannot be followed by a `not` negation modifier.
   */
  export interface NotNegatableParsedModifier<
    Modifier extends NotNegatableModifierName = NotNegatableModifierName
  > extends BaseParsedModifier<Modifier> {
    negation?: never;
  }
  type ParsedExpectModifier =
    | NotNegatableParsedModifier
    | NegatableParsedModifier;
  interface Expectation<ExpectNode extends ExpectCall = ExpectCall> {
    expect: ExpectNode;
    modifier?: ParsedExpectModifier;
    matcher?: ParsedExpectMatcher;
  }
  export const parseExpectCall: <ExpectNode extends ExpectCall>(
    expect: ExpectNode
  ) => Expectation<ExpectNode>;
  export enum DescribeAlias {
    'describe' = 'describe',
    'fdescribe' = 'fdescribe',
    'xdescribe' = 'xdescribe'
  }
  export enum TestCaseName {
    'fit' = 'fit',
    'it' = 'it',
    'test' = 'test',
    'xit' = 'xit',
    'xtest' = 'xtest'
  }
  export enum HookName {
    'beforeAll' = 'beforeAll',
    'beforeEach' = 'beforeEach',
    'afterAll' = 'afterAll',
    'afterEach' = 'afterEach'
  }
  export enum DescribeProperty {
    'each' = 'each',
    'only' = 'only',
    'skip' = 'skip'
  }
  export enum TestCaseProperty {
    'each' = 'each',
    'concurrent' = 'concurrent',
    'only' = 'only',
    'skip' = 'skip',
    'todo' = 'todo'
  }
  type JestFunctionName = DescribeAlias | TestCaseName | HookName;
  type JestPropertyName = DescribeProperty | TestCaseProperty;
  interface JestFunctionIdentifier<FunctionName extends JestFunctionName>
    extends TSESTree.Identifier {
    name: FunctionName;
  }
  interface JestFunctionMemberExpression<
    FunctionName extends JestFunctionName,
    PropertyName extends JestPropertyName = JestPropertyName
  > extends KnownMemberExpression<PropertyName> {
    object: JestFunctionIdentifier<FunctionName>;
  }
  interface JestFunctionCallExpressionWithMemberExpressionCallee<
    FunctionName extends JestFunctionName,
    PropertyName extends JestPropertyName = JestPropertyName
  > extends TSESTree.CallExpression {
    callee: JestFunctionMemberExpression<FunctionName, PropertyName>;
  }
  export interface JestFunctionCallExpressionWithIdentifierCallee<
    FunctionName extends JestFunctionName
  > extends TSESTree.CallExpression {
    callee: JestFunctionIdentifier<FunctionName>;
  }
  export type JestFunctionCallExpression<
    FunctionName extends JestFunctionName = JestFunctionName
  > =
    | JestFunctionCallExpressionWithMemberExpressionCallee<FunctionName>
    | JestFunctionCallExpressionWithIdentifierCallee<FunctionName>;
  export function getNodeName(
    node:
      | JestFunctionMemberExpression<JestFunctionName>
      | JestFunctionIdentifier<JestFunctionName>
  ): string;
  export function getNodeName(node: TSESTree.Node): string | null;
  export type FunctionExpression =
    | TSESTree.ArrowFunctionExpression
    | TSESTree.FunctionExpression;
  export const isFunction: (
    node: TSESTree.Node
  ) => node is FunctionExpression;
  export const isHook: (
    node: TSESTree.CallExpression
  ) => node is JestFunctionCallExpressionWithIdentifierCallee<HookName>;
  export const getTestCallExpressionsFromDeclaredVariables: (
    Variables: TSESLint.Scope.Variable[]
  ) => JestFunctionCallExpression<TestCaseName>[];
  export const isTestCase: (
    node: TSESTree.CallExpression
  ) => node is JestFunctionCallExpression<TestCaseName>;
  export const isDescribe: (
    node: TSESTree.CallExpression
  ) => node is JestFunctionCallExpression<DescribeAlias>;
  /**
   * Checks if the given `describe` is a call to `describe.each`.
   *
   * @param {JestFunctionCallExpression<DescribeAlias>} node
   * @return {node is JestFunctionCallExpression<DescribeAlias, DescribeProperty.each>}
   */
  export const isDescribeEach: (
    node: JestFunctionCallExpression<DescribeAlias>
  ) => node is JestFunctionCallExpressionWithMemberExpressionCallee<
    DescribeAlias,
    DescribeProperty.each
  >;
  /**
   * Gets the arguments of the given `JestFunctionCallExpression`.
   *
   * If the `node` is an `each` call, then the arguments of the actual suite
   * are returned, rather then the `each` array argument.
   *
   * @param {JestFunctionCallExpression<DescribeAlias | TestCaseName>} node
   *
   * @return {Expression[]}
   */
  export const getJestFunctionArguments: (
    node: JestFunctionCallExpression<DescribeAlias | TestCaseName>
  ) => TSESTree.Expression[];
  export const scopeHasLocalReference: (
    scope: TSESLint.Scope.Scope,
    referenceName: string
  ) => boolean;
  export {};
}

That file wouldn't need to be published in your package, since it's not needed at runtime.

I'd be happy with shipping a .d.ts with the caveat that it's possible we might change the logic of a utility, or remove or rename them in non-major versions - while I don't foresee this happening a lot, it is not impossible, especially due the nature of TypeScript & @typescript-eslint.

For example, @typescript-eslint changed the shape of their semi-internal types for ts-estree which meant our utils hit a design limitation of TypeScript. Luckily in that case it only affected one conditional in our code so I was able to refactor the logic there instead of reshaping the utils, but it was very much on the table.

If something did change that you'd like to discuss or get info on, I'd be happy for you to open a new issue here :)

@viestat
Copy link
Contributor Author

viestat commented Mar 23, 2020

@G-Rath Thanks for you answer!
I think it would be amazing if you would indeed ship a .d.ts. I think the caveat you mention is reasonable and if such thing would ever happen then it will be probably addressed then (I would be happy to contribute in any way 😄).

Keep up the good work !

@G-Rath
Copy link
Collaborator

G-Rath commented May 28, 2022

So I'm thinking about closing this as wontfix as it's been at the back of my mind every time I've been working on our utils, and I think I'd actually prefer not to be shipping types for something that is not public.

I love that someone is finding value in these utils as I've put a lot of work into them, but I feel like everytime they change it would be breaking because of their nature (not to mention the huge refactor I'm in the progress of doing). This means if you're using them you would want to lock the version of eslint-plugin-jest you're using to a specific version (you might be able to get away with patches) and then you might as well generate the definition files yourself since that should take less than 5 minutes.

This also means we're not bloating the published package with extra files that are probably only being used by a few people -while it's about 30kb I still think it's a mentionable tradeoff given the option of generating the types yourself.

I've often thought about how these could be published, as in theory they'd be useful for eslint-plugin-jest-extended (if we ever actually manage to get that published 😞) and eslint-plugin-jest-dom but so far the APIs have seemed to just narrowly not overlap enough for the gain (& pain) to not seem worth it yet.

If it turns out a bunch of people are actually heavily using (or would like to use) these utils, that'd be good to know.

@G-Rath
Copy link
Collaborator

G-Rath commented Aug 27, 2022

Closing per my comment above

@G-Rath G-Rath closed this as not planned Won't fix, can't repro, duplicate, stale Aug 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants