All files / lib parseCHeader.ts

100% Statements 31/31
100% Branches 17/17
100% Functions 4/4
100% Lines 31/31

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                                  34x   4x   10x   2x   3x   8x   4x   3x                       22x         34x   10x 10x   2x 2x   8x 8x   4x 4x       34x 34x                                                                                     26x   26x             26x 26x 1x           1x     25x     26x   26x           3x             3x                  
/**
 * Parse C/C++ header source and extract symbols for IDE features
 * ADR-055 Phase 7: Direct TCSymbol → ISymbolInfo conversion (no ISymbol intermediate)
 */
 
import HeaderParser from "../PARSE/2-Parse/HeaderParser";
import CResolver from "../PARSE/3-Declare/c/index";
import ISymbolInfo from "./types/ISymbolInfo";
import IParseWithSymbolsResult from "./types/IParseWithSymbolsResult";
import TSymbolKind from "./types/TSymbolKind";
import TCSymbol from "../types/symbols/c/TCSymbol";
import SymbolPathUtils from "./utils/SymbolPathUtils";
 
/**
 * Map TCSymbol kind to library TSymbolKind
 */
function mapCSymbolKind(kind: TCSymbol["kind"]): TSymbolKind {
  switch (kind) {
    case "struct":
      return "struct";
    case "function":
      return "function";
    case "variable":
      return "variable";
    case "enum":
      return "enum";
    case "enum_member":
      return "enumMember";
    case "type":
      return "type";
    default:
      return "variable";
  }
}
 
/**
 * ADR-055 Phase 7: Convert TCSymbol directly to ISymbolInfo.
 * Handles the discriminated union by extracting common fields and type-specific fields.
 */
function convertTCSymbolsToISymbolInfo(
  symbols: TCSymbol[],
  filePath?: string,
): ISymbolInfo[] {
  return symbols.map((sym) => {
    // Extract type and parent based on symbol kind
    let type: string | undefined;
    let parent: string | undefined;
 
    switch (sym.kind) {
      case "function":
        type = sym.type;
        break;
      case "variable":
        type = sym.type;
        break;
      case "enum_member":
        parent = sym.parent;
        break;
      case "type":
        type = sym.type;
        break;
      // struct and enum have no type field
    }
 
    const id = SymbolPathUtils.buildSimpleDotPath(parent, sym.name);
    return {
      name: sym.name,
      fullName: SymbolPathUtils.buildSimpleDotPath(parent, sym.name),
      kind: mapCSymbolKind(sym.kind),
      id,
      parentId: parent,
      type,
      parent,
      line: sym.span.line,
      sourceFile: filePath,
      language: "c",
    };
  });
}
 
/**
 * Parse C/C++ header source and extract symbols for IDE features
 *
 * Unlike transpilation, this function attempts to extract symbols even when
 * there are parse errors, making it suitable for autocomplete during typing.
 *
 * @param source - C/C++ header source code string
 * @param filePath - Optional file path for symbol source tracking
 * @returns Parse result with symbols
 *
 * @example
 * ```typescript
 * import parseCHeader from './lib/parseCHeader';
 *
 * const result = parseCHeader(headerSource, 'config.h');
 * // Find all functions defined in the header
 * const functions = result.symbols.filter(s => s.kind === 'function');
 * ```
 */
function parseCHeader(
  source: string,
  filePath?: string,
): IParseWithSymbolsResult {
  const errors: Array<{
    line: number;
    column: number;
    message: string;
    severity: "error" | "warning";
  }> = [];
 
  try {
    // ONE C-header parse for the project -- see `HeaderParser.parseC` for why the
    // error listeners are removed, measured rather than assumed. This function
    // used to rebuild the pipeline itself, and the copies had already drifted:
    // this one silenced the lexer too, so a token the C lexer cannot recognize
    // printed to stderr on the transpiler path and was silent here (#1306
    // review). Two paths that must agree is the bug, not the symptom.
    const { tree } = HeaderParser.parseC(source);
    if (tree === null) {
      errors.push({
        line: 1,
        column: 0,
        message: "C header could not be parsed",
        severity: "error",
      });
      return { success: false, errors, symbols: [] };
    }
 
    const result = CResolver.resolve(tree, filePath ?? "<header>");
 
    // ADR-055 Phase 7: Direct TCSymbol → ISymbolInfo conversion
    const symbols = convertTCSymbolsToISymbolInfo(result.symbols, filePath);
 
    return {
      success: true,
      errors,
      symbols,
    };
  } catch (err) {
    errors.push({
      line: 1,
      column: 0,
      message: err instanceof Error ? err.message : String(err),
      severity: "error",
    });
 
    return {
      success: false,
      errors,
      symbols: [],
    };
  }
}
 
export default parseCHeader;