better code through lint and checkstyle
TRANSCRIPT
Better Code through Lint and Checkstyledroidcon New York City | 28.08.2015 | Marc Prengemann
About me
Marc Prengemann Android Software Engineer
Mail: [email protected] Wire: [email protected] Github: winterDroid Google+: Marc Prengemann Twitter: @winterDroid89
Lint? Checkstyle? What?
What is Lint and Checkstyle?
App Source Files
Configuration
Analysis Tool Report
When should I use it?
• to ensure code quality • focus in reviews on real code • prevent people from misusing internal
libraries
… but what are the challenges?
• Lint API is @Beta • getting familiar with the API • integrating within your Gradle Build • debugging / testing
Getting started with own Checks
Test ideas
• Fragments and Activities should extend your BaseClass
• use ViewUtils instead of finding and casting a View
• don’t check floats for equality - use Float.equals instead
• find leaking resources
• enforce Naming conventions
• find hardcoded values in XMLs
Library misuse detection with Lint
A real life example
• Timber
• logger by Jake Wharton
• https://github.com/JakeWharton/timber
• want to create a detector that detects misuse of android.util.Log instead of Timber
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Detector
• responsible for scanning through code and to find issues and report them
Detector
• responsible for scanning through code and to find issues and report them
• most detectors implement one or more scanner interfaces that depend on the specified scope
• Detector.XmlScanner
• Detector.JavaScanner
• Detector.ClassScanner
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Detector
• responsible for scanning through code and finding issue instances and reporting them
• most detectors implement one or more scanner interfaces
• can detect multiple different issues
• allows you to have different severities for different types of issues
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Detector
• responsible for scanning through code and finding issue instances and reporting them
• most detectors implement one or more scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• overwritten method depends on implemented scanner interface
• depends on the goal of the detector
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Detector
• responsible for scanning through code and finding issue instances and reporting them
• most detectors implement one or more scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• analyze the found calls
• overwritten method depends on implemented scanner interface
• depends on the goal of the detector
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Detector
• responsible for scanning through code and finding issue instances and reporting them
• most detectors implement one or more scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• analyze the found calls
• report the found issue
• specify the location
• report() will handle to suppress warnings
• add a message for the warning
public class WrongTimberUsageDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = ...; @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } VariableReference ref = (VariableReference) node.astOperand(); String refName = ref.astIdentifier().astValue(); if (!"Timber".equals(refName)) { context.report(ISSUE, node, context.getLocation(node), String.format("Use 'Timber' instead of '%s'", refName)); } }}
Issue
• potential bug in an Android application
• is discovered by a Detector
• are exposed to the userpublic static final Issue ISSUE =
Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• should be unique
• recommended to add the package name as a prefix like com.wire.LogNotTimber
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• typically 5-6 words or less
• describe the problem rather than the fix public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• should include a suggestion how to fix it public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• Lint
• Correctness (incl. Messages)
• Security
• Performance
• Usability (incl. Icons, Typography)
• Accessibility
• Internationalization
• Bi-directional text
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• a number from 1 to 10
• 10 being most important/severe
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• Fatal
• Error
• Warning
• Informational
• Ignore
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• the default implementation for this issue
• maps to the Detector class
• specifies the scope of the implementation
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• the default implementation for this issue
• the scope of the implementation
• describes set of files a detector must consider when performing its analysis
• include:
• Resource files / folder
• Java files
• Class files
public static final Issue ISSUE = Issue.create("LogNotTimber", "Used Log instead of Timber", "Since Timber is included in the project, "
+ "it is likely that calls to Log should " + "instead be going to Timber.", Category.MESSAGES, 5, Severity.WARNING, new Implementation(WrongTimberUsageDetector.class, Scope.JAVA_FILE_SCOPE));
Issue Registry
• provide list of checks to be performed
• return a list of Issues in getIssues()
public class CustomIssueRegistry extends IssueRegistry { @Override public List<Issue> getIssues() { return Arrays.asList(WrongTimberUsageDetector.ISSUE); } }
Naming Convention enforcement with Checkstyle
A real life example
• Naming Conventions for variables
• improve Readability
public final static int FirstNumber = 0; public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;private static int sSecondNumber = 0;
private int firstNumber = 0;private int mSecondNumber = 0;
int FirstNumber = 0;int secondNumber = 0;
A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables public final static int FirstNumber = 0; public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;private static int sSecondNumber = 0;
private int firstNumber = 0;private int mSecondNumber = 0;
int FirstNumber = 0;int secondNumber = 0;
A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
public final static int FirstNumber = 0; public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0; private static int sSecondNumber = 0;
private int firstNumber = 0;private int mSecondNumber = 0;
int FirstNumber = 0;int secondNumber = 0;
A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
• member variables
public final static int FirstNumber = 0; public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;private static int sSecondNumber = 0;
private int firstNumber = 0; private int mSecondNumber = 0;
int FirstNumber = 0;int secondNumber = 0;
A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
• member variables
• local variables
public final static int FirstNumber = 0; public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;private static int sSecondNumber = 0;
private int firstNumber = 0;private int mSecondNumber = 0;
int FirstNumber = 0; int secondNumber = 0;
Check
• Visitor Pattern on the traversed Abstract Syntax Tree (AST)
• Base class for checks • beginTree()
• visitToken()
• leaveToken()
• finishTree()
public class NamingConventionCheck extends Check { private static final String MSG_CONSTANT = "Constant '%s' " + "should be named in all uppercase with underscores."; @Override public int[] getDefaultTokens() { return new int[] {VARIABLE_DEF}; } @Override public void visitToken(DetailAST ast) { final DetailAST modifier = ast.findFirstToken(MODIFIERS); final String name = ast.findFirstToken(IDENT) .getText(); if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) || "serialVersionUID".equals(name)) { return; } if (modifier.branchContains(LITERAL_STATIC) && modifier.branchContains(FINAL)) { if (NamingConventions.isWrongConstantNaming(name)) { log(ast.getLineNo(), ast.getColumnNo(), String.format(MSG_CONSTANT, name)); } } }}
public class NamingConventions { private static final Pattern WRONG_CONSTANT_NAME_PATTERN = Pattern.compile(".*[a-z].*"); public static boolean isWrongConstantNaming(String variableName) { return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName) .matches(); }}
Check
• Visitor Pattern on the traversed Abstract Syntax Tree (AST)
• Base class for checks
• register for token types
• see TokenTypes class
• popular examples
• CLASS_DEF
• VARIABLE_DEF
• METHOD_DEF
public class NamingConventionCheck extends Check { private static final String MSG_CONSTANT = "Constant '%s' " + "should be named in all uppercase with underscores."; @Override public int[] getDefaultTokens() { return new int[] {VARIABLE_DEF}; } @Override public void visitToken(DetailAST ast) { final DetailAST modifier = ast.findFirstToken(MODIFIERS); final String name = ast.findFirstToken(IDENT) .getText(); if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) || "serialVersionUID".equals(name)) { return; } if (modifier.branchContains(LITERAL_STATIC) && modifier.branchContains(FINAL)) { if (NamingConventions.isWrongConstantNaming(name)) { log(ast.getLineNo(), ast.getColumnNo(), String.format(MSG_CONSTANT, name)); } } }}
public class NamingConventions { private static final Pattern WRONG_CONSTANT_NAME_PATTERN = Pattern.compile(".*[a-z].*"); public static boolean isWrongConstantNaming(String variableName) { return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName) .matches(); }}
Check
• Visitor Pattern on the traversed Abstract Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• DetailAST provides utility methods
• possible to navigate around
• don’t abuse that feature! Just look at the neighbours if necessary
public class NamingConventionCheck extends Check { private static final String MSG_CONSTANT = "Constant '%s' " + "should be named in all uppercase with underscores."; @Override public int[] getDefaultTokens() { return new int[] {VARIABLE_DEF}; } @Override public void visitToken(DetailAST ast) { final DetailAST modifier = ast.findFirstToken(MODIFIERS); final String name = ast.findFirstToken(IDENT) .getText(); if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) || "serialVersionUID".equals(name)) { return; } if (modifier.branchContains(LITERAL_STATIC) && modifier.branchContains(FINAL)) { if (NamingConventions.isWrongConstantNaming(name)) { log(ast.getLineNo(), ast.getColumnNo(), String.format(MSG_CONSTANT, name)); } } }}
public class NamingConventions { private static final Pattern WRONG_CONSTANT_NAME_PATTERN = Pattern.compile(".*[a-z].*"); public static boolean isWrongConstantNaming(String variableName) { return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName) .matches(); } }
Check
• Visitor Pattern on the traversed Abstract Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• report the found issue
• DetailAST provides location
• supports internationalized error messages
public class NamingConventionCheck extends Check { private static final String MSG_CONSTANT = "Constant '%s' " + "should be named in all uppercase with underscores."; @Override public int[] getDefaultTokens() { return new int[] {VARIABLE_DEF}; } @Override public void visitToken(DetailAST ast) { final DetailAST modifier = ast.findFirstToken(MODIFIERS); final String name = ast.findFirstToken(IDENT) .getText(); if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) || "serialVersionUID".equals(name)) { return; } if (modifier.branchContains(LITERAL_STATIC) && modifier.branchContains(FINAL)) { if (NamingConventions.isWrongConstantNaming(name)) { log(ast.getLineNo(), ast.getColumnNo(), String.format(MSG_CONSTANT, name)); } } }}
public class NamingConventions { private static final Pattern WRONG_CONSTANT_NAME_PATTERN = Pattern.compile(".*[a-z].*"); public static boolean isWrongConstantNaming(String variableName) { return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName) .matches(); }}
Check
• Visitor Pattern on the traversed Abstract Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• report the found issue
• (optional) define check properties
• uses JavaBean reflection
• works for all primitive types
• just use a setter
public class NamingConventionCheck extends Check { private static final String MSG_CONSTANT = "Constant '%s' " + "should be named in all uppercase with underscores."; @Override public int[] getDefaultTokens() { return new int[] {VARIABLE_DEF}; } @Override public void visitToken(DetailAST ast) { final DetailAST modifier = ast.findFirstToken(MODIFIERS); final String name = ast.findFirstToken(IDENT) .getText(); if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) || "serialVersionUID".equals(name)) { return; } if (modifier.branchContains(LITERAL_STATIC) && modifier.branchContains(FINAL)) { if (NamingConventions.isWrongConstantNaming(name)) { log(ast.getLineNo(), ast.getColumnNo(), String.format(MSG_CONSTANT, name)); } } }}
public class NamingConventions { private static final Pattern WRONG_CONSTANT_NAME_PATTERN = Pattern.compile(".*[a-z].*"); public static boolean isWrongConstantNaming(String variableName) { return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName) .matches(); }}
Include Lint within your Project
Project structure
• lintrules
• Java module
• source of all custom detectors and our IssueRegistry
• specify reference in build.gradle as attribute Lint-Registry
• will export a lint.jar
$ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'
jar { manifest { attributes 'Manifest-Version': 1.0 attributes 'Lint-Registry': 'com.checks.CustomIssueRegistry' } }
Project structure
• lintrules
• lintlib
• Android library module
• has a dependency to lintrules $ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'
Project structure
• lintrules
• lintlib
• app
• has dependency to lintlib module
• since lintlib is an Android library module, we can use the generated lint.jar
• circumvent bug introduced in build tools version 1.2.0
https://goo.gl/1uCNSR
$ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'
Project structure
• lintrules
• lintlib
• app
• check your project using with
./gradlew :app:lintDebug • configure Lint as described here:
http://goo.gl/xABHhy
$ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'
Include Checkstyle within your Project
Project structure
• custom-checkstyle
• Java module
• source of all custom checks
• dependency to selected Checkstyle version $ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' \--- Project ':custom-checkstyle'
Project structure
• custom-checkstyle
• app
• uses Checkstyle plugin
• holds configuration
• module must be added with its fully qualified classname
com.wire.NamingConventionCheck
for example to the TreeWalker
• has dependency to custom-checkstyle module in the Checkstyle configuration
$ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' \--- Project ':custom-checkstyle'
Project structure
• custom-checkstyle
• app
• check your project using with
./gradlew :app:checkstyle • configure Checkstyle as described here:
http://goo.gl/aDBeU9
$ ./gradlew projects :projects
------------------------------------------------------------ Root project ------------------------------------------------------------
Root project 'awsm_app' +--- Project ':app' \--- Project ':custom-checkstyle'
Testing Lint Checks
Testing Lint Checks
• part of lintrules module
• register and execute tests as usual in build.gradle
public class WrongTimberUsageTest extends LintDetectorTest { @Override protected Detector getDetector() { return new WrongTimberUsageDetector(); } @Override protected List<Issue> getIssues() { return Arrays.asList(WrongTimberUsageDetector.ISSUE); } public void testLogTest() throws Exception { final String expected = ...; final TestFile testFile = java("src/test/pkg/WrongTimberTest.java", ...); assertEquals(expected, lintProject(testFile)); }}
• part of lintrules module
• register and execute tests as usual in build.gradle
• every test should extend LintDetectorTest • part of lint-tests dependency by
com.android.tools.lint
• tested with version 24.3.1
public class WrongTimberUsageTest extends LintDetectorTest { @Override protected Detector getDetector() { return new WrongTimberUsageDetector(); } @Override protected List<Issue> getIssues() { return Arrays.asList(WrongTimberUsageDetector.ISSUE); } public void testLogTest() throws Exception { final String expected = ...; final TestFile testFile = java("src/test/pkg/WrongTimberTest.java", ...); assertEquals(expected, lintProject(testFile)); }}
Testing Lint Checks
• part of lintrules module
• register and execute tests as usual in build.gradle
• every test should extend LintDetectorTest
• need to define the to be used detector and the selected issue(s)
public class WrongTimberUsageTest extends LintDetectorTest { @Override protected Detector getDetector() { return new WrongTimberUsageDetector(); } @Override protected List<Issue> getIssues() { return Arrays.asList(WrongTimberUsageDetector.ISSUE); } public void testLogTest() throws Exception { final String expected = ...; final TestFile testFile = java("src/test/pkg/WrongTimberTest.java", ...); assertEquals(expected, lintProject(testFile)); }}
Testing Lint Checks
• part of lintrules module
• register and execute tests as usual in build.gradle
• every test should extend LintDetectorTest
• need to define the to be used detector and the selected issue(s)
• perform usual unit tests with the helper methods including: • java - Create TestFile from source string
• lintProject - Lint on given files when constructed as separate project
public class WrongTimberUsageTest extends LintDetectorTest { @Override protected Detector getDetector() { return new WrongTimberUsageDetector(); } @Override protected List<Issue> getIssues() { return Arrays.asList(WrongTimberUsageDetector.ISSUE); } public void testLogTest() throws Exception { final String expected = ...; final TestFile testFile = java("src/test/pkg/WrongTimberTest.java", ...); assertEquals(expected, lintProject(testFile)); } }
Testing Lint Checks
Testing Checkstyle Checks
Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in build.gradle
public class NamingConventionTest extends BaseCheckTestSupport { @Test public void testConstantNaming() throws Exception { final DefaultConfiguration checkConfig = createCheckConfig(NamingConventionCheck.class); final String[] expected = { "7:5: Constant test4 should be " + "named in all uppercase with underscores." }; verify(checkConfig, getPath("ConstantName.java"), expected); }}
Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in build.gradle
• every test should extend BaseCheckTestSupport
• part of sevntu checks
• contains some nice helper methods
• createCheckConfig
• getPath
• getMessage
public class NamingConventionTest extends BaseCheckTestSupport { @Test public void testConstantNaming() throws Exception { final DefaultConfiguration checkConfig = createCheckConfig(NamingConventionCheck.class); final String[] expected = { "7:5: Constant test4 should be " + "named in all uppercase with underscores." }; verify(checkConfig, getPath("ConstantName.java"), expected); }}
Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in build.gradle
• every test should extend BaseCheckTestSupport
• need to create configuration for check
public class NamingConventionTest extends BaseCheckTestSupport { @Test public void testConstantNaming() throws Exception { final DefaultConfiguration checkConfig = createCheckConfig(NamingConventionCheck.class); final String[] expected = { "7:5: Constant test4 should be " + "named in all uppercase with underscores." }; verify(checkConfig, getPath("ConstantName.java"), expected); }}
Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in build.gradle
• every test should extend BaseCheckTestSupport
• need to create configuration for check
• tests are performed on real Java files
• part of test resources
• should be in same package as test class
public class NamingConventionTest extends BaseCheckTestSupport { @Test public void testConstantNaming() throws Exception { final DefaultConfiguration checkConfig = createCheckConfig(NamingConventionCheck.class); final String[] expected = { "7:5: Constant test4 should be " + "named in all uppercase with underscores." }; verify(checkConfig, getPath("ConstantName.java"), expected); }}
Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in build.gradle
• every test should extend BaseCheckTestSupport
• need to create configuration for check
• tests are performed on real Java files
• verify for final test
public class NamingConventionTest extends BaseCheckTestSupport { @Test public void testConstantNaming() throws Exception { final DefaultConfiguration checkConfig = createCheckConfig(NamingConventionCheck.class); final String[] expected = { "7:5: Constant test4 should be " + "named in all uppercase with underscores." }; verify(checkConfig, getPath("ConstantName.java"), expected); }}
Debugging
Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
./gradlew --daemon
$ ./gradle.properties
## Project-wide Gradle settings.# # For more details on how to configure your build environment visit# http://www.gradle.org/docs/current/userguide/build_environment.html# # Specifies the JVM arguments used for the daemon process.# The setting is particularly useful for tweaking memory settings.# Default value: -Xmx10248m -XX:MaxPermSize=256m# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8# # When configured, Gradle will run in incubating parallel mode.# This option should only be used with decoupled projects. More details, visit# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects# org.gradle.parallel=trueorg.gradle.jvmargs=-XX:MaxPermSize=4g \ -XX:+HeapDumpOnOutOfMemoryError \ -Xmx4g \ -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
• create new debug configuration in IntelliJ
Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
• create new debug configuration in IntelliJ
• debug newly created configuration
Conclusion
Conclusion
• Lint and Checkstyle do not have a well documented API
• sample project:
https://goo.gl/QBfyf8
• Checkstyle is easier to use and to integrate
• Lint seems to be more flexible
➡ to improve code quality
➡ help new team members
• integration of Lint Checks may become easier with
https://goo.gl/d7U1qS
Questions?
Thank you!