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

How GitHub uses linters

Joel Hawksley
October 28, 2021
40

How GitHub uses linters

The GitHub code base is growing at over 25% every year through contributions from over 1000 engineers, clocking in at 1.7+ million lines of Ruby. In this talk, we'll share how we use linters to keep our codebase healthy by ensuring best practices are applied consistently, feedback loops are as short as possible, and code reviews bring the most value, all without creating too much friction.

Joel Hawksley

October 28, 2021
Tweet

Transcript

  1. How GitHub uses linters @joelhawksley linter (n.) a static code

    analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs.
  2. How GitHub uses linters @joelhawksley - id: html-comment title: HTML

    Comments pattern: - <!-- description: "This HTML comment will be rendered into the DOM at runtime, potentially [exposing sensitive information] (http://cwe.mitre.org/data/definitions/615.html) about the design/implementation of the application. Please consider removing it or converting it to an ERB comment instead, since ERB comments are stripped out when the template is compiled and have no effect at runtime."
  3. How GitHub uses linters @joelhawksley - id: stylelint-rule-change title: Stylelint

    rule change description: 'Stylelint rule changes should be reviewed by @github/primer-reviewers' pattern: - (\/\/|\/\*)\s*stylelint\-disable request_review: - '@github/primer-reviewers'
  4. How GitHub uses linters @joelhawksley - id: stylelint-rule-change title: Stylelint

    rule change description: 'Stylelint rule changes should be reviewed by @github/primer-reviewers' pattern: - (\/\/|\/\*)\s*stylelint\-disable request_review: - '@github/primer-reviewers'
  5. How GitHub uses linters @joelhawksley def test_no_violations matches = grep(

    /(whitelist|blacklist)/, paths: PATHS ).lines assert_empty(matches) end
  6. How GitHub uses linters @joelhawksley Branch A Branch B Branch

    C Branch D COUNTER = 28 COUNTER = 39 COUNTER = 41 COUNTER = 42
  7. How GitHub uses linters @joelhawksley <% foo %> s(:document, s(:text,

    s(:erb, nil, nil, s(:code, " foo "), nil)))
  8. How GitHub uses linters @joelhawksley <% foo %> s(:document, s(:text,

    s(:erb, nil, nil, s(:code, " foo "), nil)))
  9. How GitHub uses linters @joelhawksley <img … alt=“Image of person

    jumping in the air” /> “Image: Image of person jumping in the air”
  10. How GitHub uses linters @joelhawksley class A11yNoRedundantImageAlt < Linter MESSAGE

    = "<img> alt attr should not contain `image` or `picture` as screen readers already announce the element as an image” REDUNDANT_ALT_WORDS = %w(image picture) end
  11. How GitHub uses linters @joelhawksley class A11yNoRedundantImageAlt < Linter MESSAGE

    = "<img> alt attr should not contain `image` or `picture` as screen readers already announce the element as an image” REDUNDANT_ALT_WORDS = %w(image picture) def run(tag) next if tag.name != "img" if (tag.alt.downcase.split & REDUNDANT_ALT_WORDS).any? generate_offense(tag) end end end
  12. How GitHub uses linters @joelhawksley class A11yNoRedundantImageAlt < Linter MESSAGE

    = "<img> alt attr should not contain `image` or `picture` as screen readers already announce the element as an image” REDUNDANT_ALT_WORDS = %w(image picture) def run(tag) next if tag.name != "img" if (tag.alt.downcase.split & REDUNDANT_ALT_WORDS).any? generate_offense(tag) end end end
  13. How GitHub uses linters @joelhawksley class DoNotBranchOnGitHub MSG = "Don't

    branch on GitHub, code paths should not differ between test and production.”.freeze def_node_search :github_env, <<~PATTERN (send (const nil? :GitHub) ${:test? :production?}) PATTERN def on_if(node) if github_env(node.condition) add_offense(node.condition) end end end
  14. How GitHub uses linters @joelhawksley $ require 'parser/current' $ p

    Parser::CurrentRuby.parse("GitHub.production?") $ => s(:send, s(:const, nil, :GitHub), :production?)
  15. How GitHub uses linters @joelhawksley class DoNotBranchOnGitHub MSG = "Don't

    branch on GitHub, code paths should not differ between test and production.”.freeze def_node_search :github_env, <<~PATTERN (send (const nil? :GitHub) ${:test? :production?}) PATTERN def on_if(node) if github_env(node.condition) add_offense(node.condition) end end end
  16. How GitHub uses linters @joelhawksley class DoNotBranchOnGitHub MSG = "Don't

    branch on GitHub, code paths should not differ between test and production.”.freeze def_node_search :github_env, <<~PATTERN (send (const nil? :GitHub) ${:test? :production?}) PATTERN def on_if(node) if github_env(node.condition) add_offense(node.condition) end end end
  17. How GitHub uses linters @joelhawksley class DoNotBranchOnGitHub MSG = "Don't

    branch on GitHub, code paths should not differ between test and production.”.freeze def_node_search :github_env, <<~PATTERN (send (const nil? :GitHub) ${:test? :production?}) PATTERN def on_if(node) binding.irb if github_env(node.condition) add_offense(node.condition) end end end
  18. How GitHub uses linters @joelhawksley $ node => s(:if, s(:send,

    s(:const, nil, :GitHub), :test?), s(:send, nil, :do_the_fake_thing), s(:send, nil, :do_the_real_thing)) $ node.condition => s(:send, s(:const, nil, :GitHub), :test?) $ node.condition.receiver.short_name => :GitHub $ node.condition.method_name => :test?
  19. How GitHub uses linters @joelhawksley $ node => s(:if, s(:send,

    s(:const, nil, :GitHub), :test?), s(:send, nil, :do_the_fake_thing), s(:send, nil, :do_the_real_thing)) $ node.condition => s(:send, s(:const, nil, :GitHub), :test?) $ node.condition.receiver.short_name => :GitHub $ node.condition.method_name => :test?
  20. How GitHub uses linters @joelhawksley $ node => s(:if, s(:send,

    s(:const, nil, :GitHub), :test?), s(:send, nil, :do_the_fake_thing), s(:send, nil, :do_the_real_thing)) $ node.condition => s(:send, s(:const, nil, :GitHub), :test?) $ node.condition.receiver.short_name => :GitHub $ node.condition.method_name => :test?
  21. How GitHub uses linters @joelhawksley $ node => s(:if, s(:send,

    s(:const, nil, :GitHub), :test?), s(:send, nil, :do_the_fake_thing), s(:send, nil, :do_the_real_thing)) $ node.condition => s(:send, s(:const, nil, :GitHub), :test?) $ node.condition.receiver.short_name => :GitHub $ node.condition.method_name => :test?
  22. How GitHub uses linters @joelhawksley def on_if(node) return unless node.condition.receiver.short_name

    == :GitHub return unless( [:test?, :production?]. include?(node.condition.method_name) ) end
  23. How GitHub uses linters @joelhawksley def on_if(node) return unless node.condition.receiver.short_name

    == :GitHub return unless( [:test?, :production?]. include?(node.condition.method_name) ) add_offense(node.condition) end
  24. How GitHub uses linters @joelhawksley def on_if(node) return unless node.condition.receiver.short_name

    == :GitHub return unless( [:test?, :production?]. include?(node.condition.method_name) ) add_offense(node.condition) end
  25. How GitHub uses linters @joelhawksley # lib/parser/source/tree_rewriter.rb # Replaces the

    the source range `range` with `content`. # # @param [Range] range # @param [String] content def replace(range, content) combine(range, replacement: content) end
  26. How GitHub uses linters @joelhawksley $ node $ => s(:send,

    nil, :assert_equal, s(:nil), s(:send, nil, :value)) $ node.arguments.last.source $ => "value" $ node.source_range $ => #<Parser::Source::Range (string) 0...23>
  27. How GitHub uses linters @joelhawksley $ node $ => s(:send,

    nil, :assert_equal, s(:nil), s(:send, nil, :value)) $ node.arguments.last.source $ => "value" $ node.source_range $ => #<Parser::Source::Range (string) 0…23>
  28. How GitHub uses linters @joelhawksley $ node $ => s(:send,

    nil, :assert_equal, s(:nil), s(:send, nil, :value)) $ node.arguments.last.source $ => "value" $ node.source_range $ => #<Parser::Source::Range (string) 0…23>
  29. How GitHub uses linters @joelhawksley def autocorrect(node) lambda do |corrector|

    corrector.replace( node.source_range, # new_source ) end end
  30. How GitHub uses linters @joelhawksley def autocorrect(node) lambda do |corrector|

    corrector.replace( node.source_range, # new_source ) end end
  31. How GitHub uses linters @joelhawksley def autocorrect(node) lambda do |corrector|

    corrector.replace( node.source_range, # new_source ) end end
  32. How GitHub uses linters @joelhawksley def autocorrect(node) lambda do |corrector|

    corrector.replace( node.source_range, # new_source ) end end
  33. How GitHub uses linters @joelhawksley def autocorrect(node) lambda do |corrector|

    corrector.replace( node.source_range, "assert_nil #{node.arguments.last.source}” ) end end