Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/superpowers/baselines/2026-04-17/BASELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ Ordered by severity. Each item cites the raw artifact it was derived from.
- **SpotBugs: 8 HIGH-priority findings (priority=1) + 1,484 at priority=2.** Total 1,492. HIGH findings must be triaged individually (read `raw/spotbugs.xml`). Noise-dominant rules (`NM_METHOD_NAMING_CONVENTION`=730, `SF_SWITCH_NO_DEFAULT`=448) should be filtered via a SpotBugs exclude file so real signal surfaces; real-concern patterns that deserve review now: `NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE` (26), `BC_UNCONFIRMED_CAST` (55), `UL_UNRELEASED_LOCK_EXCEPTION_PATH` (1), `WMI_WRONG_MAP_ITERATOR` (2), `ES_COMPARING_STRINGS_WITH_EQ` (2), `MT_CORRECTNESS` category (1).
- Raw: `raw/spotbugs.xml`, `raw/spotbugs-summary.json`.
- **RESOLVED (2026-04-17, branch `phase-a/fixups-spotbugs`)**: Added `spotbugs-exclude.xml` covering ANTLR-generated parsers and global noise rules (`NM_METHOD_NAMING_CONVENTION`, `SF_SWITCH_NO_DEFAULT`, `EI_EXPOSE_REP`/`EI_EXPOSE_REP2`, `MS_PKGPROTECT`/`MS_FINAL_PKGPROTECT`), wired via `pom.xml`. Fixed all 8 priority-1 findings in codeiq code (UTF-8 in `Analyzer.getGitHead`, narrowed catch in `IndexCommand`, dead-store removed in `PluginsCommand`, `.equals()` in `AntlrParserFactory` + `CSharpPreprocessorParserBase`, try-finally unlock in `AnalysisCache.removeFile`, merged duplicate branches in `CodeIqApplication`, removed dead `BundleCommand.writeEntry` overload, `entrySet()` iteration in `PluginsCommand` + `GitLabCiDetector`, narrowed `VersionCommand` catch). **Final: 1,492 → 38 (-97.5%); priority-1: 8 → 0.** Remaining 38 are priority-2 STYLE/BAD_PRACTICE; no CORRECTNESS/MT_CORRECTNESS/SECURITY left. Next-pass candidates: 26 `NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE`. Post-triage summary: `raw/spotbugs-summary-after-triage.json`.
- **PARTIALLY RESOLVED — NP_NULL subset (2026-04-17, branch `phase-a/fix-np-null`)**: all 26 `NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE` findings fixed — count 26 → 0. All 26 shared one pattern: calling `.toString()` on `Path.getFileName()` (or once `Path.getParent()`) where the result can be null for root-like paths. Fix: replaced every site with `java.util.Objects.toString(path.getFileName(), fallback)` using sensible per-site fallbacks (`""` for filename comparisons, `"unknown"` / `"bundle"` / `"flow"` for human-facing project names, `PROP_ROOT` in Analyzer). One `AnalysisCache` constructor call (`Files.createDirectories(dbPath.getParent())`) rewritten as a null-guarded block. 12 files touched (Analyzer.java 5 sites inc. a triplicated block, plus FileDiscovery, FileClassifier, ServiceDetector, ConfigScanner, AnalysisCache, BundleCommand, EnrichCommand, FlowCommand, PluginsCommand, StatsCommand, TopologyCommand). Full `mvn test` still green (3,059 tests, 0 failures). The broader SpotBugs triage (noise exclude + priority-1 fixes) lives on `phase-a/fixups-spotbugs`.

### Medium

