< Summary - Repometa Coverage

Information
Class: javaDetector
Assembly: repometa
File(s): /home/runner/work/repometa/repometa/detect_java.go
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 219
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
detect0%00100%

File(s)

/home/runner/work/repometa/repometa/detect_java.go

#LineLine coverage
 1package repometa
 2
 3import (
 4  "encoding/xml"
 5  "path/filepath"
 6  "regexp"
 7  "strings"
 8)
 9
 10type javaDetector struct{}
 11
 12// mavenPom captures the two shapes we care about from a pom.xml: the
 13// packaging discriminator (unused for now, kept to record its position)
 14// and the <modules> block that turns a POM into a multi-module root.
 15type mavenPom struct {
 16  XMLName xml.Name `xml:"project"`
 17  Modules struct {
 18    Module []string `xml:"module"`
 19  } `xml:"modules"`
 20}
 21
 22// gradleIncludeRE matches Gradle settings-file `include` statements in
 23// both Groovy and Kotlin DSLs — parentheses are optional in Groovy,
 24// mandatory in Kotlin. Only the first argument is captured; multi-arg
 25// `include('a', 'b')` calls are handled by matching each quoted literal
 26// independently via gradleQuotedLiteralRE.
 27var gradleIncludeRE = regexp.MustCompile(`(?m)^\s*include\b`)
 28
 29// gradleQuotedLiteralRE extracts every single- or double-quoted string
 30// from a Gradle settings-file line. Combined with gradleIncludeRE we
 31// capture every module argument regardless of arity.
 32var gradleQuotedLiteralRE = regexp.MustCompile(`['"]([^'"]+)['"]`)
 33
 8434func (javaDetector) detect(dv dirVisit, cfg options) []finding {
 8435  var out []finding
 8436
 537  if hasFile(dv.files, "pom.xml") {
 538    out = append(out, detectMaven(dv, cfg))
 539  }
 40
 941  if hasGradleMarker(dv) {
 942    out = append(out, detectGradle(dv, cfg))
 943  }
 44
 45  // Ant. build.xml with a <project> root is the canonical Ant marker;
 46  // Ivy adjuncts (ivy.xml) count only as supporting evidence.
 247  if hasFile(dv.files, "build.xml") {
 248    if f := detectAnt(dv); f != nil {
 249      out = append(out, *f)
 250    }
 51  }
 52
 8453  return out
 54}
 55
 56// detectMaven emits a java-project finding for a directory containing
 57// pom.xml. When the POM declares <modules>, a maven-multi-module
 58// workspace is attached with the listed members.
 59func detectMaven(dv dirVisit, cfg options) finding {
 60  attrs := map[string]string{"java.build": "maven"}
 61  evidence := []Evidence{{
 62    Path:   relJoin(dv.rel, "pom.xml"),
 63    Reason: "pom.xml at directory root",
 64  }}
 65  var workspaces []Workspace
 66  confidence := 1.0
 67
 68  data, err := readManifest(filepath.Join(dv.abs, "pom.xml"), cfg)
 69  switch {
 70  case err != nil:
 71    confidence = confidenceUnreadable
 72    evidence = append(evidence, evidenceUnreadable(dv.rel, "pom.xml", err))
 73  default:
 74    var pom mavenPom
 75    if uerr := xml.Unmarshal(data, &pom); uerr != nil {
 76      confidence = confidenceUnparsable
 77      evidence = append(evidence, evidenceUnparsable(dv.rel, "pom.xml", uerr))
 78    } else if len(pom.Modules.Module) > 0 {
 79      workspaces = append(workspaces, Workspace{
 80        Kind:    WorkspaceMavenMultiModule,
 81        Members: expandMembers(dv.abs, pom.Modules.Module, dv.rel),
 82      })
 83      evidence = append(evidence, Evidence{
 84        Path:   relJoin(dv.rel, "pom.xml"),
 85        Reason: "pom.xml declares <modules>",
 86      })
 87    }
 88  }
 89  return finding{
 90    Kind:       KindJavaProject,
 91    Confidence: confidence,
 92    Evidence:   evidence,
 93    Attributes: attrs,
 94    Workspaces: workspaces,
 95  }
 96}
 97
 98// hasGradleMarker reports whether the directory has any Gradle build or
 99// settings file — either DSL.
 100func hasGradleMarker(dv dirVisit) bool {
 101  return hasFile(dv.files, "build.gradle") ||
 102    hasFile(dv.files, "build.gradle.kts") ||
 103    hasFile(dv.files, "settings.gradle") ||
 104    hasFile(dv.files, "settings.gradle.kts")
 105}
 106
 107// detectGradle emits a java-project finding for a directory containing
 108// any Gradle marker. The Kotlin DSL takes precedence over Groovy when
 109// both are present in a directory (rare but legal during a migration).
 110// A settings file attaches a gradle-multi-project workspace with members
 111// parsed from `include` statements.
 112func detectGradle(dv dirVisit, cfg options) finding {
 113  attrs := map[string]string{"java.build": "gradle"}
 114  var evidence []Evidence
 115  var workspaces []Workspace
 116
 117  buildFile := ""
 118  switch {
 119  case hasFile(dv.files, "build.gradle.kts"):
 120    buildFile = "build.gradle.kts"
 121  case hasFile(dv.files, "build.gradle"):
 122    buildFile = "build.gradle"
 123  }
 124  if buildFile != "" {
 125    evidence = append(evidence, Evidence{
 126      Path:   relJoin(dv.rel, buildFile),
 127      Reason: buildFile + " at directory root",
 128    })
 129    if strings.HasSuffix(buildFile, ".kts") {
 130      attrs["java.gradle.dsl"] = "kotlin"
 131    } else {
 132      attrs["java.gradle.dsl"] = "groovy"
 133    }
 134  }
 135
 136  settingsFile := ""
 137  switch {
 138  case hasFile(dv.files, "settings.gradle.kts"):
 139    settingsFile = "settings.gradle.kts"
 140  case hasFile(dv.files, "settings.gradle"):
 141    settingsFile = "settings.gradle"
 142  }
 143  if settingsFile != "" {
 144    evidence = append(evidence, Evidence{
 145      Path:   relJoin(dv.rel, settingsFile),
 146      Reason: settingsFile + " at directory root",
 147    })
 148    members := parseGradleIncludes(filepath.Join(dv.abs, settingsFile), cfg)
 149    if len(members) > 0 {
 150      workspaces = append(workspaces, Workspace{
 151        Kind:    WorkspaceGradleMultiProject,
 152        Members: expandMembers(dv.abs, members, dv.rel),
 153      })
 154    } else {
 155      workspaces = append(workspaces, Workspace{Kind: WorkspaceGradleMultiProject})
 156    }
 157  }
 158
 159  return finding{
 160    Kind:       KindJavaProject,
 161    Confidence: 1.0,
 162    Evidence:   evidence,
 163    Attributes: attrs,
 164    Workspaces: workspaces,
 165  }
 166}
 167
 168// detectAnt emits a java-project finding for a directory whose build.xml
 169// looks like an Ant project (root element is <project>). Returns nil if
 170// the file is unreadable or doesn't have the Ant shape — build.xml is
 171// used by other tools too and a bare presence check would over-detect.
 172func detectAnt(dv dirVisit) *finding {
 173  attrs := map[string]string{"java.build": "ant"}
 174  evidence := []Evidence{{
 175    Path:   relJoin(dv.rel, "build.xml"),
 176    Reason: "build.xml at directory root",
 177  }}
 178
 179  if hasFile(dv.files, "ivy.xml") {
 180    evidence = append(evidence, Evidence{
 181      Path:   relJoin(dv.rel, "ivy.xml"),
 182      Reason: "ivy.xml present (Ivy dependency descriptor)",
 183    })
 184  }
 185
 186  return &finding{
 187    Kind:       KindJavaProject,
 188    Confidence: 1.0,
 189    Evidence:   evidence,
 190    Attributes: attrs,
 191  }
 192}
 193
 194// parseGradleIncludes returns the module paths declared by `include`
 195// statements in a Gradle settings file. Gradle uses ":a:b" to denote
 196// nested paths in the project tree; these are converted to "a/b" so the
 197// result is directly usable as a filesystem-relative member path.
 198func parseGradleIncludes(path string, cfg options) []string {
 199  data := readManifestOrNil(path, cfg)
 200  if data == nil {
 201    return nil
 202  }
 203  var members []string
 204  seen := make(map[string]bool)
 205  for line := range strings.SplitSeq(string(data), "\n") {
 206    if !gradleIncludeRE.MatchString(line) {
 207      continue
 208    }
 209    for _, m := range gradleQuotedLiteralRE.FindAllStringSubmatch(line, -1) {
 210      p := strings.ReplaceAll(strings.TrimPrefix(m[1], ":"), ":", "/")
 211      if p == "" || seen[p] {
 212        continue
 213      }
 214      seen[p] = true
 215      members = append(members, p)
 216    }
 217  }
 218  return members
 219}

Methods/Properties

detect