CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ts-api-utils

Utility functions for working with TypeScript's API, providing comprehensive tools for analyzing and manipulating TypeScript AST nodes, types, and compiler APIs.

79

1.97x
Overview
Eval results
Files

compiler-options.mddocs/

Compiler Options

Compiler Options utilities provide a robust interface for analyzing and working with TypeScript compiler options. These utilities simplify checking whether specific compiler options are enabled, handling the complex interdependencies between options, and accounting for TypeScript's strict mode hierarchy.

Overview

TypeScript's compiler behavior is controlled by numerous compiler options that can be configured in tsconfig.json or passed via command line arguments. These options often have complex relationships - some options enable others, strict mode affects multiple individual options, and boolean options can have undefined, true, or false values.

The Compiler Options module provides:

  1. Option Testing: Safe checking of boolean compiler options
  2. Strict Mode Handling: Proper resolution of strict-mode-related options
  3. Type Safety: Strongly typed interfaces for compiler options

Understanding these utilities is essential for building TypeScript tools that need to adapt their behavior based on the project's compiler configuration.

Core Concepts

Boolean Compiler Options

TypeScript compiler options can be categorized into different types. Boolean options specifically control on/off features and can have three states:

  • true: Explicitly enabled
  • false: Explicitly disabled
  • undefined: Not configured (uses default)

Strict Mode Hierarchy

TypeScript's strict flag is a meta-option that enables several individual strict checks. When strict: true is set, it automatically enables:

  • alwaysStrict
  • noImplicitAny
  • noImplicitThis
  • strictBindCallApply
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization

Individual strict options can be explicitly disabled even when strict: true is set.

Types

BooleanCompilerOptions

type BooleanCompilerOptions = keyof {
  [K in keyof ts.CompilerOptions as NonNullable<ts.CompilerOptions[K]> extends boolean ? K : never]: unknown;
}

A type representing all compiler options that accept boolean values. This mapped type extracts only those options from ts.CompilerOptions where the value type extends boolean.

Usage in type narrowing:

// Examples of boolean compiler options
const option1: BooleanCompilerOptions = "strict";           // ✓ Valid
const option2: BooleanCompilerOptions = "noImplicitAny";    // ✓ Valid
const option3: BooleanCompilerOptions = "target";          // ✗ Error - not boolean

StrictCompilerOption

type StrictCompilerOption = 
  | "alwaysStrict"
  | "noImplicitAny"
  | "noImplicitThis"
  | "strictBindCallApply"
  | "strictFunctionTypes"
  | "strictNullChecks"
  | "strictPropertyInitialization"

A union type representing all compiler options that are controlled by the strict flag. These options are automatically enabled when strict: true is set, but can be individually overridden.

Relationship to strict mode:

// When strict: true is set, all StrictCompilerOption values default to true
const strictOptions: StrictCompilerOption[] = [
  "alwaysStrict",
  "noImplicitAny", 
  "noImplicitThis",
  "strictBindCallApply",
  "strictFunctionTypes",
  "strictNullChecks",
  "strictPropertyInitialization"
];

Functions

isCompilerOptionEnabled

function isCompilerOptionEnabled(
  options: ts.CompilerOptions,
  option: BooleanCompilerOptions
): boolean

Determines whether a boolean compiler option is enabled, properly handling option dependencies and default values.

Parameters:

  • options - The compiler options object to check
  • option - The name of the boolean option to test

Returns: true if the option is enabled, false otherwise

Behavior:

  • Returns true if the option is explicitly set to true
  • Returns false if the option is explicitly set to false or undefined
  • Handles complex interdependencies between options

Example - Basic option checking:

import { isCompilerOptionEnabled } from "ts-api-utils";

const options: ts.CompilerOptions = {
  strict: true,
  noUnusedLocals: false,
  exactOptionalPropertyTypes: undefined
};

// Check individual options
console.log(isCompilerOptionEnabled(options, "strict"));                    // true
console.log(isCompilerOptionEnabled(options, "noUnusedLocals"));           // false
console.log(isCompilerOptionEnabled(options, "exactOptionalPropertyTypes")); // false

Example - Working with program options:

import { isCompilerOptionEnabled } from "ts-api-utils";
import * as ts from "typescript";

