CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typescript-eslint--experimental-utils

(Experimental) Utilities for working with TypeScript + ESLint together

Pending
Overview
Eval results
Files

eslint-utils.mddocs/

ESLint Integration Utilities

Rule creation, testing, and configuration utilities specifically designed for TypeScript ESLint rules. These utilities provide a complete toolkit for developing, testing, and configuring ESLint rules with full TypeScript integration.

Capabilities

Rule Creation

Utilities for creating TypeScript-aware ESLint rules with automatic documentation and type safety.

/**
 * Creates a rule creator function with automatic documentation URL generation
 * @param urlCreator - Function that generates documentation URLs from rule names
 * @returns Function for creating rules with automatic docs URLs
 */
function RuleCreator(urlCreator: (ruleName: string) => string): <
  TOptions extends readonly unknown[],
  TMessageIds extends string,
  TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
>(ruleDefinition: Readonly<TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>>) => TSESLint.RuleModule<TMessageIds, TOptions>;

/**
 * Creates a rule without automatic documentation URL
 * @param ruleDefinition - Rule definition object
 * @returns ESLint rule module
 */
declare namespace RuleCreator {
  function withoutDocs<
    TOptions extends readonly unknown[],
    TMessageIds extends string,
    TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
  >(ruleDefinition: Readonly<TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>>): TSESLint.RuleModule<TMessageIds, TOptions>;
}

Usage Example:

import { ESLintUtils, TSESLint } from "@typescript-eslint/experimental-utils";

const createRule = ESLintUtils.RuleCreator(
  name => `https://typescript-eslint.io/rules/${name}`
);

const rule = createRule({
  name: 'my-rule',
  meta: {
    type: 'problem',
    messages: {
      error: 'This is an error message'
    },
    schema: []
  },
  defaultOptions: [],
  create(context: TSESLint.RuleContext<'error', []>) {
    return {
      FunctionDeclaration(node) {
        context.report({
          node,
          messageId: 'error'
        });
      }
    };
  }
});

Parser Services

Utilities for accessing TypeScript compiler services within ESLint rules.

/**
 * Retrieves TypeScript parser services from the rule context
 * @param context - ESLint rule context
 * @param allowWithoutFullTypeInformation - Whether to allow partial type information
 * @returns Parser services object with TypeScript compiler access
 * @throws Error if parser services are not available
 */
function getParserServices<TMessageIds extends string, TOptions extends readonly unknown[]>(
  context: TSESLint.RuleContext<TMessageIds, TOptions>,
  allowWithoutFullTypeInformation?: boolean
): TSESTree.ParserServices;

Usage Example:

import { ESLintUtils, TSESTree } from "@typescript-eslint/experimental-utils";

const rule = ESLintUtils.RuleCreator(url => url)({
  name: 'type-aware-rule',
  meta: {
    type: 'problem',
    messages: { error: 'Type error' },
    schema: []
  },
  defaultOptions: [],
  create(context) {
    const services = ESLintUtils.getParserServices(context);
    const checker = services.program?.getTypeChecker();
    
    return {
      Identifier(node) {
        if (checker) {
          const tsNode = services.esTreeNodeToTSNodeMap.get(node);
          const type = checker.getTypeAtLocation(tsNode);
          // Use TypeScript type information
        }
      }
    };
  }
});

Configuration Utilities

Utilities for handling rule options and configuration merging.

/**
 * Applies default options to user-provided options using deep merge
 * @param defaultOptions - Default option values
 * @param userOptions - User-provided options (can be null)
 * @returns Merged options with defaults applied
 */
function applyDefault<TUser, TDefault>(
  defaultOptions: Readonly<TDefault>,
  userOptions: Readonly<TUser> | null
): TDefault;

/**
 * Deep merges two objects, combining properties recursively
 * @param first - First object to merge
 * @param second - Second object to merge
 * @returns Merged object with combined properties
 */
function deepMerge(first?: ObjectLike, second?: ObjectLike): Record<string, unknown>;

/**
 * Type guard to check if a value is an object but not an array
 * @param obj - Value to check
 * @returns True if the value is an object (not array)
 */
function isObjectNotArray<T = Record<string, unknown>>(obj: unknown): obj is T;

interface ObjectLike {
  [key: string]: unknown;
}

Usage Example:

import { ESLintUtils } from "@typescript-eslint/experimental-utils";

interface MyRuleOptions {
  checkTypes: boolean;
  ignorePatterns: string[];
  severity: 'error' | 'warn';
}

const defaultOptions: MyRuleOptions = {
  checkTypes: true,
  ignorePatterns: [],
  severity: 'error'
};

const rule = ESLintUtils.RuleCreator(url => url)({
  name: 'my-rule',
  meta: {
    type: 'problem',
    messages: { error: 'Error' },
    schema: [/* schema */]
  },
  defaultOptions: [defaultOptions],
  create(context, [userOptions]) {
    const options = ESLintUtils.applyDefault(defaultOptions, userOptions);
    // options now has all properties with defaults applied
    
    return {
      // rule implementation
    };
  }
});

