├── src ├── test │ ├── resources │ │ ├── emptyFile1.xml │ │ ├── emptyFile2.xml │ │ ├── cpstab │ │ │ ├── readme.txt │ │ │ ├── lib1.jar │ │ │ ├── libs │ │ │ │ ├── lib1.jar │ │ │ │ └── lib2.jar │ │ │ ├── hashed │ │ │ │ └── lib1.jar │ │ │ ├── mmmfkoez3kxcsqlrrqbfbcxo5e4k6gq4 │ │ │ │ └── lib1.jar │ │ │ └── a_long_folder │ │ │ │ └── path_more_than_50_characters │ │ │ │ └── in_total │ │ │ │ └── lib1.jar │ │ ├── checkstyle-idea.broken2.properties │ │ ├── checkstyle-idea.broken1.properties │ │ └── org │ │ │ └── infernus │ │ │ └── idea │ │ │ └── checkstyle │ │ │ ├── SourceFile.java │ │ │ └── config-ok.xml │ └── java │ │ └── org │ │ └── infernus │ │ └── idea │ │ └── checkstyle │ │ ├── StringConfigurationLocation.java │ │ ├── TestHelper.java │ │ ├── VersionComparatorTest.java │ │ ├── CheckstyleProjectServiceTest.java │ │ ├── VersionListReaderTest.java │ │ └── csapi │ │ └── ProcessResultsThreadTest.java ├── main │ ├── resources │ │ ├── org │ │ │ └── infernus │ │ │ │ └── idea │ │ │ │ └── checkstyle │ │ │ │ ├── images │ │ │ │ ├── checkstyle.pxd │ │ │ │ ├── checkstyle.svg │ │ │ │ ├── checkstyle_dark.svg │ │ │ │ ├── checkstyle@20x20.svg │ │ │ │ └── checkstyle@20x20_dark.svg │ │ │ │ └── util │ │ │ │ └── copylibs-readme.template.txt │ │ ├── dtd │ │ │ ├── packages_1_0.dtd │ │ │ ├── configuration_1_0.dtd │ │ │ ├── suppressions_1_0.dtd │ │ │ ├── suppressions_1_2.dtd │ │ │ ├── suppressions_1_1.dtd │ │ │ ├── suppressions_1_1_xpath_experimental.dtd │ │ │ ├── suppressions_1_2_xpath_experimental.dtd │ │ │ ├── configuration_1_1.dtd │ │ │ ├── configuration_1_2.dtd │ │ │ ├── configuration_1_3.dtd │ │ │ ├── import_control_1_0.dtd │ │ │ ├── import_control_1_1.dtd │ │ │ └── import_control_1_2.dtd │ │ └── inspectionDescriptions │ │ │ └── CheckStyle.html │ └── java │ │ └── org │ │ └── infernus │ │ └── idea │ │ └── checkstyle │ │ ├── doc-files │ │ ├── CheckstyleClassLoader-1.png │ │ └── CheckstyleClassLoader-2.png │ │ ├── config │ │ ├── ConfigurationListener.java │ │ ├── ConfigurationLocationSource.java │ │ └── PluginConfigurationManager.java │ │ ├── checker │ │ ├── ConfigurationLocationStatus.java │ │ ├── ScannerListener.java │ │ ├── SuppressForCheckstyleFix.java │ │ ├── ConfigurationLocationResult.java │ │ ├── CachedChecker.java │ │ ├── CheckerFactoryCacheKey.java │ │ ├── CheckerFactoryWorker.java │ │ ├── ListPropertyResolver.java │ │ └── CreateScannableFileAction.java │ │ ├── csapi │ │ ├── SeverityLevel.java │ │ ├── TabWidthAndBaseDirProvider.java │ │ ├── ConfigVisitor.java │ │ ├── Issue.java │ │ ├── CheckstyleInternalObject.java │ │ ├── KnownTokenTypes.java │ │ ├── BundledConfigProvider.java │ │ └── ConfigurationModule.java │ │ ├── toolwindow │ │ ├── ResultGrouping.java │ │ ├── FileGroupTreeInfo.java │ │ ├── PackageGroupTreeInfo.java │ │ ├── ConfigurationLocationGroupTreeInfo.java │ │ ├── SeverityGroupTreeInfo.java │ │ ├── CheckStyleToolWindowFactory.java │ │ ├── GroupTreeInfo.java │ │ ├── ResultProblem.java │ │ ├── ResultTreeNode.java │ │ ├── ProblemResultTreeInfo.java │ │ └── ToggleableTreeNode.java │ │ ├── util │ │ ├── Strings.java │ │ ├── Exceptions.java │ │ ├── OS.java │ │ ├── FileTypes.java │ │ ├── Objects.java │ │ ├── DisplayFormats.java │ │ ├── ProjectPaths.java │ │ ├── Streams.java │ │ ├── ClassLoaderDumper.java │ │ ├── Async.java │ │ └── ModulePaths.java │ │ ├── actions │ │ ├── GroupByFile.java │ │ ├── GroupByPackage.java │ │ ├── GroupBySeverity.java │ │ ├── GroupBySourceCheck.java │ │ ├── GroupByConfigurationLocation.java │ │ ├── Close.java │ │ ├── JumpToSource.java │ │ ├── NextResult.java │ │ ├── PreviousResult.java │ │ ├── ExpandAll.java │ │ ├── CollapseAll.java │ │ ├── ScanAllGivenFilesTask.java │ │ ├── ScanAllFilesInProjectTask.java │ │ ├── ScanAllFilesInModuleTask.java │ │ ├── AnalyseCurrentFile.java │ │ ├── ResetLoadedRulesFiles.java │ │ ├── ScrollToSource.java │ │ ├── DisplayErrors.java │ │ ├── DisplayInfo.java │ │ ├── DisplayWarnings.java │ │ ├── GroupingAction.java │ │ ├── StopCheck.java │ │ ├── ScanAllFilesTask.java │ │ ├── ScanModifiedFiles.java │ │ ├── ToolWindowAccess.java │ │ └── ScanCurrentChangeList.java │ │ ├── exception │ │ ├── CheckStylePluginParseException.java │ │ ├── CheckStylePluginException.java │ │ ├── CheckstyleToolException.java │ │ └── CheckstyleServiceException.java │ │ ├── model │ │ ├── ScanResult.java │ │ ├── ConfigurationType.java │ │ ├── RelativeFileConfigurationLocation.java │ │ ├── ScanScope.java │ │ ├── BundledConfigurationLocation.java │ │ ├── InsecureHTTPURLConfigurationLocation.java │ │ ├── NamedScopeHelper.java │ │ └── ClasspathConfigurationLocation.java │ │ ├── CheckstyleModuleConfigurationEditorProvider.java │ │ ├── CheckStyleBundle.java │ │ ├── ui │ │ ├── CompletePanel.java │ │ ├── CheckStyleInspectionPanel.java │ │ ├── PropertiesDialogue.java │ │ └── ErrorPanel.java │ │ ├── handlers │ │ └── ScanFilesBeforeCheckinHandlerFactory.java │ │ ├── CheckStylePlugin.java │ │ ├── checks │ │ ├── Check.java │ │ ├── CheckFactory.java │ │ └── PackageHtmlCheck.java │ │ ├── importer │ │ ├── modules │ │ │ ├── NoWhitespaceBeforeImporter.java │ │ │ ├── WhitespaceAfterImporter.java │ │ │ ├── NeedBracesImporter.java │ │ │ ├── FileTabCharacterImporter.java │ │ │ ├── EmptyLineSeparatorImporter.java │ │ │ ├── LineLengthImporter.java │ │ │ ├── IndentationImporter.java │ │ │ └── AvoidStarImportImporter.java │ │ ├── ModuleImporterFactory.java │ │ └── ModuleImporter.java │ │ ├── startup │ │ └── DisableCheckstyleLogging.java │ │ ├── ConfigurationInvalidator.java │ │ └── CheckstylePluginApi.java ├── csaccessTest │ ├── resources │ │ └── org │ │ │ └── infernus │ │ │ └── idea │ │ │ └── checkstyle │ │ │ └── service │ │ │ ├── SourceFile.java │ │ │ ├── cmd │ │ │ └── config-ok.xml │ │ │ └── config-file-contents-holder.xml │ └── java │ │ └── org │ │ └── infernus │ │ └── idea │ │ └── checkstyle │ │ └── service │ │ ├── FileUtil.java │ │ ├── TestHelper.java │ │ ├── SimpleResolverTest.java │ │ ├── cmd │ │ ├── OpScanTest.java │ │ ├── OpDestroyCheckerTest.java │ │ └── OpPeruseConfigurationTest.java │ │ ├── ConfigurationBuilder.java │ │ ├── StringConfigurationLocation.java │ │ └── CsVersionInfo.java └── csaccess │ └── java │ └── org │ └── infernus │ └── idea │ └── checkstyle │ └── service │ ├── entities │ ├── HasCsConfig.java │ ├── HasChecker.java │ ├── CsConfigObject.java │ └── CheckerWithConfig.java │ ├── package-info.java │ ├── cmd │ ├── CheckstyleCommand.java │ ├── CheckstyleBridge.java │ └── OpDestroyChecker.java │ └── SimpleResolver.java ├── mise.toml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── .gitignore ├── .github └── workflows │ ├── ci.yml │ └── release.yml └── LICENCE /src/test/resources/emptyFile1.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/emptyFile2.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | java = "liberica-17" 3 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.configuration-cache=true 2 | -------------------------------------------------------------------------------- /src/test/resources/cpstab/readme.txt: -------------------------------------------------------------------------------- 1 | This file should be named as in TempDirProvider.README_FILE. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/cpstab/lib1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/lib1.jar -------------------------------------------------------------------------------- /src/test/resources/cpstab/libs/lib1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/libs/lib1.jar -------------------------------------------------------------------------------- /src/test/resources/cpstab/libs/lib2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/libs/lib2.jar -------------------------------------------------------------------------------- /src/test/resources/cpstab/hashed/lib1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/hashed/lib1.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "checkstyle-idea" 2 | 3 | dependencyResolutionManagement { 4 | repositories { 5 | mavenCentral() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/cpstab/mmmfkoez3kxcsqlrrqbfbcxo5e4k6gq4/lib1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/mmmfkoez3kxcsqlrrqbfbcxo5e4k6gq4/lib1.jar -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/images/checkstyle.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/main/resources/org/infernus/idea/checkstyle/images/checkstyle.pxd -------------------------------------------------------------------------------- /src/main/resources/dtd/packages_1_0.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/doc-files/CheckstyleClassLoader-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/main/java/org/infernus/idea/checkstyle/doc-files/CheckstyleClassLoader-1.png -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/doc-files/CheckstyleClassLoader-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/main/java/org/infernus/idea/checkstyle/doc-files/CheckstyleClassLoader-2.png -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/config/ConfigurationListener.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.config; 2 | 3 | public interface ConfigurationListener { 4 | 5 | void configurationChanged(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/cpstab/a_long_folder/path_more_than_50_characters/in_total/lib1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jshiell/checkstyle-idea/HEAD/src/test/resources/cpstab/a_long_folder/path_more_than_50_characters/in_total/lib1.jar -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/CheckStyle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This inspection integrates CheckStyle and reports in real-time 4 | on problems against the current CheckStyle profile. 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/ConfigurationLocationStatus.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | public enum ConfigurationLocationStatus { 4 | 5 | PRESENT, 6 | NOT_PRESENT, 7 | BLOCKED 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/SeverityLevel.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | /** 4 | * Checkstyle violation severity levels supported by this plugin. 5 | */ 6 | public enum SeverityLevel { 7 | Ignore, Info, Warning, Error 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/checkstyle-idea.broken2.properties: -------------------------------------------------------------------------------- 1 | # Broken Checkstyle-IDEA plugin configuration for unit tests 2 | 3 | 4 | checkstyle.versions.supported = 7.1, 7.2, 7.3 5 | baseVersion = 7.1 6 | 7 | # Error: 7.1.1 does not exist 8 | checkstyle.versions.map = 7.0 -> 7.1.1 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CheckStyle-IDEA.zip 2 | .DS_Store 3 | *.iws 4 | *.ipr 5 | *.iml 6 | .idea/ 7 | /build/ 8 | /buildSrc/build/ 9 | /buildSrc/out/ 10 | /test-configs/ 11 | .gradle/ 12 | gradle-app.setting 13 | /.gradletasknamecache 14 | _support/ 15 | /out/ 16 | .intellijPlatform/ 17 | -------------------------------------------------------------------------------- /src/test/resources/checkstyle-idea.broken1.properties: -------------------------------------------------------------------------------- 1 | # Broken Checkstyle-IDEA plugin configuration for unit tests 2 | 3 | 4 | checkstyle.versions.supported = 7.1, 7.2, 7.3 5 | baseVersion = 7.1 6 | 7 | # Error: 7.1 is in fact supported 8 | checkstyle.versions.map = 7.0 -> 7.1, 7.1 -> 7.2 9 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/TabWidthAndBaseDirProvider.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | import java.util.Optional; 4 | 5 | 6 | public interface TabWidthAndBaseDirProvider { 7 | 8 | int tabWidth(); 9 | 10 | Optional baseDir(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ResultGrouping.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | public enum ResultGrouping { 4 | 5 | BY_FILE, 6 | BY_PACKAGE, 7 | BY_SEVERITY, 8 | BY_CONFIGURATION_LOCATION, 9 | BY_SOURCE_CHECK 10 | 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/test/resources/org/infernus/idea/checkstyle/SourceFile.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | /** 4 | * An example source file used for triggering Checkstyle scans in unit tests. 5 | */ 6 | public class SourceFile { 7 | public void method() { 8 | // does not matter 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/Strings.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | public final class Strings { 4 | 5 | private Strings() { 6 | } 7 | 8 | public static boolean isBlank(final String value) { 9 | return value == null || value.trim().isEmpty(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/csaccessTest/resources/org/infernus/idea/checkstyle/service/SourceFile.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | /** 4 | * An example source file used for triggering Checkstyle scans in unit tests. 5 | */ 6 | public class SourceFile { 7 | public void method() { 8 | // does not matter 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupByFile.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 4 | 5 | public class GroupByFile extends GroupingAction { 6 | 7 | public GroupByFile() { 8 | super(ResultGrouping.BY_FILE); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupByPackage.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 4 | 5 | public class GroupByPackage extends GroupingAction { 6 | 7 | public GroupByPackage() { 8 | super(ResultGrouping.BY_PACKAGE); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupBySeverity.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 4 | 5 | public class GroupBySeverity extends GroupingAction { 6 | 7 | public GroupBySeverity() { 8 | super(ResultGrouping.BY_SEVERITY); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/ConfigVisitor.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | 6 | /** 7 | * Visitor called by the PeruseConfiguration command. 8 | */ 9 | public interface ConfigVisitor { 10 | 11 | void visit(@NotNull ConfigurationModule module); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupBySourceCheck.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 4 | 5 | public class GroupBySourceCheck extends GroupingAction { 6 | 7 | public GroupBySourceCheck() { 8 | super(ResultGrouping.BY_SOURCE_CHECK); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupByConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 4 | 5 | public class GroupByConfigurationLocation extends GroupingAction { 6 | 7 | public GroupByConfigurationLocation() { 8 | super(ResultGrouping.BY_CONFIGURATION_LOCATION); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/entities/HasCsConfig.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.entities; 2 | 3 | import com.puppycrawl.tools.checkstyle.api.Configuration; 4 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 5 | 6 | 7 | public interface HasCsConfig extends CheckstyleInternalObject { 8 | 9 | Configuration getConfiguration(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/Exceptions.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | public final class Exceptions { 4 | 5 | private Exceptions() { 6 | } 7 | 8 | public static Throwable rootCauseOf(final Throwable t) { 9 | if (t.getCause() != null && t.getCause() != t) { 10 | return rootCauseOf(t.getCause()); 11 | } 12 | return t; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/OS.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import java.util.Locale; 4 | 5 | public final class OS { 6 | 7 | private static final String OPERATING_SYSTEM = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 8 | 9 | private OS() { 10 | } 11 | 12 | public static boolean isWindows() { 13 | return OPERATING_SYSTEM.contains("win"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/FileTypes.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import com.intellij.ide.highlighter.JavaFileType; 4 | import com.intellij.openapi.fileTypes.FileType; 5 | 6 | public final class FileTypes { 7 | 8 | private FileTypes() { 9 | } 10 | 11 | public static boolean isJava(final FileType fileType) { 12 | return JavaFileType.INSTANCE.equals(fileType); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/entities/HasChecker.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.entities; 2 | 3 | import com.puppycrawl.tools.checkstyle.Checker; 4 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 5 | 6 | import java.util.concurrent.locks.Lock; 7 | 8 | 9 | public interface HasChecker extends CheckstyleInternalObject { 10 | 11 | Checker getChecker(); 12 | 13 | Lock getCheckerLock(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Java 17 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: 17 20 | distribution: liberica 21 | 22 | - name: Build 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/util/copylibs-readme.template.txt: -------------------------------------------------------------------------------- 1 | This folder contains libraries copied from the "{0}" project. 2 | It is managed by the {1} IDE plugin. 3 | Do not modify this folder while the IDE is running. 4 | When the IDE is stopped, you may delete this folder at any time. It will be recreated as needed. 5 | In order to prevent the {1} IDE plugin from creating this folder, 6 | uncheck the "Copy libraries from project directory" option in the {1} settings dialog. 7 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/exception/CheckStylePluginParseException.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class CheckStylePluginParseException extends CheckStylePluginException { 6 | @Serial 7 | private static final long serialVersionUID = -2138216104879079892L; 8 | 9 | public CheckStylePluginParseException(final String message, final Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/entities/CsConfigObject.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.entities; 2 | 3 | import com.puppycrawl.tools.checkstyle.api.Configuration; 4 | 5 | 6 | public class CsConfigObject implements HasCsConfig { 7 | 8 | private final Configuration configuration; 9 | 10 | public CsConfigObject(final Configuration configuration) { 11 | this.configuration = configuration; 12 | } 13 | 14 | public Configuration getConfiguration() { 15 | return configuration; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/Objects.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | public final class Objects { 4 | 5 | private Objects() { 6 | } 7 | 8 | public static > int compare(final T obj1, final T obj2) { 9 | if (obj1 == null && obj2 == null) { 10 | return 0; 11 | } else if (obj1 == null) { 12 | return -1; 13 | } else if (obj2 == null) { 14 | return 1; 15 | } 16 | return obj1.compareTo(obj2); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/dtd/configuration_1_0.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/FileGroupTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | class FileGroupTreeInfo extends GroupTreeInfo { 6 | 7 | /** 8 | * Construct a file node. 9 | * 10 | * @param fileName the name of the file. 11 | * @param problemCount the number of problems in the file. 12 | */ 13 | FileGroupTreeInfo(final String fileName, final int problemCount) { 14 | super(fileName, "file", AllIcons.FileTypes.Java, problemCount); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/DisplayFormats.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | public final class DisplayFormats { 4 | 5 | private DisplayFormats() { 6 | } 7 | 8 | public static String shortenClassName(final String className) { 9 | final int lastPackageIndex = className.lastIndexOf("."); 10 | if (lastPackageIndex >= 0) { 11 | return className 12 | .substring(lastPackageIndex + 1) 13 | .replaceFirst("Check$", ""); 14 | } 15 | return className; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/dtd/suppressions_1_0.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/Close.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 7 | 8 | /** 9 | * Action to close the tool window. 10 | */ 11 | public class Close extends BaseAction { 12 | 13 | @Override 14 | public void actionPerformed(final @NotNull AnActionEvent event) { 15 | project(event).ifPresent(project -> toolWindow(project).hide(null)); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/PackageGroupTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | class PackageGroupTreeInfo extends GroupTreeInfo { 6 | 7 | /** 8 | * Construct a package node. 9 | * 10 | * @param packageName the name of the package. 11 | * @param problemCount the number of problems in the file. 12 | */ 13 | PackageGroupTreeInfo(final String packageName, final int problemCount) { 14 | super(packageName, "package", AllIcons.Nodes.Package, problemCount); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/dtd/suppressions_1_2.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/ScannerListener.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import com.intellij.psi.PsiFile; 4 | import org.infernus.idea.checkstyle.exception.CheckStylePluginException; 5 | import org.infernus.idea.checkstyle.model.ScanResult; 6 | 7 | import java.util.List; 8 | 9 | public interface ScannerListener { 10 | 11 | void scanStarting(List filesToScan); 12 | 13 | void filesScanned(int count); 14 | 15 | void scanCompletedSuccessfully(List scanResults); 16 | 17 | void scanFailedWithError(CheckStylePluginException error); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/exception/CheckStylePluginException.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.exception; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | /** 7 | * Common exception thrown anywhere in this plugin. 8 | */ 9 | public class CheckStylePluginException extends RuntimeException { 10 | 11 | @Serial 12 | private static final long serialVersionUID = 2L; 13 | 14 | public CheckStylePluginException(final String message) { 15 | super(message); 16 | } 17 | 18 | public CheckStylePluginException(final String message, final Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/dtd/suppressions_1_1.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /src/test/resources/org/infernus/idea/checkstyle/config-ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/exception/CheckstyleToolException.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.exception; 2 | 3 | /** 4 | * Wrapper for an exception that occurred in the Checkstyle tool itself. 5 | *

Important: Be sure to throw it only from the 'csaccess' sourceset!

6 | */ 7 | public class CheckstyleToolException extends CheckstyleServiceException { 8 | 9 | /** 10 | * Constructor which copies the given cause's message into this instance. 11 | * 12 | * @param pCause the cause 13 | */ 14 | public CheckstyleToolException(final Throwable pCause) { 15 | super(pCause.getMessage(), pCause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/csaccessTest/resources/org/infernus/idea/checkstyle/service/cmd/config-ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/SuppressForCheckstyleFix.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import com.intellij.codeInsight.daemon.impl.actions.SuppressFix; 4 | import org.infernus.idea.checkstyle.CheckStyleBundle; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class SuppressForCheckstyleFix extends SuppressFix { 8 | SuppressForCheckstyleFix(@NotNull final String sourceCheckName) { 9 | super("checkstyle:" + sourceCheckName); 10 | } 11 | 12 | @NotNull 13 | @Override 14 | public String getText() { 15 | return CheckStyleBundle.message("inspection.fix.suppress-for-checkstyle"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/ScanResult.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.psi.PsiFile; 5 | import org.infernus.idea.checkstyle.checker.ConfigurationLocationResult; 6 | import org.infernus.idea.checkstyle.checker.Problem; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public record ScanResult(ConfigurationLocationResult configurationLocationResult, 13 | Module module, 14 | Map> problems) { 15 | 16 | public static final ScanResult EMPTY = new ScanResult(null, null, Collections.emptyMap()); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/CheckstyleModuleConfigurationEditorProvider.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.openapi.module.ModuleConfigurationEditor; 4 | import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationEditorProvider; 5 | import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationState; 6 | 7 | public class CheckstyleModuleConfigurationEditorProvider implements ModuleConfigurationEditorProvider { 8 | @Override 9 | public ModuleConfigurationEditor[] createEditors(final ModuleConfigurationState state) { 10 | return new ModuleConfigurationEditor[] {new CheckStyleModuleConfigurationEditor(state.getCurrentRootModel().getModule())}; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/exception/CheckstyleServiceException.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.exception; 2 | 3 | /** 4 | * An exception that originates with the Checkstyle access layer (aka Checkstyle plugin service), but is not 5 | * a native CheckstyleException. 6 | *

Important: Be sure to throw it only from the 'csaccess' sourceset!

7 | */ 8 | public class CheckstyleServiceException extends CheckStylePluginException { 9 | 10 | public CheckstyleServiceException(final String message) { 11 | super(message); 12 | } 13 | 14 | public CheckstyleServiceException(final String message, final Throwable cause) { 15 | super(message, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/CheckStyleBundle.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.AbstractBundle; 4 | import org.jetbrains.annotations.NonNls; 5 | import org.jetbrains.annotations.PropertyKey; 6 | 7 | public final class CheckStyleBundle extends AbstractBundle { 8 | 9 | @NonNls 10 | private static final String BUNDLE = "org.infernus.idea.checkstyle.CheckStyleBundle"; 11 | private static final CheckStyleBundle INSTANCE = new CheckStyleBundle(); 12 | 13 | private CheckStyleBundle() { 14 | super(BUNDLE); 15 | } 16 | 17 | public static String message(@PropertyKey(resourceBundle = BUNDLE) final String key, final Object... params) { 18 | return INSTANCE.getMessage(key, params); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ConfigurationLocationGroupTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | class ConfigurationLocationGroupTreeInfo extends GroupTreeInfo { 6 | 7 | /** 8 | * Construct a configuration location node. 9 | * 10 | * @param configurationLocationDescription the name of the configuration location. 11 | * @param problemCount the number of problems in the file. 12 | */ 13 | ConfigurationLocationGroupTreeInfo(final String configurationLocationDescription, final int problemCount) { 14 | super(configurationLocationDescription, "configuration-location", AllIcons.FileTypes.Properties, problemCount); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/JumpToSource.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 8 | 9 | public class JumpToSource extends BaseAction { 10 | 11 | @Override 12 | public void actionPerformed(@NotNull final AnActionEvent event) { 13 | project(event).ifPresent(project -> { 14 | if (isFocusInToolWindow(project)) { 15 | actOnToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::jumpToSource); 16 | } 17 | }); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/NextResult.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 8 | 9 | public class NextResult extends BaseAction { 10 | 11 | @Override 12 | public void actionPerformed(@NotNull final AnActionEvent event) { 13 | project(event).ifPresent(project -> { 14 | if (isFocusInToolWindow(project)) { 15 | actOnToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::selectNextResult); 16 | } 17 | }); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/images/checkstyle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The Checkstyle Service Layer, or just the "service layer". The Checkstyle tool itself, which means the classes in 3 | * {@code com.puppycrawl}, are available in this package and its subpackages only. All classes of the service layer 4 | * are loaded by a custom classloader which is discarded and rebuilt whenever the user of the Checkstyle-IDEA plugin 5 | * selects a new Checkstyle version. This is required so that the plugin can support multiple Checkstyle versions. 6 | *

This layer must be kept as thin as absolutely possible. Test coverage must be very high in order to make sure 7 | * that breaking changes in the Checkstyle API are detected and handled.

8 | */ 9 | package org.infernus.idea.checkstyle.service; 10 | -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/images/checkstyle_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/PreviousResult.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 8 | 9 | public class PreviousResult extends BaseAction { 10 | 11 | @Override 12 | public void actionPerformed(@NotNull final AnActionEvent event) { 13 | project(event).ifPresent(project -> { 14 | if (isFocusInToolWindow(project)) { 15 | actOnToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::selectPreviousResult); 16 | } 17 | }); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/cmd/CheckstyleCommand.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * Describes commands of the Checkstyle access layer. 9 | * 10 | * @param result type 11 | */ 12 | public interface CheckstyleCommand { 13 | /** 14 | * Execute the command. 15 | * 16 | * @param project the IntelliJ project in whose context we are executing 17 | * @return the result of the command 18 | * @throws CheckstyleException if an exception was thrown from the Checkstyle tool 19 | */ 20 | R execute(@NotNull Project project) throws CheckstyleException; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/ui/CompletePanel.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.ui; 2 | 3 | import com.intellij.util.ui.JBUI; 4 | import org.infernus.idea.checkstyle.CheckStyleBundle; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | 9 | public class CompletePanel extends JPanel { 10 | 11 | public CompletePanel() { 12 | super(new GridBagLayout()); 13 | 14 | initialise(); 15 | } 16 | 17 | private void initialise() { 18 | final JLabel infoLabel = new JLabel(CheckStyleBundle.message("config.file.complete.text")); 19 | 20 | setBorder(JBUI.Borders.empty(4)); 21 | 22 | add(infoLabel, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, 23 | GridBagConstraints.NONE, JBUI.insets(8), 0, 0)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/handlers/ScanFilesBeforeCheckinHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.handlers; 2 | 3 | import com.intellij.openapi.vcs.CheckinProjectPanel; 4 | import com.intellij.openapi.vcs.changes.CommitContext; 5 | import com.intellij.openapi.vcs.checkin.CheckinHandler; 6 | import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ScanFilesBeforeCheckinHandlerFactory extends CheckinHandlerFactory { 10 | 11 | @NotNull 12 | @Override 13 | public CheckinHandler createHandler(@NotNull final CheckinProjectPanel checkinProjectPanel, 14 | @NotNull final CommitContext commitContext) { 15 | return new ScanFilesBeforeCheckinHandler(checkinProjectPanel); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/SimpleResolver.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import java.util.Map; 4 | 5 | import com.puppycrawl.tools.checkstyle.PropertyResolver; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | 10 | public class SimpleResolver implements PropertyResolver { 11 | 12 | private final Map properties; 13 | 14 | public SimpleResolver(@NotNull final Map properties) { 15 | this.properties = properties; 16 | } 17 | 18 | @Override 19 | @Nullable 20 | public String resolve(@Nullable final String name) { 21 | String result = null; 22 | if (name != null) { 23 | result = properties.get(name); 24 | } 25 | return result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ExpandAll.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.actOnToolWindowPanel; 8 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 9 | 10 | /** 11 | * Action to expand all nodes in the results window. 12 | */ 13 | public class ExpandAll extends BaseAction { 14 | 15 | @Override 16 | public void actionPerformed(final @NotNull AnActionEvent event) { 17 | project(event).ifPresent(project -> actOnToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::expandTree)); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/ProjectPaths.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.project.ProjectUtil; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class ProjectPaths { 11 | 12 | @Nullable 13 | public VirtualFile projectPath(@NotNull final Project project) { 14 | // workaround to allow testing with Jetbrain's love of static shite 15 | return ProjectUtil.guessProjectDir(project); 16 | } 17 | 18 | @Nullable 19 | public VirtualFile modulePath(@NotNull final Module module) { 20 | return ProjectUtil.guessModuleDir(module); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/CollapseAll.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.actOnToolWindowPanel; 8 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 9 | 10 | /** 11 | * Action to collapse all nodes in the results window. 12 | */ 13 | public class CollapseAll extends BaseAction { 14 | 15 | @Override 16 | public void actionPerformed(final @NotNull AnActionEvent event) { 17 | project(event).ifPresent(project -> actOnToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::collapseTree)); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/images/checkstyle@20x20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/infernus/idea/checkstyle/images/checkstyle@20x20_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanAllGivenFilesTask.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | class ScanAllGivenFilesTask extends ScanAllFilesTask { 9 | private final VirtualFile[] filesToScan; 10 | 11 | ScanAllGivenFilesTask(@NotNull final Project project, 12 | @NotNull final VirtualFile[] filesToScan, 13 | final ConfigurationLocation selectedOverride) { 14 | super(project, selectedOverride); 15 | this.filesToScan = filesToScan; 16 | } 17 | 18 | @Override 19 | protected VirtualFile[] files() { 20 | return filesToScan; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/dtd/suppressions_1_1_xpath_experimental.dtd: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/ConfigurationLocationResult.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public record ConfigurationLocationResult(ConfigurationLocation location, ConfigurationLocationStatus status) { 8 | 9 | public static final ConfigurationLocationResult NOT_PRESENT = new ConfigurationLocationResult(null, ConfigurationLocationStatus.NOT_PRESENT); 10 | 11 | public static ConfigurationLocationResult of(@Nullable final ConfigurationLocation configurationLocation, 12 | @NotNull final ConfigurationLocationStatus status) { 13 | return new ConfigurationLocationResult(configurationLocation, status); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanAllFilesInProjectTask.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.roots.ProjectRootManager; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ScanAllFilesInProjectTask extends ScanAllFilesTask { 10 | 11 | private final Project project; 12 | 13 | ScanAllFilesInProjectTask(@NotNull final Project project, 14 | final ConfigurationLocation selectedOverride) { 15 | super(project, selectedOverride); 16 | this.project = project; 17 | } 18 | @Override 19 | protected VirtualFile[] files() { 20 | return ProjectRootManager.getInstance(project).getContentRoots(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanAllFilesInModuleTask.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.openapi.roots.ModuleRootManager; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ScanAllFilesInModuleTask extends ScanAllFilesTask { 10 | 11 | private final Module module; 12 | 13 | ScanAllFilesInModuleTask(@NotNull final Module module, 14 | final ConfigurationLocation selectedOverride) { 15 | super(module.getProject(), selectedOverride); 16 | this.module = module; 17 | } 18 | 19 | @Override 20 | protected VirtualFile[] files() { 21 | return ModuleRootManager.getInstance(module).getContentRoots(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/FileUtil.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.junit.Assert; 5 | 6 | import java.io.IOException; 7 | import java.net.URISyntaxException; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | 12 | 13 | public final class FileUtil { 14 | 15 | private FileUtil() { 16 | super(); 17 | } 18 | 19 | 20 | public static String readFile(@NotNull final String filename) throws IOException, URISyntaxException { 21 | URL url = FileUtil.class.getResource(filename); 22 | if (url == null) { 23 | url = Thread.currentThread().getContextClassLoader().getResource(filename); 24 | } 25 | Assert.assertNotNull("File not found: " + filename, url); 26 | return Files.readString(Paths.get(url.toURI())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/dtd/suppressions_1_2_xpath_experimental.dtd: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/Issue.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | 4 | /** 5 | * An issue as reported by the Checkstyle tool. 6 | */ 7 | public class Issue { 8 | 9 | public final String fileName; 10 | public final int lineNumber; 11 | public final int columnNumber; 12 | public final String message; 13 | public final SeverityLevel severityLevel; 14 | public final String sourceName; 15 | 16 | public Issue(final String fileName, 17 | final int lineNumber, 18 | final int columnNumber, 19 | final String message, 20 | final SeverityLevel severityLevel, 21 | final String sourceName) { 22 | this.fileName = fileName; 23 | this.lineNumber = lineNumber; 24 | this.columnNumber = columnNumber; 25 | this.message = message; 26 | this.severityLevel = severityLevel; 27 | this.sourceName = sourceName; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/CheckStylePlugin.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.ide.plugins.IdeaPluginDescriptor; 4 | import com.intellij.ide.plugins.PluginManagerCore; 5 | import com.intellij.openapi.extensions.PluginId; 6 | 7 | public final class CheckStylePlugin { 8 | 9 | private CheckStylePlugin() { 10 | } 11 | 12 | /** 13 | * The plugin ID. Caution: It must be identical to the String set in build.gradle at intellij.pluginName 14 | */ 15 | public static final String ID_PLUGIN = "CheckStyle-IDEA"; 16 | 17 | public static String version() { 18 | try { 19 | final IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId(ID_PLUGIN)); 20 | if (plugin != null) { 21 | return plugin.getVersion(); 22 | } 23 | } catch (Throwable ignored) { 24 | // thrown if plugin isn't initialised, e.g. during tests 25 | } 26 | return "unknown"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/SeverityGroupTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import org.infernus.idea.checkstyle.csapi.SeverityLevel; 5 | 6 | import javax.swing.*; 7 | 8 | class SeverityGroupTreeInfo extends GroupTreeInfo { 9 | 10 | /** 11 | * Construct a severity node. 12 | * 13 | * @param severityLevel the severity level. 14 | * @param problemCount the number of problems at this severity. 15 | */ 16 | SeverityGroupTreeInfo(final SeverityLevel severityLevel, final int problemCount) { 17 | super(severityLevel.name(), "file", iconForSeverity(severityLevel), problemCount); 18 | } 19 | 20 | private static Icon iconForSeverity(final SeverityLevel severityLevel) { 21 | return switch (severityLevel) { 22 | case Error -> AllIcons.General.Error; 23 | case Warning -> AllIcons.General.Warning; 24 | default -> AllIcons.General.Information; 25 | }; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/entities/CheckerWithConfig.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.entities; 2 | 3 | import com.puppycrawl.tools.checkstyle.Checker; 4 | import com.puppycrawl.tools.checkstyle.api.Configuration; 5 | 6 | import java.util.concurrent.locks.Lock; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | 9 | 10 | public class CheckerWithConfig implements HasChecker, HasCsConfig { 11 | 12 | private final Checker checker; 13 | private final Lock lock = new ReentrantLock(); 14 | private final Configuration configuration; 15 | 16 | public CheckerWithConfig(final Checker checker, final Configuration configuration) { 17 | this.checker = checker; 18 | this.configuration = configuration; 19 | } 20 | 21 | @Override 22 | public Checker getChecker() { 23 | return checker; 24 | } 25 | 26 | @Override 27 | public Lock getCheckerLock() { 28 | return lock; 29 | } 30 | 31 | public Configuration getConfiguration() { 32 | return configuration; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/Streams.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.*; 6 | 7 | public final class Streams { 8 | 9 | private static final int BUFFER_SIZE = 4096; 10 | 11 | private Streams() { 12 | 13 | } 14 | 15 | public static InputStream inMemoryCopyOf(@NotNull final InputStream sourceStream) throws IOException { 16 | try (BufferedInputStream bis = new BufferedInputStream(sourceStream)) { 17 | return new ByteArrayInputStream(readContentOf(bis)); 18 | } 19 | } 20 | 21 | public static byte[] readContentOf(final InputStream source) throws IOException { 22 | final ByteArrayOutputStream destination = new ByteArrayOutputStream(); 23 | final byte[] readBuffer = new byte[BUFFER_SIZE]; 24 | int count; 25 | while ((count = source.read(readBuffer, 0, BUFFER_SIZE)) != -1) { 26 | destination.write(readBuffer, 0, count); 27 | } 28 | return destination.toByteArray(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/CheckstyleInternalObject.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | 4 | /** 5 | * Objects which implement this interface are keepers of objects internal to the Checkstyle tool, so from within the 6 | * normal plugin code we cannot know what they are. They are to be used from within the 'csaccess' source set only. 7 | * In this way, we can store objects from the Checkstyle tool itself in other parts of the plugin, although we 8 | * cannot use these objects. 9 | *

It is important to make sure that these objects do not outlive the classloader that loaded them (at least not by 10 | * much). When the Checkstyle version is changed in the configuration, all objects which implement this interface 11 | * must be discarded. The new classloader with the new Checkstyle version would not be able to use them and 12 | * {@link org.infernus.idea.checkstyle.exception.CheckstyleVersionMixException CheckstyleVersionMixException}s would 13 | * result.

14 | */ 15 | public interface CheckstyleInternalObject { 16 | // tagging interface 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checks/Check.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checks; 2 | 3 | import com.intellij.psi.PsiFile; 4 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * Allows extra logic for a certain Check. 9 | *

10 | * Not a lot of extra logic at present, but it's a start. 11 | */ 12 | public interface Check { 13 | 14 | String getShortName(); 15 | 16 | // TODO This may not be enough, because Checkstyle accepts more forms (e.g. FQCN without "Check" postfix). 17 | String getFullyQualifiedName(); 18 | 19 | /** 20 | * Configure the check given the CheckStyle configuration. 21 | * 22 | * @param config the configuration. 23 | */ 24 | void configure(@NotNull CheckstyleInternalObject config); 25 | 26 | /** 27 | * Process a file. 28 | * 29 | * @param file the file 30 | * @param pEventSourceName sourceName of the audit event 31 | * @return true to continue processing, false to cancel 32 | */ 33 | boolean process(@NotNull PsiFile file, @NotNull String pEventSourceName); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/NoWhitespaceBeforeImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.psi.codeStyle.CodeStyleSettings; 4 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 5 | import org.infernus.idea.checkstyle.csapi.KnownTokenTypes; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @SuppressWarnings("unused") 10 | public class NoWhitespaceBeforeImporter extends ModuleImporter { 11 | 12 | @Override 13 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 14 | // nothing to do 15 | } 16 | 17 | 18 | @Override 19 | public void importTo(@NotNull final CodeStyleSettings settings) { 20 | CommonCodeStyleSettings commonSettings = getCommonSettings(settings); 21 | if (appliesTo(KnownTokenTypes.COMMA)) { 22 | commonSettings.SPACE_BEFORE_COMMA = false; 23 | } 24 | if (appliesTo(KnownTokenTypes.SEMI)) { 25 | commonSettings.SPACE_BEFORE_SEMICOLON = false; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/TestHelper.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.packageDependencies.DependencyValidationManager; 5 | import com.intellij.psi.search.scope.packageSet.NamedScopeManager; 6 | import org.infernus.idea.checkstyle.checker.CheckerFactoryCache; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.when; 11 | 12 | public final class TestHelper { 13 | 14 | private TestHelper() { 15 | } 16 | 17 | @NotNull 18 | public static Project mockProject() { 19 | final Project project = mock(Project.class); 20 | final CheckerFactoryCache checkerFactoryCache = new CheckerFactoryCache(); 21 | when(project.getService(CheckerFactoryCache.class)).thenReturn(checkerFactoryCache); 22 | when(project.getService(NamedScopeManager.class)).thenReturn(new NamedScopeManager(project)); 23 | when(project.getService(DependencyValidationManager.class)).thenReturn(mock(DependencyValidationManager.class)); 24 | return project; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/csaccessTest/resources/org/infernus/idea/checkstyle/service/config-file-contents-holder.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/dtd/configuration_1_1.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 32 | 33 | 34 | 38 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/CachedChecker.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Value for checker cache. 7 | */ 8 | class CachedChecker { 9 | 10 | /** 11 | * We cache purely to ignore repeated requests in a multi-file scan. Hence we'll treat the cached 12 | * value as valid for time in ms. 13 | */ 14 | private static final int CACHE_VALID_TIME = 60000; 15 | 16 | private final CheckStyleChecker checkStyleChecker; 17 | 18 | private long timeStamp; 19 | 20 | CachedChecker(@NotNull final CheckStyleChecker checkStyleChecker) { 21 | this.checkStyleChecker = checkStyleChecker; 22 | this.timeStamp = System.currentTimeMillis(); 23 | } 24 | 25 | public CheckStyleChecker getCheckStyleChecker() { 26 | this.timeStamp = System.currentTimeMillis(); 27 | return checkStyleChecker; 28 | } 29 | 30 | private long getTimeStamp() { 31 | return timeStamp; 32 | } 33 | 34 | public boolean isValid() { 35 | return (getTimeStamp() + CACHE_VALID_TIME) >= System.currentTimeMillis(); 36 | } 37 | 38 | public void destroy() { 39 | checkStyleChecker.destroy(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/dtd/configuration_1_2.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 33 | 34 | 35 | 39 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/CheckStyleToolWindowFactory.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.openapi.project.DumbAware; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.wm.ToolWindow; 6 | import com.intellij.openapi.wm.ToolWindowFactory; 7 | import com.intellij.openapi.wm.ToolWindowType; 8 | import com.intellij.ui.content.Content; 9 | import org.infernus.idea.checkstyle.CheckStyleBundle; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class CheckStyleToolWindowFactory implements ToolWindowFactory, DumbAware { 13 | 14 | @Override 15 | public void createToolWindowContent(@NotNull final Project project, @NotNull final ToolWindow toolWindow) { 16 | final Content toolContent = toolWindow.getContentManager().getFactory().createContent( 17 | new CheckStyleToolWindowPanel(toolWindow, project), 18 | CheckStyleBundle.message("plugin.toolwindow.action"), 19 | false); 20 | toolWindow.getContentManager().addContent(toolContent); 21 | 22 | toolWindow.setTitle(CheckStyleBundle.message("plugin.toolwindow.name")); 23 | toolWindow.setType(ToolWindowType.DOCKED, null); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/WhitespaceAfterImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.psi.codeStyle.CodeStyleSettings; 4 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 5 | import org.infernus.idea.checkstyle.csapi.KnownTokenTypes; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @SuppressWarnings("unused") 10 | public class WhitespaceAfterImporter extends ModuleImporter { 11 | 12 | @Override 13 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 14 | // nothing to do 15 | } 16 | 17 | @Override 18 | public void importTo(@NotNull final CodeStyleSettings settings) { 19 | CommonCodeStyleSettings commonSettings = getCommonSettings(settings); 20 | if (appliesTo(KnownTokenTypes.COMMA)) { 21 | commonSettings.SPACE_AFTER_COMMA = true; 22 | } 23 | if (appliesTo(KnownTokenTypes.SEMI)) { 24 | commonSettings.SPACE_AFTER_SEMICOLON = true; 25 | } 26 | if (appliesTo(KnownTokenTypes.TYPECAST)) { 27 | commonSettings.SPACE_AFTER_TYPE_CAST = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/SimpleResolverTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static java.util.Collections.emptyMap; 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertNull; 12 | 13 | 14 | public class SimpleResolverTest { 15 | private Map props; 16 | 17 | @Before 18 | public void beforeTest() { 19 | props = new HashMap<>(); 20 | props.put("key1", "value1"); 21 | props.put("key2", "value2"); 22 | } 23 | 24 | @Test 25 | public void testResolve1() { 26 | assertEquals("value1", new SimpleResolver(props).resolve("key1")); 27 | assertEquals("value2", new SimpleResolver(props).resolve("key2")); 28 | } 29 | 30 | @Test 31 | public void testNotFound() { 32 | assertNull(new SimpleResolver(props).resolve("unknownKey")); 33 | assertNull(new SimpleResolver(props).resolve(null)); 34 | } 35 | 36 | @Test 37 | public void testNoProps() { 38 | assertNull(new SimpleResolver(emptyMap()).resolve("key1")); 39 | assertNull(new SimpleResolver(emptyMap()).resolve(null)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Build info 16 | id: build_info 17 | run: | 18 | BUILD_TAG=${GITHUB_REF#refs/tags/} 19 | cat ./CHANGELOG.md | grep "$BUILD_TAG" | sed 's%^\*[[:space:]]*\*\*[0-9.]*\*\*[[:space:]]*%%g' > ${{ github.workspace }}-CHANGELOG.txt 20 | echo "build_tag=${BUILD_TAG}" >> $GITHUB_OUTPUT 21 | 22 | - name: Set up Java 17 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: 17 26 | distribution: liberica 27 | 28 | - name: Build 29 | run: ./gradlew build buildPlugin 30 | 31 | - name: Release 32 | uses: softprops/action-gh-release@v2 33 | with: 34 | body_path: ${{ github.workspace }}-CHANGELOG.txt 35 | fail_on_unmatched_files: true 36 | draft: false 37 | prerelease: false 38 | files: 39 | build/distributions/checkstyle-idea-${{ steps.build_info.outputs.build_tag }}.zip 40 | 41 | - name: Publish to Jetbrains Plugin Repo 42 | run: ./gradlew publishPlugin 43 | env: 44 | JETBRAINS_PLUGIN_REPO_TOKEN: ${{ secrets.JETBRAINS_PLUGIN_REPO_TOKEN }} 45 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/KnownTokenTypes.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | /** 4 | * TokenTypes which we use in this plugin somehow, a subset of the TokenTypes defined by Checkstyle. 5 | */ 6 | //@formatter:off 7 | public enum KnownTokenTypes { 8 | ASSIGN, 9 | BAND, 10 | BAND_ASSIGN, 11 | BOR, 12 | BOR_ASSIGN, 13 | BSR, 14 | BSR_ASSIGN, 15 | BXOR, 16 | BXOR_ASSIGN, 17 | CLASS_DEF, 18 | COLON, 19 | COMMA, 20 | CTOR_DEF, 21 | DIV, 22 | DIV_ASSIGN, 23 | DO_WHILE, 24 | EQUAL, 25 | GE, 26 | GT, 27 | IMPORT, 28 | INTERFACE_DEF, 29 | LAND, 30 | LCURLY, 31 | LE, 32 | LITERAL_CASE, 33 | LITERAL_CATCH, 34 | LITERAL_DEFAULT, 35 | LITERAL_DO, 36 | LITERAL_ELSE, 37 | LITERAL_FINALLY, 38 | LITERAL_FOR, 39 | LITERAL_IF, 40 | LITERAL_SWITCH, 41 | LITERAL_SYNCHRONIZED, 42 | LITERAL_TRY, 43 | LITERAL_WHILE, 44 | LOR, 45 | LT, 46 | METHOD_DEF, 47 | MINUS, 48 | MINUS_ASSIGN, 49 | MOD, 50 | MOD_ASSIGN, 51 | NOT_EQUAL, 52 | PACKAGE_DEF, 53 | PLUS, 54 | PLUS_ASSIGN, 55 | QUESTION, 56 | SEMI, 57 | SL, 58 | SL_ASSIGN, 59 | SR, 60 | SR_ASSIGN, 61 | STAR, 62 | STAR_ASSIGN, 63 | TYPECAST, 64 | VARIABLE_DEF 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/startup/DisableCheckstyleLogging.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.startup; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.startup.ProjectActivity; 6 | import kotlin.Unit; 7 | import kotlin.coroutines.Continuation; 8 | import org.apache.log4j.Level; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public class DisableCheckstyleLogging implements ProjectActivity { 13 | 14 | private static final Logger LOG = Logger.getInstance(DisableCheckstyleLogging.class); 15 | 16 | @Nullable 17 | @Override 18 | public Object execute(@NotNull final Project project, 19 | @NotNull final Continuation continuation) { 20 | try { 21 | // This is a nasty hack to get around IDEA's DialogAppender sending any errors to the Event Log, 22 | // which would result in CheckStyle parse errors spamming the Event Log. 23 | org.apache.log4j.Logger.getLogger("com.puppycrawl.tools.checkstyle.TreeWalker").setLevel(Level.OFF); 24 | } catch (Exception e) { 25 | LOG.warn("Unable to suppress logging from CheckStyle's TreeWalker", e); 26 | } 27 | 28 | return null; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/cmd/CheckstyleBridge.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | import com.puppycrawl.tools.checkstyle.api.Configuration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.Map; 9 | 10 | public final class CheckstyleBridge { 11 | 12 | private CheckstyleBridge() { 13 | } 14 | 15 | @SuppressWarnings("unchecked") 16 | public static Map messagesFrom(@NotNull final Configuration source) { 17 | // Checkstyle 8.7 changes the return type of Configuration.getMessages() 18 | Method getMessagesMethod = null; 19 | try { 20 | getMessagesMethod = Configuration.class.getDeclaredMethod("getMessages"); 21 | } catch (NoSuchMethodException ignored) { 22 | } 23 | 24 | if (getMessagesMethod == null) { 25 | throw new RuntimeException("Unable to find usable getMessages method on configuration"); 26 | } 27 | 28 | try { 29 | return (Map) getMessagesMethod.invoke(source); 30 | } catch (IllegalAccessException | InvocationTargetException e) { 31 | throw new RuntimeException("Unable to invoke getMessages method on configuration", e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/AnalyseCurrentFile.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.Presentation; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import static com.intellij.openapi.actionSystem.CommonDataKeys.VIRTUAL_FILE; 11 | 12 | /** 13 | * Action to execute a CheckStyle scan on the current editor file. 14 | */ 15 | public class AnalyseCurrentFile extends ScanCurrentFile { 16 | 17 | @Override 18 | protected @Nullable VirtualFile selectedFile(@NotNull final Project project, 19 | @NotNull final AnActionEvent event) { 20 | return event.getDataContext().getData(VIRTUAL_FILE); 21 | } 22 | 23 | @Override 24 | public void update(@NotNull final AnActionEvent event) { 25 | // update event does not expose selected file, so we always enable 26 | final Presentation presentation = event.getPresentation(); 27 | project(event).ifPresentOrElse( 28 | project -> presentation.setEnabled(!staticScanner(project).isScanInProgress()), 29 | () -> presentation.setEnabled(false)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/cmd/OpScanTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | import java.util.Collections; 4 | import java.util.Optional; 5 | 6 | import com.intellij.openapi.project.Project; 7 | import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 8 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 9 | import org.infernus.idea.checkstyle.exception.CheckstyleVersionMixException; 10 | import org.infernus.idea.checkstyle.service.entities.CheckerWithConfig; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | import org.mockito.Mockito; 14 | 15 | 16 | public class OpScanTest { 17 | private static final Project PROJECT = Mockito.mock(Project.class); 18 | 19 | private static final class InvalidObject 20 | implements CheckstyleInternalObject { 21 | // does not matter 22 | } 23 | 24 | 25 | @Test(expected = CheckstyleVersionMixException.class) 26 | public void testWrongCheckerClass() { 27 | new OpScan(new InvalidObject(), Collections.emptyList(), false, 2, Optional.empty()); 28 | } 29 | 30 | 31 | @Test 32 | public void testEmptyListOfFiles() throws CheckstyleException { 33 | OpScan cmd = new OpScan(new CheckerWithConfig(null, null), Collections.emptyList(), false, 2, Optional.empty()); 34 | Assert.assertEquals(Collections.emptyMap(), cmd.execute(PROJECT)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ResetLoadedRulesFiles.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import com.intellij.openapi.editor.Document; 6 | import com.intellij.openapi.fileEditor.FileDocumentManager; 7 | import org.infernus.idea.checkstyle.ConfigurationInvalidator; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * Clear the Checker cache and blocks, forcing reloading of rules files. 12 | */ 13 | public class ResetLoadedRulesFiles extends BaseAction { 14 | 15 | @Override 16 | public void actionPerformed(@NotNull final AnActionEvent event) { 17 | if (FileDocumentManager.getInstance().getUnsavedDocuments().length > 0) { 18 | // If there is an unsaved file, this could be the changed rules file or a dependent file. 19 | // Save them first, then continue. 20 | ApplicationManager.getApplication().runWriteAction(() -> { 21 | for (Document unsavedDocument : FileDocumentManager.getInstance().getUnsavedDocuments()) { 22 | FileDocumentManager.getInstance().saveDocument(unsavedDocument); 23 | } 24 | }); 25 | } 26 | 27 | project(event).ifPresent(project -> project.getService(ConfigurationInvalidator.class).invalidateCachedResources()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/ConfigurationType.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | 4 | public enum ConfigurationType { 5 | /** 6 | * one of the configurations bundled with the Checkstyle tool, chosen from the 7 | * {@link org.infernus.idea.checkstyle.csapi.BundledConfig} enum 8 | */ 9 | BUNDLED, 10 | 11 | HTTP_URL, 12 | 13 | /** 14 | * Located on a HTTP URL where the SSL context is naughtily ignored. 15 | */ 16 | INSECURE_HTTP_URL, 17 | 18 | LOCAL_FILE, 19 | 20 | /** 21 | * Located in a local file where the path is project relative. 22 | */ 23 | PROJECT_RELATIVE, 24 | 25 | PLUGIN_CLASSPATH, 26 | 27 | LEGACY_CLASSPATH; // legacy 28 | 29 | 30 | /** 31 | * Parse a case-insensitive type string. 32 | * 33 | * @param typeAsString the type, as a string. 34 | * @return the type. 35 | */ 36 | public static ConfigurationType parse(final String typeAsString) { 37 | if (typeAsString == null) { 38 | return null; 39 | } 40 | 41 | final String processedType = typeAsString.toUpperCase().replace(' ', '_'); 42 | if ("FILE".equals(processedType)) { 43 | return LOCAL_FILE; 44 | } else if ("CLASSPATH".equals(processedType)) { 45 | return LEGACY_CLASSPATH; 46 | } 47 | 48 | return valueOf(processedType); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/ConfigurationBuilder.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import com.puppycrawl.tools.checkstyle.DefaultConfiguration; 4 | import com.puppycrawl.tools.checkstyle.api.Configuration; 5 | 6 | public final class ConfigurationBuilder { 7 | 8 | private final DefaultConfiguration configuration; 9 | 10 | private ConfigurationBuilder(final DefaultConfiguration configuration) { 11 | this.configuration = configuration; 12 | } 13 | 14 | public static ConfigurationBuilder checker() { 15 | return new ConfigurationBuilder(new DefaultConfiguration("Checker")); 16 | } 17 | 18 | public static ConfigurationBuilder config(final String name) { 19 | return new ConfigurationBuilder(new DefaultConfiguration(name)); 20 | } 21 | 22 | public ConfigurationBuilder withChild(final ConfigurationBuilder child) { 23 | configuration.addChild(child.build()); 24 | return this; 25 | } 26 | 27 | public ConfigurationBuilder withAttribute(final String name, final String value) { 28 | configuration.addProperty(name, value); 29 | return this; 30 | } 31 | 32 | public ConfigurationBuilder withMessage(final String key, final String value) { 33 | configuration.addMessage(key, value); 34 | return this; 35 | } 36 | 37 | public Configuration build() { 38 | return configuration; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/RelativeFileConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.util.Strings; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * A configuration file on a mounted file system which will always be referred to 9 | * by a path relative to the project path. 10 | */ 11 | public class RelativeFileConfigurationLocation extends FileConfigurationLocation { 12 | 13 | RelativeFileConfigurationLocation(@NotNull final Project project, 14 | @NotNull final String id) { 15 | super(project, id, ConfigurationType.PROJECT_RELATIVE); 16 | } 17 | 18 | @Override 19 | public boolean canBeResolvedInDefaultProject() { 20 | return false; 21 | } 22 | 23 | @Override 24 | public void setLocation(final String location) { 25 | if (Strings.isBlank(location)) { 26 | throw new IllegalArgumentException("A non-blank location is required"); 27 | } 28 | 29 | super.setLocation(projectFilePaths().tokenise( 30 | projectFilePaths().makeProjectRelative( 31 | projectFilePaths().detokenise(location)))); 32 | } 33 | 34 | @Override 35 | public Object clone() { 36 | return cloneCommonPropertiesTo(new RelativeFileConfigurationLocation(getProject(), getId())); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/StringConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.UUID; 7 | 8 | import com.intellij.openapi.project.Project; 9 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 10 | import org.infernus.idea.checkstyle.model.ConfigurationType; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | 14 | public class StringConfigurationLocation 15 | extends ConfigurationLocation { 16 | private final String configurationXml; 17 | 18 | public StringConfigurationLocation(@NotNull final String configurationXml, 19 | @NotNull final Project project) { 20 | super(UUID.randomUUID().toString(), ConfigurationType.LOCAL_FILE, project); 21 | setDescription("In-memory String-based configuration: " 22 | + configurationXml.substring(0, Math.min(100, configurationXml.length())) + " ..."); 23 | this.configurationXml = configurationXml; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | protected InputStream resolveFile(@NotNull final ClassLoader checkstyleClassLoader) { 29 | return new ByteArrayInputStream(configurationXml.getBytes(StandardCharsets.UTF_8)); 30 | } 31 | 32 | @Override 33 | public StringConfigurationLocation clone() { 34 | return new StringConfigurationLocation(configurationXml, getProject()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/BundledConfigProvider.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * Provider that providers the additional bundled checkstyle configs 10 | */ 11 | public interface BundledConfigProvider { 12 | 13 | /** 14 | * @return the configs 15 | */ 16 | @NotNull 17 | Collection getConfigs(); 18 | 19 | class BasicConfig { 20 | private final String id; 21 | private final String path; 22 | private final String description; 23 | 24 | public BasicConfig(final String id, final String description, final String path) { 25 | this.id = requiredNotBlank(id, "id"); 26 | this.path = requiredNotBlank(path, "path"); 27 | this.description = requiredNotBlank(description, "description"); 28 | } 29 | 30 | static String requiredNotBlank(final String val, final String name) { 31 | if (StringUtils.isBlank(val)) { 32 | throw new IllegalArgumentException(name + " must not be blank"); 33 | } 34 | return val; 35 | } 36 | 37 | public String getId() { 38 | return id; 39 | } 40 | 41 | public String getPath() { 42 | return path; 43 | } 44 | 45 | public String getDescription() { 46 | return description; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/csapi/ConfigurationModule.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | 4 | import java.util.Collections; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | 12 | public class ConfigurationModule { 13 | 14 | private final String name; 15 | 16 | private final Map properties; 17 | 18 | private final Set knownTokenTypes; 19 | 20 | public ConfigurationModule(@NotNull final String name, 21 | @Nullable final Map properties, 22 | @Nullable final Set knownTokenTypes) { 23 | this.name = name; 24 | 25 | if (properties != null) { 26 | this.properties = Collections.unmodifiableMap(properties); 27 | } else { 28 | this.properties = Collections.emptyMap(); 29 | } 30 | 31 | if (knownTokenTypes != null) { 32 | this.knownTokenTypes = Collections.unmodifiableSet(knownTokenTypes); 33 | } else { 34 | this.knownTokenTypes = Collections.emptySet(); 35 | } 36 | } 37 | 38 | 39 | @NotNull 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | @NotNull 45 | public Map getProperties() { 46 | return properties; 47 | } 48 | 49 | @NotNull 50 | public Set getKnownTokenTypes() { 51 | return knownTokenTypes; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | CheckStyle-IDEA 2 | --------------- 3 | (c) Copyright 2006-2022 CheckStyle-IDEA Contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | * Neither the name of the author nor the names of its contributors may 17 | be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/ui/CheckStyleInspectionPanel.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.ui; 2 | 3 | import com.intellij.util.ui.JBUI; 4 | import org.infernus.idea.checkstyle.CheckStyleBundle; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | 9 | /** 10 | * Provides a dummy panel for the inspection configuration. 11 | */ 12 | public final class CheckStyleInspectionPanel extends JPanel { 13 | 14 | /** 15 | * A text label for a description blurb. 16 | */ 17 | private final JTextArea descriptionLabel = new JTextArea(); 18 | 19 | /** 20 | * Create a new panel. 21 | */ 22 | public CheckStyleInspectionPanel() { 23 | super(new GridBagLayout()); 24 | 25 | initialise(); 26 | } 27 | 28 | /** 29 | * Initialise the view. 30 | */ 31 | private void initialise() { 32 | // fake a multi-line label with a text area 33 | descriptionLabel.setText(CheckStyleBundle.message("config.inspection.description")); 34 | descriptionLabel.setEditable(false); 35 | descriptionLabel.setEnabled(false); 36 | descriptionLabel.setWrapStyleWord(true); 37 | descriptionLabel.setLineWrap(true); 38 | descriptionLabel.setOpaque(false); 39 | descriptionLabel.setDisabledTextColor(descriptionLabel.getForeground()); 40 | 41 | final GridBagConstraints descLabelConstraints = new GridBagConstraints( 42 | 0, 0, 3, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, 43 | GridBagConstraints.BOTH, JBUI.insets(4), 0, 0); 44 | add(descriptionLabel, descLabelConstraints); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/StringConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 5 | import org.infernus.idea.checkstyle.model.ConfigurationType; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.InputStream; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.UUID; 12 | 13 | 14 | public class StringConfigurationLocation 15 | extends ConfigurationLocation { 16 | 17 | private static final int MAX_TRIMMED_LENGTH = 100; 18 | 19 | private final String configurationXml; 20 | 21 | public StringConfigurationLocation(@NotNull final String configurationXml, 22 | @NotNull final Project project) { 23 | super(UUID.randomUUID().toString(), ConfigurationType.LOCAL_FILE, project); 24 | setDescription("In-memory String-based configuration: " 25 | + configurationXml.substring(0, Math.min(MAX_TRIMMED_LENGTH, configurationXml.length())) + " ..."); 26 | this.configurationXml = configurationXml; 27 | } 28 | 29 | @NotNull 30 | @Override 31 | protected InputStream resolveFile(@NotNull final ClassLoader checkstyleClassLoader) { 32 | return new ByteArrayInputStream(configurationXml.getBytes(StandardCharsets.UTF_8)); 33 | } 34 | 35 | @Override 36 | public StringConfigurationLocation clone() { 37 | return new StringConfigurationLocation(configurationXml, getProject()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checks/CheckFactory.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checks; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.project.Project; 5 | import org.infernus.idea.checkstyle.CheckstyleProjectService; 6 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.function.Function; 13 | 14 | 15 | /** 16 | * Factory for producing various check modifications. 17 | */ 18 | public final class CheckFactory { 19 | 20 | private static final Logger LOG = Logger.getInstance(CheckFactory.class); 21 | 22 | private static final List> CHECKS = Arrays.asList( 23 | (project) -> new JavadocPackageCheck(project.getService(CheckstyleProjectService.class)), 24 | (project) -> new PackageHtmlCheck()); 25 | 26 | private CheckFactory() { 27 | } 28 | 29 | @NotNull 30 | public static List getChecks(final Project project, final CheckstyleInternalObject config) { 31 | final List checks = new ArrayList<>(); 32 | 33 | for (final Function checkFactory : CHECKS) { 34 | try { 35 | final Check check = checkFactory.apply(project); 36 | check.configure(config); 37 | checks.add(check); 38 | } catch (Exception e) { 39 | LOG.warn("Couldn't instantiate check", e); 40 | } 41 | } 42 | 43 | return checks; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/TestHelper.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.packageDependencies.DependencyValidationManager; 5 | import com.intellij.psi.search.scope.packageSet.InvalidPackageSet; 6 | import com.intellij.psi.search.scope.packageSet.NamedScope; 7 | import com.intellij.psi.search.scope.packageSet.NamedScopeManager; 8 | import org.infernus.idea.checkstyle.checker.CheckerFactoryCache; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import static org.infernus.idea.checkstyle.model.NamedScopeHelper.DEFAULT_SCOPE_ID; 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.when; 14 | 15 | public final class TestHelper { 16 | public static final NamedScope NAMED_SCOPE = new NamedScope("test", new InvalidPackageSet("asdf")); 17 | 18 | private TestHelper() { 19 | } 20 | 21 | @NotNull 22 | public static Project mockProject() { 23 | DependencyValidationManager dependencyValidationManager = mock(DependencyValidationManager.class); 24 | when(dependencyValidationManager.getScope(DEFAULT_SCOPE_ID)).thenReturn(NAMED_SCOPE); 25 | 26 | final CheckerFactoryCache checkerFactoryCache = new CheckerFactoryCache(); 27 | 28 | final Project project = mock(Project.class); 29 | when(project.getService(CheckerFactoryCache.class)).thenReturn(checkerFactoryCache); 30 | when(project.getService(NamedScopeManager.class)).thenReturn(new NamedScopeManager(project)); 31 | when(project.getService(DependencyValidationManager.class)).thenReturn(dependencyValidationManager); 32 | return project; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/cmd/OpDestroyCheckerTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.puppycrawl.tools.checkstyle.Checker; 5 | import com.puppycrawl.tools.checkstyle.DefaultConfiguration; 6 | import org.infernus.idea.checkstyle.CheckstyleProjectService; 7 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 8 | import org.infernus.idea.checkstyle.exception.CheckstyleVersionMixException; 9 | import org.infernus.idea.checkstyle.service.CheckstyleActionsImpl; 10 | import org.infernus.idea.checkstyle.service.entities.CheckerWithConfig; 11 | import org.junit.Test; 12 | 13 | import static org.mockito.Mockito.mock; 14 | 15 | 16 | public class OpDestroyCheckerTest { 17 | private static final Project PROJECT = mock(Project.class); 18 | 19 | private static final class WrongObject implements CheckstyleInternalObject { 20 | // does not matter 21 | } 22 | 23 | @Test 24 | public void testDestroyChecker() { 25 | CheckerWithConfig checkerWithConfig = new CheckerWithConfig(new Checker(), new DefaultConfiguration("testConfig")); 26 | new CheckstyleActionsImpl(PROJECT, mock(CheckstyleProjectService.class)).destroyChecker(checkerWithConfig); 27 | } 28 | 29 | @Test(expected = CheckstyleVersionMixException.class) 30 | public void testMixExceptionInInit() { 31 | new OpDestroyChecker(new WrongObject()); 32 | } 33 | 34 | 35 | @Test(expected = CheckstyleVersionMixException.class) 36 | public void testCheckerNull() { 37 | //noinspection ConstantConditions 38 | new OpDestroyChecker(null); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScrollToSource.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareToggleAction; 6 | import com.intellij.openapi.project.Project; 7 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 11 | 12 | /** 13 | * Toggle the scroll to source setting. 14 | */ 15 | public final class ScrollToSource extends DumbAwareToggleAction { 16 | 17 | @Override 18 | public boolean isSelected(final @NotNull AnActionEvent event) { 19 | final Project project = getEventProject(event); 20 | if (project == null) { 21 | return false; 22 | } 23 | 24 | Boolean scrollToSource = getFromToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::isScrollToSource); 25 | if (scrollToSource != null) { 26 | return scrollToSource; 27 | } 28 | return false; 29 | } 30 | 31 | @Override 32 | public void setSelected(final @NotNull AnActionEvent event, final boolean selected) { 33 | final Project project = getEventProject(event); 34 | if (project == null) { 35 | return; 36 | } 37 | 38 | actOnToolWindowPanel(toolWindow(project), panel -> panel.setScrollToSource(selected)); 39 | } 40 | 41 | @Override 42 | public @NotNull ActionUpdateThread getActionUpdateThread() { 43 | return ActionUpdateThread.EDT; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/cmd/OpPeruseConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | 4 | import com.intellij.openapi.project.Project; 5 | import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 6 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 7 | import org.infernus.idea.checkstyle.csapi.ConfigVisitor; 8 | import org.infernus.idea.checkstyle.csapi.ConfigurationModule; 9 | import org.infernus.idea.checkstyle.exception.CheckstyleVersionMixException; 10 | import org.infernus.idea.checkstyle.service.entities.CsConfigObject; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.junit.Test; 13 | import org.mockito.Mockito; 14 | 15 | import static org.junit.Assert.assertNull; 16 | 17 | public class OpPeruseConfigurationTest { 18 | private static final Project PROJECT = Mockito.mock(Project.class); 19 | 20 | private static final class InvalidObject implements CheckstyleInternalObject { 21 | // does not matter 22 | } 23 | 24 | @Test(expected = CheckstyleVersionMixException.class) 25 | public void testWrongConfigurationClass() { 26 | new OpPeruseConfiguration(new InvalidObject(), new StubVisitor()); 27 | } 28 | 29 | @Test 30 | public void testNullConfig() throws CheckstyleException { 31 | final OpPeruseConfiguration cmd = new OpPeruseConfiguration(new CsConfigObject(null), new StubVisitor()); 32 | assertNull(cmd.execute(PROJECT)); 33 | } 34 | 35 | private static final class StubVisitor implements ConfigVisitor { 36 | @Override 37 | public void visit(@NotNull final ConfigurationModule module) { 38 | 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/ModuleImporterFactory.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer; 2 | 3 | import org.infernus.idea.checkstyle.csapi.ConfigurationModule; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | 9 | final class ModuleImporterFactory { 10 | 11 | private ModuleImporterFactory() { 12 | } 13 | 14 | @Nullable 15 | static ModuleImporter getModuleImporter(@NotNull final ConfigurationModule configuration) 16 | throws InstantiationException, IllegalAccessException { 17 | String name = configuration.getName(); 18 | ModuleImporter moduleImporter = createImporter(name); 19 | if (moduleImporter != null) { 20 | moduleImporter.setFrom(configuration); 21 | } 22 | return moduleImporter; 23 | } 24 | 25 | @Nullable 26 | private static ModuleImporter createImporter(@NotNull final String name) 27 | throws IllegalAccessException, InstantiationException { 28 | String fqn = getFullyQualifiedClassName(name); 29 | try { 30 | Class c = Class.forName(fqn); 31 | Object o = c.getDeclaredConstructor().newInstance(); 32 | return o instanceof ModuleImporter ? (ModuleImporter) o : null; 33 | } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) { 34 | return null; 35 | } 36 | } 37 | 38 | private static String getFullyQualifiedClassName(@NotNull final String moduleName) { 39 | return ModuleImporterFactory.class.getPackage().getName() + ".modules." + moduleName + "Importer"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/DisplayErrors.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareToggleAction; 6 | import com.intellij.openapi.project.Project; 7 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Objects; 11 | 12 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 13 | 14 | /** 15 | * Action to toggle error display in tool window. 16 | */ 17 | public class DisplayErrors extends DumbAwareToggleAction { 18 | 19 | @Override 20 | public boolean isSelected(final @NotNull AnActionEvent event) { 21 | final Project project = getEventProject(event); 22 | if (project == null) { 23 | return false; 24 | } 25 | 26 | Boolean displayingErrors = getFromToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::isDisplayingErrors); 27 | return Objects.requireNonNullElse(displayingErrors, false); 28 | } 29 | 30 | @Override 31 | public void setSelected(final @NotNull AnActionEvent event, final boolean selected) { 32 | final Project project = getEventProject(event); 33 | if (project == null) { 34 | return; 35 | } 36 | 37 | actOnToolWindowPanel(toolWindow(project), panel -> { 38 | panel.setDisplayingErrors(selected); 39 | panel.filterDisplayedResults(); 40 | }); 41 | } 42 | 43 | @Override 44 | public @NotNull ActionUpdateThread getActionUpdateThread() { 45 | return ActionUpdateThread.EDT; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/ConfigurationInvalidator.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.checker.CheckerFactoryCache; 5 | import org.infernus.idea.checkstyle.config.PluginConfiguration; 6 | import org.infernus.idea.checkstyle.config.PluginConfigurationManager; 7 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 8 | import org.infernus.idea.checkstyle.util.TempDirProvider; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class ConfigurationInvalidator { 12 | 13 | private final Project project; 14 | private final CheckstyleProjectService checkstyleProjectService; 15 | private final PluginConfigurationManager pluginConfigurationManager; 16 | private final CheckerFactoryCache checkerFactoryCache; 17 | 18 | ConfigurationInvalidator(@NotNull final Project project) { 19 | this.project = project; 20 | this.checkstyleProjectService = project.getService(CheckstyleProjectService.class); 21 | this.checkerFactoryCache = project.getService(CheckerFactoryCache.class); 22 | this.pluginConfigurationManager = project.getService(PluginConfigurationManager.class); 23 | } 24 | 25 | public void invalidateCachedResources() { 26 | checkerFactoryCache.invalidate(); 27 | 28 | PluginConfiguration config = pluginConfigurationManager.getCurrent(); 29 | config.getLocations().forEach(ConfigurationLocation::reset); 30 | 31 | checkstyleProjectService.activateCheckstyleVersion(config.getCheckstyleVersion(), config.getThirdPartyClasspath()); 32 | if (!config.isCopyLibs()) { 33 | new TempDirProvider().deleteCopiedLibrariesDir(project); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/DisplayInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareToggleAction; 6 | import com.intellij.openapi.project.Project; 7 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 11 | 12 | /** 13 | * Action to toggle error display in tool window. 14 | */ 15 | public class DisplayInfo extends DumbAwareToggleAction { 16 | 17 | @Override 18 | public boolean isSelected(final @NotNull AnActionEvent event) { 19 | final Project project = getEventProject(event); 20 | if (project == null) { 21 | return false; 22 | } 23 | 24 | Boolean displayingInfo = getFromToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::isDisplayingInfo); 25 | if (displayingInfo != null) { 26 | return displayingInfo; 27 | } 28 | return false; 29 | } 30 | 31 | @Override 32 | public void setSelected(final @NotNull AnActionEvent event, final boolean selected) { 33 | final Project project = getEventProject(event); 34 | if (project == null) { 35 | return; 36 | } 37 | 38 | actOnToolWindowPanel(toolWindow(project), panel -> { 39 | panel.setDisplayingInfo(selected); 40 | panel.filterDisplayedResults(); 41 | }); 42 | } 43 | 44 | @Override 45 | public @NotNull ActionUpdateThread getActionUpdateThread() { 46 | return ActionUpdateThread.EDT; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/DisplayWarnings.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareToggleAction; 6 | import com.intellij.openapi.project.Project; 7 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Objects; 11 | 12 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 13 | 14 | /** 15 | * Action to toggle error display in tool window. 16 | */ 17 | public class DisplayWarnings extends DumbAwareToggleAction { 18 | 19 | @Override 20 | public boolean isSelected(final @NotNull AnActionEvent event) { 21 | final Project project = getEventProject(event); 22 | if (project == null) { 23 | return false; 24 | } 25 | 26 | Boolean displayingWarnings = getFromToolWindowPanel(toolWindow(project), CheckStyleToolWindowPanel::isDisplayingWarnings); 27 | return Objects.requireNonNullElse(displayingWarnings, false); 28 | } 29 | 30 | @Override 31 | public void setSelected(final @NotNull AnActionEvent event, final boolean selected) { 32 | final Project project = getEventProject(event); 33 | if (project == null) { 34 | return; 35 | } 36 | 37 | actOnToolWindowPanel(toolWindow(project), panel -> { 38 | panel.setDisplayingWarnings(selected); 39 | panel.filterDisplayedResults(); 40 | }); 41 | } 42 | 43 | @Override 44 | public @NotNull ActionUpdateThread getActionUpdateThread() { 45 | return ActionUpdateThread.EDT; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/GroupingAction.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareToggleAction; 6 | import com.intellij.openapi.project.Project; 7 | import org.infernus.idea.checkstyle.toolwindow.ResultGrouping; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Objects; 11 | 12 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.*; 13 | 14 | abstract class GroupingAction extends DumbAwareToggleAction { 15 | 16 | private final ResultGrouping grouping; 17 | 18 | GroupingAction(@NotNull final ResultGrouping grouping) { 19 | this.grouping = grouping; 20 | } 21 | 22 | @Override 23 | public boolean isSelected(final @NotNull AnActionEvent event) { 24 | final Project project = getEventProject(event); 25 | if (project == null) { 26 | return false; 27 | } 28 | 29 | Boolean selected = getFromToolWindowPanel(toolWindow(project), panel -> panel.groupedBy() == grouping); 30 | return Objects.requireNonNullElse(selected, false); 31 | } 32 | 33 | @Override 34 | public void setSelected(final @NotNull AnActionEvent event, final boolean selected) { 35 | final Project project = getEventProject(event); 36 | if (project == null) { 37 | return; 38 | } 39 | 40 | actOnToolWindowPanel(toolWindow(project), panel -> { 41 | panel.groupBy(grouping); 42 | }); 43 | } 44 | 45 | @Override 46 | public @NotNull ActionUpdateThread getActionUpdateThread() { 47 | return ActionUpdateThread.EDT; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/CheckerFactoryCacheKey.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import java.util.Objects; 4 | 5 | import com.intellij.openapi.module.Module; 6 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | 11 | class CheckerFactoryCacheKey { 12 | 13 | private final String projectName; 14 | private final String moduleName; 15 | private final ConfigurationLocation location; 16 | 17 | // We can disregard Checkstyle version and third party jars as elements of the cache key, because the cache 18 | // must be invalidated when any of these properties change anyway. 19 | 20 | 21 | CheckerFactoryCacheKey(@NotNull final ConfigurationLocation location, @Nullable final Module module) { 22 | this.projectName = module != null ? module.getProject().getName() : "noProject"; 23 | this.moduleName = module != null ? module.getName() : "noModule"; 24 | this.location = location; 25 | } 26 | 27 | 28 | @Override 29 | public boolean equals(final Object pOther) { 30 | if (this == pOther) { 31 | return true; 32 | } 33 | if (pOther == null || getClass() != pOther.getClass()) { 34 | return false; 35 | } 36 | final CheckerFactoryCacheKey other = (CheckerFactoryCacheKey) pOther; 37 | return Objects.equals(projectName, other.projectName) 38 | && Objects.equals(moduleName, other.moduleName) 39 | && Objects.equals(location, other.location); 40 | } 41 | 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(projectName, moduleName, location); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/CheckerFactoryWorker.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import org.infernus.idea.checkstyle.CheckstyleProjectService; 5 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.Map; 10 | 11 | 12 | class CheckerFactoryWorker extends Thread { 13 | private final ConfigurationLocation location; 14 | private final Map properties; 15 | private final Module module; 16 | private final CheckstyleProjectService checkstyleProjectService; 17 | 18 | private Object threadReturn = null; 19 | 20 | CheckerFactoryWorker(@NotNull final ConfigurationLocation location, 21 | @Nullable final Map properties, 22 | @Nullable final Module module, 23 | @NotNull final CheckstyleProjectService checkstyleProjectService) { 24 | this.location = location; 25 | this.properties = properties; 26 | this.module = module; 27 | this.checkstyleProjectService = checkstyleProjectService; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | super.run(); 33 | 34 | try { 35 | final CheckStyleChecker checker = checkstyleProjectService 36 | .getCheckstyleInstance() 37 | .createChecker(module, location, properties); 38 | threadReturn = new CachedChecker(checker); 39 | } catch (RuntimeException e) { 40 | threadReturn = e; 41 | } 42 | } 43 | 44 | public Object getResult() { 45 | return threadReturn; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/ClassLoaderDumper.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import com.intellij.util.lang.UrlClassLoader; 4 | 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | 8 | public final class ClassLoaderDumper { 9 | 10 | private ClassLoaderDumper() { 11 | } 12 | 13 | public static String dumpClassLoader(final ClassLoader classLoader) { 14 | StringBuilder dump = new StringBuilder(); 15 | 16 | if (classLoader != null) { 17 | ClassLoader currentLoader = classLoader; 18 | while (currentLoader != null) { 19 | if (currentLoader instanceof URLClassLoader) { 20 | dump.append("URLClassLoader: ") 21 | .append(currentLoader.getClass().getName()) 22 | .append('\n'); 23 | for (final URL url : ((URLClassLoader) currentLoader).getURLs()) { 24 | dump.append("- url=").append(url).append('\n'); 25 | } 26 | } else if (currentLoader instanceof UrlClassLoader) { 27 | dump.append("UrlClassLoader: ") 28 | .append(currentLoader.getClass().getName()) 29 | .append('\n'); 30 | for (final URL url : ((UrlClassLoader) currentLoader).getUrls()) { 31 | dump.append("- url=").append(url).append('\n'); 32 | } 33 | } else { 34 | dump.append("ClassLoader: ").append(currentLoader.getClass().getName()); 35 | } 36 | 37 | currentLoader = currentLoader.getParent(); 38 | } 39 | } 40 | 41 | return dump.toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/NeedBracesImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.psi.codeStyle.CodeStyleSettings; 4 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 5 | import org.infernus.idea.checkstyle.csapi.KnownTokenTypes; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @SuppressWarnings("unused") 10 | public class NeedBracesImporter extends ModuleImporter { 11 | 12 | private static final String ALLOW_SINGLE_LINE_STATEMENT_PROP = "allowSingleLineStatement"; 13 | 14 | private int forceBraces = CommonCodeStyleSettings.FORCE_BRACES_ALWAYS; 15 | 16 | @Override 17 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 18 | if (ALLOW_SINGLE_LINE_STATEMENT_PROP.equals(attrName)) { 19 | if (Boolean.parseBoolean(attrValue)) { 20 | forceBraces = CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE; 21 | } 22 | } 23 | } 24 | 25 | @Override 26 | public void importTo(@NotNull final CodeStyleSettings settings) { 27 | CommonCodeStyleSettings commonSettings = getCommonSettings(settings); 28 | if (appliesTo(KnownTokenTypes.LITERAL_DO)) { 29 | commonSettings.DOWHILE_BRACE_FORCE = forceBraces; 30 | } 31 | if (appliesTo(KnownTokenTypes.LITERAL_FOR)) { 32 | commonSettings.FOR_BRACE_FORCE = forceBraces; 33 | } 34 | if (appliesTo(KnownTokenTypes.LITERAL_IF)) { 35 | commonSettings.IF_BRACE_FORCE = forceBraces; 36 | } 37 | if (appliesTo(KnownTokenTypes.LITERAL_WHILE)) { 38 | commonSettings.WHILE_BRACE_FORCE = forceBraces; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/VersionComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | 8 | /** 9 | * Unit tests of {@link VersionComparator}. 10 | */ 11 | public class VersionComparatorTest { 12 | @Test 13 | public void testComp() { 14 | VersionComparator testee = new VersionComparator(); 15 | 16 | assertTrue(testee.compare("8.1", "8.1.2") < 0); 17 | assertTrue(testee.compare("8.1.2", "8.1") > 0); 18 | assertTrue(testee.compare("8.2", "8.1.2") > 0); 19 | assertTrue(testee.compare("8.1.2", "8.2") < 0); 20 | assertTrue(testee.compare("8.1", "9.1") < 0); 21 | assertTrue(testee.compare("9.1", "8.1") > 0); 22 | assertTrue(testee.compare("8.1.3", "8.1.2") > 0); 23 | assertTrue(testee.compare("8.1.2", "8.1.3") < 0); 24 | assertTrue(testee.compare("8.1", "foo") < 0); 25 | assertTrue(testee.compare("foo", "8.1") > 0); 26 | assertTrue(testee.compare("foo", "FOO") > 0); 27 | assertTrue(testee.compare("FOO", "foo") < 0); 28 | assertTrue(testee.compare("8.1", null) < 0); 29 | assertTrue(testee.compare(null, "8.1") > 0); 30 | assertEquals(0, testee.compare(null, null)); 31 | assertEquals(0, testee.compare("8.1.2", "8.1.2")); 32 | assertEquals(0, testee.compare("8.1", "8.1")); 33 | assertEquals(0, testee.compare("foo", "foo")); 34 | assertTrue(testee.compare("8", "0815") > 0); // lex. order b/c "6" does not match version pattern 35 | assertTrue(testee.compare("8.1", "8.x") < 0); 36 | assertTrue(testee.compare("8.x", "8.1") > 0); 37 | assertTrue(testee.compare("8.1.8", "8.1.x") < 0); 38 | assertTrue(testee.compare("8.1.x", "8.1.8") > 0); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/csaccess/java/org/infernus/idea/checkstyle/service/cmd/OpDestroyChecker.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service.cmd; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.project.Project; 5 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 6 | import org.infernus.idea.checkstyle.exception.CheckstyleVersionMixException; 7 | import org.infernus.idea.checkstyle.service.entities.HasChecker; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * Destroy a checker instance. 15 | */ 16 | public class OpDestroyChecker 17 | implements CheckstyleCommand { 18 | 19 | private static final Logger LOG = Logger.getInstance(OpDestroyChecker.class); 20 | 21 | private final HasChecker hasChecker; 22 | 23 | public OpDestroyChecker(@NotNull final CheckstyleInternalObject checker) { 24 | if (!(checker instanceof HasChecker)) { 25 | throw new CheckstyleVersionMixException(HasChecker.class, checker); 26 | } 27 | this.hasChecker = (HasChecker) checker; 28 | } 29 | 30 | @Nullable 31 | @Override 32 | public Void execute(@NotNull final Project project) { 33 | try { 34 | if (hasChecker.getCheckerLock().tryLock(1, TimeUnit.SECONDS)) { 35 | try { 36 | hasChecker.getChecker().destroy(); 37 | } finally { 38 | hasChecker.getCheckerLock().unlock(); 39 | } 40 | } 41 | } catch (InterruptedException e) { 42 | LOG.debug("Checker will not be destroyed as we couldn't lock the checker", e); 43 | Thread.currentThread().interrupt(); 44 | } 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/ListPropertyResolver.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import static org.infernus.idea.checkstyle.util.Strings.isBlank; 9 | 10 | class ListPropertyResolver { 11 | 12 | private final Map propertyNamesToValues = new HashMap<>(); 13 | 14 | private final List propertyNames = new ArrayList<>(); 15 | 16 | ListPropertyResolver(final Map properties) { 17 | setProperties(properties); 18 | } 19 | 20 | public Map getPropertyNamesToValues() { 21 | return propertyNamesToValues; 22 | } 23 | 24 | public String resolve(final String propertyName) { 25 | // collect properties that are referenced in the config file 26 | if (!propertyNames.contains(propertyName)) { 27 | propertyNames.add(propertyName); 28 | } 29 | 30 | final String propertyValue = propertyNamesToValues.get(propertyName); 31 | if (isBlank(propertyValue)) { 32 | return null; 33 | } 34 | return propertyValue; 35 | } 36 | 37 | public void setProperty(final String name, final String value) { 38 | if (!propertyNames.contains(name)) { 39 | propertyNames.add(name); 40 | } 41 | 42 | propertyNamesToValues.put(name, value); 43 | } 44 | 45 | public void setProperties(final Map properties) { 46 | if (properties == null) { 47 | return; 48 | } 49 | 50 | for (final Map.Entry propertyEntry : properties.entrySet()) { 51 | setProperty(propertyEntry.getKey(), propertyEntry.getValue()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/StopCheck.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.Presentation; 5 | import com.intellij.openapi.diagnostic.Logger; 6 | import com.intellij.openapi.wm.ToolWindow; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 10 | 11 | /** 12 | * Action to stop a check in progress. 13 | */ 14 | public class StopCheck extends BaseAction { 15 | private static final Logger LOG = Logger.getInstance(StopCheck.class); 16 | 17 | @Override 18 | public void actionPerformed(final @NotNull AnActionEvent event) { 19 | project(event).ifPresent(project -> { 20 | try { 21 | final ToolWindow toolWindow = toolWindow(project); 22 | toolWindow.activate(() -> { 23 | setProgressText(toolWindow, "plugin.status.in-progress.current"); 24 | 25 | staticScanner(project).stopChecks(); 26 | 27 | setProgressText(toolWindow, "plugin.status.aborted"); 28 | }); 29 | 30 | } catch (Throwable e) { 31 | LOG.warn("Abort Scan failed", e); 32 | } 33 | }); 34 | } 35 | 36 | @Override 37 | public void update(final @NotNull AnActionEvent event) { 38 | final Presentation presentation = event.getPresentation(); 39 | project(event).ifPresentOrElse(project -> { 40 | try { 41 | presentation.setEnabled(staticScanner(project).isScanInProgress()); 42 | 43 | } catch (Throwable e) { 44 | LOG.warn("Abort button update failed", e); 45 | } 46 | }, () -> presentation.setEnabled(false)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/dtd/configuration_1_3.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 33 | 34 | 35 | 39 | 40 | 52 | 53 | 57 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/FileTabCharacterImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.openapi.fileTypes.FileType; 4 | import com.intellij.openapi.fileTypes.FileTypeManager; 5 | import com.intellij.psi.codeStyle.CodeStyleSettings; 6 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 7 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @SuppressWarnings("unused") 11 | public class FileTabCharacterImporter extends ModuleImporter { 12 | 13 | private static final String FILE_EXTENSIONS_PROP = "fileExtensions"; 14 | private String[] extensions; 15 | 16 | @Override 17 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 18 | if (FILE_EXTENSIONS_PROP.equals(attrName)) { 19 | extensions = attrValue.split("\\s*,\\s*"); 20 | } 21 | } 22 | 23 | @Override 24 | public void importTo(@NotNull final CodeStyleSettings settings) { 25 | if (extensions != null) { 26 | for (String extension : extensions) { 27 | if (!extension.isEmpty()) { 28 | FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(extension); 29 | setNoTabChar(settings, fileType); 30 | } 31 | } 32 | } else { 33 | for (FileType fileType : FileTypeManager.getInstance().getRegisteredFileTypes()) { 34 | setNoTabChar(settings, fileType); 35 | } 36 | } 37 | } 38 | 39 | private void setNoTabChar(@NotNull final CodeStyleSettings settings, final FileType fileType) { 40 | CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptions(fileType); 41 | indentOptions.USE_TAB_CHARACTER = false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/EmptyLineSeparatorImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.psi.codeStyle.CodeStyleSettings; 4 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 5 | import org.infernus.idea.checkstyle.csapi.KnownTokenTypes; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @SuppressWarnings("unused") 10 | public class EmptyLineSeparatorImporter extends ModuleImporter { 11 | 12 | private boolean noEmptyLinesBetweenFields = false; 13 | private static final String NO_EMPTY_LINES_BETWEEN_FIELDS_PROP = "allowNoEmptyLineBetweenFields"; 14 | 15 | @Override 16 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 17 | if (NO_EMPTY_LINES_BETWEEN_FIELDS_PROP.equals(attrName)) { 18 | noEmptyLinesBetweenFields = Boolean.parseBoolean(attrValue); 19 | } 20 | } 21 | 22 | @Override 23 | public void importTo(@NotNull final CodeStyleSettings settings) { 24 | CommonCodeStyleSettings commonSettings = getCommonSettings(settings); 25 | 26 | if (noEmptyLinesBetweenFields) { 27 | commonSettings.BLANK_LINES_AROUND_FIELD = 0; 28 | } else if (appliesTo(KnownTokenTypes.VARIABLE_DEF)) { 29 | commonSettings.BLANK_LINES_AROUND_FIELD = 1; 30 | } 31 | if (appliesTo(KnownTokenTypes.PACKAGE_DEF)) { 32 | commonSettings.BLANK_LINES_AFTER_PACKAGE = 1; 33 | commonSettings.BLANK_LINES_BEFORE_PACKAGE = 1; 34 | } 35 | if (appliesTo(KnownTokenTypes.IMPORT)) { 36 | commonSettings.BLANK_LINES_AFTER_IMPORTS = 1; 37 | } 38 | if (appliesTo(KnownTokenTypes.METHOD_DEF)) { 39 | commonSettings.BLANK_LINES_AROUND_METHOD = 1; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checker/CreateScannableFileAction.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checker; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.psi.PsiFile; 5 | import com.intellij.util.ThrowableRunnable; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Action to read the file to a temporary file. 13 | */ 14 | class CreateScannableFileAction implements ThrowableRunnable { 15 | 16 | /** 17 | * Any failure that occurred on the thread. 18 | */ 19 | private IOException failure; 20 | 21 | private final PsiFile psiFile; 22 | private final Module module; 23 | 24 | /** 25 | * The created temporary file. 26 | */ 27 | private ScannableFile file; 28 | 29 | /** 30 | * Create a thread to read the given file to a temporary file. 31 | * 32 | * @param psiFile the file to read. 33 | * @param module the module the file belongs to. 34 | */ 35 | CreateScannableFileAction(@NotNull final PsiFile psiFile, 36 | @Nullable final Module module) { 37 | this.psiFile = psiFile; 38 | this.module = module; 39 | } 40 | 41 | /** 42 | * Get any failure that occurred in this thread. 43 | * 44 | * @return the failure, if any. 45 | */ 46 | public IOException getFailure() { 47 | return failure; 48 | } 49 | 50 | /** 51 | * Get the scannable file. 52 | * 53 | * @return the scannable file. 54 | */ 55 | public ScannableFile getFile() { 56 | return file; 57 | } 58 | 59 | @Override 60 | public void run() { 61 | try { 62 | file = new ScannableFile(psiFile, module); 63 | 64 | } catch (IOException e) { 65 | failure = e; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/GroupTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import org.infernus.idea.checkstyle.CheckStyleBundle; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.swing.*; 7 | 8 | abstract class GroupTreeInfo extends ResultTreeNode { 9 | 10 | private final String name; 11 | private final String groupId; 12 | private final int totalProblems; 13 | private int visibleProblems; 14 | 15 | /** 16 | * Construct a group node. 17 | * 18 | * @param name the name of the group. 19 | * @param groupId the ID used as part of message lookup. 20 | * @param icon the icon of the group. 21 | * @param problemCount the number of problems in the group. 22 | */ 23 | public GroupTreeInfo(@NotNull final String name, 24 | @NotNull final String groupId, 25 | @NotNull final Icon icon, 26 | final int problemCount) { 27 | super(CheckStyleBundle.message("plugin.results.scan-" + groupId + "-result", name, problemCount)); 28 | 29 | this.name = name; 30 | this.groupId = groupId; 31 | this.totalProblems = problemCount; 32 | this.visibleProblems = problemCount; 33 | 34 | updateDisplayText(); 35 | setIcon(icon); 36 | } 37 | 38 | private void updateDisplayText() { 39 | if (totalProblems == visibleProblems) { 40 | setText(CheckStyleBundle.message("plugin.results.scan-" + groupId + "-result", name, totalProblems)); 41 | } else { 42 | setText(CheckStyleBundle.message("plugin.results.scan-" + groupId + "-result.filtered", name, visibleProblems, totalProblems - visibleProblems)); 43 | } 44 | } 45 | 46 | void setVisibleProblems(final int visibleProblems) { 47 | this.visibleProblems = visibleProblems; 48 | 49 | updateDisplayText(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ResultProblem.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.psi.PsiElement; 5 | import org.infernus.idea.checkstyle.CheckStyleBundle; 6 | import org.infernus.idea.checkstyle.checker.ConfigurationLocationResult; 7 | import org.infernus.idea.checkstyle.checker.Problem; 8 | import org.infernus.idea.checkstyle.csapi.SeverityLevel; 9 | import org.infernus.idea.checkstyle.util.DisplayFormats; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | record ResultProblem( 13 | ConfigurationLocationResult configurationLocationResult, 14 | Module module, 15 | PsiElement target, 16 | SeverityLevel severityLevel, 17 | int line, 18 | int column, 19 | String sourceName, 20 | String message, 21 | boolean afterEndOfLine, 22 | boolean suppressErrors) { 23 | 24 | ResultProblem(@NotNull final ConfigurationLocationResult configurationLocationResult, 25 | @NotNull final Module module, 26 | @NotNull final Problem csProblem) { 27 | this(configurationLocationResult, module, csProblem.target(), csProblem.severityLevel(), csProblem.line(), 28 | csProblem.column(), csProblem.sourceName(), csProblem.message(), csProblem.afterEndOfLine(), 29 | csProblem.suppressErrors()); 30 | } 31 | 32 | @NotNull 33 | public String sourceCheck() { 34 | if (sourceName != null) { 35 | return DisplayFormats.shortenClassName(sourceName); 36 | } 37 | return CheckStyleBundle.message("plugin.results.unknown-source"); 38 | } 39 | 40 | public String locationDescription() { 41 | if (configurationLocationResult.location() != null) { 42 | return configurationLocationResult().location().getDescription(); 43 | } 44 | return CheckStyleBundle.message("plugin.results.unknown-location"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/checks/PackageHtmlCheck.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.checks; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.psi.PsiFile; 5 | import org.infernus.idea.checkstyle.csapi.CheckstyleInternalObject; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | 9 | /** 10 | * Extra logic for the PackageHtmlCheck check. 11 | */ 12 | public class PackageHtmlCheck implements Check { 13 | 14 | private static final String CHECK_PACKAGE_HTML = "com.puppycrawl.tools.checkstyle.checks.javadoc.PackageHtmlCheck"; 15 | private static final String PACKAGE_HTML_FILE = "package.html"; 16 | 17 | @Override 18 | public String getShortName() { 19 | return "PackageHtml"; 20 | } 21 | 22 | @Override 23 | public String getFullyQualifiedName() { 24 | return CHECK_PACKAGE_HTML; 25 | } 26 | 27 | 28 | public void configure(@NotNull final CheckstyleInternalObject config) { 29 | } 30 | 31 | 32 | public boolean process(@NotNull final PsiFile file, @NotNull final String pEventSourceName) { 33 | if (!CHECK_PACKAGE_HTML.equals(pEventSourceName)) { 34 | return true; 35 | } 36 | 37 | PsiElement currentSibling = findFirstSibling(file); 38 | 39 | while (currentSibling != null) { 40 | if (currentSibling.isPhysical() && currentSibling.isValid() 41 | && currentSibling instanceof PsiFile 42 | && PACKAGE_HTML_FILE.equals(((PsiFile) currentSibling).getName())) { 43 | return false; 44 | } 45 | 46 | currentSibling = currentSibling.getNextSibling(); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | 53 | private PsiElement findFirstSibling(@NotNull final PsiFile psiFile) { 54 | PsiElement currentSibling = psiFile; 55 | while (currentSibling.getPrevSibling() != null) { 56 | currentSibling = currentSibling.getPrevSibling(); 57 | } 58 | return currentSibling; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/ui/PropertiesDialogue.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.ui; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import org.infernus.idea.checkstyle.CheckStyleBundle; 6 | import org.infernus.idea.checkstyle.CheckstyleProjectService; 7 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | 14 | /** 15 | * Allows setting of file properties. 16 | */ 17 | public class PropertiesDialogue extends DialogWrapper { 18 | 19 | private final PropertiesPanel propertiesPanel; 20 | 21 | 22 | public PropertiesDialogue(@Nullable final Dialog parent, 23 | @NotNull final Project project, 24 | @NotNull final CheckstyleProjectService checkstyleProjectService) { 25 | super(project, parent, false, IdeModalityType.IDE); 26 | 27 | this.propertiesPanel = new PropertiesPanel(project, checkstyleProjectService); 28 | 29 | setTitle(CheckStyleBundle.message("config.file.properties.title")); 30 | init(); 31 | } 32 | 33 | @Override 34 | protected @Nullable JComponent createCenterPanel() { 35 | return propertiesPanel; 36 | } 37 | 38 | /** 39 | * Get the configuration location entered in the dialogue, or null if no valid location was entered. 40 | * 41 | * @return the location or null if no valid location entered. 42 | */ 43 | public ConfigurationLocation getConfigurationLocation() { 44 | return propertiesPanel.getConfigurationLocation(); 45 | } 46 | 47 | /** 48 | * Set the configuration location. 49 | * 50 | * @param configurationLocation the location. 51 | */ 52 | public void setConfigurationLocation(final ConfigurationLocation configurationLocation) { 53 | propertiesPanel.setConfigurationLocation(configurationLocation); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ResultTreeNode.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | import javax.swing.*; 6 | 7 | import static org.infernus.idea.checkstyle.util.Strings.isBlank; 8 | 9 | 10 | /** 11 | * The user object for meta-data on tree nodes in the tool window. 12 | */ 13 | public class ResultTreeNode { 14 | 15 | private String text; 16 | private Icon icon = AllIcons.General.Information; 17 | 18 | /** 19 | * Construct an informational node. 20 | * 21 | * @param text the information text. 22 | */ 23 | public ResultTreeNode(final String text) { 24 | if (text == null) { 25 | throw new IllegalArgumentException("Text may not be null"); 26 | } 27 | 28 | this.text = text; 29 | } 30 | 31 | /** 32 | * Get the node's icon when in an expanded state. 33 | * 34 | * @return the node's icon when in an expanded state. 35 | */ 36 | public Icon getExpandedIcon() { 37 | return icon; 38 | } 39 | 40 | /** 41 | * Get the node's icon when in a collapsed state. 42 | * 43 | * @return the node's icon when in a collapsed state. 44 | */ 45 | public Icon getCollapsedIcon() { 46 | return icon; 47 | } 48 | 49 | /** 50 | * Get the file the node represents. 51 | * 52 | * @return the file the node represents. 53 | */ 54 | public String getText() { 55 | return text; 56 | } 57 | 58 | /** 59 | * Set the file the node represents. 60 | * 61 | * @param text the file the node represents. 62 | */ 63 | void setText(final String text) { 64 | if (isBlank(text)) { 65 | throw new IllegalArgumentException("Text may not be null/empty"); 66 | } 67 | this.text = text; 68 | } 69 | 70 | protected void setIcon(final Icon icon) { 71 | this.icon = icon; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return text; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/ui/ErrorPanel.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.ui; 2 | 3 | import com.intellij.ui.components.JBScrollPane; 4 | import com.intellij.util.ui.JBUI; 5 | import org.infernus.idea.checkstyle.CheckStyleBundle; 6 | 7 | import javax.swing.*; 8 | import java.awt.*; 9 | import java.io.PrintWriter; 10 | import java.io.StringWriter; 11 | 12 | import static javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS; 13 | import static javax.swing.JScrollPane.VERTICAL_SCROLLBAR_ALWAYS; 14 | 15 | public class ErrorPanel extends JPanel { 16 | private final JTextArea errorField = new JTextArea(); 17 | 18 | public ErrorPanel() { 19 | super(new BorderLayout()); 20 | 21 | initialise(); 22 | 23 | setError(new RuntimeException()); 24 | } 25 | 26 | private void initialise() { 27 | setBorder(JBUI.Borders.empty(8)); 28 | 29 | final JLabel infoLabel = new JLabel(CheckStyleBundle.message("config.file.error.caption")); 30 | infoLabel.setBorder(JBUI.Borders.emptyBottom(8)); 31 | add(infoLabel, BorderLayout.NORTH); 32 | 33 | errorField.setEditable(false); 34 | errorField.setTabSize(2); 35 | errorField.setWrapStyleWord(true); 36 | errorField.setLineWrap(true); 37 | 38 | final JScrollPane errorScrollPane = new JBScrollPane(errorField, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_ALWAYS); 39 | add(errorScrollPane, BorderLayout.CENTER); 40 | } 41 | 42 | public void setError(final Throwable t) { 43 | final StringWriter errorWriter = new StringWriter(); 44 | causeOf(t).printStackTrace(new PrintWriter(errorWriter)); 45 | 46 | errorField.setText(errorWriter.getBuffer().toString()); 47 | errorField.setCaretPosition(0); 48 | invalidate(); 49 | } 50 | 51 | private Throwable causeOf(final Throwable t) { 52 | if (t.getCause() != null && t.getCause() != t 53 | && !t.getClass().getPackage().getName().startsWith("com.puppycrawl")) { 54 | return causeOf(t.getCause()); 55 | } 56 | return t; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/ScanScope.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import org.infernus.idea.checkstyle.CheckStyleBundle; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Possible values of the 'scope' configuration item. 8 | */ 9 | public enum ScanScope { 10 | /** 11 | * Scan only Java files which reside in source folders (main only) 12 | */ 13 | JavaOnly, 14 | 15 | /** 16 | * Scan only Java files which reside in source folders (main and test) 17 | */ 18 | JavaOnlyWithTests, 19 | 20 | /** 21 | * Scan all files which reside in source folders (main only) 22 | */ 23 | AllSources, 24 | 25 | /** 26 | * Scan all files which reside in source folders (main and test) 27 | */ 28 | AllSourcesWithTests, 29 | 30 | /** 31 | * Scan all files in the project, regardless of their location 32 | */ 33 | Everything; 34 | 35 | private final String dropdownBoxEntry; 36 | 37 | ScanScope() { 38 | dropdownBoxEntry = CheckStyleBundle.message("config.scanscope." + this.name()); 39 | } 40 | 41 | public boolean includeTestClasses() { 42 | return this == JavaOnlyWithTests || this == AllSourcesWithTests || this == Everything; 43 | } 44 | 45 | public boolean includeNonJavaSources() { 46 | return this == AllSources || this == AllSourcesWithTests || this == Everything; 47 | } 48 | 49 | @NotNull 50 | public static ScanScope fromFlags(final boolean pIncludeTests, final boolean pIncludeNonJava) { 51 | ScanScope result = getDefaultValue(); 52 | if (pIncludeTests) { 53 | if (pIncludeNonJava) { 54 | result = AllSourcesWithTests; 55 | } else { 56 | result = JavaOnlyWithTests; 57 | } 58 | } else if (pIncludeNonJava) { 59 | result = AllSources; 60 | } 61 | return result; 62 | } 63 | 64 | @NotNull 65 | public static ScanScope getDefaultValue() { 66 | return JavaOnly; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return dropdownBoxEntry; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/CheckstyleProjectServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.config.PluginConfigurationBuilder; 5 | import org.infernus.idea.checkstyle.config.PluginConfigurationManager; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.util.SortedSet; 10 | 11 | import static org.hamcrest.Matchers.*; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | 17 | public class CheckstyleProjectServiceTest { 18 | private static final String CHECKSTYLE_VERSION = "9.0.1"; 19 | 20 | private CheckstyleProjectService underTest; 21 | 22 | @Before 23 | public void setUp() { 24 | Project project = mock(Project.class); 25 | 26 | PluginConfigurationManager pluginConfigManager = mock(PluginConfigurationManager.class); 27 | when(pluginConfigManager.getCurrent()) 28 | .thenReturn(PluginConfigurationBuilder.testInstance(CHECKSTYLE_VERSION).build()); 29 | when(project.getService(PluginConfigurationManager.class)).thenReturn(pluginConfigManager); 30 | 31 | underTest = new CheckstyleProjectService(project); 32 | } 33 | 34 | @Test 35 | public void readingSupportedVersionsReturnsASetOfVersions() { 36 | SortedSet versions = underTest.getSupportedVersions(); 37 | assertThat(versions, hasItem(CHECKSTYLE_VERSION)); 38 | assertThat(versions.comparator(), is(instanceOf(VersionComparator.class))); 39 | } 40 | 41 | @Test 42 | public void classLoaderCanBeRetrievedByExternalTools() { 43 | underTest.activateCheckstyleVersion(CHECKSTYLE_VERSION, null); 44 | assertThat(underTest.underlyingClassLoader(), is(not(nullValue()))); 45 | } 46 | 47 | @Test 48 | public void classLoaderCanLoadCheckStyleInternalClasses() throws ClassNotFoundException { 49 | underTest.activateCheckstyleVersion(CHECKSTYLE_VERSION, null); 50 | assertThat(underTest.underlyingClassLoader().loadClass("com.puppycrawl.tools.checkstyle.Checker"), 51 | is(not(nullValue()))); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/ModuleImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import com.intellij.lang.java.JavaLanguage; 8 | import com.intellij.psi.codeStyle.CodeStyleSettings; 9 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 10 | import com.intellij.psi.codeStyle.JavaCodeStyleSettings; 11 | import org.infernus.idea.checkstyle.csapi.ConfigurationModule; 12 | import org.infernus.idea.checkstyle.csapi.KnownTokenTypes; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public abstract class ModuleImporter { 16 | 17 | private Set tokens; 18 | 19 | @NotNull 20 | protected CommonCodeStyleSettings getCommonSettings(@NotNull final CodeStyleSettings settings) { 21 | return settings.getCommonSettings(JavaLanguage.INSTANCE); 22 | } 23 | @NotNull 24 | protected JavaCodeStyleSettings getJavaSettings(@NotNull final CodeStyleSettings settings) { 25 | return settings.getCustomSettings(JavaCodeStyleSettings.class); 26 | } 27 | 28 | public void setFrom(@NotNull final ConfigurationModule moduleConfig) { 29 | tokens = moduleConfig.getKnownTokenTypes(); 30 | for (Map.Entry entry : moduleConfig.getProperties().entrySet()) { 31 | handleAttribute(entry.getKey(), entry.getValue()); 32 | } 33 | } 34 | 35 | protected abstract void handleAttribute(@NotNull String attrName, @NotNull String attrValue); 36 | 37 | 38 | protected boolean appliesTo(final KnownTokenTypes token) { 39 | return tokens == null || tokens.isEmpty() || tokens.contains(token); 40 | } 41 | 42 | protected boolean appliesToOneOf(final Set tokenSet) { 43 | return tokens == null || tokens.isEmpty() || !Collections.disjoint(tokens, tokenSet); 44 | } 45 | 46 | 47 | public abstract void importTo(@NotNull CodeStyleSettings settings); 48 | 49 | 50 | protected int getIntOrDefault(@NotNull final String intStr, final int defaultValue) { 51 | try { 52 | return Integer.parseInt(intStr); 53 | } catch (NumberFormatException e) { 54 | return defaultValue; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/dtd/import_control_1_0.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 15 | 18 | 20 | 21 | 24 | 25 | 26 | 31 | 33 | 34 | 54 | 59 | 60 | 63 | 64 | 66 | 67 | 70 | 71 | 73 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/Async.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.Future; 5 | 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.progress.ProcessCanceledException; 9 | import com.intellij.openapi.progress.ProgressManager; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | public final class Async { 14 | private static final Logger LOG = Logger.getInstance(Async.class); 15 | 16 | private static final int FIFTY_MS = 50; 17 | 18 | private Async() { 19 | } 20 | 21 | @Nullable 22 | public static T asyncResultOf(@NotNull final Callable callable, 23 | @Nullable final T defaultValue, 24 | final long timeoutInMs) { 25 | try { 26 | return whenFinished(executeOnPooledThread(callable), timeoutInMs).get(); 27 | 28 | } catch (Exception e) { 29 | return defaultValue; 30 | } 31 | } 32 | 33 | public static Future executeOnPooledThread(final Callable callable) { 34 | return ApplicationManager.getApplication().executeOnPooledThread(callable); 35 | } 36 | 37 | public static Future whenFinished(final Future future, 38 | final long timeoutInMs) { 39 | long elapsedTime = 0; 40 | while (!future.isDone() && !future.isCancelled()) { 41 | ProgressManager.checkCanceled(); 42 | elapsedTime += waitFor(FIFTY_MS); 43 | 44 | if (timeoutInMs > 0 && elapsedTime >= timeoutInMs) { 45 | LOG.debug("Async task exhausted timeout of " + timeoutInMs + "ms, cancelling."); 46 | future.cancel(true); 47 | throw new ProcessCanceledException(); 48 | } 49 | } 50 | return future; 51 | } 52 | 53 | private static long waitFor(final int millis) { 54 | try { 55 | Thread.sleep(millis); 56 | } catch (InterruptedException ignored) { 57 | Thread.currentThread().interrupt(); 58 | } 59 | return millis; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanAllFilesTask.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.application.ReadAction; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.vfs.VfsUtilCore; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.intellij.openapi.vfs.VirtualFileVisitor; 8 | import org.infernus.idea.checkstyle.StaticScanner; 9 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.Callable; 15 | 16 | 17 | abstract class ScanAllFilesTask implements Callable { 18 | 19 | private final Project project; 20 | private final ConfigurationLocation selectedOverride; 21 | 22 | ScanAllFilesTask(@NotNull final Project project, 23 | final ConfigurationLocation selectedOverride) { 24 | this.project = project; 25 | this.selectedOverride = selectedOverride; 26 | } 27 | 28 | @Override 29 | public Void call() { 30 | project.getService(StaticScanner.class) 31 | .asyncScanFiles(flattenFiles(files()), selectedOverride); 32 | return null; 33 | } 34 | 35 | protected abstract VirtualFile[] files(); 36 | 37 | private List flattenFiles(final VirtualFile[] files) { 38 | final List flattened = new ArrayList<>(); 39 | if (files != null) { 40 | for (final VirtualFile file : files) { 41 | flattened.add(file); 42 | flattened.addAll(ReadAction.compute(() -> { 43 | final List flattenedChildren = new ArrayList<>(); 44 | VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor<>() { 45 | @Override 46 | @NotNull 47 | public Result visitFileEx(@NotNull final VirtualFile file) { 48 | flattenedChildren.add(file); 49 | return CONTINUE; 50 | } 51 | }); 52 | return flattenedChildren; 53 | })); 54 | } 55 | } 56 | return flattened; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/CheckstylePluginApi.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.config.PluginConfigurationManager; 5 | import org.infernus.idea.checkstyle.csapi.CheckstyleActions; 6 | import org.infernus.idea.checkstyle.csapi.ConfigurationModule; 7 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.HashMap; 12 | import java.util.SortedSet; 13 | import java.util.function.BiConsumer; 14 | 15 | /** 16 | * This class contains methods known to be used by external dependencies. 17 | *

18 | * We guarantee nothing, but try to keep compatibility :-) 19 | */ 20 | @SuppressWarnings("unused") 21 | public class CheckstylePluginApi { 22 | private final Project project; 23 | 24 | public CheckstylePluginApi(final Project project) { 25 | this.project = project; 26 | } 27 | 28 | @Nullable 29 | public ClassLoader currentCheckstyleClassLoader() { 30 | return checkstyleProjectService().underlyingClassLoader(); 31 | } 32 | 33 | public void visitCurrentConfiguration(@NotNull final ConfigurationVisitor visitor) { 34 | SortedSet activeLocations = pluginConfigurationManager().getCurrent().getActiveLocations(); 35 | 36 | activeLocations.forEach(it -> { 37 | CheckstyleActions checkstyleInstance = checkstyleProjectService().getCheckstyleInstance(); 38 | checkstyleInstance.peruseConfiguration( 39 | checkstyleInstance.loadConfiguration( 40 | it, 41 | new HashMap<>()), 42 | module -> visitor.accept(it.getDescription(), module)); 43 | }); 44 | } 45 | 46 | @SuppressWarnings("WeakerAccess") 47 | public interface ConfigurationVisitor extends BiConsumer { 48 | 49 | } 50 | 51 | private PluginConfigurationManager pluginConfigurationManager() { 52 | return project.getService(PluginConfigurationManager.class); 53 | } 54 | 55 | private CheckstyleProjectService checkstyleProjectService() { 56 | return project.getService(CheckstyleProjectService.class); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanModifiedFiles.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.Presentation; 5 | import com.intellij.openapi.diagnostic.Logger; 6 | import com.intellij.openapi.vcs.changes.ChangeListManager; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.List; 12 | 13 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 14 | 15 | /** 16 | * Scan modified files. 17 | *

18 | * If the project is not setup to use VCS then no files will be scanned. 19 | */ 20 | public class ScanModifiedFiles extends BaseAction { 21 | 22 | private static final Logger LOG = Logger.getInstance(ScanModifiedFiles.class); 23 | 24 | @Override 25 | public final void actionPerformed(final @NotNull AnActionEvent event) { 26 | project(event).ifPresent(project -> { 27 | try { 28 | List affectedFiles = ChangeListManager.getInstance(project).getAffectedFiles(); 29 | if (affectedFiles.isEmpty()) { 30 | CheckStyleToolWindowPanel checkStyleToolWindowPanel = CheckStyleToolWindowPanel.panelFor(project); 31 | if (checkStyleToolWindowPanel != null) { 32 | checkStyleToolWindowPanel.displayWarningResult("plugin.status.in-progress.no-modified-files"); 33 | } 34 | } else { 35 | staticScanner(project).asyncScanFiles( 36 | affectedFiles, 37 | getSelectedOverride(toolWindow(project))); 38 | } 39 | } catch (Throwable e) { 40 | LOG.warn("Modified files scan failed", e); 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | public void update(final @NotNull AnActionEvent event) { 47 | final Presentation presentation = event.getPresentation(); 48 | 49 | project(event).ifPresentOrElse( 50 | project -> presentation.setEnabled(!staticScanner(project).isScanInProgress()), 51 | () -> presentation.setEnabled(false)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/dtd/import_control_1_1.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 15 | 18 | 20 | 21 | 24 | 25 | 26 | 31 | 33 | 34 | 56 | 62 | 63 | 66 | 67 | 69 | 70 | 73 | 74 | 76 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/config/ConfigurationLocationSource.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.config; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.openapi.project.Project; 5 | import org.infernus.idea.checkstyle.model.ConfigurationLocation; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.Collections; 10 | import java.util.Objects; 11 | import java.util.SortedSet; 12 | import java.util.TreeSet; 13 | import java.util.stream.Collectors; 14 | 15 | public class ConfigurationLocationSource { 16 | 17 | private final Project project; 18 | 19 | public ConfigurationLocationSource(@NotNull final Project project) { 20 | this.project = project; 21 | } 22 | 23 | public SortedSet getConfigurationLocations(@Nullable final Module module, 24 | @Nullable final ConfigurationLocation override) { 25 | if (override != null) { 26 | return new TreeSet<>(Collections.singleton(override)); 27 | } 28 | 29 | if (module != null) { 30 | ModuleConfigurationState moduleConfiguration = checkstyleModuleConfiguration(module); 31 | if (moduleConfiguration.isExcluded()) { 32 | return Collections.emptySortedSet(); 33 | } 34 | 35 | PluginConfiguration configuration = configurationManager().getCurrent(); 36 | TreeSet moduleActiveConfigurations = moduleConfiguration.getActiveLocationIds().stream() 37 | .map(id -> configuration.getLocationById(id).orElse(null)) 38 | .filter(Objects::nonNull) 39 | .collect(Collectors.toCollection(TreeSet::new)); 40 | if (!moduleActiveConfigurations.isEmpty()) { 41 | return moduleActiveConfigurations; 42 | } 43 | } 44 | 45 | return configurationManager().getCurrent().getActiveLocations(); 46 | } 47 | 48 | private PluginConfigurationManager configurationManager() { 49 | return project.getService(PluginConfigurationManager.class); 50 | } 51 | 52 | private ModuleConfigurationState checkstyleModuleConfiguration(final Module module) { 53 | return module.getService(ModuleConfigurationState.class); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/BundledConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.infernus.idea.checkstyle.csapi.BundledConfig; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | import static org.infernus.idea.checkstyle.util.Streams.inMemoryCopyOf; 12 | 13 | 14 | public class BundledConfigurationLocation extends ConfigurationLocation { 15 | @NotNull 16 | private final BundledConfig bundledConfig; 17 | 18 | BundledConfigurationLocation(@NotNull final BundledConfig bundledConfig, 19 | @NotNull final Project project) { 20 | super(bundledConfig.getId(), ConfigurationType.BUNDLED, project); 21 | super.setLocation(bundledConfig.getLocation()); 22 | super.setDescription(bundledConfig.getDescription()); 23 | 24 | this.bundledConfig = bundledConfig; 25 | } 26 | 27 | @NotNull 28 | public BundledConfig getBundledConfig() { 29 | return bundledConfig; 30 | } 31 | 32 | @Override 33 | public void setLocation(final String location) { 34 | // do nothing, we always use the hard-coded location 35 | } 36 | 37 | @Override 38 | public void setDescription(@Nullable final String description) { 39 | // do nothing, we always use the hard-coded description 40 | } 41 | 42 | @Override 43 | @NotNull 44 | protected InputStream resolveFile(@NotNull final ClassLoader checkstyleClassLoader) throws IOException { 45 | try { 46 | InputStream source = checkstyleClassLoader.loadClass("com.puppycrawl.tools.checkstyle.Checker").getResourceAsStream(bundledConfig.getPath()); 47 | if (source == null) { 48 | throw new IOException("Could not read " + bundledConfig.getPath() + " from classpath"); 49 | } 50 | return inMemoryCopyOf(source); 51 | } catch (ClassNotFoundException e) { 52 | throw new IOException("Couldn't find Checkstyle on classpath", e); 53 | } 54 | } 55 | 56 | public boolean isRemovable() { 57 | return false; 58 | } 59 | 60 | 61 | @Override 62 | @NotNull 63 | public BundledConfigurationLocation clone() { 64 | return new BundledConfigurationLocation(bundledConfig, getProject()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ToolWindowAccess.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.wm.ToolWindow; 5 | import com.intellij.openapi.wm.ToolWindowManager; 6 | import com.intellij.ui.content.Content; 7 | import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import javax.swing.*; 11 | import java.awt.*; 12 | import java.util.function.Consumer; 13 | import java.util.function.Function; 14 | 15 | public final class ToolWindowAccess { 16 | private ToolWindowAccess() { 17 | } 18 | 19 | static void actOnToolWindowPanel(final ToolWindow toolWindow, final Consumer action) { 20 | final Content content = toolWindow.getContentManager().getContent(0); 21 | // the content instance will be a JLabel while the component initialises 22 | if (content != null && content.getComponent() instanceof CheckStyleToolWindowPanel) { 23 | action.accept((CheckStyleToolWindowPanel) content.getComponent()); 24 | } 25 | } 26 | 27 | static R getFromToolWindowPanel(final ToolWindow toolWindow, final Function action) { 28 | final Content content = toolWindow.getContentManager().getContent(0); 29 | // the content instance will be a JLabel while the component initialises 30 | if (content != null && content.getComponent() instanceof CheckStyleToolWindowPanel) { 31 | return action.apply((CheckStyleToolWindowPanel) content.getComponent()); 32 | } 33 | return null; 34 | } 35 | 36 | static ToolWindow toolWindow(final Project project) { 37 | return ToolWindowManager 38 | .getInstance(project) 39 | .getToolWindow(CheckStyleToolWindowPanel.ID_TOOLWINDOW); 40 | } 41 | 42 | static boolean isFocusInToolWindow(@NotNull final Project project) { 43 | final ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(CheckStyleToolWindowPanel.ID_TOOLWINDOW); 44 | if (toolWindow == null || !toolWindow.isVisible()) { 45 | return false; 46 | } 47 | final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 48 | return focusOwner != null && SwingUtilities.isDescendingFrom(focusOwner, toolWindow.getComponent()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/config/PluginConfigurationManager.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.config; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.TreeSet; 10 | 11 | public class PluginConfigurationManager { 12 | 13 | private final List configurationListeners = Collections.synchronizedList(new ArrayList<>()); 14 | 15 | private final Project project; 16 | 17 | private volatile PluginConfiguration cachedConfiguration; 18 | 19 | public PluginConfigurationManager(@NotNull final Project project) { 20 | this.project = project; 21 | } 22 | 23 | public void addConfigurationListener(final ConfigurationListener configurationListener) { 24 | if (configurationListener != null) { 25 | configurationListeners.add(configurationListener); 26 | } 27 | } 28 | 29 | private void fireConfigurationChanged() { 30 | synchronized (configurationListeners) { 31 | for (ConfigurationListener configurationListener : configurationListeners) { 32 | configurationListener.configurationChanged(); 33 | } 34 | } 35 | } 36 | 37 | public synchronized void disableActiveConfiguration() { 38 | setCurrent(PluginConfigurationBuilder.from(getCurrent()) 39 | .withActiveLocationIds(new TreeSet<>()) 40 | .build(), true); 41 | } 42 | 43 | @NotNull 44 | public synchronized PluginConfiguration getCurrent() { 45 | if (cachedConfiguration != null) { 46 | return cachedConfiguration; 47 | } 48 | 49 | cachedConfiguration = projectConfigurationState() 50 | .populate(PluginConfigurationBuilder.defaultConfiguration(project)) 51 | .build(); 52 | return cachedConfiguration; 53 | } 54 | 55 | public synchronized void setCurrent(@NotNull final PluginConfiguration updatedConfiguration, final boolean fireEvents) { 56 | cachedConfiguration = null; 57 | 58 | projectConfigurationState().setCurrentConfig(updatedConfiguration); 59 | if (fireEvents) { 60 | fireConfigurationChanged(); 61 | } 62 | } 63 | 64 | private ProjectConfigurationState projectConfigurationState() { 65 | return project.getService(ProjectConfigurationState.class); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/VersionListReaderTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle; 2 | 3 | 4 | import org.infernus.idea.checkstyle.exception.CheckStylePluginException; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | 9 | public class VersionListReaderTest { 10 | @Test 11 | public void testNormalLoading() { 12 | VersionListReader underTest = new VersionListReader(); 13 | Assert.assertNotNull(underTest.getSupportedVersions()); 14 | Assert.assertTrue(underTest.getSupportedVersions().size() > 1); 15 | Assert.assertNotNull(underTest.getDefaultVersion()); 16 | Assert.assertNotNull(underTest.getReplacementMap()); 17 | Assert.assertTrue(underTest.getReplacementMap().size() > 1); 18 | } 19 | 20 | 21 | @Test 22 | public void testNonExistingFile() { 23 | try { 24 | new VersionListReader("non-existent.file"); 25 | Assert.fail("expected exception was not thrown"); 26 | } catch (CheckStylePluginException e) { 27 | // expected 28 | Assert.assertTrue(e.getMessage().startsWith("Internal error: Could not read internal configuration file")); 29 | } 30 | } 31 | 32 | 33 | @Test 34 | public void testSupportedVersionMustNotBeMapped() { 35 | try { 36 | new VersionListReader("checkstyle-idea.broken1.properties"); 37 | Assert.fail("expected exception was not thrown"); 38 | } catch (CheckStylePluginException e) { 39 | // expected 40 | Assert.assertEquals("Internal error: Property 'checkstyle.versions.map' contains " 41 | + "invalid mapping '7.1 -> 7.2'. Checkstyle version 7.1 is in fact supported " 42 | + "in configuration file 'checkstyle-idea.broken1.properties'", e.getMessage()); 43 | } 44 | } 45 | 46 | 47 | @Test 48 | public void testTargetVersionMustExist() { 49 | try { 50 | new VersionListReader("checkstyle-idea.broken2.properties"); 51 | Assert.fail("expected exception was not thrown"); 52 | } catch (CheckStylePluginException e) { 53 | // expected 54 | Assert.assertEquals("Internal error: Property 'checkstyle.versions.map' contains " 55 | + "invalid mapping '7.0 -> 7.1.1'. Target version 7.1.1 is not a supported " 56 | + "version in configuration file 'checkstyle-idea.broken2.properties'", e.getMessage()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/LineLengthImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.lang.java.JavaLanguage; 4 | import com.intellij.psi.codeStyle.CodeStyleSettings; 5 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import static com.intellij.psi.codeStyle.CommonCodeStyleSettings.WRAP_AS_NEEDED; 10 | 11 | @SuppressWarnings("unused") 12 | public class LineLengthImporter extends ModuleImporter { 13 | 14 | private static final int DEFAULT_MAX_COLUMNS = 80; 15 | private static final String MAX_PROP = "max"; 16 | private int maxColumns = DEFAULT_MAX_COLUMNS; 17 | 18 | @Override 19 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 20 | if (MAX_PROP.equals(attrName)) { 21 | try { 22 | maxColumns = Integer.parseInt(attrValue); 23 | } catch (NumberFormatException nfe) { 24 | // ignore 25 | } 26 | } 27 | } 28 | 29 | @Override 30 | public void importTo(@NotNull final CodeStyleSettings settings) { 31 | settings.setRightMargin(JavaLanguage.INSTANCE, maxColumns); 32 | CommonCodeStyleSettings commonSettings = settings.getCommonSettings(JavaLanguage.INSTANCE); 33 | 34 | commonSettings.CALL_PARAMETERS_WRAP = WRAP_AS_NEEDED; 35 | commonSettings.METHOD_PARAMETERS_WRAP = WRAP_AS_NEEDED; 36 | commonSettings.RESOURCE_LIST_WRAP = WRAP_AS_NEEDED; 37 | commonSettings.EXTENDS_LIST_WRAP = WRAP_AS_NEEDED; 38 | commonSettings.THROWS_LIST_WRAP = WRAP_AS_NEEDED; 39 | commonSettings.EXTENDS_KEYWORD_WRAP = WRAP_AS_NEEDED; 40 | commonSettings.THROWS_KEYWORD_WRAP = WRAP_AS_NEEDED; 41 | commonSettings.METHOD_CALL_CHAIN_WRAP = WRAP_AS_NEEDED; 42 | commonSettings.BINARY_OPERATION_WRAP = WRAP_AS_NEEDED; 43 | commonSettings.TERNARY_OPERATION_WRAP = WRAP_AS_NEEDED; 44 | commonSettings.FOR_STATEMENT_WRAP = WRAP_AS_NEEDED; 45 | commonSettings.ARRAY_INITIALIZER_WRAP = WRAP_AS_NEEDED; 46 | commonSettings.ASSIGNMENT_WRAP = WRAP_AS_NEEDED; 47 | commonSettings.ASSERT_STATEMENT_WRAP = WRAP_AS_NEEDED; 48 | commonSettings.PARAMETER_ANNOTATION_WRAP = WRAP_AS_NEEDED; 49 | commonSettings.VARIABLE_ANNOTATION_WRAP = WRAP_AS_NEEDED; 50 | commonSettings.ENUM_CONSTANTS_WRAP = WRAP_AS_NEEDED; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ProblemResultTreeInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.psi.PsiFile; 5 | import org.infernus.idea.checkstyle.CheckStyleBundle; 6 | import org.infernus.idea.checkstyle.csapi.SeverityLevel; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ProblemResultTreeInfo extends ResultTreeNode { 10 | 11 | private final PsiFile file; 12 | private final ResultProblem problem; 13 | private final SeverityLevel severity; 14 | 15 | /** 16 | * Construct a node for a given problem. 17 | * 18 | * @param file the file the problem exists in. 19 | * @param problem the problem. 20 | */ 21 | ProblemResultTreeInfo(@NotNull final PsiFile file, 22 | @NotNull final ResultProblem problem) { 23 | super(CheckStyleBundle.message("plugin.results.file-result", 24 | file.getName(), 25 | problem.message(), 26 | problem.line(), 27 | Integer.toString(problem.column()), 28 | problem.sourceCheck(), 29 | problem.locationDescription())); 30 | 31 | this.file = file; 32 | this.problem = problem; 33 | 34 | severity = problem.severityLevel(); 35 | 36 | updateIconsForProblem(); 37 | } 38 | 39 | private void updateIconsForProblem() { 40 | if (SeverityLevel.Ignore.equals(severity)) { 41 | setIcon(AllIcons.General.Note); 42 | } else if (SeverityLevel.Warning.equals(severity)) { 43 | setIcon(AllIcons.General.Warning); 44 | } else if (SeverityLevel.Info.equals(severity)) { 45 | setIcon(AllIcons.General.Information); 46 | } else { 47 | setIcon(AllIcons.General.Error); 48 | } 49 | } 50 | 51 | /** 52 | * Get the file associated with this node. 53 | * 54 | * @return the file associated with this node. 55 | */ 56 | public PsiFile getFile() { 57 | return file; 58 | } 59 | 60 | /** 61 | * Get the problem associated with this node. 62 | * 63 | * @return the problem associated with this node. 64 | */ 65 | ResultProblem getProblem() { 66 | return problem; 67 | } 68 | 69 | /** 70 | * Get the severity of the problem. 71 | * 72 | * @return the severity, or null if not applicable. 73 | */ 74 | public SeverityLevel getSeverity() { 75 | return severity; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/resources/dtd/import_control_1_2.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 15 | 18 | 21 | 22 | 25 | 26 | 27 | 33 | 36 | 37 | 59 | 65 | 66 | 69 | 70 | 72 | 73 | 76 | 77 | 79 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/IndentationImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.psi.codeStyle.CodeStyleSettings; 5 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 6 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @SuppressWarnings("unused") 10 | public class IndentationImporter extends ModuleImporter { 11 | 12 | private static final Logger LOG = Logger.getInstance(IndentationImporter.class); 13 | 14 | private static final String BASIC_OFFSET_PROP = "basicOffset"; 15 | private static final String CASE_INDENT_PROP = "caseIndent"; 16 | private static final String LINE_WRAP_INDENT_PROP = "lineWrappingIndentation"; 17 | 18 | private static final int DEFAULT_BASIC_OFFSET = 4; 19 | private static final int DEFAULT_LINE_WRAP_INDENT = 4; 20 | private static final boolean DEFAULT_INDENT_CASE = true; 21 | 22 | private int basicIndent = DEFAULT_BASIC_OFFSET; 23 | private int continuationIndent = DEFAULT_LINE_WRAP_INDENT; 24 | private boolean indentCase = DEFAULT_INDENT_CASE; 25 | 26 | @Override 27 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 28 | switch (attrName) { 29 | case BASIC_OFFSET_PROP: 30 | basicIndent = getIntOrDefault(attrValue, DEFAULT_BASIC_OFFSET); 31 | break; 32 | case CASE_INDENT_PROP: 33 | int caseIndent = getIntOrDefault(attrValue, 0); 34 | indentCase = caseIndent > 0; 35 | break; 36 | case LINE_WRAP_INDENT_PROP: 37 | continuationIndent = getIntOrDefault(attrValue, DEFAULT_LINE_WRAP_INDENT); 38 | break; 39 | 40 | default: 41 | // uncharted territory - https://checkstyle.org/property_types.html#LeftCurlyOption 42 | LOG.warn("Unexpected indentation policy: " + attrValue); 43 | break; 44 | } 45 | } 46 | 47 | @Override 48 | public void importTo(@NotNull final CodeStyleSettings settings) { 49 | CommonCodeStyleSettings commonSettings = getCommonSettings(settings); 50 | CommonCodeStyleSettings.IndentOptions indentOptions = commonSettings.getIndentOptions(); 51 | if (indentOptions != null) { 52 | indentOptions.INDENT_SIZE = basicIndent; 53 | indentOptions.CONTINUATION_INDENT_SIZE = continuationIndent; 54 | } 55 | commonSettings.INDENT_CASE_FROM_SWITCH = indentCase; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/InsecureHTTPURLConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.net.ssl.HttpsURLConnection; 7 | import javax.net.ssl.SSLContext; 8 | import javax.net.ssl.TrustManager; 9 | import javax.net.ssl.X509TrustManager; 10 | import java.io.IOException; 11 | import java.net.URLConnection; 12 | import java.security.KeyManagementException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.cert.X509Certificate; 15 | 16 | /** 17 | * A configuration location located on a HTTP server for which we wish to ignore any SSL 18 | * errors. Caveat emptor. 19 | */ 20 | public class InsecureHTTPURLConfigurationLocation extends HTTPURLConfigurationLocation { 21 | 22 | public InsecureHTTPURLConfigurationLocation(@NotNull final Project project, 23 | @NotNull final String id) { 24 | super(id, ConfigurationType.INSECURE_HTTP_URL, project); 25 | } 26 | 27 | @Override 28 | @NotNull URLConnection connectionTo(final String location) throws IOException { 29 | final URLConnection urlConnection = super.connectionTo(location); 30 | 31 | if (urlConnection instanceof HttpsURLConnection httpsURLConnection) { 32 | try { 33 | final TrustManager[] trustAllCerts = new TrustManager[]{new AllTrustingTrustManager()}; 34 | final SSLContext sc = SSLContext.getInstance("TLSv1.3"); 35 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 36 | httpsURLConnection.setSSLSocketFactory(sc.getSocketFactory()); 37 | } catch (NoSuchAlgorithmException | KeyManagementException e) { 38 | throw new IOException("Failed to set an insecure SSL socket factory", e); 39 | } 40 | } 41 | 42 | return urlConnection; 43 | } 44 | 45 | private static final class AllTrustingTrustManager implements X509TrustManager { 46 | @Override 47 | public X509Certificate[] getAcceptedIssuers() { 48 | return new X509Certificate[] {}; 49 | } 50 | 51 | @Override 52 | public void checkClientTrusted(final X509Certificate[] certs, final String authType) { 53 | } 54 | 55 | @Override 56 | public void checkServerTrusted(final X509Certificate[] certs, final String authType) { 57 | } 58 | } 59 | 60 | @Override 61 | public Object clone() { 62 | return cloneCommonPropertiesTo(new InsecureHTTPURLConfigurationLocation(getProject(), getId())); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/NamedScopeHelper.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.application.ReadAction; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.packageDependencies.DependencyValidationManager; 6 | import com.intellij.psi.PsiFile; 7 | import com.intellij.psi.search.scope.packageSet.NamedScope; 8 | import com.intellij.psi.search.scope.packageSet.NamedScopeManager; 9 | import com.intellij.psi.search.scope.packageSet.PackageSet; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.Arrays; 13 | import java.util.stream.Stream; 14 | 15 | public final class NamedScopeHelper { 16 | 17 | private NamedScopeHelper() { 18 | } 19 | 20 | public static final String DEFAULT_SCOPE_ID = "All"; 21 | 22 | /** 23 | * Returns the scope with the given id. 24 | * If no scope with this ID exists, the Scope with the {@link #DEFAULT_SCOPE_ID} is being returned. 25 | */ 26 | @NotNull 27 | public static NamedScope getScopeByIdWithDefaultFallback(@NotNull final Project project, 28 | @NotNull final String id) { 29 | final NamedScope localScopeOrNull = NamedScopeManager.getInstance(project).getScope(id); 30 | if (localScopeOrNull != null) { 31 | return localScopeOrNull; 32 | } 33 | final NamedScope sharedScopeOrNull = DependencyValidationManager.getInstance(project).getScope(id); 34 | if (sharedScopeOrNull != null) { 35 | return sharedScopeOrNull; 36 | } 37 | 38 | return getDefaultScope(project); 39 | } 40 | 41 | public static Stream getAllScopes(@NotNull final Project project) { 42 | return Stream.concat( 43 | Arrays.stream(NamedScopeManager.getInstance(project).getScopes()), 44 | Arrays.stream(DependencyValidationManager.getInstance(project).getScopes())); 45 | } 46 | 47 | public static NamedScope getDefaultScope(@NotNull final Project project) { 48 | return DependencyValidationManager.getInstance(project).getScope(DEFAULT_SCOPE_ID); 49 | } 50 | 51 | public static boolean isFileInScope(final PsiFile psiFile, @NotNull final NamedScope namedScope) { 52 | return ReadAction.compute(() -> { 53 | final PackageSet packageSet = namedScope.getValue(); 54 | if (packageSet == null) { 55 | return true; 56 | } 57 | 58 | return packageSet.contains( 59 | psiFile, 60 | DependencyValidationManager.getInstance(psiFile.getProject())); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/util/ModulePaths.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.util; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.module.Module; 5 | import com.intellij.openapi.roots.CompilerModuleExtension; 6 | import com.intellij.openapi.roots.libraries.LibraryUtil; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.io.File; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static java.util.Collections.emptyList; 17 | 18 | public final class ModulePaths { 19 | 20 | private static final Logger LOG = Logger.getInstance(ModulePaths.class); 21 | 22 | private ModulePaths() { 23 | // utility class 24 | } 25 | 26 | public static List libraryPathsFor(final Module moduleInScope) { 27 | return pathsOf(libraryRootsFor(moduleInScope)); 28 | } 29 | 30 | private static VirtualFile[] libraryRootsFor(final Module module) { 31 | return LibraryUtil.getLibraryRoots(new Module[]{module}, false, false); 32 | } 33 | 34 | public static List compilerOutputPathsFor(final Module module) { 35 | final CompilerModuleExtension compilerModule = CompilerModuleExtension.getInstance(module); 36 | if (compilerModule != null) { 37 | return pathsOf(compilerModule.getOutputRoots(true)); 38 | } 39 | return emptyList(); 40 | } 41 | 42 | private static List pathsOf(final VirtualFile[] files) { 43 | final List outputPaths = new ArrayList<>(); 44 | for (final VirtualFile file : files) { 45 | try { 46 | outputPaths.add(urlFor(pathOf(file))); 47 | } catch (MalformedURLException e) { 48 | LOG.warn("Malformed virtual file URL: " + file, e); 49 | } 50 | } 51 | return outputPaths; 52 | } 53 | 54 | @NotNull 55 | private static URL urlFor(final String filePath) throws MalformedURLException { 56 | // toURI().toURL() escapes, whereas toURL() doesn't. 57 | return new File(filePath).toURI().toURL(); 58 | } 59 | 60 | @NotNull 61 | private static String pathOf(final VirtualFile file) { 62 | return stripJarFileSuffix(file); 63 | } 64 | 65 | @NotNull 66 | private static String stripJarFileSuffix(final VirtualFile file) { 67 | final String filePath = file.getPath(); 68 | if (filePath.endsWith("!/")) { // filter JAR suffix 69 | return filePath.substring(0, filePath.length() - 2); 70 | } 71 | return filePath; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/importer/modules/AvoidStarImportImporter.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.importer.modules; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.psi.codeStyle.CodeStyleSettings; 5 | import com.intellij.psi.codeStyle.JavaCodeStyleSettings; 6 | import com.intellij.psi.codeStyle.PackageEntry; 7 | import com.intellij.psi.codeStyle.PackageEntryTable; 8 | import org.infernus.idea.checkstyle.importer.ModuleImporter; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | @SuppressWarnings("unused") 12 | public class AvoidStarImportImporter extends ModuleImporter { 13 | private static final Logger LOG = Logger.getInstance(AvoidStarImportImporter.class); 14 | 15 | private static final String EXCLUDES = "excludes"; 16 | private static final String ALLOW_CLASS_STAR_IMPORT = "allowClassImports"; 17 | private static final String ALLOW_STATIC_STAR_IMPORT = "allowStaticMemberImports"; 18 | private static final int MAXIMUM_INPUTS = 999; 19 | 20 | private boolean allowClassStarImports; 21 | private boolean allowStaticStarImports; 22 | private String[] excludes; 23 | 24 | @Override 25 | protected void handleAttribute(@NotNull final String attrName, @NotNull final String attrValue) { 26 | switch (attrName) { 27 | case EXCLUDES: 28 | excludes = attrValue.split(","); 29 | break; 30 | case ALLOW_CLASS_STAR_IMPORT: 31 | allowClassStarImports = Boolean.parseBoolean(attrValue); 32 | break; 33 | case ALLOW_STATIC_STAR_IMPORT: 34 | allowStaticStarImports = Boolean.parseBoolean(attrValue); 35 | break; 36 | default: 37 | LOG.warn("Unexpected avoid star import policy: " + attrValue); 38 | break; 39 | } 40 | 41 | } 42 | 43 | @Override 44 | public void importTo(@NotNull final CodeStyleSettings settings) { 45 | JavaCodeStyleSettings javaSettings = getJavaSettings(settings); 46 | if (!allowClassStarImports) { 47 | javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = MAXIMUM_INPUTS; 48 | } 49 | 50 | if (!allowStaticStarImports) { 51 | javaSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = MAXIMUM_INPUTS; 52 | } 53 | 54 | PackageEntryTable excludeTable = new PackageEntryTable(); 55 | if (excludes != null) { 56 | for (String exclude : excludes) { 57 | excludeTable.addEntry(new PackageEntry(false, exclude, false)); 58 | } 59 | } 60 | javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.copyFrom(excludeTable); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/toolwindow/ToggleableTreeNode.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.toolwindow; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import javax.swing.tree.DefaultMutableTreeNode; 6 | import javax.swing.tree.TreeNode; 7 | import java.io.Serial; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * Tree node with toggleable visibility. 14 | */ 15 | public class ToggleableTreeNode extends DefaultMutableTreeNode { 16 | @Serial 17 | private static final long serialVersionUID = -4490734768175672868L; 18 | 19 | private boolean visible = true; 20 | 21 | public ToggleableTreeNode() { 22 | } 23 | 24 | public ToggleableTreeNode(final Object userObject) { 25 | super(userObject); 26 | } 27 | 28 | public boolean isVisible() { 29 | return visible; 30 | } 31 | 32 | public void setVisible(final boolean visible) { 33 | this.visible = visible; 34 | } 35 | 36 | @NotNull 37 | List getAllChildren() { 38 | if (children != null) { 39 | return children.stream() 40 | .map(child -> (ToggleableTreeNode) child) 41 | .collect(Collectors.toList()); 42 | } 43 | return Collections.emptyList(); 44 | } 45 | 46 | @Override 47 | public void removeAllChildren() { 48 | if (children != null) { 49 | children.clear(); 50 | } 51 | } 52 | 53 | @Override 54 | public TreeNode getChildAt(final int index) { 55 | if (children == null) { 56 | throw new ArrayIndexOutOfBoundsException("Invalid index: " + index + " (no children)"); 57 | } 58 | 59 | int realIndex = -1; 60 | int visibleIndex = -1; 61 | 62 | for (final Object child : children) { 63 | final ToggleableTreeNode node = (ToggleableTreeNode) child; 64 | if (node.isVisible()) { 65 | ++visibleIndex; 66 | } 67 | ++realIndex; 68 | if (visibleIndex == index) { 69 | return children.get(realIndex); 70 | } 71 | } 72 | 73 | throw new ArrayIndexOutOfBoundsException("Invalid index: " + index); 74 | } 75 | 76 | @Override 77 | public int getChildCount() { 78 | if (children == null) { 79 | return 0; 80 | } 81 | 82 | int count = 0; 83 | for (final Object child : children) { 84 | final ToggleableTreeNode node = (ToggleableTreeNode) child; 85 | if (node.isVisible()) { 86 | ++count; 87 | } 88 | } 89 | 90 | return count; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/csaccessTest/java/org/infernus/idea/checkstyle/service/CsVersionInfo.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.service; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.TypeSafeMatcher; 6 | import org.infernus.idea.checkstyle.VersionComparator; 7 | import org.infernus.idea.checkstyle.exception.CheckStylePluginException; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.junit.Assert; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.Properties; 14 | 15 | import static org.junit.Assert.assertNotNull; 16 | 17 | 18 | /** 19 | * Utility class for unit tests in 'csaccessTest' to query the currently used Checkstyle runtime. 20 | */ 21 | public final class CsVersionInfo { 22 | private static final String PROPS_FILE_NAME = "/checkstyle-idea.properties"; 23 | private static final String BASE_VERSION = readBaseVersion(); 24 | 25 | public static final String CSVERSION_SYSPROP_NAME = "org.infernus.idea.checkstyle.version"; 26 | 27 | private CsVersionInfo() { 28 | } 29 | 30 | @NotNull 31 | private static String readBaseVersion() { 32 | String result; 33 | try (InputStream is = CsVersionInfo.class.getResourceAsStream(PROPS_FILE_NAME)) { 34 | Properties props = new Properties(); 35 | props.load(is); 36 | result = props.getProperty("baseVersion"); 37 | } catch (IOException e) { 38 | throw new CheckStylePluginException("internal error - Failed to read property file: " + PROPS_FILE_NAME, e); 39 | } 40 | assertNotNull(result); 41 | return result; 42 | } 43 | 44 | @NotNull 45 | public static String currentCsVersion() { 46 | final String sysPropValue = System.getProperty(CSVERSION_SYSPROP_NAME); 47 | if (sysPropValue == null) { 48 | return BASE_VERSION; 49 | } else { 50 | Assert.assertTrue("System property \"" + CSVERSION_SYSPROP_NAME // 51 | + "\" does not contain a valid Checkstyle version: " + sysPropValue, // 52 | VersionComparator.isValidVersion(System.getProperty(CSVERSION_SYSPROP_NAME))); 53 | return sysPropValue; 54 | } 55 | } 56 | 57 | public static Matcher isGreaterThanOrEqualTo(@NotNull final String expectedCsVersion) { 58 | return new TypeSafeMatcher<>() { 59 | @Override 60 | protected boolean matchesSafely(final String actualCsVersion) { 61 | return new VersionComparator().compare(actualCsVersion, expectedCsVersion) >= 0; 62 | } 63 | 64 | @Override 65 | public void describeTo(final Description description) { 66 | description.appendText("is greater than").appendValue(expectedCsVersion); 67 | } 68 | }; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/actions/ScanCurrentChangeList.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.Presentation; 5 | import com.intellij.openapi.diagnostic.Logger; 6 | import com.intellij.openapi.vcs.changes.Change; 7 | import com.intellij.openapi.vcs.changes.ChangeListManager; 8 | import com.intellij.openapi.vcs.changes.LocalChangeList; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.*; 13 | 14 | import static org.infernus.idea.checkstyle.actions.ToolWindowAccess.toolWindow; 15 | 16 | /** 17 | * Scan files in the current change-list. 18 | */ 19 | public class ScanCurrentChangeList extends BaseAction { 20 | 21 | private static final Logger LOG = Logger.getInstance(ScanCurrentChangeList.class); 22 | 23 | @Override 24 | public final void actionPerformed(final @NotNull AnActionEvent event) { 25 | project(event).ifPresent(project -> { 26 | try { 27 | final ChangeListManager changeListManager = ChangeListManager.getInstance(project); 28 | staticScanner(project).asyncScanFiles(filesFor(changeListManager.getDefaultChangeList()), getSelectedOverride(toolWindow(project))); 29 | } catch (Throwable e) { 30 | LOG.warn("Modified files scan failed", e); 31 | } 32 | }); 33 | } 34 | 35 | private List filesFor(final LocalChangeList changeList) { 36 | if (changeList == null || changeList.getChanges() == null) { 37 | return Collections.emptyList(); 38 | } 39 | 40 | final Collection filesInChanges = new HashSet<>(); 41 | for (Change change : changeList.getChanges()) { 42 | if (change.getVirtualFile() != null) { 43 | filesInChanges.add(change.getVirtualFile()); 44 | } 45 | } 46 | 47 | return new ArrayList<>(filesInChanges); 48 | } 49 | 50 | @Override 51 | public void update(final @NotNull AnActionEvent event) { 52 | final Presentation presentation = event.getPresentation(); 53 | 54 | project(event).ifPresentOrElse(project -> { 55 | try { 56 | final LocalChangeList changeList = ChangeListManager.getInstance(project).getDefaultChangeList(); 57 | if (changeList.getChanges() == null || changeList.getChanges().isEmpty()) { 58 | presentation.setEnabled(false); 59 | } else { 60 | presentation.setEnabled(!staticScanner(project).isScanInProgress()); 61 | } 62 | 63 | } catch (Throwable e) { 64 | LOG.warn("Button update failed.", e); 65 | } 66 | }, () -> presentation.setEnabled(false)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/infernus/idea/checkstyle/model/ClasspathConfigurationLocation.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.model; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.project.Project; 5 | import org.infernus.idea.checkstyle.CheckstyleProjectService; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.FileNotFoundException; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | import static org.infernus.idea.checkstyle.util.Streams.readContentOf; 14 | 15 | public class ClasspathConfigurationLocation extends ConfigurationLocation { 16 | 17 | private static final Logger LOG = Logger.getInstance(ClasspathConfigurationLocation.class); 18 | 19 | private static final int CONTENT_CACHE_SECONDS = 2; 20 | private static final int ONE_SECOND = 1000; 21 | 22 | private byte[] cachedContent; 23 | private long cacheExpiry; 24 | 25 | ClasspathConfigurationLocation(@NotNull final Project project, 26 | @NotNull final String id) { 27 | super(id, ConfigurationType.PLUGIN_CLASSPATH, project); 28 | } 29 | 30 | @NotNull 31 | protected InputStream resolveFile(@NotNull final ClassLoader checkstyleClassLoader) throws IOException { 32 | if (cachedContent != null && cacheExpiry > System.currentTimeMillis()) { 33 | return new ByteArrayInputStream(cachedContent); 34 | } 35 | 36 | try { 37 | cachedContent = readContentOf(streamOf(getLocation())); 38 | cacheExpiry = System.currentTimeMillis() + (CONTENT_CACHE_SECONDS * ONE_SECOND); 39 | return new ByteArrayInputStream(cachedContent); 40 | 41 | } catch (IOException e) { 42 | LOG.info("Couldn't read file from classpath: " + getLocation(), e); 43 | cachedContent = null; 44 | cacheExpiry = 0; 45 | throw e; 46 | } 47 | } 48 | 49 | private InputStream streamOf(final String classpathLocation) throws IOException { 50 | InputStream resourceStream = checkstyleClassLoader().getResourceAsStream(classpathLocation); 51 | if (resourceStream == null) { 52 | throw new FileNotFoundException("Couldn't read classpath resource: " + classpathLocation); 53 | } 54 | return resourceStream; 55 | } 56 | 57 | private ClassLoader checkstyleClassLoader() { 58 | return checkstyleProjectService(getProject()).underlyingClassLoader(); 59 | } 60 | 61 | private CheckstyleProjectService checkstyleProjectService(@NotNull final Project project) { 62 | // we can't bring this in at construction time due to a cyclic dep 63 | return project.getService(CheckstyleProjectService.class); 64 | } 65 | 66 | @Override 67 | public Object clone() { 68 | return cloneCommonPropertiesTo(new ClasspathConfigurationLocation(getProject(), getId())); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/org/infernus/idea/checkstyle/csapi/ProcessResultsThreadTest.java: -------------------------------------------------------------------------------- 1 | package org.infernus.idea.checkstyle.csapi; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.psi.PsiFile; 5 | import org.infernus.idea.checkstyle.checker.Problem; 6 | import org.junit.Test; 7 | 8 | import java.util.*; 9 | 10 | import static java.util.Collections.singletonList; 11 | import static org.hamcrest.Matchers.hasEntry; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.when; 15 | 16 | public class ProcessResultsThreadTest { 17 | 18 | @Test 19 | public void fileWithMatchingPathIsLinked() { 20 | PsiFile expectedFile = aPsiFile(); 21 | Map fileNamesToPsiFiles = new HashMap<>(); 22 | fileNamesToPsiFiles.put("aFileName", expectedFile); 23 | fileNamesToPsiFiles.put("anotherFileName", mock(PsiFile.class)); 24 | 25 | ProcessResultsThread underTest = underTest(fileNamesToPsiFiles, singletonList(anIssueFor("aFileName"))); 26 | underTest.run(); 27 | 28 | assertThat(underTest.getProblems(), 29 | hasEntry(expectedFile, singletonList(aProblemFor(expectedFile)))); 30 | } 31 | 32 | @Test 33 | public void fileWithMatchingButDenormalisedPathIsLinked() { 34 | PsiFile expectedFile = aPsiFile(); 35 | 36 | Map fileNamesToPsiFiles = new HashMap<>(); 37 | fileNamesToPsiFiles.put("aFileName", expectedFile); 38 | fileNamesToPsiFiles.put("anotherFileName", mock(PsiFile.class)); 39 | 40 | List events = singletonList(anIssueFor("foo/bar/../../aFileName")); 41 | 42 | ProcessResultsThread underTest = underTest(fileNamesToPsiFiles, events); 43 | 44 | underTest.run(); 45 | 46 | assertThat(underTest.getProblems(), 47 | hasEntry(expectedFile, singletonList(aProblemFor(expectedFile)))); 48 | } 49 | 50 | private ProcessResultsThread underTest(final Map fileNamesToPsiFiles, final List events) { 51 | return new ProcessResultsThread( 52 | false, Collections.emptyList(), 4, Optional.empty(), events, fileNamesToPsiFiles); 53 | } 54 | 55 | private PsiFile aPsiFile() { 56 | PsiFile expectedFile = mock(PsiFile.class); 57 | when(expectedFile.textToCharArray()).thenReturn("import boo.*;".toCharArray()); 58 | when(expectedFile.findElementAt(6)).thenReturn(mock(PsiElement.class)); 59 | return expectedFile; 60 | } 61 | 62 | private Issue anIssueFor(final String aFileName) { 63 | return new Issue(aFileName, 1, 7, "aMessage", SeverityLevel.Error, "com.checkstyle.rules.aCheck"); 64 | } 65 | 66 | private Problem aProblemFor(final PsiFile expectedFile) { 67 | return new Problem(expectedFile, "aMessage", SeverityLevel.Error, 1, 7, "com.checkstyle.rules.aCheck", false, false); 68 | } 69 | 70 | } 71 | --------------------------------------------------------------------------------