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 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 2x 4x 4x 2x 2x 2x 2x 4x 4x 4x 4x 4x 2x 2x | // @fitness-ignore-file error-handling-quality -- realpathSync probe for symlink dedup; exception → fall through with the original path (file might be in a symlinked dir or have been unlinked), already marked v8-ignore as effectively unreachable on real input.
// @fitness-ignore-file batch-operation-limits -- iterates bounded collection (source directories within a single project root)
/**
* Shared file-discovery scaffolding for the tree-sitter graph adapters.
*
* The discover template is byte-identical across graph-go / graph-java /
* graph-python / graph-rust save four data inputs:
*
* - `extension` — the source-file extension to glob (`.go`, …).
* - `excludedDirGlobs` — vendored / build-output / VCS dirs to skip.
* - `configCandidates` — the ordered config-file precedence list
* (resolved-deps first, e.g. `['go.sum','go.mod']`).
* - `languageId` — the `graph:discover:<id>` log tag.
*
* `createDiscover` closes over those and returns the adapter's
* `discoverFiles(input): DiscoverOutput`. The collect loop, the symlink
* realpath/dedup/sort normalization (so I-9 referential transparency
* holds), and the `DiscoverOutput` assembly are shared verbatim.
*/
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';
/** Per-language inputs to the shared discover template. */
export interface TreeSitterDiscoverConfig {
/** Source-file extension WITHOUT the leading dot, e.g. `'go'`, `'py'`. */
readonly extension: string;
/** Directory globs to exclude from the recursive source-file walk. */
readonly excludedDirGlobs: readonly string[];
/**
* Ordered config-file precedence list (resolved-deps first). The first
* candidate that exists at the project root becomes the cacheKey anchor.
*/
readonly configCandidates: readonly string[];
/** Log-tag suffix for `graph:discover:<languageId>`. */
readonly languageId: string;
}
/** Builds the adapter's `discoverFiles` from per-language config. */
export function createDiscover(
config: TreeSitterDiscoverConfig,
): (input: DiscoverInput) => DiscoverOutput {
const { extension, excludedDirGlobs, configCandidates, languageId } = config;
const module = `graph:discover:${languageId}`;
const pattern = `**/*.${extension}`;
return function discoverFiles(input: DiscoverInput): DiscoverOutput {
logger.info({
evt: 'graph.discover.start',
module,
projectDir: input.cwd,
});
const projectDirAbs = normalizeProjectDir(input.cwd);
const configPathAbs = resolveConfigPath(
projectDirAbs,
input.configPathOverride,
configCandidates,
);
const files = collectFiles(projectDirAbs, pattern, excludedDirGlobs);
logger.info({
evt: 'graph.discover.complete',
module,
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,
configCandidates: readonly string[],
): string | undefined {
Iif (override !== undefined && override.length > 0) {
const abs = resolve(projectDirAbs, override);
return existsSync(abs) ? realpathOrPath(abs) : abs;
}
for (const candidate of configCandidates) {
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 collectFiles(
projectDirAbs: string,
pattern: string,
excludedDirGlobs: readonly string[],
): readonly string[] {
const matches: string[] = glob.sync(pattern, {
cwd: projectDirAbs,
absolute: true,
ignore: [...excludedDirGlobs],
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('/');
Iif (seen.has(key)) continue;
seen.add(key);
out.push(real);
}
out.sort();
return out;
}
|