SlideShare uma empresa Scribd logo
1 de 64
Baixar para ler offline
Better Code through Lint and Checkstyle
droidcon New York City | 28.08.2015 | Marc Prengemann
About me
Marc Prengemann
Android Software Engineer
Mail: marc@wire.com
Wire: marcprengemann@gmail.com
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 user
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
• 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=true

org.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!

Mais conteúdo relacionado

Mais procurados

Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
David Gómez García
 
walkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventionswalkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventions
walkmod
 

Mais procurados (20)

BarcelonaJUG2016: walkmod: how to run and design code transformations
BarcelonaJUG2016: walkmod: how to run and design code transformationsBarcelonaJUG2016: walkmod: how to run and design code transformations
BarcelonaJUG2016: walkmod: how to run and design code transformations
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 
TDD & BDD
TDD & BDDTDD & BDD
TDD & BDD
 
Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
Midiendo la calidad de código en WTF/Min (Revisado EUI Abril 2014)
 
JAVASCRIPT Test Driven Development & Jasmine
JAVASCRIPT Test Driven Development & JasmineJAVASCRIPT Test Driven Development & Jasmine
JAVASCRIPT Test Driven Development & Jasmine
 
Pyramid of-developer-skills
Pyramid of-developer-skillsPyramid of-developer-skills
Pyramid of-developer-skills
 
Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)
 
groovy & grails - lecture 7
groovy & grails - lecture 7groovy & grails - lecture 7
groovy & grails - lecture 7
 
Creating Gradle Plugins - Oredev
Creating Gradle Plugins - OredevCreating Gradle Plugins - Oredev
Creating Gradle Plugins - Oredev
 
Working Effectively with Legacy Code
Working Effectively with Legacy CodeWorking Effectively with Legacy Code
Working Effectively with Legacy Code
 
Developing ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller PatternDeveloping ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller Pattern
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 
We Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End DevelopmentWe Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End Development
 
Xdebug and Drupal8 tests (PhpUnit and Simpletest)
Xdebug and Drupal8 tests (PhpUnit and Simpletest)Xdebug and Drupal8 tests (PhpUnit and Simpletest)
Xdebug and Drupal8 tests (PhpUnit and Simpletest)
 
walkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventionswalkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventions
 
Creating Gradle Plugins - GR8Conf US
Creating Gradle Plugins - GR8Conf USCreating Gradle Plugins - GR8Conf US
Creating Gradle Plugins - GR8Conf US
 
Redux for ReactJS Programmers
Redux for ReactJS ProgrammersRedux for ReactJS Programmers
Redux for ReactJS Programmers
 
walkmod: quick start
walkmod: quick startwalkmod: quick start
walkmod: quick start
 
Automation patterns on practice
Automation patterns on practiceAutomation patterns on practice
Automation patterns on practice
 
Testing Legacy Rails Apps
Testing Legacy Rails AppsTesting Legacy Rails Apps
Testing Legacy Rails Apps
 

Semelhante a Better Code through Lint and Checkstyle

Java Unit Test and Coverage Introduction
Java Unit Test and Coverage IntroductionJava Unit Test and Coverage Introduction
Java Unit Test and Coverage Introduction
Alex Su
 
Akka london scala_user_group
Akka london scala_user_groupAkka london scala_user_group
Akka london scala_user_group
Skills Matter
 

Semelhante a Better Code through Lint and Checkstyle (20)

Looking for Bugs in MonoDevelop
Looking for Bugs in MonoDevelopLooking for Bugs in MonoDevelop
Looking for Bugs in MonoDevelop
 
Java Unit Test and Coverage Introduction
Java Unit Test and Coverage IntroductionJava Unit Test and Coverage Introduction
Java Unit Test and Coverage Introduction
 
Code transformation With Spoon
Code transformation With SpoonCode transformation With Spoon
Code transformation With Spoon
 
Practical Chaos Engineering
Practical Chaos EngineeringPractical Chaos Engineering
Practical Chaos Engineering
 
Java-Intro.pptx
Java-Intro.pptxJava-Intro.pptx
Java-Intro.pptx
 
Akka london scala_user_group
Akka london scala_user_groupAkka london scala_user_group
Akka london scala_user_group
 