function analyzeProject(program: ts.Program) {
  const options = program.getCompilerOptions();
  
  // Adapt behavior based on compiler options
  if (isCompilerOptionEnabled(options, "strict")) {
    console.log("Strict mode enabled - applying strict analysis");
  }
  
  if (isCompilerOptionEnabled(options, "noImplicitAny")) {
    console.log("Implicit any detection enabled");
  }
  
  if (isCompilerOptionEnabled(options, "noUnusedLocals")) {
    console.log("Unused locals checking enabled");
  }
}

isStrictCompilerOptionEnabled

function isStrictCompilerOptionEnabled(
  options: ts.CompilerOptions,
  option: StrictCompilerOption
): boolean

Determines whether a strict-mode compiler option is enabled, accounting for both the global strict flag and individual option overrides.

Parameters:

  • options - The compiler options object to check
  • option - The name of the strict option to test

Returns: true if the strict option is effectively enabled, false otherwise

Resolution logic:

  1. If the specific option is explicitly set (true/false), use that value
  2. If the specific option is undefined, check if strict is enabled
  3. Return the effective boolean value

Example - Strict mode resolution:

import { isStrictCompilerOptionEnabled } from "ts-api-utils";

// Case 1: Strict mode enables all strict options
const strictOptions: ts.CompilerOptions = {
  strict: true
};

console.log(isStrictCompilerOptionEnabled(strictOptions, "noImplicitAny"));    // true
console.log(isStrictCompilerOptionEnabled(strictOptions, "strictNullChecks")); // true

// Case 2: Individual override disables specific strict option
const mixedOptions: ts.CompilerOptions = {
  strict: true,
  noImplicitAny: false  // Explicitly disabled despite strict: true
};

console.log(isStrictCompilerOptionEnabled(mixedOptions, "noImplicitAny"));     // false
console.log(isStrictCompilerOptionEnabled(mixedOptions, "strictNullChecks"));  // true

// Case 3: Individual strict option without global strict
const individualOptions: ts.CompilerOptions = {
  strict: false,
  strictNullChecks: true  // Explicitly enabled
};

console.log(isStrictCompilerOptionEnabled(individualOptions, "strictNullChecks")); // true
console.log(isStrictCompilerOptionEnabled(individualOptions, "noImplicitAny"));    // false

Example - Conditional analysis based on strict options:

import { isStrictCompilerOptionEnabled } from "ts-api-utils";
import * as ts from "typescript";

function analyzeTypeChecking(options: ts.CompilerOptions, node: ts.Node) {
  // Check for strict null checks
  if (isStrictCompilerOptionEnabled(options, "strictNullChecks")) {
    console.log("Strict null checks enabled - analyzing null/undefined usage");
    // Perform strict null checking analysis
  }
  
  // Check for implicit any
  if (isStrictCompilerOptionEnabled(options, "noImplicitAny")) {
    console.log("No implicit any enabled - checking type annotations");
    // Verify explicit type annotations
  }
  
  // Check for strict function types
  if (isStrictCompilerOptionEnabled(options, "strictFunctionTypes")) {
    console.log("Strict function types enabled - analyzing function assignments");
    // Perform strict function type analysis
  }
}

Practical Examples

Building a Configuration Analyzer

import { 
  isCompilerOptionEnabled, 
  isStrictCompilerOptionEnabled,
  BooleanCompilerOptions,
  StrictCompilerOption 
} from "ts-api-utils";
import * as ts from "typescript";

class CompilerConfigAnalyzer {
  constructor(private options: ts.CompilerOptions) {}
  
  analyzeStrictness(): { level: string; enabledOptions: string[] } {
    const strictOptions: StrictCompilerOption[] = [
      "alwaysStrict",
      "noImplicitAny",
      "noImplicitThis", 
      "strictBindCallApply",
      "strictFunctionTypes",
      "strictNullChecks",
      "strictPropertyInitialization"
    ];
    
    const enabledStrict = strictOptions.filter(option =>
      isStrictCompilerOptionEnabled(this.options, option)
    );
    
    if (enabledStrict.length === strictOptions.length) {
      return { level: "Full Strict Mode", enabledOptions: enabledStrict };
    } else if (enabledStrict.length > 0) {
      return { level: "Partial Strict Mode", enabledOptions: enabledStrict };
    } else {
      return { level: "Non-Strict Mode", enabledOptions: [] };
    }
  }
  
  getEnabledFeatures(): string[] {
    const features: BooleanCompilerOptions[] = [
      "allowUnreachableCode",
      "allowUnusedLabels",
      "exactOptionalPropertyTypes",
      "noFallthroughCasesInSwitch",
      "noImplicitReturns",
      "noUncheckedIndexedAccess",
      "noUnusedLocals",
      "noUnusedParameters"
    ];
    
    return features.filter(feature =>
      isCompilerOptionEnabled(this.options, feature)
    );
  }
}