Type Utilities

Type utilities for inferring types from rule definitions.

/**
 * Infers the options type from a rule module
 */
type InferOptionsTypeFromRule<T> = T extends TSESLint.RuleModule<string, infer TOptions, TSESLint.RuleListener> ? TOptions : unknown;

/**
 * Infers the message IDs type from a rule module
 */
type InferMessageIdsTypeFromRule<T> = T extends TSESLint.RuleModule<infer TMessageIds, readonly unknown[], TSESLint.RuleListener> ? TMessageIds : string;

Utility Functions

Helper functions for common rule development tasks.

/**
 * Non-null assertion with custom error message
 * @param value - Value to check for null/undefined
 * @param message - Error message if value is null/undefined
 * @returns Non-null value
 * @throws Error if value is null or undefined
 */
function nullThrows<T>(value: T | null | undefined, message: string): T;

/**
 * Common error messages for nullThrows function
 */
const NullThrowsReasons: {
  readonly MissingParent: "Expected node to have a parent.";
  readonly MissingToken: "Expected to find a token.";
};

Usage Example:

import { ESLintUtils, TSESTree } from "@typescript-eslint/experimental-utils";

function analyzeNode(node: TSESTree.Node): void {
  const parent = ESLintUtils.nullThrows(
    node.parent, 
    ESLintUtils.NullThrowsReasons.MissingParent
  );
  // parent is now guaranteed to be non-null
}

Rule Testing

Enhanced rule testing utilities with TypeScript support and batch test processing.

/**
 * Enhanced ESLint rule tester with TypeScript support
 */
class RuleTester {
  /**
   * Creates a new rule tester with base configuration
   * @param baseOptions - Base configuration for the rule tester
   */
  constructor(baseOptions: TSESLint.RuleTesterConfig);

  /**
   * Runs tests for a specific rule with TypeScript support
   * @param name - Name of the rule being tested
   * @param rule - Rule module to test
   * @param tests - Test cases (valid and invalid)
   */
  run<TMessageIds extends string, TOptions extends readonly unknown[]>(
    name: string,
    rule: TSESLint.RuleModule<TMessageIds, TOptions>,
    tests: TSESLint.RunTests<TMessageIds, TOptions>
  ): void;

  /**
   * Static cleanup function called after all tests
   */
  static afterAll: (() => void) | undefined;
}

/**
 * Template tag to mark code as "no format" for testing purposes
 * @param raw - Template strings array
 * @param keys - Template substitution values
 * @returns Formatted string marked as no-format
 */
function noFormat(raw: TemplateStringsArray, ...keys: string[]): string;

/**
 * Converts batch test cases into individual test cases
 * @param test - Batch test configuration
 * @returns Array of individual test cases
 */
function batchedSingleLineTests<TOptions extends readonly unknown[]>(test: {
  code: string;
  options?: TOptions;
  skip?: boolean;
  only?: boolean;
}): TSESLint.ValidTestCase<TOptions>[];

function batchedSingleLineTests<TMessageIds extends string, TOptions extends readonly unknown[]>(test: {
  code: string;
  options?: TOptions;
  skip?: boolean;
  only?: boolean;
  errors: TestCaseError<TMessageIds>[];
  output?: string | null;
}): TSESLint.InvalidTestCase<TMessageIds, TOptions>[];

interface TestCaseError<TMessageIds extends string> {
  messageId: TMessageIds;  
  data?: Record<string, unknown>;
  type?: string;
  line?: number;
  column?: number;
  endLine?: number;
  endColumn?: number;
  suggestions?: SuggestionOutput<TMessageIds>[];
}

interface SuggestionOutput<TMessageIds extends string> {
  messageId: TMessageIds;
  data?: Record<string, unknown>;
  output: string;
  desc?: string;
}

Usage Example:

import { ESLintUtils, TSESLint } from "@typescript-eslint/experimental-utils";

const ruleTester = new ESLintUtils.RuleTester({
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    project: './tsconfig.json'
  }
});

const rule = ESLintUtils.RuleCreator(url => url)({
  name: 'test-rule',
  meta: {
    type: 'problem',
    messages: {
      error: 'Found error in {{name}}'
    },
    schema: []
  },
  defaultOptions: [],
  create(context) {
    return {
      FunctionDeclaration(node) {
        context.report({
          node,
          messageId: 'error',
          data: { name: node.id?.name || 'unknown' }
        });
      }
    };
  }
});