Write code that writes code! A beginner's guide to Annotation Processing - Ja...
Write code that writes code! A beginner's guide to Annotation Processing - Ja...Write code that writes code! A beginner's guide to Annotation Processing - Ja...
Write code that writes code! A beginner's guide to Annotation Processing - Ja...
 
Write code that writes code!
Write code that writes code!Write code that writes code!
Write code that writes code!
 
Framework engineering JCO 2011
Framework engineering JCO 2011Framework engineering JCO 2011
Framework engineering JCO 2011
 
Ajax Under The Hood
Ajax Under The HoodAjax Under The Hood
Ajax Under The Hood
 
The operation principles of PVS-Studio static code analyzer
The operation principles of PVS-Studio static code analyzerThe operation principles of PVS-Studio static code analyzer
The operation principles of PVS-Studio static code analyzer
 
Testing Ext JS and Sencha Touch
Testing Ext JS and Sencha TouchTesting Ext JS and Sencha Touch
Testing Ext JS and Sencha Touch
 
Don't Be Afraid of Abstract Syntax Trees
Don't Be Afraid of Abstract Syntax TreesDon't Be Afraid of Abstract Syntax Trees
Don't Be Afraid of Abstract Syntax Trees
 
SCWCD : Thread safe servlets : CHAP : 8
SCWCD : Thread safe servlets : CHAP : 8SCWCD : Thread safe servlets : CHAP : 8
SCWCD : Thread safe servlets : CHAP : 8
 
Reversing JavaScript
Reversing JavaScriptReversing JavaScript
Reversing JavaScript
 
Static analysis: Around Java in 60 minutes
Static analysis: Around Java in 60 minutesStatic analysis: Around Java in 60 minutes
Static analysis: Around Java in 60 minutes
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
 
Mcknight well built extensions
Mcknight well built extensionsMcknight well built extensions
Mcknight well built extensions
 
The First C# Project Analyzed
The First C# Project AnalyzedThe First C# Project Analyzed
The First C# Project Analyzed
 
Application Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockApplication Frameworks: The new kids on the block
Application Frameworks: The new kids on the block
 

Último

Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Christo Ananth
 
UNIT-V FMM.HYDRAULIC TURBINE - Construction and working
UNIT-V FMM.HYDRAULIC TURBINE - Construction and workingUNIT-V FMM.HYDRAULIC TURBINE - Construction and working
UNIT-V FMM.HYDRAULIC TURBINE - Construction and working
rknatarajan
 
AKTU Computer Networks notes --- Unit 3.pdf
AKTU Computer Networks notes ---  Unit 3.pdfAKTU Computer Networks notes ---  Unit 3.pdf
AKTU Computer Networks notes --- Unit 3.pdf
ankushspencer015
 
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
dharasingh5698
 

Último (20)

Online banking management system project.pdf
Online banking management system project.pdfOnline banking management system project.pdf
Online banking management system project.pdf
 
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
 
Java Programming :Event Handling(Types of Events)
Java Programming :Event Handling(Types of Events)Java Programming :Event Handling(Types of Events)
Java Programming :Event Handling(Types of Events)
 
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdfONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
 
UNIT-V FMM.HYDRAULIC TURBINE - Construction and working
UNIT-V FMM.HYDRAULIC TURBINE - Construction and workingUNIT-V FMM.HYDRAULIC TURBINE - Construction and working
UNIT-V FMM.HYDRAULIC TURBINE - Construction and working
 
Call Girls in Nagpur Suman Call 7001035870 Meet With Nagpur Escorts
Call Girls in Nagpur Suman Call 7001035870 Meet With Nagpur EscortsCall Girls in Nagpur Suman Call 7001035870 Meet With Nagpur Escorts
Call Girls in Nagpur Suman Call 7001035870 Meet With Nagpur Escorts
 
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete RecordCCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
 
(ANJALI) Dange Chowk Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANJALI) Dange Chowk Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(ANJALI) Dange Chowk Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANJALI) Dange Chowk Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
 
University management System project report..pdf
University management System project report..pdfUniversity management System project report..pdf
University management System project report..pdf
 
