All files / src/utils ast-parser.ts

49.54% Statements 55/111
40.32% Branches 50/124
45.45% Functions 10/22
53.48% Lines 46/86

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201                55x 55x 55x 77x 132x 132x   132x                       87x 72x     17x                     124005x   124005x   102496x 124005x 882742x   780246x 291202x 930649x 140467x 644668x 280209x 1015706x 321035x     488464x 612469x       102204x                                               48132x       52x                                           159x   108x       159x   51x 51x 51x   159x                                   1101x       60x       522x     522x         522x 1566x   1566x       1566x         522x       1566x     522x                                        
import { parse, TSESTree } from '@typescript-eslint/typescript-estree';
import { readFileSync } from 'fs';
 
/**
 * Parse a file into an AST
 * Only supports TypeScript/JavaScript files (.ts, .tsx, .js, .jsx)
 */
export function parseFile(
  filePath: string,
  content?: string
): TSESTree.Program | null {
  try {
    const code = content ?? readFileSync(filePath, 'utf-8');
    const isTypeScript = filePath.match(/\.tsx?$/);
 
    return parse(code, {
      jsx: filePath.match(/\.[jt]sx$/i) !== null,
      loc: true,
      range: true,
      comment: false,
      tokens: false,
      // Relaxed parsing for JavaScript files
      sourceType: 'module',
      ecmaVersion: 'latest',
      // Only use TypeScript parser features for .ts/.tsx files
      filePath: isTypeScript ? filePath : undefined,
    });
  } catch (error) {
    void error;
    // Silently skip files that fail to parse (likely non-JS/TS or syntax errors)
    // Non-JS/TS files should be filtered before reaching this point
    return null;
  }
}
 
/**
 * Traverse AST nodes with a visitor pattern
 */
export function traverseAST(
  node: TSESTree.Node,
  visitor: {
    enter?: (node: TSESTree.Node, parent: TSESTree.Node | null) => void;
  I  leave?: (node: TSESTree.Node, parent: TSESTree.Node | null) => void;
  },
  parent: TSESTree.Node | null = null
): void {
  Iif (!node) return;
 
  visitor.enter?.(node, parent);
 
  // Visit children
  for (const key of Object.keys(node)) {
    const value = (node as any)[key];
 
    if (Array.isArray(value)) {
      for (const child of value) {
        if (child && typeof child === 'object' && 'type' in child) {
          traverseAST(child as TSESTree.Node, visitor, node);
        }
      }
    } else if (value && typeof value === 'object' && 'type' in value) {
      traverseAST(value as TSESTree.Node, visitor, node);
    }
  }
 
  visitor.leave?.(node, parent);
}

/**
 * Check if a node is within a specific type of ancestor
 */
export function hasAncestor(
  node: TSESTree.Node,
  ancestorTypes: string[],
  ancestors: TSESTree.Node[]
): boolean {
  return ancestors.some((ancestor) => ancestorTypes.includes(ancestor.type));
}

/**
 * Get the name of an identifier or pattern
 */
export function getIdentifierName(node: TSESTree.Node): string | null {
  if (node.type === 'Identifier') {
    return node.name;
  }
  return null;
}
 
/**
 * Check if a node is a loop
 */
export function isLoopStatement(node: TSESTree.Node): boolean {
  return [
    'ForStatement',
    'ForInStatement',
    'ForOfStatement',
    'WhileStatement',
    'DoWhileStatement',
  ].includes(node.type);
}

/**
 * Check if a node is an arrow function or callback
 */
export function isCallback(node: TSESTree.Node): boolean {
  if (node.type === 'ArrowFunctionExpression') {
    return true;
  }
  if (node.type === 'FunctionExpression') {
    return true;
  }
  return false;
}
 
/**
 * Extract function/method name from various declaration types
 */
export function getFunctionName(node: TSESTree.Node): string | null {
  switch (node.type) {
    case 'FunctionDeclaration':
      return node.id?.name ?? null;
    case 'FunctionExpression':
      Ereturn node.id?.name ?? null;
    case 'ArrowFunctionExpression':
      return null; // Arrow functions don't have names directly
    case 'MethodDefinition':
      Eif (node.key.type === 'Identifier') {
        return node.key.name;
      }
      return null;
    default:
      return null;
  }
}

/**
 * Check if a variable declaration is in a destructuring pattern
 */
export function isInDestructuring(node: TSESTree.Node): boolean {
  if (!node) return false;
 
  return node.type === 'ObjectPattern' || node.type === 'ArrayPattern';
}
 
/**
 * Get the line number from a node
 */
export function getLineNumber(node: TSESTree.Node): number {
  return node.loc?.start.line ?? 0;
}
 
/**
 * Check if a node represents a coverage metric context
 */
export function isCoverageContext(
  Inode: TSESTree.Node,
  ancestors: TSESTree.Node[]
): boolean {
  // Check if any ancestor or the node itself references coverage-related properties
  const coveragePatterns =
    /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
I
  // Check variable name
  Iif (node.type === 'Identifier' && coveragePatterns.test(node.name)) {
    return true;
  }
 
  //I Check if it's a property of something coverage-related
  for (const ancestor of ancestors.slice(-3)) {
    // Check last 3 ancestors
    if (ancestor.type === 'MemberExpression') {
      const memberExpr = ancestor as TSESTree.MemberExpression;
      if (
        memberExpr.object.type === 'Identifier' &&
        coveragePatterns.test(memberExpr.object.name)
      ) {
        return true;
      }
    }
    if (
      ancestor.type === 'ObjectPattern' ||
      ancestor.type === 'ObjectExpression'
    ) {
      // Check if parent variable has coverage-related name
      const parent = ancestors[ancestors.indexOf(ancestor) - 1];
      if (parent?.type === 'VariableDeclarator') {
        const varDecl = parent as TSESTree.VariableDeclarator;
        if (
          varDecl.id.type === 'Identifier' &&
          coveragePatterns.test(varDecl.id.name)
        ) {
          return true;
        }
      }
    }
  }
 
  return false;
}