kotlin linter

kotlin linter

Kotlin Fest( https://kotlin.connpass.com/event/91666/ )で発表したkotlinのlintに関する発表資料です。

3c4e24fd827c789cb67a9f759f057b06?s=128

Shinnosuke Kugimiya

August 25, 2018
Tweet

Transcript

  1. © DMM.com !LHNZTIJO ,PUMJO'FTU LPUMJOMJOUFS

  2. © DMM.com X w LHNZTIJOఝٶ ͗͘Έ΍  w "OESPJEΤϯδχΞ w

    ߹ಉձࣾ%..DPN$50ࣨॴଐ ࣗݾ঺հ
  3. © DMM.com X ͜ͷηογϣϯͷΰʔϧ

  4. © DMM.com X w ηογϣϯΛݟͨํʑ͕ɺ,PUMJO༻ͷ֤छ-JOUFSͷ࢖ ͍ํɺಛ௃Λཧղ͢Δ͜ͱ w ηογϣϯΛݟͨํʑ͕ɺ֤छ-JOUFSͷΧελϜ͠ ͨϧʔϧͷ࡞ΕΔΑ͏ʹͳΔ͜ͱ w

    ηογϣϯΛݟͨํʑ͕ɺ-JOUFSΛٕज़બఆ͢Δ৔ ߹ͷࡐྉʹͳΔ͜ͱ ͜ͷηογϣϯͷΰʔϧ
  5. © DMM.com X BHFOEB

  6. © DMM.com X w -JOUFSͱ͸ w LUMJOU w Πϯετʔϧํ๏ w

    ࢖͍ํ w ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ w ϧʔϧͷઃఆํ๏ w ΧελϜϧʔϧͷ࡞Γํ w ࡞ͬͨΧελϜϧʔϧͷద༻ํ๏ w ͦͷଞ w EFUFLU w BOESPJEMJOU w ·ͱΊ BHFOEB
  7. © DMM.com X -JOUFSͱ͸

  8. © DMM.com X –wikipedia l"MJOUFSPSMJOUSFGFSTUPUPPMTUIBUBOBMZ[FTPVSDF DPEFUPqBHQSPHSBNNJOHFSSPST CVHT TUZMJTUJD FSSPST BOETVTQJDJPVTDPOTUSVDUTz

  9. © DMM.com X –wikipedia l-JOUFS΋͘͠͸-JOU͸ɺϓϩάϥϛϯάΤϥʔɺ όάɺॻࣜΤϥʔɺ͓ΑͼٙΘ͍͠ߏ଄ʹϑϥά ΛཱͯΔͨΊͷιʔείʔυΛ෼ੳ͢Δϓϩάϥ ϜͰ͋Δz

  10. © DMM.com X LPUMJOͷMJOUFS LPUMJOͷMJOUFS͸ओʹ͸ೋͭɻͦͯ͠ɺBOESPJEͷඪ४MJOU ͕LPUMJOʹ΋ରԠ͍ͯ͠Δɻ w LUMJOU w EFUFLU

    w BOESPJEMJOU  ͜ͷ̏ͭʹ͍ͭͯઆ໌͍͖ͯ͠·͢ɻ
  11. © DMM.com X LUMJOU

  12. © DMM.com Πϯετʔϧํ๏

  13. © DMM.com X Πϯετʔϧํ๏ $ curl -sSLO https://github.com/shyiko/ktlint/releases/download/ 0.27.0/ktlint &&

    chmod a+x ktlint && sudo mv ktlint /usr/local/bin/ IUUQTHJUIVCDPNTIZJLPLUMJOUJOTUBMMBUJPO 3&"%.&ͷJOTUBMMBUJPOʹै͏ɻ
  14. © DMM.com X HSBEMFΛ࢖͏৔߹ QMVHJO͕ೋͭ͋ΔʢQMVHJOͳ͠Ͱ΋ɺઃఆͰ͖Δʣ w IUUQTHJUIVCDPNKMMFJUTDIVILUMJOUHSBEMF w IUUQTHJUIVCDPNKFSFNZNBJMFOLPUMJOUFSHSBEMF ࣗ෼͸ALPMJOUFSHSBEMFAΛ࢖͍ͬͯΔɻ

    ٕज़બఆ౰࣌ɺLUMJOUHSBEMFͷํͰ͸DVTUPNSVMFΛద ༻Ͱ͖ͳ͔ͬͨͷ͕ཧ༝ɻ ݱࡏ͸ͦͷ໰୊͸ղফ͞Ε͍ͯΔͷͰɺͲͪΒͰ΋ྑ͞ ͦ͏ɻ
  15. © DMM.com ࢖͍ํ

  16. © DMM.com X ࢖͍ํ LUMJOU LU6OFYQFDUFETQBDJOHBGUFS  LU6OFYQFDUFETQBDJOHBGUFS  LU6OOFDFTTBSZTQBDF

    T  LU/FFEMFTTCMBOLMJOF T  LU6OFYQFDUFETQBDJOHBGUFS  LU6OFYQFDUFETQBDJOHBGUFS  LU.JTTJOHTQBDFBGUFS LU6OFYQFDUFETQBDJOHBGUFS   ίϚϯυΛଧ͚ͭͩɻΤϥʔݸॴͷҰཡ͕දࣔ͞ΕΔɻ
  17. © DMM.com ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ

  18. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ  TQBDFTGPSJOEFOUBUJPO  /PTFNJDPMPOT VOMFTTVTFEUPTFQBSBUFNVMUJQMFTUBUFNFOUTPOUIFTBNFMJOF 

     /PXJMEDBSEVOVTFEAJNQPSUAT  /PDPOTFDVUJWFCMBOLMJOFT  /PCMBOLMJOFTCFGPSFA^A  /PUSBJMJOHXIJUFTQBDFT  /PA6OJUASFUVSOT AGVOGO\^AJOTUFBEPGAGVOGO6OJU\^A   /PFNQUZ A\^A DMBTTCPEJFT  /PTQBDFTBSPVOESBOHF AA PQFSBUPS  /POFXMJOFCFGPSF CJOBSZ A AAA A A AA AA AA AccA  8IFOXSBQQJOHDIBJOFEDBMMTAA A ABOEA ATIPVMECFQMBDFEPOUIFOFYUMJOF  8IFOBMJOFJTCSPLFOBUBOBTTJHONFOU AA PQFSBUPSUIFCSFBLDPNFTBGUFSUIFTZNCPM  8IFODMBTTGVODUJPOTJHOBUVSFEPFTOUpUPOBTJOHMFMJOF FBDIQBSBNFUFSNVTUCFPOB TFQBSBUFMJOF  $POTJTUFOUTUSJOHUFNQMBUFT AWAJOTUFBEPGA\W^A A\QW^AJOTUFBEPGA\QWUP4USJOH ^A   $POTJTUFOUPSEFSPGNPEJpFST  $POTJTUFOUTQBDJOHBGUFSLFZXPSET DPNNBTBSPVOEDPMPOT DVSMZCSBDFT QBSFOT JOpY PQFSBUPST DPNNFOUT FUD  /FXMJOFBUUIFFOEPGFBDIpMF OPUFOBCMFECZEFGBVMU CVUSFDPNNFOEFE 
  19. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ  Πϯσϯτ͸ͭ  ηϛίϩϯېࢭ  ϫΠϧυΧʔυJNQPSUɺ࢖༻ͯ͠ͳ͍JNQPSUېࢭ

     ࿈ଓۭͨ͠ߦېࢭ  A^Aͷ௚લͷۭߦېࢭ  ຤ඌۭߦېࢭ  6OJUͷSFUVSOېࢭ  ۭͷΫϥεϘσΟA\^Aېࢭ  SBOHFΦϖϨʔλʔAAͷલޙʹۭനېࢭ A A AA A A AA AA AA AccAͷΦϖϨʔλͷ௚લʹ վߦېࢭ
  20. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ AA A A A Aͷ௚ޙͰվߦېࢭ AAͷ௚લͰվߦېࢭ

    ෳ਺ύϥϝʔλͷ৔߹ɺશͯվߦ͢Δ͔ҰߦͰॻ͘ Ұ؏ͨ͠TUSJOHUFNQMBUFʢA\W^AͰ͸ͳͯ͘AWAɻ A\QWUP4USJOH ^AͰͳͯ͘A\QW^Aɻ  Ұ؏ͨ͠म০ࢠͷॱ൪ Ұ؏ͨ͠εϖʔγϯά ֤ϑΝΠϧͷ຤ඌʹվߦΛೖΕΔ σϑΥϧτͰPOʹͳͬͯ ͸ͳ͍͕ɺਪ঑ 
  21. © DMM.com ϧʔϧͷઃఆํ๏

  22. © DMM.com X ඪ४ϧʔϧͷઃఆํ๏ جຊతʹ֤ϧʔϧʹ͍ͭͯɺFOBCMFEJTBCMFͳͲͷ ઃఆ͸Ͱ͖ͳ͍ɻ ͨͩ͠ɺΠϯσϯταΠζͱϑΝΠϧ຤ඌվߦʹ͍ͭͯ ͸&EJUPS$POpHͰઃఆͰ͖Δɻ [*.{kt,kts}] indent_size=2

    continuation_indent_size=2 insert_final_newline=true
  23. © DMM.com X ඪ४ϧʔϧͷઃఆํ๏ ·ͨɺಛఆͷ৔ॴ͚ͩɺಛఆͷϧʔϧ΍શϧʔϧΛແࢹ ͢Δ͜ͱ͕Ͱ͖Δ import package.* // ktlint-disable

    no-wildcard-imports /* ktlint-disable parameter-list-wrapping */ data class A( val a: String, val b: String, val c: String ) /* ktlint-disable parameter-list-wrapping */ /* ktlint-disable */ : શ෦ແࢹ /* ktlint-disable */
  24. © DMM.com ΧελϜϧʔϧͷ࡞Γํ

  25. © DMM.com ·ͣ͸"45Λ஌Δ

  26. © DMM.com X ·ͣ͸"45 ˺14* Λ஌Δ package yourpackage class Hoge

    { fun fuga(): Int = 2 + 3 }
  27. © DMM.com X ·ͣ͸"45 ˺14* Λ஌Δ 'JMF 1"$,"(& %*3&$5*7& $-"44

    FMFNFOU DMBTT 8)*5&@41"$& FMFNFOU
 *%&/5*'*&3 8)*5&@41"$& $-"44@#0%: package yourpackage class Hoge { fun fuga(): Int = 2 + 3 } πϦʔߏ଄Ͱղऍ͞ΕΔ
  28. © DMM.com X ͨͱ͑͹ɺҾ਺ͰA A͕͋ͬͨΒઈରվߦ͢ΔΧελϜϧʔϧΛ࡞Δ࣌ fuga(1, 2) fuga( 1, 2

    ) /( 0,
  29. © DMM.com X ͨͱ͑͹ɺҾ਺ͰA A͕͋ͬͨΒઈରվߦ͢ΔΧελϜϧʔϧΛ࡞Δ࣌ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.."

    8)*5&41"$& 7"-6& "3(6.&/5 /(ͷ࣌΋0,ͷ࣌΋ "45ͷπϦʔ͸͜ͷΑ͏ʹͳΔ Ұ෦୺ંͬͯ·͢    
  30. © DMM.com X ͨͱ͑͹ɺҾ਺ͰA A͕͋ͬͨΒઈରվߦ͢ΔΧελϜϧʔϧΛ࡞Δ࣌ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.."

    8)*5&41"$& 7"-6& "3(6.&/5     0, վߦίʔυؚ͕·Ε͍ͯΔ̋
  31. © DMM.com X ͨͱ͑͹ɺҾ਺ͰA A͕͋ͬͨΒઈରվߦ͢ΔΧελϜϧʔϧΛ࡞Δ࣌ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.."

    8)*5&41"$& 7"-6& "3(6.&/5     /( վߦίʔυؚ͕·Ε͍ͯͳ͍ʷ
  32. © DMM.com X ͨͱ͑͹ɺҾ਺ͰA A͕͋ͬͨΒઈରվߦ͢ΔΧελϜϧʔϧΛ࡞Δ࣌ ΧελϜϧʔϧͷϩδοΫ͸ɺԼهΛ֬ೝ͢Δ w Լهͷߏ଄ʹͳ͍ͬͯΔͱ͖ʹ 7"-6& "3(6.&/5-*45

    7"-6& "3(6.&/5 $0.." 8)*5&41"$& 7"-6& "3(6.&/5 w $0.."ͷ࣍ͷ8)*5&@41"$&ʹվߦίʔυΛؚ·ΕΔ͜ͱ LUMJOUͰ΋EFUFLUͰ΋BOESPJEMJOUͰ΋ͲΕͰ΋ɺ ɹɹɹɹɹɹΧελϜϧʔϧ͸͜ͷΑ͏ʹͯ͠࡞͍͖ͬͯ·͢
  33. © DMM.com X ͳΜͱͳ͘Θ͔͚ͬͨͲɺ ͦ΋ͦ΋πϦʔߏ଄͕Ͳ͏ͳͬͯΔͱ͔Θ͔ΒΜʜ

  34. © DMM.com X େৎ෉ɺ๻΋Θ͔Γ·ͤΜ

  35. © DMM.com X 1TJ7JFXFS࢖͍·͠ΐ͏ IUUQTQMVHJOTKFUCSBJOTDPNQMVHJOQTJWJFXFS

  36. © DMM.com LUMJOUͰ$VTUPN-JOU

  37. © DMM.com X αϯϓϧͱͯ͠ɺ͜ͷΧελϜϧʔϧΛ࡞Δ fuga(1, 2) fuga( 1, 2 )

    /( 0, Ҿ਺ͰA A͕͋ͬͨΒվߦΛଅ͢
  38. © DMM.com X खॱ  ςϯϓϨʔτΛDMPOFͯ͘͠Δ  3VMFΛ࡞Δ  3VMF4FU1SPWJEFSΛ࡞Δ

     SFTPVSDFT.&5"*/'TFSWJDFT DPNHJUIVCTIZJLPLUMJOUDPSF3VMF4FU1SPWJEFSʹ࡞ͬͨ 3VMF4FU1SPWJEFSΛهड़  KBSΛ࡞Δ
  39. © DMM.com X ςϯϓϨʔτΛDMPOFͯ͘͠Δ ςϯϓϨʔτ͕LUMJOUͷϦϙδτϦʹ༻ҙ͞Ε͍ͯΔͷͰɺ͜ ΕΛམͱͯ͘͠Δɻ IUUQTHJUIVCDPNTIZJLPLUMJOUCMPCNBTUFSLUMJOUSVMFTFU UFNQMBUF ෆཁͳαϯϓϧͳͲΛ࡟আͯ͠ɺ࡞੒։࢝ɻ

  40. © DMM.com X 3VMFΛ࡞Δ abstract class Rule(val id: String) {

    ... abstract fun visit( node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) } ࡞Γ͍ͨ3VMFΛ࡞ͬͯߦ͘ɻ ΍Δ͜ͱ͸Ұͭɻ 3VMFΛܧঝͯ͠ɺWJTJUϝιουΛ࣮૷͢Δɻ
  41. © DMM.com X WJTJUϝιου 'JMF 1"$,"(& %*3&$5*7& $-"44 FMFNFOU DMBTT

    8)*5&@41"$& FMFNFOU
 *%&/5*'*&3 8)*5&@41"$& $-"44@#0%: WJTJU WJTJU WJTJU WJTJU WJTJU WJTJU WJTJU WJTJU "45ͷOPEFΛ্͔ΒॱʹḷͬͯWJTJUϝιου͕ݺ͹ΕΔ
  42. © DMM.com X WJTJUϝιου WJTJUϝιουͰԼهͷϩδοΫΛॻ͘ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.."

    8)*5&41"$& 7"-6& "3(6.&/5 w $0.."ͷ࣍ͷ8)*5&@41"$&ʹվߦίʔυ͕ͳ͍৔ ߹͸ܯࠂΛग़͢ w Լهͷߏ଄ʹͳ͍ͬͯΔ࣌ʹ
  43. © DMM.com X ྫ͑͹͜Μͳײ͡ʹ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 7"-6& "3(6.&/5 -JTU ʹͨͲΓண͍ͨ࣌ WJTJU
  44. © DMM.com X ྫ͑͹͜Μͳײ͡ʹ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 7"-6& "3(6.&/5 -JTU ʹͨͲΓண͍ͨ࣌ WJTJU DIJMESFOͷதʹ $0.."͕͍Δ৔߹ʹ
  45. © DMM.com X ྫ͑͹͜Μͳײ͡ʹ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 7"-6& "3(6.&/5 -JTU ʹͨͲΓண͍ͨ࣌ WJTJU DIJMESFOͷதʹ $0.."͕͍Δ৔߹ʹ ࣍ͷۭന͕ վߦΛؚΉ͔ʁ
  46. © DMM.com X ίʔυʹ͢Δͱ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_PARAMETER_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } }
  47. © DMM.com X ίʔυʹ͢Δͱ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_ARGUMENT_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } } OPEF͕ 7"-6&@"3(6.&/5@-*45ͷ࣌ʹ
  48. © DMM.com X ίʔυʹ͢Δͱ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_ARGUMENT_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } } OPEF͕ DIJMESFOͷதʹ$0.."͕͋Δ࣌ʹ
  49. © DMM.com X ίʔυʹ͢Δͱ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_ARGUMENT_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } } ࣍ͷۭനʹ վߦؚ͕·Ε͍ͯΔ͔ʁ
  50. © DMM.com X ίʔυʹ͢Δͱ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_PARAMETER_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } } վߦ͞Εͯ ͳ͔ͬͨΒ FNJUʂ
  51. © DMM.com X 3VMF4FU1SPWJEFSΛ࡞Δ 3VMF4FU1SPWJEFSΛܧঝ͠ɺHFUϝιουΛ࣮૷͢Δɻ ࡞ͬͨ3VMFΛొ࿥͢Δɻ class CustomRuleSetProvider : RuleSetProvider

    { override fun get(): RuleSet = RuleSet("custom", LineBreakInArgsListRule()) }
  52. © DMM.com X DPNHJUIVCTIZJLPLUMJOUDPSF3VMF4FU1SPWJEFS SFTPVSDFT.&5"*/'TFSWJDFT DPNHJUIVCTIZJLPLUMJOUDPSF3VMF4FU1SPWJEFSʹ࡞ͬͨ 3VMF4FU1SPWJEFSΛهड़ com.kgmyshin.ktlint.CustomRuleSetProvider

  53. © DMM.com X KBSΛ࡞Δ KBSΛ࡞੒͢Δɻ ./gradlew jar CVJMEMJCT഑Լʹ࡞੒͞ΕΔɻ

  54. © DMM.com ࡞ͬͨΧελϜϧʔϧΛద༻͢Δ

  55. © DMM.com X ࡞ͬͨΧελϜϧʔϧΛద༻͢Δ $ ktlint -R ktlint-custom-rulseset.jar “src/main/**/*.kt" 3ͰKBSͷύεΛ౉͢

    $ ktlint -R ktlint-custom-ruleset.jar “app/src/main/**/*.kt” ... Lint error > ...kt:43:17: should line break Lint error > ....kt:43:33: should line break ͦΕ͚ͩͰ0,ɻ
  56. © DMM.com X LPUMJOUFSHSBEMFΛ࢖͍ͬͯΔ৔߹ buildscript { dependencies { classpath "gradle.plugin.org.jmailen.gradle:kotlinter-gradle:1.11.1"

    classpath files('lint/ktlint/ktlint-custom-ruleset.jar') } } DMBTTQBUIΛ௨͢ $ ./gradlew lintKotlin ... > Task :app:lintKotlinMain Lint error > ...kt:43:17: should line break Lint error > ....kt:43:33: should line break ͦΕ͚ͩͰ0,ɻ
  57. © DMM.com ͦͷଞGPSNBUΛ͔͚Δ

  58. © DMM.com X GPSNBUΛ͔͚Δ LUMJOU͸ܯࠂΛग़͚ͩ͢Ͱͳ͘ɺ͋Δఔ౓GPSNBUΛ͔͚Δ͜ͱ ͕Ͱ͖Δɻ LPMJOUFSHSBEMFͷ৔߹ $ ktlint -F

    “src/main/**/*.kt” $ ./gradlew formatKotlin
  59. © DMM.com X GPSNBUΛ͔͚Δ data class A( val bb: String,

    val cc: String, val dd: String ) #FGPSF "GUFS data class A( val bb: String, val cc: String, val dd: String ) $ ./gradlew formatKotlin
  60. © DMM.com X ΧελϜϧʔϧͷ৔߹ ΧελϜϧʔϧͷ৔߹Ͱ΋ɺGPSNBUΛ͔͚Δ͜ͱ͸Ͱ͖Δɻ ͨͩ͠ʢ΋ͪΖΜ͚ͩͲʣɺGPSNBUͷϩδοΫΛ࣮૷͢Δඞ ཁ͕͋Δɻ

  61. © DMM.com X ઌ΄Ͳͷίʔυ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_PARAMETER_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) } } } GPSNBUΦϓγϣϯ͕͍ͭͯΔ࣌ UVSFʹͳΔ
  62. © DMM.com X ઌ΄Ͳͷίʔυ override fun visit( node: ASTNode, autoCorrect:

    Boolean, emit: ( offset: Int, errorMessage: String, canBeAutoCorrected: Boolean ) -> Unit ) { if (node.elementType == KtNodeTypes.VALUE_PARAMETER_LIST) { val isError = node.children().any { it.elementType == KtTokens.COMMA && it.treeNext.elementType == KtTokens.WHITE_SPACE && !it.treeNext.text.contains("\n") } if (isError) { emit( node.startOffset, "should line break", false ) if (autoCorrect) { format(node) } } } } ௥Ճ
  63. © DMM.com X GPSNBUϝιουͷத਎ private fun format(node: ASTNode) { val

    indent = node.indentSize() node.children().forEach { if (..লུ..) { (it.treeNext as LeafPsiElement).rawReplaceWithText( "\n" + " ".repeat(indent) ) } } } GPSNBUϝιουͷத਎ͷྫɻ ࣮ࡍͷOPEFͷ಺༰Λ௚઀ฤू͢Ε͹0,ɻ
  64. © DMM.com X GPSNBUϝιουͷத਎ private fun format(node: ASTNode) { val

    indent = node.indentSize() node.children().forEach { if (..লུ..) { (it.treeNext as LeafPsiElement).rawReplaceWithText( "\n" + " ".repeat(indent) ) } } } GPSNBUϝιουͷத਎ͷྫɻ ࣮ࡍͷOPEFͷ಺༰Λ௚઀ฤू͢Ε͹0,ɻ
  65. © DMM.com X ΧελϜϧʔϧͷ৔߹ data class A( val bb: String,

    val cc: String, val dd: String ) #FGPSF "GUFS data class A( val bb: String, val cc: String, val dd: String ) $ ./gradlew formatKotlin
  66. © DMM.com X EFUFLU

  67. © DMM.com Πϯετʔϧํ๏

  68. © DMM.com X HSBEMFQSPKFDUʹಋೖ͢Δ৔߹ CVJMEHSBEMFʹԼهΛهड़ɻ buildscript { repositories { jcenter()

    } } plugins { id "io.gitlab.arturbosch.detekt" version "1.0.0.RC8" } detekt { version = "1.0.0.[version]" defaultProfile { input = file("src/main/kotlin") filters = ".*/resources/.*,.*/build/.*" } }
  69. © DMM.com ࢖͍ํ

  70. © DMM.com X ࢖͍ํ AHSBEMFXEFUFLU$IFDLAΛ࣮ߦ͢Δ͚ͩ

  71. © DMM.com X ࢖͍ํ $ ./gradlew detektCheck : Ruleset: comments

    Ruleset: complexity - 20min debt LongMethod - 29/20 - [onCreate] at ...kt:45:3 Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 : Overall debt: 25min Complexity Report: - 489 lines of code (loc) - 416 source lines of code (sloc) - 263 logical lines of code (lloc) - 3 comment lines of code (cloc) - 46 McCabe complexity (mcc) - 2 number of total code smells - 0 % comment source ratio - 174 mcc per 1000 lloc - 7 code smells per 1000 lloc Project Statistics: - number of properties: 32 - number of functions: 24 - number of classes: 26 - number of packages: 3 - number of kt files: 12 : detekt finished in 1804 ms.
  72. © DMM.com X HSBEMFQSPKFDUʹಋೖ͢Δ৔߹ $ ./gradlew detektCheck : Ruleset: comments

    Ruleset: complexity - 20min debt LongMethod - 29/20 - [onCreate] at ...kt:45:3 Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 : Overall debt: 25min Complexity Report: - 489 lines of code (loc) - 416 source lines of code (sloc) - 263 logical lines of code (lloc) - 3 comment lines of code (cloc) - 46 McCabe complexity (mcc) - 2 number of total code smells - 0 % comment source ratio - 174 mcc per 1000 lloc - 7 code smells per 1000 lloc Project Statistics: - number of properties: 32 - number of functions: 24 - number of classes: 26 - number of packages: 3 - number of kt files: 12 : detekt finished in 1804 ms. -JOUͷ݁Ռ
  73. © DMM.com X HSBEMFQSPKFDUʹಋೖ͢Δ৔߹ $ ./gradlew detektCheck : Ruleset: comments

    Ruleset: complexity - 20min debt LongMethod - 29/20 - [onCreate] at ...kt:45:3 Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 : Overall debt: 25min Complexity Report: - 489 lines of code (loc) - 416 source lines of code (sloc) - 263 logical lines of code (lloc) - 3 comment lines of code (cloc) - 46 McCabe complexity (mcc) - 2 number of total code smells - 0 % comment source ratio - 174 mcc per 1000 lloc - 7 code smells per 1000 lloc Project Statistics: - number of properties: 32 - number of functions: 24 - number of classes: 26 - number of packages: 3 - number of kt files: 12 : detekt finished in 1804 ms. -JOUͷमਖ਼ʹ͔͔Δ࣌ؒ
  74. © DMM.com X HSBEMFQSPKFDUʹಋೖ͢Δ৔߹ $ ./gradlew detektCheck : Ruleset: comments

    Ruleset: complexity - 20min debt LongMethod - 29/20 - [onCreate] at ...kt:45:3 Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 : Overall debt: 25min Complexity Report: - 489 lines of code (loc) - 416 source lines of code (sloc) - 263 logical lines of code (lloc) - 3 comment lines of code (cloc) - 46 McCabe complexity (mcc) - 2 number of total code smells - 0 % comment source ratio - 174 mcc per 1000 lloc - 7 code smells per 1000 lloc Project Statistics: - number of properties: 32 - number of functions: 24 - number of classes: 26 - number of packages: 3 - number of kt files: 12 : detekt finished in 1804 ms. ෳࡶ౓
  75. © DMM.com X HSBEMFQSPKFDUʹಋೖ͢Δ৔߹ $ ./gradlew detektCheck : Ruleset: comments

    Ruleset: complexity - 20min debt LongMethod - 29/20 - [onCreate] at ...kt:45:3 Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 : Overall debt: 25min Complexity Report: - 489 lines of code (loc) - 416 source lines of code (sloc) - 263 logical lines of code (lloc) - 3 comment lines of code (cloc) - 46 McCabe complexity (mcc) - 2 number of total code smells - 0 % comment source ratio - 174 mcc per 1000 lloc - 7 code smells per 1000 lloc Project Statistics: - number of properties: 32 - number of functions: 24 - number of classes: 26 - number of packages: 3 - number of kt files: 12 : detekt finished in 1804 ms. ϑΝΠϧ਺ͳͲͷϨϙʔτ
  76. © DMM.com ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ

  77. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ͸͍ͬͺ͍͋Δ  $IBJO8SBQQJOH  $MBTT/BNJOH  $PNNFOU4QBDJOH

     $PNQMFY$POEJUJPO  $PNQMFY.FUIPE  %VQMJDBUF$BTF*O8IFO&YQSFTTJPO  &NQUZ$BUDI#MPDL  &NQUZ$MBTT#MPDL  &NQUZ%FGBVMU$POTUSVDUPS  &NQUZ%P8IJMF#MPDL  &NQUZ&MTF#MPDL  &NQUZ'JOBMMZ#MPDL  &NQUZ'PS#MPDL  &NQUZ'VODUJPO#MPDL  &NQUZ*G#MPDL  &NQUZ*OJU#MPDL  &NQUZ,U'JMF  &NQUZ4FDPOEBSZ$POTUSVDUPS  &NQUZ8IFO#MPDL  &NQUZ8IJMF#MPDL  &OVN/BNJOH  &RVBMT8JUI)BTI$PEF&YJTU  &YQMJDJU(BSCBHF$PMMFDUJPO$BMM  'JMFOBNF  'JOBM/FXMJOF  'PSCJEEFO$PNNFOU  'PS&BDI0O3BOHF  'VODUJPO/BNJOH  *NQPSU0SEFSJOH  *OEFOUBUJPO  -BSHF$MBTT  -POH.FUIPE  -POH1BSBNFUFS-JTU  .BHJD/VNCFS  .BUDIJOH%FDMBSBUJPO/BNF  .BYJNVN-JOF-FOHUI  .BY-JOF-FOHUI  .PEJpFS0SEFS  .PEJpFS0SEF  /FTUFE#MPDL%  /FX-JOF"U&O  /P#MBOL-JOF#  /P$POTFDVUJW  /P&NQUZ$MBT  /P*U1BSBN*O.  /P-JOF#SFBL"  /P-JOF#SFBL#  /P.VMUJQMF4Q  /P4FNJDPMPO  /P5SBJMJOH4QB  /P6OJU3FUVSO  /P6OVTFE*N  /P8JMEDBSE*N  0CKFDU1SPQFS  0QUJPOBM"CTUS  1BDLBHF/BN  1BSBNFUFS-JT
  78. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ͸͍ͬͺ͍͋Δ  $IBJO8SBQQJOH  $MBTT/BNJOH  $PNNFOU4QBDJOH

     $PNQMFY$POEJUJPO  $PNQMFY.FUIPE  %VQMJDBUF$BTF*O8IFO&YQSFTTJPO  &NQUZ$BUDI#MPDL  &NQUZ$MBTT#MPDL  &NQUZ%FGBVMU$POTUSVDUPS  &NQUZ%P8IJMF#MPDL  &NQUZ&MTF#MPDL  &NQUZ'JOBMMZ#MPDL  &NQUZ'PS#MPDL  &NQUZ'VODUJPO#MPDL  &NQUZ*G#MPDL  &NQUZ*OJU#MPDL  &NQUZ,U'JMF  &NQUZ4FDPOEBSZ$POTUSVDUPS  &NQUZ8IFO#MPDL  &NQUZ8IJMF#MPDL  &OVN/BNJOH  &RVBMT8JUI)BTI$PEF&YJTU  &YQMJDJU(BSCBHF$PMMFDUJPO$BMM  'JMFOBNF  'JOBM/FXMJOF  'PSCJEEFO$PNNFOU  'PS&BDI0O3BOHF  'VODUJPO/BNJOH  *NQPSU0SEFSJOH  *OEFOUBUJPO  -BSHF$MBTT  -POH.FUIPE  -POH1BSBNFUFS-JTU  .BHJD/VNCFS  .BUDIJOH%FDMBSBUJPO/BNF  .BYJNVN-JOF-FOHUI  .BY-JOF-FOHUI  .PEJpFS0SEFS  .PEJpFS0SEF  /FTUFE#MPDL%  /FX-JOF"U&O  /P#MBOL-JOF#  /P$POTFDVUJW  /P&NQUZ$MBT  /P*U1BSBN*O.  /P-JOF#SFBL"  /P-JOF#SFBL#  /P.VMUJQMF4Q  /P4FNJDPMPO  /P5SBJMJOH4QB  /P6OJU3FUVSO  /P6OVTFE*N  /P8JMEDBSE*N  0CKFDU1SPQFS  0QUJPOBM"CTUS  1BDLBHF/BN  1BSBNFUFS-JT EFGBVMUͰBDUJWFʹͳ͍ͬͯΔ΋ͷΛ ਺͑Δͱݸʂ
  79. © DMM.com ϧʔϧͷઃఆํ๏

  80. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ HFOFSBUFίϚϯυΛ࣮ߦ͢Δͱɺ ɹɹɹɹɹɹɹɹɹɹɹEFGBVMUEFUFLUDPOpHZNM͕Ͱ͖Δ HSBEMFXEFUFLU(FOFSBUF$POpH

  81. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ …লུ… style: active: true CollapsibleIfStatements: active:

    false DataClassContainsFunctions: active: false conversionFunctionPrefix: 'to' EqualsNullCall: active: false ExpressionBodySyntax: active: false includeLineWrapping: false ForbiddenComment: active: true values: 'TODO:,FIXME:,STOPSHIP:' ForbiddenImport: active: false imports: '' FunctionOnlyReturningConstant: active: false ignoreOverridableFunction: true excludedFunctions: 'describeContents'
  82. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ …লུ… style: active: true CollapsibleIfStatements: active:

    false DataClassContainsFunctions: active: false conversionFunctionPrefix: 'to' EqualsNullCall: active: false ExpressionBodySyntax: active: false includeLineWrapping: false ForbiddenComment: active: true values: 'TODO:,FIXME:,STOPSHIP:' ForbiddenImport: active: false imports: '' FunctionOnlyReturningConstant: active: false ignoreOverridableFunction: true excludedFunctions: 'describeContents' 3VMF4FU
  83. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ …লུ… style: active: true CollapsibleIfStatements: active:

    false DataClassContainsFunctions: active: false conversionFunctionPrefix: 'to' EqualsNullCall: active: false ExpressionBodySyntax: active: false includeLineWrapping: false ForbiddenComment: active: true values: 'TODO:,FIXME:,STOPSHIP:' ForbiddenImport: active: false imports: '' FunctionOnlyReturningConstant: active: false ignoreOverridableFunction: true excludedFunctions: 'describeContents' 3VMF
  84. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ …লུ… style: active: true CollapsibleIfStatements: active:

    false DataClassContainsFunctions: active: false conversionFunctionPrefix: 'to' EqualsNullCall: active: false ExpressionBodySyntax: active: false includeLineWrapping: false ForbiddenComment: active: true values: 'TODO:,FIXME:,STOPSHIP:' ForbiddenImport: active: false imports: '' FunctionOnlyReturningConstant: active: false ignoreOverridableFunction: true excludedFunctions: 'describeContents' GBMTF ݸผʹ ઃఆͰ͖Δ
  85. © DMM.com ΧελϜϧʔϧͷ࡞Γํ

  86. © DMM.com X αϯϓϧͱͯ͠ɺ͜ͷϧʔϧΛͭ͘Δ fuga(1, 2) fuga( 1, 2 )

    /( 0, Ҿ਺ͰA A͕͋ͬͨΒվߦΛଅ͢
  87. © DMM.com X खॱ  ϓϩδΣΫτ࡞੒  3VMFΛ࡞Δ  3VMF4FU1SPWJEFSΛ࡞Δ

     SFTPVSDFT.&5"*/'TFSWJDFT JPHJUMBCBSUVSCPTDIEFUFLUBQJ3VMF4FU1SPWJEFSʹ࡞ͬͨ 3VMF4FU1SPWJEFSΛهड़  KBSΛ࡞Δ
  88. © DMM.com X ϓϩδΣΫτ࡞੒ buildscript { ext.kotlin_version = '1.2.60' repositories

    { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } group 'detekt-custom-lint' version '1.0-SNAPSHOT' apply plugin: 'kotlin' repositories { mavenCentral() } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "io.gitlab.arturbosch.detekt:detekt-api:1.0.0.RC8" testCompile "junit:junit:4.12" testCompile "org.assertj:assertj-core:3.10.0" testCompile "io.gitlab.arturbosch.detekt:detekt-api:1.0.0.RC8" testCompile "io.gitlab.arturbosch.detekt:detekt-test:1.0.0.RC8" }
  89. © DMM.com X ϓϩδΣΫτ࡞੒ buildscript { ext.kotlin_version = '1.2.60' repositories

    { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } group 'detekt-custom-lint' version '1.0-SNAPSHOT' apply plugin: 'kotlin' repositories { mavenCentral() } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "io.gitlab.arturbosch.detekt:detekt-api:1.0.0.RC8" testCompile "junit:junit:4.12" testCompile "org.assertj:assertj-core:3.10.0" testCompile "io.gitlab.arturbosch.detekt:detekt-api:1.0.0.RC8" testCompile "io.gitlab.arturbosch.detekt:detekt-test:1.0.0.RC8" } LPUMJOͷϓϩδΣΫτΛ࡞ͬͯ EFUFLUBQJΛ௥Ճ
  90. © DMM.com X 3VMFΛ࡞Δ ࡞Γ͍ͨ3VMFΛ࡞ͬͯߦ͘ɻ 3VMFΛܧঝͯ͠ɺJTTVFΛ࡞੒ͯ͠ɺద੾ͳWJTJUϝιουΛ࣮ ૷͢ΔɻʢLUMJOUͱҟͳΓෳ਺ͷWJTJUϝιου͕͋Δʣ

  91. © DMM.com X ·ͣ͸JTTVFΛ࡞Δ class LineBreakInArgsListDetektRule : Rule() { override

    val issue: Issue = Issue( id = "line-break-in-args-list", severity = Severity.Style, debt = Debt.FIVE_MINS, description = "should line break" ) } *E ΧςΰϦɺमਖ਼͔͔Δ࣌ؒɺઆ໌ΛҾ਺ʹ*TTVFͷΠϯελ ϯεΛ࡞Δɻ
  92. © DMM.com X 3VMFΛ࡞Δ LUMJOUͱҟͳΓෳ਺ͷWJTJUϝιου͕͋Δɻ 'JMF 1"$,"(& %*3&$5*7& $-"44 FMFNFOU

    DMBTT 8)*5&@41"$& FMFNFOU
 *%&/5*'*&3 8)*5&@41"$& $-"44@#0%: WJTJU'JMF WJTJU1BDLBHF%JSFDUJWF WJTJU$MBTT WJTJU$MBTT#PEZ
  93. © DMM.com X 3VMFΛ࡞Δ ͜ΕΛ֬ೝͯ͠ߦ͘ɻ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.."

    8)*5&41"$& 7"-6& "3(6.&/5 w $0.."ͷ࣍ͷ8)*5&@41"$&ʹվߦίʔυΛؚ·ΕΔ͜ͱ w Լهͷߏ଄ʹͳ͍ͬͯΔ࣌ʹ
  94. © DMM.com X 3VMFΛ࡞Δ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5
  95. © DMM.com X 3VMFΛ࡞Δ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 WJTJU7BMVF"SHVNFOU-JTU WJTJU7BMVF"SHVNFOU-JTUΛ࣮૷͢Δ
  96. © DMM.com X 3VMFΛ࡞Δ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 WJTJU7BMVF"SHVNFOU-JTU $0.."ͷ࣍ͷ WJTJU7BMVF"SHVNFOU-JTUΛ࣮૷͢Δ
  97. © DMM.com X 3VMFΛ࡞Δ 7"-6& "3(6.&/5-*45 7"-6& "3(6.&/5 $0.." 8)*5&41"$&

    7"-6& "3(6.&/5 WJTJU7BMVF"SHVNFOU-JTU $0.."ͷ࣍ͷ ۭന಺Ͱ վߦ͞ΕͯΔ͔ʁʁʁ WJTJU7BMVF"SHVNFOU-JTUΛ࣮૷͢Δ
  98. © DMM.com X 3VMFΛ࡞Δ class LineBreakInArgsListDetektRule : Rule() { override

    val issue: Issue = Issue( id = "line-break-in-args-list", severity = Severity.Style, debt = Debt.FIVE_MINS, description = "should line break" ) override fun visitValueArgumentList(list: KtValueArgumentList) { super.visitValueArgumentList(list) list.children.forEach { if (it.node.elementType == KtTokens.COMMA && it.nextSibling is PsiWhiteSpace && !it.nextSibling.text.contains("\n") ) { report( CodeSmell( issue, Entity.from(list), "should line break" ) ) return } } } } վߦ͕ͳ͍৔߹͸ɺSFQPSUϝιουΛݺͿɻ
  99. © DMM.com X 3VMFΛ࡞Δ class LineBreakInArgsListDetektRule : Rule() { override

    val issue: Issue = Issue( id = "line-break-in-args-list", severity = Severity.Style, debt = Debt.FIVE_MINS, description = "should line break" ) override fun visitValueArgumentList(list: KtValueArgumentList) { super.visitValueArgumentList(list) list.children.forEach { if (it.node.elementType == KtTokens.COMMA && it.nextSibling is PsiWhiteSpace && !it.nextSibling.text.contains("\n") ) { report( CodeSmell( issue, Entity.from(list), "should line break" ) ) return } } } } վߦ͕ͳ͍৔߹͸ɺSFQPSUϝιουΛݺͿɻ $0.."ͷ ࣍ͷ8)*5&@41"$&͕վߦΛ ؚ·ͳ͍৔߹
  100. © DMM.com X 3VMFΛ࡞Δ class LineBreakInArgsListDetektRule : Rule() { override

    val issue: Issue = Issue( id = "line-break-in-args-list", severity = Severity.Style, debt = Debt.FIVE_MINS, description = "should line break" ) override fun visitValueArgumentList(list: KtValueArgumentList) { super.visitValueArgumentList(list) list.children.forEach { if (it.node.elementType == KtTokens.COMMA && it.nextSibling is PsiWhiteSpace && !it.nextSibling.text.contains("\n") ) { report( CodeSmell( issue, Entity.from(list), "should line break" ) ) return } } } } վߦ͕ͳ͍৔߹͸ɺSFQPSUϝιουΛݺͿɻ SFQPSU͢Δ
  101. © DMM.com X 3VMF4FU1SPWJEFSΛ࡞Δ 3VMF4FU1SPWJEFSΛܧঝ͠ɺJOTUBODFϝιουΛ࣮૷͢Δɻ ࡞ͬͨ3VMFΛొ࿥͢Δɻ class CustomRuleSetProvider : RuleSetProvider

    { override val ruleSetId: String = "detekt-custom-rules" override fun instance(config: Config): RuleSet = RuleSet( ruleSetId, listOf(LineBreakInArgsListDetektRule()) ) }
  102. © DMM.com X DPNHJUIVCTIZJLPLUMJOUDPSF3VMF4FU1SPWJEFS SFTPVSDFT.&5"*/'TFSWJDFT JPHJUMBCBSUVSCPTDIEFUFLUBQJ3VMF4FU1SPWJEFSʹ࡞ͬͨ 3VMF4FU1SPWJEFSΛهड़ com.kgmyshin.ktlint.CustomRuleSetProvider

  103. © DMM.com X KBSΛ࡞Δ KBSΛ࡞੒͢Δɻ ./gradlew jar CVJMEMJCT഑Լʹ࡞੒͞ΕΔɻ

  104. © DMM.com ࡞ͬͨΧελϜϧʔϧΛద༻͢Δ

  105. © DMM.com X ࡞ͬͨΧελϜϧʔϧΛద༻͢Δ dependencies { … detekt files("detekt-custom-lint.jar") }

    EFQFOEFODJFTʹEFUFLUDPOpHVSBUJPOʹKBSΛ௥Ճ͢Δ $ ./gradlew detektCheck : Ruleset: detekt-custom-rules - 5min debt line-break-in-args-list - [A] at ...kt:42:13 ͦΕ͚ͩͰ0,ɻ
  106. © DMM.com ͦͷଞGPSNBUΛ͔͚Δ

  107. © DMM.com X BVUP$PSSFDU1SPQFSUZ l5IJTPQUJPOJTTUJMMQSFTFOUEVFUPMFHBDZ SFBTPOT*OUIFpSTUNJMFTUPOFSFMFBTFTEFUFLU BMTPGPSNBUUFELPUMJODPEF8JUIJOUIFEFUFLU UFBNXFEFDJEFEUPOPUNFTTXJUIVTFSDPEFBOE MFUPUIFSUPPMTEPUIFGPSNBUUJOHFHJOUFMMJKPS ,U-JOU4UJMMBTEFUFLUDBOCFFYUFOEFEXJUI

    DVTUPNSVMFT ZPVBSFGSFFUPXSJUFSVMFTXIJDI TVQQPSUBVUPDPSSFDUJPO0OMZXSJUFDPSSFDUJOH DPEFXJUIJOUIFXJUI"VUP$PSSFDU GVODUJPOz IUUQTHJUIVCDPNBSUVSCPTDIEFUFLUCMPCNBTUFSEPDTQBHFTDPOpHVSBUJPOTNEBVUPDPSSFDUQSPQFSUZ
  108. © DMM.com X BVUP$PSSFDU1SPQFSUZ w ࠷ॳͷϚΠϧετʔϯͰ͸ϑΥʔϚοτػೳ͸͋ͬͨ w EFUFLUνʔϜ͸ɺࠞཚΛ͚͞ΔͨΊϑΥʔϚοτػೳ Λͳͨ͘͠ w

    ͨͩ͠ɺΧελϜϧʔϧͰϑΥʔϚοτΛ͢Δ͜ͱ͕ Ͱ͖Δɻ w XJUI"VUP$PSSFDUϝιου಺ʹ࣮૷͍ͯͩ͘͠͞
  109. © DMM.com X ΧελϜϧʔϧͰXJUI"VUP$PSSFDUΛ࢖͏ override fun visitValueArgumentList(list: KtValueArgumentList) { super.visitValueArgumentList(list)

    list.children.forEach { if (it.nextSibling.node.elementType == KtTokens.COMMA && it.nextSibling.nextSibling is PsiWhiteSpace && !it.nextSibling.nextSibling.text.contains("\n") ) { report( CodeSmell( issue, Entity.from(list), "should line break" ) ) withAutoCorrect { format(list) } return } } } } DPOJGHͰABVUP$PSSFDUUVSFAͱͳ͍ͬͯΔ͚࣌ͩ ϑΥʔϚοτ͞ΕΔ
  110. © DMM.com X ΧελϜϧʔϧͰXJUI"VUP$PSSFDUΛ࢖͏ override fun visitValueArgumentList(list: KtValueArgumentList) { super.visitValueArgumentList(list)

    list.children.forEach { if (it.nextSibling.node.elementType == KtTokens.COMMA && it.nextSibling.nextSibling is PsiWhiteSpace && !it.nextSibling.nextSibling.text.contains("\n") ) { report( CodeSmell( issue, Entity.from(list), "should line break" ) ) withAutoCorrect { format(list) } return } } } } DPOJGHͰABVUP$PSSFDUUVSFAͱͳ͍ͬͯΔ͚࣌ͩ ϑΥʔϚοτ͞ΕΔ
  111. © DMM.com ͪͳΈʹ

  112. © DMM.com X EFUFDUGPSNBUUJOH EFUFLUGPSNBUUJOHΛCVJMEHSBEMF಺ͰEFUFLUDPOpHVSBUJPOʹࢦ ఆ͢Δ͜ͱͰ,UMJOUͷඪ४ϧʔϧΛ௥ՃͰ͖Δ dependencies { detekt “io.gitlab.arturbosch.detekt:detekt-formatting:1.0.0.RC8"

    } ˞GPSNBUͨ͠Γͱ͔͸ಛʹؔ܎ͳ͍Ͱ͢
  113. © DMM.com X BOESPJEMJOU

  114. © DMM.com ࢖͍ํ

  115. © DMM.com X ࢖͍ํ HSBEMFXMJOU ʜ 3BOMJOUPOWBSJBOUEFCVHJTTVFTGPVOE 3BOMJOUPOWBSJBOUSFMFBTFJTTVFTGPVOE 8SPUF)5.-SFQPSUUPpMFCVJMESFQPSUTMJOUSFTVMUTIUNM 8SPUF9.-SFQPSUUPpMFCVJMESFQPSUTMJOUSFTVMUTYNM

    ʜ BOESPJEϓϩδΣΫτͰMJOUλεΫΛ࣮ߦ͢Δɻ
  116. © DMM.com X ࢖͍ํ BOESPJEϓϩδΣΫτͰMJOUλεΫΛ࣮ߦ͢Δɻ

  117. © DMM.com ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ

  118. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ͸͍ͬͺ͍͋Δ  "DDFTTJCJMJUZ%FUFDUPS  "EE+BWBTDSJQU*OUFSGBDF%FUFDUPS  "MBSN%FUFDUPS

     "MXBZT4IPX"DUJPO%FUFDUPS  "OOPUBUJPO%FUFDUPS  "QJ%FUFDUPS  "QQ$PNQBU$BMM%FUFDUPS  "QQ$PNQBU3FTPVSDF%FUFDUPS  "QQ*OEFYJOH"QJ%FUFDUPS  "SSBZ4J[F%FUFDUPS  "TTFSU%FUFDUPS  #VJMUJO*TTVF3FHJTUSZ  #VUUPO%FUFDUPS  #ZUF0SEFS.BSL%FUFDUPS  $BMM4VQFS%FUFDUPS  $IJME$PVOU%FUFDUPS  $JQIFS(FU*OTUBODF%FUFDUPS  $MFBOVQ%FUFDUPS  $MJDLBCMF7JFX"DDFTTJCJMJUZ%FUFDUPS  $PNNFOU%FUFDUPS  $POUSPM'MPX(SBQI  $VTUPN7JFX%FUFDUPS  $VU1BTUF%FUFDUPS  %BUF'PSNBU%FUFDUPS  %FQSFDBUJPO%FUFDUPS  %FUFDU.JTTJOH1SFpY  %PT-JOF&OEJOH%FUFDUPS  %VQMJDBUF*E%FUFDUPS  %VQMJDBUF3FTPVSDF%FUFDUPS  &YUSB5FYU%FUFDUPS  'JFME(FUUFS%FUFDUPS  'SBHNFOU%FUFDUPS  'VMM#BDLVQ$POUFOU%FUFDUPS  (FU4JHOBUVSFT%FUFDUPS  (SBEMF%FUFDUPS  (SJE-BZPVU%FUFDUPS  )BOEMFS%FUFDUPS  )BSEDPEFE%FCVH.PEF%FUFDUPS  )BSEDPEFE7B  *DPO%FUFDUPS  *ODMVEF%FUFD  *OF⒏DJFOU8F  *OWBMJE1BDLBH  +BWB1FSGPSNB  +BWB4DSJQU*OUF  -BCFM'PS%FUF  -BZPVU$POTJT  -BZPVU*OqBUJP  -PDBMF%FUFDU  -PDBMF'PMEFS%  -PH%FUFDUPS  .BOJGFTU%FUF  .BOJGFTU5ZQP  .BUI%FUFDUP  .FSHF3PPU'SB  .JTTJOH$MBTT  .JTTJOH*E%FU
  119. © DMM.com X ඪ४Ͱ༻ҙ͞Ε͍ͯΔϧʔϧ͸͍ͬͺ͍͋Δ  "DDFTTJCJMJUZ%FUFDUPS  "EE+BWBTDSJQU*OUFSGBDF%FUFDUPS  "MBSN%FUFDUPS

     "MXBZT4IPX"DUJPO%FUFDUPS  "OOPUBUJPO%FUFDUPS  "QJ%FUFDUPS  "QQ$PNQBU$BMM%FUFDUPS  "QQ$PNQBU3FTPVSDF%FUFDUPS  "QQ*OEFYJOH"QJ%FUFDUPS  "SSBZ4J[F%FUFDUPS  "TTFSU%FUFDUPS  #VJMUJO*TTVF3FHJTUSZ  #VUUPO%FUFDUPS  #ZUF0SEFS.BSL%FUFDUPS  $BMM4VQFS%FUFDUPS  $IJME$PVOU%FUFDUPS  $JQIFS(FU*OTUBODF%FUFDUPS  $MFBOVQ%FUFDUPS  $MJDLBCMF7JFX"DDFTTJCJMJUZ%FUFDUPS  $PNNFOU%FUFDUPS  $POUSPM'MPX(SBQI  $VTUPN7JFX%FUFDUPS  $VU1BTUF%FUFDUPS  %BUF'PSNBU%FUFDUPS  %FQSFDBUJPO%FUFDUPS  %FUFDU.JTTJOH1SFpY  %PT-JOF&OEJOH%FUFDUPS  %VQMJDBUF*E%FUFDUPS  %VQMJDBUF3FTPVSDF%FUFDUPS  &YUSB5FYU%FUFDUPS  'JFME(FUUFS%FUFDUPS  'SBHNFOU%FUFDUPS  'VMM#BDLVQ$POUFOU%FUFDUPS  (FU4JHOBUVSFT%FUFDUPS  (SBEMF%FUFDUPS  (SJE-BZPVU%FUFDUPS  )BOEMFS%FUFDUPS  )BSEDPEFE%FCVH.PEF%FUFDUPS  )BSEDPEFE7B  *DPO%FUFDUPS  *ODMVEF%FUFD  *OF⒏DJFOU8F  *OWBMJE1BDLBH  +BWB1FSGPSNB  +BWB4DSJQU*OUF  -BCFM'PS%FUF  -BZPVU$POTJT  -BZPVU*OqBUJP  -PDBMF%FUFDU  -PDBMF'PMEFS%  -PH%FUFDUPS  .BOJGFTU%FUF  .BOJGFTU5ZQP  .BUI%FUFDUP  .FSHF3PPU'SB  .JTTJOH$MBTT  .JTTJOH*E%FU YNM KBWB LPUMJOͱ͍ΖΜͳSVMF͕ ࠞࡏ͍ͯ͠Δ
  120. © DMM.com ϧʔϧͷઃఆํ๏

  121. © DMM.com X ઃఆํ๏ͷҰྫ <?xml version="1.0" encoding="UTF-8"?> <lint> <issue id="all">

    <ignore path="build" /> </issue> <issue id="GoogleAppIndexingWarning" severity="ignore" /> </lint> DPOpHϑΝΠϧΛ࡞੒ͯ͠ɺ֤3VMFʢ͜͜Ͱ͸JTTVFʣ ͝ͱʹઃఆ͢Δɻ
  122. © DMM.com X ·ͣ͸DPOpHϑΝΠϧΛ࡞Δ android { ... lintOptions { abortOnError

    false lintConfig file("path/to/lint/config.xml") } } DPOpHϑΝΠϧΛMJOU$POpHʹઃఆ͢Ε͹0,ɻ
  123. © DMM.com ΧελϜϧʔϧͷ࡞Γํ

  124. © DMM.com X ͜͏͍͏ͷΛ࡞ͬͯݟ·͢ 3Y+BWBΛ࢖ͬͯͯɺ %JTQPTBCMFΛద੾ʹॲཧͯ͠ͳ͍ͱܯࠂग़͢ΧελϜϧʔϧ

  125. © DMM.com X खॱ  ϓϩδΣΫτ࡞੒  %FUFDUPSΛ࡞Δ  *TTVFΛ࡞Δ

     3FHJTUSZΛ࡞Δ  3FHJTUSZΛCVJMEHSBEMFʹهड़  KBSΛ࡞Δ
  126. © DMM.com X ̍ϓϩδΣΫτΛ࡞Δ HPPHMFTBNQMFTʹςϯϓϨʔτͷΑ͏ͳ΋ͷ͕͋Δͷ ͰɺͦΕʹͳΒ͏ͷ͕ૣ͍ IUUQTHJUIVCDPNHPPHMFTBNQMFTBOESPJEDVTUPN MJOUSVMFTUSFFNBTUFSBOESPJETUVEJP

  127. © DMM.com X ̎%FUFDUPSΛ࡞Δ public class NotHandledDisposableDetector extends Detector implements

    UastScanner { @Override public List<Class<? extends UElement>> getApplicableUastTypes() { return Collections.singletonList(UQualifiedReferenceExpression.class); } @Override public UElementHandler createUastHandler(JavaContext context) { return new UElementHandler() { @Override public void visitQualifiedReferenceExpression( ɹɹɹɹɹɹɹ UQualifiedReferenceExpression node ɹɹɹɹɹɹɹ) { : ॲཧ } }; } } ׬੒ܗ
  128. © DMM.com X ̎%FUFDUPSΛ࡞Δ public class NotHandledDisposableDetector extends Detector implements

    UastScanner { @Override public List<Class<? extends UElement>> getApplicableUastTypes() { return Collections.singletonList(UQualifiedReferenceExpression.class); } @Override public UElementHandler createUastHandler(JavaContext context) { return new UElementHandler() { @Override public void visitQualifiedReferenceExpression( ɹɹɹɹɹɹɹ UQualifiedReferenceExpression node ɹɹɹɹɹɹɹ) { : ॲཧ } }; } } %FUFDUPSΛFYUFOETɺ6BTU4DBOOFSΛJNQMFNFOUTɻ 6BTU͸6OJpFE"45ͷུɻ
  129. © DMM.com X ̎%FUFDUPSΛ࡞Δ public class NotHandledDisposableDetector extends Detector implements

    UastScanner { @Override public List<Class<? extends UElement>> getApplicableUastTypes() { return Collections.singletonList(UQualifiedReferenceExpression.class); } @Override public UElementHandler createUastHandler(JavaContext context) { return new UElementHandler() { @Override public void visitQualifiedReferenceExpression( ɹɹɹɹɹɹɹ UQualifiedReferenceExpression node ɹɹɹɹɹɹɹ) { : ॲཧ } }; } } DSFBUF6BTU)BOEMFSΛPWFSSJEF͢Δɻ ͭ·Γ6&MFNFOU)BOEMFSΛ࣮૷͢Δɻ
  130. © DMM.com X 6*&MFNFOU)BOEMFSͱ͸ USFFͷ্͔ΒॱʹTDBO͍ͯͬͯ͠ɺ֤छOPEF͕ݟ͔ͭΔͨ ͼʹWJTJUϝιου͕ݺ͹ΕΔਓɻ ֤छWJTJUϝιουΛPWFSSJEFͯ͠࢖͏ɻ 7"-6& "3(6.&/5-*45 7"-6&

    "3(6.&/5 $0.." 8)*5&41"$& 7"-6& "3(6.&/5 WJTJU"SHVNFOU-JTU WJTJU"SHVNFOU WJTJU"SHVNFOU
  131. © DMM.com X ࠓճ͸ ࠓճ͸1TJ7JXFSΛݟΔݶΓɺ%05@26"-*'*&%@&913&44*0/ ͕དྷͨ࣌ʹ֬ೝ͢Δ͚ͩͰࡁΈͦ͏ͳͷͰɺ WJTJU2VBMJpFE3FGFSFODF&YQSFTTJPOͷΈΛPWFSJSEF͢Δɻ

  132. © DMM.com X ̎%FUFDUPSΛ࡞Δ public class NotHandledDisposableDetector extends Detector implements

    UastScanner { @Override public List<Class<? extends UElement>> getApplicableUastTypes() { return Collections.singletonList(UQualifiedReferenceExpression.class); } @Override public UElementHandler createUastHandler(JavaContext context) { return new UElementHandler() { @Override public void visitQualifiedReferenceExpression( ɹɹɹɹɹɹɹ UQualifiedReferenceExpression node ɹɹɹɹɹɹɹ) { : ॲཧ } }; } } WJTJUର৅ͷOPEFͷΫϥεҰཡΛHFU"QQMJDBCMF6BTU5ZQFTͰ ฦ٫͢Δɻ͜ΕΛ͠ͳ͍ͱWJTJUϝιου͕ݺ͹Ε·ͤΜ
  133. © DMM.com X ̎%FUFDUPSΛ࡞Δ // kotlin if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().toString().equals("BLOCK") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType() .getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); return; } WJTJU2VBMJpFE3FGFSFODF&YQSFTTJPOͷத਎Λઆ໌͠·͢ɻ
  134. © DMM.com X ̎%FUFDUPSΛ࡞Δ // kotlin if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().toString().equals("BLOCK") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType() .getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); return; } ਌͕#MPDLA\^AͰɺ
  135. © DMM.com X ̎%FUFDUPSΛ࡞Δ // kotlin if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().toString().equals("BLOCK") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType() .getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); return; } ϝιουνΣʔϯͷ࠷ޙ͕ATVCTDSJCF AͰ
  136. © DMM.com X ̎%FUFDUPSΛ࡞Δ // kotlin if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().toString().equals("BLOCK") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType() .getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); return; } ͦͷTVCTDJSCFϝιουͷฦΓ஋͕JPSFBDUJWFYEJTQPTBCMFT%JTQPTBCMFͷ࣌ ‐͜Ε͕LUMJOUͱEFUFLUͰ͸ࠓͷॴͰ͖·ͤΜ
  137. © DMM.com X ̎%FUFDUPSΛ࡞Δ // kotlin if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().toString().equals("BLOCK") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType() .getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); return; } Ϩϙʔτ͢Δʂ ܯࠂ͕ग़Δ
  138. © DMM.com X ̎%FUFDUPSΛ࡞Δ // java if (node.getPsi() != null

    && node.getPsi().getContext() != null && node.getPsi().getContext().getContext() != null && node.getPsi().getContext().getContext().toString() .equals(“PsiCodeBlock") && node.getSelector().asRenderString().startsWith("subscribe(") && node.getSelector().getExpressionType() != null && node.getSelector().getExpressionType().getCanonicalText() .equals("io.reactivex.disposables.Disposable")) { context.report( ISSUE, node, context.getLocation(node), "Should handle Disposable” ); } KBWBͷ৔߹΋ॻ͖·͢ɻ6BTU4DBOOFS͸LPUMJOͱKBWBͰ۠ผͳ͘WJTJUͯ͘͠Ε·͢ɻ ͨͩ͠ɺLPUMJOͷ࣌ͱߏ଄͸େମಉ͡Ͱ͕͢ɺ14*ͷܕ͕ҧ͏ͷͰผ్ॻ͘ඞཁ͕͋Γ·͢ɻ
  139. © DMM.com X ̏*TTVFΛ࡞Δ public class NotHandledDisposableDetector extends Detector implements

    UastScanner { public static final Issue ISSUE = Issue.create( "NotHandledDisposable", "Not Handled Disposable", "Disposable should be called dispose.", Category.CORRECTNESS, 6, Severity.ERROR, new Implementation( NotHandledDisposableDetector.class, Scope.JAVA_FILE_SCOPE ) ); *%΍આ໌ɺΧςΰϦ৘ใΛ࣋ͬͨTUBUJDͳ*TTVFΠϯελϯεΛ࡞Γ·͢ɻ ৔ॴ͸Ͳ͜Ͱ΋͍͍͚Ͳɺطଘͷ-JOUΛݟΔݶΓ%FUFDUPSΫϥεʹॻ͍ͯΔͱ͜Ζ͕΄ͱΜͲ Ͱ͢ɻ
  140. © DMM.com X ̐3FHJTUSZΛ࡞Δ public class CustomIssueRegistry extends IssueRegistry {

    @Override public List<Issue> getIssues() { return Collections.singletonList( NotHandledDisposableDetector.ISSUE ); } } ࡞ͬͨ*TTVFΛొ࿥͢Δ3FHJTUPSZΫϥεΛ࡞Γ·͢ɻ *TTVF͸ෳ਺ొ࿥Ͱ͖·͢ɻ
  141. © DMM.com X ̑3FHJTUSZΛCVJMEHSBEMFʹهड़ jar { manifest { attributes("Lint-Registry-v2": "com.kgmyshin.lint.CustomIssueRegistry")

    } } A-JOU3FHJTUSZ7Aʹઌ΄Ͳ࡞ͬͨ3FHJTUSZΛઃఆ͠·͢ɻ A7AΛ๨Εͳ͍Α͏ʹɻ
  142. © DMM.com X KBSΛ࡞Δ KBSΛ࡞੒͢Δɻ ./gradlew jar CVJMEMJCT഑Լʹ࡞੒͞ΕΔɻ

  143. © DMM.com ࡞ͬͨΧελϜϧʔϧΛద༻͢Δ

  144. © DMM.com X ࡞ͬͨ$VTUPN-JOUͷ࢖͍ํ dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.1.10' lintChecks files("lint/custom-lint.jar")

    } AMJOU$IFDLTpMFT KBSͷύε A Λ௥ՃͰ0,
  145. © DMM.com X ࡞ͬͨ$VTUPN-JOUͷ࢖͍ํ dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.1.10' lintChecks project(":checks")

    } ಉҰϓϩδΣΫτʹ͋Δ৔߹͸ɺԼهͷΑ͏ʹEFQFOEFODJFTʹ MJOU$IFDLTQSPKFDU ΧελϜϧʔϧͷϞδϡʔϧ  Λ௥Ճ
  146. © DMM.com X ࡞ͬͨ$VTUPN-JOUͷ࢖͍ํ ./gradlew lint : Errors found: /../custom-lint-rules/library/src/main/java/test/pkg/MainJava.java:8:

    Error: Should handle Disposable [NotHandledDisposable] Single.just("test").subscribe(); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /../custom-lint-rules/library/src/main/java/test/pkg/MainKt.kt:8: Error: Should handle Disposable [NotHandledDisposable] Single.just("aa").subscribe() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ : ແࣄಈ͖·ͨ͠ʂ
  147. © DMM.com X ·ͱΊ

  148. © DMM.com X ൺֱද ඪ४ϧʔϧ ݸผઃఆ $VTUPN-JOU ͦͷଞ LUMJOU ݸऑ΄Ͳ

    جຊͰ͖ͳ͍ ࡞ΕΔ GPSNBUͰ͖Δ EFUFLU ͨ͘͞Μ͋Δ Ͱ͖Δ ࡞ΕΔ ෳࡶ౓ͳͲ΋ Θ͔Δ BOESPJEMJOU ͨ͘͞Μ͋Δ Ͱ͖Δ ࡞ΕΔ ܕΛݟΕΔ
  149. © DMM.com X ·ͱΊ w $VTUPN-JOUͷ࡞Γํ͸ͲΕ΋͍͍ͩͨڞ௨ w LUMJOU͸جຊతʹϧʔϧͷݸผઃఆ͸ͮ͠Β͍ w LUMJOU͸GPSNBU΋Ͱ͖Δ

    w EFUFLU͸ෳࡶ౓ͳͲ΋Ϩϙʔτ͞ΕΔ w EFUFLU͸GPSNBUػೳ͸΍Ίͨ w BOESPJEMJOU͸KBWBͱLPUMJOΛࠩผ͠ͳ͍VBTUΛ࢖ͬ ͍ͯΔ w BOESPJEMJOU͸ܕΛ֬ೝͰ͖Δ