Expand Down
26 changes: 15 additions & 11 deletions src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
// 5b. Detect service boundaries and create SERVICE nodes
report.accept("Detecting service boundaries...");
var serviceDetector = new ServiceDetector();
String projectDirName = root.getFileName() != null ? root.getFileName().toString() : PROP_ROOT;
String projectDirName = java.util.Objects.toString(root.getFileName(), PROP_ROOT);
var serviceResult = serviceDetector.detect(allNodes, builder.getEdges(), projectDirName, root);
if (!serviceResult.serviceNodes().isEmpty()) {
serviceResult.serviceNodes().forEach(n -> n.setProvenance(builder.getProvenance()));
Expand Down Expand Up @@ -885,10 +885,11 @@
} else {
filesSkipped++;
// Zero data loss: create minimal inventory node for filtered files
String filteredFileName = java.util.Objects.toString(file.path().getFileName(), "");
CodeNode fileNode = new CodeNode(
"file:" + file.path() + ":module:" + file.path().getFileName(),
"file:" + file.path() + ":module:" + filteredFileName,

Check failure on line 890 in src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "file:" 4 times.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_code-iq&issues=AZ2biRxBrNAGlJL6rcfc&open=AZ2biRxBrNAGlJL6rcfc&pullRequest=44

Check failure on line 890 in src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal ":module:" 4 times.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_code-iq&issues=AZ2biRxBrNAGlJL6rcfd&open=AZ2biRxBrNAGlJL6rcfd&pullRequest=44
NodeKind.MODULE,
file.path().getFileName().toString());
filteredFileName);
fileNode.setFilePath(file.path().toString());
fileNode.setModule(DetectorUtils.deriveModuleName(file.path().toString(), file.language()));
fileNode.getProperties().put("status", "filtered");
Expand Down Expand Up @@ -1130,7 +1131,7 @@
// Collect unique module directories from boundary marker files
Set<String> moduleDirs = new java.util.TreeSet<>();
for (DiscoveredFile file : files) {
if (MODULE_BOUNDARY_MARKERS.contains(file.path().getFileName().toString())) {
if (MODULE_BOUNDARY_MARKERS.contains(java.util.Objects.toString(file.path().getFileName(), ""))) {
Path parent = file.path().getParent();
moduleDirs.add(parent != null ? parent.toString().replace('\\', '/') : "");
}
Expand Down Expand Up @@ -1235,10 +1236,11 @@
if (isMinified(file, content)) {
log.debug("Skipping detectors for minified file: {}", file.path());
String moduleName = DetectorUtils.deriveModuleName(file.path().toString(), file.language());
String fileNameStr = java.util.Objects.toString(file.path().getFileName(), "");
CodeNode node = new CodeNode(
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : file.path().getFileName().toString()),
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : fileNameStr),
NodeKind.MODULE,
file.path().getFileName().toString());
fileNameStr);
node.setFilePath(file.path().toString());
node.setModule(moduleName);
node.setProperties(new java.util.LinkedHashMap<>(Map.of("minified", true, "file_type", "minified")));
Expand Down Expand Up @@ -1351,7 +1353,7 @@
}

