CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-type-coverage

A CLI tool to check type coverage for typescript code

Pending
Overview
Eval results
Files

programmatic-api.mddocs/

Programmatic API

The type-coverage-core library provides a programmatic API for integrating TypeScript type coverage analysis into custom tools, build systems, and development workflows.

Capabilities

Primary Analysis Functions

Asynchronous Type Coverage Analysis

Analyze a TypeScript project asynchronously with automatic configuration discovery.

/**
 * Analyze TypeScript project for type coverage
 * @param project - Path to project directory or tsconfig.json file
 * @param options - Optional configuration for analysis
 * @returns Promise resolving to analysis results
 */
function lint(
  project: string,
  options?: Partial<LintOptions>
): Promise<LintResult>;

Usage Examples:

import { lint } from "type-coverage-core";

// Basic analysis
const result = await lint("./");
console.log(`Coverage: ${result.correctCount}/${result.totalCount}`);

// Analysis with options
const result = await lint("./src", {
  strict: true,
  enableCache: true,
  ignoreFiles: ["**/*.test.ts"],
  reportSemanticError: true
});

// Check coverage threshold
const percentage = (result.correctCount / result.totalCount) * 100;
if (percentage < 90) {
  throw new Error(`Coverage ${percentage.toFixed(2)}% below threshold`);
}

Synchronous Type Coverage Analysis

Analyze TypeScript files synchronously with explicit compiler configuration.

/**
 * Analyze TypeScript files synchronously with explicit configuration
 * @param compilerOptions - TypeScript compiler options
 * @param rootNames - Array of root file paths to analyze
 * @param options - Optional configuration for analysis
 * @returns Analysis results
 */
function lintSync(
  compilerOptions: ts.CompilerOptions,
  rootNames: string[],
  options?: Partial<LintOptions>
): LintSyncResult;

Usage Examples:

import { lintSync } from "type-coverage-core";
import * as ts from "typescript";

// With explicit configuration
const compilerOptions: ts.CompilerOptions = {
  target: ts.ScriptTarget.ES2020,
  module: ts.ModuleKind.CommonJS,
  lib: ["ES2020"],
  strict: true
};

const result = lintSync(compilerOptions, ["src/main.ts", "src/utils.ts"], {
  strict: true,
  ignoreNested: true
});

console.log(`Files analyzed: ${result.fileCounts?.size || 0}`);

Types and Interfaces

Analysis Results

// Return type for lint() function
type LintResult = {
  /** Number of identifiers with explicit types */
  correctCount: number;
  /** Total number of identifiers analyzed */
  totalCount: number;
  /** Array of any type issues found */
  anys: AnyInfo[];
  /** TypeScript program instance used for analysis */
  program: ts.Program;
  /** Per-file coverage statistics (when fileCounts option is true) */
  fileCounts: Map<string, Pick<FileTypeCheckResult, 'correctCount' | 'totalCount'>>;
};

// Return type for lintSync() function  
type LintSyncResult = {
  /** Number of identifiers with explicit types */
  correctCount: number;
  /** Total number of identifiers analyzed */
  totalCount: number;
  /** Array of any type issues with source file references */
  anys: Array<AnyInfo & { sourceFile: ts.SourceFile }>;
  /** TypeScript program instance used for analysis */
  program: ts.Program;
  /** Per-file coverage statistics (when fileCounts option is true) */
  fileCounts: Map<string, Pick<FileTypeCheckResult, 'correctCount' | 'totalCount'>>;
};

Configuration Options

interface LintOptions {
  /** Enable debug output during analysis */
  debug: boolean;
  /** Enable strict mode checking (includes all strict options) */
  strict: boolean;
  /** Enable result caching for improved performance */
  enableCache: boolean;
  /** File patterns to ignore during analysis */
  ignoreFiles?: string | string[];
  /** Ignore catch clause variables */
  ignoreCatch: boolean;
  /** Allow writes to variables with implicit any types */
  ignoreUnreadAnys: boolean;
  /** Generate per-file coverage statistics */
  fileCounts: boolean;
  /** Use absolute file paths in results */
  absolutePath?: boolean;
  /** Report TypeScript semantic errors */
  reportSemanticError: boolean;
  /** Report unused ignore directives */
  reportUnusedIgnore: boolean;
  /** Custom cache directory location */
  cacheDirectory?: string;
  /** Include results outside current working directory */
  notOnlyInCWD?: boolean;
  /** Specific files to analyze (overrides project discovery) */
  files?: string[];
  /** Previous TypeScript program for incremental compilation */
  oldProgram?: ts.Program;
  /** Custom any type processor function */
  processAny?: ProccessAny;
  
  // Strict mode options (individually configurable)
  /** Ignore any in type arguments (Promise<any>) */
  ignoreNested: boolean;
  /** Ignore as assertions (foo as string) */
  ignoreAsAssertion: boolean;
  /** Ignore type assertions (<string>foo) */
  ignoreTypeAssertion: boolean;
  /** Ignore non-null assertions (foo!) */
  ignoreNonNullAssertion: boolean;
  /** Don't count Object type as any */
  ignoreObject: boolean;
  /** Don't count empty type {} as any */
  ignoreEmptyType: boolean;
}

