import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.commons.lang.StringUtils;
if (pathPatterns.length > 0) {
patternsByLanguageBuilder.put(language, pathPatterns);
} else {
- // If no custom language pattern is defined then fallback to suffixes declared by language
- String[] patterns = language.fileSuffixes().toArray(new String[0]);
- for (int i = 0; i < patterns.length; i++) {
- String suffix = patterns[i];
- String extension = sanitizeExtension(suffix);
- patterns[i] = "**/*." + extension;
- }
- PathPattern[] defaultLanguagePatterns = PathPattern.create(patterns);
- patternsByLanguageBuilder.put(language, defaultLanguagePatterns);
- LOG.debug("Declared extensions of language {} were converted to {}", language, getDetails(language, defaultLanguagePatterns));
+ PathPattern[] languagePatterns = getLanguagePatterns(language);
+ patternsByLanguageBuilder.put(language, languagePatterns);
}
}
patternsByLanguage = unmodifiableMap(patternsByLanguageBuilder);
}
+ private static PathPattern[] getLanguagePatterns(Language language) {
+ Stream<PathPattern> fileSuffixes = language.fileSuffixes().stream()
+ .map(suffix -> "**/*." + sanitizeExtension(suffix))
+ .map(PathPattern::create);
+ Stream<PathPattern> filenamePatterns = language.filenamePatterns()
+ .stream()
+ .map(filenamePattern -> "**/" + filenamePattern)
+ .map(PathPattern::create);
+
+ PathPattern[] defaultLanguagePatterns = Stream.concat(fileSuffixes, filenamePatterns)
+ .distinct()
+ .toArray(PathPattern[]::new);
+ LOG.debug("Declared patterns of language {} were converted to {}", language, getDetails(language, defaultLanguagePatterns));
+ return defaultLanguagePatterns;
+ }
+
@CheckForNull
Language language(Path absolutePath, Path relativePath) {
Language detectedLanguage = null;
private boolean isCandidateForLanguage(Path absolutePath, Path relativePath, Language language) {
PathPattern[] patterns = patternsByLanguage.get(language);
- if (patterns != null) {
- for (PathPattern pathPattern : patterns) {
- if (pathPattern.match(absolutePath, relativePath, false)) {
- return true;
- }
- }
- }
- return false;
+ return patterns != null && Arrays.stream(patterns).anyMatch(pattern -> pattern.match(absolutePath, relativePath, false));
}
private static String getFileLangPatternPropKey(String languageKey) {
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class LanguageTest {
@Test
public void hashCode_and_equals_depends_on_key() {
- Language lang1 = new Language("key1", "name1", true, "f1");
- Language lang2 = new Language("key1", "name2", false, "f2");
- Language lang3 = new Language("key2", "name1", true, "f1");
+ Language lang1 = new Language(mockApiLanguage("key1", "name1", true, new String[] {"f1"}, new String[0]));
+ Language lang2 = new Language(mockApiLanguage("key1", "name2", false, new String[] {"f2"}, new String[0]));
+ Language lang3 = new Language(mockApiLanguage("key2", "name1", true, new String[] {"f1"}, new String[0]));
assertThat(lang1)
.hasSameHashCodeAs(lang2)
.doesNotHaveSameHashCodeAs(lang3);
assertThat(lang2).doesNotHaveSameHashCodeAs(lang3);
-
+
assertThat(lang1)
.isEqualTo(lang2)
.isNotEqualTo(lang3);
@Test
public void getters_match_constructor() {
- Language lang1 = new Language("key1", "name1", true, "f1");
+ Language lang1 = new Language(mockApiLanguage("key1", "name1", true, new String[] {"f1"}, new String[] {"p1"}));
assertThat(lang1.key()).isEqualTo("key1");
assertThat(lang1.name()).isEqualTo("name1");
assertThat(lang1.isPublishAllFiles()).isTrue();
assertThat(lang1.fileSuffixes()).containsOnly("f1");
+ assertThat(lang1.filenamePatterns()).containsOnly("p1");
+ }
+
+ private org.sonar.api.resources.Language mockApiLanguage(String key, String name, boolean publishAllFiles, String[] fileSuffixes, String[] filenamePatterns) {
+ org.sonar.api.resources.Language mock = mock(org.sonar.api.resources.Language.class);
+ when(mock.getKey()).thenReturn(key);
+ when(mock.getName()).thenReturn(name);
+ when(mock.publishAllFiles()).thenReturn(publishAllFiles);
+ when(mock.getFileSuffixes()).thenReturn(fileSuffixes);
+ when(mock.filenamePatterns()).thenReturn(filenamePatterns);
+ return mock;
}
}
*/
package org.sonar.scanner.scan.filesystem;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.nio.file.Paths;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.scanner.repository.language.DefaultLanguagesRepository;
import org.sonar.scanner.repository.language.LanguagesRepository;
-import static junit.framework.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.spy;
+@RunWith(DataProviderRunner.class)
public class LanguageDetectionTest {
@Rule
}
@Test
- public void test_sanitizeExtension() {
- assertThat(LanguageDetection.sanitizeExtension(".cbl")).isEqualTo("cbl");
- assertThat(LanguageDetection.sanitizeExtension(".CBL")).isEqualTo("cbl");
- assertThat(LanguageDetection.sanitizeExtension("CBL")).isEqualTo("cbl");
- assertThat(LanguageDetection.sanitizeExtension("cbl")).isEqualTo("cbl");
+ @UseDataProvider("extensionsForSanitization")
+ public void sanitizeExtension_shouldRemoveObsoleteCharacters(String extension) {
+ assertThat(LanguageDetection.sanitizeExtension(extension)).isEqualTo("cbl");
+ }
+
+ @DataProvider
+ public static Object[][] extensionsForSanitization() {
+ return new Object[][] {
+ {".cbl"},
+ {".CBL"},
+ {"CBL"},
+ {"cbl"},
+ };
}
@Test
- public void search_by_file_extension() {
+ public void detectLanguageKey_shouldDetectByFileExtension() {
LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob")));
LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
}
@Test
- public void should_not_fail_if_no_language() {
+ @UseDataProvider("filenamePatterns")
+ public void detectLanguageKey_shouldDetectByFileNamePattern(String fileName, String expectedLanguageKey) {
+ LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(
+ new MockLanguage("docker", new String[0], new String[] {"*.dockerfile", "*.Dockerfile", "Dockerfile", "Dockerfile.*"}),
+ new MockLanguage("terraform", new String[] {"tf"}, new String[] {".tf"}),
+ new MockLanguage("java", new String[0], new String[] {"**/*Test.java"})));
+ LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
+ assertThat(detectLanguageKey(detection, fileName)).isEqualTo(expectedLanguageKey);
+ }
+
+ @DataProvider
+ public static Object[][] filenamePatterns() {
+ return new Object[][] {
+ {"Dockerfile", "docker"},
+ {"src/Dockerfile", "docker"},
+ {"my.Dockerfile", "docker"},
+ {"my.dockerfile", "docker"},
+ {"Dockerfile.old", "docker"},
+ {"Dockerfile.OLD", "docker"},
+ {"DOCKERFILE", null},
+ {"infra.tf", "terraform"},
+ {"FooTest.java", "java"},
+ {"FooTest.JAVA", "java"},
+ {"FooTEST.java", null}
+ };
+ }
+
+ @Test
+ public void detectLanguageKey_shouldNotFailIfNoLanguage() {
LanguageDetection detection = spy(new LanguageDetection(settings.asConfig(), new DefaultLanguagesRepository(new Languages())));
assertThat(detectLanguageKey(detection, "Foo.java")).isNull();
}
@Test
- public void plugin_can_declare_a_file_extension_twice_for_case_sensitivity() {
+ public void detectLanguageKey_shouldAllowPluginsToDeclareFileExtensionTwiceForCaseSensitivity() {
LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("abap", "abap", "ABAP")));
LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
}
@Test
- public void fail_if_conflicting_language_suffix() {
+ public void detectLanguageKey_shouldFailIfConflictingLanguageSuffix() {
LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml")));
LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
- try {
- detectLanguageKey(detection, "abc.xhtml");
- fail();
- } catch (MessageException e) {
- assertThat(e.getMessage())
- .contains("Language of file 'abc.xhtml' can not be decided as the file matches patterns of both ")
- .contains("sonar.lang.patterns.web : **/*.xhtml")
- .contains("sonar.lang.patterns.xml : **/*.xhtml");
- }
+ assertThatThrownBy(() -> detectLanguageKey(detection, "abc.xhtml"))
+ .isInstanceOf(MessageException.class)
+ .hasMessageContaining("Language of file 'abc.xhtml' can not be decided as the file matches patterns of both ")
+ .hasMessageContaining("sonar.lang.patterns.web : **/*.xhtml")
+ .hasMessageContaining("sonar.lang.patterns.xml : **/*.xhtml");
}
@Test
- public void solve_conflict_using_filepattern() {
+ public void detectLanguageKey_shouldSolveConflictUsingFilePattern() {
LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml")));
settings.setProperty("sonar.lang.patterns.xml", "xml/**");
}
@Test
- public void fail_if_conflicting_filepattern() {
+ public void detectLanguageKey_shouldFailIfConflictingFilePattern() {
LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("abap", "abap"), new MockLanguage("cobol", "cobol")));
settings.setProperty("sonar.lang.patterns.abap", "*.abap,*.txt");
settings.setProperty("sonar.lang.patterns.cobol", "*.cobol,*.txt");
assertThat(detectLanguageKey(detection, "abc.abap")).isEqualTo("abap");
assertThat(detectLanguageKey(detection, "abc.cobol")).isEqualTo("cobol");
- try {
- detectLanguageKey(detection, "abc.txt");
- fail();
- } catch (MessageException e) {
- assertThat(e.getMessage())
- .contains("Language of file 'abc.txt' can not be decided as the file matches patterns of both ")
- .contains("sonar.lang.patterns.abap : *.abap,*.txt")
- .contains("sonar.lang.patterns.cobol : *.cobol,*.txt");
- }
+
+ assertThatThrownBy(() -> detectLanguageKey(detection, "abc.txt"))
+ .hasMessageContaining("Language of file 'abc.txt' can not be decided as the file matches patterns of both ")
+ .hasMessageContaining("sonar.lang.patterns.abap : *.abap,*.txt")
+ .hasMessageContaining("sonar.lang.patterns.cobol : *.cobol,*.txt");
}
private String detectLanguageKey(LanguageDetection detection, String path) {
static class MockLanguage implements Language {
private final String key;
private final String[] extensions;
+ private final String[] filenamePatterns;
MockLanguage(String key, String... extensions) {
this.key = key;
this.extensions = extensions;
+ this.filenamePatterns = new String[0];
+ }
+
+ MockLanguage(String key, String[] extensions, String[] filenamePatterns) {
+ this.key = key;
+ this.extensions = extensions;
+ this.filenamePatterns = filenamePatterns;
}
@Override
return extensions;
}
+ @Override
+ public String[] filenamePatterns() {
+ return filenamePatterns;
+ }
+
@Override
public boolean publishAllFiles() {
return true;