diff --git a/integrationTests/src/test/java/org/sonarqube/gradle/GradleTest.java b/integrationTests/src/test/java/org/sonarqube/gradle/GradleTest.java index 364f478f..f6f68c54 100644 --- a/integrationTests/src/test/java/org/sonarqube/gradle/GradleTest.java +++ b/integrationTests/src/test/java/org/sonarqube/gradle/GradleTest.java @@ -354,11 +354,14 @@ public void testScanAllOnMultiModuleWithSubModulesProjectCollectsTheExpectedSour baseDir.resolve("module/submodule/submoduleScript.sh").toString(), baseDir.resolve("gradlew").toString(), baseDir.resolve("gradlew.bat").toString(), - baseDir.resolve("gradle/wrapper/gradle-wrapper.properties").toString() + baseDir.resolve("gradle/wrapper/gradle-wrapper.properties").toString(), + baseDir.resolve(".hiddenDir/file.txt").toString(), + baseDir.resolve(".hiddenConfig").toString() ).doesNotContain( baseDir.resolve("skippedModule/build.gradle.kts").toString(), baseDir.resolve("skippedModule/skippedSubmodule/skippedSubmoduleScript.sh").toString(), - baseDir.resolve("skippedModule/skippedSubmodule/build.gradle.kts").toString() + baseDir.resolve("skippedModule/skippedSubmodule/build.gradle.kts").toString(), + baseDir.resolve(".hiddenDir/script.py").toString() ); assertThat(props.getProperty(":module.sonar.sources")).isEqualTo(baseDir.resolve("module/src/main/java").toString()); diff --git a/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenConfig b/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenConfig new file mode 100644 index 00000000..e69de29b diff --git a/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenDir/file.txt b/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenDir/file.txt new file mode 100644 index 00000000..e69de29b diff --git a/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenDir/script.py b/integrationTests/src/test/resources/multi-module-with-submodules/.hiddenDir/script.py new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/org/sonarqube/gradle/SonarPropertyComputer.java b/src/main/java/org/sonarqube/gradle/SonarPropertyComputer.java index d3ff297b..53233d3e 100644 --- a/src/main/java/org/sonarqube/gradle/SonarPropertyComputer.java +++ b/src/main/java/org/sonarqube/gradle/SonarPropertyComputer.java @@ -220,7 +220,13 @@ private static void computeScanAllProperties(Project project, Map excludedFiles = computeReportPaths(properties); - SourceCollector visitor = new SourceCollector(allModulesExistingSources, skippedDirs, excludedFiles, false); + SourceCollector visitor = SourceCollector.builder() + .setRoot(project.getProjectDir().toPath()) + .setExistingSources(allModulesExistingSources) + .setExcludedFiles(excludedFiles) + .setDirectoriesToIgnore(skippedDirs) + .build(); + try { Files.walkFileTree(project.getProjectDir().toPath(), visitor); diff --git a/src/main/java/org/sonarqube/gradle/SourceCollector.java b/src/main/java/org/sonarqube/gradle/SourceCollector.java index a9ada973..2162715b 100644 --- a/src/main/java/org/sonarqube/gradle/SourceCollector.java +++ b/src/main/java/org/sonarqube/gradle/SourceCollector.java @@ -35,6 +35,17 @@ public class SourceCollector implements FileVisitor { private static final Set EXCLUDED_DIRECTORIES = new HashSet<>( Arrays.asList( + ".cache", + ".env", + ".git", + ".gradle", + ".jruby", + ".m2", + ".node_modules", + ".npm", + ".pycache", + ".pytest_cache", + ".venv", "bin", "build", "dist", @@ -81,6 +92,48 @@ public class SourceCollector implements FileVisitor { ".kt")).map(ext -> ext.toLowerCase(Locale.ROOT)) .collect(Collectors.toSet()); + private static final Set INCLUDE_EXTENSIONS_FOR_HIDDEN_FILES = Set.of( + ".bash", + ".bat", + ".cnf", + ".config", + ".db", + ".env", + ".htpasswd", + ".json", + ".ksh", + ".properties", + ".ps1", + ".settings", + ".sh", + ".txt", + ".xml", + ".yaml", + ".yml", + ".zsh" + ); + + private static final Set INCLUDE_HIDDEN_FILES_KEYWORDS = Set.of( + ".env.", + "access", + "cfg", + "config", + "credential", + "history", + "id_dsa", + "id_ecdsa", + "id_ed25519", + "id_rsa", + "key", + "password", + "private", + "pwd", + "secret", + "sessions", + "token" + ); + + private final Path root; private final Set existingSources; private final Set directoriesToIgnore; private final Set excludedFiles; @@ -92,7 +145,12 @@ public Set getCollectedSources() { private final Set collectedSources = new HashSet<>(); - public SourceCollector(Set existingSources, Set directoriesToIgnore, Set excludedFiles, boolean shouldCollectJavaAndKotlinSources) { + public static Builder builder() { + return new Builder(); + } + + private SourceCollector(Path root, Set existingSources, Set directoriesToIgnore, Set excludedFiles, boolean shouldCollectJavaAndKotlinSources) { + this.root = root; this.existingSources = existingSources; this.directoriesToIgnore = directoriesToIgnore; this.excludedFiles = excludedFiles; @@ -100,12 +158,10 @@ public SourceCollector(Set existingSources, Set directoriesToIgnore, } @Override - public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes) throws IOException { - if ( - isHidden(path) || - isExcludedDirectory(path) || - isCoveredByExistingSources(path) - ) { + public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes) { + boolean isHiddenAndTooFarDownTheTree = isHidden(path) && !isChildOrGrandChildOfRoot(path); + + if (isHiddenAndTooFarDownTheTree || isExcludedDirectory(path) || isCoveredByExistingSources(path)) { return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; @@ -116,6 +172,10 @@ private static boolean isHidden(Path path) { .anyMatch(token -> token.toString().startsWith(".")); } + private boolean isChildOrGrandChildOfRoot(Path path) { + return root.equals(path.getParent()) || root.equals(path.getParent().getParent()); + } + private boolean isExcludedDirectory(Path path) { String pathAsString = path.getFileName().toString().toLowerCase(Locale.ROOT); return EXCLUDED_DIRECTORIES.contains(pathAsString) || directoriesToIgnore.contains(path); @@ -129,10 +189,18 @@ private boolean isCoveredByExistingSources(Path path) { public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) { if (!basicFileAttributes.isSymbolicLink() && !excludedFiles.contains(path) && existingSources.stream().noneMatch(path::equals)) { String lowerCaseFileName = path.getFileName().toString().toLowerCase(Locale.ROOT); - if (excludedExtensions.stream().noneMatch(lowerCaseFileName::endsWith)) { + + if (isHidden(path)) { + boolean isHiddenFileToCollect = INCLUDE_HIDDEN_FILES_KEYWORDS.stream().anyMatch(lowerCaseFileName::contains) + || INCLUDE_EXTENSIONS_FOR_HIDDEN_FILES.stream().anyMatch(lowerCaseFileName::endsWith); + if (isHiddenFileToCollect) { + collectedSources.add(path); + } + } else if (excludedExtensions.stream().noneMatch(lowerCaseFileName::endsWith)) { collectedSources.add(path); } } + return FileVisitResult.CONTINUE; } @@ -145,4 +213,46 @@ public FileVisitResult visitFileFailed(Path path, IOException e) throws IOExcept public FileVisitResult postVisitDirectory(Path path, IOException e) throws IOException { return FileVisitResult.CONTINUE; } + + public static class Builder { + private Path root = null; + private Set existingSources = new HashSet<>(); + private Set directoriesToIgnore = new HashSet<>(); + private Set excludedFiles = new HashSet<>(); + private boolean shouldCollectJavaAndKotlinSources = false; + + private Builder() { } + + public Builder setRoot(Path root) { + this.root = root; + return this; + } + + public Builder setExistingSources(Set existingSources) { + this.existingSources = existingSources; + return this; + } + + public Builder setDirectoriesToIgnore(Set directoriesToIgnore) { + this.directoriesToIgnore = directoriesToIgnore; + return this; + } + + public Builder setExcludedFiles(Set excludedFiles) { + this.excludedFiles = excludedFiles; + return this; + } + + public Builder setShouldCollectJavaAndKotlinSources(boolean shouldCollectJavaAndKotlinSources) { + this.shouldCollectJavaAndKotlinSources = shouldCollectJavaAndKotlinSources; + return this; + } + + public SourceCollector build() { + if (root == null) { + throw new IllegalStateException("Root path must be set"); + } + return new SourceCollector(root, existingSources, directoriesToIgnore, excludedFiles, shouldCollectJavaAndKotlinSources); + } + } } diff --git a/src/test/groovy/org/sonarqube/gradle/FunctionalTests.groovy b/src/test/groovy/org/sonarqube/gradle/FunctionalTests.groovy index 4a8698f0..25fdc283 100644 --- a/src/test/groovy/org/sonarqube/gradle/FunctionalTests.groovy +++ b/src/test/groovy/org/sonarqube/gradle/FunctionalTests.groovy @@ -505,7 +505,7 @@ class FunctionalTests extends Specification { .withArguments('sonar', '--info', '-Dsonar.gradle.scanAll=true', '-Dsonar.coverageReportPaths=my-first-coverage-report.xml,my-second-coverage-report.xml', - '-Dsonar.coverage.jacoco.xmlReportPaths=' + thirdCoverageReport.toAbsolutePath().toString(), + '-Dsonar.coverage.jacoco.xmlReportPaths=' + thirdCoverageReport.toRealPath().toString(), '-Dsonar.scanner.dumpToFile=' + outFile.toAbsolutePath()) .withPluginClasspath() .withDebug(true) @@ -518,7 +518,7 @@ class FunctionalTests extends Specification { props.load(outFile.newDataInputStream()) props."sonar.gradle.scanAll" == "true" props."sonar.coverageReportPaths" == "my-first-coverage-report.xml,my-second-coverage-report.xml" - props."sonar.coverage.jacoco.xmlReportPaths" == thirdCoverageReport.toAbsolutePath().toString() + props."sonar.coverage.jacoco.xmlReportPaths" == thirdCoverageReport.toRealPath().toString() result.output.contains("Parameter sonar.gradle.scanAll is enabled. The scanner will attempt to collect additional sources.") // Assert that the extra files (empty script and reports) exist on disk diff --git a/src/test/groovy/org/sonarqube/gradle/SonarQubePluginTest.groovy b/src/test/groovy/org/sonarqube/gradle/SonarQubePluginTest.groovy index cdb5194b..69a72c83 100644 --- a/src/test/groovy/org/sonarqube/gradle/SonarQubePluginTest.groovy +++ b/src/test/groovy/org/sonarqube/gradle/SonarQubePluginTest.groovy @@ -1011,6 +1011,13 @@ class SonarQubePluginTest extends Specification { then: def expectedSources = """ + .hidden/.hidden-file.txt + .hidden/file.txt + .hidden/folder/.hidden-nested-file.txt + .hidden/folder/nested-file.txt + .hidden/folder/public-id_rsa-prod + .hidden/folder/test-config.config + .hidden/prod-token build.gradle.kts module1/build.gradle.kts module1/extras/pyScriptM1.py @@ -1024,7 +1031,7 @@ class SonarQubePluginTest extends Specification { assert properties[":module2.:module2:submodule.sonar.sources"] == null } - def "scan all detects scripts in all modules"() { + def "scan all detects files in all modules"() { def dir = new File("src/test/projects/java-multi-nested-modules") def parent = ProjectBuilder.builder() .withName("java-multi-nested-modules") @@ -1076,6 +1083,13 @@ class SonarQubePluginTest extends Specification { then: def expectedSources = """ + .hidden/.hidden-file.txt + .hidden/file.txt + .hidden/folder/.hidden-nested-file.txt + .hidden/folder/nested-file.txt + .hidden/folder/public-id_rsa-prod + .hidden/folder/test-config.config + .hidden/prod-token build.gradle.kts module1/build.gradle.kts module1/extras/pyScriptM1.py @@ -1085,7 +1099,8 @@ class SonarQubePluginTest extends Specification { module2/settings.gradle.kts module2/submodule/build.gradle.kts module2/submodule/scriptM2S.sh - settings.gradle.kts""".stripIndent().trim() + settings.gradle.kts + """.stripIndent().trim() assert sources == expectedSources assert normalizePathArray(module1Sources).contains(normalizePathString("src/test/projects/java-multi-nested-modules/module1/src/main/java")) diff --git a/src/test/java/org/sonarqube/gradle/SourceCollectorTest.java b/src/test/java/org/sonarqube/gradle/SourceCollectorTest.java index 20c00cdb..d6b653b9 100644 --- a/src/test/java/org/sonarqube/gradle/SourceCollectorTest.java +++ b/src/test/java/org/sonarqube/gradle/SourceCollectorTest.java @@ -1,4 +1,4 @@ -package org.sonarqube.gradle;/* +/* * SonarQube Scanner for Gradle * Copyright (C) 2015-2024 SonarSource * mailto:info AT sonarsource DOT com @@ -17,6 +17,7 @@ * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ +package org.sonarqube.gradle; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -28,6 +29,7 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class SourceCollectorTest { @TempDir @@ -45,78 +47,93 @@ static void setup() throws IOException { singlePomXml.toFile().createNewFile(); } + @Test + void testSourceCollectorBuilder() { + SourceCollector.Builder sourceCollectorBuilder = SourceCollector.builder(); + assertThatThrownBy(sourceCollectorBuilder::build) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Root path must be set"); + } + @Test void testPrevisitDirectories() throws IOException { - Path srcMainJava = Paths.get("src", "main", "java"); + Path src = createDirectory(simpleProjectBasedDir, "src"); + Path srcMain = createDirectory(src, "main"); + Path srcMainJava = createDirectory(srcMain, "java"); + Path srcMainJs = createDirectory(srcMain, "js"); + Set existingSources = Collections.singleton(srcMainJava); - FileVisitor visitor = new SourceCollector(existingSources, Collections.emptySet(), Collections.emptySet(), false); + FileVisitor visitor = SourceCollector.builder() + .setRoot(simpleProjectBasedDir) + .setExistingSources(existingSources) + .build(); - Path gitFolder = Paths.get(".git"); - Path gitHooksFolder = Paths.get(".git", "hooks"); - Path sources = Paths.get("scripts"); + Path gitFolder = createDirectory(simpleProjectBasedDir, ".git"); + Path gitHooksFolder = createDirectory(gitFolder, "hooks"); + Path sources = createDirectory(simpleProjectBasedDir, "scripts"); assertThat(visitor.preVisitDirectory(gitFolder, null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(gitHooksFolder, null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - - assertThat(visitor.preVisitDirectory(Paths.get("src", "main", "java"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("bin"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("build"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("target"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("out"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("tmp"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("dist"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("nbdist"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); - assertThat(visitor.preVisitDirectory(Paths.get("nbbuild"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(gitHooksFolder, null)).isEqualTo(FileVisitResult.CONTINUE); + + assertThat(visitor.preVisitDirectory(srcMainJava, null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "bin"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "build"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "target"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "out"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "tmp"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "dist"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "nbdist"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); + assertThat(visitor.preVisitDirectory(createDirectory(simpleProjectBasedDir, "nbbuild"), null)).isEqualTo(FileVisitResult.SKIP_SUBTREE); assertThat(visitor.preVisitDirectory(sources, null)).isEqualTo(FileVisitResult.CONTINUE); - assertThat(visitor.preVisitDirectory(Paths.get("src", "main", "js"), null)).isEqualTo(FileVisitResult.CONTINUE); + assertThat(visitor.preVisitDirectory(srcMainJs, null)).isEqualTo(FileVisitResult.CONTINUE); } @Test void visitorCollectsConsistently() throws IOException { // File in the existing source is not repeated in the collected files - SourceCollector visitor = new SourceCollector(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), false); + SourceCollector visitor = SourceCollector.builder().setRoot(emptyProjectBasedir).build(); Files.walkFileTree(emptyProjectBasedir, visitor); assertThat(visitor.getCollectedSources()).isEmpty(); - SourceCollector otherVisitor = new SourceCollector(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), false); + SourceCollector otherVisitor = SourceCollector.builder().setRoot(singleFileProjectBaseDir).build(); Files.walkFileTree(singleFileProjectBaseDir, otherVisitor); assertThat(otherVisitor.getCollectedSources()).containsOnly(singleFileProjectBaseDir.resolve("pom.xml")); - SourceCollector visitorAvoidingPomXml = new SourceCollector(Collections.singleton(singleFileProjectBaseDir.resolve("pom.xml")), Collections.emptySet(), Collections.emptySet(), - false); + SourceCollector visitorAvoidingPomXml = SourceCollector.builder() + .setRoot(singleFileProjectBaseDir) + .setExistingSources(Collections.singleton(singleFileProjectBaseDir.resolve("pom.xml"))) + .build(); Files.walkFileTree(singleFileProjectBaseDir, visitorAvoidingPomXml); assertThat(visitorAvoidingPomXml.getCollectedSources()).isEmpty(); } @Test void visitorIgnoresFilesInDirectoriesToIgnore() throws IOException { - Path simpleProjectPom = simpleProjectBasedDir.resolve("pom.xml"); - simpleProjectPom.toFile().createNewFile(); - Path rootJavaFile = simpleProjectBasedDir.resolve("ProjectRoot.java"); - rootJavaFile.toFile().createNewFile(); - Path subModule = simpleProjectBasedDir.resolve("submodule"); - subModule.toFile().mkdirs(); - Path fileInSubModule = subModule.resolve("ignore-me.php"); - fileInSubModule.toFile().createNewFile(); - - SourceCollector visitor = new SourceCollector(Collections.emptySet(), Collections.singleton(subModule), Collections.emptySet(), true); + Path simpleProjectPom = createFile(simpleProjectBasedDir, "pom.xml"); + Path rootJavaFile = createFile(simpleProjectBasedDir, "ProjectRoot.java"); + Path subModule = createDirectory(simpleProjectBasedDir, "submodule"); + Path fileInSubModule = createFile(subModule, "ignore-me.php"); + + SourceCollector visitor = SourceCollector.builder() + .setRoot(simpleProjectBasedDir) + .setDirectoriesToIgnore(Collections.singleton(subModule)) + .setShouldCollectJavaAndKotlinSources(true) + .build(); Files.walkFileTree(simpleProjectBasedDir, visitor); assertThat(visitor.getCollectedSources()) + .contains(simpleProjectPom) .contains(rootJavaFile) .doesNotContain(fileInSubModule); } @Test void visitorIgnoresJavaAndKotlinFiles() throws IOException { - Path simpleProjectPom = simpleProjectBasedDir.resolve("pom.xml"); - simpleProjectPom.toFile().createNewFile(); - Path rootJavaFile = simpleProjectBasedDir.resolve("ProjectRoot.java"); - rootJavaFile.toFile().createNewFile(); - Path rootKotlinFile = simpleProjectBasedDir.resolve("ProjectRoot.kt"); - rootKotlinFile.toFile().createNewFile(); + Path simpleProjectPom = createFile(simpleProjectBasedDir, "pom.xml"); + Path rootJavaFile = createFile(simpleProjectBasedDir, "ProjectRoot.java"); + Path rootKotlinFile = createFile(simpleProjectBasedDir, "ProjectRoot.kt"); - SourceCollector visitor = new SourceCollector(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), false); + SourceCollector visitor = SourceCollector.builder().setRoot(simpleProjectBasedDir).build(); Files.walkFileTree(simpleProjectBasedDir, visitor); assertThat(visitor.getCollectedSources()) .contains(simpleProjectPom) @@ -131,7 +148,7 @@ void visitorIgnoresExcludedFiles() throws IOException { Path cppFile = simpleProjectBasedDir.resolve("hello.cpp"); cppFile.toFile().createNewFile(); - SourceCollector visitor = new SourceCollector(Collections.emptySet(), Collections.emptySet(), Set.of(cppFile), false); + SourceCollector visitor = SourceCollector.builder().setRoot(simpleProjectBasedDir).setExcludedFiles(Set.of(cppFile)).setShouldCollectJavaAndKotlinSources(false).build(); Files.walkFileTree(simpleProjectBasedDir, visitor); assertThat(visitor.getCollectedSources()) .contains(pythonScript) @@ -145,10 +162,37 @@ void visitorIgnoresSymbolicLinks() throws IOException { Path link = simpleProjectBasedDir.resolve("pom.xml.symbolic.link"); Files.createSymbolicLink(link, simpleProjectPom); - SourceCollector visitor = new SourceCollector(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), false); + SourceCollector visitor = SourceCollector.builder().setRoot(simpleProjectBasedDir).build(); Files.walkFileTree(simpleProjectBasedDir, visitor); assertThat(visitor.getCollectedSources()) .contains(simpleProjectPom) .doesNotContain(link); } + + @Test + void visitorCollectsExpectedHiddenFiles() throws IOException { + Path hiddenDirectory = createDirectory(simpleProjectBasedDir, ".hidden"); + Path hiddenFile = createFile(hiddenDirectory, "file.txt"); + Path hiddenFile2 = createFile(hiddenDirectory, "configuration"); + Path hiddenFile3 = createFile(hiddenDirectory, "non-relevant-file"); + + SourceCollector visitor = SourceCollector.builder().setRoot(simpleProjectBasedDir).build(); + Files.walkFileTree(simpleProjectBasedDir, visitor); + assertThat(visitor.getCollectedSources()) + .contains(hiddenFile) + .contains(hiddenFile2) + .doesNotContain(hiddenFile3); + } + + private Path createFile(Path parent, String name) throws IOException { + Path file = parent.resolve(name); + file.toFile().createNewFile(); + return file; + } + + private Path createDirectory(Path parent, String name) { + Path directory = parent.resolve(name); + directory.toFile().mkdirs(); + return directory; + } } diff --git a/src/test/projects/java-multi-nested-modules/.hidden/.hidden-file.txt b/src/test/projects/java-multi-nested-modules/.hidden/.hidden-file.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/file.txt b/src/test/projects/java-multi-nested-modules/.hidden/file.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/.hidden-nested-file.txt b/src/test/projects/java-multi-nested-modules/.hidden/folder/.hidden-nested-file.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/nested-file.txt b/src/test/projects/java-multi-nested-modules/.hidden/folder/nested-file.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/public-id_rsa-prod b/src/test/projects/java-multi-nested-modules/.hidden/folder/public-id_rsa-prod new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/subfolder/nested-nested-file b/src/test/projects/java-multi-nested-modules/.hidden/folder/subfolder/nested-nested-file new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/subfolder/test-config.config b/src/test/projects/java-multi-nested-modules/.hidden/folder/subfolder/test-config.config new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/folder/test-config.config b/src/test/projects/java-multi-nested-modules/.hidden/folder/test-config.config new file mode 100644 index 00000000..e69de29b diff --git a/src/test/projects/java-multi-nested-modules/.hidden/prod-token b/src/test/projects/java-multi-nested-modules/.hidden/prod-token new file mode 100644 index 00000000..e69de29b