Upgrade to Pro — share decks privately, control downloads, hide ads and more …

All About RuboCop (RubyKaigi 2018)

All About RuboCop (RubyKaigi 2018)

Slide deck from my talk at RubyKaigi 2018 in Sendai.

Bozhidar Batsov

May 31, 2018
Tweet

More Decks by Bozhidar Batsov

Other Decks in Programming

Transcript

  1. Hello!

    View Slide

  2. Божидар

    View Slide

  3. View Slide

  4. Bozhidar

    View Slide

  5. Божo

    View Slide

  6. Bozho
    cool

    View Slide

  7. Bozo
    not
    cool

    View Slide

  8. Bozho is not a bozo!

    View Slide

  9. Bozho is not a bozo!
    (at least he claims so)

    View Slide

  10. Bug
    cool

    View Slide

  11. (The RuboCop Guy)

    View Slide

  12. Sofia, Bulgaria
    Sofia, Bulgaria

    View Slide

  13. View Slide

  14. Grigor Dimitrov vs Kei Nishikori

    View Slide

  15. View Slide

  16. Lukanka

    View Slide

  17. Bulgarian Cheese

    View Slide

  18. Rakia
    Connecting People

    View Slide

  19. Shopska Salad
    (use only with Rakia)

    View Slide

  20. View Slide

  21. bbatsov

    View Slide

  22. Ruby & Rails
    style guides

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. First time in Japan!!!

    View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. All
    About
    RuboCop
    by Bozhidar Batsov
    (a.k.a. bug)

    View Slide

  35. Writing Lint
    for
    Ruby
    by Masataka Kuwabara
    (a.k.a. Pocke)
    http://rubykaigi.org/2017/presentations/p_ck_.html

    View Slide

  36. Improve Ruby
    coding style
    rules and Lint
    by Koichi Ito
    http://rubykaigi.org/2018/presentations/koic.html#jun01

    View Slide

  37. Something
    About
    RuboCop
    by Bozhidar Batsov
    (a.k.a. bug)

    View Slide

  38. RuboCop in a
    Nutshell

    View Slide

  39. A Ruby static code analysis
    tool aimed to enforce the
    Ruby Community Style Guide

    View Slide

  40. In other words…

    View Slide

  41. It keeps your
    codebase consistent

    View Slide

  42. It saves you time

    View Slide

  43. It advances the Ruby
    language forward

    View Slide

  44. View Slide

  45. 32,608,128

    View Slide

  46. Provides an efficient
    way for codebases to be
    updated

    View Slide

  47. Disclaimer

    View Slide

  48. Lint tools are not a
    replacement for
    common sense

    View Slide

  49. Why an entire talk about
    a mere lint tool?

    View Slide

  50. View Slide

  51. View Slide

  52. Notable Japanese
    Contributors
    • Yuji Nakayama (@yujinakayama)

    • Masataka Kuwabara (@pocke)

    • Koichi Ito (@koic)

    • Yukihiro "Matz" Matsumoto (@matz) ;-)

    View Slide

  53. View Slide

  54. https://github.com/rubocop-jp/issues

    View Slide

  55. https://github.com/fortissimo1997/ruby-style-guide

    View Slide

  56. 314 open issues

    View Slide

  57. 100 volunteers

    View Slide

  58. 3.14 issues/person

    View Slide

  59. Coincidence?

    View Slide

  60. Or providence?

    View Slide

  61. A Brief History of Time

    View Slide

  62. A Brief History of
    RuboCop

    View Slide

  63. 2011
    The Ruby Style Guide

    View Slide

  64. View Slide

  65. May, 2012
    RuboCop 0.0.0

    View Slide

  66. May, 2012
    RuboCop 0.0.0

    View Slide

  67. View Slide

  68. Static code analysis
    with regular
    expressions …

    View Slide

  69. View Slide

  70. Nov, 2012
    Along Came Jonas

    View Slide

  71. View Slide

  72. View Slide

  73. Spring, 2013
    Return of the Jedi

    View Slide

  74. Using a proper
    parser!

    View Slide

  75. View Slide

  76. Struggles with Ripper

    View Slide

  77. Ripper supported
    only MRI

    View Slide

  78. I wanted to build a
    cross-platform tool

    View Slide

  79. Ripper had (has?)
    almost no
    documentation

    View Slide

  80. Ripper is tied to the
    underlying Ruby
    runtime

    View Slide

  81. …which means you can’t
    parse code as Ruby 2.3
    while running Ruby 2.5

    View Slide

  82. Ripper has a very hard
    to work with AST
    representation

    View Slide

  83. pry(main)> Ripper.sexp('alias :some :test')
    => [:program,
    [[:alias,
    [:symbol_literal, [:symbol, [:@ident, "some", [1, 7]]]],
    [:symbol_literal, [:symbol, [:@ident, "test", [1, 13]]]]]]]

    View Slide

  84. each(:method_add_arg, sexp) do |s|
    next if s[1][0] != :call
    receiver = s[1][1][1]
    method_name = s[1][3][1]
    if receiver && receiver[1] == 'Array' &&
    method_name == 'new' && s[2] == [:arg_paren, nil]
    offences.delete(Offence.new(:convention,
    receiver[2].lineno,
    ERROR_MESSAGE))
    add_offence(:convention,
    receiver[2].lineno,
    ERROR_MESSAGE)
    end
    end

    View Slide

  85. And many quirks…

    View Slide

  86. pry(main)> Ripper.lex(':one')
    => [[[1, 0], :on_symbeg, ":"], [[1, 1], :on_ident,
    "one"]]
    pry(main)> Ripper.lex(':alias')
    => [[[1, 0], :on_symbeg, ":"], [[1, 1], :on_kw,
    "alias"]]

    View Slide

  87. pry(main)> Ripper.lex('def alias(arg)')

    => [[[1, 0], :on_kw, "def"],

    [[1, 3], :on_sp, " "],

    [[1, 4], :on_kw, "alias"],

    [[1, 9], :on_lparen, "("],

    [[1, 10], :on_ident, “arg"],

    [[1, 13], :on_rparen, ")"]]
    pry(main)> Ripper.lex('def aliass(arg)')

    => [[[1, 0], :on_kw, "def"],

    [[1, 3], :on_sp, " "],

    [[1, 4], :on_ident, "aliass"],

    [[1, 10], :on_lparen, "("],

    [[1, 11], :on_ident, "arg"],

    [[1, 14], :on_rparen, ")"]]

    View Slide

  88. Which no one really
    considered
    problematic…

    View Slide

  89. View Slide

  90. View Slide

  91. Parser: A New Hope

    View Slide

  92. parser is
    cross-platform

    View Slide

  93. well documented

    View Slide

  94. supports multiple
    parsing targets
    (Ruby 1.9 - 2.5)

    View Slide

  95. View Slide

  96. easy to work with ast
    format
    https://github.com/whitequark/ast

    View Slide

  97. p Parser::CurrentRuby.parse("2 + 2")
    # (send
    # (int 2) :+
    # (int 2))

    View Slide

  98. AST node source
    location mappings

    View Slide

  99. p Parser::CurrentRuby.parse("2 + 2").loc
    # ## @dot=nil,
    # @begin=nil,
    # @end=nil,
    # @selector=#,
    # @expression=#>
    p Parser::CurrentRuby.parse("2 + 2").loc.selector.source
    # "+"

    View Slide

  100. $ ruby-parse -L -e "2+2"
    (send
    (int 2) :+
    (int 2))
    2+2
    ~ selector
    ~~~ expression
    (int 2)
    2+2
    ~ expression
    (int 2)
    2+2
    ~ expression

    View Slide

  101. powerful code
    rewriting capabilities

    View Slide

  102. View Slide

  103. Struggles with Parser

    View Slide

  104. Parser was brand
    new

    View Slide

  105. Had no real users

    View Slide

  106. And had plenty of
    bugs…

    View Slide

  107. View Slide

  108. Peter Zotov
    (@whitequark)

    View Slide

  109. 28th May, 2013
    State of Unity

    View Slide

  110. RuboCop 0.8

    View Slide

  111. 1st July, 2013
    RuboCop 0.9

    View Slide

  112. Formatters

    View Slide

  113. Auto-correct

    View Slide

  114. View Slide

  115. Commissioner
    (single parsing run triggers all cops)

    View Slide

  116. Results caching
    (rubocop —-cache)

    View Slide

  117. Parallel Checks
    (rubocop —-parallel)

    View Slide

  118. Pattern Matching
    (regex-style matching for ast nodes)

    View Slide

  119. def on_send(node)
    receiver_node, method_name, *arg_nodes = *node
    return unless receiver_node && receiver_node.array_type? &&
    method_name == :* && arg_nodes.first.str_type?
    add_offense(node, location: :selector)
    end
    Style/ArrayJoin (before)

    View Slide

  120. Style/ArrayJoin (now)
    def_node_matcher :join_candidate?, '(send $array :* $str)'
    def on_send(node)
    join_candidate?(node) { add_offense(node, location: :selector) }
    end

    View Slide

  121. RuboCop Today

    View Slide

  122. Started out with no
    configuration at all…

    View Slide

  123. Today things are
    infinitely configurable

    View Slide

  124. Metrics/LineLength:
    Enabled: false

    View Slide

  125. Metrics/LineLength:
    Max: 100

    View Slide

  126. Style/StringLiterals:
    Enabled: false

    View Slide

  127. Style/StringLiterals:
    EnforcedStyle: double_quotes

    View Slide

  128. You can set a target
    Ruby/Rails version

    View Slide

  129. You can limit cops to
    certain folders with
    Exclude/Include directives

    View Slide

  130. You can have different
    settings for the same cops
    in different directories

    View Slide

  131. You can inherit
    between configuration
    files

    View Slide

  132. You can auto-generate
    an initial configuration
    for your project

    View Slide

  133. rubocop —-auto-gen-config

    View Slide

  134. And you can read
    about all of this in
    RuboCop’s manual…

    View Slide

  135. rubocop.readthedocs.io

    View Slide

  136. gry is a smarter
    alternative to the
    built-in command
    https://github.com/pocke/gry

    View Slide

  137. RuboCop started as a
    code style checker

    View Slide

  138. •Style
    •Lint
    •Layout
    •Naming
    •Security
    •Performance
    •Performance
    •Rails
    •Bundler
    •Metric
    •Gemspec

    View Slide

  139. RuboCop is a great
    code formatter

    View Slide

  140. rubocop —-only Layout -a

    View Slide

  141. RuboCop does some
    linting better than
    ruby -w

    View Slide

  142. rubocop —-lint

    View Slide

  143. You can extend
    RuboCop with your
    own cops

    View Slide

  144. Granite
    (business actions architecture for Rails applications)
    https://toptal.github.io/granite/

    View Slide

  145. $ bundle exec rake new_cop[Department/Name]

    View Slide

  146. https://rubocop.readthedocs.io/
    en/latest/development/

    View Slide

  147. class SimplifyNotEmptyWithAny < Cop
    MSG = 'Use `.any?` and remove the negation part.'.freeze
    def_node_matcher :not_empty_call?, <(send (send (...) :empty?) :!)
    PATTERN
    def on_send(node)
    return unless not_empty_call?(node)
    add_offense(node)
    end
    end

    View Slide

  148. RuboCop has a rich
    ecosystem of
    extensions

    View Slide

  149. View Slide

  150. View Slide

  151. 189!!!

    View Slide

  152. Notable Extensions
    • rubocop-rspec
    • guard-rubocop
    • rubocop-sequel
    • rubocop-cask

    View Slide

  153. Integration with code
    quality services
    •Code Climate
    •HoundCI
    •SideCI
    •Codacy

    View Slide

  154. RuboCop has
    integrations with
    many editors and IDEs

    View Slide

  155. Emacs 26.1 is out
    and it’s amazing!

    View Slide

  156. (spacemacs)

    View Slide

  157. The Road to
    RuboCop 1.0

    View Slide

  158. #1 question I get
    asked about RuboCop?

    View Slide

  159. When is RuboCop 1.0
    coming?

    View Slide

  160. Remove Rails
    (and maybe Performance)
    cops from RuboCop’s core

    View Slide

  161. Provide a proper (and
    stable) API for writing
    RuboCop extensions

    View Slide

  162. Introducing the brand
    new RuboCop HQ!

    View Slide

  163. https://github.com/bbatsov/rubocop

    View Slide

  164. https://github.com/rubocop-hq/rubocop

    View Slide

  165. RuboCop HQ
    •RuboCop
    •RuboCop JP
    •RuboCop RSpec/Rails/Performance
    •guard-rubocop, etc
    •Ruby & Rails Style Guides
    •Libraries extracted from RuboCop (e.g. node
    extensions, node pattern matching, etc)

    View Slide

  166. Review the config of all
    cops and come up with
    the best defaults

    View Slide

  167. Ideally each default
    should be backed by
    solid data/rationale

    View Slide

  168. Adjust the list of
    enabled by default cops

    View Slide

  169. Come up with a less painful
    process for dealing with new
    cops and configuration changes

    View Slide

  170. mry makes RuboCop
    upgrades less painful
    https://github.com/pocke/mry

    View Slide

  171. Agree on what’s going to
    be constituting breaking
    changes down the road

    View Slide

  172. Breaking changes
    •Cop API changes
    •Extension API changes
    •Dropping support for Ruby versions
    •Renaming/removing cops
    •Renaming/changing/removing config values

    View Slide

  173. Cop status “New” in
    minor releases

    View Slide

  174. Cop status gets changed
    to Enabled/Disabled in
    major releases

    View Slide

  175. Extra Cop metadata
    •Version added
    •Version default config changed
    •Version deprecated

    View Slide

  176. eslint.org-grade
    manual/website

    View Slide

  177. RuboCop.org
    •Easy to navigate & mobile-friendly
    •Published using MkDocs (or similar)
    •Separate User and Developer manual
    •Getter getting started resources (e.g. video tutorials)
    •Improved cop documentation

    View Slide

  178. View Slide

  179. That’s a lot of work!

    View Slide

  180. Apes Together Strong!

    View Slide

  181. Rubyists Together Strong!

    View Slide

  182. Felina

    View Slide

  183. Thanks!
    twitter: @bbatsov
    github: @bbatsov
    http//batsov.com
    http://emacsredux.com
    RubyKaigi
    Sendai,
    Japan
    31.05.2018

    View Slide