All files / transpiler/data InputExpansion.ts

94.73% Statements 36/38
88% Branches 22/25
100% Functions 3/3
94.73% Lines 36/38

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                                  5x   5x 7x   7x 1x     6x   6x       6x   6x 6x         4x                   12x   12x 12x   12x 19x     19x 6x 6x             5x       1x 1x 13x 13x 13x 11x         1x         11x                         17x 17x     17x 17x 6x               11x 11x 3x                  
import { resolve, extname, basename } from "node:path";
import { existsSync, statSync, readdirSync } from "node:fs";
 
/**
 * Input expansion for C-Next CLI
 *
 * Expands file paths and directories into a list of .cnx files to compile.
 * Handles recursive directory scanning and file validation.
 */
class InputExpansion {
  /**
   * Expand inputs (files or directories) to list of .cnx files
   *
   * @param inputs - Array of file paths or directories
   * @returns Array of .cnx file paths
   */
  static expandInputs(inputs: string[]): string[] {
    const files: string[] = [];
 
    for (const input of inputs) {
      const resolvedPath = resolve(input);
 
      if (!existsSync(resolvedPath)) {
        throw new Error(`Input not found: ${input}`);
      }
 
      const stats = statSync(resolvedPath);
 
      Iif (stats.isDirectory()) {
        // Recursively find .cnx files
        const cnextFiles = this.findCNextFiles(resolvedPath);
        files.push(...cnextFiles);
      E} else if (stats.isFile()) {
        // Validate and add file
        this.validateFileExtension(resolvedPath);
        files.push(resolvedPath);
      }
    }
 
    // Remove duplicates
    return Array.from(new Set(files));
  }
 
  /**
   * Recursively find .cnx files in directory
   *
   * @param dir - Directory to scan
   * @returns Array of .cnx file paths
   */
  static findCNextFiles(dir: string): string[] {
    const files: string[] = [];
 
    try {
      const entries = readdirSync(dir, { withFileTypes: true });
 
      for (const entry of entries) {
        const fullPath = resolve(dir, entry.name);
 
        // Skip hidden directories and common build/dependency directories
        if (entry.isDirectory()) {
          const dirName = entry.name;
          if (
            dirName.startsWith(".") ||
            dirName === "node_modules" ||
            dirName === "build" ||
            dirName === ".pio" ||
            dirName === "dist"
          ) {
            continue;
          }
 
          // Recursively scan subdirectory
          const subFiles = this.findCNextFiles(fullPath);
          files.push(...subFiles);
        E} else if (entry.isFile()) {
          const ext = extname(entry.name);
          if (ext === ".cnx" || ext === ".cnext") {
            files.push(fullPath);
          }
        }
      }
    } catch (error) {
      throw new Error(`Failed to scan directory ${dir}: ${error}`, {
        cause: error,
      });
    }
 
    return files;
  }
 
  /**
   * Validate file extension
   *
   * Accepts: .cnx, .cnext
   * Rejects: .c, .cpp, .cc, .cxx, .c++ (implementation files)
   *
   * @param path - File path to validate
   * @throws Error if extension is invalid
   */
  static validateFileExtension(path: string): void {
    const ext = extname(path);
    const fileName = basename(path);
 
    // Reject implementation files
    const rejectedExtensions = [".c", ".cpp", ".cc", ".cxx", ".c++"];
    if (rejectedExtensions.includes(ext)) {
      throw new Error(
        `Cannot process implementation file '${fileName}'. ` +
          `C-Next only compiles .cnx files. ` +
          `If you need to include this file, create a header (.h) instead.`,
      );
    }
 
    // Accept C-Next source files
    const acceptedExtensions = [".cnx", ".cnext"];
    if (!acceptedExtensions.includes(ext)) {
      throw new Error(
        `Invalid file extension '${ext}' for file '${fileName}'. ` +
          `C-Next only accepts .cnx or .cnext files.`,
      );
    }
  }
}
 
export default InputExpansion;