All files / transpiler/data CnxFileResolver.ts

100% Statements 20/20
100% Branches 10/10
100% Functions 3/3
100% Lines 20/20

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                                  13x       7x 8x 8x 5x     2x                       13x       8x 9x     9x 1x     8x     8x 6x     2x             13x 13x       13x 13x 13x        
/**
 * CnxFileResolver
 * Static utilities for resolving C-Next file paths.
 *
 * Extracted from IncludeGenerator.ts as part of layer architecture cleanup.
 * File discovery and path resolution belong in the data layer, not output layer.
 */
 
import { existsSync, statSync } from "node:fs";
import { resolve, relative } from "node:path";
 
/**
 * Find a .cnx file in the given search paths.
 * Returns the absolute path if found, null otherwise.
 *
 * Issue #349: Used by IncludeGenerator for angle-bracket include resolution.
 */
const findCnxFile = (
  filename: string,
  searchPaths: string[],
): string | null => {
  for (const searchPath of searchPaths) {
    const cnxPath = resolve(searchPath, `${filename}.cnx`);
    if (existsSync(cnxPath)) {
      return cnxPath;
    }
  }
  return null;
};
 
/**
 * Calculate the relative path from input directories.
 * Returns the relative path (e.g., "Display/utils.cnx") or null if not found.
 *
 * Issue #349: Used by IncludeGenerator for correct header path calculation.
 *
 * Note: PathResolver has an instance method version that uses config.inputs.
 * This static version is for cases where inputs are passed as a parameter.
 */
const getRelativePathFromInputs = (
  filePath: string,
  inputs: string[],
): string | null => {
  for (const input of inputs) {
    const resolvedInput = resolve(input);
 
    // Skip if input is a file (not a directory)
    if (existsSync(resolvedInput) && statSync(resolvedInput).isFile()) {
      continue;
    }
 
    const relativePath = relative(resolvedInput, filePath);
 
    // If relative path doesn't start with '..' it's under this input
    if (!relativePath.startsWith("..") && !relativePath.startsWith("/")) {
      return relativePath;
    }
  }
  return null;
};
 
/**
 * Check if a .cnx file exists at the given path.
 * Used by IncludeGenerator for quote-style include validation.
 */
const cnxFileExists = (cnxPath: string): boolean => {
  return existsSync(cnxPath);
};
 
class CnxFileResolver {
  static readonly findCnxFile = findCnxFile;
  static readonly getRelativePathFromInputs = getRelativePathFromInputs;
  static readonly cnxFileExists = cnxFileExists;
}
 
export default CnxFileResolver;