package org.sonar.api.batch.fs; | package org.sonar.api.batch.fs; | ||||
import org.sonar.api.ExtensionPoint; | import org.sonar.api.ExtensionPoint; | ||||
import org.sonar.api.batch.ScannerSide; | |||||
import org.sonar.api.scanner.ScannerSide; | |||||
import org.sonarsource.api.sonarlint.SonarLintSide; | import org.sonarsource.api.sonarlint.SonarLintSide; | ||||
/** | /** | ||||
* Extension point to exclude some files from inspection | * Extension point to exclude some files from inspection | ||||
* @since 4.2 | * @since 4.2 | ||||
* @since 7.6 evaluated at project level | |||||
*/ | */ | ||||
@ScannerSide | @ScannerSide | ||||
@SonarLintSide | @SonarLintSide |
import java.io.Reader; | import java.io.Reader; | ||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import javax.annotation.concurrent.Immutable; | import javax.annotation.concurrent.Immutable; | ||||
import org.sonar.api.batch.ScannerSide; | |||||
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.fs.internal.charhandler.CharHandler; | import org.sonar.api.batch.fs.internal.charhandler.CharHandler; | ||||
import org.sonar.api.batch.fs.internal.charhandler.FileHashComputer; | import org.sonar.api.batch.fs.internal.charhandler.FileHashComputer; | ||||
* Computes hash of files. Ends of Lines are ignored, so files with | * Computes hash of files. Ends of Lines are ignored, so files with | ||||
* same content but different EOL encoding have the same hash. | * same content but different EOL encoding have the same hash. | ||||
*/ | */ | ||||
@ScannerSide | |||||
@Immutable | @Immutable | ||||
public class FileMetadata { | public class FileMetadata { | ||||
private static final char LINE_FEED = '\n'; | private static final char LINE_FEED = '\n'; |
@Deprecated | @Deprecated | ||||
@ScannerSide | @ScannerSide | ||||
public class FileExclusions { | public class FileExclusions { | ||||
private final Configuration settings; | |||||
public FileExclusions(Configuration settings) { | |||||
this.settings = settings; | |||||
private final Configuration config; | |||||
public FileExclusions(Configuration config) { | |||||
this.config = config; | |||||
} | } | ||||
public String[] sourceInclusions() { | public String[] sourceInclusions() { | ||||
} | } | ||||
private String[] inclusions(String propertyKey) { | private String[] inclusions(String propertyKey) { | ||||
return Arrays.stream(settings.getStringArray(propertyKey)) | |||||
return Arrays.stream(config.getStringArray(propertyKey)) | |||||
.map(StringUtils::trim) | .map(StringUtils::trim) | ||||
.filter(s -> !"**/*".equals(s)) | .filter(s -> !"**/*".equals(s)) | ||||
.filter(s -> !"file:**/*".equals(s)) | .filter(s -> !"file:**/*".equals(s)) | ||||
} | } | ||||
private String[] exclusions(String globalExclusionsProperty, String exclusionsProperty) { | private String[] exclusions(String globalExclusionsProperty, String exclusionsProperty) { | ||||
String[] globalExclusions = settings.getStringArray(globalExclusionsProperty); | |||||
String[] exclusions = settings.getStringArray(exclusionsProperty); | |||||
String[] globalExclusions = config.getStringArray(globalExclusionsProperty); | |||||
String[] exclusions = config.getStringArray(exclusionsProperty); | |||||
return Stream.concat(Arrays.stream(globalExclusions), Arrays.stream(exclusions)) | return Stream.concat(Arrays.stream(globalExclusions), Arrays.stream(exclusions)) | ||||
.map(StringUtils::trim) | .map(StringUtils::trim) | ||||
.toArray(String[]::new); | .toArray(String[]::new); |
import java.util.Set; | import java.util.Set; | ||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.sonar.api.batch.fs.internal.charhandler.CharHandler; | import org.sonar.api.batch.fs.internal.charhandler.CharHandler; | ||||
import org.sonar.api.utils.log.Logger; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
import org.sonar.scanner.issue.ignore.pattern.LineRange; | import org.sonar.scanner.issue.ignore.pattern.LineRange; | ||||
import org.sonar.scanner.issue.ignore.pattern.PatternMatcher; | import org.sonar.scanner.issue.ignore.pattern.PatternMatcher; | ||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader.DoubleRegexpMatcher; | import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader.DoubleRegexpMatcher; | ||||
public class IssueExclusionsRegexpScanner extends CharHandler { | public class IssueExclusionsRegexpScanner extends CharHandler { | ||||
private static final Logger LOG = LoggerFactory.getLogger(IssueExclusionsLoader.class); | |||||
private static final Logger LOG = Loggers.get(IssueExclusionsLoader.class); | |||||
private final StringBuilder sb = new StringBuilder(); | private final StringBuilder sb = new StringBuilder(); | ||||
private final List<Pattern> allFilePatterns; | private final List<Pattern> allFilePatterns; |
import java.util.Arrays; | import java.util.Arrays; | ||||
import org.sonar.api.CoreProperties; | import org.sonar.api.CoreProperties; | ||||
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||||
import org.sonar.api.batch.fs.internal.AbstractProjectOrModule; | import org.sonar.api.batch.fs.internal.AbstractProjectOrModule; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | import org.sonar.api.batch.fs.internal.DefaultInputModule; | ||||
import org.sonar.api.batch.fs.internal.InputModuleHierarchy; | import org.sonar.api.batch.fs.internal.InputModuleHierarchy; | ||||
import org.sonar.api.notifications.AnalysisWarnings; | import org.sonar.api.notifications.AnalysisWarnings; | ||||
import org.sonar.api.utils.log.Logger; | import org.sonar.api.utils.log.Logger; | ||||
import org.sonar.api.utils.log.Loggers; | import org.sonar.api.utils.log.Loggers; | ||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | ||||
import org.sonar.scanner.rule.QProfileVerifier; | |||||
import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | ||||
import org.sonar.scanner.scan.filesystem.FileIndexer; | |||||
public abstract class AbstractModulePhaseExecutor { | public abstract class AbstractModulePhaseExecutor { | ||||
private final PostJobsExecutor postJobsExecutor; | private final PostJobsExecutor postJobsExecutor; | ||||
private final SensorsExecutor sensorsExecutor; | private final SensorsExecutor sensorsExecutor; | ||||
private final DefaultModuleFileSystem fs; | private final DefaultModuleFileSystem fs; | ||||
private final QProfileVerifier profileVerifier; | |||||
private final IssueExclusionsLoader issueExclusionsLoader; | private final IssueExclusionsLoader issueExclusionsLoader; | ||||
private final InputModuleHierarchy hierarchy; | private final InputModuleHierarchy hierarchy; | ||||
private final FileIndexer fileIndexer; | |||||
private final ModuleCoverageExclusions moduleCoverageExclusions; | private final ModuleCoverageExclusions moduleCoverageExclusions; | ||||
private final ProjectCoverageExclusions projectCoverageExclusions; | private final ProjectCoverageExclusions projectCoverageExclusions; | ||||
private final AnalysisWarnings analysisWarnings; | private final AnalysisWarnings analysisWarnings; | ||||
private boolean warnCoverageAlreadyLogged; | private boolean warnCoverageAlreadyLogged; | ||||
public AbstractModulePhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, InputModuleHierarchy hierarchy, DefaultModuleFileSystem fs, | public AbstractModulePhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, InputModuleHierarchy hierarchy, DefaultModuleFileSystem fs, | ||||
QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, FileIndexer fileIndexer, | |||||
IssueExclusionsLoader issueExclusionsLoader, | |||||
ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | ||||
AnalysisWarnings analysisWarnings) { | AnalysisWarnings analysisWarnings) { | ||||
this.postJobsExecutor = postJobsExecutor; | this.postJobsExecutor = postJobsExecutor; | ||||
this.sensorsExecutor = sensorsExecutor; | this.sensorsExecutor = sensorsExecutor; | ||||
this.fs = fs; | this.fs = fs; | ||||
this.profileVerifier = profileVerifier; | |||||
this.issueExclusionsLoader = issueExclusionsLoader; | this.issueExclusionsLoader = issueExclusionsLoader; | ||||
this.hierarchy = hierarchy; | this.hierarchy = hierarchy; | ||||
this.fileIndexer = fileIndexer; | |||||
this.moduleCoverageExclusions = moduleCoverageExclusions; | this.moduleCoverageExclusions = moduleCoverageExclusions; | ||||
this.projectCoverageExclusions = projectCoverageExclusions; | this.projectCoverageExclusions = projectCoverageExclusions; | ||||
this.analysisWarnings = analysisWarnings; | this.analysisWarnings = analysisWarnings; | ||||
* Executed on each module | * Executed on each module | ||||
*/ | */ | ||||
public final void execute(DefaultInputModule module) { | public final void execute(DefaultInputModule module) { | ||||
// Index the filesystem | |||||
fileIndexer.index(); | |||||
// Log detected languages and their profiles after FS is indexed and languages detected | |||||
profileVerifier.execute(); | |||||
// Initialize issue exclusions | |||||
initIssueExclusions(); | |||||
// Initialize coverage exclusions | // Initialize coverage exclusions | ||||
evaluateCoverageExclusions(module); | evaluateCoverageExclusions(module); | ||||
protected abstract void executeOnRoot(); | protected abstract void executeOnRoot(); | ||||
private void initIssueExclusions() { | |||||
if (issueExclusionsLoader.shouldExecute()) { | |||||
for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { | |||||
issueExclusionsLoader.addMulticriteriaPatterns(((DefaultInputFile) inputFile).getModuleRelativePath(), inputFile.key()); | |||||
} | |||||
} | |||||
} | |||||
} | } |
import org.sonar.api.notifications.AnalysisWarnings; | import org.sonar.api.notifications.AnalysisWarnings; | ||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | ||||
import org.sonar.scanner.issue.tracking.IssueTransition; | import org.sonar.scanner.issue.tracking.IssueTransition; | ||||
import org.sonar.scanner.rule.QProfileVerifier; | |||||
import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | ||||
import org.sonar.scanner.scan.filesystem.FileIndexer; | |||||
import org.sonar.scanner.scan.report.IssuesReports; | import org.sonar.scanner.scan.report.IssuesReports; | ||||
public final class IssuesPhaseExecutor extends AbstractModulePhaseExecutor { | public final class IssuesPhaseExecutor extends AbstractModulePhaseExecutor { | ||||
private final IssueTransition localIssueTracking; | private final IssueTransition localIssueTracking; | ||||
public IssuesPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, | public IssuesPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, | ||||
IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, | |||||
IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, InputModuleHierarchy moduleHierarchy, FileIndexer fileIndexer, | |||||
ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | |||||
AnalysisWarnings analysisWarnings) { | |||||
super(postJobsExecutor, sensorsExecutor, moduleHierarchy, fs, profileVerifier, issueExclusionsLoader, fileIndexer, | |||||
IssuesReports jsonReport, DefaultModuleFileSystem fs, | |||||
IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, InputModuleHierarchy moduleHierarchy, | |||||
ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | |||||
AnalysisWarnings analysisWarnings) { | |||||
super(postJobsExecutor, sensorsExecutor, moduleHierarchy, fs, issueExclusionsLoader, | |||||
moduleCoverageExclusions, projectCoverageExclusions, analysisWarnings); | moduleCoverageExclusions, projectCoverageExclusions, analysisWarnings); | ||||
this.issuesReport = jsonReport; | this.issuesReport = jsonReport; | ||||
this.localIssueTracking = localIssueTracking; | this.localIssueTracking = localIssueTracking; |
import org.sonar.scanner.cpd.CpdExecutor; | import org.sonar.scanner.cpd.CpdExecutor; | ||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | ||||
import org.sonar.scanner.report.ReportPublisher; | import org.sonar.scanner.report.ReportPublisher; | ||||
import org.sonar.scanner.rule.QProfileVerifier; | |||||
import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | ||||
import org.sonar.scanner.scan.filesystem.FileIndexer; | |||||
import org.sonar.scanner.scm.ScmPublisher; | import org.sonar.scanner.scm.ScmPublisher; | ||||
public final class PublishPhaseExecutor extends AbstractModulePhaseExecutor { | public final class PublishPhaseExecutor extends AbstractModulePhaseExecutor { | ||||
private final ScmPublisher scm; | private final ScmPublisher scm; | ||||
public PublishPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, | public PublishPhaseExecutor(PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, | ||||
ReportPublisher reportPublisher, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, | |||||
CpdExecutor cpdExecutor, ScmPublisher scm, InputModuleHierarchy hierarchy, FileIndexer fileIndexer, | |||||
ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | |||||
AnalysisWarnings analysisWarnings) { | |||||
super(postJobsExecutor, sensorsExecutor, hierarchy, fs, profileVerifier, issueExclusionsLoader, fileIndexer, moduleCoverageExclusions, | |||||
ReportPublisher reportPublisher, DefaultModuleFileSystem fs, IssueExclusionsLoader issueExclusionsLoader, | |||||
CpdExecutor cpdExecutor, ScmPublisher scm, InputModuleHierarchy hierarchy, | |||||
ModuleCoverageExclusions moduleCoverageExclusions, ProjectCoverageExclusions projectCoverageExclusions, | |||||
AnalysisWarnings analysisWarnings) { | |||||
super(postJobsExecutor, sensorsExecutor, hierarchy, fs, issueExclusionsLoader, moduleCoverageExclusions, | |||||
projectCoverageExclusions, analysisWarnings); | projectCoverageExclusions, analysisWarnings); | ||||
this.reportPublisher = reportPublisher; | this.reportPublisher = reportPublisher; | ||||
this.cpdExecutor = cpdExecutor; | this.cpdExecutor = cpdExecutor; |
import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||
import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
import org.sonar.api.batch.ScannerSide; | |||||
import org.sonar.api.batch.fs.FileSystem; | |||||
import org.sonar.api.config.Configuration; | import org.sonar.api.config.Configuration; | ||||
import org.sonar.api.utils.MessageException; | import org.sonar.api.utils.MessageException; | ||||
import org.sonar.scanner.scan.filesystem.InputComponentStore; | |||||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | import static org.apache.commons.lang.StringUtils.isNotEmpty; | ||||
@ScannerSide | |||||
public class QProfileVerifier { | public class QProfileVerifier { | ||||
private static final Logger LOG = LoggerFactory.getLogger(QProfileVerifier.class); | private static final Logger LOG = LoggerFactory.getLogger(QProfileVerifier.class); | ||||
private final Configuration settings; | |||||
private final FileSystem fs; | |||||
private final Configuration config; | |||||
private final InputComponentStore store; | |||||
private final QualityProfiles profiles; | private final QualityProfiles profiles; | ||||
public QProfileVerifier(Configuration settings, FileSystem fs, QualityProfiles profiles) { | |||||
this.settings = settings; | |||||
this.fs = fs; | |||||
public QProfileVerifier(Configuration config, InputComponentStore store, QualityProfiles profiles) { | |||||
this.config = config; | |||||
this.store = store; | |||||
this.profiles = profiles; | this.profiles = profiles; | ||||
} | } | ||||
@VisibleForTesting | @VisibleForTesting | ||||
void execute(Logger logger) { | void execute(Logger logger) { | ||||
String defaultName = settings.get(QualityProfiles.SONAR_PROFILE_PROP).orElse(null); | |||||
String defaultName = config.get(QualityProfiles.SONAR_PROFILE_PROP).orElse(null); | |||||
boolean defaultNameUsed = StringUtils.isBlank(defaultName); | boolean defaultNameUsed = StringUtils.isBlank(defaultName); | ||||
for (String lang : fs.languages()) { | |||||
for (String lang : store.getLanguages()) { | |||||
QProfile profile = profiles.findByLanguage(lang); | QProfile profile = profiles.findByLanguage(lang); | ||||
if (profile == null) { | if (profile == null) { | ||||
logger.warn("No Quality profile found for language {}", lang); | logger.warn("No Quality profile found for language {}", lang); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!defaultNameUsed && !fs.languages().isEmpty()) { | |||||
if (!defaultNameUsed && !store.getLanguages().isEmpty()) { | |||||
throw MessageException.of("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration."); | throw MessageException.of("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration."); | ||||
} | } | ||||
} | } |
import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | import org.sonar.api.batch.fs.internal.DefaultInputModule; | ||||
import org.sonar.api.batch.fs.internal.FileMetadata; | |||||
import org.sonar.api.scan.filesystem.FileExclusions; | import org.sonar.api.scan.filesystem.FileExclusions; | ||||
import org.sonar.core.extension.CoreExtensionsInstaller; | import org.sonar.core.extension.CoreExtensionsInstaller; | ||||
import org.sonar.core.platform.ComponentContainer; | import org.sonar.core.platform.ComponentContainer; | ||||
import org.sonar.scanner.deprecated.perspectives.ScannerPerspectives; | import org.sonar.scanner.deprecated.perspectives.ScannerPerspectives; | ||||
import org.sonar.scanner.issue.ModuleIssueFilters; | import org.sonar.scanner.issue.ModuleIssueFilters; | ||||
import org.sonar.scanner.issue.ModuleIssues; | import org.sonar.scanner.issue.ModuleIssues; | ||||
import org.sonar.scanner.issue.ignore.EnforceIssuesFilter; | |||||
import org.sonar.scanner.issue.ignore.IgnoreIssuesFilter; | |||||
import org.sonar.scanner.issue.ignore.pattern.IssueExclusionPatternInitializer; | |||||
import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer; | |||||
import org.sonar.scanner.issue.ignore.pattern.PatternMatcher; | |||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | |||||
import org.sonar.scanner.phases.AbstractModulePhaseExecutor; | import org.sonar.scanner.phases.AbstractModulePhaseExecutor; | ||||
import org.sonar.scanner.phases.IssuesPhaseExecutor; | import org.sonar.scanner.phases.IssuesPhaseExecutor; | ||||
import org.sonar.scanner.phases.ModuleCoverageExclusions; | import org.sonar.scanner.phases.ModuleCoverageExclusions; | ||||
import org.sonar.scanner.postjob.PostJobOptimizer; | import org.sonar.scanner.postjob.PostJobOptimizer; | ||||
import org.sonar.scanner.rule.QProfileVerifier; | import org.sonar.scanner.rule.QProfileVerifier; | ||||
import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; | ||||
import org.sonar.scanner.scan.filesystem.ExclusionFilters; | |||||
import org.sonar.scanner.scan.filesystem.FileIndexer; | |||||
import org.sonar.scanner.scan.filesystem.InputFileBuilder; | |||||
import org.sonar.scanner.scan.filesystem.MetadataGenerator; | |||||
import org.sonar.scanner.scan.filesystem.ModuleInputComponentStore; | import org.sonar.scanner.scan.filesystem.ModuleInputComponentStore; | ||||
import org.sonar.scanner.scan.report.IssuesReports; | import org.sonar.scanner.scan.report.IssuesReports; | ||||
import org.sonar.scanner.sensor.DefaultSensorContext; | import org.sonar.scanner.sensor.DefaultSensorContext; | ||||
// file system | // file system | ||||
ModuleInputComponentStore.class, | ModuleInputComponentStore.class, | ||||
FileExclusions.class, | FileExclusions.class, | ||||
ExclusionFilters.class, | |||||
MetadataGenerator.class, | |||||
FileMetadata.class, | |||||
FileIndexer.class, | |||||
InputFileBuilder.class, | |||||
DefaultModuleFileSystem.class, | DefaultModuleFileSystem.class, | ||||
QProfileVerifier.class, | |||||
SensorOptimizer.class, | SensorOptimizer.class, | ||||
PostJobOptimizer.class, | PostJobOptimizer.class, | ||||
// issues | // issues | ||||
ModuleIssues.class, | ModuleIssues.class, | ||||
// issue exclusions | |||||
IssueInclusionPatternInitializer.class, | |||||
IssueExclusionPatternInitializer.class, | |||||
PatternMatcher.class, | |||||
IssueExclusionsLoader.class, | |||||
EnforceIssuesFilter.class, | |||||
IgnoreIssuesFilter.class, | |||||
// Perspectives | // Perspectives | ||||
ScannerPerspectives.class, | ScannerPerspectives.class, | ||||
import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | import org.sonar.api.batch.fs.internal.DefaultInputModule; | ||||
import org.sonar.api.batch.fs.internal.FileMetadata; | |||||
import org.sonar.api.batch.fs.internal.InputModuleHierarchy; | import org.sonar.api.batch.fs.internal.InputModuleHierarchy; | ||||
import org.sonar.api.batch.fs.internal.SensorStrategy; | import org.sonar.api.batch.fs.internal.SensorStrategy; | ||||
import org.sonar.api.batch.rule.CheckFactory; | import org.sonar.api.batch.rule.CheckFactory; | ||||
import org.sonar.scanner.deprecated.test.TestableBuilder; | import org.sonar.scanner.deprecated.test.TestableBuilder; | ||||
import org.sonar.scanner.issue.DefaultProjectIssues; | import org.sonar.scanner.issue.DefaultProjectIssues; | ||||
import org.sonar.scanner.issue.IssueCache; | import org.sonar.scanner.issue.IssueCache; | ||||
import org.sonar.scanner.issue.ignore.EnforceIssuesFilter; | |||||
import org.sonar.scanner.issue.ignore.IgnoreIssuesFilter; | |||||
import org.sonar.scanner.issue.ignore.pattern.IssueExclusionPatternInitializer; | |||||
import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer; | |||||
import org.sonar.scanner.issue.ignore.pattern.PatternMatcher; | |||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | |||||
import org.sonar.scanner.issue.tracking.DefaultServerLineHashesLoader; | import org.sonar.scanner.issue.tracking.DefaultServerLineHashesLoader; | ||||
import org.sonar.scanner.issue.tracking.IssueTransition; | import org.sonar.scanner.issue.tracking.IssueTransition; | ||||
import org.sonar.scanner.issue.tracking.LocalIssueTracking; | import org.sonar.scanner.issue.tracking.LocalIssueTracking; | ||||
import org.sonar.scanner.rule.ActiveRulesProvider; | import org.sonar.scanner.rule.ActiveRulesProvider; | ||||
import org.sonar.scanner.rule.DefaultActiveRulesLoader; | import org.sonar.scanner.rule.DefaultActiveRulesLoader; | ||||
import org.sonar.scanner.rule.DefaultRulesLoader; | import org.sonar.scanner.rule.DefaultRulesLoader; | ||||
import org.sonar.scanner.rule.QProfileVerifier; | |||||
import org.sonar.scanner.rule.RulesLoader; | import org.sonar.scanner.rule.RulesLoader; | ||||
import org.sonar.scanner.rule.RulesProvider; | import org.sonar.scanner.rule.RulesProvider; | ||||
import org.sonar.scanner.scan.branch.BranchConfiguration; | import org.sonar.scanner.scan.branch.BranchConfiguration; | ||||
import org.sonar.scanner.scan.branch.BranchType; | import org.sonar.scanner.scan.branch.BranchType; | ||||
import org.sonar.scanner.scan.branch.ProjectBranchesProvider; | import org.sonar.scanner.scan.branch.ProjectBranchesProvider; | ||||
import org.sonar.scanner.scan.branch.ProjectPullRequestsProvider; | import org.sonar.scanner.scan.branch.ProjectPullRequestsProvider; | ||||
import org.sonar.scanner.scan.filesystem.FileIndexer; | |||||
import org.sonar.scanner.scan.filesystem.InputComponentStore; | import org.sonar.scanner.scan.filesystem.InputComponentStore; | ||||
import org.sonar.scanner.scan.filesystem.LanguageDetection; | import org.sonar.scanner.scan.filesystem.LanguageDetection; | ||||
import org.sonar.scanner.scan.filesystem.MetadataGenerator; | |||||
import org.sonar.scanner.scan.filesystem.ProjectExclusionFilters; | |||||
import org.sonar.scanner.scan.filesystem.ProjectFileIndexer; | |||||
import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator; | import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator; | ||||
import org.sonar.scanner.scan.filesystem.StatusDetection; | import org.sonar.scanner.scan.filesystem.StatusDetection; | ||||
import org.sonar.scanner.scan.measure.DefaultMetricFinder; | import org.sonar.scanner.scan.measure.DefaultMetricFinder; | ||||
new ScmChangedFilesProvider(), | new ScmChangedFilesProvider(), | ||||
StatusDetection.class, | StatusDetection.class, | ||||
LanguageDetection.class, | LanguageDetection.class, | ||||
MetadataGenerator.class, | |||||
FileMetadata.class, | |||||
FileIndexer.class, | |||||
ProjectFileIndexer.class, | |||||
ProjectExclusionFilters.class, | |||||
// rules | // rules | ||||
new ActiveRulesProvider(), | new ActiveRulesProvider(), | ||||
new QualityProfilesProvider(), | new QualityProfilesProvider(), | ||||
CheckFactory.class, | CheckFactory.class, | ||||
QProfileVerifier.class, | |||||
// issues | // issues | ||||
IssueCache.class, | IssueCache.class, | ||||
// Measures | // Measures | ||||
MeasureCache.class, | MeasureCache.class, | ||||
// issue exclusions | |||||
IssueInclusionPatternInitializer.class, | |||||
IssueExclusionPatternInitializer.class, | |||||
PatternMatcher.class, | |||||
IssueExclusionsLoader.class, | |||||
EnforceIssuesFilter.class, | |||||
IgnoreIssuesFilter.class, | |||||
// context | // context | ||||
ContextPropertiesCache.class, | ContextPropertiesCache.class, | ||||
ContextPropertiesPublisher.class, | ContextPropertiesPublisher.class, | ||||
LOG.info("Branch name: {}, type: {}", branchConfig.branchName(), branchTypeToDisplayName(branchConfig.branchType())); | LOG.info("Branch name: {}, type: {}", branchConfig.branchName(), branchTypeToDisplayName(branchConfig.branchType())); | ||||
} | } | ||||
getComponentByType(ProjectFileIndexer.class).index(); | |||||
// Log detected languages and their profiles after FS is indexed and languages detected | |||||
getComponentByType(QProfileVerifier.class).execute(); | |||||
LOG.debug("Start recursive analysis of project modules"); | LOG.debug("Start recursive analysis of project modules"); | ||||
scanRecursively(tree, tree.root(), analysisMode); | scanRecursively(tree, tree.root(), analysisMode); | ||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import java.nio.file.Path; | |||||
import java.util.Arrays; | |||||
import java.util.function.Function; | |||||
import java.util.stream.Stream; | |||||
import org.apache.commons.lang.ArrayUtils; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.sonar.api.CoreProperties; | |||||
import org.sonar.api.batch.fs.InputFile; | |||||
import org.sonar.api.batch.fs.internal.PathPattern; | |||||
import org.sonar.api.utils.log.Logger; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
public abstract class AbstractExclusionFilters { | |||||
private static final Logger LOG = Loggers.get(AbstractExclusionFilters.class); | |||||
private final String[] sourceInclusions; | |||||
private final String[] testInclusions; | |||||
private final String[] sourceExclusions; | |||||
private final String[] testExclusions; | |||||
private PathPattern[] mainInclusionsPattern; | |||||
private PathPattern[] mainExclusionsPattern; | |||||
private PathPattern[] testInclusionsPattern; | |||||
private PathPattern[] testExclusionsPattern; | |||||
public AbstractExclusionFilters(Function<String, String[]> configProvider) { | |||||
this.sourceInclusions = inclusions(configProvider, CoreProperties.PROJECT_INCLUSIONS_PROPERTY); | |||||
this.testInclusions = inclusions(configProvider, CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY); | |||||
this.sourceExclusions = exclusions(configProvider, CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY, CoreProperties.PROJECT_EXCLUSIONS_PROPERTY); | |||||
this.testExclusions = exclusions(configProvider, CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY, CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY); | |||||
this.mainInclusionsPattern = prepareMainInclusions(sourceInclusions); | |||||
this.mainExclusionsPattern = prepareMainExclusions(sourceExclusions, testInclusions); | |||||
this.testInclusionsPattern = prepareTestInclusions(testInclusions); | |||||
this.testExclusionsPattern = prepareTestExclusions(testExclusions); | |||||
} | |||||
protected void log() { | |||||
log("Included sources: ", mainInclusionsPattern); | |||||
log("Excluded sources: ", mainExclusionsPattern); | |||||
log("Included tests: ", testInclusionsPattern); | |||||
log("Excluded tests: ", testExclusionsPattern); | |||||
} | |||||
private String[] inclusions(Function<String, String[]> configProvider, String propertyKey) { | |||||
return Arrays.stream(configProvider.apply(propertyKey)) | |||||
.map(StringUtils::trim) | |||||
.filter(s -> !"**/*".equals(s)) | |||||
.filter(s -> !"file:**/*".equals(s)) | |||||
.toArray(String[]::new); | |||||
} | |||||
private String[] exclusions(Function<String, String[]> configProvider, String globalExclusionsProperty, String exclusionsProperty) { | |||||
String[] globalExclusions = configProvider.apply(globalExclusionsProperty); | |||||
String[] exclusions = configProvider.apply(exclusionsProperty); | |||||
return Stream.concat(Arrays.stream(globalExclusions), Arrays.stream(exclusions)) | |||||
.map(StringUtils::trim) | |||||
.toArray(String[]::new); | |||||
} | |||||
public boolean hasPattern() { | |||||
return mainInclusionsPattern.length > 0 || mainExclusionsPattern.length > 0 || testInclusionsPattern.length > 0 || testExclusionsPattern.length > 0; | |||||
} | |||||
private static void log(String title, PathPattern[] patterns) { | |||||
if (patterns.length > 0) { | |||||
LOG.info(title); | |||||
for (PathPattern pattern : patterns) { | |||||
LOG.info(" {}", pattern); | |||||
} | |||||
} | |||||
} | |||||
public boolean accept(Path absolutePath, Path relativePath, InputFile.Type type) { | |||||
PathPattern[] inclusionPatterns; | |||||
PathPattern[] exclusionPatterns; | |||||
if (InputFile.Type.MAIN == type) { | |||||
inclusionPatterns = mainInclusionsPattern; | |||||
exclusionPatterns = mainExclusionsPattern; | |||||
} else if (InputFile.Type.TEST == type) { | |||||
inclusionPatterns = testInclusionsPattern; | |||||
exclusionPatterns = testExclusionsPattern; | |||||
} else { | |||||
throw new IllegalArgumentException("Unknown file type: " + type); | |||||
} | |||||
if (inclusionPatterns.length > 0) { | |||||
boolean matchInclusion = false; | |||||
for (PathPattern pattern : inclusionPatterns) { | |||||
matchInclusion |= pattern.match(absolutePath, relativePath); | |||||
} | |||||
if (!matchInclusion) { | |||||
return false; | |||||
} | |||||
} | |||||
if (exclusionPatterns.length > 0) { | |||||
for (PathPattern pattern : exclusionPatterns) { | |||||
if (pattern.match(absolutePath, relativePath)) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
private static PathPattern[] prepareMainInclusions(String[] sourceInclusions) { | |||||
if (sourceInclusions.length > 0) { | |||||
// User defined params | |||||
return PathPattern.create(sourceInclusions); | |||||
} | |||||
return new PathPattern[0]; | |||||
} | |||||
private static PathPattern[] prepareTestInclusions(String[] testInclusions) { | |||||
return PathPattern.create(testInclusions); | |||||
} | |||||
static PathPattern[] prepareMainExclusions(String[] sourceExclusions, String[] testInclusions) { | |||||
String[] patterns = (String[]) ArrayUtils.addAll( | |||||
sourceExclusions, testInclusions); | |||||
return PathPattern.create(patterns); | |||||
} | |||||
private static PathPattern[] prepareTestExclusions(String[] testExclusions) { | |||||
return PathPattern.create(testExclusions); | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { | |||||
return true; | |||||
} | |||||
if (!(o instanceof AbstractExclusionFilters)) { | |||||
return false; | |||||
} | |||||
AbstractExclusionFilters that = (AbstractExclusionFilters) o; | |||||
return Arrays.equals(sourceInclusions, that.sourceInclusions) && | |||||
Arrays.equals(testInclusions, that.testInclusions) && | |||||
Arrays.equals(sourceExclusions, that.sourceExclusions) && | |||||
Arrays.equals(testExclusions, that.testExclusions); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = Arrays.hashCode(sourceInclusions); | |||||
result = 31 * result + Arrays.hashCode(testInclusions); | |||||
result = 31 * result + Arrays.hashCode(sourceExclusions); | |||||
result = 31 * result + Arrays.hashCode(testExclusions); | |||||
return result; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import java.nio.file.Path; | |||||
import org.apache.commons.lang.ArrayUtils; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.sonar.api.batch.ScannerSide; | |||||
import org.sonar.api.batch.fs.InputFile; | |||||
import org.sonar.api.batch.fs.internal.PathPattern; | |||||
import org.sonar.api.scan.filesystem.FileExclusions; | |||||
@ScannerSide | |||||
public class ExclusionFilters { | |||||
private static final Logger LOG = LoggerFactory.getLogger(ExclusionFilters.class); | |||||
private final FileExclusions exclusionSettings; | |||||
private PathPattern[] mainInclusions; | |||||
private PathPattern[] mainExclusions; | |||||
private PathPattern[] testInclusions; | |||||
private PathPattern[] testExclusions; | |||||
public ExclusionFilters(FileExclusions exclusions) { | |||||
this.exclusionSettings = exclusions; | |||||
} | |||||
public void prepare() { | |||||
mainInclusions = prepareMainInclusions(); | |||||
mainExclusions = prepareMainExclusions(); | |||||
testInclusions = prepareTestInclusions(); | |||||
testExclusions = prepareTestExclusions(); | |||||
log("Included sources: ", mainInclusions); | |||||
log("Excluded sources: ", mainExclusions); | |||||
log("Included tests: ", testInclusions); | |||||
log("Excluded tests: ", testExclusions); | |||||
} | |||||
public boolean hasPattern() { | |||||
return mainInclusions.length > 0 || mainExclusions.length > 0 || testInclusions.length > 0 || testExclusions.length > 0; | |||||
} | |||||
private static void log(String title, PathPattern[] patterns) { | |||||
if (patterns.length > 0) { | |||||
LOG.info(title); | |||||
for (PathPattern pattern : patterns) { | |||||
LOG.info(" {}", pattern); | |||||
} | |||||
} | |||||
} | |||||
public boolean accept(Path absolutePath, Path relativePath, InputFile.Type type) { | |||||
PathPattern[] inclusionPatterns; | |||||
PathPattern[] exclusionPatterns; | |||||
if (InputFile.Type.MAIN == type) { | |||||
inclusionPatterns = mainInclusions; | |||||
exclusionPatterns = mainExclusions; | |||||
} else if (InputFile.Type.TEST == type) { | |||||
inclusionPatterns = testInclusions; | |||||
exclusionPatterns = testExclusions; | |||||
} else { | |||||
throw new IllegalArgumentException("Unknown file type: " + type); | |||||
} | |||||
if (inclusionPatterns.length > 0) { | |||||
boolean matchInclusion = false; | |||||
for (PathPattern pattern : inclusionPatterns) { | |||||
matchInclusion |= pattern.match(absolutePath, relativePath); | |||||
} | |||||
if (!matchInclusion) { | |||||
return false; | |||||
} | |||||
} | |||||
if (exclusionPatterns.length > 0) { | |||||
for (PathPattern pattern : exclusionPatterns) { | |||||
if (pattern.match(absolutePath, relativePath)) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
PathPattern[] prepareMainInclusions() { | |||||
if (exclusionSettings.sourceInclusions().length > 0) { | |||||
// User defined params | |||||
return PathPattern.create(exclusionSettings.sourceInclusions()); | |||||
} | |||||
return new PathPattern[0]; | |||||
} | |||||
PathPattern[] prepareTestInclusions() { | |||||
return PathPattern.create(computeTestInclusions()); | |||||
} | |||||
private String[] computeTestInclusions() { | |||||
if (exclusionSettings.testInclusions().length > 0) { | |||||
// User defined params | |||||
return exclusionSettings.testInclusions(); | |||||
} | |||||
return ArrayUtils.EMPTY_STRING_ARRAY; | |||||
} | |||||
PathPattern[] prepareMainExclusions() { | |||||
String[] patterns = (String[]) ArrayUtils.addAll( | |||||
exclusionSettings.sourceExclusions(), computeTestInclusions()); | |||||
return PathPattern.create(patterns); | |||||
} | |||||
PathPattern[] prepareTestExclusions() { | |||||
return PathPattern.create(exclusionSettings.testExclusions()); | |||||
} | |||||
} |
*/ | */ | ||||
package org.sonar.scanner.scan.filesystem; | package org.sonar.scanner.scan.filesystem; | ||||
import com.google.common.util.concurrent.ThreadFactoryBuilder; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.FileSystemLoopException; | |||||
import java.nio.file.FileVisitOption; | |||||
import java.nio.file.FileVisitResult; | |||||
import java.nio.file.FileVisitor; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.LinkOption; | import java.nio.file.LinkOption; | ||||
import java.nio.file.Path; | import java.nio.file.Path; | ||||
import java.nio.file.attribute.BasicFileAttributes; | |||||
import java.util.ArrayList; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import java.util.concurrent.ExecutionException; | |||||
import java.util.concurrent.ExecutorService; | |||||
import java.util.concurrent.Executors; | |||||
import java.util.concurrent.Future; | |||||
import java.util.concurrent.TimeUnit; | |||||
import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.sonar.api.batch.ScannerSide; | |||||
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.fs.InputFile.Type; | import org.sonar.api.batch.fs.InputFile.Type; | ||||
import org.sonar.api.batch.fs.InputFileFilter; | import org.sonar.api.batch.fs.InputFileFilter; | ||||
import org.sonar.api.batch.fs.internal.DefaultIndexedFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | import org.sonar.api.batch.fs.internal.DefaultInputModule; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputProject; | |||||
import org.sonar.api.batch.fs.internal.SensorStrategy; | |||||
import org.sonar.api.notifications.AnalysisWarnings; | |||||
import org.sonar.api.utils.MessageException; | import org.sonar.api.utils.MessageException; | ||||
import org.sonar.api.utils.log.Logger; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | |||||
import org.sonar.scanner.scan.ScanProperties; | |||||
import org.sonar.scanner.util.ProgressReport; | import org.sonar.scanner.util.ProgressReport; | ||||
/** | /** | ||||
* Index input files into {@link InputComponentStore}. | * Index input files into {@link InputComponentStore}. | ||||
*/ | */ | ||||
@ScannerSide | |||||
public class FileIndexer { | public class FileIndexer { | ||||
private static final Logger LOG = LoggerFactory.getLogger(FileIndexer.class); | |||||
private static final Logger LOG = Loggers.get(FileIndexer.class); | |||||
private final AnalysisWarnings analysisWarnings; | |||||
private final ScanProperties properties; | |||||
private final InputFileFilter[] filters; | private final InputFileFilter[] filters; | ||||
private final ExclusionFilters exclusionFilters; | |||||
private final InputFileBuilder inputFileBuilder; | |||||
private final DefaultInputModule module; | |||||
private final ProjectExclusionFilters projectExclusionFilters; | |||||
private final IssueExclusionsLoader issueExclusionsLoader; | |||||
private final MetadataGenerator metadataGenerator; | |||||
private final DefaultInputProject project; | |||||
private final ScannerComponentIdGenerator scannerComponentIdGenerator; | private final ScannerComponentIdGenerator scannerComponentIdGenerator; | ||||
private final InputComponentStore componentStore; | private final InputComponentStore componentStore; | ||||
private ExecutorService executorService; | |||||
private final List<Future<Void>> tasks; | |||||
private final DefaultModuleFileSystem defaultModuleFileSystem; | |||||
private final SensorStrategy sensorStrategy; | |||||
private final LanguageDetection langDetection; | private final LanguageDetection langDetection; | ||||
private ProgressReport progressReport; | |||||
private boolean warnExclusionsAlreadyLogged; | |||||
public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, DefaultInputModule module, ExclusionFilters exclusionFilters, | |||||
InputFileBuilder inputFileBuilder, DefaultModuleFileSystem defaultModuleFileSystem, | |||||
LanguageDetection languageDetection, | |||||
InputFileFilter[] filters) { | |||||
public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, ProjectExclusionFilters projectExclusionFilters, | |||||
IssueExclusionsLoader issueExclusionsLoader, | |||||
MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, | |||||
LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties, | |||||
InputFileFilter[] filters) { | |||||
this.project = project; | |||||
this.scannerComponentIdGenerator = scannerComponentIdGenerator; | this.scannerComponentIdGenerator = scannerComponentIdGenerator; | ||||
this.componentStore = componentStore; | this.componentStore = componentStore; | ||||
this.module = module; | |||||
this.inputFileBuilder = inputFileBuilder; | |||||
this.defaultModuleFileSystem = defaultModuleFileSystem; | |||||
this.issueExclusionsLoader = issueExclusionsLoader; | |||||
this.metadataGenerator = metadataGenerator; | |||||
this.sensorStrategy = sensorStrategy; | |||||
this.langDetection = languageDetection; | this.langDetection = languageDetection; | ||||
this.analysisWarnings = analysisWarnings; | |||||
this.properties = properties; | |||||
this.filters = filters; | this.filters = filters; | ||||
this.exclusionFilters = exclusionFilters; | |||||
this.tasks = new ArrayList<>(); | |||||
this.projectExclusionFilters = projectExclusionFilters; | |||||
} | } | ||||
public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, DefaultInputModule module, ExclusionFilters exclusionFilters, | |||||
InputFileBuilder inputFileBuilder, DefaultModuleFileSystem defaultModuleFileSystem, | |||||
LanguageDetection languageDetection) { | |||||
this(scannerComponentIdGenerator, componentStore, module, exclusionFilters, inputFileBuilder, defaultModuleFileSystem, languageDetection, | |||||
new InputFileFilter[0]); | |||||
public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, ProjectExclusionFilters projectExclusionFilters, | |||||
IssueExclusionsLoader issueExclusionsLoader, | |||||
MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties) { | |||||
this(project, scannerComponentIdGenerator, componentStore, projectExclusionFilters, issueExclusionsLoader, metadataGenerator, sensorStrategy, languageDetection, analysisWarnings, | |||||
properties, new InputFileFilter[0]); | |||||
} | } | ||||
public void index() { | |||||
int threads = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); | |||||
this.executorService = Executors.newFixedThreadPool(threads, new ThreadFactoryBuilder() | |||||
.setNameFormat("FileIndexer-%d") | |||||
.setDaemon(true) | |||||
.build()); | |||||
progressReport = new ProgressReport("Report about progress of file indexation", TimeUnit.SECONDS.toMillis(10)); | |||||
progressReport.start("Index files"); | |||||
exclusionFilters.prepare(); | |||||
Progress progress = new Progress(); | |||||
indexFiles(module.getSourceDirsOrFiles(), InputFile.Type.MAIN, progress); | |||||
indexFiles(module.getTestDirsOrFiles(), InputFile.Type.TEST, progress); | |||||
waitForTasksToComplete(progressReport); | |||||
progressReport.stop(progress.count() + " " + pluralizeFiles(progress.count()) + " indexed"); | |||||
if (exclusionFilters.hasPattern()) { | |||||
LOG.info("{} {} ignored because of inclusion/exclusion patterns", progress.excludedByPatternsCount(), pluralizeFiles(progress.excludedByPatternsCount())); | |||||
} | |||||
} | |||||
private void waitForTasksToComplete(ProgressReport report) { | |||||
executorService.shutdown(); | |||||
for (Future<Void> task : tasks) { | |||||
try { | |||||
task.get(); | |||||
} catch (ExecutionException e) { | |||||
// Unwrap ExecutionException | |||||
stopAsap(report); | |||||
throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() : new IllegalStateException(e.getCause()); | |||||
} catch (InterruptedException e) { | |||||
stopAsap(report); | |||||
throw new IllegalStateException(e); | |||||
} | |||||
} | |||||
} | |||||
private void stopAsap(ProgressReport report) { | |||||
report.stop(null); | |||||
executorService.shutdownNow(); | |||||
try { | |||||
executorService.awaitTermination(5, TimeUnit.SECONDS); | |||||
} catch (InterruptedException e1) { | |||||
// ignore, what's important is the original exception | |||||
} | |||||
} | |||||
private static String pluralizeFiles(int count) { | |||||
return count == 1 ? "file" : "files"; | |||||
} | |||||
private void indexFiles(List<Path> sources, InputFile.Type type, Progress progress) { | |||||
try { | |||||
for (Path dirOrFile : sources) { | |||||
if (dirOrFile.toFile().isDirectory()) { | |||||
indexDirectory(dirOrFile, type, progress); | |||||
} else { | |||||
tasks.add(executorService.submit(() -> indexFile(dirOrFile, type, progress))); | |||||
} | |||||
} | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Failed to index files", e); | |||||
} | |||||
} | |||||
private void indexDirectory(Path dirToIndex, InputFile.Type type, Progress progress) throws IOException { | |||||
Files.walkFileTree(dirToIndex.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, | |||||
new IndexFileVisitor(type, progress)); | |||||
} | |||||
private Void indexFile(Path sourceFile, InputFile.Type type, Progress progress) throws IOException { | |||||
public void indexFile(DefaultInputModule module, AbstractExclusionFilters moduleExclusionFilters, Path sourceFile, InputFile.Type type, ProgressReport progressReport, | |||||
AtomicInteger excludedByPatternsCount) | |||||
throws IOException { | |||||
// get case of real file without resolving link | // get case of real file without resolving link | ||||
Path realAbsoluteFile = sourceFile.toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize(); | Path realAbsoluteFile = sourceFile.toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize(); | ||||
if (!realAbsoluteFile.startsWith(module.getBaseDir())) { | |||||
LOG.warn("File '{}' is ignored. It is not located in module basedir '{}'.", realAbsoluteFile.toAbsolutePath(), module.getBaseDir()); | |||||
return null; | |||||
if (!realAbsoluteFile.startsWith(project.getBaseDir())) { | |||||
LOG.warn("File '{}' is ignored. It is not located in project basedir '{}'.", realAbsoluteFile.toAbsolutePath(), project.getBaseDir()); | |||||
return; | |||||
} | } | ||||
Path relativePath = module.getBaseDir().relativize(realAbsoluteFile); | |||||
if (!exclusionFilters.accept(realAbsoluteFile, relativePath, type)) { | |||||
progress.increaseExcludedByPatternsCount(); | |||||
return null; | |||||
Path projectRelativePath = project.getBaseDir().relativize(realAbsoluteFile); | |||||
Path moduleRelativePath = module.getBaseDir().relativize(realAbsoluteFile); | |||||
if (!projectExclusionFilters.accept(realAbsoluteFile, projectRelativePath, type)) { | |||||
excludedByPatternsCount.incrementAndGet(); | |||||
return; | |||||
} | |||||
if (!moduleExclusionFilters.accept(realAbsoluteFile, moduleRelativePath, type)) { | |||||
if (projectExclusionFilters.equals(moduleExclusionFilters)) { | |||||
warnOnce("File '" + projectRelativePath + "' was excluded because patterns are still evaluated using module relative paths but this is deprecated. " + | |||||
"Please update file inclusion/exclusion configuration so that patterns refer to project relative paths."); | |||||
} else { | |||||
warnOnce("Defining inclusion/exclusions at module level is deprecated. " + | |||||
"Move file inclusion/exclusion configuration from module '" + module.getName() + "' " + | |||||
"to the root project and update patterns to refer to project relative paths."); | |||||
} | |||||
excludedByPatternsCount.incrementAndGet(); | |||||
return; | |||||
} | } | ||||
String language = langDetection.language(realAbsoluteFile, relativePath); | |||||
String language = langDetection.language(realAbsoluteFile, projectRelativePath); | |||||
if (language == null && langDetection.getForcedLanguage() != null) { | if (language == null && langDetection.getForcedLanguage() != null) { | ||||
LOG.warn("File '{}' is ignored because it doesn't belong to the forced language '{}'", realAbsoluteFile.toAbsolutePath(), langDetection.getForcedLanguage()); | LOG.warn("File '{}' is ignored because it doesn't belong to the forced language '{}'", realAbsoluteFile.toAbsolutePath(), langDetection.getForcedLanguage()); | ||||
return null; | |||||
return; | |||||
} | |||||
DefaultIndexedFile indexedFile = new DefaultIndexedFile(realAbsoluteFile, project.key(), | |||||
projectRelativePath.toString(), | |||||
moduleRelativePath.toString(), | |||||
type, language, scannerComponentIdGenerator.getAsInt(), sensorStrategy); | |||||
DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(module.getKeyWithBranch(), f, module.getEncoding())); | |||||
if (language != null) { | |||||
inputFile.setPublished(true); | |||||
} | } | ||||
DefaultInputFile inputFile = inputFileBuilder.create(type, realAbsoluteFile, language); | |||||
if (!accept(inputFile)) { | if (!accept(inputFile)) { | ||||
progress.increaseExcludedByPatternsCount(); | |||||
return null; | |||||
return; | |||||
} | |||||
if (componentStore.getFile(inputFile.getProjectRelativePath()) != null) { | |||||
throw MessageException.of("File " + inputFile + " can't be indexed twice. Please check that inclusion/exclusion patterns produce " | |||||
+ "disjoint sets for main and test files"); | |||||
} | } | ||||
synchronized (this) { | |||||
progress.markAsIndexed(inputFile); | |||||
defaultModuleFileSystem.add(inputFile); | |||||
componentStore.put(module.key(), inputFile); | |||||
if (issueExclusionsLoader.shouldExecute()) { | |||||
issueExclusionsLoader.addMulticriteriaPatterns(inputFile.getProjectRelativePath(), inputFile.key()); | |||||
} | |||||
LOG.debug("'{}' indexed {}with language '{}'", projectRelativePath, type == Type.TEST ? "as test " : "", inputFile.language()); | |||||
if (properties.preloadFileMetadata()) { | |||||
inputFile.checkMetadata(); | |||||
} | |||||
int count = componentStore.allFiles().size(); | |||||
progressReport.message(count + " " + pluralizeFiles(count) + " indexed... (last one was " + inputFile.getProjectRelativePath() + ")"); | |||||
} | |||||
private void warnOnce(String msg) { | |||||
if (!warnExclusionsAlreadyLogged) { | |||||
LOG.warn(msg); | |||||
analysisWarnings.addUnique(msg); | |||||
warnExclusionsAlreadyLogged = true; | |||||
} | } | ||||
LOG.debug("'{}' indexed {}with language '{}'", relativePath, type == Type.TEST ? "as test " : "", inputFile.language()); | |||||
inputFileBuilder.checkMetadata(inputFile); | |||||
return null; | |||||
} | } | ||||
private boolean accept(InputFile indexedFile) { | private boolean accept(InputFile indexedFile) { | ||||
return true; | return true; | ||||
} | } | ||||
private class IndexFileVisitor implements FileVisitor<Path> { | |||||
private final Progress status; | |||||
private final Type type; | |||||
IndexFileVisitor(InputFile.Type type, Progress status) { | |||||
this.status = status; | |||||
this.type = type; | |||||
} | |||||
@Override | |||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { | |||||
Path fileName = dir.getFileName(); | |||||
if (fileName != null && fileName.toString().length() > 1 && fileName.toString().charAt(0) == '.') { | |||||
return FileVisitResult.SKIP_SUBTREE; | |||||
} | |||||
if (Files.isHidden(dir)) { | |||||
return FileVisitResult.SKIP_SUBTREE; | |||||
} | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
@Override | |||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | |||||
if (!Files.isHidden(file)) { | |||||
tasks.add(executorService.submit(() -> indexFile(file, type, status))); | |||||
} | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
@Override | |||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { | |||||
if (exc instanceof FileSystemLoopException) { | |||||
LOG.warn("Not indexing due to symlink loop: {}", file.toFile()); | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
throw exc; | |||||
} | |||||
@Override | |||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
} | |||||
private class Progress { | |||||
private AtomicInteger indexedCount = new AtomicInteger(0); | |||||
private AtomicInteger excludedByPatternsCount = new AtomicInteger(0); | |||||
void markAsIndexed(DefaultInputFile inputFile) { | |||||
if (componentStore.getFile(inputFile.getProjectRelativePath()) != null) { | |||||
throw MessageException.of("File " + inputFile + " can't be indexed twice. Please check that inclusion/exclusion patterns produce " | |||||
+ "disjoint sets for main and test files"); | |||||
} | |||||
int count = indexedCount.incrementAndGet(); | |||||
progressReport.message(count + " " + pluralizeFiles(count) + " indexed... (last one was " + inputFile.getProjectRelativePath() + ")"); | |||||
} | |||||
void increaseExcludedByPatternsCount() { | |||||
excludedByPatternsCount.incrementAndGet(); | |||||
} | |||||
public int excludedByPatternsCount() { | |||||
return excludedByPatternsCount.get(); | |||||
} | |||||
int count() { | |||||
return indexedCount.get(); | |||||
} | |||||
private static String pluralizeFiles(int count) { | |||||
return count == 1 ? "file" : "files"; | |||||
} | } | ||||
} | } |
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | import org.sonar.api.batch.fs.internal.DefaultInputModule; | ||||
import org.sonar.api.batch.fs.internal.FileExtensionPredicate; | import org.sonar.api.batch.fs.internal.FileExtensionPredicate; | ||||
import org.sonar.api.scanner.fs.InputProject; | |||||
import org.sonar.scanner.scan.branch.BranchConfiguration; | import org.sonar.scanner.scan.branch.BranchConfiguration; | ||||
/** | /** | ||||
private final Map<String, InputComponent> inputComponents = new HashMap<>(); | private final Map<String, InputComponent> inputComponents = new HashMap<>(); | ||||
private final SetMultimap<String, InputFile> filesByNameCache = LinkedHashMultimap.create(); | private final SetMultimap<String, InputFile> filesByNameCache = LinkedHashMultimap.create(); | ||||
private final SetMultimap<String, InputFile> filesByExtensionCache = LinkedHashMultimap.create(); | private final SetMultimap<String, InputFile> filesByExtensionCache = LinkedHashMultimap.create(); | ||||
private final InputProject project; | |||||
private final BranchConfiguration branchConfiguration; | private final BranchConfiguration branchConfiguration; | ||||
public InputComponentStore(InputProject project, BranchConfiguration branchConfiguration) { | |||||
this.project = project; | |||||
public InputComponentStore(BranchConfiguration branchConfiguration) { | |||||
this.branchConfiguration = branchConfiguration; | this.branchConfiguration = branchConfiguration; | ||||
} | } | ||||
::iterator; | ::iterator; | ||||
} | } | ||||
public Iterable<InputFile> allFiles() { | |||||
public Collection<InputFile> allFiles() { | |||||
return globalInputFileCache.values(); | return globalInputFileCache.values(); | ||||
} | } | ||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import java.nio.file.Path; | |||||
import javax.annotation.Nullable; | |||||
import org.sonar.api.batch.fs.InputFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultIndexedFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputProject; | |||||
import org.sonar.api.batch.fs.internal.SensorStrategy; | |||||
import org.sonar.scanner.scan.ScanProperties; | |||||
public class InputFileBuilder { | |||||
private final DefaultInputProject project; | |||||
private final DefaultInputModule module; | |||||
private final ScannerComponentIdGenerator idGenerator; | |||||
private final MetadataGenerator metadataGenerator; | |||||
private final boolean preloadMetadata; | |||||
private final Path projectBaseDir; | |||||
private final SensorStrategy sensorStrategy; | |||||
public InputFileBuilder(DefaultInputProject project, DefaultInputModule module, MetadataGenerator metadataGenerator, | |||||
ScannerComponentIdGenerator idGenerator, ScanProperties properties, | |||||
SensorStrategy sensorStrategy) { | |||||
this.sensorStrategy = sensorStrategy; | |||||
this.projectBaseDir = project.getBaseDir(); | |||||
this.project = project; | |||||
this.module = module; | |||||
this.metadataGenerator = metadataGenerator; | |||||
this.idGenerator = idGenerator; | |||||
this.preloadMetadata = properties.preloadFileMetadata(); | |||||
} | |||||
DefaultInputFile create(InputFile.Type type, Path absolutePath, @Nullable String language) { | |||||
DefaultIndexedFile indexedFile = new DefaultIndexedFile(absolutePath, project.key(), | |||||
projectBaseDir.relativize(absolutePath).toString(), | |||||
module.getBaseDir().relativize(absolutePath).toString(), | |||||
type, language, idGenerator.getAsInt(), sensorStrategy); | |||||
DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(f, module.getEncoding())); | |||||
if (language != null) { | |||||
inputFile.setPublished(true); | |||||
} | |||||
return inputFile; | |||||
} | |||||
void checkMetadata(DefaultInputFile inputFile) { | |||||
if (preloadMetadata) { | |||||
inputFile.checkMetadata(); | |||||
} | |||||
} | |||||
} |
import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.sonar.api.batch.fs.InputFile.Type; | import org.sonar.api.batch.fs.InputFile.Type; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import org.sonar.api.batch.fs.internal.FileMetadata; | import org.sonar.api.batch.fs.internal.FileMetadata; | ||||
import org.sonar.api.batch.fs.internal.Metadata; | import org.sonar.api.batch.fs.internal.Metadata; | ||||
import org.sonar.api.utils.log.Logger; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader; | ||||
public class MetadataGenerator { | public class MetadataGenerator { | ||||
private static final Logger LOG = LoggerFactory.getLogger(MetadataGenerator.class); | |||||
private static final Logger LOG = Loggers.get(MetadataGenerator.class); | |||||
@VisibleForTesting | @VisibleForTesting | ||||
static final Charset UTF_32BE = Charset.forName("UTF-32BE"); | static final Charset UTF_32BE = Charset.forName("UTF-32BE"); | ||||
private final StatusDetection statusDetection; | private final StatusDetection statusDetection; | ||||
private final FileMetadata fileMetadata; | private final FileMetadata fileMetadata; | ||||
private final DefaultInputModule inputModule; | |||||
private final IssueExclusionsLoader exclusionsScanner; | private final IssueExclusionsLoader exclusionsScanner; | ||||
public MetadataGenerator(DefaultInputModule inputModule, StatusDetection statusDetection, FileMetadata fileMetadata, IssueExclusionsLoader exclusionsScanner) { | |||||
this.inputModule = inputModule; | |||||
public MetadataGenerator(StatusDetection statusDetection, FileMetadata fileMetadata, IssueExclusionsLoader exclusionsScanner) { | |||||
this.statusDetection = statusDetection; | this.statusDetection = statusDetection; | ||||
this.fileMetadata = fileMetadata; | this.fileMetadata = fileMetadata; | ||||
this.exclusionsScanner = exclusionsScanner; | this.exclusionsScanner = exclusionsScanner; | ||||
* Sets all metadata in the file, including charset and status. | * Sets all metadata in the file, including charset and status. | ||||
* It is an expensive computation, reading the entire file. | * It is an expensive computation, reading the entire file. | ||||
*/ | */ | ||||
public void setMetadata(final DefaultInputFile inputFile, Charset defaultEncoding) { | |||||
public void setMetadata(String moduleKeyWithBranch, final DefaultInputFile inputFile, Charset defaultEncoding) { | |||||
CharsetDetector charsetDetector = new CharsetDetector(inputFile.path(), defaultEncoding); | CharsetDetector charsetDetector = new CharsetDetector(inputFile.path(), defaultEncoding); | ||||
try { | try { | ||||
Charset charset; | Charset charset; | ||||
inputFile.setCharset(charset); | inputFile.setCharset(charset); | ||||
Metadata metadata = fileMetadata.readMetadata(is, charset, inputFile.absolutePath(), exclusionsScanner.createCharHandlerFor(inputFile.key())); | Metadata metadata = fileMetadata.readMetadata(is, charset, inputFile.absolutePath(), exclusionsScanner.createCharHandlerFor(inputFile.key())); | ||||
inputFile.setMetadata(metadata); | inputFile.setMetadata(metadata); | ||||
inputFile.setStatus(statusDetection.status(inputModule.definition().getKeyWithBranch(), inputFile, metadata.hash())); | |||||
LOG.debug("'{}' generated metadata {} with charset '{}'", inputFile, inputFile.type() == Type.TEST ? "as test " : "", charset); | |||||
inputFile.setStatus(statusDetection.status(moduleKeyWithBranch, inputFile, metadata.hash())); | |||||
LOG.debug("'{}' generated metadata{} with charset '{}'", inputFile, inputFile.type() == Type.TEST ? " as test " : "", charset); | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
throw new IllegalStateException(e); | throw new IllegalStateException(e); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv; | |||||
public class ModuleExclusionFilters extends AbstractExclusionFilters { | |||||
public ModuleExclusionFilters(DefaultInputModule module) { | |||||
super(k -> { | |||||
String value = module.properties().get(k); | |||||
return value != null ? parseAsCsv(k, value) : new String[0]; | |||||
}); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import org.sonar.api.config.Configuration; | |||||
public class ProjectExclusionFilters extends AbstractExclusionFilters { | |||||
public ProjectExclusionFilters(Configuration projectConfig) { | |||||
super(projectConfig::getStringArray); | |||||
log(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import java.io.IOException; | |||||
import java.nio.file.FileSystemLoopException; | |||||
import java.nio.file.FileVisitOption; | |||||
import java.nio.file.FileVisitResult; | |||||
import java.nio.file.FileVisitor; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.Path; | |||||
import java.nio.file.attribute.BasicFileAttributes; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import java.util.concurrent.TimeUnit; | |||||
import java.util.concurrent.atomic.AtomicInteger; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.sonar.api.batch.fs.InputFile.Type; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputProject; | |||||
import org.sonar.api.batch.fs.internal.InputModuleHierarchy; | |||||
import org.sonar.scanner.util.ProgressReport; | |||||
/** | |||||
* Index project input files into {@link InputComponentStore}. | |||||
*/ | |||||
public class ProjectFileIndexer { | |||||
private static final Logger LOG = LoggerFactory.getLogger(ProjectFileIndexer.class); | |||||
private final AbstractExclusionFilters projectExclusionFilters; | |||||
private final DefaultInputProject project; | |||||
private final InputComponentStore componentStore; | |||||
private final InputModuleHierarchy inputModuleHierarchy; | |||||
private final FileIndexer fileIndexer; | |||||
private ProgressReport progressReport; | |||||
public ProjectFileIndexer(DefaultInputProject project, InputComponentStore componentStore, AbstractExclusionFilters exclusionFilters, | |||||
InputModuleHierarchy inputModuleHierarchy, | |||||
FileIndexer fileIndexer) { | |||||
this.project = project; | |||||
this.componentStore = componentStore; | |||||
this.inputModuleHierarchy = inputModuleHierarchy; | |||||
this.fileIndexer = fileIndexer; | |||||
this.projectExclusionFilters = exclusionFilters; | |||||
} | |||||
public void index() { | |||||
progressReport = new ProgressReport("Report about progress of file indexation", TimeUnit.SECONDS.toMillis(10)); | |||||
progressReport.start("Index files"); | |||||
AtomicInteger excludedByPatternsCount = new AtomicInteger(0); | |||||
indexModulesRecursively(inputModuleHierarchy.root(), excludedByPatternsCount); | |||||
int totalIndexed = componentStore.allFiles().size(); | |||||
progressReport.stop(totalIndexed + " " + pluralizeFiles(totalIndexed) + " indexed"); | |||||
if (projectExclusionFilters.hasPattern()) { | |||||
int excludedFileCount = excludedByPatternsCount.get(); | |||||
LOG.info("{} {} ignored because of inclusion/exclusion patterns", excludedFileCount, pluralizeFiles(excludedFileCount)); | |||||
} | |||||
} | |||||
private void indexModulesRecursively(DefaultInputModule module, AtomicInteger excludedByPatternsCount) { | |||||
inputModuleHierarchy.children(module).forEach(m -> indexModulesRecursively(m, excludedByPatternsCount)); | |||||
index(module, excludedByPatternsCount); | |||||
} | |||||
private void index(DefaultInputModule module, AtomicInteger excludedByPatternsCount) { | |||||
ModuleExclusionFilters moduleExclusionFilters = new ModuleExclusionFilters(module); | |||||
indexFiles(module, moduleExclusionFilters, module.getSourceDirsOrFiles(), Type.MAIN, excludedByPatternsCount); | |||||
indexFiles(module, moduleExclusionFilters, module.getTestDirsOrFiles(), Type.TEST, excludedByPatternsCount); | |||||
} | |||||
private static String pluralizeFiles(int count) { | |||||
return count == 1 ? "file" : "files"; | |||||
} | |||||
private void indexFiles(DefaultInputModule module, AbstractExclusionFilters moduleExclusionFilters, List<Path> sources, Type type, AtomicInteger excludedByPatternsCount) { | |||||
try { | |||||
for (Path dirOrFile : sources) { | |||||
if (dirOrFile.toFile().isDirectory()) { | |||||
indexDirectory(module, moduleExclusionFilters, dirOrFile, type, excludedByPatternsCount); | |||||
} else { | |||||
fileIndexer.indexFile(module, moduleExclusionFilters, dirOrFile, type, progressReport, excludedByPatternsCount); | |||||
} | |||||
} | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Failed to index files", e); | |||||
} | |||||
} | |||||
private void indexDirectory(DefaultInputModule module, AbstractExclusionFilters moduleExclusionFilters, Path dirToIndex, Type type, AtomicInteger excludedByPatternsCount) | |||||
throws IOException { | |||||
Files.walkFileTree(dirToIndex.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, | |||||
new IndexFileVisitor(module, moduleExclusionFilters, type, excludedByPatternsCount)); | |||||
} | |||||
private class IndexFileVisitor implements FileVisitor<Path> { | |||||
private final DefaultInputModule module; | |||||
private final AbstractExclusionFilters moduleExclusionFilters; | |||||
private final Type type; | |||||
private final AtomicInteger excludedByPatternsCount; | |||||
IndexFileVisitor(DefaultInputModule module, AbstractExclusionFilters moduleExclusionFilters, Type type, AtomicInteger excludedByPatternsCount) { | |||||
this.module = module; | |||||
this.moduleExclusionFilters = moduleExclusionFilters; | |||||
this.type = type; | |||||
this.excludedByPatternsCount = excludedByPatternsCount; | |||||
} | |||||
@Override | |||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { | |||||
Path fileName = dir.getFileName(); | |||||
if (fileName != null && fileName.toString().length() > 1 && fileName.toString().charAt(0) == '.') { | |||||
return FileVisitResult.SKIP_SUBTREE; | |||||
} | |||||
if (Files.isHidden(dir)) { | |||||
return FileVisitResult.SKIP_SUBTREE; | |||||
} | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
@Override | |||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | |||||
if (!Files.isHidden(file)) { | |||||
fileIndexer.indexFile(module, moduleExclusionFilters, file, type, progressReport, excludedByPatternsCount); | |||||
} | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
@Override | |||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { | |||||
if (exc instanceof FileSystemLoopException) { | |||||
LOG.warn("Not indexing due to symlink loop: {}", file.toFile()); | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
throw exc; | |||||
} | |||||
@Override | |||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { | |||||
return FileVisitResult.CONTINUE; | |||||
} | |||||
} | |||||
} |
when(publisher.getWriter()).thenReturn(new ScannerReportWriter(outputDir)); | when(publisher.getWriter()).thenReturn(new ScannerReportWriter(outputDir)); | ||||
DefaultInputProject project = TestInputFileBuilder.newDefaultInputProject("foo", baseDir); | DefaultInputProject project = TestInputFileBuilder.newDefaultInputProject("foo", baseDir); | ||||
componentStore = new InputComponentStore(project, mock(BranchConfiguration.class)); | |||||
componentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
executor = new CpdExecutor(settings, index, publisher, componentStore, executorService); | executor = new CpdExecutor(settings, index, publisher, componentStore, executorService); | ||||
reader = new ScannerReportReader(outputDir); | reader = new ScannerReportReader(outputDir); | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.utils.MessageException; | import org.sonar.api.utils.MessageException; | ||||
import org.sonar.api.utils.System2; | import org.sonar.api.utils.System2; | ||||
import org.sonar.api.utils.log.LogTester; | |||||
import org.sonar.api.utils.log.LoggerLevel; | |||||
import org.sonar.scanner.mediumtest.AnalysisResult; | import org.sonar.scanner.mediumtest.AnalysisResult; | ||||
import org.sonar.scanner.mediumtest.LogOutputRecorder; | |||||
import org.sonar.scanner.mediumtest.ScannerMediumTester; | import org.sonar.scanner.mediumtest.ScannerMediumTester; | ||||
import org.sonar.xoo.XooPlugin; | import org.sonar.xoo.XooPlugin; | ||||
import org.sonar.xoo.global.GlobalSensor; | import org.sonar.xoo.global.GlobalSensor; | ||||
import org.sonar.xoo.rule.XooRulesDefinition; | import org.sonar.xoo.rule.XooRulesDefinition; | ||||
import static java.util.stream.Collectors.joining; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.junit.Assume.assumeTrue; | import static org.junit.Assume.assumeTrue; | ||||
@Rule | @Rule | ||||
public ExpectedException thrown = ExpectedException.none(); | public ExpectedException thrown = ExpectedException.none(); | ||||
private LogOutputRecorder logs = new LogOutputRecorder(); | |||||
@Rule | |||||
public LogTester logTester = new LogTester(); | |||||
@Rule | @Rule | ||||
public ScannerMediumTester tester = new ScannerMediumTester() | public ScannerMediumTester tester = new ScannerMediumTester() | ||||
.registerPlugin("xoo", new XooPlugin()) | .registerPlugin("xoo", new XooPlugin()) | ||||
.addDefaultQProfile("xoo", "Sonar Way") | .addDefaultQProfile("xoo", "Sonar Way") | ||||
.addDefaultQProfile("xoo2", "Sonar Way") | |||||
.setLogOutput(logs); | |||||
.addDefaultQProfile("xoo2", "Sonar Way"); | |||||
private File baseDir; | private File baseDir; | ||||
private ImmutableMap.Builder<String, String> builder; | private ImmutableMap.Builder<String, String> builder; | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); | |||||
assertThat(logs.getAllAsString()).contains("Organization key: my org"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("Branch key"); | |||||
assertThat(logTester.logs()).contains("Project key: com.foo.project"); | |||||
assertThat(logTester.logs()).contains("Organization key: my org"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("Branch key"); | |||||
} | } | ||||
@Test | @Test | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); | |||||
assertThat(logs.getAllAsString()).contains("Branch key: my-branch"); | |||||
assertThat(logs.getAllAsString()).contains("The use of \"sonar.branch\" is deprecated and replaced by \"sonar.branch.name\"."); | |||||
assertThat(logTester.logs()).contains("Project key: com.foo.project"); | |||||
assertThat(logTester.logs()).contains("Branch key: my-branch"); | |||||
assertThat(logTester.logs()).contains("The use of \"sonar.branch\" is deprecated and replaced by \"sonar.branch.name\". See https://redirect.sonarsource.com/doc/branches.html."); | |||||
} | } | ||||
@Test | @Test | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("Organization key"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("Branch key"); | |||||
assertThat(logTester.logs()).contains("Project key: com.foo.project"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("Organization key"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("Branch key"); | |||||
} | } | ||||
@Test | @Test | ||||
File javaFile = new File(srcDir, "sample.java"); | File javaFile = new File(srcDir, "sample.java"); | ||||
FileUtils.write(javaFile, "Sample xoo\ncontent"); | FileUtils.write(javaFile, "Sample xoo\ncontent"); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
tester.newAnalysis() | tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src") | .put("sonar.sources", "src") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("2 files indexed"); | |||||
assertThat(logs.getAllAsString()).contains("'src/sample.xoo' generated metadata"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("'src/sample.java' generated metadata"); | |||||
assertThat(logTester.logs()).contains("2 files indexed"); | |||||
assertThat(logTester.logs()).contains("'src/sample.xoo' generated metadata with charset 'UTF-8'"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("'src/sample.java' generated metadata"); | |||||
} | } | ||||
File javaFile = new File(srcDir, "sample.java"); | File javaFile = new File(srcDir, "sample.java"); | ||||
FileUtils.write(javaFile, "Sample xoo\ncontent"); | FileUtils.write(javaFile, "Sample xoo\ncontent"); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
tester.newAnalysis() | tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src") | .put("sonar.sources", "src") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("2 files indexed"); | |||||
assertThat(logs.getAllAsString()).contains("'src/sample.xoo' generated metadata"); | |||||
assertThat(logs.getAllAsString()).contains("'src/sample.java' generated metadata"); | |||||
assertThat(logTester.logs()).contains("2 files indexed"); | |||||
assertThat(logTester.logs()).contains("'src/sample.xoo' generated metadata with charset 'UTF-8'"); | |||||
assertThat(logTester.logs()).contains("'src/sample.java' generated metadata with charset 'UTF-8'"); | |||||
} | } | ||||
@Test | @Test | ||||
Path javaFile = mainDir.resolve("sample.java"); | Path javaFile = mainDir.resolve("sample.java"); | ||||
Files.write(javaFile, "Sample xoo\ncontent".getBytes(StandardCharsets.UTF_8)); | Files.write(javaFile, "Sample xoo\ncontent".getBytes(StandardCharsets.UTF_8)); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
AnalysisResult result = tester.newAnalysis() | AnalysisResult result = tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src/main") | .put("sonar.sources", "src/main") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("3 files indexed"); | |||||
assertThat(logs.getAllAsString()).contains("'src/main/sample.xoo' generated metadata"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("'src/main/sample.java' generated metadata"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("'src/test/sample.java' generated metadata"); | |||||
assertThat(logTester.logs()).contains("3 files indexed"); | |||||
assertThat(logTester.logs()).contains("'src/main/sample.xoo' generated metadata with charset 'UTF-8'"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("'src/main/sample.java' generated metadata"); | |||||
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("'src/test/sample.java' generated metadata"); | |||||
DefaultInputFile javaInputFile = (DefaultInputFile) result.inputFile("src/main/sample.java"); | DefaultInputFile javaInputFile = (DefaultInputFile) result.inputFile("src/main/sample.java"); | ||||
thrown.expect(IllegalStateException.class); | thrown.expect(IllegalStateException.class); | ||||
File xooFile = new File(srcDir, "sample.unknown"); | File xooFile = new File(srcDir, "sample.unknown"); | ||||
FileUtils.write(xooFile, "Sample xoo\ncontent"); | FileUtils.write(xooFile, "Sample xoo\ncontent"); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
AnalysisResult result = tester.newAnalysis() | AnalysisResult result = tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src") | .put("sonar.sources", "src") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).contains("1 file indexed"); | |||||
assertThat(logs.getAllAsString()).contains("'src" + File.separator + "sample.unknown' indexed with language 'null'"); | |||||
assertThat(logs.getAllAsString()).contains("'src/sample.unknown' generated metadata"); | |||||
assertThat(logTester.logs()).contains("1 file indexed"); | |||||
assertThat(logTester.logs()).contains("'src" + File.separator + "sample.unknown' indexed with language 'null'"); | |||||
assertThat(logTester.logs()).contains("'src/sample.unknown' generated metadata with charset 'UTF-8'"); | |||||
DefaultInputFile inputFile = (DefaultInputFile) result.inputFile("src/sample.unknown"); | DefaultInputFile inputFile = (DefaultInputFile) result.inputFile("src/sample.unknown"); | ||||
assertThat(result.getReportComponent(inputFile)).isNotNull(); | assertThat(result.getReportComponent(inputFile)).isNotNull(); | ||||
} | } | ||||
new Random().nextBytes(b); | new Random().nextBytes(b); | ||||
FileUtils.writeByteArrayToFile(unknownFile, b); | FileUtils.writeByteArrayToFile(unknownFile, b); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
tester.newAnalysis() | tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src") | .put("sonar.sources", "src") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).containsOnlyOnce("'src" + File.separator + "myfile.binary' indexed with language 'null'"); | |||||
assertThat(logs.getAllAsString()).doesNotContain("'src/myfile.binary' generating issue exclusions"); | |||||
assertThat(logs.getAllAsString()).containsOnlyOnce("'src/sample.xoo' generating issue exclusions"); | |||||
assertThat(logTester.logs()).containsOnlyOnce("'src" + File.separator + "myfile.binary' indexed with language 'null'"); | |||||
assertThat(logTester.logs()).doesNotContain("'src/myfile.binary' generating issue exclusions"); | |||||
assertThat(logTester.logs()).containsOnlyOnce("'src/sample.xoo' generating issue exclusions"); | |||||
} | } | ||||
@Test | @Test | ||||
File unknownFile = new File(srcDir, "myfile.binary"); | File unknownFile = new File(srcDir, "myfile.binary"); | ||||
FileUtils.write(unknownFile, "some text"); | FileUtils.write(unknownFile, "some text"); | ||||
logTester.setLevel(LoggerLevel.DEBUG); | |||||
tester.newAnalysis() | tester.newAnalysis() | ||||
.properties(builder | .properties(builder | ||||
.put("sonar.sources", "src") | .put("sonar.sources", "src") | ||||
.build()) | .build()) | ||||
.execute(); | .execute(); | ||||
assertThat(logs.getAllAsString()).containsOnlyOnce("- Exclusion pattern 'pattern'"); | |||||
assertThat(logs.getAllAsString()).containsOnlyOnce("'src/myfile.binary' generating issue exclusions"); | |||||
assertThat(logTester.logs()).containsOnlyOnce("- Exclusion pattern 'pattern': every issue in this file will be ignored."); | |||||
assertThat(logTester.logs()).containsOnlyOnce("'src/myfile.binary' generating issue exclusions"); | |||||
} | } | ||||
@Test | @Test | ||||
assertThat(result.inputFiles()).hasSize(2); | assertThat(result.inputFiles()).hasSize(2); | ||||
} | } | ||||
@Test | |||||
public void warn_user_for_outdated_inherited_exclusions_for_multi_module_project() throws IOException { | |||||
File baseDir = temp.getRoot(); | |||||
File baseDirModuleA = new File(baseDir, "moduleA"); | |||||
File baseDirModuleB = new File(baseDir, "moduleB"); | |||||
File srcDirA = new File(baseDirModuleA, "src"); | |||||
srcDirA.mkdirs(); | |||||
File srcDirB = new File(baseDirModuleB, "src"); | |||||
srcDirB.mkdirs(); | |||||
File xooFileA = new File(srcDirA, "sample.xoo"); | |||||
FileUtils.write(xooFileA, "Sample xoo\ncontent", StandardCharsets.UTF_8); | |||||
File xooFileB = new File(srcDirB, "sample.xoo"); | |||||
FileUtils.write(xooFileB, "Sample xoo\ncontent", StandardCharsets.UTF_8); | |||||
AnalysisResult result = tester.newAnalysis() | |||||
.properties(ImmutableMap.<String, String>builder() | |||||
.put("sonar.task", "scan") | |||||
.put("sonar.projectBaseDir", baseDir.getAbsolutePath()) | |||||
.put("sonar.projectKey", "com.foo.project") | |||||
.put("sonar.sources", "src") | |||||
.put("sonar.modules", "moduleA,moduleB") | |||||
.put("sonar.exclusions", "src/sample.xoo") | |||||
.build()) | |||||
.execute(); | |||||
InputFile fileA = result.inputFile("moduleA/src/sample.xoo"); | |||||
assertThat(fileA).isNull(); | |||||
InputFile fileB = result.inputFile("moduleB/src/sample.xoo"); | |||||
assertThat(fileB).isNull(); | |||||
assertThat(logTester.logs(LoggerLevel.WARN)) | |||||
.contains("File 'moduleB/src/sample.xoo' was excluded because patterns are still evaluated using module relative paths but this is deprecated. " + | |||||
"Please update file inclusion/exclusion configuration so that patterns refer to project relative paths."); | |||||
} | |||||
@Test | |||||
public void warn_user_for_outdated_module_exclusions_for_multi_module_project() throws IOException { | |||||
File baseDir = temp.getRoot(); | |||||
File baseDirModuleA = new File(baseDir, "moduleA"); | |||||
File baseDirModuleB = new File(baseDir, "moduleB"); | |||||
File srcDirA = new File(baseDirModuleA, "src"); | |||||
srcDirA.mkdirs(); | |||||
File srcDirB = new File(baseDirModuleB, "src"); | |||||
srcDirB.mkdirs(); | |||||
File xooFileA = new File(srcDirA, "sample.xoo"); | |||||
FileUtils.write(xooFileA, "Sample xoo\ncontent", StandardCharsets.UTF_8); | |||||
File xooFileB = new File(srcDirB, "sample.xoo"); | |||||
FileUtils.write(xooFileB, "Sample xoo\ncontent", StandardCharsets.UTF_8); | |||||
AnalysisResult result = tester.newAnalysis() | |||||
.properties(ImmutableMap.<String, String>builder() | |||||
.put("sonar.task", "scan") | |||||
.put("sonar.projectBaseDir", baseDir.getAbsolutePath()) | |||||
.put("sonar.projectKey", "com.foo.project") | |||||
.put("sonar.sources", "src") | |||||
.put("sonar.modules", "moduleA,moduleB") | |||||
.put("moduleB.sonar.exclusions", "src/sample.xoo") | |||||
.build()) | |||||
.execute(); | |||||
InputFile fileA = result.inputFile("moduleA/src/sample.xoo"); | |||||
assertThat(fileA).isNotNull(); | |||||
InputFile fileB = result.inputFile("moduleB/src/sample.xoo"); | |||||
assertThat(fileB).isNull(); | |||||
assertThat(logTester.logs(LoggerLevel.WARN)) | |||||
.contains("Defining inclusion/exclusions at module level is deprecated. " + | |||||
"Move file inclusion/exclusion configuration from module 'moduleB' to the root project and update patterns to refer to project relative paths."); | |||||
} | |||||
@Test | @Test | ||||
public void failForDuplicateInputFile() throws IOException { | public void failForDuplicateInputFile() throws IOException { | ||||
File srcDir = new File(baseDir, "src"); | File srcDir = new File(baseDir, "src"); | ||||
.execute(); | .execute(); | ||||
assertThat(result.inputFiles()).hasSize(4); | assertThat(result.inputFiles()).hasSize(4); | ||||
assertThat(logs.get("INFO")).contains( | |||||
assertThat(logTester.logs(LoggerLevel.INFO)).contains( | |||||
"Global Sensor: module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", | "Global Sensor: module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", | ||||
"Global Sensor: module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", | "Global Sensor: module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", | ||||
"Global Sensor: module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", | "Global Sensor: module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", |
import org.junit.Test; | import org.junit.Test; | ||||
import org.junit.rules.TemporaryFolder; | import org.junit.rules.TemporaryFolder; | ||||
import org.sonar.api.batch.AnalysisMode; | import org.sonar.api.batch.AnalysisMode; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputProject; | |||||
import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | ||||
import org.sonar.api.batch.postjob.issue.PostJobIssue; | import org.sonar.api.batch.postjob.issue.PostJobIssue; | ||||
import org.sonar.api.batch.rule.Severity; | import org.sonar.api.batch.rule.Severity; | ||||
@Before | @Before | ||||
public void setUp() throws IOException { | public void setUp() throws IOException { | ||||
issueCache = mock(IssueCache.class); | issueCache = mock(IssueCache.class); | ||||
DefaultInputProject project = TestInputFileBuilder.newDefaultInputProject("foo", temp.newFolder()); | |||||
componentStore = new InputComponentStore(project, mock(BranchConfiguration.class)); | |||||
componentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
settings = new MapSettings(); | settings = new MapSettings(); | ||||
analysisMode = mock(AnalysisMode.class); | analysisMode = mock(AnalysisMode.class); | ||||
context = new DefaultPostJobContext(settings.asConfig(), settings, issueCache, componentStore, analysisMode); | context = new DefaultPostJobContext(settings.asConfig(), settings, issueCache, componentStore, analysisMode); |
.setWorkDir(temp.newFolder()); | .setWorkDir(temp.newFolder()); | ||||
DefaultInputProject project = new DefaultInputProject(rootDef, 1); | DefaultInputProject project = new DefaultInputProject(rootDef, 1); | ||||
InputComponentStore store = new InputComponentStore(project, branchConfiguration); | |||||
InputComponentStore store = new InputComponentStore(branchConfiguration); | |||||
Path moduleBaseDir = temp.newFolder().toPath(); | Path moduleBaseDir = temp.newFolder().toPath(); | ||||
ProjectDefinition module1Def = ProjectDefinition.create() | ProjectDefinition module1Def = ProjectDefinition.create() | ||||
DefaultInputProject project = new DefaultInputProject(rootDef, 1); | DefaultInputProject project = new DefaultInputProject(rootDef, 1); | ||||
InputComponentStore store = new InputComponentStore(project, branchConfiguration); | |||||
InputComponentStore store = new InputComponentStore(branchConfiguration); | |||||
ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ||||
publisher.publish(writer); | publisher.publish(writer); | ||||
.setWorkDir(temp.newFolder()); | .setWorkDir(temp.newFolder()); | ||||
DefaultInputProject project = new DefaultInputProject(rootDef, 1); | DefaultInputProject project = new DefaultInputProject(rootDef, 1); | ||||
InputComponentStore store = new InputComponentStore(project, branchConfiguration); | |||||
InputComponentStore store = new InputComponentStore(branchConfiguration); | |||||
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.java", 5) | DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.java", 5) | ||||
.setLines(2) | .setLines(2) | ||||
.setWorkDir(temp.newFolder()); | .setWorkDir(temp.newFolder()); | ||||
DefaultInputProject project = new DefaultInputProject(rootDef, 1); | DefaultInputProject project = new DefaultInputProject(rootDef, 1); | ||||
InputComponentStore store = new InputComponentStore(project, branchConfiguration); | |||||
InputComponentStore store = new InputComponentStore(branchConfiguration); | |||||
ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ||||
publisher.publish(writer); | publisher.publish(writer); | ||||
.setWorkDir(temp.newFolder()); | .setWorkDir(temp.newFolder()); | ||||
DefaultInputProject project = new DefaultInputProject(rootDef, 1); | DefaultInputProject project = new DefaultInputProject(rootDef, 1); | ||||
InputComponentStore store = new InputComponentStore(project, branchConfiguration); | |||||
InputComponentStore store = new InputComponentStore(branchConfiguration); | |||||
ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ComponentsPublisher publisher = new ComponentsPublisher(project, store); | ||||
publisher.publish(writer); | publisher.publish(writer); | ||||
String moduleKey = "foo"; | String moduleKey = "foo"; | ||||
inputFile = new TestInputFileBuilder(moduleKey, "src/Foo.php").setLines(5).build(); | inputFile = new TestInputFileBuilder(moduleKey, "src/Foo.php").setLines(5).build(); | ||||
DefaultInputProject rootModule = TestInputFileBuilder.newDefaultInputProject(moduleKey, temp.newFolder()); | DefaultInputProject rootModule = TestInputFileBuilder.newDefaultInputProject(moduleKey, temp.newFolder()); | ||||
InputComponentStore componentCache = new InputComponentStore(rootModule, mock(BranchConfiguration.class)); | |||||
InputComponentStore componentCache = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
componentCache.put(moduleKey, inputFile); | componentCache.put(moduleKey, inputFile); | ||||
measureCache = mock(MeasureCache.class); | measureCache = mock(MeasureCache.class); |
String projectKey = "foo"; | String projectKey = "foo"; | ||||
project = TestInputFileBuilder.newDefaultInputProject(projectKey, temp.newFolder()); | project = TestInputFileBuilder.newDefaultInputProject(projectKey, temp.newFolder()); | ||||
inputFile = new TestInputFileBuilder(projectKey, "src/Foo.php").setPublish(true).build(); | inputFile = new TestInputFileBuilder(projectKey, "src/Foo.php").setPublish(true).build(); | ||||
InputComponentStore componentCache = new InputComponentStore(project, mock(BranchConfiguration.class)); | |||||
InputComponentStore componentCache = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
componentCache.put(projectKey, inputFile); | componentCache.put(projectKey, inputFile); | ||||
measureCache = mock(MeasureCache.class); | measureCache = mock(MeasureCache.class); | ||||
when(measureCache.byComponentKey(anyString())).thenReturn(Collections.<DefaultMeasure<?>>emptyList()); | when(measureCache.byComponentKey(anyString())).thenReturn(Collections.<DefaultMeasure<?>>emptyList()); |
.build(); | .build(); | ||||
DefaultInputProject rootProject = TestInputFileBuilder.newDefaultInputProject(moduleKey, baseDir); | DefaultInputProject rootProject = TestInputFileBuilder.newDefaultInputProject(moduleKey, baseDir); | ||||
InputComponentStore componentStore = new InputComponentStore(rootProject, mock(BranchConfiguration.class)); | |||||
InputComponentStore componentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
componentStore.put(moduleKey, inputFile); | componentStore.put(moduleKey, inputFile); | ||||
publisher = new SourcePublisher(componentStore); | publisher = new SourcePublisher(componentStore); |
import org.junit.rules.ExpectedException; | import org.junit.rules.ExpectedException; | ||||
import org.junit.rules.TemporaryFolder; | import org.junit.rules.TemporaryFolder; | ||||
import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
import org.sonar.api.batch.fs.internal.DefaultFileSystem; | |||||
import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | ||||
import org.sonar.api.config.internal.MapSettings; | import org.sonar.api.config.internal.MapSettings; | ||||
import org.sonar.api.utils.MessageException; | import org.sonar.api.utils.MessageException; | ||||
import org.sonar.scanner.scan.branch.BranchConfiguration; | |||||
import org.sonar.scanner.scan.filesystem.InputComponentStore; | |||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.verify; | import static org.mockito.Mockito.verify; | ||||
@Rule | @Rule | ||||
public ExpectedException thrown = ExpectedException.none(); | public ExpectedException thrown = ExpectedException.none(); | ||||
private DefaultFileSystem fs; | |||||
private InputComponentStore store; | |||||
private QualityProfiles profiles; | private QualityProfiles profiles; | ||||
private MapSettings settings = new MapSettings(); | private MapSettings settings = new MapSettings(); | ||||
@Before | @Before | ||||
public void before() throws Exception { | public void before() throws Exception { | ||||
fs = new DefaultFileSystem(temp.newFolder().toPath()); | |||||
store = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
profiles = mock(QualityProfiles.class); | profiles = mock(QualityProfiles.class); | ||||
QProfile javaProfile = new QProfile("p1", "My Java profile", "java", null); | QProfile javaProfile = new QProfile("p1", "My Java profile", "java", null); | ||||
when(profiles.findByLanguage("java")).thenReturn(javaProfile); | when(profiles.findByLanguage("java")).thenReturn(javaProfile); | ||||
@Test | @Test | ||||
public void should_log_all_used_profiles() { | public void should_log_all_used_profiles() { | ||||
fs.add(new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
fs.add(new TestInputFileBuilder("foo", "src/Baz.cbl").setLanguage("cobol").build()); | |||||
store.put("foo", new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
store.put("foo", new TestInputFileBuilder("foo", "src/Baz.cbl").setLanguage("cobol").build()); | |||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), fs, profiles); | |||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), store, profiles); | |||||
Logger logger = mock(Logger.class); | Logger logger = mock(Logger.class); | ||||
profileLogger.execute(logger); | profileLogger.execute(logger); | ||||
@Test | @Test | ||||
public void should_fail_if_default_profile_not_used() { | public void should_fail_if_default_profile_not_used() { | ||||
fs.add(new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
store.put("foo", new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
settings.setProperty("sonar.profile", "Unknown"); | settings.setProperty("sonar.profile", "Unknown"); | ||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), fs, profiles); | |||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), store, profiles); | |||||
thrown.expect(MessageException.class); | thrown.expect(MessageException.class); | ||||
thrown.expectMessage("sonar.profile was set to 'Unknown' but didn't match any profile for any language. Please check your configuration."); | thrown.expectMessage("sonar.profile was set to 'Unknown' but didn't match any profile for any language. Please check your configuration."); | ||||
public void should_not_fail_if_no_language_on_project() { | public void should_not_fail_if_no_language_on_project() { | ||||
settings.setProperty("sonar.profile", "Unknown"); | settings.setProperty("sonar.profile", "Unknown"); | ||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), fs, profiles); | |||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), store, profiles); | |||||
profileLogger.execute(); | profileLogger.execute(); | ||||
@Test | @Test | ||||
public void should_not_fail_if_default_profile_used_at_least_once() { | public void should_not_fail_if_default_profile_used_at_least_once() { | ||||
fs.add(new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
store.put("foo", new TestInputFileBuilder("foo", "src/Bar.java").setLanguage("java").build()); | |||||
settings.setProperty("sonar.profile", "My Java profile"); | settings.setProperty("sonar.profile", "My Java profile"); | ||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), fs, profiles); | |||||
QProfileVerifier profileLogger = new QProfileVerifier(settings.asConfig(), store, profiles); | |||||
profileLogger.execute(); | profileLogger.execute(); | ||||
} | } |
private InputComponentStore componentStore; | private InputComponentStore componentStore; | ||||
public void createIndexer(DefaultInputProject rootProject) { | public void createIndexer(DefaultInputProject rootProject) { | ||||
componentStore = new InputComponentStore(rootProject, mock(BranchConfiguration.class)); | |||||
componentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
moduleHierarchy = mock(DefaultInputModuleHierarchy.class); | moduleHierarchy = mock(DefaultInputModuleHierarchy.class); | ||||
indexer = new ModuleIndexer(componentStore, moduleHierarchy); | indexer = new ModuleIndexer(componentStore, moduleHierarchy); | ||||
} | } |
DefaultInputProject rootProject = TestInputFileBuilder.newDefaultInputProject(rootDef); | DefaultInputProject rootProject = TestInputFileBuilder.newDefaultInputProject(rootDef); | ||||
DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule(moduleDef); | DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule(moduleDef); | ||||
InputComponentStore store = new InputComponentStore(rootProject, mock(BranchConfiguration.class)); | |||||
InputComponentStore store = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
store.put(subModule); | store.put(subModule); | ||||
DefaultInputFile fooFile = new TestInputFileBuilder(rootModuleKey, "src/main/java/Foo.java") | DefaultInputFile fooFile = new TestInputFileBuilder(rootModuleKey, "src/main/java/Foo.java") | ||||
static class InputComponentStoreTester extends InputComponentStore { | static class InputComponentStoreTester extends InputComponentStore { | ||||
InputComponentStoreTester() throws IOException { | InputComponentStoreTester() throws IOException { | ||||
super(TestInputFileBuilder.newDefaultInputProject("root", temp.newFolder()), mock(BranchConfiguration.class)); | |||||
super(mock(BranchConfiguration.class)); | |||||
} | } | ||||
InputFile addFile(String moduleKey, String relpath, String language) { | InputFile addFile(String moduleKey, String relpath, String language) { |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.scanner.scan.filesystem; | |||||
import java.io.IOException; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.Path; | |||||
import org.junit.Before; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.junit.rules.TemporaryFolder; | |||||
import org.sonar.api.CoreProperties; | |||||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||||
import org.sonar.api.batch.fs.InputFile.Type; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import org.sonar.api.batch.fs.internal.DefaultInputProject; | |||||
import org.sonar.api.batch.fs.internal.SensorStrategy; | |||||
import org.sonar.scanner.scan.ScanProperties; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.mockito.Mockito.mock; | |||||
public class InputFileBuilderTest { | |||||
@Rule | |||||
public TemporaryFolder temp = new TemporaryFolder(); | |||||
private Path baseDir; | |||||
private Path workDir; | |||||
private InputFileBuilder builder; | |||||
private SensorStrategy sensorStrategy; | |||||
@Before | |||||
public void setUp() throws IOException { | |||||
baseDir = temp.newFolder().toPath(); | |||||
workDir = temp.newFolder().toPath(); | |||||
DefaultInputProject project = new DefaultInputProject(ProjectDefinition.create() | |||||
.setBaseDir(baseDir.toFile()) | |||||
.setWorkDir(workDir.toFile()) | |||||
.setProperty(CoreProperties.ENCODING_PROPERTY, StandardCharsets.UTF_8.name()) | |||||
.setKey("root"), 0); | |||||
Path moduleBaseDir = baseDir.resolve("module1"); | |||||
Files.createDirectories(moduleBaseDir); | |||||
DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create() | |||||
.setBaseDir(moduleBaseDir.toFile()) | |||||
.setWorkDir(workDir.toFile()) | |||||
.setKey("module1"), 1); | |||||
MetadataGenerator metadataGenerator = mock(MetadataGenerator.class); | |||||
ScannerComponentIdGenerator idGenerator = new ScannerComponentIdGenerator(); | |||||
ScanProperties properties = mock(ScanProperties.class); | |||||
sensorStrategy = new SensorStrategy(); | |||||
builder = new InputFileBuilder(project, module, metadataGenerator, idGenerator, properties, | |||||
sensorStrategy); | |||||
} | |||||
@Test | |||||
public void testBuild() { | |||||
Path filePath = baseDir.resolve("module1/src/File1.xoo"); | |||||
DefaultInputFile inputFile = builder.create(Type.MAIN, filePath, null); | |||||
assertThat(inputFile.absolutePath()).isEqualTo(filePath.toString().replaceAll("\\\\", "/")); | |||||
assertThat(inputFile.relativePath()).isEqualTo("src/File1.xoo"); | |||||
assertThat(inputFile.path()).isEqualTo(filePath); | |||||
assertThat(inputFile.key()).isEqualTo("root:module1/src/File1.xoo"); | |||||
assertThat(inputFile.isPublished()).isFalse(); | |||||
sensorStrategy.setGlobal(true); | |||||
assertThat(inputFile.relativePath()).isEqualTo("module1/src/File1.xoo"); | |||||
} | |||||
} |
import org.junit.rules.TemporaryFolder; | import org.junit.rules.TemporaryFolder; | ||||
import org.mockito.Mock; | import org.mockito.Mock; | ||||
import org.mockito.MockitoAnnotations; | import org.mockito.MockitoAnnotations; | ||||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||||
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputModule; | |||||
import org.sonar.api.batch.fs.internal.FileMetadata; | import org.sonar.api.batch.fs.internal.FileMetadata; | ||||
import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; | ||||
import org.sonar.api.utils.PathUtils; | import org.sonar.api.utils.PathUtils; | ||||
MockitoAnnotations.initMocks(this); | MockitoAnnotations.initMocks(this); | ||||
metadata = new FileMetadata(); | metadata = new FileMetadata(); | ||||
IssueExclusionsLoader issueExclusionsLoader = new IssueExclusionsLoader(mock(IssueExclusionPatternInitializer.class), mock(PatternMatcher.class)); | IssueExclusionsLoader issueExclusionsLoader = new IssueExclusionsLoader(mock(IssueExclusionPatternInitializer.class), mock(PatternMatcher.class)); | ||||
generator = new MetadataGenerator(new DefaultInputModule(ProjectDefinition.create().setKey("module").setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder())), | |||||
statusDetection, metadata, issueExclusionsLoader); | |||||
generator = new MetadataGenerator(statusDetection, metadata, issueExclusionsLoader); | |||||
} | } | ||||
@Test | @Test | ||||
DefaultInputFile inputFile = new TestInputFileBuilder("struts", relativePath) | DefaultInputFile inputFile = new TestInputFileBuilder("struts", relativePath) | ||||
.setModuleBaseDir(baseDir) | .setModuleBaseDir(baseDir) | ||||
.build(); | .build(); | ||||
generator.setMetadata(inputFile, StandardCharsets.US_ASCII); | |||||
generator.setMetadata("module", inputFile, StandardCharsets.US_ASCII); | |||||
return inputFile; | return inputFile; | ||||
} | } | ||||
@Before | @Before | ||||
public void setUp() throws IOException { | public void setUp() throws IOException { | ||||
DefaultInputProject root = TestInputFileBuilder.newDefaultInputProject(projectKey, temp.newFolder()); | DefaultInputProject root = TestInputFileBuilder.newDefaultInputProject(projectKey, temp.newFolder()); | ||||
componentStore = new InputComponentStore(root, mock(BranchConfiguration.class)); | |||||
componentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
} | } | ||||
@Test | @Test |
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.fs.internal.DefaultIndexedFile; | import org.sonar.api.batch.fs.internal.DefaultIndexedFile; | ||||
import org.sonar.api.config.internal.MapSettings; | import org.sonar.api.config.internal.MapSettings; | ||||
import org.sonar.api.scan.filesystem.FileExclusions; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
public class ExclusionFiltersTest { | |||||
public class ProjectExclusionFiltersTest { | |||||
@Rule | @Rule | ||||
public TemporaryFolder temp = new TemporaryFolder(); | public TemporaryFolder temp = new TemporaryFolder(); | ||||
private Path moduleBaseDir; | private Path moduleBaseDir; | ||||
private MapSettings settings; | private MapSettings settings; | ||||
private ExclusionFilters filter; | |||||
@Before | @Before | ||||
public void setUp() throws IOException { | public void setUp() throws IOException { | ||||
settings = new MapSettings(); | settings = new MapSettings(); | ||||
moduleBaseDir = temp.newFolder().toPath(); | moduleBaseDir = temp.newFolder().toPath(); | ||||
filter = new ExclusionFilters(new FileExclusions(settings.asConfig())); | |||||
} | } | ||||
@Test | @Test | ||||
public void no_inclusions_nor_exclusions() throws IOException { | public void no_inclusions_nor_exclusions() throws IOException { | ||||
filter.prepare(); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | ||||
assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | ||||
@Test | @Test | ||||
public void match_inclusion() throws IOException { | public void match_inclusion() throws IOException { | ||||
settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java"); | settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java"); | ||||
filter.prepare(); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | ||||
assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | ||||
@Test | @Test | ||||
public void match_at_least_one_inclusion() throws IOException { | public void match_at_least_one_inclusion() throws IOException { | ||||
settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java,**/*Dto.java"); | settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java,**/*Dto.java"); | ||||
filter.prepare(); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java", null); | IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java", null); | ||||
assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isFalse(); | assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isFalse(); | ||||
settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); | settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); | ||||
settings.setProperty(CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY, "src/test/java/**/*"); | settings.setProperty(CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY, "src/test/java/**/*"); | ||||
settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*Dao.java"); | settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*Dao.java"); | ||||
filter.prepare(); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null); | ||||
assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isFalse(); | assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isFalse(); | ||||
settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); | settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); | ||||
settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "file:" + excludedFile.getAbsolutePath()); | settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "file:" + excludedFile.getAbsolutePath()); | ||||
filter.prepare(); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Foo.java", null); | IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Foo.java", null); | ||||
assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | assertThat(filter.accept(indexedFile.path(), Paths.get(indexedFile.relativePath()), InputFile.Type.MAIN)).isTrue(); | ||||
@Test | @Test | ||||
public void trim_pattern() { | public void trim_pattern() { | ||||
settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, " **/*Dao.java "); | |||||
ProjectExclusionFilters filter = new ProjectExclusionFilters(settings.asConfig()); | |||||
assertThat(filter.prepareMainExclusions()[0].toString()).isEqualTo("**/*Dao.java"); | |||||
assertThat(filter.prepareMainExclusions(new String[] {" **/*Dao.java "}, new String[0])[0].toString()).isEqualTo("**/*Dao.java"); | |||||
} | } | ||||
} | } |
ProjectDefinition def = ProjectDefinition.create().setBaseDir(projectBaseDir).setWorkDir(temp.newFolder()).setKey("struts"); | ProjectDefinition def = ProjectDefinition.create().setBaseDir(projectBaseDir).setWorkDir(temp.newFolder()).setKey("struts"); | ||||
DefaultInputProject project = new DefaultInputProject(def, 1); | DefaultInputProject project = new DefaultInputProject(def, 1); | ||||
InputComponentStore inputComponentStore = new InputComponentStore(project, mock(BranchConfiguration.class)); | |||||
InputComponentStore inputComponentStore = new InputComponentStore(mock(BranchConfiguration.class)); | |||||
DefaultInputFile inputFile = new TestInputFileBuilder("struts", "src/main/java/org/apache/struts/Action.java") | DefaultInputFile inputFile = new TestInputFileBuilder("struts", "src/main/java/org/apache/struts/Action.java") | ||||
.setModuleBaseDir(projectBaseDir.toPath()).build(); | .setModuleBaseDir(projectBaseDir.toPath()).build(); |