// Usage
const config = {
  strict: true,
  noImplicitAny: false,  // Override
  noUnusedLocals: true,
  exactOptionalPropertyTypes: true
};

const analyzer = new CompilerConfigAnalyzer(config);
console.log(analyzer.analyzeStrictness());
// { level: "Partial Strict Mode", enabledOptions: [...] }

console.log(analyzer.getEnabledFeatures());
// ["noUnusedLocals", "exactOptionalPropertyTypes"]

Conditional Tool Behavior

import { isCompilerOptionEnabled, isStrictCompilerOptionEnabled } from "ts-api-utils";
import * as ts from "typescript";

function createLintRules(program: ts.Program) {
  const options = program.getCompilerOptions();
  const rules: string[] = [];
  
  // Add rules based on compiler options
  if (isStrictCompilerOptionEnabled(options, "noImplicitAny")) {
    rules.push("require-explicit-types");
  }
  
  if (isStrictCompilerOptionEnabled(options, "strictNullChecks")) {
    rules.push("null-checks");
    rules.push("undefined-checks");
  }
  
  if (isCompilerOptionEnabled(options, "noUnusedLocals")) {
    rules.push("no-unused-vars");
  }
  
  if (isCompilerOptionEnabled(options, "noFallthroughCasesInSwitch")) {
    rules.push("no-fallthrough");
  }
  
  return rules;
}

// Example: Adaptive code analysis
function analyzeSourceFile(sourceFile: ts.SourceFile, options: ts.CompilerOptions) {
  const hasStrictNullChecks = isStrictCompilerOptionEnabled(options, "strictNullChecks");
  const hasNoImplicitAny = isStrictCompilerOptionEnabled(options, "noImplicitAny");
  
  ts.forEachChild(sourceFile, function visit(node) {
    if (ts.isVariableDeclaration(node)) {
      // Only check for explicit types if noImplicitAny is enabled
      if (hasNoImplicitAny && !node.type) {
        console.log(`Variable ${node.name.getText()} lacks explicit type annotation`);
      }
    }
    
    if (hasStrictNullChecks && ts.isPropertyAccessExpression(node)) {
      // Perform null-checking analysis
      console.log(`Checking null safety for ${node.getText()}`);
    }
    
    ts.forEachChild(node, visit);
  });
}

Best Practices

Option Checking Patterns

// ✅ Good: Use the appropriate function for the option type
if (isStrictCompilerOptionEnabled(options, "strictNullChecks")) {
  // Handle strict option
}

if (isCompilerOptionEnabled(options, "allowUnreachableCode")) {
  // Handle regular boolean option
}

// ❌ Avoid: Direct property access doesn't handle strict mode properly
if (options.strictNullChecks) {  // May miss strict mode inheritance
  // This misses cases where strict: true but strictNullChecks is undefined
}

Type Safety

// ✅ Good: Use the typed option parameters
function checkOption(options: ts.CompilerOptions, option: BooleanCompilerOptions) {
  return isCompilerOptionEnabled(options, option);
}

// ✅ Good: Use union types for multiple options
function checkAnyStrict(
  options: ts.CompilerOptions, 
  ...checkOptions: StrictCompilerOption[]
): boolean {
  return checkOptions.some(option => 
    isStrictCompilerOptionEnabled(options, option)
  );
}

Configuration Analysis

// ✅ Good: Comprehensive option analysis
function describeConfiguration(options: ts.CompilerOptions): string {
  const strictness = isCompilerOptionEnabled(options, "strict") ? "strict" : "non-strict";
  const strictOptions = [
    "noImplicitAny", "strictNullChecks", "strictFunctionTypes"
  ] as const;
  
  const enabledStrict = strictOptions.filter(opt =>
    isStrictCompilerOptionEnabled(options, opt)  
  );
  
  return `${strictness} mode with ${enabledStrict.length} strict options enabled`;
}

The Compiler Options utilities provide a reliable foundation for building TypeScript tools that need to adapt their behavior based on the project's configuration, ensuring proper handling of the complex relationships between different compiler options.

Install with Tessl CLI

npx tessl i tessl/npm-ts-api-utils

docs

compiler-options.md

index.md

node-analysis.md

syntax-utilities.md

type-system.md

usage-analysis.md

tile.json