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. Божидар

    View full-size slide

  2. Bozho is not a bozo!

    View full-size slide

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

    View full-size slide

  4. (The RuboCop Guy)

    View full-size slide

  5. Sofia, Bulgaria
    Sofia, Bulgaria

    View full-size slide

  6. Grigor Dimitrov vs Kei Nishikori

    View full-size slide

  7. Bulgarian Cheese

    View full-size slide

  8. Rakia
    Connecting People

    View full-size slide

  9. Shopska Salad
    (use only with Rakia)

    View full-size slide

  10. Ruby & Rails
    style guides

    View full-size slide

  11. First time in Japan!!!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. RuboCop in a
    Nutshell

    View full-size slide

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

    View full-size slide

  18. In other words…

    View full-size slide

  19. It keeps your
    codebase consistent

    View full-size slide

  20. It saves you time

    View full-size slide

  21. It advances the Ruby
    language forward

    View full-size slide

  22. Provides an efficient
    way for codebases to be
    updated

    View full-size slide

  23. Lint tools are not a
    replacement for
    common sense

    View full-size slide

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

    View full-size slide

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

    • Masataka Kuwabara (@pocke)

    • Koichi Ito (@koic)

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. 314 open issues

    View full-size slide

  29. 100 volunteers

    View full-size slide

  30. 3.14 issues/person

    View full-size slide

  31. Coincidence?

    View full-size slide

  32. Or providence?

    View full-size slide

  33. A Brief History of Time

    View full-size slide

  34. A Brief History of
    RuboCop

    View full-size slide

  35. 2011
    The Ruby Style Guide

    View full-size slide

  36. May, 2012
    RuboCop 0.0.0

    View full-size slide

  37. May, 2012
    RuboCop 0.0.0

    View full-size slide

  38. Static code analysis
    with regular
    expressions …

    View full-size slide

  39. Nov, 2012
    Along Came Jonas

    View full-size slide

  40. Spring, 2013
    Return of the Jedi

    View full-size slide

  41. Using a proper
    parser!

    View full-size slide

  42. Struggles with Ripper

    View full-size slide

  43. Ripper supported
    only MRI

    View full-size slide

  44. I wanted to build a
    cross-platform tool

    View full-size slide

  45. Ripper had (has?)
    almost no
    documentation

    View full-size slide

  46. Ripper is tied to the
    underlying Ruby
    runtime

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  50. 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 full-size slide

  51. And many quirks…

    View full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. Which no one really
    considered
    problematic…

    View full-size slide

  55. Parser: A New Hope

    View full-size slide

  56. parser is
    cross-platform

    View full-size slide

  57. well documented

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  61. AST node source
    location mappings

    View full-size slide

  62. 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 full-size slide

  63. $ 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 full-size slide

  64. powerful code
    rewriting capabilities

    View full-size slide

  65. Struggles with Parser

    View full-size slide

  66. Parser was brand
    new

    View full-size slide

  67. Had no real users

    View full-size slide

  68. And had plenty of
    bugs…

    View full-size slide

  69. Peter Zotov
    (@whitequark)

    View full-size slide

  70. 28th May, 2013
    State of Unity

    View full-size slide

  71. 1st July, 2013
    RuboCop 0.9

    View full-size slide

  72. Auto-correct

    View full-size slide

  73. Commissioner
    (single parsing run triggers all cops)

    View full-size slide

  74. Results caching
    (rubocop —-cache)

    View full-size slide

  75. Parallel Checks
    (rubocop —-parallel)

    View full-size slide

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

    View full-size slide

  77. 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 full-size slide

  78. 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 full-size slide

  79. RuboCop Today

    View full-size slide

  80. Started out with no
    configuration at all…

    View full-size slide

  81. Today things are
    infinitely configurable

    View full-size slide

  82. Metrics/LineLength:
    Enabled: false

    View full-size slide

  83. Metrics/LineLength:
    Max: 100

    View full-size slide

  84. Style/StringLiterals:
    Enabled: false

    View full-size slide

  85. Style/StringLiterals:
    EnforcedStyle: double_quotes

    View full-size slide

  86. You can set a target
    Ruby/Rails version

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  89. You can inherit
    between configuration
    files

    View full-size slide

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

    View full-size slide

  91. rubocop —-auto-gen-config

    View full-size slide

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

    View full-size slide

  93. rubocop.readthedocs.io

    View full-size slide

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

    View full-size slide

  95. RuboCop started as a
    code style checker

    View full-size slide

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

    View full-size slide

  97. RuboCop is a great
    code formatter

    View full-size slide

  98. rubocop —-only Layout -a

    View full-size slide

  99. RuboCop does some
    linting better than
    ruby -w

    View full-size slide

  100. rubocop —-lint

    View full-size slide

  101. You can extend
    RuboCop with your
    own cops

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  106. RuboCop has a rich
    ecosystem of
    extensions

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  109. RuboCop has
    integrations with
    many editors and IDEs

    View full-size slide

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

    View full-size slide

  111. The Road to
    RuboCop 1.0

    View full-size slide

  112. #1 question I get
    asked about RuboCop?

    View full-size slide

  113. When is RuboCop 1.0
    coming?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  116. Introducing the brand
    new RuboCop HQ!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  119. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  122. Adjust the list of
    enabled by default cops

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  127. Cop status “New” in
    minor releases

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  130. eslint.org-grade
    manual/website

    View full-size slide

  131. 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 full-size slide

  132. That’s a lot of work!

    View full-size slide

  133. Apes Together Strong!

    View full-size slide

  134. Rubyists Together Strong!

    View full-size slide

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

    View full-size slide