Skip to content

Commit f6f8065

Browse files
authored
Improved tech detection when tech is provided by user (#175)
1 parent 1bcec51 commit f6f8065

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

utils/techutils/techutils.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const (
4141
)
4242
const Pypi = "pypi"
4343

44+
var AllTechnologiesStrings = []string{Maven.String(), Gradle.String(), Npm.String(), Pnpm.String(), Yarn.String(), Go.String(), Pip.String(), Pipenv.String(), Poetry.String(), Nuget.String(), Dotnet.String(), Docker.String(), Oci.String(), Conan.String()}
45+
4446
type CodeLanguage string
4547

4648
const (
@@ -400,13 +402,19 @@ func isExclude(path string, techData TechData) bool {
400402
func mapWorkingDirectoriesToTechnologies(workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedTechs []Technology, requestedDescriptors map[Technology][]string) (technologiesDetected map[Technology]map[string][]string, err error) {
401403
// Get the relevant technologies to check
402404
technologies := requestedTechs
405+
var techProvidedByUser bool
403406
if len(technologies) == 0 {
404407
technologies = GetAllTechnologiesList()
408+
} else {
409+
// If the project's technology was provided by the user, and isn't detected by us, we want to enable capturing the technology by its descriptor as well as by its indicators.
410+
// In case we execute our auto-detection we want to avoid that since it may lead to collisions between package managers with the same descriptor (like Npm and Yarn)
411+
techProvidedByUser = true
412+
log.Debug(fmt.Sprintf("Technologies were identified either from the command flags supplied by the user or inferred from the provided installation command. Detected technologies: %s.", technologies))
405413
}
406414
technologiesDetected = make(map[Technology]map[string][]string)
407415
// Map working directories to technologies
408416
for _, tech := range technologies {
409-
if techWorkingDirs, e := getTechInformationFromWorkingDir(tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, requestedDescriptors); e != nil {
417+
if techWorkingDirs, e := getTechInformationFromWorkingDir(tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, requestedDescriptors, techProvidedByUser); e != nil {
410418
err = errors.Join(err, fmt.Errorf("failed to get information from working directory for %s", tech))
411419
} else if len(techWorkingDirs) > 0 {
412420
// Found indicators of the technology, add to detected.
@@ -423,11 +431,12 @@ func mapWorkingDirectoriesToTechnologies(workingDirectoryToIndicators map[string
423431
return
424432
}
425433

426-
func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedDescriptors map[Technology][]string) (techWorkingDirs map[string][]string, err error) {
434+
func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicators map[string][]string, excludedTechAtWorkingDir map[string][]Technology, requestedDescriptors map[Technology][]string, techProvidedByUser bool) (techWorkingDirs map[string][]string, err error) {
427435
techWorkingDirs = make(map[string][]string)
428436
for wd, indicators := range workingDirectoryToIndicators {
429437
descriptorsAtWd := []string{}
430438
foundIndicator := false
439+
foundDescriptor := false
431440
if isTechExcludedInWorkingDir(tech, wd, excludedTechAtWorkingDir) {
432441
// Exclude this technology from this working directory
433442
continue
@@ -436,6 +445,7 @@ func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicat
436445
for _, path := range indicators {
437446
if tech.isDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) {
438447
descriptorsAtWd = append(descriptorsAtWd, path)
448+
foundDescriptor = true
439449
}
440450
if indicator, e := tech.isIndicator(path); e != nil {
441451
err = errors.Join(err, fmt.Errorf("failed to check if %s is an indicator of %s: %w", path, tech, e))
@@ -444,8 +454,9 @@ func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicat
444454
foundIndicator = true
445455
}
446456
}
447-
if foundIndicator {
448-
// Found indicators of the technology in the current working directory, add to detected.
457+
if foundIndicator || (foundDescriptor && techProvidedByUser) {
458+
// If indicators of the technology were found in the current working directory, add to detected.
459+
// If descriptors were found for a specific tech that was provided by the user, we add the descriptor to detected.
449460
techWorkingDirs[wd] = descriptorsAtWd
450461
}
451462
}

utils/techutils/techutils_test.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,7 @@ func TestMapWorkingDirectoriesToTechnologies(t *testing.T) {
152152
excludedTechAtWorkingDir map[string][]Technology
153153
requestedTechs []Technology
154154
requestedDescriptors map[Technology][]string
155-
156-
expected map[Technology]map[string][]string
155+
expected map[Technology]map[string][]string
157156
}{
158157
{
159158
name: "noTechTest",
@@ -210,6 +209,20 @@ func TestMapWorkingDirectoriesToTechnologies(t *testing.T) {
210209
Dotnet: {"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
211210
},
212211
},
212+
{
213+
// When tech is requested by user we detect technology by indicator as well as by descriptors, therefore we can relate descriptor files to tech even when indicator doesn't exist
214+
name: "tech requested by user test",
215+
workingDirectoryToIndicators: map[string][]string{
216+
"dir3": {filepath.Join("dir3", "package.json")},
217+
projectDir: {filepath.Join(projectDir, "pyproject.toml")},
218+
},
219+
requestedTechs: []Technology{Yarn, Poetry},
220+
requestedDescriptors: noRequestSpecialDescriptors,
221+
expected: map[Technology]map[string][]string{
222+
Yarn: {"dir3": {filepath.Join("dir3", "package.json")}},
223+
Poetry: {projectDir: {filepath.Join(projectDir, "pyproject.toml")}},
224+
},
225+
},
213226
}
214227

215228
for _, test := range tests {
@@ -385,6 +398,7 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
385398
"dir": {filepath.Join("dir", "package.json"), filepath.Join("dir", "package-lock.json"), filepath.Join("dir", "build.gradle.kts"), filepath.Join("dir", "project.sln"), filepath.Join("dir", "blabla.txt")},
386399
"directory": {filepath.Join("directory", "npm-shrinkwrap.json")},
387400
"dir3": {filepath.Join("dir3", "package.json"), filepath.Join("dir3", ".yarn")},
401+
"dir4": {filepath.Join("dir4", "package.json")},
388402
projectDir: {filepath.Join(projectDir, "pyproject.toml")},
389403
filepath.Join("dir3", "dir"): {filepath.Join("dir3", "dir", "package.json"), filepath.Join("dir3", "dir", "pnpm-lock.yaml")},
390404
filepath.Join("dir", "dir2"): {filepath.Join("dir", "dir2", "go.mod")},
@@ -403,12 +417,14 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
403417
name string
404418
tech Technology
405419
requestedDescriptors map[Technology][]string
420+
techProvidedByUser bool
406421
expected map[string][]string
407422
}{
408423
{
409424
name: "mavenTest",
410425
tech: Maven,
411426
requestedDescriptors: map[Technology][]string{},
427+
techProvidedByUser: false,
412428
expected: map[string][]string{
413429
"folder": {
414430
filepath.Join("folder", "pom.xml"),
@@ -421,33 +437,39 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
421437
name: "npmTest",
422438
tech: Npm,
423439
requestedDescriptors: map[Technology][]string{},
440+
techProvidedByUser: false,
424441
expected: map[string][]string{
425442
"dir": {filepath.Join("dir", "package.json")},
443+
"dir4": {filepath.Join("dir4", "package.json")},
426444
"directory": {},
427445
},
428446
},
429447
{
430448
name: "pnpmTest",
431449
tech: Pnpm,
432450
requestedDescriptors: map[Technology][]string{},
451+
techProvidedByUser: false,
433452
expected: map[string][]string{filepath.Join("dir3", "dir"): {filepath.Join("dir3", "dir", "package.json")}},
434453
},
435454
{
436455
name: "yarnTest",
437456
tech: Yarn,
438457
requestedDescriptors: map[Technology][]string{},
458+
techProvidedByUser: false,
439459
expected: map[string][]string{"dir3": {filepath.Join("dir3", "package.json")}},
440460
},
441461
{
442462
name: "golangTest",
443463
tech: Go,
444464
requestedDescriptors: map[Technology][]string{},
465+
techProvidedByUser: false,
445466
expected: map[string][]string{filepath.Join("dir", "dir2"): {filepath.Join("dir", "dir2", "go.mod")}},
446467
},
447468
{
448469
name: "pipTest",
449470
tech: Pip,
450471
requestedDescriptors: map[Technology][]string{},
472+
techProvidedByUser: false,
451473
expected: map[string][]string{
452474
filepath.Join("users_dir", "test", "package"): {filepath.Join("users_dir", "test", "package", "setup.py")},
453475
filepath.Join("users_dir", "test", "package2"): {filepath.Join("users_dir", "test", "package2", "requirements.txt")},
@@ -458,6 +480,7 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
458480
name: "pipRequestedDescriptorTest",
459481
tech: Pip,
460482
requestedDescriptors: map[Technology][]string{Pip: {"blabla.txt"}},
483+
techProvidedByUser: false,
461484
expected: map[string][]string{
462485
"dir": {filepath.Join("dir", "blabla.txt")},
463486
filepath.Join("users_dir", "test", "package"): {filepath.Join("users_dir", "test", "package", "setup.py")},
@@ -469,12 +492,14 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
469492
name: "pipenvTest",
470493
tech: Pipenv,
471494
requestedDescriptors: map[Technology][]string{},
495+
techProvidedByUser: false,
472496
expected: map[string][]string{filepath.Join("users", "test", "package"): {filepath.Join("users", "test", "package", "Pipfile")}},
473497
},
474498
{
475499
name: "gradleTest",
476500
tech: Gradle,
477501
requestedDescriptors: map[Technology][]string{},
502+
techProvidedByUser: false,
478503
expected: map[string][]string{
479504
filepath.Join("users", "test", "package"): {filepath.Join("users", "test", "package", "build.gradle")},
480505
"dir": {filepath.Join("dir", "build.gradle.kts")},
@@ -484,19 +509,33 @@ func TestGetTechInformationFromWorkingDir(t *testing.T) {
484509
name: "nugetTest",
485510
tech: Nuget,
486511
requestedDescriptors: map[Technology][]string{},
512+
techProvidedByUser: false,
487513
expected: map[string][]string{"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
488514
},
489515
{
490516
name: "dotnetTest",
491517
tech: Dotnet,
492518
requestedDescriptors: map[Technology][]string{},
519+
techProvidedByUser: false,
493520
expected: map[string][]string{"dir": {filepath.Join("dir", "project.sln"), filepath.Join("dir", "sub1", "project.csproj")}},
494521
},
522+
// When tech is provided by the user we detect technology by indicator and descriptors and not just by indicator. Test cases are provided only for technologies that might experience conflicts.
523+
{
524+
name: "yarnTestWithProvidedTechFromUser",
525+
tech: Yarn,
526+
requestedDescriptors: make(map[Technology][]string),
527+
techProvidedByUser: true,
528+
expected: map[string][]string{
529+
"dir": {filepath.Join("dir", "package.json")},
530+
"dir3": {filepath.Join("dir3", "package.json")},
531+
"dir4": {filepath.Join("dir4", "package.json")},
532+
},
533+
},
495534
}
496535

497536
for _, test := range tests {
498537
t.Run(test.name, func(t *testing.T) {
499-
techInformation, err := getTechInformationFromWorkingDir(test.tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, test.requestedDescriptors)
538+
techInformation, err := getTechInformationFromWorkingDir(test.tech, workingDirectoryToIndicators, excludedTechAtWorkingDir, test.requestedDescriptors, test.techProvidedByUser)
500539
assert.NoError(t, err)
501540
expectedKeys := maps.Keys(test.expected)
502541
actualKeys := maps.Keys(techInformation)

0 commit comments

Comments
 (0)