ruleTester.run('test-rule', rule, {
  valid: [
    'const x = 1;',
    'class MyClass {}',
    {
      code: 'const arrow = () => {};',
      parserOptions: { ecmaVersion: 6 }
    }
  ],
  invalid: [
    {
      code: 'function foo() {}',
      errors: [{
        messageId: 'error',
        data: { name: 'foo' },
        line: 1,
        column: 1
      }]
    },
    {
      code: ESLintUtils.noFormat`
        function bar() {
          // some code
        }
      `,
      errors: [{ messageId: 'error' }]
    }
  ]
});

// Using batched tests
const batchTests = ESLintUtils.batchedSingleLineTests({
  code: `
    function a() {}
    function b() {}  
    function c() {}
  `,
  errors: [
    { messageId: 'error', line: 2 },
    { messageId: 'error', line: 3 },
    { messageId: 'error', line: 4 }
  ]
});

Dependency Constraints

Utilities for checking package version dependencies in tests.

/**
 * Checks if all specified dependency version constraints are satisfied
 * @param dependencyConstraints - Object mapping package names to version constraints
 * @returns True if all constraints are satisfied
 */
function satisfiesAllDependencyConstraints(
  dependencyConstraints?: DependencyConstraint
): boolean;

/**
 * Interface for specifying package version constraints
 */
interface DependencyConstraint {
  [packageName: string]: VersionConstraint;
}

type VersionConstraint = string | Range | SemVer;

interface Range {
  range: string;
  raw: string;
  loose: boolean;
  includePrerelease: boolean;
  test(version: string | SemVer): boolean;
  intersects(range: Range): boolean;
}

interface SemVer {
  version: string;
  major: number;
  minor: number;
  patch: number;
  prerelease: readonly (string | number)[];
  build: readonly string[];
  compare(other: string | SemVer): -1 | 0 | 1;
  compareMain(other: string | SemVer): -1 | 0 | 1;
}

Usage Example:

import { ESLintUtils } from "@typescript-eslint/experimental-utils";

// Check if TypeScript version meets requirements
const hasRequiredTypescript = ESLintUtils.satisfiesAllDependencyConstraints({
  typescript: '>=4.0.0'
});

if (hasRequiredTypescript) {
  // Run TypeScript-specific tests
} else {
  // Skip tests that require newer TypeScript
}

Test Case Types

/**
 * Valid test case configuration
 */
interface ValidTestCase<TOptions extends readonly unknown[]> {
  /** Source code that should not trigger any errors */
  code: string;
  /** Rule options to use for this test */
  options?: TOptions;
  /** Filename for the test case */
  filename?: string;
  /** Parser options specific to this test */
  parserOptions?: TSESLint.ParserOptions;
  /** ESLint settings for this test */
  settings?: Record<string, unknown>;
  /** Parser to use (defaults to configured parser) */
  parser?: string;
  /** Global variables available in this test */
  globals?: Record<string, boolean>;
  /** Environment settings for this test */
  env?: TSESLint.Linter.Environment;
  /** Skip this test case */
  skip?: boolean;
  /** Run only this test case */
  only?: boolean;
  /** Dependency constraints for this test */
  dependencyConstraints?: DependencyConstraint;
}

/**
 * Invalid test case configuration  
 */
interface InvalidTestCase<TMessageIds extends string, TOptions extends readonly unknown[]> 
  extends ValidTestCase<TOptions> {
  /** Expected errors from this test case */
  errors: TestCaseError<TMessageIds>[];
  /** Expected output after fixes are applied */
  output?: string | null;
}

/**
 * Complete test suite for a rule
 */
interface RunTests<TMessageIds extends string, TOptions extends readonly unknown[]> {
  /** Test cases that should not produce any errors */
  valid: (string | ValidTestCase<TOptions>)[];
  /** Test cases that should produce errors */
  invalid: InvalidTestCase<TMessageIds, TOptions>[];
}

Rule Tester Configuration

/**
 * Configuration for the ESLint rule tester
 */
interface RuleTesterConfig {
  /** Parser to use for parsing test code */
  parser?: string;
  /** Parser options passed to the parser */
  parserOptions?: TSESLint.ParserOptions;
  /** Global variables available during testing */
  globals?: Record<string, boolean>;
  /** Environment settings */
  env?: TSESLint.Linter.Environment;
  /** ESLint settings object */
  settings?: Record<string, unknown>;
  /** Language options for the parser */
  languageOptions?: {
    ecmaVersion?: TSESLint.ParserOptions['ecmaVersion'];
    sourceType?: TSESLint.ParserOptions['sourceType'];
    globals?: Record<string, boolean>;
    parser?: { parse(text: string, options?: any): any };
    parserOptions?: Record<string, unknown>;
  };
  /** Linter options */
  linterOptions?: {
    noInlineConfig?: boolean;
    reportUnusedDisableDirectives?: boolean;
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-typescript-eslint--experimental-utils

docs

ast-utils.md

eslint-utils.md

index.md

json-schema.md

scope-analysis.md

ts-eslint.md

ts-estree.md

tile.json