All files discover.ts

100% Statements 72/72
95.23% Branches 20/21
100% Functions 5/5
100% Lines 72/72

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 1421x                                                                   1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x   1x 61x 61x 61x 61x 61x   61x 61x 61x   61x 61x 61x 61x 61x 61x 61x   61x 55x 6x 61x 61x                         61x 61x 61x 61x 61x 2x 2x 2x 61x 230x 230x 230x 55x 55x                       61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 66x               66x 66x 66x 66x 66x 61x 61x 61x  
/**
 * Java file discovery.
 *
 * Strategy mirrors graph-go:
 *   1. Locate a build file. Precedence (resolved-deps first):
 *      gradle.lockfile > pom.xml > build.gradle.kts > build.gradle.
 *      We do NOT parse them — recursive `.java` glob with build
 *      output dirs excluded handles single modules and most multi-
 *      module layouts. Multi-module workspace-aware discovery
 *      (Gradle subprojects, Maven `<modules>`) is a follow-up.
 *   2. If no build file present, configPath is undefined; cacheKey
 *      falls back to `no-config`.
 *
 * Excluded directories:
 *   - `target/`       — Maven build output
 *   - `build/`        — Gradle build output
 *   - `out/`          — IntelliJ default output
 *   - `bin/`          — Eclipse default output
 *   - `.gradle/`      — Gradle cache
 *   - `node_modules/` — defensive
 *   - `.git/`         — VCS metadata
 *
 * Returns absolute, realpath-normalized, sorted, deduped paths so I-9
 * holds across runs.
 */
 
import { existsSync, realpathSync } from 'node:fs';
import { resolve, sep } from 'node:path';
 
import { logger } from '@opensip-tools/core';
import { glob } from 'glob';
 
import type { DiscoverInput, DiscoverOutput } from '@opensip-tools/graph';
 
const EXCLUDED_DIR_GLOBS: readonly string[] = [
  '**/target/**',
  '**/build/**',
  '**/out/**',
  '**/bin/**',
  '**/.gradle/**',
  '**/node_modules/**',
  '**/.git/**',
];
 
// Search order: lockfile (most resolved) → pom.xml → build.gradle.kts → build.gradle.
const CONFIG_CANDIDATES: readonly string[] = [
  'gradle.lockfile',
  'pom.xml',
  'build.gradle.kts',
  'build.gradle',
];
 
export function discoverFiles(input: DiscoverInput): DiscoverOutput {
  logger.info({
    evt: 'graph.discover.start',
    module: 'graph:discover:java',
    projectDir: input.cwd,
  });
 
  const projectDirAbs = normalizeProjectDir(input.cwd);
  const configPathAbs = resolveConfigPath(projectDirAbs, input.configPathOverride);
  const files = collectJavaFiles(projectDirAbs);
 
  logger.info({
    evt: 'graph.discover.complete',
    module: 'graph:discover:java',
    projectDir: projectDirAbs,
    configPath: configPathAbs ?? '(none)',
    fileCount: files.length,
  });
 
  const out: DiscoverOutput = configPathAbs === undefined
    ? { projectDirAbs, files }
    : { projectDirAbs, files, configPathAbs };
  return out;
}
 
/* v8 ignore start */
function normalizeProjectDir(projectDir: string): string {
  const abs = resolve(projectDir);
  try {
    return realpathSync(abs);
  } catch {
    return abs;
  }
}
/* v8 ignore stop */
 
function resolveConfigPath(
  projectDirAbs: string,
  override: string | undefined,
): string | undefined {
  if (override !== undefined && override.length > 0) {
    const abs = resolve(projectDirAbs, override);
    return existsSync(abs) ? realpathOrPath(abs) : abs;
  }
  for (const candidate of CONFIG_CANDIDATES) {
    const path = resolve(projectDirAbs, candidate);
    if (existsSync(path)) return realpathOrPath(path);
  }
  return undefined;
}
 
/* v8 ignore start */
function realpathOrPath(p: string): string {
  try {
    return realpathSync(p);
  } catch {
    return p;
  }
}
/* v8 ignore stop */
 
function collectJavaFiles(projectDirAbs: string): readonly string[] {
  const matches: string[] = glob.sync('**/*.java', {
    cwd: projectDirAbs,
    absolute: true,
    ignore: [...EXCLUDED_DIR_GLOBS],
    nodir: true,
    follow: false,
    dot: false,
  });
  const seen = new Set<string>();
  const out: string[] = [];
  for (const m of matches) {
    let real: string = m;
    /* v8 ignore start */
    try {
      real = realpathSync(m);
    } catch {
      // fall through with original
    }
    /* v8 ignore stop */
    const key = real.split(sep).join('/');
    if (seen.has(key)) continue;
    seen.add(key);
    out.push(real);
  }
  out.sort();
  return out;
}