Issue Information

interface AnyInfo {
  /** File path where issue was found */
  file: string;
  /** Line number (0-based) */
  line: number;
  /** Character position (0-based) */
  character: number;
  /** Description of the issue */
  text: string;
  /** Category of any type issue */
  kind: FileAnyInfoKind;
}

interface FileAnyInfo {
  /** Line number (0-based) */
  line: number;
  /** Character position (0-based) */
  character: number;
  /** Description of the issue */
  text: string;
  /** Category of any type issue */
  kind: FileAnyInfoKind;
}

enum FileAnyInfoKind {
  /** Explicit any type */
  any = 1,
  /** any in type arguments (Promise<any>) */
  containsAny = 2,
  /** Unsafe as assertion (foo as string) */
  unsafeAs = 3,
  /** Unsafe type assertion (<string>foo) */
  unsafeTypeAssertion = 4,
  /** Unsafe non-null assertion (foo!) */
  unsafeNonNull = 5,
  /** TypeScript semantic error */
  semanticError = 6,
  /** Unused ignore directive */
  unusedIgnore = 7
}

File Analysis Results

interface FileTypeCheckResult {
  /** Number of identifiers with explicit types in this file */
  correctCount: number;
  /** Total number of identifiers in this file */
  totalCount: number;
  /** Array of any type issues in this file */
  anys: FileAnyInfo[];
}

Custom Processing

/**
 * Custom processor function for any type nodes
 * @param node - TypeScript AST node being processed  
 * @param context - File analysis context
 * @returns true if node should be counted as any type issue
 */
type ProccessAny = (node: ts.Node, context: FileContext) => boolean;

interface FileContext {
  /** Current file path */
  file: string;
  /** TypeScript source file */
  sourceFile: ts.SourceFile;
  /** Type checker instance */
  checker: ts.TypeChecker;
  /** Current analysis results */
  typeCheckResult: FileTypeCheckResult;
  /** Catch clause variables (for ignoreCatch option) */
  catchVariables: { [variable: string]: boolean };
  /** Lines with ignore directives */
  ignoreLines?: Set<number>;
  /** Lines where ignore directives were used */
  usedIgnoreLines?: Set<number>;
  
  // Configuration options
  debug: boolean;
  strict: boolean;
  ignoreCatch: boolean;
  ignoreUnreadAnys: boolean;
  ignoreNested: boolean;
  ignoreAsAssertion: boolean;
  ignoreTypeAssertion: boolean;
  ignoreNonNullAssertion: boolean;
  ignoreObject: boolean;
  ignoreEmptyType: boolean;
  processAny?: ProccessAny;
}

Advanced Usage Examples

Custom Analysis Pipeline

import { lint, FileAnyInfoKind } from "type-coverage-core";

async function analyzeProject(projectPath: string) {
  const result = await lint(projectPath, {
    strict: true,
    enableCache: true,
    fileCounts: true,
    reportSemanticError: true
  });

  // Calculate overall coverage
  const coverage = (result.correctCount / result.totalCount) * 100;
  
  // Group issues by type
  const issuesByKind = result.anys.reduce((acc, issue) => {
    const kind = FileAnyInfoKind[issue.kind];
    acc[kind] = (acc[kind] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);

  // Analyze per-file coverage
  const fileStats = Array.from(result.fileCounts?.entries() || [])
    .map(([file, stats]) => ({
      file,
      coverage: (stats.correctCount / stats.totalCount) * 100,
      issues: stats.anys.length
    }))
    .sort((a, b) => a.coverage - b.coverage);

  return {
    overall: { coverage, issues: result.anys.length },
    byType: issuesByKind,
    byFile: fileStats
  };
}

Integration with Build Tools

import { lint } from "type-coverage-core";

async function typeCoveragePlugin(options: { threshold: number }) {
  return {
    name: "type-coverage",
    async buildStart() {
      const result = await lint("./", {
        strict: true,
        enableCache: true
      });

      const coverage = (result.correctCount / result.totalCount) * 100;
      
      if (coverage < options.threshold) {
        this.error(
          `Type coverage ${coverage.toFixed(2)}% below threshold ${options.threshold}%`
        );
      }

      console.log(`Type coverage: ${coverage.toFixed(2)}%`);
    }
  };
}

Incremental Analysis

import { lintSync } from "type-coverage-core";
import * as ts from "typescript";

class IncrementalTypeCoverage {
  private program: ts.Program | undefined;

  async analyze(compilerOptions: ts.CompilerOptions, rootNames: string[]) {
    const result = lintSync(compilerOptions, rootNames, {
      oldProgram: this.program,
      enableCache: true,
      fileCounts: true
    });

    // Store program for next incremental analysis
    this.program = result.program;

    return result;
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-type-coverage

docs

cli.md

index.md

programmatic-api.md

tile.json