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 | 203x 33x 78x 78x 78x 78x 49x 40x 38x 38x 38x 38x 37x 37x 36x 7x 115x 115x 30x 85x 85x 85x 85x | /**
* IncludeTreeWalker
* Issue #591: Shared utility for traversing C-Next include trees
*
* Eliminates duplicate recursive traversal logic in:
* - Transpiler._discoverFromSource() (standalone include discovery)
* - TransitiveEnumCollector.collectForStandalone()
* - TransitiveEnumCollector.collectRecursively()
*/
import { readFileSync } from "node:fs";
import { dirname } from "node:path";
import IncludeResolver from "./IncludeResolver";
import IDiscoveredFile from "./types/IDiscoveredFile";
import EFileType from "./types/EFileType";
/**
* Callback invoked for each C-Next include file encountered during traversal.
* @param file - The discovered include file
* @returns false to stop traversal of this branch, true/void to continue
*/
type TWalkCallback = (file: IDiscoveredFile) => boolean | void;
/**
* Walks the C-Next include tree, invoking a callback for each file.
*
* Handles:
* - Cycle detection (files are visited only once)
* - Recursive include resolution
* - Error handling for unreadable files
*/
class IncludeTreeWalker {
/**
* Walk the include tree starting from a list of includes.
*
* @param includes - Initial list of include files to process
* @param includeDirs - Directories to search for nested includes
* @param callback - Function called for each include file
* @param visited - Optional set of already-visited paths (for external tracking)
*/
static walk(
includes: ReadonlyArray<{ path: string }>,
includeDirs: readonly string[],
callback: TWalkCallback,
visited: Set<string> = new Set(),
): void {
for (const include of includes) {
IncludeTreeWalker.walkRecursively(
include.path,
includeDirs,
callback,
visited,
);
}
}
/**
* Walk the include tree starting from a single file path.
*
* @param filePath - Path to start walking from
* @param includeDirs - Directories to search for nested includes
* @param callback - Function called for each include file
* @param visited - Optional set of already-visited paths
*/
static walkFromFile(
filePath: string,
includeDirs: readonly string[],
callback: TWalkCallback,
visited: Set<string> = new Set(),
): void {
// Don't call callback for the root file, just its includes
Iif (visited.has(filePath)) return;
visited.add(filePath);
const nestedIncludes = IncludeTreeWalker.resolveIncludes(
filePath,
includeDirs,
);
if (nestedIncludes) {
IncludeTreeWalker.walk(nestedIncludes, includeDirs, callback, visited);
}
}
/**
* Internal recursive walker.
*/
private static walkRecursively(
filePath: string,
includeDirs: readonly string[],
callback: TWalkCallback,
visited: Set<string>,
): void {
if (visited.has(filePath)) return;
visited.add(filePath);
// Create a minimal IDiscoveredFile for the callback
const file: IDiscoveredFile = {
path: filePath,
type: EFileType.CNext,
extension: ".cnx",
};
// Invoke callback - if it returns false, stop this branch
const result = callback(file);
if (result === false) return;
// Resolve and walk nested includes
const nestedIncludes = IncludeTreeWalker.resolveIncludes(
filePath,
includeDirs,
);
if (nestedIncludes) {
for (const nested of nestedIncludes) {
IncludeTreeWalker.walkRecursively(
nested.path,
includeDirs,
callback,
visited,
);
}
}
}
/**
* Resolve includes from a file path.
* @returns Array of C-Next includes, or null if file can't be read
*/
private static resolveIncludes(
filePath: string,
includeDirs: readonly string[],
): IDiscoveredFile[] | null {
let content: string;
try {
content = readFileSync(filePath, "utf-8");
} catch {
return null;
}
const searchPaths = IncludeResolver.buildSearchPaths(
dirname(filePath),
[...includeDirs],
[],
);
const resolver = new IncludeResolver(searchPaths);
const resolved = resolver.resolve(content, filePath);
return resolved.cnextIncludes;
}
}
export default IncludeTreeWalker;
|