A CLI tool to check type coverage for typescript code
—
The type-coverage-core library provides a programmatic API for integrating TypeScript type coverage analysis into custom tools, build systems, and development workflows.
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`);
}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}`);// 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'>>;
};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;
}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
}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 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;
}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
};
}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)}%`);
}
};
}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