All About RuboCop (RubyKaigi 2018)

All About RuboCop (RubyKaigi 2018)

Slide deck from my talk at RubyKaigi 2018 in Sendai.

1be785d1d788b82929e55fc83a9f0aaa?s=128

Bozhidar Batsov

May 31, 2018
Tweet

Transcript

  1. Hello!

  2. Божидар

  3. None
  4. Bozhidar

  5. Божo

  6. Bozho cool

  7. Bozo not cool

  8. Bozho is not a bozo!

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

  10. Bug cool

  11. (The RuboCop Guy)

  12. Sofia, Bulgaria Sofia, Bulgaria

  13. None
  14. Grigor Dimitrov vs Kei Nishikori

  15. None
  16. Lukanka

  17. Bulgarian Cheese

  18. Rakia Connecting People

  19. Shopska Salad (use only with Rakia)

  20. None
  21. bbatsov

  22. Ruby & Rails style guides

  23. None
  24. None
  25. None
  26. None
  27. First time in Japan!!!

  28. None
  29. None
  30. None
  31. None
  32. None
  33. None
  34. All About RuboCop by Bozhidar Batsov (a.k.a. bug)

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

  36. Improve Ruby coding style rules and Lint by Koichi Ito

    http://rubykaigi.org/2018/presentations/koic.html#jun01
  37. Something About RuboCop by Bozhidar Batsov (a.k.a. bug)

  38. RuboCop in a Nutshell

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

    Ruby Community Style Guide
  40. In other words…

  41. It keeps your codebase consistent

  42. It saves you time

  43. It advances the Ruby language forward

  44. None
  45. 32,608,128

  46. Provides an efficient way for codebases to be updated

  47. Disclaimer

  48. Lint tools are not a replacement for common sense

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

  50. None
  51. None
  52. Notable Japanese Contributors • Yuji Nakayama (@yujinakayama) • Masataka Kuwabara

    (@pocke) • Koichi Ito (@koic) • Yukihiro "Matz" Matsumoto (@matz) ;-)
  53. None
  54. https://github.com/rubocop-jp/issues

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

  56. 314 open issues

  57. 100 volunteers

  58. 3.14 issues/person

  59. Coincidence?

  60. Or providence?

  61. A Brief History of Time

  62. A Brief History of RuboCop

  63. 2011 The Ruby Style Guide

  64. None
  65. May, 2012 RuboCop 0.0.0

  66. May, 2012 RuboCop 0.0.0

  67. None
  68. Static code analysis with regular expressions …

  69. None
  70. Nov, 2012 Along Came Jonas

  71. None
  72. None
  73. Spring, 2013 Return of the Jedi

  74. Using a proper parser!

  75. None
  76. Struggles with Ripper

  77. Ripper supported only MRI

  78. I wanted to build a cross-platform tool

  79. Ripper had (has?) almost no documentation

  80. Ripper is tied to the underlying Ruby runtime

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

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

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

    "some", [1, 7]]]], [:symbol_literal, [:symbol, [:@ident, "test", [1, 13]]]]]]]
  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
  85. And many quirks…

  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"]]
  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, ")"]]
  88. Which no one really considered problematic…

  89. None
  90. None
  91. Parser: A New Hope

  92. parser is cross-platform

  93. well documented

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

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

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

    # (int 2))
  98. AST node source location mappings

  99. p Parser::CurrentRuby.parse("2 + 2").loc # #<Parser::Source::Map::Send:0x007fe5a1ac2388 # @dot=nil, # @begin=nil,

    # @end=nil, # @selector=#<Source::Range (string) 2...3>, # @expression=#<Source::Range (string) 0...5>> p Parser::CurrentRuby.parse("2 + 2").loc.selector.source # "+"
  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
  101. powerful code rewriting capabilities

  102. None
  103. Struggles with Parser

  104. Parser was brand new

  105. Had no real users

  106. And had plenty of bugs…

  107. None
  108. Peter Zotov (@whitequark)

  109. 28th May, 2013 State of Unity

  110. RuboCop 0.8

  111. 1st July, 2013 RuboCop 0.9

  112. Formatters

  113. Auto-correct

  114. None
  115. Commissioner (single parsing run triggers all cops)

  116. Results caching (rubocop —-cache)

  117. Parallel Checks (rubocop —-parallel)

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

  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)
  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
  121. RuboCop Today

  122. Started out with no configuration at all…

  123. Today things are infinitely configurable

  124. Metrics/LineLength: Enabled: false

  125. Metrics/LineLength: Max: 100

  126. Style/StringLiterals: Enabled: false

  127. Style/StringLiterals: EnforcedStyle: double_quotes

  128. You can set a target Ruby/Rails version

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

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

    different directories
  131. You can inherit between configuration files

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

  133. rubocop —-auto-gen-config

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

    manual…
  135. rubocop.readthedocs.io

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

  137. RuboCop started as a code style checker

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

    •Gemspec
  139. RuboCop is a great code formatter

  140. rubocop —-only Layout -a

  141. RuboCop does some linting better than ruby -w

  142. rubocop —-lint

  143. You can extend RuboCop with your own cops

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

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

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

  147. 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
  148. RuboCop has a rich ecosystem of extensions

  149. None
  150. None
  151. 189!!!

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

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

  154. RuboCop has integrations with many editors and IDEs

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

  156. (spacemacs)

  157. The Road to RuboCop 1.0

  158. #1 question I get asked about RuboCop?

  159. When is RuboCop 1.0 coming?

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

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

  162. Introducing the brand new RuboCop HQ!

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

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

  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)
  166. Review the config of all cops and come up with

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

  168. Adjust the list of enabled by default cops

  169. Come up with a less painful process for dealing with

    new cops and configuration changes
  170. mry makes RuboCop upgrades less painful https://github.com/pocke/mry

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

    the road
  172. Breaking changes •Cop API changes •Extension API changes •Dropping support

    for Ruby versions •Renaming/removing cops •Renaming/changing/removing config values
  173. Cop status “New” in minor releases

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

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

    deprecated
  176. eslint.org-grade manual/website

  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
  178. None
  179. That’s a lot of work!

  180. Apes Together Strong!

  181. Rubyists Together Strong!

  182. Felina

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

    31.05.2018