Lint for Kotlin @R.kt#3

Lint for Kotlin @R.kt#3

A374f41eab3f73c50d8bab0652cb207a?s=128

TakuSemba

March 15, 2018
Tweet

Transcript

  1. Lint For Kotlin TakuSemba CyberAgent.Inc

  2. @takusemba https://github.com/TakuSemba

  3. https://android.github.io/kotlin-guides/style.html

  4. ktlint ɾStatic code analysis tool for kotlin ɾBased on official

    code style ɾBuilt-in formatter ɾCustomizable output
  5. ktlint with gradle configurations { ktlint } dependencies { ktlint

    "com.github.shyiko:ktlint:$KTLINT_VERSION" } check.dependsOn ktlint
  6. ktlint with gradle task ktlint(type: JavaExec, group: "kotlin verification") {

    description = 'Check Kotlin code style.' args 'src/**/*.kt' main = 'com.github.shyiko.ktlint.Main' classpath = configurations.ktlint + sourceSets.main.output } $ ./gradlew tasks
  7. ktlint with gradle task ktFormat(type: JavaExec, group: "kotlin Formatting") {

    description = 'Fix Kotlin code style deviations.' args '-F', 'src/**/*.kt' main = 'com.github.shyiko.ktlint.Main' classpath = configurations.ktlint + sourceSets.main.output } $ ./gradlew tasks
  8. ktlint with CLI $ brew install shyiko/ktlint/ktlint $ ktlint "src/**/*.kt"

  9. Outputs task ktlint(type: JavaExec, dependsOn: classes) { description = 'Check

    Kotlin code style.' args 'src/**/*.kt', ‘--reporter=plain' main = 'com.github.shyiko.ktlint.Main' classpath = configurations.ktlint + sourceSets.main.output }
  10. Outputs task ktlint(type: JavaExec, dependsOn: classes) { description = 'Check

    Kotlin code style.' args 'src/**/*.kt', ‘--reporter=plain' main = 'com.github.shyiko.ktlint.Main' classpath = configurations.ktlint + sourceSets.main.output } '--reporter=plain'
  11. Outputs --reporter=plain takusemba/ktlint-sample/app/…/Hoge.kt:44:1: Unexpected blank line(s) before "}" takusemba/ktlint-sample/app/…/Fuga.kt:108:1: Needless

    blank line(s) takusemba/ktlint-sample/app/…/Foo.kt:25:7: Line break before assignment is not allowed takusemba/ktlint-sample/app/…/Bar.kt:47:1: Unexpected blank line(s) before “}" …
  12. Outputs --reporter=checkstyle <?xml version="1.0" encoding="utf-8"?> <checkstyle version=“8.0"> <file name="takusemba/ktlint-sample/app/…/Hoge.kt"> <error

    line="25" column="7" severity="error" message="Line break before assignment is not allowed" source="no-line-break-before-assignment" /> </file> … </checkstyle>
  13. Outputs --reporter=custom-ktlint-rule class CustomReporter(private val out: PrintStream) : Reporter {

    private val errors = ArrayList<LintError>() override fun onLintError( file: String, err: LintError, corrected: Boolean) { errors.add(err) } override fun afterAll() { out.println("hello custom reporter.") out.println("found ${errors.size} errors.") } }
  14. Outputs --reporter=custom-ktlint-rule class CustomReporter(private val out: PrintStream) : Reporter {

    private val errors = ArrayList<LintError>() override fun onLintError( file: String, err: LintError, corrected: Boolean) { errors.add(err) } override fun afterAll() { out.println("hello custom reporter.") out.println("found ${errors.size} errors.") } } override fun afterAll() { out.println("hello custom reporter.") out.println("found ${errors.size} errors.") }
  15. Outputs class CustomReporterProvider : ReporterProvider { override val id: String

    = “custom-ktlint-reporter" override fun get( out: PrintStream, opt: Map<String, String> ): Reporter = CustomReporter(out) } --reporter=custom-ktlint-rule
  16. Outputs class CustomReporterProvider : ReporterProvider { override val id: String

    = “custom-ktlint-reporter" override fun get( out: PrintStream, opt: Map<String, String> ): Reporter = CustomReporter(out) } --reporter=custom-ktlint-rule ): Reporter = CustomReporter(out)
  17. Outputs --reporter=custom-ktlint-rule

  18. Disable rules import package.* // ktlint-disable no-wildcard-imports import package.* //

    ktlint-disable disable specific rule disable the whole rule
  19. Disable rules

  20. Disable rules [*.{kt,kts}] indent_size=2 continuation_indent_size=4 insert_final_newline=unset max_line_length=off .editorconfig

  21. Add rules class CustomRule : Rule("custom-rule") { override fun visit(

    node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { if (isLintError) { emit(node.startOffset, “LINT ERROR FOUND!”, false) } } }
  22. Add rules class CustomRule : Rule("custom-rule") { override fun visit(

    node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { if (isLintError) { emit(node.startOffset, “LINT ERROR FOUND!”, false) } } } node: ASTNode,
  23. Abstract Syntax Tree

  24. AST fun add(a: Int, b: Int): Int { return a

    + b }
  25. node node node node node node node node node node

    AST
  26. AST fun add(a: Int, b: Int): Int { return a

    + b }
  27. AST fun add(a: Int, b: Int): Int { return a

    + b } KtNamedFunction
  28. AST { return a + b } KtKeywordToken.fun fun Int

    : (a: Int, b: Int) add KtToken.IDENTIFIER KtTypeReference KtSingleValueToken.COLON KtParameterList KtBlockExpression
  29. AST { return a + b } KtKeywordToken.fun fun Int

    : (a: Int, b: Int) add KtToken.IDENTIFIER KtTypeReference KtSingleValueToken.COLON KtParameterList KtBlockExpression
  30. AST KtSingleValueToken.LBRACE KtReturnExpression KtSingleValueToken.RBRACE } { return a + b

  31. AST KtSingleValueToken.LBRACE KtReturnExpression KtSingleValueToken.RBRACE } { return a + b

  32. AST KtKeywordToken.return KtToken.IDENTIFIER KtSingleValueToken.PLUS a return + b KtToken.IDENTIFIER

  33. node node node node node node node node node node

    AST
  34. Add rules class CustomRule : Rule("custom-rule") { override fun visit(

    node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { if (isLintError) { emit(node.startOffset, “LINT ERROR FOUND!”, false) } } }
  35. Add rules class CustomRule : Rule("custom-rule") { override fun visit(

    node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { if (isLintError) { emit(node.startOffset, “LINT ERROR FOUND!”, false) } } } node: ASTNode,
  36. Add rules fun add(a: Int, b: Int): Int = a

    + b // OK fun add(a: Int, b: Int) = a + b // NG
  37. fun add(a: Int, b: Int) = a + b //

    NG
  38. Add rules fun add(a: Int, b: Int) = a +

    b // NG if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!", false) } }
  39. Add rules fun add(a: Int, b: Int) = a +

    b // NG if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!", false) } } if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { }
  40. Add rules fun add(a: Int, b: Int) = a +

    b // NG (a: Int, b: Int) if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!”, false) } } val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
  41. Add rules fun add(a: Int, b: Int) = a +

    b // NG if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!", false) } } if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { =
  42. Add rules fun add(a: Int, b: Int) = a +

    b // NG if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!", false) } } fun add(a: Int, b: Int) = a + b emit(node.startOffset, "need return type!!!", false)
  43. Add rules class ExpressionFunctionRule : Rule("one-line-function") { override fun visit(

    node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { if (node.elementType == KtStubElementTypes.FUNCTION && !node.text.contains('\n')) { val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return if (child.treeNext.elementType == KtTokens.WHITE_SPACE && child.treeNext.treeNext.elementType == KtTokens.EQ) { emit(node.startOffset, "need return type!!!", false) } } } }
  44. Add rules class CustomRuleSetProvider : RuleSetProvider { override fun get():

    RuleSet = RuleSet( “custom-rule", ExpressionFunctionRule() ) }
  45. Add rules class CustomRuleSetProvider : RuleSetProvider { override fun get():

    RuleSet = RuleSet( “custom-rule", ExpressionFunctionRule() ) } ExpressionFunctionRule()
  46. Add rules

  47. Add rules $ ktlint "**/Hoge.kt" -R ktrule/build/libs/ktrule.jar --relative $ ./gradlew

    jar
  48. Add rules val rule = ExpressionFunctionRule() assertThat(rule.lint( """ fun add(a:

    Int, b: Int): Int = a + b """ .trimIndent()) ).isEqualTo(emptyList<LintError>()) assertThat(rule.lint( """ fun add(a: Int, b: Int) = a + b """ .trimIndent()) ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need return type!!!")))
  49. Add rules val rule = ExpressionFunctionRule() assertThat(rule.lint( """ fun add(a:

    Int, b: Int): Int = a + b """ .trimIndent()) ).isEqualTo(emptyList<LintError>()) assertThat(rule.lint( """ fun add(a: Int, b: Int) = a + b """ .trimIndent()) ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need return type!!!"))) val rule = ExpressionFunctionRule() assertThat(rule.lint( """ fun add(a: Int, b: Int): Int = a + b """ .trimIndent()) ).isEqualTo(emptyList<LintError>())
  50. Add rules val rule = ExpressionFunctionRule() assertThat(rule.lint( """ fun add(a:

    Int, b: Int): Int = a + b """ .trimIndent()) ).isEqualTo(emptyList<LintError>()) assertThat(rule.lint( """ fun add(a: Int, b: Int) = a + b """ .trimIndent()) ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need return type!!!"))) assertThat(rule.lint( """ fun add(a: Int, b: Int) = a + b """ .trimIndent()) ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need return type!!!")))
  51. Add rules

  52. with Danger

  53. with Danger BQQTSDNBJOKBWBDPNUBLVTFNCB)PHFLU Danger # Dangerfile checkstyle_format.base_path = Dir.pwd checkstyle_format.report

    ‘path/to/result.xml’
  54. Lint For Kotlin https://github.com/takusemba https://twitter.com/takusemba