CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typescript-eslint--type-utils

Type utilities for working with TypeScript + ESLint together

Pending
Overview
Eval results
Files

property-types.mddocs/

Property Type Utilities

Functions for extracting and analyzing property types from complex type structures. These utilities help understand object properties, their types, and relationships within TypeScript's type system.

Capabilities

Property Type Extraction

Functions for getting property types from complex objects and type structures.

/**
 * Gets the type of a property by name, handling symbolic names correctly
 */
function getTypeOfPropertyOfName(
  checker: ts.TypeChecker, 
  type: ts.Type, 
  name: string, 
  escapedName?: ts.__String
): ts.Type | undefined;

/**
 * Gets the type of a property using a symbol
 */
function getTypeOfPropertyOfType(
  checker: ts.TypeChecker, 
  type: ts.Type, 
  property: ts.Symbol
): ts.Type | undefined;

Usage Examples:

import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";

// In an ESLint rule analyzing object property access
export default {
  create(context) {
    const services = context.parserServices;
    const checker = services.program.getTypeChecker();

    return {
      MemberExpression(node) {
        if (node.computed === false && node.property.type === "Identifier") {
          const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object);
          const objectType = checker.getTypeAtLocation(objectTsNode);
          
          // Get property type by name
          const propertyType = getTypeOfPropertyOfName(
            checker, 
            objectType, 
            node.property.name
          );
          
          if (propertyType) {
            const propertyTypeName = checker.typeToString(propertyType);
            console.log(`Property ${node.property.name} has type: ${propertyTypeName}`);
            
            // Example: Check for specific property types
            if (checker.typeToString(propertyType) === "any") {
              context.report({
                node: node.property,
                messageId: "anyPropertyType"
              });
            }
          } else {
            context.report({
              node: node.property,
              messageId: "unknownProperty",
              data: { propertyName: node.property.name }
            });
          }
        }
      },
      
      TSPropertySignature(node) {
        if (node.key.type === "Identifier") {
          const tsNode = services.esTreeNodeToTSNodeMap.get(node);
          const symbol = checker.getSymbolAtLocation(tsNode);
          
          if (symbol && symbol.parent) {
            const parentType = checker.getTypeOfSymbolAtLocation(symbol.parent, tsNode);
            
            // Get property type using symbol
            const propertyType = getTypeOfPropertyOfType(checker, parentType, symbol);
            
            if (propertyType) {
              console.log(`Property symbol type: ${checker.typeToString(propertyType)}`);
            }
          }
        }
      }
    };
  }
};

Advanced Property Analysis Patterns

Object Property Analysis

// Example: Comprehensive object property analysis
import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";

interface PropertyInfo {
  name: string;
  type: string;
  isOptional: boolean;
  isReadonly: boolean;
  symbol?: ts.Symbol;
}

function analyzeObjectProperties(
  checker: ts.TypeChecker,
  objectType: ts.Type
): PropertyInfo[] {
  const properties: PropertyInfo[] = [];
  const propertySymbols = checker.getPropertiesOfType(objectType);
  
  propertySymbols.forEach(symbol => {
    const propertyType = getTypeOfPropertyOfType(checker, objectType, symbol);
    
    if (propertyType) {
      const isOptional = (symbol.flags & ts.SymbolFlags.Optional) !== 0;
      const isReadonly = symbol.valueDeclaration ? 
        ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Readonly : false;
      
      properties.push({
        name: symbol.name,
        type: checker.typeToString(propertyType),
        isOptional,
        isReadonly: !!isReadonly,
        symbol
      });
    }
  });
  
  return properties;
}

// Usage in ESLint rule
function validateObjectStructure(
  services: ParserServicesWithTypeInformation,
  objectLiteral: TSESTree.ObjectExpression
) {
  const checker = services.program.getTypeChecker();
  const tsNode = services.esTreeNodeToTSNodeMap.get(objectLiteral);
  const objectType = checker.getTypeAtLocation(tsNode);
  
  const properties = analyzeObjectProperties(checker, objectType);
  
  properties.forEach(prop => {
    if (prop.type === "any") {
      console.log(`Property ${prop.name} has any type`);
    }
    
    if (!prop.isOptional && prop.type.includes("undefined")) {
      console.log(`Required property ${prop.name} includes undefined`);
    }
  });
}

Dynamic Property Access Analysis

// Example: Analyzing dynamic property access patterns
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";

function analyzeDynamicPropertyAccess(
  services: ParserServicesWithTypeInformation,
  memberExpression: TSESTree.MemberExpression
): {
  isValidAccess: boolean;
  propertyType: string | null;
  suggestions: string[];
} {
  const checker = services.program.getTypeChecker();
  const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);
  const objectType = checker.getTypeAtLocation(objectTsNode);
  
  let propertyName: string;
  let isValidAccess = false;
  let propertyType: string | null = null;
  const suggestions: string[] = [];
  
  if (memberExpression.computed && memberExpression.property.type === "Literal") {
    // obj["propertyName"]
    propertyName = String(memberExpression.property.value);
  } else if (!memberExpression.computed && memberExpression.property.type === "Identifier") {
    // obj.propertyName
    propertyName = memberExpression.property.name;
  } else {
    return { isValidAccess: false, propertyType: null, suggestions: [] };
  }
  
  // Check if property exists
  const type = getTypeOfPropertyOfName(checker, objectType, propertyName);
  
  if (type) {
    isValidAccess = true;
    propertyType = checker.typeToString(type);
  } else {
    // Property doesn't exist, suggest similar properties
    const allProperties = checker.getPropertiesOfType(objectType);
    
    allProperties.forEach(symbol => {
      const similarity = calculateSimilarity(propertyName, symbol.name);
      if (similarity > 0.6) { // Basic similarity threshold
        suggestions.push(symbol.name);
      }
    });
  }
  
  return { isValidAccess, propertyType, suggestions };
}

