All files / PARSE/4-Resolve TransitiveEnumCollector.ts

100% Statements 15/15
100% Branches 6/6
100% Functions 2/2
100% Lines 15/15

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                                                                                              6454x 6454x 6454x   6454x 6780x 341x 15x   326x 326x 326x 326x 165x   326x     6454x   6454x          
/**
 * TransitiveEnumCollector
 * Issue #588: extracted from Transpiler; pass 1.4 Resolve's since #1472.
 *
 * Collects symbol information from transitively included .cnx files.
 * This enables proper enum prefixing when enums are defined in deeply
 * nested includes (A includes B, B includes C with enum).
 */
 
import ICodeGenSymbols from "../../types/ICodeGenSymbols";
import type ITransitiveIncludes from "./types/ITransitiveIncludes";
 
/**
 * Collects symbol information by traversing the include graph.
 *
 * When generating code, we need to know about enums defined in included files
 * so we can properly prefix enum member references. This collector walks the
 * include graph starting from a root file and gathers symbol info from all
 * transitively included .cnx files.
 */
class TransitiveEnumCollector {
  /**
   * Collect symbol info from every file `filePath` transitively includes.
   *
   * #1435: the closure is taken over the include graph discovery resolved, and
   * over nothing else. There were two entry points here, and neither read that
   * graph: one re-read each file from disk and rebuilt a search path without
   * the PlatformIO and Arduino tiers or the injected filesystem, so an include
   * discovery had compiled was invisible to its includer (E0426); the other
   * started its walk without the root, so a cycle handed the root its own
   * symbols as an "external" source. One entry point over one graph cannot
   * disagree with discovery, or with itself.
   *
   * Depth-first, each file before its includes, each file once. The root is
   * visited first and is never a source of its own view.
   *
   * @param filePath - The root file to start collecting from
   * @param includesByFile - Each file's direct includes, as discovery resolved
   *   them. A file with no entry includes nothing.
   * @param symbolInfoByFile - Map of file paths to their symbol info
   * @returns the closure's `ICodeGenSymbols`, and every file it visited
   */
  static collect(
    filePath: string,
    includesByFile: ReadonlyMap<string, ReadonlyArray<{ path: string }>>,
    symbolInfoByFile: ReadonlyMap<string, ICodeGenSymbols>,
  ): ITransitiveIncludes {
    const sources: ICodeGenSymbols[] = [];
    const paths: string[] = [];
    const visited = new Set<string>([filePath]);
 
    const visit = (from: string): void => {
      for (const include of includesByFile.get(from) ?? []) {
        if (visited.has(include.path)) {
          continue;
        }
        visited.add(include.path);
        paths.push(include.path);
        const externalInfo = symbolInfoByFile.get(include.path);
        if (externalInfo) {
          sources.push(externalInfo);
        }
        visit(include.path);
      }
    };
    visit(filePath);
 
    return { sources, paths };
  }
}
 
export default TransitiveEnumCollector;