private boolean isMinified(DiscoveredFile file, String content) {
String name = file.path().getFileName().toString();
String name = java.util.Objects.toString(file.path().getFileName(), "");
boolean nameHint = name.endsWith(".min.js") || name.endsWith(".bundle.js")
|| name.endsWith(".min.css") || name.endsWith(".min.mjs");
boolean jsOrCss = name.endsWith(".js") || name.endsWith(".mjs") || name.endsWith(".cjs")
Expand Down Expand Up @@ -1434,10 +1436,11 @@
if (isMinified(file, content)) {
log.debug("Skipping detectors for minified file: {}", file.path());
String moduleName = DetectorUtils.deriveModuleName(file.path().toString(), file.language());
String fileNameStr = java.util.Objects.toString(file.path().getFileName(), "");
CodeNode node = new CodeNode(
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : file.path().getFileName().toString()),
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : fileNameStr),
NodeKind.MODULE,
file.path().getFileName().toString());
fileNameStr);
node.setFilePath(file.path().toString());
node.setModule(moduleName);
node.setProperties(new java.util.LinkedHashMap<>(Map.of("minified", true, "file_type", "minified")));
Expand Down Expand Up @@ -1520,10 +1523,11 @@
*/
private static DetectorResult createInventoryNode(DiscoveredFile file, String fileType) {
String moduleName = DetectorUtils.deriveModuleName(file.path().toString(), file.language());
String fileNameStr = java.util.Objects.toString(file.path().getFileName(), "");
CodeNode node = new CodeNode(
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : file.path().getFileName().toString()),
"file:" + file.path() + ":module:" + (moduleName != null ? moduleName : fileNameStr),
NodeKind.MODULE,
file.path().getFileName().toString());
fileNameStr);
node.setFilePath(file.path().toString());
node.setModule(moduleName);
node.setProperties(new java.util.LinkedHashMap<>(Map.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private void scanSpringConfig(Path root, InfrastructureRegistry registry) {

for (Path candidate : candidates) {
if (Files.isRegularFile(candidate)) {
String name = candidate.getFileName().toString();
String name = java.util.Objects.toString(candidate.getFileName(), "");
if (name.endsWith(".properties")) {
parseSpringProperties(candidate, registry);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public enum FileType {
*/
public static FileType classify(Path relativePath, String language) {
String pathStr = relativePath.toString().replace('\\', '/');
String fileName = relativePath.getFileName().toString();
String fileName = java.util.Objects.toString(relativePath.getFileName(), "");
String ext = getExtension(fileName);

// Binary check first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ private List<DiscoveredFile> discoverViaWalk(Path root) {
Files.walkFileTree(root, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
String dirName = dir.getFileName() != null ? dir.getFileName().toString() : "";
// Objects.toString yields fallback when getFileName() is null (root paths).
String dirName = java.util.Objects.toString(dir.getFileName(), "");
if (DEFAULT_EXCLUDES.contains(dirName)) {
return FileVisitResult.SKIP_SUBTREE;
}
Expand Down Expand Up @@ -243,7 +244,7 @@ private boolean isExcluded(Path relPath) {
}

private static boolean isExcludedFilename(Path relPath) {
String filename = relPath.getFileName().toString();
String filename = java.util.Objects.toString(relPath.getFileName(), "");
return EXCLUDED_FILENAMES.contains(filename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public ServiceDetectionResult detect(List<CodeNode> nodes, List<CodeEdge> edges,
String filePath = node.getFilePath();
if (filePath == null) continue;

String fileName = Path.of(filePath).getFileName().toString();
String fileName = java.util.Objects.toString(Path.of(filePath).getFileName(), "");
String dirPath = parentDir(filePath);

String buildTool = BUILD_FILES.get(fileName);
Expand Down Expand Up @@ -343,7 +343,7 @@ private void scanFilesystemForBuildFiles(Path root, Path projectRoot, Map<String

@Override
public java.nio.file.FileVisitResult preVisitDirectory(Path dir, java.nio.file.attribute.BasicFileAttributes attrs) {
String dirName = dir.getFileName() != null ? dir.getFileName().toString() : "";
String dirName = java.util.Objects.toString(dir.getFileName(), "");
if (SKIP_DIRS.contains(dirName)) {
return java.nio.file.FileVisitResult.SKIP_SUBTREE;
}
Expand All @@ -352,7 +352,7 @@ public java.nio.file.FileVisitResult preVisitDirectory(Path dir, java.nio.file.a

@Override
public java.nio.file.FileVisitResult visitFile(Path file, java.nio.file.attribute.BasicFileAttributes attrs) {
String name = file.getFileName().toString();
String name = java.util.Objects.toString(file.getFileName(), "");
boolean isBuildFile = BUILD_FILES.containsKey(name)
|| name.endsWith(CSPROJ_EXTENSION) || name.endsWith(FSPROJ_EXTENSION)
|| name.endsWith(VBPROJ_EXTENSION) || name.endsWith(GEMSPEC_EXTENSION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ public AnalysisCache(Path dbPath, boolean readOnly) {
this.dbPath = dbPath;
try {
if (!readOnly) {
Files.createDirectories(dbPath.getParent());
// dbPath.getParent() is null for a bare filename (no directory
// component). Treat "already a directory" as nothing to create.
Path parent = dbPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
}
// Strip .db extension if present — H2 appends its own .mv.db
String dbFile = dbPath.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public Integer call() {
return 1;
}

String projectName = root.getFileName().toString();
String projectName = java.util.Objects.toString(root.getFileName(), "bundle");
String bundleTag = tag != null ? tag : "latest";

Path zipPath = output != null ? output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,12 @@ public Integer call() {

// 1. Open H2 file
Path cachePath = root.resolve(config.getCacheDir()).resolve("analysis-cache.db");
if (!Files.exists(cachePath.getParent())) {
CliOutput.error("No index found at " + cachePath.getParent());
// cachePath.getParent() is always non-null here because we resolve off
// `root` (a directory), but null-guard explicitly for SpotBugs and to
// protect against a future refactor that changes the resolution.
Path cacheParent = cachePath.getParent();
if (cacheParent == null || !Files.exists(cacheParent)) {
CliOutput.error("No index found at " + cacheParent);
CliOutput.info(" Run 'code-iq index " + root + "' first.");
return 1;
}
Expand Down Expand Up @@ -180,7 +184,7 @@ private int enrichFromCache(AnalysisCache cache, Path root, NumberFormat nf, Ins
CliOutput.step("[^]", "Detecting service boundaries...");
stepStart = Instant.now();
var serviceDetector = new io.github.randomcodespace.iq.analyzer.ServiceDetector();
String projectName = root.getFileName().toString();
String projectName = java.util.Objects.toString(root.getFileName(), "unknown");
var serviceResult = serviceDetector.detect(enrichedNodes, enrichedEdges, projectName, root);
if (!serviceResult.serviceNodes().isEmpty()) {
serviceResult.serviceNodes().forEach(n -> n.setProvenance(builder.getProvenance()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ public Integer call() {
String content;

if ("html".equalsIgnoreCase(format)) {
String projectName = path.toAbsolutePath().getFileName().toString();
String projectName = java.util.Objects.toString(
path.toAbsolutePath().getFileName(), "flow");
content = engine.renderInteractive(projectName);
} else {
FlowDiagram diagram = engine.generate(view.toLowerCase());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ public Integer call() {

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
String name = dir.getFileName() != null ? dir.getFileName().toString() : "";
String name = java.util.Objects.toString(dir.getFileName(), "");
if (SKIP_DIRS.contains(name)) {
return FileVisitResult.SKIP_SUBTREE;
}
Expand All @@ -356,7 +356,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (!attrs.isRegularFile()) return FileVisitResult.CONTINUE;
String name = file.getFileName().toString();
String name = java.util.Objects.toString(file.getFileName(), "");
int dot = name.lastIndexOf('.');
if (dot > 0) {
String ext = name.substring(dot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ public Integer call() {

int outputPretty(Map<String, Object> stats) {
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
String projectName = path.toAbsolutePath().normalize().getFileName().toString();
String projectName = java.util.Objects.toString(
path.toAbsolutePath().normalize().getFileName(), "unknown");

out.println();
CliOutput.print(out, "@|bold [=] Code IQ Stats -- " + projectName + "|@");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public Integer call() {
// Check if service nodes exist; if not, run ServiceDetector
boolean hasServices = nodes.stream().anyMatch(n -> n.getKind() == NodeKind.SERVICE);
if (!hasServices) {
String projectName = root.getFileName().toString();
String projectName = java.util.Objects.toString(root.getFileName(), "unknown");
var detector = new ServiceDetector();
var result = detector.detect(nodes, edges, projectName);
nodes.addAll(result.serviceNodes());
Expand Down
Loading