High Profile Call Girls Nagpur Isha Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Isha Call 7001035870 Meet With Nagpur EscortsHigh Profile Call Girls Nagpur Isha Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Isha Call 7001035870 Meet With Nagpur Escorts
 
(PRIYA) Rajgurunagar Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(PRIYA) Rajgurunagar Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(PRIYA) Rajgurunagar Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(PRIYA) Rajgurunagar Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
 
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLSMANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
 
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
 
AKTU Computer Networks notes --- Unit 3.pdf
AKTU Computer Networks notes ---  Unit 3.pdfAKTU Computer Networks notes ---  Unit 3.pdf
AKTU Computer Networks notes --- Unit 3.pdf
 
Introduction to Multiple Access Protocol.pptx
Introduction to Multiple Access Protocol.pptxIntroduction to Multiple Access Protocol.pptx
Introduction to Multiple Access Protocol.pptx
 
(INDIRA) Call Girl Aurangabad Call Now 8617697112 Aurangabad Escorts 24x7
(INDIRA) Call Girl Aurangabad Call Now 8617697112 Aurangabad Escorts 24x7(INDIRA) Call Girl Aurangabad Call Now 8617697112 Aurangabad Escorts 24x7
(INDIRA) Call Girl Aurangabad Call Now 8617697112 Aurangabad Escorts 24x7
 
UNIT - IV - Air Compressors and its Performance
UNIT - IV - Air Compressors and its PerformanceUNIT - IV - Air Compressors and its Performance
UNIT - IV - Air Compressors and its Performance
 
UNIT-III FMM. DIMENSIONAL ANALYSIS
UNIT-III FMM.        DIMENSIONAL ANALYSISUNIT-III FMM.        DIMENSIONAL ANALYSIS
UNIT-III FMM. DIMENSIONAL ANALYSIS
 
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
 
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
 

Better Code through Lint and Checkstyle

  • 1. Better Code through Lint and Checkstyle droidcon New York City | 28.08.2015 | Marc Prengemann
  • 2. About me Marc Prengemann Android Software Engineer Mail: marc@wire.com Wire: marcprengemann@gmail.com Github: winterDroid Google+: Marc Prengemann Twitter: @winterDroid89
  • 4. What is Lint and Checkstyle? App Source Files Configuration Analysis Tool Report
  • 5. 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
  • 6. Getting started with own Checks
  • 7. 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
  • 9. 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
  • 10. 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
  • 11. 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));
 }
 }
 }
  • 12. 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));
 }
 }
 }
  • 13. 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));
 }
 }
 }
  • 14. 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));
 }
 }
 }
  • 15. 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));
 }
 }
 }
  • 16. Issue • potential bug in an Android application • is discovered by a Detector • are exposed to the user 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));
  • 17. 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));
  • 18. 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));
  • 19. 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));
  • 20. 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));
  • 21. 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));
  • 22. 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));
  • 23. 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));
  • 24. 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));
  • 25. 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);
 }
 }
  • 27. 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;
  • 28. 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;
  • 29. 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;
  • 30. 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;
  • 31. 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;
  • 32. 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();
 }
 }
  • 33. 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();
 }
 }
  • 34. 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();
 }
 }
  • 35. 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();
 }
 }
  • 36. 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();
 }
 }
  • 37. Include Lint within your Project
  • 38. 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' } }
  • 39. 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'
  • 40. 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'
  • 41. 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'
  • 43. 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'
  • 44. 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'
  • 45. 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'
  • 47. 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));
 }
 }
  • 48. • 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
  • 49. • 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
  • 50. • 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
  • 52. 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);
 }
 }
  • 53. 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);
 }
 }
  • 54. 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);
 }
 }
  • 55. 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);
 }
 }
  • 56. 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);
 }
 }
  • 58. 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=true
 org.gradle.jvmargs=-XX:MaxPermSize=4g 
 -XX:+HeapDumpOnOutOfMemoryError 
 -Xmx4g 
 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
  • 59. Debugging • same for Checkstyle and Lint • prepare and run Gradle daemon • create new debug configuration in IntelliJ
  • 60. Debugging • same for Checkstyle and Lint • prepare and run Gradle daemon • create new debug configuration in IntelliJ • debug newly created configuration
  • 62. 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