├── .java-version ├── gradle ├── gradle-daemon-jvm.properties └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── .gitignore ├── src ├── main │ ├── resources │ │ ├── META-INF │ │ │ ├── lint-rules │ │ │ │ ├── space-assignment.properties │ │ │ │ ├── deprecated-task-operator.properties │ │ │ │ ├── multiproject-circular-dependency.properties │ │ │ │ ├── deprecated-dependency-configuration.properties │ │ │ │ ├── bypassed-forces.properties │ │ │ │ ├── rename-nebula-deb.properties │ │ │ │ ├── rename-nebula-facet.properties │ │ │ │ ├── rename-nebula-info.properties │ │ │ │ ├── rename-nebula-stash.properties │ │ │ │ ├── unused-dependency.properties │ │ │ │ ├── recommended-versions.properties │ │ │ │ ├── rename-nebula-clojure.properties │ │ │ │ ├── rename-nebula-git-scm.properties │ │ │ │ ├── rename-nebula-info-ci.properties │ │ │ │ ├── rename-nebula-javadoc-jar.properties │ │ │ │ ├── rename-nebula-override.properties │ │ │ │ ├── rename-nebula-release.properties │ │ │ │ ├── rename-nebula-source-jar.properties │ │ │ │ ├── undeclared-dependency.properties │ │ │ │ ├── dependency-tuple.properties │ │ │ │ ├── rename-nebula-integtest.properties │ │ │ │ ├── rename-nebula-ospackage.properties │ │ │ │ ├── unused-exclude-by-dep.properties │ │ │ │ ├── dependency-parentheses.properties │ │ │ │ ├── rename-nebula-dependency-lock.properties │ │ │ │ ├── unused-exclude-by-conf.properties │ │ │ │ ├── minimum-dependency-version.properties │ │ │ │ ├── duplicate-dependency-class.properties │ │ │ │ ├── rename-nebula-ospackage-application.properties │ │ │ │ ├── transitive-duplicate-dependency-class.properties │ │ │ │ ├── rename-nebula-ospackage-application-daemon.properties │ │ │ │ ├── all-dependency.properties │ │ │ │ └── all-nebula-renames.properties │ │ │ └── gradle-plugins │ │ │ │ └── nebula.configEnvironment.properties │ │ └── log4j.properties │ ├── groovy │ │ └── com │ │ │ └── netflix │ │ │ └── nebula │ │ │ ├── lint │ │ │ ├── utils │ │ │ │ ├── IndentUtils.groovy │ │ │ │ └── DeprecationLoggerUtils.groovy │ │ │ ├── rule │ │ │ │ ├── GradlePlugin.groovy │ │ │ │ ├── dependency │ │ │ │ │ ├── FirstOrderDuplicateDependencyClassRule.groovy │ │ │ │ │ ├── TransitiveDuplicateDepenencyClassRule.groovy │ │ │ │ │ ├── ResolvedArtifactInfo.groovy │ │ │ │ │ ├── JarContents.groovy │ │ │ │ │ ├── provider │ │ │ │ │ │ ├── RecommendationProvider.java │ │ │ │ │ │ └── AbstractRecommendationProvider.java │ │ │ │ │ ├── ClassInformation.groovy │ │ │ │ │ ├── ModuleDescriptor.groovy │ │ │ │ │ ├── DependencyViolationUtil.groovy │ │ │ │ │ ├── MethodReference.groovy │ │ │ │ │ ├── DependencyTupleExpressionRule.groovy │ │ │ │ │ ├── DependencyParenthesesRule.groovy │ │ │ │ │ ├── ClassHierarchyUtils.groovy │ │ │ │ │ ├── UnusedExcludeByConfigurationRule.groovy │ │ │ │ │ ├── MultiProjectCircularDependencyRule.groovy │ │ │ │ │ ├── UnusedDependencyExcludeRule.groovy │ │ │ │ │ └── AbstractDuplicateDependencyClassRule.groovy │ │ │ │ ├── rename │ │ │ │ │ ├── RenameNebulaDebRule.groovy │ │ │ │ │ ├── RenameNebulaInfoRule.groovy │ │ │ │ │ ├── RenameNebulaInfoCiRule.groovy │ │ │ │ │ ├── RenameNebulaClojureRule.groovy │ │ │ │ │ ├── RenameNebulaFacetRule.groovy │ │ │ │ │ ├── RenameNebulaReleaseRule.groovy │ │ │ │ │ ├── RenameNebulaStashRule.groovy │ │ │ │ │ ├── RenameNebulaGitScmRule.groovy │ │ │ │ │ ├── RenameNebulaIntegTestRule.groovy │ │ │ │ │ ├── RenameNebulaOspackageRule.groovy │ │ │ │ │ ├── RenameNebulaSourceJarRule.groovy │ │ │ │ │ ├── RenameNebulaOverrideRule.groovy │ │ │ │ │ ├── RenameNebulaJavadocJarRule.groovy │ │ │ │ │ ├── RenameNebulaDependencyLockRule.groovy │ │ │ │ │ ├── RenameNebulaOspackageDaemonRule.groovy │ │ │ │ │ ├── RenameNebulaOspackageApplicationRule.groovy │ │ │ │ │ ├── RenameNebulaOspackageApplicationDaemonRule.groovy │ │ │ │ │ └── PluginRenamedRule.groovy │ │ │ │ ├── FixmeRule.groovy │ │ │ │ ├── GradleAstUtil.groovy │ │ │ │ ├── dsl │ │ │ │ │ └── SpaceAssignmentRule.groovy │ │ │ │ ├── task │ │ │ │ │ └── TaskDefinitionOperatorRule.groovy │ │ │ │ └── GradleDependency.groovy │ │ │ ├── SourceSetUtils.groovy │ │ │ ├── GradleLintViolationAction.groovy │ │ │ ├── plugin │ │ │ │ ├── report │ │ │ │ │ └── internal │ │ │ │ │ │ ├── LintTextReport.groovy │ │ │ │ │ │ ├── LintHtmlReport.groovy │ │ │ │ │ │ └── LintXmlReport.groovy │ │ │ │ ├── RuleSetFactory.groovy │ │ │ │ ├── InvalidRuleException.groovy │ │ │ │ ├── UnexpectedLintRuleFailureException.groovy │ │ │ │ ├── SourceCollector.groovy │ │ │ │ ├── GradleLintPlugin.groovy │ │ │ │ ├── LintRuleDescriptor.groovy │ │ │ │ ├── GradleLintExtension.groovy │ │ │ │ ├── NotNecessarilyGitRepository.groovy │ │ │ │ ├── AppliedFilesAstVisitor.groovy │ │ │ │ ├── AbstractLintPluginTaskConfigurer.groovy │ │ │ │ └── LintRuleRegistry.groovy │ │ │ ├── UnfixedViolationReason.groovy │ │ │ ├── FileMode.groovy │ │ │ ├── GradleLintInfoBrokerAction.groovy │ │ │ ├── postprocess │ │ │ │ └── EmptyClosureRule.groovy │ │ │ └── StyledTextService.groovy │ │ │ └── config │ │ │ └── plugin │ │ │ ├── ConfigurationEnvironmentPrintTask.groovy │ │ │ ├── ConfigurationEnvironmentPlugin.groovy │ │ │ └── DependencyHierarchyWriter.groovy │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── nebula │ │ └── lint │ │ └── plugin │ │ └── GradleLintDeprecationEmitterPlugin.kt └── test │ ├── resources │ └── META-INF │ │ └── lint-rules │ │ ├── test-user-action-required.properties │ │ ├── test-dependency-replace.properties │ │ ├── test-dependency-remove-version.properties │ │ ├── test-fails-to-apply-successfully.properties │ │ ├── test-dependency-replace-version.properties │ │ └── test-fails-to-apply-successfully-with-extra-context.properties │ ├── java │ └── com │ │ └── netflix │ │ └── nebula │ │ └── lint │ │ └── GradleVersions.java │ └── groovy │ └── com │ └── netflix │ └── nebula │ └── lint │ ├── BaseIntegrationTestKitSpec.groovy │ ├── rule │ ├── AbstractExampleGradleLintRule.groovy │ ├── AbstractModelAwareExampleGradleLintRule.groovy │ ├── GradleDependencySpec.groovy │ ├── FixmeRuleSpec.groovy │ ├── GradleLintRuleIntegSpec.groovy │ ├── dependency │ │ ├── ClassHierarchyUtilsSpec.groovy │ │ ├── DependencyParenthesesRuleSpec.groovy │ │ ├── DependencyServiceWithJavaPlatformSpec.groovy │ │ ├── UnusedExcludeByConfigurationRuleSpec.groovy │ │ ├── ArtifactHelpers.groovy │ │ ├── DependencyTupleExpressionRuleSpec.groovy │ │ └── DependencyViolationUtilSpec.groovy │ ├── task │ │ └── TaskDefinitionOperatorRuleSpec.groovy │ ├── rename │ │ └── PluginRenamedRuleSpec.groovy │ ├── postprocess │ │ └── EmptyClosureRuleSpec.groovy │ ├── BuildFilesTest.groovy │ └── LintRuleApplicationFailureContextSpec.groovy │ ├── self │ ├── ShadedCoordinate.groovy │ ├── AbstractShadedDependencies.groovy │ └── ShadedDependenciesTest.groovy │ ├── issues │ ├── Issue39Spec.groovy │ ├── Issue45Spec.groovy │ ├── Issue37Spec.groovy │ └── Issue314Spec.groovy │ └── plugin │ ├── FixGradleLintTaskCriticalRulesSpec.groovy │ ├── SourceCollectorTest.groovy │ └── LintRuleRegistrySpec.groovy ├── issues └── issue38 │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── src │ └── main │ │ └── java │ │ └── Main.java │ ├── build.gradle │ └── gradlew.bat ├── settings.gradle ├── example └── build.gradle ├── CONTRIBUTING.md ├── .github └── workflows │ ├── build.yml │ └── release.yml └── gradlew.bat /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 -------------------------------------------------------------------------------- /gradle/gradle-daemon-jvm.properties: -------------------------------------------------------------------------------- 1 | toolchainVersion=21 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | systemProp.nebula.features.coreLockingSupport=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle 3 | .idea/ 4 | out/ 5 | *.iml 6 | *.ipr 7 | *.iws 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nebula-plugins/gradle-lint-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/space-assignment.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dsl.SpaceAssignmentRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/deprecated-task-operator.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.task.TaskDefinitionOperatorRule -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-user-action-required.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.plugin.UserActionRequiredExampleRule 2 | -------------------------------------------------------------------------------- /issues/issue38/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nebula-plugins/gradle-lint-plugin/HEAD/issues/issue38/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-dependency-replace.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dependency.TestDependencyReplaceRule 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/multiproject-circular-dependency.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dependency.MultiProjectCircularDependencyRule -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-dependency-remove-version.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dependency.TestDependencyRemoveVersionRule 2 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-fails-to-apply-successfully.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.ExampleRuleWhichFailsToApplySuccessfully 2 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-dependency-replace-version.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dependency.TestDependencyReplaceVersionRule 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/deprecated-dependency-configuration.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.dependency.DeprecatedDependencyConfigurationRule 2 | -------------------------------------------------------------------------------- /src/test/java/com/netflix/nebula/lint/GradleVersions.java: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint; 2 | 3 | public class GradleVersions { 4 | public static String[] ALL = new String[]{"9.0.0", "9.1.0"}; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/lint-rules/test-fails-to-apply-successfully-with-extra-context.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.nebula.lint.rule.ExampleRuleWhichFailsToApplySuccessfullyWithExtraContext 2 | -------------------------------------------------------------------------------- /issues/issue38/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import org.springframework.boot.autoconfigure.SpringBootApplication; 2 | import org.springframework.scheduling.annotation.EnableScheduling; 3 | 4 | @SpringBootApplication 5 | @EnableScheduling 6 | public class Main { 7 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/utils/IndentUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.utils 2 | 3 | class IndentUtils { 4 | static String indentText(def node, String text) { 5 | return (' ' * (node.columnNumber - 1)) + text 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/GradlePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import groovy.transform.Canonical 4 | import groovy.transform.CompileStatic 5 | 6 | @Canonical 7 | @CompileStatic 8 | class GradlePlugin { 9 | String id 10 | } 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.gradle.develocity" version "4.0.2" 3 | } 4 | 5 | develocity { 6 | buildScan { 7 | termsOfUseUrl = 'https://gradle.com/terms-of-service' 8 | termsOfUseAgree = 'yes' 9 | } 10 | } 11 | 12 | rootProject.name = 'gradle-lint-plugin' 13 | -------------------------------------------------------------------------------- /issues/issue38/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 27 10:50:23 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/FirstOrderDuplicateDependencyClassRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import org.gradle.api.artifacts.Configuration 4 | import org.gradle.api.artifacts.ModuleVersionIdentifier 5 | 6 | class FirstOrderDuplicateDependencyClassRule extends AbstractDuplicateDependencyClassRule { 7 | @Override 8 | protected List moduleIds(Configuration conf) { 9 | return firstOrderModuleIds(conf) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/TransitiveDuplicateDepenencyClassRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import org.gradle.api.artifacts.Configuration 4 | import org.gradle.api.artifacts.ModuleVersionIdentifier 5 | 6 | class TransitiveDuplicateDepenencyClassRule extends AbstractDuplicateDependencyClassRule { 7 | @Override 8 | protected List moduleIds(Configuration conf) { 9 | return transitiveModuleIds(conf) - firstOrderModuleIds(conf) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/SourceSetUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.plugins.JavaPluginExtension 5 | import org.gradle.api.tasks.SourceSetContainer 6 | 7 | class SourceSetUtils { 8 | static boolean hasSourceSets(Project project) { 9 | return project.extensions.findByType(JavaPluginExtension) 10 | } 11 | 12 | static SourceSetContainer getSourceSets(Project project) { 13 | return project.extensions.getByType(JavaPluginExtension).sourceSets 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/BaseIntegrationTestKitSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint 2 | 3 | import nebula.test.IntegrationTestKitSpec 4 | 5 | abstract class BaseIntegrationTestKitSpec extends IntegrationTestKitSpec { 6 | 7 | void disableConfigurationCache() { 8 | def propertiesFile = new File(projectDir, 'gradle.properties') 9 | if(propertiesFile.exists()) { 10 | propertiesFile.delete() 11 | } 12 | propertiesFile.createNewFile() 13 | propertiesFile << '''org.gradle.configuration-cache=false'''.stripIndent() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/config/plugin/ConfigurationEnvironmentPrintTask.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.config.plugin 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | class ConfigurationEnvironmentPrintTask extends DefaultTask { 7 | @TaskAction 8 | void printConfigurationEnvironment() { 9 | def confExtensions = project.configurations.inject([]) { acc, conf -> 10 | acc += conf.extendsFrom.collect { extended -> "$conf.name->$extended.name" } 11 | acc 12 | } 13 | 14 | println(new DependencyHierarchyWriter().printHierarchy(*confExtensions)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/config/plugin/ConfigurationEnvironmentPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.config.plugin 2 | 3 | import groovy.transform.CompileStatic 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | 7 | /** 8 | * This plugin is used to print the configuration hierarchy, 9 | * useful in understanding such hierarchies for the creation of 10 | * new lint rules that may depend on manipulating dependencies in a 11 | * configuration-dependent way. 12 | */ 13 | @CompileStatic 14 | class ConfigurationEnvironmentPlugin implements Plugin { 15 | @Override 16 | void apply(Project project) { 17 | project.tasks.create('configurationEnvironment', ConfigurationEnvironmentPrintTask) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/bypassed-forces.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.BypassedForcesRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-deb.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaDebRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-facet.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaFacetRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-info.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaInfoRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-stash.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaStashRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/unused-dependency.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.UnusedDependencyRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/recommended-versions.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017-2018-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | implementation-class=com.netflix.nebula.lint.rule.dependency.RecommendedVersionsRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-clojure.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaClojureRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-git-scm.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaGitScmRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-info-ci.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaInfoCiRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-javadoc-jar.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaJavadocJarRule 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-override.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaOverrideRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-release.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaReleaseRule 18 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-source-jar.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaSourceJarRule 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/undeclared-dependency.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | implementation-class=com.netflix.nebula.lint.rule.dependency.UndeclaredDependencyRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/dependency-tuple.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.DependencyTupleExpressionRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-integtest.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaIntegTestRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-ospackage.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaOspackageRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/unused-exclude-by-dep.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.UnusedDependencyExcludeRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/nebula.configEnvironment.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.config.plugin.ConfigurationEnvironmentPlugin -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/dependency-parentheses.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.DependencyParenthesesRule 18 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-dependency-lock.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaDependencyLockRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/unused-exclude-by-conf.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.UnusedExcludeByConfigurationRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/minimum-dependency-version.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017-2018-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.MinimumDependencyVersionRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/duplicate-dependency-class.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.FirstOrderDuplicateDependencyClassRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-ospackage-application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaOspackageApplicationRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/transitive-duplicate-dependency-class.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.dependency.TransitiveDuplicateDepenencyClassRule -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/ResolvedArtifactInfo.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import groovy.transform.CompileStatic 4 | import org.gradle.api.artifacts.ResolvedArtifact 5 | 6 | @CompileStatic 7 | class ResolvedArtifactInfo { 8 | String organization 9 | String name 10 | String version 11 | String type 12 | 13 | static ResolvedArtifactInfo fromResolvedArtifact(ResolvedArtifact resolvedArtifact) { 14 | ResolvedArtifactInfo info = new ResolvedArtifactInfo() 15 | info.organization = resolvedArtifact.moduleVersion.id.group 16 | info.name = resolvedArtifact.moduleVersion.id.name 17 | info.version = resolvedArtifact.moduleVersion.id.version 18 | info.type = resolvedArtifact.type 19 | return info 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/rename-nebula-ospackage-application-daemon.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | implementation-class=com.netflix.nebula.lint.rule.rename.RenameNebulaOspackageApplicationDaemonRule -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/all-dependency.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | includes=unused-exclude-by-conf,unused-exclude-by-dep,unused-dependency,duplicate-dependency-class,transitive-duplicate-dependency-class,recommended-versions,undeclared-dependency 17 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/AbstractExampleGradleLintRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import org.codehaus.groovy.ast.expr.EmptyExpression 4 | import org.codehaus.groovy.ast.expr.LambdaExpression 5 | import org.codehaus.groovy.ast.expr.MethodReferenceExpression 6 | 7 | //tests are compiled against Groovy 3 main code against Groovy 2 8 | //this rule declares new methods so we don't have to declare them in every test rule in tests 9 | abstract class AbstractExampleGradleLintRule extends GradleLintRule { 10 | @Override 11 | void visitLambdaExpression(LambdaExpression lambdaExpression) { 12 | } 13 | 14 | @Override 15 | void visitMethodReferenceExpression(MethodReferenceExpression methodReferenceExpression) { 16 | } 17 | 18 | @Override 19 | void visitEmptyExpression(EmptyExpression expression) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenLocal() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.netflix.nebula:gradle-lint-plugin:latest.release' 9 | } 10 | 11 | configurations.classpath.resolutionStrategy { 12 | cacheDynamicVersionsFor 0, 'seconds' 13 | } 14 | } 15 | 16 | apply plugin: 'java' 17 | apply plugin: 'nebula.lint' 18 | 19 | gradleLint.rules = ['dependency-parentheses', 'dependency-tuple'] 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | dependencies { 26 | compile('org.codenarc:CodeNarc:latest.release') 27 | compile('com.google.guava:guava:latest.release') 28 | 29 | gradleLint.ignore('dependency-parentheses') { 30 | compile('com.google.guava:guava:latest.release') 31 | } 32 | 33 | testCompile group: 'junit', name: 'junit', version: '4.11' 34 | } -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | log4j.rootLogger=DEBUG, console 18 | log4j.appender.console=org.apache.log4j.ConsoleAppender 19 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 20 | log4j.appender.console.layout.conversionPattern=%r [%t] %-5p %c - %m%n -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/AbstractModelAwareExampleGradleLintRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import org.codehaus.groovy.ast.expr.EmptyExpression 4 | import org.codehaus.groovy.ast.expr.LambdaExpression 5 | import org.codehaus.groovy.ast.expr.MethodReferenceExpression 6 | 7 | //tests are compiled against Groovy 3 main code against Groovy 2 8 | //this rule declares new methods so we don't have to declare them in every test rule in tests 9 | abstract class AbstractModelAwareExampleGradleLintRule extends ModelAwareGradleLintRule { 10 | @Override 11 | void visitLambdaExpression(LambdaExpression lambdaExpression) { 12 | } 13 | 14 | @Override 15 | void visitMethodReferenceExpression(MethodReferenceExpression methodReferenceExpression) { 16 | } 17 | 18 | @Override 19 | void visitEmptyExpression(EmptyExpression expression) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/JarContents.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import groovy.transform.CompileDynamic 4 | 5 | class JarContents { 6 | Set entryNames 7 | 8 | /** 9 | * Fully qualified class names with '/' package separators 10 | */ 11 | @Lazy Set classes = entryNames 12 | // classes like org/hornetq/utils/HornetQUtilLogger_$logger are generated by references to interface fields, skip these 13 | .findAll { it.endsWith('.class') && !(it =~ /_\$\w+\.class$/) } 14 | .collect { it.replaceAll(/\.class$/, '') } 15 | .toSet() 16 | 17 | @Lazy boolean isServiceProvider = entryNames.any { it == 'META-INF/services/' } 18 | @Lazy boolean nothingButMetaInf = !entryNames.any { !it.startsWith('META-INF') } 19 | @Lazy boolean isWebjar = entryNames.any { it == 'META-INF/resources/webjars/' } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/GradleLintViolationAction.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint 18 | 19 | abstract class GradleLintViolationAction { 20 | void lintFinished(Collection violations) {} 21 | void lintFixesApplied(Collection violations) {} 22 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintTextReport.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin.report.internal 2 | 3 | import com.netflix.nebula.lint.plugin.GradleLintReportTask 4 | import com.netflix.nebula.lint.plugin.report.LintReport 5 | import org.codenarc.report.AbstractReportWriter 6 | import org.codenarc.report.TextReportWriter 7 | import org.gradle.api.file.RegularFile 8 | import org.gradle.api.model.ObjectFactory 9 | 10 | import javax.inject.Inject 11 | 12 | abstract class LintTextReport extends LintReport { 13 | @Inject 14 | LintTextReport(ObjectFactory objects, GradleLintReportTask task) { 15 | super(objects, task) 16 | } 17 | 18 | @Override 19 | String getName() { 20 | return "text" 21 | } 22 | 23 | @Override 24 | AbstractReportWriter getWriter() { 25 | def writer = new TextReportWriter() 26 | writer.outputFile = outputLocation.get().asFile 27 | return writer 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintHtmlReport.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin.report.internal 2 | 3 | import com.netflix.nebula.lint.plugin.GradleLintReportTask 4 | import com.netflix.nebula.lint.plugin.report.LintReport 5 | import org.codenarc.report.AbstractReportWriter 6 | import org.codenarc.report.HtmlReportWriter 7 | import org.gradle.api.file.RegularFile 8 | import org.gradle.api.model.ObjectFactory 9 | 10 | import javax.inject.Inject 11 | 12 | abstract class LintHtmlReport extends LintReport { 13 | @Inject 14 | LintHtmlReport(ObjectFactory objects, GradleLintReportTask task) { 15 | super(objects, task) 16 | } 17 | 18 | @Override 19 | String getName() { 20 | return "html" 21 | } 22 | 23 | @Override 24 | AbstractReportWriter getWriter() { 25 | def writer = new HtmlReportWriter() 26 | writer.outputFile = outputLocation.get().asFile 27 | return writer 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaDebRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaDebRule extends PluginRenamedRule { 23 | RenameNebulaDebRule() { 24 | super('deb', 'nebula.deb') 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaInfoRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaInfoRule extends PluginRenamedRule { 23 | RenameNebulaInfoRule() { 24 | super('info', 'nebula.info') 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaInfoCiRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaInfoCiRule extends PluginRenamedRule { 23 | RenameNebulaInfoCiRule() { 24 | super('info-ci', 'nebula.info-ci') 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaClojureRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaClojureRule extends PluginRenamedRule { 23 | RenameNebulaClojureRule() { 24 | super('nebula-clojure', 'nebula.clojure') 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaFacetRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaFacetRule extends PluginRenamedRule { 23 | RenameNebulaFacetRule() { 24 | super('nebula-facet', 'nebula.facet') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintXmlReport.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin.report.internal 2 | 3 | import com.netflix.nebula.lint.plugin.GradleLintReportTask 4 | import com.netflix.nebula.lint.plugin.report.LintReport 5 | import org.codenarc.report.AbstractReportWriter 6 | import org.codenarc.report.HtmlReportWriter 7 | import org.codenarc.report.XmlReportWriter 8 | import org.gradle.api.file.RegularFile 9 | import org.gradle.api.model.ObjectFactory 10 | 11 | import javax.inject.Inject 12 | 13 | abstract class LintXmlReport extends LintReport { 14 | @Inject 15 | LintXmlReport(ObjectFactory objects, GradleLintReportTask task) { 16 | super(objects, task) 17 | } 18 | 19 | @Override 20 | String getName() { 21 | return "xml" 22 | } 23 | 24 | @Override 25 | AbstractReportWriter getWriter() { 26 | def writer = new XmlReportWriter() 27 | writer.outputFile = outputLocation.get().asFile 28 | return writer 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaReleaseRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.nebula.lint.rule.rename 17 | 18 | import groovy.transform.CompileStatic 19 | 20 | @CompileStatic 21 | class RenameNebulaReleaseRule extends PluginRenamedRule { 22 | RenameNebulaReleaseRule() { 23 | super('nebula.nebula-release', 'nebula.release') 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaStashRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaStashRule extends PluginRenamedRule { 23 | RenameNebulaStashRule() { 24 | super('gradle-stash', 'nebula.gradle-stash') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaGitScmRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaGitScmRule extends PluginRenamedRule { 23 | RenameNebulaGitScmRule() { 24 | super('gradle-git-scm', 'nebula.gradle-git-scm') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaIntegTestRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaIntegTestRule extends PluginRenamedRule { 23 | RenameNebulaIntegTestRule() { 24 | super('nebula-integtest', 'nebula.integtest') 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaOspackageRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaOspackageRule extends PluginRenamedRule { 23 | RenameNebulaOspackageRule() { 24 | super('os-package', 'nebula.ospackage') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaSourceJarRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.nebula.lint.rule.rename 17 | 18 | import groovy.transform.CompileStatic 19 | 20 | @CompileStatic 21 | class RenameNebulaSourceJarRule extends PluginRenamedRule { 22 | RenameNebulaSourceJarRule() { 23 | super('nebula-source-jar', 'nebula.source-jar') 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaOverrideRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaOverrideRule extends PluginRenamedRule { 23 | RenameNebulaOverrideRule() { 24 | super('nebula-override', 'nebula.override') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaJavadocJarRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.nebula.lint.rule.rename 17 | 18 | import groovy.transform.CompileStatic 19 | 20 | @CompileStatic 21 | class RenameNebulaJavadocJarRule extends PluginRenamedRule { 22 | RenameNebulaJavadocJarRule() { 23 | super('nebula-javadoc-jar', 'nebula.javadoc-jar') 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/provider/RecommendationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018-2019 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.rule.dependency.provider; 20 | 21 | public interface RecommendationProvider { 22 | String getVersion(String org, String name) throws Exception; 23 | 24 | String getName(); 25 | 26 | void setName(String name); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaDependencyLockRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaDependencyLockRule extends PluginRenamedRule { 23 | RenameNebulaDependencyLockRule() { 24 | super('gradle-dependency-lock', 'nebula.dependency-lock') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaOspackageDaemonRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaOspackageDaemonRule extends PluginRenamedRule { 23 | RenameNebulaOspackageDaemonRule() { 24 | super('nebula-ospackage-daemon', 'nebula.ospackage-daemon') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/netflix/nebula/lint/plugin/GradleLintDeprecationEmitterPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import org.gradle.api.GradleException 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.internal.operations.BuildOperationProgressEventEmitter 7 | 8 | /** 9 | * Plugin that emits lint violations as deprecations. 10 | * 11 | * Requires Gradle 6.6 and later. We use [BuildOperationProgressEventEmitter] because it allows us to bypass the assumptions of Gradle's deprecation logging builder and avoid our violations being 12 | * treated as Gradle deprecations. 13 | */ 14 | @Deprecated("This uses internal APIs and should be avoided") 15 | class GradleLintDeprecationEmitterPlugin : Plugin { 16 | override fun apply(project: Project) { 17 | if (project !== project.rootProject) { 18 | throw GradleException("This plugin can only be applied to the root project") 19 | } 20 | project.logger.debug("GradleLintDeprecationEmitterPlugin is a no-op plugin now") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaOspackageApplicationRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaOspackageApplicationRule extends PluginRenamedRule { 23 | RenameNebulaOspackageApplicationRule() { 24 | super('nebula-ospackage-application', 'nebula.ospackage-application') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/lint-rules/all-nebula-renames.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2019 Netflix, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | includes=rename-nebula-clojure,rename-nebula-deb,rename-nebula-dependency-lock,rename-nebula-facet,rename-nebula-git-scm,rename-nebula-info,rename-nebula-info-ci,rename-nebula-integtest,rename-nebula-javadoc-jar,rename-nebula-ospackage,rename-nebula-ospackage-application-daemon,rename-nebula-ospackage-application,rename-nebula-override,rename-nebula-source-jar,rename-nebula-stash,rename-nebula-release 17 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/UnfixedViolationReason.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | enum UnfixedViolationReason { 23 | OverlappingPatch('one or more fixes overlap with another, run fixGradleLint again to apply the change') 24 | 25 | String message 26 | 27 | UnfixedViolationReason(String message) { 28 | this.message = message 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/RuleSetFactory.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import org.codenarc.rule.Rule 20 | import org.codenarc.ruleset.CompositeRuleSet 21 | import org.codenarc.ruleset.RuleSet 22 | 23 | class RuleSetFactory { 24 | static RuleSet configureRuleSet(List rules) { 25 | def ruleSet = new CompositeRuleSet() 26 | rules.each { ruleSet.addRule(it) } 27 | ruleSet 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/RenameNebulaOspackageApplicationDaemonRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | @CompileStatic 22 | class RenameNebulaOspackageApplicationDaemonRule extends PluginRenamedRule { 23 | RenameNebulaOspackageApplicationDaemonRule() { 24 | super('nebula-ospackage-application-daemon', 'nebula.ospackage-application-daemon') 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/InvalidRuleException.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import org.gradle.api.GradleException 20 | 21 | /** 22 | * Thrown when a rule is found to be invalid when it is loaded. 23 | */ 24 | class InvalidRuleException extends GradleException { 25 | 26 | InvalidRuleException(String message) { 27 | super(message) 28 | } 29 | 30 | InvalidRuleException(String message, Throwable cause) { 31 | super(message, cause) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Nebula 2 | 3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request. When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. 4 | 5 | ## License 6 | 7 | By contributing your code, you agree to license your contribution under the terms of the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0). Your contributions should also include the following header: 8 | 9 | ``` 10 | /** 11 | * Copyright 2016 the original author or authors. 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | * 17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | * 19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | */ 25 | ``` 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/UnexpectedLintRuleFailureException.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import org.gradle.api.GradleException 20 | 21 | /** 22 | * Thrown when a rule is found to be invalid for some reason when the rule is applied. 23 | */ 24 | class UnexpectedLintRuleFailureException extends GradleException { 25 | 26 | UnexpectedLintRuleFailureException(String message) { 27 | super(message) 28 | } 29 | 30 | UnexpectedLintRuleFailureException(String message, Throwable cause) { 31 | super(message, cause) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Nebula Build 2 | permissions: 3 | contents: read 4 | actions: write 5 | on: 6 | push: 7 | branches: 8 | - 'main' 9 | pull_request: 10 | 11 | jobs: 12 | buildmultijdk: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | # test against latest update of some major Java version(s), as well as specific LTS version(s) 17 | java: [17, 21, 25] 18 | name: Gradle Build without Publish 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Setup git user 22 | run: | 23 | git config --global user.name "Nebula Plugin Maintainers" 24 | git config --global user.email "nebula-plugins-oss@netflix.com" 25 | - name: Set up JDKs 26 | uses: actions/setup-java@v4 27 | with: 28 | distribution: 'zulu' 29 | java-version: | 30 | 17 31 | 21 32 | ${{ matrix.java }} 33 | java-package: jdk 34 | # - name: Setup Gradle 35 | # uses: gradle/actions/setup-gradle@v5 36 | # with: 37 | # cache-overwrite-existing: true 38 | - name: Gradle build 39 | run: ./gradlew --stacktrace build 40 | env: 41 | JDK_VERSION_FOR_TESTS: ${{ matrix.java }} -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/ClassInformation.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class ClassInformation { 7 | String filePath 8 | String source 9 | String name 10 | 11 | Collection methodReferences = [] 12 | 13 | ClassInformation(String source, String name, Collection methodReferences) { 14 | this.source = source 15 | this.name = name 16 | this.methodReferences = methodReferences 17 | this.filePath = calculateFilePath(source, name) 18 | } 19 | 20 | @Override 21 | String toString() { 22 | return "source: $source - filePath: $filePath - name: $name - methodReferences: ${methodReferences*.toString().join(' | ')}" 23 | } 24 | 25 | private String calculateFilePath(String source, String name) { 26 | String extension = getFileExtension(source) 27 | return name + extension 28 | } 29 | 30 | private String getFileExtension(String fileName) { 31 | int lastIndexOf = fileName.lastIndexOf(".") 32 | if (lastIndexOf == -1) { 33 | return "" 34 | } 35 | return fileName.substring(lastIndexOf) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/ModuleDescriptor.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018-2019 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.rule.dependency 20 | 21 | import com.netflix.nebula.lint.rule.GradleDependency 22 | import groovy.transform.CompileStatic 23 | import groovy.transform.Immutable 24 | 25 | @Immutable 26 | @CompileStatic 27 | class ModuleDescriptor { 28 | String group 29 | String name 30 | String version 31 | 32 | static ModuleDescriptor fromGradleDependency(GradleDependency dep) { 33 | new ModuleDescriptor(dep.group, dep.name, dep.version ?: '') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/DependencyViolationUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import com.netflix.nebula.lint.GradleViolation 4 | import com.netflix.nebula.lint.rule.GradleDependency 5 | import groovy.transform.CompileStatic 6 | import org.codehaus.groovy.ast.expr.MethodCallExpression 7 | 8 | @CompileStatic 9 | class DependencyViolationUtil { 10 | 11 | static void replaceProjectDependencyConfiguration(GradleViolation violation, MethodCallExpression call, String configuration, String project) { 12 | violation.replaceWith(call, "$configuration project('$project')") 13 | } 14 | 15 | static void replaceDependencyConfiguration(GradleViolation violation, MethodCallExpression call, String conf, GradleDependency dep) { 16 | violation.replaceWith(call, "$conf '${dep.toNotation()}'") 17 | } 18 | 19 | static void replaceDependencyConfiguration(GradleViolation violation, MethodCallExpression call, String conf) { 20 | List lines = violation.files.text.readLines() 21 | List closureLines = lines.subList(call.lineNumber-1, call.lastLineNumber) 22 | String codeBlock = closureLines.join('\n').trim() 23 | violation.replaceWith(call, codeBlock.replace(call.methodAsString, conf)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/self/ShadedCoordinate.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2020 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.self 20 | 21 | class ShadedCoordinate { 22 | String artifactName 23 | String artifactGroup 24 | String originalPackageGroup 25 | String relocatedPackageGroup 26 | 27 | ShadedCoordinate(String originalPackageGroup, String relocatedPackageGroup, String artifactGroup, String artifactName) { 28 | this.originalPackageGroup = originalPackageGroup 29 | this.relocatedPackageGroup = relocatedPackageGroup 30 | this.artifactGroup = artifactGroup 31 | this.artifactName = artifactName 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/GradleDependencySpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class GradleDependencySpec extends Specification { 7 | @Unroll 8 | def 'serialize to and deserialize from String notation `#serialized`'() { 9 | when: 10 | def dep = new GradleDependency(group, name, version, classifier, ext, null, GradleDependency.Syntax.StringNotation) 11 | 12 | then: 13 | dep.toNotation() == serialized 14 | 15 | when: 16 | def deser = GradleDependency.fromConstant(serialized) 17 | 18 | then: 19 | deser.group == group 20 | deser.name == name 21 | deser.version == version 22 | deser.classifier == classifier 23 | deser.ext == ext 24 | 25 | where: 26 | group | name | version | classifier | ext | serialized 27 | 'a' | 'a' | '1' | 'tests' | 'jar' | 'a:a:1:tests@jar' 28 | 'a' | 'a' | null | 'tests' | 'jar' | 'a:a::tests@jar' 29 | 'a' | 'a' | '1' | 'tests' | null | 'a:a:1:tests' 30 | 'a' | 'a' | '1' | null | null | 'a:a:1' 31 | 'a' | 'a' | null | null | null | 'a:a' 32 | null | 'a' | null | null | null | ':a' 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/FileMode.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint 18 | 19 | import groovy.transform.CompileStatic 20 | 21 | import static java.nio.file.Files.isSymbolicLink 22 | 23 | @CompileStatic 24 | enum FileMode { 25 | Regular(100644), 26 | Symlink(120000), 27 | Executable(100755) 28 | 29 | int mode 30 | 31 | FileMode(int mode) { 32 | this.mode = mode 33 | } 34 | 35 | static fromFile(File file) { 36 | if (isSymbolicLink(file.toPath())) 37 | return Symlink 38 | else if (file.canExecute()) { 39 | return Executable 40 | } else { 41 | return Regular 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/SourceCollector.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import org.codehaus.groovy.ast.ClassNode 4 | import org.codehaus.groovy.ast.ModuleNode 5 | import org.codenarc.source.SourceCode 6 | import org.codenarc.source.SourceString 7 | import org.gradle.api.Project 8 | 9 | class SourceCollector { 10 | 11 | /** 12 | * It scans given build file for possible `apply from: 'another.gradle'` and recursively 13 | * collect all build files which are present. 14 | */ 15 | static List getAllFiles(File buildFile, Project project) { 16 | if (buildFile.exists()) { 17 | List result = new ArrayList<>() 18 | result.add(buildFile) 19 | SourceCode sourceCode = new SourceString(buildFile.text) 20 | ModuleNode ast = sourceCode.getAst() 21 | if (ast != null && ast.getClasses() != null) { 22 | for (ClassNode classNode : ast.getClasses()) { 23 | AppliedFilesAstVisitor visitor = new AppliedFilesAstVisitor(project) 24 | visitor.visitClass(classNode) 25 | result.addAll(visitor.appliedFiles) 26 | } 27 | } 28 | return result 29 | } else { 30 | return Collections.emptyList() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/provider/AbstractRecommendationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018-2019 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.rule.dependency.provider; 20 | 21 | import groovy.transform.CompileStatic; 22 | 23 | @CompileStatic 24 | public abstract class AbstractRecommendationProvider implements RecommendationProvider { 25 | protected String name; 26 | 27 | static int providersWithoutNames = 0; 28 | 29 | @Override 30 | public String getName() { 31 | return name == null ? "recommender-" + (++providersWithoutNames) : name; 32 | } 33 | 34 | @Override 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/FixmeRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import groovy.transform.CompileStatic 4 | import org.codenarc.rule.Rule 5 | import org.codenarc.rule.Violation 6 | import org.codenarc.source.SourceCode 7 | 8 | @CompileStatic 9 | class FixmeRule implements Rule { 10 | @Override 11 | List applyTo(SourceCode sourceCode) { 12 | throw new RuntimeException('This should never be called') 13 | } 14 | 15 | @Override 16 | int getPriority() { 17 | // A system property is used since not all lint rules are GradleModelAware in order to pass in a project 18 | // We would prefer to use https://docs.gradle.org/6.1/javadoc/org/gradle/api/provider/ProviderFactory.html#systemProperty-java.lang.String- 19 | // but this was introduced in Gradle 6.1 20 | def nonCriticalPriority = System.getProperty('nebula.lint.fixmeAsNonCritical') 21 | if (nonCriticalPriority != null && nonCriticalPriority == "true") { 22 | return 2 // violations of fixmes are noncritical only when the property is used 23 | } 24 | return 1 // violations of fixmes are always critical rule failures 25 | } 26 | 27 | @Override 28 | String getName() { 29 | return 'expired-fixme' 30 | } 31 | 32 | @Override 33 | int getCompilerPhase() { 34 | return 0 // irrelevant 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /issues/issue38/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.5.RELEASE") 8 | } 9 | } 10 | 11 | plugins { 12 | id 'nebula.lint' version '0.30.12' 13 | } 14 | 15 | allprojects { 16 | apply plugin: 'nebula.lint' 17 | gradleLint.rules = ['unused-dependency'] // add as many rules here as you'd like 18 | } 19 | 20 | apply plugin: 'java' 21 | apply plugin: 'eclipse' 22 | apply plugin: 'idea' 23 | apply plugin: 'spring-boot' 24 | 25 | jar { 26 | baseName = 'gs-spring-boot' 27 | version = '0.1.0' 28 | } 29 | 30 | repositories { 31 | mavenCentral() 32 | mavenCentral() 33 | } 34 | 35 | sourceCompatibility = 1.7 36 | targetCompatibility = 1.7 37 | 38 | dependencies { 39 | compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2' 40 | // tag::jetty[] 41 | compile("org.springframework.boot:spring-boot-starter-web") { 42 | exclude module: "spring-boot-starter-tomcat" 43 | } 44 | compile("org.springframework.boot:spring-boot-starter-jetty") 45 | // end::jetty[] 46 | // tag::actuator[] 47 | compile("org.springframework.boot:spring-boot-starter-actuator") 48 | // end::actuator[] 49 | testCompile("junit:junit") 50 | } 51 | 52 | task wrapper(type: Wrapper) { 53 | gradleVersion = '2.14' 54 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/issues/Issue39Spec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.issues 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | import com.netflix.nebula.lint.rule.dependency.UnusedDependencyRule 5 | import spock.lang.Subject 6 | 7 | @Subject(UnusedDependencyRule) 8 | class Issue39Spec extends BaseIntegrationTestKitSpec { 9 | def 'place dependencies in the correct configuration by source set'() { 10 | when: 11 | buildFile.text = """ 12 | plugins { 13 | id 'nebula.lint' 14 | id 'com.netflix.nebula.integtest' version '10.1.4' 15 | id 'java' 16 | } 17 | 18 | gradleLint { 19 | rules = ['unused-dependency'] 20 | } 21 | 22 | repositories { mavenCentral() } 23 | 24 | dependencies { 25 | implementation group: 'com.google.guava', name: 'guava', version: '26.0-jre' 26 | } 27 | """ 28 | 29 | writeUnitTest(''' 30 | import com.google.common.collect.*; 31 | public class A { 32 | Object m = HashMultimap.create(); 33 | } 34 | ''', new File(projectDir, 'src/integTest/java')) 35 | 36 | then: 37 | runTasks('fixGradleLint') 38 | 39 | buildFile.text.contains("integTestImplementation 'com.google.guava:guava:26.0-jre'") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/issues/Issue45Spec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.issues 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | import com.netflix.nebula.lint.rule.dependency.UnusedDependencyRule 5 | import spock.lang.Issue 6 | import spock.lang.Subject 7 | 8 | @Subject(UnusedDependencyRule) 9 | class Issue45Spec extends BaseIntegrationTestKitSpec { 10 | @Issue('45') 11 | def 'interaction with nebula.dependency-recommender'() { 12 | when: 13 | buildFile.text = """ 14 | plugins { 15 | id 'nebula.lint' 16 | id 'nebula.dependency-recommender' version '9.0.2' 17 | id 'java' 18 | } 19 | 20 | gradleLint.rules = ['unused-dependency'] 21 | 22 | dependencyRecommendations { 23 | propertiesFile file: file('dependency-versions.properties') 24 | } 25 | 26 | repositories { mavenCentral() } 27 | 28 | dependencies { 29 | testImplementation 'junit:junit' 30 | } 31 | """ 32 | 33 | new File(projectDir, 'dependency-versions.properties') << 'junit:junit = 4.11' 34 | 35 | writeUnitTest('public class A {}') // notice how the dependency is not in here 36 | 37 | then: 38 | def results = runTasks('compileTestJava') 39 | results.output.contains('unused-dependency') 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/FixmeRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | import spock.lang.Subject 5 | 6 | @Subject(FixmeRule) 7 | class FixmeRuleSpec extends BaseIntegrationTestKitSpec { 8 | def tasks = ['assemble', 'fixGradleLint'] 9 | 10 | def setup() { 11 | def oldDate = '2010-12-1' 12 | buildFile.text = """ 13 | plugins { 14 | id 'nebula.lint' 15 | id 'java-library' 16 | } 17 | gradleLint.rules = ['minimum-dependency-version'] 18 | repositories { mavenCentral() } 19 | dependencies { 20 | gradleLint.fixme('$oldDate') { 21 | implementation 'com.google.guava:guava:18.+' 22 | } 23 | } 24 | """.stripIndent() 25 | } 26 | 27 | def 'expired fixmes are critical violations'() { 28 | expect: 29 | def result = runTasksAndFail(*tasks) 30 | result.output.contains('needs fixing') 31 | result.output.contains('critical lint violation') 32 | } 33 | 34 | def 'expired fixmes are noncritical violations when a property is used'() { 35 | expect: 36 | def result = runTasks(*tasks, '-Dnebula.lint.fixmeAsNonCritical=true') 37 | result.output.contains('needs fixing') 38 | !result.output.contains('critical lint violation') 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/MethodReference.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | 6 | @CompileStatic 7 | class MethodReference { 8 | String methodName 9 | String owner 10 | String methodDesc 11 | int line 12 | boolean isInterface 13 | OpCode opCode 14 | Collection artifacts 15 | 16 | MethodReference(String methodName, String owner, String methodDesc, int line, boolean isInterface, int code, Collection artifacts) { 17 | this.methodName = methodName 18 | this.owner = owner 19 | this.methodDesc = methodDesc 20 | this.line = line 21 | this.isInterface = isInterface 22 | this.opCode = OpCode.findByCode(code) 23 | this.artifacts = artifacts 24 | } 25 | 26 | @Override 27 | String toString() { 28 | return "methodName: $methodName - owner: $owner - methodDesc: $methodDesc - line: $line - isInterface: $isInterface - opCode: ${opCode.name()}" 29 | } 30 | 31 | enum OpCode { 32 | INVOKEVIRTUAL(182), 33 | INVOKESPECIAL(183), 34 | INVOKESTATIC(184), 35 | INVOKEINTERFACE(185), 36 | INVOKEDYNAMIC(186) 37 | 38 | int code 39 | 40 | OpCode(int code) { 41 | this.code = code 42 | } 43 | 44 | static OpCode findByCode(int code) { 45 | values().find { it.code == code } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/GradleLintInfoBrokerAction.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint 2 | 3 | import groovy.transform.Canonical 4 | import org.gradle.api.Project 5 | 6 | @Canonical 7 | class GradleLintInfoBrokerAction extends GradleLintViolationAction { 8 | Project project 9 | 10 | @Override 11 | void lintFinished(Collection violations) { 12 | project.getPlugins().withId('nebula.info-broker') { 13 | def reportItems = violations.collect { buildReportItem(it) } 14 | it.addReport('gradleLintViolations', reportItems) 15 | } 16 | } 17 | 18 | @Override 19 | void lintFixesApplied(Collection violations) { 20 | project.getPlugins().withId('nebula.info-broker') { 21 | def reportItems = violations.findAll { !it.fixes.any { it.reasonForNotFixing } } 22 | .collect { buildReportItem(it) } 23 | it.addReport('fixedGradleLintViolations', reportItems) 24 | } 25 | } 26 | 27 | LintReportItem buildReportItem(GradleViolation v) { 28 | def buildFilePath = project.rootDir.toURI().relativize(v.file.toURI()).toString() 29 | new LintReportItem(buildFilePath, v.rule.name, v.rule.getPriority() as String, 30 | v.lineNumber ?: -1, v.sourceLine ?: 'unspecified', v.message ?: "") 31 | } 32 | } 33 | 34 | @Canonical 35 | class LintReportItem { 36 | String buildFilePath 37 | String ruleId 38 | String severity 39 | Integer lineNumber 40 | String sourceLine 41 | String message 42 | } 43 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/issues/Issue37Spec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.issues 2 | 3 | import com.netflix.nebula.lint.plugin.NotNecessarilyGitRepository 4 | import org.eclipse.jgit.api.ApplyCommand 5 | import org.junit.Rule 6 | import org.junit.rules.TemporaryFolder 7 | import spock.lang.Specification 8 | 9 | class Issue37Spec extends Specification { 10 | @Rule 11 | TemporaryFolder temp 12 | 13 | def 'patch fails to apply'() { 14 | setup: 15 | def build = temp.newFile('build.gradle') 16 | def patch = temp.newFile('lint.patch') 17 | 18 | build << '''\ 19 | dependencies { 20 | compile('log4j:log4j:1.2.17') 21 | compile('com.google.guava:guava:19.0') 22 | compile('org.apache.commons:commons-lang3:3.4') 23 | 24 | provided('com.ibm:server-runtime:8.5.0') 25 | provided 'com.jscape:sftp:9.0.0' 26 | ''' 27 | 28 | when: 29 | patch << '''\ 30 | diff --git a/build.gradle b/build.gradle 31 | --- a/build.gradle 32 | +++ b/build.gradle 33 | @@ -1,7 +1,7 @@ 34 | dependencies { 35 | - compile('log4j:log4j:1.2.17') 36 | + compile 'log4j:log4j:1.2.17' 37 | - compile('com.google.guava:guava:19.0') 38 | + compile 'com.google.guava:guava:19.0' 39 | - compile('org.apache.commons:commons-lang3:3.4') 40 | + compile 'org.apache.commons:commons-lang3:3.4' 41 | 42 | provided('com.ibm:server-runtime:8.5.0') 43 | provided 'com.jscape:sftp:9.0.0' 44 | ''' 45 | 46 | then: 47 | new ApplyCommand(new NotNecessarilyGitRepository(temp.root)).setPatch(patch.newInputStream()).call() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2024 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.nebula.lint.plugin 17 | 18 | import com.netflix.nebula.interop.GradleKt 19 | import org.gradle.api.BuildCancelledException 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | 23 | class GradleLintPlugin implements Plugin { 24 | @Override 25 | void apply(Project project) { 26 | if (project.buildFile.name.toLowerCase().endsWith('.kts')) { 27 | throw new BuildCancelledException("Gradle Lint Plugin currently doesn't support kotlin build scripts." + 28 | " Please, switch to groovy build script if you want to use linting.") 29 | } 30 | 31 | LintRuleRegistry.classLoader = getClass().classLoader 32 | if (GradleKt.versionLessThan(project.gradle, '7.1')) { 33 | throw new BuildCancelledException("Gradle Lint Plugin requires Gradle 7.1 or newer.") 34 | } 35 | new GradleLintPluginTaskConfigurer().configure(project) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/self/AbstractShadedDependencies.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2020 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.self 20 | 21 | abstract trait AbstractShadedDependencies { 22 | static Collection SHARED_COORDINATES = [ 23 | new ShadedCoordinate('org.eclipse.jdt', 'com.netflix.nebula.lint.jdt', 'org.eclipse.jdt', 'core'), 24 | new ShadedCoordinate('org.eclipse.jgit', 'com.netflix.nebula.lint.jgit', 'org.eclipse.jgit', 'org.eclipse.jgit'), 25 | new ShadedCoordinate('org.apache.commons.lang', 'com.netflix.nebula.lint.commons.lang', 'commons-lang', 'commons-lang'), 26 | new ShadedCoordinate('org.codenarc', 'com.netflix.nebula.lint.org.codenarc', 'org.codenarc', 'CodeNarc'), 27 | new ShadedCoordinate('org.objectweb.asm', 'com.netflix.nebula.lint.org.objectweb.asm', 'org.ow2.asm', 'asm'), 28 | new ShadedCoordinate('org.objectweb.asm.commons', 'com.netflix.nebula.lint.org.objectweb.asm.commons', 'org.ow2.asm', 'asm-commons') 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/DependencyTupleExpressionRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.GradleAstUtil 20 | import com.netflix.nebula.lint.rule.GradleDependency 21 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 22 | import groovy.transform.CompileStatic 23 | import org.codehaus.groovy.ast.expr.MethodCallExpression 24 | 25 | @CompileStatic 26 | class DependencyTupleExpressionRule extends ModelAwareGradleLintRule { 27 | String description = "use the more compact string representation of a dependency when possible" 28 | 29 | @Override 30 | void visitAnyGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 31 | if(dep.conf == null && dep.syntax == GradleDependency.Syntax.MapNotation) { 32 | // FIXME what if one of the values is a function call? 33 | def ex = GradleAstUtil.collectEntryExpressions(call) 34 | addBuildLintViolation('use the shortcut form of the dependency', call) 35 | .replaceWith(call, "${call.methodAsString} '${ex.group ?: ''}:${ex.name}${ex.version ? ":$ex.version" : ''}'") 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/DependencyParenthesesRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.GradleDependency 20 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 21 | import org.codehaus.groovy.ast.expr.ClosureExpression 22 | import org.codehaus.groovy.ast.expr.MethodCallExpression 23 | 24 | class DependencyParenthesesRule extends ModelAwareGradleLintRule { 25 | String description = "don't put parentheses around dependency definitions unless it is necessary" 26 | 27 | @Override 28 | void visitAnyGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 29 | def args = call.arguments.expressions as List 30 | if(!args.empty && !(args[-1] instanceof ClosureExpression)) { 31 | def callSource = getSourceCode().line(call.lineNumber-1) 32 | def matcher = callSource =~ /^${call.methodAsString}\s*\((?[^\)]+)/ 33 | if(matcher.find()) { 34 | addBuildLintViolation('parentheses are unnecessary for dependencies', call) 35 | .replaceWith(call, "${call.methodAsString} ${matcher.group('dep')}") 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/config/plugin/DependencyHierarchyWriter.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.config.plugin; 2 | 3 | class DependencyHierarchyWriter { 4 | String printHierarchy(Map> dependencies) { 5 | if (dependencies.isEmpty()) return '' 6 | 7 | def roots = dependencies.keySet().findAll { root -> !dependencies.values().flatten().find { it == root } } 8 | 9 | roots.inject('') { acc, root -> 10 | acc += '\n\n' + printHierarchyRecurse('', root, dependencies, [:]) 11 | acc.trim() 12 | } 13 | } 14 | 15 | private String printHierarchyRecurse(String out, String dep, Map> dependencies, 16 | Map path) { 17 | def markers = [] 18 | path.values().eachWithIndex { hasAnotherChild, i -> 19 | def lineMarker = (i < path.size() - 1 || hasAnotherChild) ? '|' : '\\' 20 | markers += hasAnotherChild || i == path.size() - 1 ? lineMarker : ' ' 21 | } 22 | 23 | out += markers.join(' ') 24 | if (!path.isEmpty()) out += '_ ' 25 | out += dep 26 | 27 | if (path.containsKey(dep)) 28 | return out + ' (cycle)\n' 29 | 30 | out += '\n' 31 | 32 | def children = dependencies.get(dep) 33 | children.eachWithIndex { child, i -> 34 | out = printHierarchyRecurse(out, child, dependencies, path + [(dep): i < children.size() - 1]) 35 | } 36 | 37 | return out 38 | } 39 | 40 | String printHierarchy(String... depStrings) { 41 | printHierarchy(depStrings.inject([:].withDefault { [] }) { allDeps, line -> 42 | def dep = line.split('->') // split into [parent, child] 43 | if (dep.size() == 2) 44 | allDeps[dep[0]] += dep[1] 45 | return allDeps 46 | }) 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/ClassHierarchyUtils.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import groovy.transform.CompileStatic 20 | import groovy.transform.Memoized 21 | import org.slf4j.Logger 22 | import org.slf4j.LoggerFactory 23 | 24 | @CompileStatic 25 | class ClassHierarchyUtils { 26 | private static Logger logger = LoggerFactory.getLogger(ClassHierarchyUtils) 27 | 28 | /** 29 | * @return All types in the type hierarchy, including parameterizations at every level 30 | */ 31 | @Memoized 32 | static Collection typeHierarchy(Class clazz) { 33 | try { 34 | return typeHierarchyRecursive(clazz) - clazz.name 35 | } catch(Throwable t) { 36 | logger.debug("Unable to load super type or interfaces", t) 37 | return [] 38 | } 39 | } 40 | 41 | private static Collection typeHierarchyRecursive(Class clazz) { 42 | if(clazz.name.startsWith('java.')) 43 | return [] 44 | 45 | return (clazz.superclass ? typeHierarchyRecursive(clazz.superclass) : []) + 46 | (clazz.interfaces.collect { typeHierarchyRecursive(it) }.flatten() as Collection) + 47 | clazz.name 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/utils/DeprecationLoggerUtils.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.nebula.lint.utils 17 | 18 | import groovy.transform.CompileDynamic 19 | import org.gradle.api.GradleException 20 | import org.gradle.util.GradleVersion 21 | 22 | import java.lang.reflect.InvocationTargetException 23 | 24 | @CompileDynamic 25 | class DeprecationLoggerUtils { 26 | private static final String LEGACY_DEPRECATION_LOGGER_CLASS = 'org.gradle.util.DeprecationLogger' 27 | private static final String DEPRECATION_LOGGER_CLASS = 'org.gradle.internal.deprecation.DeprecationLogger' 28 | 29 | static void whileDisabled(Runnable action) { 30 | String gradleDeprecationLoggerClassName = (GradleVersion.current() >= GradleVersion.version('6.2') || GradleVersion.current().version.startsWith('6.2')) ? DEPRECATION_LOGGER_CLASS : LEGACY_DEPRECATION_LOGGER_CLASS 31 | try { 32 | Class clazz = Class.forName(gradleDeprecationLoggerClassName) 33 | clazz.getMethod("whileDisabled", Runnable).invoke(this, action) 34 | } catch (ClassNotFoundException e) { 35 | throw new GradleException("Could not execute whileDisabled runnable action for $gradleDeprecationLoggerClassName | $e.message", e) 36 | } catch (InvocationTargetException e) { 37 | throw e.targetException 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/GradleLintRuleIntegSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | import spock.lang.Issue 5 | 6 | class GradleLintRuleIntegSpec extends BaseIntegrationTestKitSpec { 7 | 8 | @Issue('#67') 9 | def 'find dependencies that are provided by extension properties'() { 10 | setup: 11 | buildFile.text = """ 12 | plugins { 13 | id 'java' 14 | id 'nebula.lint' 15 | } 16 | 17 | gradleLint.criticalRules = ['unused-dependency'] 18 | 19 | repositories { mavenCentral() } 20 | 21 | ext.deps = [ guava: 'com.google.guava:guava:19.0' ] 22 | 23 | dependencies { 24 | implementation deps.guava 25 | } 26 | """ 27 | 28 | when: 29 | writeHelloWorld() 30 | 31 | then: 32 | def result = runTasksAndFail('compileJava', 'lintGradle') 33 | result.output.contains('this dependency is unused and can be removed') 34 | } 35 | 36 | @Issue('#65') 37 | def 'find dependencies that are provided by interpolated strings'() { 38 | setup: 39 | buildFile.text = """ 40 | plugins { 41 | id 'java' 42 | id 'nebula.lint' 43 | } 44 | 45 | gradleLint.criticalRules = ['unused-dependency'] 46 | 47 | repositories { mavenCentral() } 48 | 49 | def v = 'latest.release' 50 | dependencies { 51 | implementation "com.google.guava:guava:\$v" 52 | implementation group: 'commons-lang', name: 'commons-lang', version: "\$v" 53 | } 54 | """ 55 | 56 | when: 57 | writeHelloWorld() 58 | 59 | then: 60 | def result = runTasksAndFail('compileJava', 'lintGradle') 61 | result.output.contains('this dependency is unused and can be removed') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/GradleAstUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule 18 | 19 | import org.codehaus.groovy.ast.expr.MapEntryExpression 20 | import org.codehaus.groovy.ast.expr.MapExpression 21 | import org.codehaus.groovy.ast.expr.MethodCallExpression 22 | import org.codenarc.source.SourceCode 23 | 24 | class GradleAstUtil { 25 | static Map collectEntryExpressions(MethodCallExpression call, SourceCode originalSource = null) { 26 | call.arguments.expressions 27 | .findAll { it instanceof MapExpression } 28 | .collect { it.mapEntryExpressions } 29 | .flatten() 30 | .collectEntries { MapEntryExpression entry -> [entry.keyExpression.text, extractValue(entry, originalSource)] } as Map 31 | } 32 | 33 | private static String extractValue(MapEntryExpression entry, SourceCode originalSource) { 34 | def value = entry.valueExpression 35 | //for one line declaration we try to be more precise and extract original source code since `.text` can be lossy for GString expressions 36 | if (originalSource != null && value.lineNumber == value.lastLineNumber) 37 | originalSource.lines.get(value.lineNumber - 1).substring(value.columnNumber, value.lastColumnNumber - 2) 38 | else 39 | entry.valueExpression.text 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/rename/PluginRenamedRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import com.netflix.nebula.lint.rule.GradleLintRule 20 | import com.netflix.nebula.lint.rule.GradlePlugin 21 | import groovy.transform.Canonical 22 | import groovy.transform.CompileStatic 23 | import org.codehaus.groovy.ast.expr.MethodCallExpression 24 | 25 | @CompileStatic 26 | @Canonical 27 | class PluginRenamedRule extends GradleLintRule { 28 | String deprecatedPluginName 29 | String pluginName 30 | 31 | @Override 32 | String getDescription() { 33 | return "the plugin name $deprecatedPluginName has been deprecated in favor of $pluginName" 34 | } 35 | 36 | @Override 37 | void visitApplyPlugin(MethodCallExpression call, String plugin) { 38 | if(plugin == deprecatedPluginName) { 39 | addBuildLintViolation("plugin $deprecatedPluginName has been renamed to $pluginName", call) 40 | .replaceWith(call, "apply plugin: '$pluginName'") 41 | 42 | } 43 | } 44 | 45 | @Override 46 | void visitGradlePlugin(MethodCallExpression call, String conf, GradlePlugin plugin) { 47 | if (plugin.id == deprecatedPluginName) { 48 | addBuildLintViolation("plugin $deprecatedPluginName has been renamed to $pluginName", call) 49 | .replaceWith(call, "id '$pluginName'") 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v*.*.* 6 | - v*.*.*-rc.* 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | name: Gradle Build and Publish 12 | environment: 13 | name: Publish 14 | url: "https://repo1.maven.org/maven2/com/netflix/nebula/gradle-lint-plugin/" 15 | env: 16 | NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }} 17 | NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }} 18 | NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} 19 | NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} 20 | NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} 21 | NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} 22 | GRADLE_PUBLISH_KEY: ${{ secrets.ORG_GRADLE_PUBLISH_KEY }} 23 | GRADLE_PUBLISH_SECRET: ${{ secrets.ORG_GRADLE_PUBLISH_SECRET }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Setup git user 27 | run: | 28 | git config --global user.name "Nebula Plugin Maintainers" 29 | git config --global user.email "nebula-plugins-oss@netflix.com" 30 | - name: Set up JDKs 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: 'zulu' 34 | java-version: | 35 | 17 36 | 21 37 | java-package: jdk 38 | # - name: Setup Gradle 39 | # uses: gradle/actions/setup-gradle@v5 40 | # with: 41 | # cache-overwrite-existing: true 42 | - name: Verify plugin publication 43 | if: (!contains(github.ref, '-rc.')) 44 | run: ./gradlew --stacktrace -Prelease.useLastTag=true final publishPlugin --validate-only -x check 45 | - name: Publish candidate 46 | if: contains(github.ref, '-rc.') 47 | run: ./gradlew --info --stacktrace -Prelease.useLastTag=true candidate 48 | - name: Publish release 49 | if: (!contains(github.ref, '-rc.')) 50 | run: ./gradlew --stacktrace -Prelease.useLastTag=true final -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/ClassHierarchyUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import spock.lang.Specification 20 | 21 | class ClassHierarchyUtilsSpec extends Specification { 22 | def java = new JavaFixture() 23 | 24 | def 'interface hierarchy'() { 25 | when: 26 | java.compile(''' 27 | package a; 28 | public interface AInt {} 29 | ''') 30 | 31 | java.compile(''' 32 | package b; 33 | public interface BInt extends a.AInt {} 34 | ''') 35 | 36 | java.compile(''' 37 | package c; 38 | public class C implements b.BInt {} 39 | ''') 40 | 41 | then: 42 | ClassHierarchyUtils.typeHierarchy(java.classLoader.findClass('c.C')).sort() == ['a.AInt', 'b.BInt'] 43 | } 44 | 45 | def 'class hierarchy'() { 46 | when: 47 | java.compile(''' 48 | package a; 49 | public class A {} 50 | ''') 51 | 52 | java.compile(''' 53 | package b; 54 | public class B extends a.A {} 55 | ''') 56 | 57 | java.compile(''' 58 | package c; 59 | public class C extends b.B {} 60 | ''') 61 | 62 | then: 63 | ClassHierarchyUtils.typeHierarchy(java.classLoader.findClass('c.C')).sort() == ['a.A', 'b.B'] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyParenthesesRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 20 | 21 | class DependencyParenthesesRuleSpec extends AbstractRuleSpec { 22 | def rule = new DependencyParenthesesRule() 23 | 24 | def 'valid uses of parentheses pass'() { 25 | when: 26 | project.buildFile << """ 27 | dependencies { 28 | compile 'junit:junit:4.11' 29 | compile ('a:a:1') { } 30 | } 31 | """ 32 | def results = runRulesAgainst(rule) 33 | 34 | then: 35 | results.doesNotViolate() 36 | } 37 | 38 | def 'parenthesized dependency violates'() { 39 | when: 40 | project.buildFile << """ 41 | dependencies { 42 | compile('junit:junit:4.11') 43 | } 44 | """ 45 | def results = runRulesAgainst(rule) 46 | 47 | then: 48 | results.violates() 49 | } 50 | 51 | def 'parenthesized dependencies are corrected'() { 52 | when: 53 | project.buildFile << """ 54 | dependencies { 55 | compile('junit:junit:4.11') 56 | } 57 | """ 58 | def results = correct(rule) 59 | 60 | then: 61 | results == """ 62 | dependencies { 63 | compile 'junit:junit:4.11' 64 | } 65 | """ 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/task/TaskDefinitionOperatorRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.task 2 | 3 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 4 | import spock.lang.Subject 5 | 6 | @Subject(TaskDefinitionOperatorRule) 7 | class TaskDefinitionOperatorRuleSpec extends AbstractRuleSpec { 8 | 9 | def 'report a violation if << is used to declare task'() { 10 | project.buildFile << """ 11 | apply plugin: 'java' 12 | 13 | task helloTask << { 14 | println 'hello' 15 | } 16 | """ 17 | 18 | when: 19 | def response = runRulesAgainst(new TaskDefinitionOperatorRule()) 20 | 21 | then: 22 | response.violations.size() == 1 23 | response.violations[0].message == 'The << operator was deprecated. Need to use doLast method' 24 | } 25 | 26 | def 'reports multiple violations if << is used to declare tasks'() { 27 | project.buildFile << """ 28 | apply plugin: 'java' 29 | 30 | task helloTask << { 31 | println 'hello' 32 | } 33 | 34 | task helloTask2 << { 35 | println 'hello' 36 | } 37 | """ 38 | 39 | when: 40 | def response = runRulesAgainst(new TaskDefinitionOperatorRule()) 41 | 42 | then: 43 | response.violations.size() == 2 44 | response.violations[0].message == 'The << operator was deprecated. Need to use doLast method' 45 | response.violations[1].message == 'The << operator was deprecated. Need to use doLast method' 46 | } 47 | 48 | def 'do not report a violation if << is not used to declare task'() { 49 | project.buildFile << """ 50 | apply plugin: 'java' 51 | 52 | task helloTask { 53 | doLast { 54 | println 'hello' 55 | } 56 | } 57 | """ 58 | 59 | when: 60 | def response = runRulesAgainst(new TaskDefinitionOperatorRule()) 61 | 62 | then: 63 | response.violations.size() == 0 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/LintRuleDescriptor.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import groovy.transform.Canonical 20 | import org.gradle.api.UncheckedIOException 21 | 22 | import javax.annotation.Nullable 23 | 24 | @Canonical 25 | class LintRuleDescriptor { 26 | URL propertiesFileUrl 27 | 28 | String getImplementationClassName() { 29 | loadProperties(propertiesFileUrl).getProperty('implementation-class') 30 | } 31 | 32 | List getIncludes() { 33 | loadProperties(propertiesFileUrl).getProperty('includes')?.split(',') ?: [] as List 34 | } 35 | 36 | private static Properties loadProperties(URL url) { 37 | try { 38 | URLConnection uc = url.openConnection() 39 | uc.setUseCaches(false) 40 | return loadProperties(uc.inputStream) 41 | } catch (IOException e) { 42 | throw new UncheckedIOException(e) 43 | } 44 | } 45 | 46 | private static Properties loadProperties(InputStream inputStream) { 47 | Properties properties = new Properties() 48 | try { 49 | properties.load(inputStream) 50 | } catch (IOException e) { 51 | throw new UncheckedIOException(e) 52 | } finally { 53 | closeQuietly(inputStream) 54 | } 55 | 56 | return properties 57 | } 58 | 59 | private static void closeQuietly(@Nullable Closeable resource) { 60 | try { 61 | if (resource != null) { 62 | resource.close() 63 | } 64 | } catch (IOException e) { 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/plugin/FixGradleLintTaskCriticalRulesSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import com.netflix.nebula.lint.rule.AbstractExampleGradleLintRule 4 | import com.netflix.nebula.lint.rule.AbstractModelAwareExampleGradleLintRule 5 | import com.netflix.nebula.lint.rule.GradleModelAware 6 | import nebula.test.IntegrationSpec 7 | import org.codehaus.groovy.ast.expr.MethodCallExpression 8 | 9 | class FixGradleLintTaskCriticalRulesSpec extends IntegrationSpec { 10 | def 'critical lint violations include cases requiring user action'() { 11 | given: 12 | def lintRuleDirectory = new File(projectDir, 'src/main/resources/META-INF/lint-rules') 13 | lintRuleDirectory.mkdirs() 14 | 15 | buildFile << """ 16 | plugins { 17 | id 'java' 18 | } 19 | apply plugin: 'nebula.lint' 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | gradleLint.criticalRules = ['test-user-action-required'] 26 | 27 | dependencies { 28 | implementation('commons-lang:commons-lang:2.6') 29 | } 30 | """ 31 | 32 | when: 33 | writeHelloWorld('test.nebula') 34 | 35 | then: 36 | def results = runTasksWithFailure('compileJava', 'fixGradleLint', '-s') 37 | 38 | //we need to check both stream because Gradle 4.8 changes where error are printed, during transition period 39 | //we run tests or multiple version so we need to maintain compatibility 40 | def expectedMessage = 'This build contains 1 critical lint violation' 41 | results.standardOutput.contains(expectedMessage) || results.standardError.contains(expectedMessage) 42 | 43 | def expectedRuleFailure = 'needs fixing.*test-user-action-required' 44 | results.standardOutput.findAll(expectedRuleFailure).size() == 1 || 45 | results.standardError.findAll(expectedRuleFailure).size() == 1 46 | } 47 | } 48 | 49 | class UserActionRequiredExampleRule extends AbstractModelAwareExampleGradleLintRule { 50 | String description = 'example rule that requires a user action' 51 | 52 | @Override 53 | void visitDependencies(MethodCallExpression call) { 54 | addBuildLintViolation("$description", call) 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyServiceWithJavaPlatformSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | import org.gradle.api.Project 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Subject 7 | import spock.lang.Unroll 8 | 9 | @Subject(DependencyService) 10 | class DependencyServiceWithJavaPlatformSpec extends BaseIntegrationTestKitSpec { 11 | Project project 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().withName('dependency-service').withProjectDir(projectDir).build() 15 | project.with { 16 | apply plugin: 'java-platform' 17 | repositories { mavenCentral() } 18 | } 19 | } 20 | 21 | @Unroll 22 | def 'findAndReplaceNonResolvableConfiguration works with java-platform plugin'() { 23 | given: 24 | project.with { 25 | 26 | // to verify with custom configurations 27 | configurations { 28 | myNonResolvableConfig { 29 | canBeResolved = false 30 | canBeConsumed = true 31 | } 32 | myNonResolvableConfigWithParent { 33 | canBeResolved = false 34 | canBeConsumed = true 35 | } 36 | myResolvableConfig { 37 | canBeResolved = true 38 | canBeConsumed = true 39 | } 40 | compileClasspath.extendsFrom myNonResolvableConfigWithParent 41 | } 42 | } 43 | writeJavaSourceFile('public class Main {}') 44 | 45 | def dependencyService = DependencyService.forProject(project) 46 | 47 | when: 48 | def resolvableConfig = dependencyService.findAndReplaceNonResolvableConfiguration(project.configurations."$configName") 49 | 50 | then: 51 | resolvableConfig.name == resolvableConfigName 52 | 53 | where: 54 | configName | resolvableConfigName 55 | 'myResolvableConfig' | 'myResolvableConfig' 56 | 'myNonResolvableConfigWithParent' | 'compileClasspath' 57 | 'myNonResolvableConfig' | 'myNonResolvableConfig' // returns the original config when the resolution alternative is unclear 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/plugin/SourceCollectorTest.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import nebula.test.ProjectSpec 4 | 5 | class SourceCollectorTest extends ProjectSpec { 6 | 7 | 8 | def 'all build files are collected'() { 9 | given: 10 | def projectDir = project.projectDir 11 | def rootFile = new File(projectDir, 'build.gradle') 12 | def level1Sibling1 = new File(projectDir, 'level1Sibling1.gradle') 13 | def level1Sibling2 = new File(projectDir, 'level1Sibling2.gradle') 14 | def level2 = new File(projectDir, 'level2.gradle') 15 | rootFile.text = """ 16 | apply from: 'level1Sibling1.gradle' 17 | apply from: 'level1Sibling2.gradle' 18 | apply from: 'http://DUMMY_WHICH_IS_IGNORED' 19 | """ 20 | level1Sibling1.text = """ 21 | apply from: 'level2.gradle' 22 | """ 23 | level1Sibling2.text = " " 24 | level2.text = " " 25 | 26 | when: 27 | def files = SourceCollector.getAllFiles(rootFile, project) 28 | 29 | then: 30 | files.containsAll([rootFile, level1Sibling1, level1Sibling2, level2]) 31 | } 32 | 33 | def 'all build files are collected when absolute paths with project variables are used'() { 34 | given: 35 | def projectDir = project.projectDir 36 | def rootFile = new File(projectDir, 'build.gradle') 37 | def level1Sibling1 = new File(projectDir, 'level1Sibling1.gradle') 38 | def level1Sibling2 = new File(projectDir, 'level1Sibling2.gradle') 39 | def level1Sibling3 = new File(projectDir, 'level1Sibling3.gradle') 40 | def level2 = new File(projectDir, 'level2.gradle') 41 | rootFile.text = """ 42 | apply from: "\${project.projectDir}/level1Sibling1.gradle" 43 | apply from: "\${projectDir}/level1Sibling2.gradle" 44 | apply from: "\${rootDir}/level1Sibling3.gradle" 45 | """ 46 | level1Sibling1.text = """ 47 | apply from: "\${project.rootDir}/level2.gradle" 48 | """ 49 | level1Sibling2.text = " " 50 | level1Sibling3.text = " " 51 | level2.text = " " 52 | 53 | when: 54 | def files = SourceCollector.getAllFiles(rootFile, project) 55 | 56 | then: 57 | files.containsAll([rootFile, level1Sibling1, level1Sibling2, level1Sibling3, level2]) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/rename/PluginRenamedRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.rename 18 | 19 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 20 | 21 | class PluginRenamedRuleSpec extends AbstractRuleSpec { 22 | def 'deprecated plugin names are recorded as violations'() { 23 | when: 24 | project.buildFile << """ 25 | apply plugin: 'ye-olde-plugin' 26 | """ 27 | 28 | def results = runRulesAgainst(new PluginRenamedRule('ye-olde-plugin', 'shiny-new-plugin')) 29 | 30 | then: 31 | results.violations.size() == 1 32 | } 33 | 34 | def 'deprecated plugin names (using plugins DSL) are recorded as violations'() { 35 | when: 36 | project.buildFile << """ 37 | plugins { 38 | id 'ye-olde-plugin' 39 | } 40 | """ 41 | 42 | def results = runRulesAgainst(new PluginRenamedRule('ye-olde-plugin', 'shiny-new-plugin')) 43 | 44 | then: 45 | results.violations.size() == 1 46 | } 47 | def 'deprecated plugin names are replaced with new names'() { 48 | when: 49 | project.buildFile << """ 50 | apply plugin: 'ye-olde-plugin' 51 | """ 52 | 53 | def corrected = correct(new PluginRenamedRule('ye-olde-plugin', 'shiny-new-plugin')) 54 | 55 | then: 56 | corrected == """ 57 | apply plugin: 'shiny-new-plugin' 58 | """ 59 | } 60 | 61 | def 'concrete implementation of PluginRenamedRule'() { 62 | when: 63 | project.buildFile << """ 64 | apply plugin: 'gradle-dependency-lock' 65 | """ 66 | 67 | def results = runRulesAgainst(new RenameNebulaDependencyLockRule()) 68 | 69 | then: 70 | results.violations.size() == 1 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/postprocess/EmptyClosureRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.postprocess 18 | 19 | import com.netflix.nebula.lint.postprocess.EmptyClosureRule 20 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 21 | 22 | class EmptyClosureRuleSpec extends AbstractRuleSpec { 23 | def 'if a deletion rule(s) causes a closure to be empty, delete the closure'() { 24 | when: 25 | project.buildFile << """ 26 | nebula { moduleOwner = 'me' } 27 | nebula { } 28 | """.substring(1).stripIndent() 29 | 30 | then: 31 | correct(new EmptyClosureRule()).replaceAll(/\s/, '') == /nebula{moduleOwner='me'}/ 32 | } 33 | 34 | def 'if a deletion rule(s) may be limited to a subset of blocks'() { 35 | when: 36 | project.buildFile << """ 37 | nebula { moduleOwner = 'me' } 38 | test { } 39 | """.substring(1).stripIndent() 40 | 41 | def rule = new EmptyClosureRule() 42 | rule.enableDeletableBlocks = true 43 | rule.deletableBlocks.add('nebula') 44 | 45 | then: 46 | correct(rule).replaceAll(/\s/, '') == /nebula{moduleOwner='me'}test{}/ 47 | } 48 | 49 | def 'if a deletion rule(s) may be limited to a subset of blocks and deletes if in list'() { 50 | when: 51 | project.buildFile << """ 52 | nebula { moduleOwner = 'me' } 53 | nebula { } 54 | """.substring(1).stripIndent() 55 | 56 | def rule = new EmptyClosureRule() 57 | rule.enableDeletableBlocks = true 58 | rule.deletableBlocks.add('nebula') 59 | 60 | then: 61 | correct(rule).replaceAll(/\s/, '') == /nebula{moduleOwner='me'}/ 62 | } 63 | 64 | def 'do not delete empty tasks'() { 65 | when: 66 | project.buildFile << """ 67 | task taskA {} 68 | """ 69 | 70 | then: 71 | correct(new EmptyClosureRule()) == """ 72 | task taskA {} 73 | """ 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/postprocess/EmptyClosureRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.postprocess 18 | 19 | import com.netflix.nebula.lint.rule.GradleLintRule 20 | import org.codehaus.groovy.ast.ClassNode 21 | import org.codehaus.groovy.ast.expr.ClosureExpression 22 | import org.codehaus.groovy.ast.expr.Expression 23 | import org.codehaus.groovy.ast.expr.MethodCallExpression 24 | 25 | /** 26 | * DSL blocks made empty by the deletion of their contents by other rules should be purged altogether. 27 | * This rule cannot distinguish between blocks made empty by other rules and those that were already empty, 28 | * but (while imperfect) doesn't do any harm by removing unused cruft incidentally. 29 | */ 30 | class EmptyClosureRule extends GradleLintRule { 31 | String description = 'empty closures should be removed' 32 | 33 | def emptyClosureCalls = [] as List 34 | def taskNames = [] as List 35 | List deletableBlocks = [] 36 | Boolean enableDeletableBlocks = false 37 | 38 | @Override 39 | void visitMethodCallExpression(MethodCallExpression call) { 40 | def expressions = call.arguments.expressions 41 | 42 | expressions.each { 43 | // prevents empty tasks from being deleted that take this form: 44 | // task taskA {} 45 | taskNames.add(it) 46 | } 47 | 48 | if (isDeletable(call.methodAsString) && expressions.size() == 1 && expressions.last() instanceof ClosureExpression) { 49 | if(expressions.last().code.empty) { 50 | emptyClosureCalls.add(call) 51 | } 52 | } 53 | } 54 | 55 | protected Boolean isDeletable(String block) { 56 | !enableDeletableBlocks || (enableDeletableBlocks && deletableBlocks.contains(block)) 57 | } 58 | 59 | @Override 60 | void visitClassComplete(ClassNode node) { 61 | (emptyClosureCalls - taskNames).unique().each { 62 | addBuildLintViolation('this is an empty configuration closure that can be removed', it) 63 | .delete(it) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /issues/issue38/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/BuildFilesTest.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import org.junit.ClassRule 4 | import org.junit.rules.TemporaryFolder 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | import spock.lang.Unroll 8 | 9 | class BuildFilesTest extends Specification { 10 | 11 | @Shared 12 | @ClassRule 13 | TemporaryFolder temporaryFolder 14 | @Shared 15 | def file1 16 | @Shared 17 | def file2 18 | @Shared 19 | def file3 20 | @Shared 21 | def file4 22 | 23 | def setupSpec() { 24 | file1 = temporaryFolder.newFile() 25 | file1.text = """\nline 1\n """ 26 | file2 = temporaryFolder.newFile() 27 | file2.text = """\nline 2\n""" 28 | 29 | file3 = temporaryFolder.newFile() 30 | file3.text = "\nline 3\n" 31 | 32 | file4 = temporaryFolder.newFile() 33 | } 34 | 35 | 36 | def 'files are correctly concatenated'() { 37 | when: 38 | def text = new BuildFiles([file1, file2, file3, file4]).text 39 | 40 | then: 41 | text == "\nline 1\n \n\nline 2\n\n\nline 3\n\n\n" 42 | } 43 | 44 | @Unroll 45 | def 'original file and line is retrieved'() { 46 | given: 47 | def buildFiles = new BuildFiles([file1, file2, file3, file4]) 48 | 49 | when: 50 | def original = buildFiles.original(concatenatedLine) 51 | 52 | then: 53 | original.file == expectedFile 54 | original.line == originalLine 55 | 56 | where: 57 | concatenatedLine | expectedFile | originalLine 58 | 1 | file1 | 1 59 | 2 | file1 | 2 60 | 3 | file1 | 3 61 | 4 | file2 | 1 62 | 5 | file2 | 2 63 | 6 | file2 | 3 64 | 7 | file3 | 1 65 | 8 | file3 | 2 66 | 9 | file3 | 3 67 | 10 | file4 | 1 68 | } 69 | 70 | def 'exception with details when you are asking line out of range'() { 71 | given: 72 | def buildFiles = new BuildFiles([file1, file2, file3, file4]) 73 | 74 | when: 75 | buildFiles.original(11) 76 | 77 | then: 78 | def exception = thrown(IllegalArgumentException) 79 | def lines = exception.message.split('\n') 80 | lines[0] == 'Asked line in concatenated file was: 11 but it wasn\'t found. Original project files were concatenated to following ranges:' 81 | lines[1].startsWith("Lines 1 - 3 are ") 82 | lines[2].startsWith("Lines 4 - 6 are ") 83 | lines[3].startsWith("Lines 7 - 9 are ") 84 | lines[4].startsWith("Lines 10 - 10 are ") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/StyledTextService.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint 2 | 3 | import groovy.transform.TupleConstructor 4 | import org.gradle.internal.service.ServiceRegistry 5 | 6 | /** 7 | * Bridges the internal Gradle 2 and 3 APIs for styled text output to provide 8 | * a single backwards-compatible interface. 9 | */ 10 | class StyledTextService { 11 | def textOutput 12 | 13 | StyledTextService(ServiceRegistry registry) { 14 | Class factoryClass 15 | try { 16 | factoryClass = Class.forName('org.gradle.internal.logging.text.StyledTextOutputFactory') 17 | } catch(ClassNotFoundException ignore) { 18 | factoryClass = Class.forName('org.gradle.logging.StyledTextOutputFactory') 19 | } 20 | 21 | def textOutputFactory = registry.get(factoryClass) 22 | textOutput = textOutputFactory.create("gradle-lint") 23 | } 24 | 25 | VersionNeutralTextOutput withStyle(Styling styling) { 26 | Class styleClass 27 | try { 28 | styleClass = Class.forName('org.gradle.internal.logging.text.StyledTextOutput$Style') 29 | } catch(ClassNotFoundException ignore) { 30 | styleClass = Class.forName('org.gradle.logging.StyledTextOutput$Style') 31 | } 32 | 33 | styleClass.enumConstants 34 | def styleByName = { String name -> 35 | styleClass.enumConstants.find { it.name() == name } 36 | } 37 | 38 | switch (styling) { 39 | case Styling.Bold: 40 | return new VersionNeutralTextOutput(textOutput.withStyle(styleByName('UserInput'))) 41 | case Styling.Green: 42 | return new VersionNeutralTextOutput(textOutput.withStyle(styleByName('Identifier'))) 43 | case Styling.Yellow: 44 | return new VersionNeutralTextOutput(textOutput.withStyle(styleByName('Description'))) 45 | case Styling.Red: 46 | return new VersionNeutralTextOutput(textOutput.withStyle(styleByName('Failure'))) 47 | } 48 | } 49 | 50 | StyledTextService text(String text) { 51 | textOutput.text(text) 52 | return this 53 | } 54 | 55 | StyledTextService println(String text) { 56 | textOutput.println(text) 57 | return this 58 | } 59 | 60 | StyledTextService println() { 61 | // the no-arg form is a dangerous overload on Groovy's println() metaclass extension of Object 62 | textOutput.println('') 63 | return this 64 | } 65 | 66 | static enum Styling { 67 | Bold, Green, Yellow, Red 68 | } 69 | } 70 | 71 | @TupleConstructor 72 | class VersionNeutralTextOutput { 73 | def textOutput 74 | 75 | void text(Object v) { textOutput.text(v) } 76 | void println(Object v) { textOutput.println(v) } 77 | void println() { textOutput.println('') } 78 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/UnusedExcludeByConfigurationRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 20 | 21 | class UnusedExcludeByConfigurationRuleSpec extends AbstractRuleSpec { 22 | def rule 23 | 24 | def setup() { 25 | rule = new UnusedExcludeByConfigurationRule(project: project) 26 | } 27 | 28 | def 'unused exclude violates (no closure)'() { 29 | when: 30 | // trivial case: no dependencies 31 | project.buildFile << """ 32 | apply plugin: 'java' 33 | configurations.all*.exclude group: 'com.google.guava', module: 'guava' 34 | """ 35 | 36 | project.apply plugin: 'java' 37 | 38 | def results = runRulesAgainst(rule) 39 | 40 | then: 41 | results.violates() 42 | } 43 | 44 | def 'unused exclude violates'() { 45 | when: 46 | // trivial case: no dependencies 47 | project.buildFile << """ 48 | apply plugin: 'java' 49 | configurations { 50 | all*.exclude group: 'com.google.guava', module: 'guava' 51 | } 52 | """ 53 | 54 | project.apply plugin: 'java' 55 | 56 | def results = runRulesAgainst(rule) 57 | 58 | then: 59 | results.violates() 60 | } 61 | 62 | def 'exclude matching a transitive dependency does not violate'() { 63 | when: 64 | project.buildFile << """ 65 | configurations { 66 | compile.exclude group: 'commons-logging', module: 'commons-logging' 67 | all*.exclude group: 'commons-lang', module: 'commons-lang' 68 | } 69 | """ 70 | 71 | project.with { 72 | apply plugin: 'java' 73 | repositories { mavenCentral() } 74 | configurations { 75 | compile.exclude group: 'commons-logging', module: 'commons-logging' 76 | compile.exclude group: 'commons-lang', module: 'commons-lang' 77 | } 78 | dependencies { 79 | compile 'commons-configuration:commons-configuration:1.10' 80 | } 81 | } 82 | 83 | def results = runRulesAgainst(rule) 84 | 85 | then: 86 | results.doesNotViolate() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/LintRuleApplicationFailureContextSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule 2 | 3 | import nebula.test.IntegrationSpec 4 | import org.codehaus.groovy.ast.expr.MethodCallExpression 5 | import org.gradle.api.logging.LogLevel 6 | 7 | class LintRuleApplicationFailureContextSpec extends IntegrationSpec { 8 | def setup() { 9 | def lintRuleDirectory = new File(projectDir, 'src/main/resources/META-INF/lint-rules') 10 | lintRuleDirectory.mkdirs() 11 | 12 | buildFile << """ 13 | plugins { 14 | id 'java-library' 15 | } 16 | apply plugin: 'nebula.lint' 17 | repositories { 18 | mavenCentral() 19 | } 20 | dependencies { 21 | implementation('commons-lang:commons-lang:2.6') 22 | } 23 | """.stripIndent() 24 | 25 | logLevel = LogLevel.LIFECYCLE 26 | } 27 | 28 | def 'lint rule application failure with extra context'() { 29 | given: 30 | buildFile << """ 31 | gradleLint.rules = ['test-fails-to-apply-successfully-with-extra-context'] 32 | """.stripIndent() 33 | 34 | when: 35 | def results = runTasksWithFailure('fixGradleLint') 36 | 37 | then: 38 | results.standardError.contains("Error processing rule Lint Rule 'test-fails-to-apply-successfully-with-extra-context'. " + 39 | "Here is some extra context about what to do when you see this unexpected failure") 40 | } 41 | 42 | def 'default lint rule application failure messaging'() { 43 | given: 44 | buildFile << """ 45 | gradleLint.rules = ['test-fails-to-apply-successfully'] 46 | """.stripIndent() 47 | 48 | when: 49 | def results = runTasksWithFailure('fixGradleLint') 50 | 51 | then: 52 | results.standardError.contains("Error processing rule Lint Rule 'test-fails-to-apply-successfully'\n") 53 | } 54 | } 55 | 56 | class ExampleRuleWhichFailsToApplySuccessfully extends AbstractModelAwareExampleGradleLintRule { 57 | String description = 'example rule that fails to apply successfully with no extra context' 58 | 59 | @Override 60 | void visitAnyGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 61 | throw new RuntimeException("testing when something goes wrong") 62 | } 63 | } 64 | 65 | class ExampleRuleWhichFailsToApplySuccessfullyWithExtraContext extends AbstractModelAwareExampleGradleLintRule { 66 | String description = 'example rule that fails to apply successfully with some extra context' 67 | 68 | @Override 69 | protected String ruleFailureContext() { 70 | return "Here is some extra context about what to do when you see this unexpected failure" 71 | } 72 | 73 | @Override 74 | void visitAnyGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 75 | throw new RuntimeException("testing when something goes wrong") 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import com.netflix.nebula.lint.GradleLintViolationAction 20 | import org.gradle.api.Incubating 21 | import org.gradle.api.InvalidUserDataException 22 | 23 | class GradleLintExtension { 24 | List rules = [] 25 | 26 | /** 27 | * Allows for the exclusion of individual rules when we include rule sets 28 | */ 29 | List excludedRules = [] 30 | 31 | /** 32 | * Rules that, when violated, cause the build to fail 33 | */ 34 | List criticalRules = [] 35 | 36 | String reportFormat = 'html' 37 | boolean reportOnlyFixableViolations = false 38 | boolean alwaysRun = true 39 | boolean autoLintAfterFailure = true 40 | 41 | List skipForTasks = ['help', 'tasks', 'dependencies', 'dependencyInsight', 'components', 'model', 'projects', 'properties', 'wrapper'] 42 | 43 | @Incubating 44 | List listeners = [] 45 | 46 | void setReportFormat(String reportFormat) { 47 | if (reportFormat in ['xml', 'html', 'text']) { 48 | this.reportFormat = reportFormat 49 | } else { 50 | throw new InvalidUserDataException("'$reportFormat' is not a valid CodeNarc report format") 51 | } 52 | } 53 | 54 | // pass-thru markers for the linter to know which blocks of code to ignore 55 | void ignore(Closure c) { c() } 56 | void ignore(String ruleName, Closure c) { c() } 57 | void ignore(String r1, String r2, Closure c) { c() } 58 | void ignore(String r1, String r2, String r3, Closure c) { c() } 59 | void ignore(String r1, String r2, String r3, String r4, Closure c) { c() } 60 | void ignore(String r1, String r2, String r3, String r4, String r5, Closure c) { c() } 61 | 62 | void fixme(String ignoreUntil, Closure c) { c() } 63 | void fixme(String ignoreUntil, String ruleName, Closure c) { c() } 64 | void fixme(String ignoreUntil, String r1, String r2, Closure c) { c() } 65 | void fixme(String ignoreUntil, String r1, String r2, String r3, Closure c) { c() } 66 | void fixme(String ignoreUntil, String r1, String r2, String r3, String r4, Closure c) { c() } 67 | void fixme(String ignoreUntil, String r1, String r2, String r3, String r4, String r5, Closure c) { c() } 68 | 69 | void skipForTask(String taskName) { 70 | skipForTasks.add(taskName) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dsl/SpaceAssignmentRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dsl 2 | 3 | import com.netflix.nebula.lint.rule.BuildFiles 4 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 5 | import com.netflix.nebula.lint.utils.IndentUtils 6 | import org.codehaus.groovy.ast.expr.ClosureExpression 7 | import org.codehaus.groovy.ast.expr.MethodCallExpression 8 | 9 | class SpaceAssignmentRule extends ModelAwareGradleLintRule { 10 | 11 | String description = "space-assignment syntax is deprecated" 12 | 13 | @Override 14 | void visitMethodCallExpression(MethodCallExpression call) { 15 | if(dslStack().contains("plugins")) { 16 | return 17 | } 18 | if(call.methodAsString == 'group' && !isGradleGroup(call)) { 19 | return 20 | } 21 | if (call.arguments.size() != 1 || call.arguments[-1] instanceof ClosureExpression) { 22 | return 23 | } 24 | 25 | def receiverClass = receiver(call)?.clazz 26 | if (receiverClass == null) { 27 | return // no enough data to analyze 28 | } 29 | 30 | def invokedMethodName = call.method.value 31 | 32 | // check if the method has a matching property 33 | def setter = receiverClass.getMethods().find { it.name == "set${invokedMethodName.capitalize()}" } 34 | if (setter == null) { 35 | return // no matching property 36 | } 37 | 38 | // check if it's a generated method for space assignment 39 | def exactMethod = receiverClass.getMethods().find { it.name == invokedMethodName } 40 | if (exactMethod != null) { 41 | def deprecatedAnnotation = exactMethod.getAnnotation(Deprecated) 42 | if (deprecatedAnnotation != null) { 43 | // may be false positive when the explicit method is deprecated 44 | addViolation(call) 45 | } 46 | } else { 47 | addViolation(call) 48 | } 49 | } 50 | 51 | private boolean isGradleGroup(MethodCallExpression call) { 52 | if(call.methodAsString != 'group') { 53 | return false 54 | } 55 | 56 | return dslStack().empty || 57 | dslStack().containsAll(['subprojects']) || 58 | dslStack().containsAll(['allprojects']) || 59 | dslStack().contains('configureEach') 60 | } 61 | 62 | private void addViolation(MethodCallExpression call) { 63 | BuildFiles.Original originalFile = buildFiles.original(call.lineNumber) 64 | String replacement = IndentUtils.indentText(call, getReplacement(call)) 65 | addBuildLintViolation(description, call) 66 | .insertBefore(call, replacement) 67 | .deleteLines(originalFile.file, originalFile.line..originalFile.line) 68 | } 69 | 70 | private String getReplacement(MethodCallExpression call){ 71 | def originalLine = getSourceCode().line(call.lineNumber-1) 72 | return originalLine.replaceFirst(call.methodAsString, call.methodAsString + " =") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/ArtifactHelpers.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2018-2019 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.rule.dependency 20 | 21 | import groovy.transform.PackageScope 22 | import nebula.test.dependencies.Coordinate 23 | 24 | import java.util.jar.Attributes 25 | import java.util.jar.JarOutputStream 26 | import java.util.jar.Manifest 27 | import java.util.zip.ZipEntry 28 | 29 | @PackageScope 30 | class ArtifactHelpers { 31 | protected static File setupSamplePomWith(File repo, Coordinate coordinate, String sampleFileContents) { 32 | def sample = new File(repo, coordinate.getGroup() + File.separator + coordinate.getArtifact() + File.separator + coordinate.getVersion()) 33 | sample.mkdirs() 34 | def sampleFile = new File(sample, coordinate.getArtifact() + '-' + coordinate.getVersion() + '.pom') 35 | sampleFile << sampleFileContents 36 | } 37 | 38 | protected static setupSampleJar(File repo, Coordinate coordinate) { 39 | def sampleJarClasspath = repo.absolutePath + File.separator + 40 | coordinate.getGroup() + File.separator + 41 | coordinate.getArtifact() + File.separator + 42 | coordinate.getVersion() + File.separator + 43 | coordinate.getArtifact() + '-' + coordinate.getVersion() + '.jar' 44 | def jar = new File(sampleJarClasspath) 45 | jar.createNewFile() 46 | createJar(jar, manifestWithClasspath(sampleJarClasspath)) 47 | } 48 | 49 | private static def createJar(File jarFile, Manifest manifest = null) throws IOException { 50 | def jarOutputStream 51 | 52 | if (manifest == null) { 53 | jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile)) 54 | } else { 55 | jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile), manifest) 56 | } 57 | 58 | jarOutputStream.putNextEntry(new ZipEntry("META-INF/")) 59 | jarOutputStream.close() 60 | } 61 | 62 | private static Manifest manifestWithClasspath(def manifestClasspath) { 63 | Manifest manifest = new Manifest() 64 | Attributes attributes = manifest.getMainAttributes() 65 | attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0") 66 | if (manifestClasspath != null) { 67 | attributes.putValue("Class-Path", manifestClasspath) 68 | } 69 | return manifest 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/NotNecessarilyGitRepository.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import org.eclipse.jgit.attributes.AttributesNodeProvider 20 | import org.eclipse.jgit.errors.NoWorkTreeException 21 | import org.eclipse.jgit.lib.* 22 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder 23 | 24 | import static org.eclipse.jgit.util.FS.DETECTED 25 | 26 | /** 27 | * Repository implementation that doesn't require the root directory to actually be a git repository. 28 | * The git apply command which we use in the fix task is just a patch application tool that doesn't require 29 | * the target directory to be a git repository. 30 | */ 31 | class NotNecessarilyGitRepository extends Repository { 32 | File workTree 33 | 34 | NotNecessarilyGitRepository(File gitDir) { 35 | super(new FileRepositoryBuilder().setGitDir(gitDir).setFS(DETECTED)); 36 | workTree = gitDir 37 | 38 | } 39 | 40 | @Override 41 | File getWorkTree() throws NoWorkTreeException { 42 | return workTree 43 | } 44 | 45 | @Override 46 | void create(boolean bare) throws IOException { 47 | throw new UnsupportedOperationException('This is not necessarily a git repo') 48 | } 49 | 50 | @Override 51 | ObjectDatabase getObjectDatabase() { 52 | throw new UnsupportedOperationException('This is not necessarily a git repo') 53 | } 54 | 55 | @Override 56 | RefDatabase getRefDatabase() { 57 | throw new UnsupportedOperationException('This is not necessarily a git repo') 58 | } 59 | 60 | @Override 61 | StoredConfig getConfig() { 62 | throw new UnsupportedOperationException('This is not necessarily a git repo') 63 | } 64 | 65 | @Override 66 | AttributesNodeProvider createAttributesNodeProvider() { 67 | throw new UnsupportedOperationException('This is not necessarily a git repo') 68 | } 69 | 70 | @Override 71 | void scanForRepoChanges() throws IOException { 72 | throw new UnsupportedOperationException('This is not necessarily a git repo') 73 | } 74 | 75 | @Override 76 | void notifyIndexChanged(boolean internal) { 77 | throw new UnsupportedOperationException('This is not necessarily a git repo') 78 | } 79 | 80 | @Override 81 | ReflogReader getReflogReader(String refName) throws IOException { 82 | throw new UnsupportedOperationException('This is not necessarily a git repo') 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/UnusedExcludeByConfigurationRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.GradleDependency 20 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 21 | import org.codehaus.groovy.ast.expr.MethodCallExpression 22 | import org.gradle.api.artifacts.Configuration 23 | import org.gradle.api.specs.Specs 24 | 25 | class UnusedExcludeByConfigurationRule extends ModelAwareGradleLintRule { 26 | String description = 'excludes that have no effect on the classpath should be removed for clarity' 27 | 28 | @Override 29 | void visitConfigurationExclude(MethodCallExpression call, String conf, GradleDependency exclude) { 30 | // Since Gradle does not expose any information about which excludes were effective, we will create a new configuration 31 | // lintExcludeConf, adding all first order dependencies from the conf that the exclusion applies to and resolve it. 32 | Configuration lintExcludeConf = project.configurations.create("lintExcludes") 33 | project.configurations.findAll { (conf == 'all' || conf == it.name) && it != lintExcludeConf }*.allDependencies.flatten().each { d -> 34 | project.dependencies.add(lintExcludeConf.name, "$d.group:$d.name:$d.version") 35 | } 36 | 37 | // If we find a dependency in the transitive closure of this special conf, then we can infer that the exclusion is 38 | // doing something. Note that all*.exclude style exclusions are applied to all of the configurations at the time 39 | // of project evaluation, but not lintExcludeConf. 40 | def excludeIsInTransitiveClosure = false 41 | def deps = lintExcludeConf.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies() 42 | while(!deps.isEmpty() && !excludeIsInTransitiveClosure) { 43 | deps = deps.collect { d -> 44 | if((!exclude.group || d.moduleGroup == exclude.group) && (!exclude.name || d.moduleName == exclude.name)) { 45 | excludeIsInTransitiveClosure = true 46 | } 47 | d.children 48 | } 49 | .flatten() 50 | } 51 | 52 | project.configurations.remove(lintExcludeConf) 53 | 54 | if(!excludeIsInTransitiveClosure) { 55 | addBuildLintViolation('the exclude dependency is not in your dependency graph, so has no effect', call) 56 | .delete(call) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyTupleExpressionRuleSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.test.AbstractRuleSpec 20 | 21 | class DependencyTupleExpressionRuleSpec extends AbstractRuleSpec { 22 | def rule = new DependencyTupleExpressionRule() 23 | 24 | def 'dependency tuples violate rule'() { 25 | when: 26 | project.buildFile << """ 27 | dependencies { 28 | compile group: 'junit', name: 'junit', version: '4.11' 29 | } 30 | """ 31 | def results = runRulesAgainst(rule) 32 | 33 | then: 34 | results.violates() 35 | } 36 | 37 | def 'violations are corrected'() { 38 | when: 39 | project.buildFile << """ 40 | dependencies { 41 | compile group: 'junit', 42 | name: 'junit', 43 | version: '4.11' 44 | compile group: 'netflix', name: 'platform' 45 | } 46 | """ 47 | def results = correct(rule) 48 | 49 | then: 50 | results == """ 51 | dependencies { 52 | compile 'junit:junit:4.11' 53 | compile 'netflix:platform' 54 | } 55 | """ 56 | } 57 | 58 | def 'dependency does not violate rule if it contains a secondary method call'() { 59 | when: 60 | project.buildFile << """ 61 | dependencies { 62 | compile dep('junit:junit') 63 | } 64 | """ 65 | def results = runRulesAgainst(rule) 66 | 67 | then: 68 | results.doesNotViolate() 69 | } 70 | 71 | def 'dependency does not violate rule if configuration is present'() { 72 | when: 73 | project.buildFile << """ 74 | dependencies { 75 | compile group: 'junit', name: 'junit', version: '4.11', conf: 'conf' 76 | } 77 | """ 78 | def results = runRulesAgainst(rule) 79 | 80 | then: 81 | results.doesNotViolate() 82 | } 83 | 84 | def 'exclude tuples do not violate rule'() { 85 | when: 86 | project.buildFile << """ 87 | dependencies { 88 | compile('junit:junit:4.11') { 89 | exclude group: 'a' 90 | } 91 | } 92 | """ 93 | def results = runRulesAgainst(rule) 94 | 95 | then: 96 | results.doesNotViolate() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/AppliedFilesAstVisitor.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import com.netflix.nebula.lint.rule.GradleAstUtil 4 | import org.codehaus.groovy.ast.ClassCodeVisitorSupport 5 | import org.codehaus.groovy.ast.expr.MapExpression 6 | import org.codehaus.groovy.ast.expr.MethodCallExpression 7 | import org.codehaus.groovy.control.SourceUnit 8 | import org.gradle.api.Project 9 | 10 | /** 11 | * Groovy AST visitor which searches for `apply from: 'another.gradle'` It takes the file and process it recursively 12 | */ 13 | class AppliedFilesAstVisitor extends ClassCodeVisitorSupport { 14 | 15 | Project project 16 | List appliedFiles = new ArrayList() 17 | Map projectVariablesMapping 18 | 19 | AppliedFilesAstVisitor(Project project) { 20 | this.project = project 21 | projectVariablesMapping = [ 22 | "\$projectDir" : project.projectDir.toString(), 23 | "\${projectDir}" : project.projectDir.toString(), 24 | "\$project.projectDir" : project.projectDir.toString(), 25 | "\${project.projectDir}" : project.projectDir.toString(), 26 | "\$rootDir" : project.rootDir.toString(), 27 | "\${rootDir}" : project.rootDir.toString(), 28 | "\$project.rootDir" : project.rootDir.toString(), 29 | "\${project.rootDir}" : project.rootDir.toString(), 30 | ] 31 | } 32 | 33 | void visitApplyFrom(String from) { 34 | if (! isHttpLink(from)) { 35 | //handle if path contains ${rootDir} ${project.rootDir} ${projectDir} ${project.projectDir} 36 | def projectVariable = projectVariablesMapping.find {from.contains(it.key) } 37 | if (projectVariable) { 38 | def absolutePath = from.replace(projectVariable.key, projectVariable.value) 39 | appliedFiles.addAll(SourceCollector.getAllFiles(new File(absolutePath), project)) 40 | } else { 41 | appliedFiles.addAll(SourceCollector.getAllFiles(new File(project.projectDir, from), project)) 42 | } 43 | } 44 | } 45 | 46 | boolean isHttpLink(String from) { 47 | from.toLowerCase().startsWith("http") 48 | } 49 | 50 | @Override 51 | void visitMethodCallExpression(MethodCallExpression call) { 52 | super.visitMethodCallExpression(call) 53 | def methodName = call.methodAsString 54 | def expressions = call.arguments.expressions 55 | def objectExpression = call.objectExpression.text 56 | 57 | if (methodName == 'ignore' && objectExpression == 'gradleLint') { 58 | return // short-circuit ignore calls 59 | } 60 | 61 | if (methodName == 'apply') { 62 | if (expressions.any { it instanceof MapExpression }) { 63 | def entries = GradleAstUtil.collectEntryExpressions(call) 64 | if (entries.from) { 65 | visitApplyFrom(entries.from) 66 | } 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | protected SourceUnit getSourceUnit() { 73 | throw new RuntimeException("should never be called") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/AbstractLintPluginTaskConfigurer.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.plugin 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.Project 5 | import org.gradle.api.Task 6 | 7 | 8 | abstract class AbstractLintPluginTaskConfigurer { 9 | public static final String LINT_GROUP = 'lint' 10 | public static final String AUTO_LINT_GRADLE = 'autoLintGradle' 11 | public static final String CLEAN_TASK_NAME = 'clean' 12 | public static final String LINT_GRADLE = 'lintGradle' 13 | public static final String CRITICAL_LINT_GRADLE = 'criticalLintGradle' 14 | public static final String FIX_GRADLE_LINT = 'fixGradleLint' 15 | public static final String FIX_LINT_GRADLE = 'fixLintGradle' 16 | public static final String GENERATE_GRADLE_LINT_REPORT = 'generateGradleLintReport' 17 | 18 | void configure(Project project) { 19 | def lintExt = project.extensions.create('gradleLint', GradleLintExtension) 20 | createTasks(project, lintExt) 21 | wireJavaPluginConditionally(project) 22 | } 23 | 24 | abstract void createTasks(Project project, GradleLintExtension lintExtension) 25 | 26 | protected void wireJavaPluginConditionally(Project project) { 27 | boolean wireJavaProject = project.hasProperty('gradleLint.wireJavaPlugin') ? Boolean.valueOf(project.property('gradleLint.wireJavaPlugin').toString()) : true 28 | if(wireJavaProject) { 29 | wireJavaPlugin(project) 30 | } 31 | } 32 | 33 | abstract void wireJavaPlugin(Project project) 34 | abstract Action configureReportAction(Project project, GradleLintExtension extension) 35 | 36 | protected static boolean hasValidTaskConfiguration(Project project, GradleLintExtension lintExt) { 37 | boolean shouldLint = project.hasProperty('gradleLint.alwaysRun') ? 38 | Boolean.valueOf(project.property('gradleLint.alwaysRun').toString()) : lintExt.alwaysRun 39 | boolean excludedAutoLintGradle = project.gradle.startParameter.excludedTaskNames.contains(AUTO_LINT_GRADLE) 40 | boolean skipForSpecificTask = project.gradle.startParameter.taskNames.any { lintExt.skipForTasks.contains(it) } 41 | return shouldLint && !excludedAutoLintGradle && !skipForSpecificTask 42 | } 43 | 44 | protected static boolean hasExplicitLintTask(List allTasks, def lintTasks) { 45 | allTasks.any { 46 | lintTasks.contains(it) 47 | } 48 | } 49 | 50 | protected static boolean hasFailedCriticalLintTask(List tasks, def criticalLintTask) { 51 | return tasks.any { it == criticalLintTask && it.state.failure != null } 52 | } 53 | 54 | protected static String getReportFormat(Project project, GradleLintExtension extension) { 55 | return project.hasProperty('gradleLint.reportFormat') ? project.property('gradleLint.reportFormat') : extension.reportFormat 56 | } 57 | 58 | protected static boolean getReportOnlyFixableViolations(Project project, GradleLintExtension extension) { 59 | return project.hasProperty('gradleLint.reportOnlyFixableViolations') ? Boolean.valueOf(project.property('gradleLint.reportOnlyFixableViolations').toString()) : extension.reportOnlyFixableViolations 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/MultiProjectCircularDependencyRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 4 | import groovy.transform.CompileDynamic 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.TupleConstructor 7 | import org.codehaus.groovy.ast.expr.ConstantExpression 8 | import org.codehaus.groovy.ast.expr.Expression 9 | import org.codehaus.groovy.ast.expr.MethodCallExpression 10 | 11 | 12 | @CompileStatic 13 | class MultiProjectCircularDependencyRule extends ModelAwareGradleLintRule { 14 | String description = 'Detect circular dependencies in multi projects' 15 | 16 | private final String PROJECT_METHOD_NAME = 'project' 17 | private final String PROJECT_GRADLE_REFERENCE = ':' 18 | private final String EMPTY_STRING = '' 19 | static final List allProjectDependencies = [] 20 | 21 | @Override 22 | void visitDependencies(MethodCallExpression call) { 23 | List projectDependecies = findProjectDependecies(call) 24 | if(!projectDependecies) { 25 | return 26 | } 27 | 28 | projectDependecies.each { MethodCallExpression projectDependency -> 29 | String dependsOn = findProjectName(projectDependency) 30 | if(!dependsOn) { 31 | return 32 | } 33 | ProjectDependency dependency = new ProjectDependency(project.name, dependsOn) 34 | allProjectDependencies << dependency 35 | if (isCircularDependency(allProjectDependencies, dependency)) { 36 | addBuildLintViolation("Multi-project circular dependencies are not allowed. Circular dependency found between projects '$dependency.name' and '$dependency.dependsOn'", call) 37 | } 38 | } 39 | } 40 | 41 | @CompileDynamic 42 | private String findProjectName(Expression projectDependency) { 43 | String projectName = projectDependency?.arguments?.expressions?.find { it instanceof ConstantExpression }?.value 44 | if(!projectName) { 45 | return null 46 | } 47 | return projectName.replace(PROJECT_GRADLE_REFERENCE, EMPTY_STRING) 48 | } 49 | 50 | 51 | @CompileDynamic 52 | private List findProjectDependecies(MethodCallExpression call) { 53 | List expressions = call.arguments.expressions*.code*.statements.flatten().expression 54 | List dependencyExpressions = expressions.findAll { it instanceof MethodCallExpression }.arguments.expressions.flatten().findAll { 55 | it instanceof MethodCallExpression && ((MethodCallExpression) it).methodAsString == PROJECT_METHOD_NAME 56 | } 57 | return dependencyExpressions 58 | } 59 | 60 | private boolean isCircularDependency(List projectDependencies, ProjectDependency current) { 61 | projectDependencies.any { projectDependency -> 62 | projectDependency.name == current.dependsOn && projectDependency.dependsOn == current.name 63 | } 64 | } 65 | 66 | @TupleConstructor 67 | private static class ProjectDependency { 68 | String name 69 | String dependsOn 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/task/TaskDefinitionOperatorRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.task 2 | 3 | 4 | import com.netflix.nebula.lint.GradleViolation 5 | import com.netflix.nebula.lint.rule.GradleLintRule 6 | import groovy.transform.CompileStatic 7 | import org.apache.commons.lang.StringUtils 8 | import org.codehaus.groovy.ast.expr.BinaryExpression 9 | import org.codehaus.groovy.ast.expr.MethodCallExpression 10 | import org.codehaus.groovy.ast.expr.VariableExpression 11 | 12 | class TaskDefinitionOperatorRule extends GradleLintRule { 13 | 14 | private final String TASK_DO_LAST = 'doLast' 15 | private final String TASK_INDICATOR = 'task' 16 | private final String TASK_OPERATOR = '<<' 17 | private final String OPENING_BLACKET = '{' 18 | private final String CLOSING_BRACKET = '}' 19 | private final String EMPTY_STRING = '' 20 | 21 | String description = "The $TASK_OPERATOR operator was deprecated. Need to use doLast method" 22 | 23 | @Override 24 | void visitMethodCallExpression(MethodCallExpression call) { 25 | if(call.methodAsString == TASK_INDICATOR) { 26 | BinaryExpression binaryExpression = call.arguments.expressions.find { it instanceof BinaryExpression } as BinaryExpression 27 | if(binaryExpression && binaryExpression.operation.text == TASK_OPERATOR){ 28 | GradleViolation violation = addBuildLintViolation("The $TASK_OPERATOR operator was deprecated. Need to use doLast method", call) 29 | if(binaryExpression.leftExpression instanceof VariableExpression) { 30 | String changes = changeTaskDeclaration(call, binaryExpression, violation) 31 | violation.replaceWith(call, changes) 32 | } 33 | } 34 | } 35 | } 36 | 37 | private String changeTaskDeclaration(MethodCallExpression call, BinaryExpression binaryExpression, GradleViolation violation) { 38 | String taskName = binaryExpression.leftExpression.variable 39 | String indent = ' ' * (call.columnNumber - 1) 40 | String changes = "$TASK_INDICATOR $taskName {\n" + 41 | " $indent$TASK_DO_LAST $OPENING_BLACKET\n" 42 | List lines = violation.files.text.readLines() 43 | List closureLines = lines.subList(call.lineNumber-1, call.lastLineNumber) 44 | List indentedLines = closureLines.collect { " $it" } 45 | String originalClosure = indentedLines.join('\n') 46 | changes += extractClosureCodeBlock(taskName, originalClosure) 47 | changes += "\n $indent$CLOSING_BRACKET\n$indent$CLOSING_BRACKET" 48 | return changes 49 | } 50 | 51 | @CompileStatic 52 | private String extractClosureCodeBlock(String taskName, String originalClosure) { 53 | String codeBlock = originalClosure.replace(TASK_INDICATOR, EMPTY_STRING) 54 | .replace(taskName, EMPTY_STRING) 55 | .replace(TASK_OPERATOR, EMPTY_STRING) 56 | 57 | codeBlock = StringUtils.replaceOnce(codeBlock, OPENING_BLACKET, EMPTY_STRING) 58 | 59 | int closingBracketIndex = codeBlock.lastIndexOf(CLOSING_BRACKET) 60 | if(closingBracketIndex > 0) 61 | codeBlock = codeBlock.substring(0, closingBracketIndex) 62 | 63 | return codeBlock 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/self/ShadedDependenciesTest.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2020 Netflix, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.netflix.nebula.lint.self 20 | 21 | 22 | import org.junit.Test 23 | import spock.lang.Specification 24 | 25 | class ShadedDependenciesTest extends Specification implements AbstractShadedDependencies { 26 | File directory = new File("build/reports/project") 27 | 28 | @Test 29 | def 'compile classpath contains dependencies from shaded and unshaded configurations'() { 30 | when: 31 | File report = new File(directory, "compileClasspath-dependencies.txt"); 32 | report.exists() 33 | 34 | then: 35 | SHARED_COORDINATES.each { shadedCoordinate -> 36 | String artifactGroupAndName = "${shadedCoordinate.artifactGroup}:${shadedCoordinate.artifactName}" 37 | report.text.contains(artifactGroupAndName) 38 | } 39 | report.text.contains('org.apache.maven:maven-model-builder') 40 | } 41 | 42 | @Test 43 | def 'runtime classpath contains dependencies from shaded and unshaded configurations'() { 44 | when: 45 | File report = new File(directory, "runtimeClasspath-dependencies.txt"); 46 | report.exists() 47 | 48 | then: 49 | SHARED_COORDINATES.each { shadedCoordinate -> 50 | String artifactGroupAndName = "${shadedCoordinate.artifactGroup}:${shadedCoordinate.artifactName}" 51 | report.text.contains(artifactGroupAndName) 52 | } 53 | report.text.contains('org.apache.maven:maven-model-builder') 54 | } 55 | 56 | @Test 57 | def 'test compile classpath contains dependencies from shaded and unshaded configurations'() { 58 | when: 59 | File report = new File(directory, "testCompileClasspath-dependencies.txt"); 60 | report.exists() 61 | 62 | then: 63 | SHARED_COORDINATES.each { shadedCoordinate -> 64 | String artifactGroupAndName = "${shadedCoordinate.artifactGroup}:${shadedCoordinate.artifactName}" 65 | report.text.contains(artifactGroupAndName) 66 | } 67 | report.text.contains('org.apache.maven:maven-model-builder') 68 | } 69 | 70 | @Test 71 | def 'test runtime classpath contains dependencies from shaded and unshaded configurations'() { 72 | when: 73 | File report = new File(directory, "testRuntimeClasspath-dependencies.txt"); 74 | report.exists() 75 | 76 | then: 77 | SHARED_COORDINATES.each { shadedCoordinate -> 78 | String artifactGroupAndName = "${shadedCoordinate.artifactGroup}:${shadedCoordinate.artifactName}" 79 | report.text.contains(artifactGroupAndName) 80 | } 81 | report.text.contains('org.apache.maven:maven-model-builder') 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/plugin/LintRuleRegistry.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import com.netflix.nebula.lint.rule.GradleLintRule 20 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 21 | import org.codenarc.rule.Rule 22 | import org.gradle.api.Project 23 | 24 | class LintRuleRegistry { 25 | static ClassLoader classLoader = null 26 | 27 | private static LintRuleDescriptor findRuleDescriptor(String ruleId) { 28 | assert classLoader != null 29 | URL resource = classLoader.getResource(String.format("META-INF/lint-rules/%s.properties", ruleId)) 30 | return resource ? new LintRuleDescriptor(resource) : null 31 | } 32 | 33 | static List findRules(String ruleId) { 34 | assert classLoader != null 35 | def ruleDescriptor = findRuleDescriptor(ruleId) 36 | if (ruleDescriptor == null) 37 | return [] 38 | 39 | if(ruleDescriptor.implementationClassName) 40 | return [ruleId] 41 | else 42 | return (ruleDescriptor.includes?.collect { findRules(it as String) }?.flatten() ?: []) as List 43 | } 44 | 45 | 46 | 47 | List buildRules(String ruleId, Project project, boolean critical) { 48 | assert classLoader != null 49 | def ruleDescriptor = findRuleDescriptor(ruleId) 50 | if (ruleDescriptor == null) 51 | return [] 52 | 53 | def implClassName = ruleDescriptor.implementationClassName 54 | def includes = ruleDescriptor.includes 55 | 56 | if (!implClassName && includes.isEmpty()) { 57 | throw new InvalidRuleException(String.format("No implementation class or includes specified for rule '%s' in %s.", ruleId, ruleDescriptor)) 58 | } 59 | 60 | def included = includes.collect { buildRules(it as String, project, critical) }.flatten() as List 61 | 62 | if(implClassName) { 63 | try { 64 | Rule r = (Rule) classLoader.loadClass(implClassName).newInstance() 65 | if(r instanceof ModelAwareGradleLintRule) { 66 | (r as ModelAwareGradleLintRule).project = project 67 | } 68 | 69 | if(r instanceof GradleLintRule) { 70 | r.ruleId = ruleId 71 | r.critical = critical 72 | } 73 | 74 | return included + r 75 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { 76 | throw new InvalidRuleException(String.format( 77 | "Could not find or load implementation class '%s' for rule '%s' specified in %s.", implClassName, ruleId, ruleDescriptor), e) 78 | } 79 | } else { 80 | return included 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/GradleDependency.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule 18 | 19 | import groovy.transform.Canonical 20 | import groovy.transform.CompileDynamic 21 | import groovy.transform.CompileStatic 22 | import org.gradle.api.artifacts.ModuleIdentifier 23 | import org.gradle.api.artifacts.ModuleVersionIdentifier 24 | import org.gradle.api.internal.artifacts.DefaultModuleIdentifier 25 | import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier 26 | 27 | @CompileStatic 28 | @Canonical 29 | class GradleDependency implements Cloneable { 30 | String group 31 | String name 32 | String version 33 | String classifier 34 | String ext 35 | String conf // no way to express a conf in string notation 36 | Syntax syntax 37 | 38 | enum Syntax { 39 | MapNotation, 40 | StringNotation, 41 | EvaluatedArbitraryCode 42 | } 43 | 44 | @CompileDynamic 45 | ModuleVersionIdentifier toModuleVersion() { 46 | return new DefaultModuleVersionIdentifier(group, name, version) 47 | } 48 | 49 | @CompileDynamic 50 | ModuleIdentifier toModule() { 51 | return new DefaultModuleIdentifier(group, name) 52 | } 53 | 54 | // group:name:version:classifier@extension 55 | String toNotation() { 56 | def notation = (group ?: '') + ':' 57 | notation += name 58 | if(version) notation += ":$version" 59 | if(version && classifier) notation += ":$classifier" 60 | if(!version && classifier) notation += "::$classifier" 61 | if(ext) notation += "@$ext" 62 | 63 | return notation 64 | } 65 | 66 | Map toMap() { 67 | Map dependency = new HashMap<>() 68 | if (group) dependency.put('group', group) 69 | if (name) dependency.put('name', name) 70 | if (version) dependency.put('version', version) 71 | if (classifier) dependency.put('classifier', classifier) 72 | if (ext) dependency.put('ext', ext) 73 | if (conf) dependency.put('conf', conf) 74 | 75 | return dependency 76 | } 77 | 78 | static GradleDependency fromConstant(Object expr) { 79 | def matcher = expr =~ /(?[^:]+)?(:(?[^:]+))(:(?[^@:]+)?(?:[^@]+)?(?@.+)?)?/ 80 | if (matcher.matches()) { 81 | return new GradleDependency( 82 | matcher.group('group'), 83 | matcher.group('name'), 84 | matcher.group('version'), 85 | matcher.group('classifier')?.substring(1), // strip the leading `:` 86 | matcher.group('ext')?.substring(1), // strip the leading `@` 87 | null, 88 | GradleDependency.Syntax.StringNotation) 89 | } 90 | return null 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/UnusedDependencyExcludeRule.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.rule.dependency 18 | 19 | import com.netflix.nebula.lint.rule.GradleAstUtil 20 | import com.netflix.nebula.lint.rule.GradleDependency 21 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 22 | import org.codehaus.groovy.ast.expr.MethodCallExpression 23 | 24 | class UnusedDependencyExcludeRule extends ModelAwareGradleLintRule { 25 | String description = 'excludes that have no effect on the classpath should be removed for clarity' 26 | 27 | GradleDependency dependency 28 | MethodCallExpression dependencyCall 29 | 30 | @Override 31 | void visitGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 32 | dependency = dep 33 | dependencyCall = call 34 | } 35 | 36 | @Override 37 | void visitMethodCallExpression(MethodCallExpression call) { 38 | if(callStack.contains(dependencyCall)) { 39 | // https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ModuleDependency.html#exclude(java.util.Map) 40 | if (call.methodAsString == 'exclude') { 41 | def entries = GradleAstUtil.collectEntryExpressions(call) 42 | if (isExcludeUnnecessary(entries.group, entries.module)) { 43 | addBuildLintViolation("the excluded dependency is not a transitive of $dependency.group:$dependency.name:$dependency.version, so has no effect", call) 44 | .documentationUri("https://github.com/nebula-plugins/gradle-lint-plugin/wiki/Unused-Exclude-Rule") 45 | .delete(call) 46 | } 47 | } 48 | } 49 | } 50 | 51 | private boolean isExcludeUnnecessary(String group, String name) { 52 | // Use detached configurations instead of project configurations to avoid threading issues 53 | // in Gradle 9.x which requires exclusive locks for configuration resolution 54 | def detachedConf = project.configurations.detachedConfiguration( 55 | project.dependencies.create("$dependency.group:$dependency.name:$dependency.version") 56 | ) 57 | 58 | // This is thread-safe and doesn't require exclusive locks 59 | def resolutionResult = detachedConf.incoming.resolutionResult 60 | 61 | def excludeIsInTransitiveClosure = false 62 | 63 | def allComponents = resolutionResult.allComponents 64 | 65 | for (component in allComponents) { 66 | def moduleVersion = component.moduleVersion 67 | if (moduleVersion && 68 | (!group || moduleVersion.group == group) && 69 | (!name || moduleVersion.name == name)) { 70 | excludeIsInTransitiveClosure = true 71 | break 72 | } 73 | } 74 | 75 | return !excludeIsInTransitiveClosure 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/plugin/LintRuleRegistrySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.nebula.lint.plugin 18 | 19 | import com.netflix.nebula.lint.rule.AbstractExampleGradleLintRule 20 | import com.netflix.nebula.lint.rule.GradleLintRule 21 | import org.gradle.api.Project 22 | import org.junit.Rule 23 | import org.junit.rules.TemporaryFolder 24 | import spock.lang.Specification 25 | 26 | class LintRuleRegistrySpec extends Specification { 27 | @Rule 28 | TemporaryFolder temp 29 | 30 | def setup() { 31 | LintRuleRegistry.classLoader = new URLClassLoader([temp.root.toURI().toURL()] as URL[], getClass().getClassLoader()) 32 | } 33 | 34 | def 'load a rule with a single defined implementation class'() { 35 | setup: 36 | def project = Mock(Project) 37 | 38 | def singleRule = ruleFile('single-rule') 39 | singleRule << "implementation-class=${MockRule1.name}" 40 | 41 | when: 42 | def rules = new LintRuleRegistry().buildRules('single-rule', project, false) 43 | 44 | then: 45 | rules.size() == 1 46 | rules[0] instanceof MockRule1 47 | rules[0].ruleId == 'single-rule' 48 | } 49 | 50 | def 'load a rule that includes other rules'() { 51 | setup: 52 | def project = Mock(Project) 53 | 54 | def rule1 = ruleFile('rule1') 55 | rule1 << "implementation-class=${MockRule1.name}" 56 | 57 | def rule2 = ruleFile('rule2') 58 | rule2 << "implementation-class=${MockRule2.name}" 59 | 60 | def composite = ruleFile('composite') 61 | composite << 'includes=rule1,rule2' 62 | 63 | when: 64 | def rules = new LintRuleRegistry().buildRules('composite', project, false) 65 | 66 | then: 67 | rules.size() == 2 68 | rules[0] instanceof MockRule1 69 | rules[1] instanceof MockRule2 70 | } 71 | 72 | def 'list rule ids associated with a single rule id'() { 73 | setup: 74 | def rule1 = ruleFile('rule1') 75 | rule1 << "implementation-class=${MockRule1.name}" 76 | 77 | def rule2 = ruleFile('rule2') 78 | rule2 << "implementation-class=${MockRule2.name}" 79 | 80 | def composite = ruleFile('composite') 81 | composite << 'includes=rule1,rule2' 82 | 83 | when: 84 | def rules = new LintRuleRegistry().findRules('composite') 85 | 86 | then: 87 | rules.size() == 2 88 | rules[0] == 'rule1' 89 | rules[1] == 'rule2' 90 | } 91 | 92 | private File ruleFile(String ruleId) { 93 | new File(temp.root, 'META-INF/lint-rules').mkdirs() 94 | temp.newFile("META-INF/lint-rules/${ruleId}.properties") 95 | } 96 | 97 | static class MockRule1 extends AbstractExampleGradleLintRule { 98 | String description = 'mock1' 99 | } 100 | 101 | static class MockRule2 extends AbstractExampleGradleLintRule { 102 | String description = 'mock2' 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/groovy/com/netflix/nebula/lint/rule/dependency/AbstractDuplicateDependencyClassRule.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import com.netflix.nebula.lint.rule.GradleDependency 4 | import com.netflix.nebula.lint.rule.ModelAwareGradleLintRule 5 | import org.codehaus.groovy.ast.ClassNode 6 | import org.codehaus.groovy.ast.expr.MethodCallExpression 7 | import org.gradle.api.artifacts.Configuration 8 | import org.gradle.api.artifacts.ModuleVersionIdentifier 9 | 10 | abstract class AbstractDuplicateDependencyClassRule extends ModelAwareGradleLintRule { 11 | String description = 'classpaths with duplicate classes may break unpredictably depending on the order in which dependencies are provided to the classpath' 12 | 13 | Set directlyUsedConfigurations = [] as Set 14 | Set ignoredDependencies = [] as Set 15 | 16 | Set resolvableAndResolvedConfigurations 17 | DependencyService dependencyService 18 | 19 | abstract protected List moduleIds(Configuration conf) 20 | 21 | protected static List firstOrderModuleIds(Configuration conf) { 22 | return conf.resolvedConfiguration.firstLevelModuleDependencies.collect { it.module.id } 23 | } 24 | 25 | protected static List transitiveModuleIds(Configuration conf) { 26 | // Classifier artifacts (javadoc/sources/etc.) can sometimes be resolved implicitly causing duplicates, we're interested only in distinct modules 27 | return conf.resolvedConfiguration.resolvedArtifacts 28 | .collect { it.moduleVersion.id } 29 | .unique { it.module.toString() } 30 | } 31 | 32 | @Override 33 | protected void beforeApplyTo() { 34 | dependencyService = DependencyService.forProject(project) 35 | resolvableAndResolvedConfigurations = dependencyService.resolvableAndResolvedConfigurations() 36 | } 37 | 38 | @Override 39 | void visitGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { 40 | if (ignored) { 41 | ignoredDependencies.add(dep.toModuleVersion()) 42 | } else { 43 | def confModel = project.configurations.findByName(conf) 44 | if (confModel) { 45 | directlyUsedConfigurations.add(confModel) 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | void visitClassComplete(ClassNode node) { 52 | for (Configuration conf : directlyUsedConfigurations) { 53 | String resolvableConfigurationName = dependencyService.findAndReplaceNonResolvableConfiguration(conf).name 54 | if(!resolvableConfigurationName) { 55 | continue 56 | } 57 | Configuration toResolve = project.configurations.getByName(resolvableConfigurationName) 58 | if (!toResolve) { 59 | continue 60 | } 61 | def moduleIds = moduleIds(toResolve) 62 | def duplicateDependencyService = Class.forName('com.netflix.nebula.lint.rule.dependency.DuplicateDependencyService').newInstance(project) 63 | def checkForDuplicates = duplicateDependencyService.class.methods.find { 64 | it.name == 'violationsForModules' 65 | } 66 | def violations = checkForDuplicates.invoke(duplicateDependencyService, moduleIds, conf, ignoredDependencies) as List 67 | violations.each { message -> 68 | addBuildLintViolation(message) 69 | .documentationUri("https://github.com/nebula-plugins/gradle-lint-plugin/wiki/Duplicate-Classes-Rule") 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/issues/Issue314Spec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.issues 2 | 3 | import com.netflix.nebula.lint.BaseIntegrationTestKitSpec 4 | 5 | class Issue314Spec extends BaseIntegrationTestKitSpec { 6 | 7 | def main = ''' 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | public class Main { 11 | public static void main(String[] args) { 12 | Log log = LogFactory.getLog(Main.class); 13 | log.info("foo"); 14 | } 15 | } 16 | ''' 17 | 18 | def 'lintGradle does not fail when build.gradle has java-test-fixtures'() { 19 | setup: 20 | writeJavaSourceFile(main, 'src/testFixtures/java') 21 | 22 | buildFile.text = """ 23 | plugins { 24 | id "java-library" 25 | id "java-test-fixtures" 26 | id "nebula.lint" 27 | } 28 | 29 | gradleLint { 30 | rules = ['all-dependency'] 31 | } 32 | 33 | repositories { mavenCentral() } 34 | 35 | dependencies { 36 | testFixturesImplementation "commons-logging:commons-logging:1.2" 37 | } 38 | """ 39 | 40 | when: 41 | def results = runTasks('lintGradle') 42 | 43 | then: 44 | println results?.output 45 | noExceptionThrown() 46 | } 47 | 48 | def 'lintGradle finds unused dependencies in java-test-fixtures'() { 49 | setup: 50 | writeJavaSourceFile(main, 'src/testFixtures/java') 51 | 52 | buildFile.text = """ 53 | plugins { 54 | id "java-library" 55 | id "java-test-fixtures" 56 | id "nebula.lint" 57 | } 58 | 59 | gradleLint { 60 | rules = ['all-dependency'] 61 | } 62 | 63 | repositories { mavenCentral() } 64 | 65 | dependencies { 66 | testFixturesImplementation "commons-io:commons-io:2.8.0" 67 | testFixturesImplementation "commons-logging:commons-logging:1.2" 68 | } 69 | """ 70 | 71 | when: 72 | def results = runTasksAndFail('lintGradle') 73 | 74 | then: 75 | println results?.output 76 | results.output.contains('this dependency is unused and can be removed') 77 | } 78 | 79 | def 'fixGradleLint should remove unused dependencies in java-test-fixtures'() { 80 | setup: 81 | writeJavaSourceFile(main, 'src/testFixtures/java') 82 | 83 | buildFile.text = """ 84 | plugins { 85 | id "java-library" 86 | id "java-test-fixtures" 87 | id "nebula.lint" 88 | } 89 | 90 | gradleLint { 91 | rules = ['all-dependency'] 92 | } 93 | 94 | repositories { mavenCentral() } 95 | 96 | dependencies { 97 | testFixturesImplementation "commons-io:commons-io:2.8.0" 98 | testFixturesImplementation "commons-logging:commons-logging:1.2" 99 | } 100 | """ 101 | 102 | when: 103 | def results = runTasks('fixGradleLint') 104 | 105 | then: 106 | println results?.output 107 | results.output.contains('this dependency is unused and can be removed') 108 | results.output.contains('build.gradle:15') 109 | results.output.contains('testFixturesImplementation "commons-io:commons-io:2.8.0"') 110 | } 111 | } -------------------------------------------------------------------------------- /src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyViolationUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.nebula.lint.rule.dependency 2 | 3 | import com.netflix.nebula.lint.GradleViolation 4 | import com.netflix.nebula.lint.rule.BuildFiles 5 | import com.netflix.nebula.lint.rule.GradleDependency 6 | import org.codehaus.groovy.ast.expr.MethodCallExpression 7 | import org.junit.Rule 8 | import org.junit.rules.TestName 9 | import spock.lang.Specification 10 | import spock.lang.Subject 11 | 12 | @Subject(DependencyViolationUtil) 13 | class DependencyViolationUtilSpec extends Specification { 14 | 15 | @Rule 16 | final TestName testName = new TestName() 17 | 18 | def 'replaces dependency configuration - single line'() { 19 | setup: 20 | GradleViolation mockViolation = Mock(GradleViolation) 21 | MethodCallExpression mockMethodCallExpression = Mock(MethodCallExpression) 22 | GradleDependency mockGradleDependency = Mock(GradleDependency) 23 | 24 | when: 25 | DependencyViolationUtil.replaceDependencyConfiguration(mockViolation, mockMethodCallExpression, "implementation", mockGradleDependency) 26 | 27 | then: 28 | 1 * mockViolation.replaceWith(mockMethodCallExpression, "implementation 'example:foo:1.0.0'") 29 | 1 * mockGradleDependency.toNotation() >> 'example:foo:1.0.0' 30 | } 31 | 32 | def 'replaces dependency configuration - multi line'() { 33 | setup: 34 | File projectDir = new File("build/nebulatest/${this.class.canonicalName}/${testName.methodName.replaceAll(/\W+/, '-')}").absoluteFile 35 | if (projectDir.exists()) { 36 | projectDir.deleteDir() 37 | } 38 | projectDir.mkdirs() 39 | File buildFile = new File(projectDir, "build.gradle") 40 | buildFile << """ 41 | plugins { 42 | id 'nebula.lint' 43 | id 'java-library' 44 | } 45 | 46 | gradleLint.rules = ['deprecated-dependency-configuration'] 47 | 48 | repositories { 49 | mavenCentral() 50 | } 51 | 52 | dependencies { 53 | compile('example:foo:1.0.0') { 54 | exclude module: 'spring-boot-starter-tomcat' 55 | } 56 | } 57 | """ 58 | BuildFiles buildFiles = new BuildFiles([buildFile]) 59 | GradleViolation mockViolation = Mock(GradleViolation) 60 | MethodCallExpression mockMethodCallExpression = Mock(MethodCallExpression) 61 | 62 | when: 63 | DependencyViolationUtil.replaceDependencyConfiguration(mockViolation, mockMethodCallExpression, 'implementation') 64 | 65 | then: 66 | 1 * mockViolation.getFiles() >> buildFiles 67 | 1 * mockMethodCallExpression.getLineNumber() >> 13 68 | 1 * mockMethodCallExpression.getLastLineNumber() >> 16 69 | 1 * mockViolation.replaceWith(mockMethodCallExpression, "dependencies {\n implementation(\'example:foo:1.0.0\') {\n exclude module: \'spring-boot-starter-tomcat\'\n }") 70 | 1 * mockMethodCallExpression.getMethodAsString() >> 'compile' 71 | } 72 | 73 | def 'replaces project dependency configuration'() { 74 | setup: 75 | GradleViolation mockViolation = Mock(GradleViolation) 76 | MethodCallExpression mockMethodCallExpression = Mock(MethodCallExpression) 77 | 78 | when: 79 | DependencyViolationUtil.replaceProjectDependencyConfiguration(mockViolation, mockMethodCallExpression, "implementation", ":sub1") 80 | 81 | then: 82 | 1 * mockViolation.replaceWith(mockMethodCallExpression, "implementation project(':sub1')") 83 | } 84 | 85 | } 86 | --------------------------------------------------------------------------------