function calculateSimilarity(a: string, b: string): number {
  // Simple similarity calculation (Levenshtein-based)
  const longer = a.length > b.length ? a : b;
  const shorter = a.length > b.length ? b : a;
  
  if (longer.length === 0) return 1.0;
  
  const distance = levenshteinDistance(longer, shorter);
  return (longer.length - distance) / longer.length;
}

function levenshteinDistance(str1: string, str2: string): number {
  const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
  
  for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
  for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
  
  for (let j = 1; j <= str2.length; j++) {
    for (let i = 1; i <= str1.length; i++) {
      const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
      matrix[j][i] = Math.min(
        matrix[j][i - 1] + 1,
        matrix[j - 1][i] + 1,
        matrix[j - 1][i - 1] + indicator
      );
    }
  }
  
  return matrix[str2.length][str1.length];
}

Nested Property Type Analysis

// Example: Deep property type analysis for nested objects
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";

interface NestedPropertyPath {
  path: string[];
  type: string;
  depth: number;
}

function analyzeNestedProperties(
  checker: ts.TypeChecker,
  rootType: ts.Type,
  maxDepth: number = 3
): NestedPropertyPath[] {
  const paths: NestedPropertyPath[] = [];
  
  function traverse(type: ts.Type, currentPath: string[], depth: number) {
    if (depth >= maxDepth) return;
    
    const properties = checker.getPropertiesOfType(type);
    
    properties.forEach(symbol => {
      const propertyType = getTypeOfPropertyOfName(checker, type, symbol.name);
      
      if (propertyType) {
        const newPath = [...currentPath, symbol.name];
        const typeString = checker.typeToString(propertyType);
        
        paths.push({
          path: newPath,
          type: typeString,
          depth: depth + 1
        });
        
        // Recursively analyze object properties
        const propertySymbols = checker.getPropertiesOfType(propertyType);
        if (propertySymbols.length > 0 && depth < maxDepth - 1) {
          traverse(propertyType, newPath, depth + 1);
        }
      }
    });
  }
  
  traverse(rootType, [], 0);
  return paths;
}

// Usage example
function validateNestedObjectAccess(
  services: ParserServicesWithTypeInformation,
  node: TSESTree.MemberExpression
) {
  const checker = services.program.getTypeChecker();
  
  // Build property access path
  const path: string[] = [];
  let current: TSESTree.Node = node;
  
  while (current.type === "MemberExpression") {
    if (!current.computed && current.property.type === "Identifier") {
      path.unshift(current.property.name);
    }
    current = current.object;
  }
  
  // Get root object type
  const rootTsNode = services.esTreeNodeToTSNodeMap.get(current);
  const rootType = checker.getTypeAtLocation(rootTsNode);
  
  // Validate each step in the path
  let currentType = rootType;
  
  for (const propertyName of path) {
    const propertyType = getTypeOfPropertyOfName(checker, currentType, propertyName);
    
    if (!propertyType) {
      console.log(`Property ${propertyName} not found in path ${path.join('.')}`);
      break;
    }
    
    currentType = propertyType;
  }
}

Index Signature Analysis

// Example: Analyzing index signatures and dynamic properties
import { getTypeOfPropertyOfName } from "@typescript-eslint/type-utils";

function analyzeIndexSignatures(
  checker: ts.TypeChecker,
  type: ts.Type
): {
  hasStringIndex: boolean;
  hasNumberIndex: boolean;
  stringIndexType?: string;
  numberIndexType?: string;
  dynamicPropertyAccess: boolean;
} {
  const stringIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.String);
  const numberIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
  
  return {
    hasStringIndex: !!stringIndexType,
    hasNumberIndex: !!numberIndexType,
    stringIndexType: stringIndexType ? checker.typeToString(stringIndexType) : undefined,
    numberIndexType: numberIndexType ? checker.typeToString(numberIndexType) : undefined,
    dynamicPropertyAccess: !!(stringIndexType || numberIndexType)
  };
}

// Check if dynamic property access is safe
function validateDynamicAccess(
  services: ParserServicesWithTypeInformation,
  memberExpression: TSESTree.MemberExpression
) {
  if (!memberExpression.computed) return;
  
  const checker = services.program.getTypeChecker();
  const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);
  const objectType = checker.getTypeAtLocation(objectTsNode);
  
  const indexInfo = analyzeIndexSignatures(checker, objectType);
  
  if (!indexInfo.dynamicPropertyAccess) {
    console.log("Object doesn't support dynamic property access");
    return;
  }
  
  // Validate property key type
  const propertyTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.property);
  const propertyType = checker.getTypeAtLocation(propertyTsNode);
  const propertyTypeString = checker.typeToString(propertyType);
  
  if (indexInfo.hasStringIndex && propertyTypeString !== "string") {
    console.log("String index signature requires string key");
  }
  
  if (indexInfo.hasNumberIndex && propertyTypeString !== "number") {
    console.log("Number index signature requires number key");
  }
}

Install with Tessl CLI

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

docs

builtin-types.md

index.md

property-types.md

symbol-analysis.md

type-analysis.md

type-constraints.md

type-predicates.md

type-safety.md

type-specifiers.md

tile.json