@dailephd/my-dev-kit-lab 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/scripts/security/validate.js +8 -18
- package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +96 -0
- package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +202 -4
- package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +19 -1
- package/dist/src/audits/codeRot/detectors/testRotDetector.js +126 -0
- package/dist/src/audits/codeRot/utils/jvmSourceFactsUtils.js +153 -0
- package/dist/src/audits/core/collectSourceFacts.js +21 -10
- package/dist/src/audits/core/javaAnalyzer.js +282 -0
- package/dist/src/audits/core/jvmProjectMetadata.js +106 -0
- package/dist/src/audits/core/kotlinAnalyzer.js +280 -0
- package/dist/src/audits/core/languageAnalyzerRegistry.js +9 -3
- package/dist/src/audits/core/projectInventory.js +41 -0
- package/dist/src/audits/security/securityAuditAdapter.js +15 -12
- package/dist/src/securityValidation/report/writeSecurityReportFiles.js +28 -0
- package/package.json +97 -97
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/README.md +16 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/build.gradle.kts +4 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/java/com/example/Widget.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/kotlin/com/example/Gadget.kt +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/build/Main.class +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/dist/index.js +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/generated/schema.ts +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/vendor/lib.py +2 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/build.gradle.kts +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/settings.gradle.kts +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/java/com/example/FooService.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/kotlin/com/example/BarService.kt +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/java/com/example/FooServiceTest.java +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/kotlin/com/example/BarServiceTest.kt +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/README.md +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/app.py +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/tests/test_app.py +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/README.md +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/helper.js +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/tests/index.sample.ts +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/build.gradle.kts +4 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/pytests/test_app.py +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/app.py +2 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/helper.js +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/java/com/example/Multiplier.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/kotlin/com/example/Divider.kt +7 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { collectPythonProjectMetadata } from "../../core/pythonProjectMetadata.js";
|
|
4
|
+
import { collectJvmProjectMetadata } from "../../core/jvmProjectMetadata.js";
|
|
4
5
|
import { readBoundedFileText } from "../utils/boundedRead.js";
|
|
6
|
+
import { baseNameNoExt } from "../utils/filePatternUtils.js";
|
|
5
7
|
import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
|
|
6
8
|
import { getParsedSourceFacts, indexSourceFactsByPath } from "../utils/sourceFactsLookup.js";
|
|
7
9
|
import { splitLines } from "../utils/textLines.js";
|
|
10
|
+
import { jvmImportCandidatePaths, jvmImportLooksPossiblyLocal, jvmImportSimpleName, } from "../utils/jvmSourceFactsUtils.js";
|
|
8
11
|
// ---------------------------------------------------------------------------
|
|
9
12
|
// v0.3.0 Batch 4 — test-rot detector.
|
|
10
13
|
//
|
|
@@ -51,6 +54,9 @@ export const TEST_ROT_DETECTOR = {
|
|
|
51
54
|
// required condition -- its absence must not change whether a finding
|
|
52
55
|
// is emitted, only add or omit one extra evidence line.
|
|
53
56
|
const pythonMetadata = collectPythonProjectMetadata(ctx.target.rootPath, ctx.inventory);
|
|
57
|
+
// v0.3.3 Batch 2 -- same "computed once, weak supporting context only"
|
|
58
|
+
// treatment as pythonMetadata above.
|
|
59
|
+
const jvmMetadata = collectJvmProjectMetadata(ctx.target.rootPath, ctx.inventory);
|
|
54
60
|
for (const testFile of ctx.inventory.testFiles) {
|
|
55
61
|
const content = readBoundedFileText(ctx.target.rootPath, testFile.relativePath, testFile.sizeBytes, MAX_READ_BYTES);
|
|
56
62
|
if (content === null)
|
|
@@ -87,6 +93,15 @@ export const TEST_ROT_DETECTOR = {
|
|
|
87
93
|
// this guard is the fix for that latent cross-language bug.
|
|
88
94
|
issues.push(...findMissingPythonSourceImports(ctx, testFile.relativePath, parsedFacts, pythonMetadata));
|
|
89
95
|
}
|
|
96
|
+
else if (parsedFacts?.language === "java" || parsedFacts?.language === "kotlin") {
|
|
97
|
+
// v0.3.3 Batch 2 -- Java/Kotlin imports are fully-qualified dotted
|
|
98
|
+
// package names ("com.example.Foo"), neither TS/JS's path-like
|
|
99
|
+
// "./module" syntax nor Python's dotted-relative-import syntax.
|
|
100
|
+
// Routing them through either existing resolver would misresolve
|
|
101
|
+
// every import -- this guard is the JVM analogue of the Python
|
|
102
|
+
// guard immediately above.
|
|
103
|
+
issues.push(...findMissingJvmSourceImports(ctx, testFile.relativePath, parsedFacts, jvmMetadata));
|
|
104
|
+
}
|
|
90
105
|
else if (parsedFacts) {
|
|
91
106
|
issues.push(...findMissingSourceImportsFromSourceFacts(ctx, testFile.relativePath, parsedFacts));
|
|
92
107
|
}
|
|
@@ -291,6 +306,117 @@ function findMissingPythonSourceImports(ctx, testRelativePath, facts, pythonMeta
|
|
|
291
306
|
}
|
|
292
307
|
return issues;
|
|
293
308
|
}
|
|
309
|
+
// v0.3.3 Batch 2 -- Java/Kotlin-specific counterpart to
|
|
310
|
+
// findMissingSourceImportsFromSourceFacts()/findMissingPythonSourceImports()
|
|
311
|
+
// above. Only ever called for facts with `language === "java"` or
|
|
312
|
+
// `"kotlin"` (see caller) -- kept fully separate from both existing
|
|
313
|
+
// resolvers for the same reason the Python resolver is kept separate from
|
|
314
|
+
// the TS/JS one: the resolution algorithm (fully-qualified dotted package
|
|
315
|
+
// names -> conventional Gradle/Maven source-set paths) is genuinely
|
|
316
|
+
// different from either.
|
|
317
|
+
//
|
|
318
|
+
// Deliberately lower severity/confidence than the TS/JS counterpart, same
|
|
319
|
+
// tier as the Python counterpart: this scan has no real classpath/build
|
|
320
|
+
// awareness (no Gradle/Maven execution, no compiler), so it is weaker
|
|
321
|
+
// evidence than the TS/JS analyzer's near-exact relative-path resolution.
|
|
322
|
+
// Standard-library, common third-party test-framework, and wildcard
|
|
323
|
+
// imports are skipped entirely via jvmImportLooksPossiblyLocal() -- see
|
|
324
|
+
// jvmSourceFactsUtils.ts for the full skip-prefix list and rationale.
|
|
325
|
+
function findMissingJvmSourceImports(ctx, testRelativePath, facts, jvmMetadata) {
|
|
326
|
+
const issues = [];
|
|
327
|
+
const isKotlin = facts.language === "kotlin";
|
|
328
|
+
const languageLabel = isKotlin ? "Kotlin" : "Java";
|
|
329
|
+
// Kotlin may legitimately import Java source in a mixed JVM project;
|
|
330
|
+
// Java never imports Kotlin-only source through a plain `import`
|
|
331
|
+
// statement in the way this conservative scan can see, so a Java test
|
|
332
|
+
// only ever checks .java candidates.
|
|
333
|
+
const extensions = isKotlin ? ["kt", "java"] : ["java"];
|
|
334
|
+
// Both recognized main and test source-set directories are checked --
|
|
335
|
+
// the latter specifically covers the "importing a test-scoped helper
|
|
336
|
+
// from another test-set file" case (the importing file here is, by
|
|
337
|
+
// definition, already test-scoped, since this function is only ever
|
|
338
|
+
// called from the per-test-file loop above).
|
|
339
|
+
const candidateDirs = [...jvmMetadata.recognizedSourceDirectories, ...jvmMetadata.recognizedTestDirectories];
|
|
340
|
+
const inventoryBasenameIndex = buildJvmInventoryBasenameIndex(ctx, extensions);
|
|
341
|
+
for (const imp of facts.imports) {
|
|
342
|
+
if (!jvmImportLooksPossiblyLocal(imp))
|
|
343
|
+
continue;
|
|
344
|
+
// A Java `import static com.example.Foo.bar;` (or a Kotlin top-level
|
|
345
|
+
// member import) has its *member* name as the last dotted segment, not
|
|
346
|
+
// a class name -- this analyzer's ImportFact model (shared across
|
|
347
|
+
// every language) does not distinguish "static import of a member"
|
|
348
|
+
// from "plain class import", so both interpretations are tried: the
|
|
349
|
+
// full dotted path as-is (the common, plain-class-import case), and
|
|
350
|
+
// with its last segment dropped (the static/member-import case). This
|
|
351
|
+
// is a detector-side fix, not a change to javaAnalyzer.ts/
|
|
352
|
+
// kotlinAnalyzer.ts's ImportFact shape.
|
|
353
|
+
const parentSource = imp.source.includes(".") ? imp.source.slice(0, imp.source.lastIndexOf(".")) : null;
|
|
354
|
+
const conventionalCandidates = [
|
|
355
|
+
...jvmImportCandidatePaths(imp.source, candidateDirs, extensions),
|
|
356
|
+
...(parentSource ? jvmImportCandidatePaths(parentSource, candidateDirs, extensions) : []),
|
|
357
|
+
];
|
|
358
|
+
const resolved = conventionalCandidates.some((candidate) => fs.existsSync(path.join(ctx.target.rootPath, candidate))) ||
|
|
359
|
+
inventoryBasenameIndex.has(jvmImportSimpleName(imp.source)) ||
|
|
360
|
+
(parentSource !== null && inventoryBasenameIndex.has(jvmImportSimpleName(parentSource)));
|
|
361
|
+
if (resolved)
|
|
362
|
+
continue;
|
|
363
|
+
const evidence = [
|
|
364
|
+
{
|
|
365
|
+
kind: "file",
|
|
366
|
+
message: `Analyzer-recorded ${languageLabel} import does not resolve to a candidate local source file under a recognized source-set directory, nor to any inventoried file with a matching basename. This is a best-effort, path-based check -- no compiler/classpath analysis was performed, and no Gradle/Maven execution was performed, so confirm manually before treating it as stale.`,
|
|
367
|
+
filePath: testRelativePath,
|
|
368
|
+
excerpt: imp.source,
|
|
369
|
+
source: DETECTOR_ID,
|
|
370
|
+
confidence: "low",
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
if (candidateDirs.length > 0) {
|
|
374
|
+
evidence.push({
|
|
375
|
+
kind: "observation",
|
|
376
|
+
message: `Weak supporting context: recognized source-set directories for this project are: ${candidateDirs.join(", ")}.`,
|
|
377
|
+
source: DETECTOR_ID,
|
|
378
|
+
confidence: "low",
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
issues.push(makeCodeRotIssue({
|
|
382
|
+
auditType: "code-rot",
|
|
383
|
+
detectorId: DETECTOR_ID,
|
|
384
|
+
idCues: [testRelativePath, `missing-${facts.language}-source-import`, imp.source],
|
|
385
|
+
title: `Test "${testRelativePath}" imports a ${languageLabel} class that may not exist: "${imp.source}"`,
|
|
386
|
+
description: `${testRelativePath} imports "${imp.source}" (source-facts-derived via the ${languageLabel} analyzer). No candidate local source file was found under a recognized source-set directory, and no inventoried file shares its simple name. This is a deterministic source-facts signal from a conservative, non-semantic scan -- no compiler/classpath analysis was performed, and no Gradle/Maven execution was performed, so this does not resolve the real classpath, package visibility, or multi-module project layout. It may indicate a stale import but is not proof.`,
|
|
387
|
+
severity: "low",
|
|
388
|
+
confidence: "low",
|
|
389
|
+
falsePositiveRisk: "medium",
|
|
390
|
+
category: "test-rot",
|
|
391
|
+
recommendedAction: "Confirm the imported class exists (it may resolve via a module/classpath this scan cannot see) before treating this as stale.",
|
|
392
|
+
suggestedFixStrategy: `Update or remove the import "${imp.source}" in ${testRelativePath} if confirmed stale.`,
|
|
393
|
+
validationCommands: [`npx vitest run ${testRelativePath}`],
|
|
394
|
+
releaseBlocking: false,
|
|
395
|
+
implementationBlocking: false,
|
|
396
|
+
autoFixEligible: false,
|
|
397
|
+
evidence,
|
|
398
|
+
affectedFiles: [testRelativePath],
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
return issues;
|
|
402
|
+
}
|
|
403
|
+
// Lowercased-basename index (without extension) of every inventoried
|
|
404
|
+
// Java/Kotlin file matching the given extensions -- the lenient,
|
|
405
|
+
// last-resort fallback used by findMissingJvmSourceImports() above so a
|
|
406
|
+
// real (if unconventionally placed) local file is never flagged as
|
|
407
|
+
// missing merely because the project doesn't follow the conventional
|
|
408
|
+
// src/main/{java,kotlin} source-set layout (mirrors
|
|
409
|
+
// jvmProjectMetadata.ts's own "non-conventional layout" reasoning).
|
|
410
|
+
function buildJvmInventoryBasenameIndex(ctx, extensions) {
|
|
411
|
+
const extSet = new Set(extensions.map((e) => `.${e}`));
|
|
412
|
+
const basenames = new Set();
|
|
413
|
+
for (const file of ctx.inventory.files) {
|
|
414
|
+
if (!extSet.has(file.extension))
|
|
415
|
+
continue;
|
|
416
|
+
basenames.add(baseNameNoExt(file.relativePath));
|
|
417
|
+
}
|
|
418
|
+
return basenames;
|
|
419
|
+
}
|
|
294
420
|
function findStaleNpmRunReferences(testRelativePath, content, scriptNames) {
|
|
295
421
|
const issues = [];
|
|
296
422
|
const seen = new Set();
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.3.3 Batch 2 -- shared JVM (Java/Kotlin) source-facts helpers reused by
|
|
3
|
+
// deadCodeCandidateDetector.ts, duplicateImplementationDetector.ts, and
|
|
4
|
+
// testRotDetector.ts.
|
|
5
|
+
//
|
|
6
|
+
// Deterministic and dependency-free: no Gradle/Maven execution, no
|
|
7
|
+
// classpath resolution, no Java/Kotlin compiler, no traversal outside the
|
|
8
|
+
// target root. Everything here operates purely on already-collected
|
|
9
|
+
// SourceFacts/inventory data (see javaAnalyzer.ts/kotlinAnalyzer.ts/
|
|
10
|
+
// jvmProjectMetadata.ts from v0.3.3 Batch 1).
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Import prefixes considered standard-library or common third-party test
|
|
13
|
+
// framework noise -- never treated as "possibly a local project import" by
|
|
14
|
+
// testRotDetector.ts's JVM resolver, regardless of whether a local file
|
|
15
|
+
// happens to share a path segment. Matched against the import source's
|
|
16
|
+
// leading dotted segments so "java.util.List" matches "java." but a
|
|
17
|
+
// hypothetical local package literally named "javautils.Foo" (no dot after
|
|
18
|
+
// "java") would not.
|
|
19
|
+
const JVM_SKIPPABLE_IMPORT_PREFIXES = [
|
|
20
|
+
"java.",
|
|
21
|
+
"javax.",
|
|
22
|
+
"jakarta.",
|
|
23
|
+
"kotlin.",
|
|
24
|
+
"kotlinx.",
|
|
25
|
+
"org.junit.",
|
|
26
|
+
"org.assertj.",
|
|
27
|
+
"org.mockito.",
|
|
28
|
+
"org.hamcrest.",
|
|
29
|
+
"io.kotest.",
|
|
30
|
+
];
|
|
31
|
+
export function isJvmSkippableImportSource(source) {
|
|
32
|
+
return JVM_SKIPPABLE_IMPORT_PREFIXES.some((prefix) => source.startsWith(prefix));
|
|
33
|
+
}
|
|
34
|
+
// True for a wildcard-style import (`import com.example.*` or, for Java
|
|
35
|
+
// static imports, `import static com.example.Foo.*`). Deliberately treated
|
|
36
|
+
// as never "safely local" by testRotDetector.ts's JVM resolver -- there is
|
|
37
|
+
// no way to derive a single candidate file from a wildcard without real
|
|
38
|
+
// classpath/package-member awareness, so guessing would either miss real
|
|
39
|
+
// stale imports (if resolved unconditionally) or produce high-noise false
|
|
40
|
+
// positives (if flagged unconditionally). Skipping entirely is the
|
|
41
|
+
// conservative choice.
|
|
42
|
+
export function isJvmWildcardImportSource(source) {
|
|
43
|
+
return source.endsWith(".*");
|
|
44
|
+
}
|
|
45
|
+
// The simple (unqualified) name a dotted JVM import source resolves to --
|
|
46
|
+
// the last segment after the final ".". For a Java static import
|
|
47
|
+
// (`static com.example.Foo.bar`), this is the imported static member name,
|
|
48
|
+
// not the enclosing class -- an accepted, documented conflation for this
|
|
49
|
+
// conservative, best-effort scan (see javaAnalyzer.ts's own header comment
|
|
50
|
+
// on why `import static` is not modeled any more precisely than a plain
|
|
51
|
+
// import).
|
|
52
|
+
export function jvmImportSimpleName(source) {
|
|
53
|
+
const idx = source.lastIndexOf(".");
|
|
54
|
+
return idx === -1 ? source : source.slice(idx + 1);
|
|
55
|
+
}
|
|
56
|
+
// Converts a dotted JVM import source (e.g. "com.example.Foo") into a
|
|
57
|
+
// package-shaped relative path segment (e.g. "com/example/Foo"), dropping
|
|
58
|
+
// any trailing wildcard/member segment handling -- callers are responsible
|
|
59
|
+
// for skipping wildcards via isJvmWildcardImportSource() first.
|
|
60
|
+
export function jvmImportToPackagePath(source) {
|
|
61
|
+
return source.split(".").join("/");
|
|
62
|
+
}
|
|
63
|
+
// Candidate on-disk relative paths for a (non-wildcard, non-skippable)
|
|
64
|
+
// import, given the project's recognized Java/Kotlin source-set
|
|
65
|
+
// directories (see jvmProjectMetadata.ts's recognizedSourceDirectories).
|
|
66
|
+
// Conservative and path-based only: this never resolves the real classpath,
|
|
67
|
+
// only checks the conventional Gradle/Maven `src/main/{java,kotlin}/...`
|
|
68
|
+
// layout. `extensions` lets a Kotlin importer also check `.java` (Kotlin
|
|
69
|
+
// can legitimately import Java source in a mixed JVM project) while a Java
|
|
70
|
+
// importer only ever checks `.java`.
|
|
71
|
+
export function jvmImportCandidatePaths(source, recognizedSourceDirectories, extensions) {
|
|
72
|
+
const packagePath = jvmImportToPackagePath(source);
|
|
73
|
+
const candidates = [];
|
|
74
|
+
for (const dir of recognizedSourceDirectories) {
|
|
75
|
+
for (const ext of extensions) {
|
|
76
|
+
candidates.push(`${dir}/${packagePath}.${ext}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return candidates;
|
|
80
|
+
}
|
|
81
|
+
// True when an ImportFact looks worth checking for local-file existence at
|
|
82
|
+
// all -- i.e. not a standard-library/common-test-framework import and not a
|
|
83
|
+
// wildcard. Callers still need their own existence check; this only
|
|
84
|
+
// filters out imports this conservative scan should never second-guess.
|
|
85
|
+
export function jvmImportLooksPossiblyLocal(imp) {
|
|
86
|
+
if (isJvmWildcardImportSource(imp.source))
|
|
87
|
+
return false;
|
|
88
|
+
if (isJvmSkippableImportSource(imp.source))
|
|
89
|
+
return false;
|
|
90
|
+
// A bare, undotted import (e.g. a same-package reference, or a malformed
|
|
91
|
+
// scan artifact) has no package path to resolve against -- not
|
|
92
|
+
// checkable by this path-based resolver.
|
|
93
|
+
if (!imp.source.includes("."))
|
|
94
|
+
return false;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Declaration-name conservatism helpers.
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Common JVM class/type names that legitimately repeat across unrelated
|
|
101
|
+
// projects/modules (a "Main" class, an "App" entrypoint, a "Config" class
|
|
102
|
+
// per module, etc.) -- excluded from duplicateImplementationDetector.ts's
|
|
103
|
+
// JVM duplicate-declaration-candidate check the same way
|
|
104
|
+
// GENERIC_DECLARATION_NAMES/GENERIC_INFRA_BASENAMES already exclude their
|
|
105
|
+
// TS/JS/Python/file-basename equivalents.
|
|
106
|
+
const JVM_COMMON_TYPE_NAMES = new Set([
|
|
107
|
+
"main",
|
|
108
|
+
"app",
|
|
109
|
+
"application",
|
|
110
|
+
"config",
|
|
111
|
+
"configuration",
|
|
112
|
+
"utils",
|
|
113
|
+
"helper",
|
|
114
|
+
"factory",
|
|
115
|
+
"builder",
|
|
116
|
+
"manager",
|
|
117
|
+
"service",
|
|
118
|
+
"controller",
|
|
119
|
+
"repository",
|
|
120
|
+
]);
|
|
121
|
+
export function isJvmCommonLowSignalDeclarationName(name) {
|
|
122
|
+
const lower = name.toLowerCase();
|
|
123
|
+
if (JVM_COMMON_TYPE_NAMES.has(lower))
|
|
124
|
+
return true;
|
|
125
|
+
if (lower.startsWith("test"))
|
|
126
|
+
return true;
|
|
127
|
+
return lower === "setup" || lower === "teardown";
|
|
128
|
+
}
|
|
129
|
+
// Java/Kotlin lifecycle, `Object`-override, and bean-accessor-shaped names
|
|
130
|
+
// -- excluded from deadCodeCandidateDetector.ts's JVM dead-code-candidate
|
|
131
|
+
// check, the same way isGenericOrTestPrefixedDeclarationName() already
|
|
132
|
+
// excludes TS/JS/Python's equivalents. `get*`/`set*`/`is*` in particular
|
|
133
|
+
// cover the extremely common JavaBean accessor convention, where a
|
|
134
|
+
// "no detected cross-file reference" signal is essentially meaningless
|
|
135
|
+
// (framework/reflection-based access is invisible to this static scan).
|
|
136
|
+
const JVM_LIFECYCLE_EXACT_NAMES = new Set([
|
|
137
|
+
"main",
|
|
138
|
+
"tostring",
|
|
139
|
+
"equals",
|
|
140
|
+
"hashcode",
|
|
141
|
+
"compareto",
|
|
142
|
+
"clone",
|
|
143
|
+
"setup",
|
|
144
|
+
"teardown",
|
|
145
|
+
"copy", // Kotlin data class synthesized-like member
|
|
146
|
+
]);
|
|
147
|
+
const JVM_LIFECYCLE_PREFIXES = ["get", "set", "is", "can", "should", "before", "after", "test", "component"];
|
|
148
|
+
export function isJvmLifecycleOrConventionalDeclarationName(name) {
|
|
149
|
+
const lower = name.toLowerCase();
|
|
150
|
+
if (JVM_LIFECYCLE_EXACT_NAMES.has(lower))
|
|
151
|
+
return true;
|
|
152
|
+
return JVM_LIFECYCLE_PREFIXES.some((prefix) => lower.startsWith(prefix));
|
|
153
|
+
}
|
|
@@ -41,17 +41,18 @@ export async function collectSourceFacts(targetRoot, inventory, registry = DEFAU
|
|
|
41
41
|
// filtering preserves that order, so no re-sort is needed here.
|
|
42
42
|
const eligible = inventory.files.filter((entry) => ANALYZABLE_ROLES.has(entry.role));
|
|
43
43
|
for (const entry of eligible) {
|
|
44
|
+
const relativePath = normalizeRelativePath(entry.relativePath);
|
|
44
45
|
const analyzer = selectLanguageAnalyzer(registry, entry.language, entry.extension);
|
|
45
46
|
if (!analyzer) {
|
|
46
|
-
files.push(buildFallbackFacts(entry));
|
|
47
|
+
files.push(buildFallbackFacts(entry, relativePath));
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
49
|
-
const absolutePath =
|
|
50
|
+
const absolutePath = resolveInventoryFilePath(resolvedRoot, relativePath);
|
|
50
51
|
try {
|
|
51
52
|
const content = fs.readFileSync(absolutePath, "utf8");
|
|
52
53
|
const facts = await analyzer.analyzeFile({
|
|
53
54
|
targetRoot: resolvedRoot,
|
|
54
|
-
relativePath
|
|
55
|
+
relativePath,
|
|
55
56
|
absolutePath,
|
|
56
57
|
content,
|
|
57
58
|
inventoryEntry: entry,
|
|
@@ -65,13 +66,13 @@ export async function collectSourceFacts(targetRoot, inventory, registry = DEFAU
|
|
|
65
66
|
const diagnostic = {
|
|
66
67
|
severity: "error",
|
|
67
68
|
code: "analyzer-error",
|
|
68
|
-
message: `Analyzer "${analyzer.id}" failed on "${
|
|
69
|
-
path:
|
|
69
|
+
message: `Analyzer "${analyzer.id}" failed on "${relativePath}": ${message}`,
|
|
70
|
+
path: relativePath,
|
|
70
71
|
analyzerId: analyzer.id,
|
|
71
72
|
};
|
|
72
73
|
analyzerDiagnostics.push(diagnostic);
|
|
73
74
|
files.push({
|
|
74
|
-
relativePath
|
|
75
|
+
relativePath,
|
|
75
76
|
language: entry.language,
|
|
76
77
|
role: entry.role,
|
|
77
78
|
parseStatus: "parse-error",
|
|
@@ -94,7 +95,7 @@ export async function collectSourceFacts(targetRoot, inventory, registry = DEFAU
|
|
|
94
95
|
warnings,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
|
-
function buildFallbackFacts(entry) {
|
|
98
|
+
function buildFallbackFacts(entry, relativePath) {
|
|
98
99
|
const known = KNOWN_LANGUAGES.has(entry.language);
|
|
99
100
|
const parseStatus = known ? "file-level-only" : "unsupported";
|
|
100
101
|
const diagnostic = known
|
|
@@ -102,18 +103,18 @@ function buildFallbackFacts(entry) {
|
|
|
102
103
|
severity: "info",
|
|
103
104
|
code: "no-analyzer-registered",
|
|
104
105
|
message: `No language analyzer is registered for "${entry.language}" yet; only inventory-level facts are available.`,
|
|
105
|
-
path:
|
|
106
|
+
path: relativePath,
|
|
106
107
|
analyzerId: null,
|
|
107
108
|
}
|
|
108
109
|
: {
|
|
109
110
|
severity: "info",
|
|
110
111
|
code: "unsupported-language",
|
|
111
112
|
message: `Language "${entry.language}" is not supported for source-fact extraction.`,
|
|
112
|
-
path:
|
|
113
|
+
path: relativePath,
|
|
113
114
|
analyzerId: null,
|
|
114
115
|
};
|
|
115
116
|
return {
|
|
116
|
-
relativePath
|
|
117
|
+
relativePath,
|
|
117
118
|
language: entry.language,
|
|
118
119
|
role: entry.role,
|
|
119
120
|
parseStatus,
|
|
@@ -126,6 +127,16 @@ function buildFallbackFacts(entry) {
|
|
|
126
127
|
diagnostics: [diagnostic],
|
|
127
128
|
};
|
|
128
129
|
}
|
|
130
|
+
function normalizeRelativePath(relativePath) {
|
|
131
|
+
const normalized = path.posix.normalize(relativePath.replace(/\\/g, "/"));
|
|
132
|
+
if (normalized === ".")
|
|
133
|
+
return "";
|
|
134
|
+
return normalized.replace(/^\/+/, "").replace(/^\.\//, "");
|
|
135
|
+
}
|
|
136
|
+
function resolveInventoryFilePath(root, relativePath) {
|
|
137
|
+
const parts = relativePath.split("/").filter((part) => part.length > 0);
|
|
138
|
+
return parts.length === 0 ? root : path.resolve(root, ...parts);
|
|
139
|
+
}
|
|
129
140
|
function countByLanguage(files) {
|
|
130
141
|
const counts = Object.fromEntries(NORMALIZED_LANGUAGES.map((l) => [l, 0]));
|
|
131
142
|
for (const file of files) {
|