Slide 1

Slide 1 text

Prettier for Ruby github.com/prettier/plugin-ruby twitter.com/kddeisz

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

github.com/prettier/plugin-ruby twitter.com/kddeisz What is prettier?

Slide 4

Slide 4 text

github.com/prettier/plugin-ruby twitter.com/kddeisz What is prettier? • A JavaScript package that provides a language-agnostic framework for building formatters

Slide 5

Slide 5 text

github.com/prettier/plugin-ruby twitter.com/kddeisz What is prettier? • A JavaScript package that provides a language-agnostic framework for building formatters • A set of language-specific parsers that build a prettier- specific intermediate representation (IR)

Slide 6

Slide 6 text

github.com/prettier/plugin-ruby twitter.com/kddeisz What is prettier? • A JavaScript package that provides a language-agnostic framework for building formatters • A set of language-specific parsers that build a prettier- specific intermediate representation (IR) • A printer for printing out prettier IR

Slide 7

Slide 7 text

github.com/prettier/plugin-ruby twitter.com/kddeisz What is prettier? • A JavaScript package that provides a language-agnostic framework for building formatters • A set of language-specific parsers that build a prettier- specific intermediate representation (IR) • A printer for printing out prettier IR • A suite of editor tools that enable formatting on save

Slide 8

Slide 8 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Language support

Slide 9

Slide 9 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Language support • JavaScript (JSX, Flow, TypeScript, JSON)

Slide 10

Slide 10 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Language support • JavaScript (JSX, Flow, TypeScript, JSON) • HTML (Vue, Angular)

Slide 11

Slide 11 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Language support • JavaScript (JSX, Flow, TypeScript, JSON) • HTML (Vue, Angular) • CSS (Less, SCSS, styled-components, styled-jsx)

Slide 12

Slide 12 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Language support • JavaScript (JSX, Flow, TypeScript, JSON) • HTML (Vue, Angular) • CSS (Less, SCSS, styled-components, styled-jsx) • Markdown (MDX, YAML)

Slide 13

Slide 13 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Plugin support

Slide 14

Slide 14 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Plugin support • Java (community) • PHP (official) • PostgreSQL (community) • Ruby (official) • SVG/XML (official) • Swift (official)

Slide 15

Slide 15 text

github.com/prettier/plugin-ruby twitter.com/kddeisz prettier/plugin-ruby • A ruby gem and a node module • ~20K downloads/week • Active development

Slide 16

Slide 16 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Formatting • Convert source Ruby to concrete syntax tree (CST) • Attach comments to the CST • Walk the CST to convert to the prettier IR • Walk the prettier IR to print out the formatted result

Slide 17

Slide 17 text

github.com/prettier/plugin-ruby twitter.com/kddeisz d=[30644250780,9003106878, 30636278846,66641217692,4501790980, 671_24_603036,131_61973916,66_606629_920, 30642677916,30643069058];a,s=[],$*[0] s.each_byte{|b|a<<("%036b"%d[b. chr.to_i]).scan(/\d{6}/)} a.transpose.each{ |a| a.join.each_byte{\ |i|print i==49?\ ($*[1]||"#")\ :32.chr} puts }

Slide 18

Slide 18 text

github.com/prettier/plugin-ruby twitter.com/kddeisz d = [ 30_644_250_780, 9_003_106_878, 30_636_278_846, 66_641_217_692, 4_501_790_980, 671_24_603036, 131_61973916, 66_606629_920, 30_642_677_916, 30_643_069_058 ] a, s = [], $*[0] s.each_byte { |b| a << ('%036b' % d[b.chr.to_i]).scan(/\d{6}/) } a.transpose.each do |a| a.join.each_byte { |i| print i == 49 ? ($*[1] || '#') : 32.chr } puts end

Slide 19

Slide 19 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Ruby source to CST • Spawns a Ruby process that parses using ripper • Track extra metadata as we parse through the source • Maintain the list of comments and their source location

Slide 20

Slide 20 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { spawnSync } = require("child_process"); const path = require("path"); function parse(text, _parsers, _opts) { const child = spawnSync( "ruby", ["--disable-gems", path.join(__dirname, "./parser.rb")], { input: text, maxBuffer: 10 * 1024 * 1024 } ); const error = child.stderr.toString(); if (error) { throw new Error(error); } const response = child.stdout.toString(); return JSON.parse(response); }; module.exports = parse;

Slide 21

Slide 21 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper'

Slide 22

Slide 22 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper end

Slide 23

Slide 23 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper def on_binary(left, oper, right) end end

Slide 24

Slide 24 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper def on_binary(left, oper, right) pp [left, oper, right] end end

Slide 25

Slide 25 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper def on_binary(left, oper, right) pp [left, oper, right] end end Parser.new('1 + 2').parse

Slide 26

Slide 26 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper def on_binary(left, oper, right) pp [left, oper, right] end end Parser.new('1 + 2').parse # => ["1", :+, "2"]

Slide 27

Slide 27 text

github.com/prettier/plugin-ruby twitter.com/kddeisz require 'ripper' class Parser < Ripper SCANNER_EVENTS.each do |event| define_method(:"on_#{event}") do |body| { type: :"@#{event}", body: body } end end PARSER_EVENTS.each do |event| define_method(:"on_#{event}") do |*body| { type: event, body: body } end end end

Slide 28

Slide 28 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Parser.prepend( Module.new do private events = %i[ args mlhs mrhs qsymbols qwords regexp stmts string symbols words xstring ] events.each do |event| suffix = event == :string ? 'content' : 'new' define_method(:"on_#{event}_#{suffix}") do { type: event, body: [] } end define_method(:"on_#{event}_add") do |parts, part| parts.tap { |node| node[:body] << part } end end end )

Slide 29

Slide 29 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Parser.prepend( Module.new do private def on_massign(left, right) super.tap do next unless left[:type] == :mlhs range = left[:char_start]..left[:char_end] left[:comma] = source[range].strip.end_with?(',') end end end )

Slide 30

Slide 30 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Parser.prepend( Module.new do def initialize(*args) super(*args) @comments = [] end private def on_comment(value) @comments << { value: value[1..-1].chomp, char_start: char_pos, char_end: char_pos + value.length - 1 } end def on_program(*body) super(*body).merge!(comments: @comments) end end )

Slide 31

Slide 31 text

github.com/prettier/plugin-ruby twitter.com/kddeisz if $0 == __FILE__ builder = Parser.new($stdin.read) response = builder.parse if !response || builder.error? warn(error_message) exit 1 end puts JSON.fast_generate(response) end

Slide 32

Slide 32 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Attach comments to CST • For each comment, walk through the tree and attach to the closest node in the source location • Provide special guidelines for comments based on their • preceding node • following node • enclosing node

Slide 33

Slide 33 text

github.com/prettier/plugin-ruby twitter.com/kddeisz module.exports = { printers: { ruby: { print, handleComments: comments, canAttachComment: (node) => { return !["args_add_block", "args"].includes(node.type); }, getCommentChildNodes: (node) => { if (node.type === "undef") { return node.body[0]; } return node.body; }, printComment: (path, _opts) => { const { value } = path.getValue(); return `#${value}`; } } } };

Slide 34

Slide 34 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { addTrailingComment } = require("./prettier"); const endOfLine = (comment) => { const { enclosingNode } = comment; if (!enclosingNode) { return false; } if ( enclosingNode.type === "assign" && enclosingNode.body[0].type === "aref_field" ) { addTrailingComment(enclosingNode, comment); return true; } return false; };

Slide 35

Slide 35 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Convert CST to prettier IR • Uses prettier doc “builders” as primitives • Builds “group” nodes that can be broken when the maximum line length has been hit • Provides line suffixes for comments

Slide 36

Slide 36 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 37

Slide 37 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 38

Slide 38 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 39

Slide 39 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 40

Slide 40 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 41

Slide 41 text

github.com/prettier/plugin-ruby twitter.com/kddeisz const { concat, group, indent, line } = require("../prettier"); function opassign(path, opts, print) { const [variable, operator, value] = path.map(print, "body"); return group( concat([ variable, " ", operator, indent(concat([line, value])) ]) ); } module.exports = opassign;

Slide 42

Slide 42 text

github.com/prettier/plugin-ruby twitter.com/kddeisz $

Slide 43

Slide 43 text

github.com/prettier/plugin-ruby twitter.com/kddeisz $ bin/doc "foo ||= bar"

Slide 44

Slide 44 text

github.com/prettier/plugin-ruby twitter.com/kddeisz $ bin/doc "foo ||= bar" [ group([ "foo", " ", "||=", indent([ line, "bar" ]) ]), hardline, breakParent ]

Slide 45

Slide 45 text

github.com/prettier/plugin-ruby twitter.com/kddeisz $ bin/doc "foo ||= bar" [ group([ "foo", " ", "||=", indent([ line, "bar" ]) ]), hardline, breakParent ] group(concat([ variable, " ", operator, indent(concat([ line, value ])) ]));

Slide 46

Slide 46 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Prettier IR to source • Walk the prettier IR and print nodes as you go • At each node, call the correct print function for that node from the necessary plugin/printer • If you hit the line limit, break the outermost group

Slide 47

Slide 47 text

github.com/prettier/plugin-ruby twitter.com/kddeisz doubled_values ||= values.map { |value| value * 2 }

Slide 48

Slide 48 text

github.com/prettier/plugin-ruby twitter.com/kddeisz doubled_values ||= values.map { |value| value * 2 }

Slide 49

Slide 49 text

github.com/prettier/plugin-ruby twitter.com/kddeisz doubled_values ||= values.map do |value| value * 2 end

Slide 50

Slide 50 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Ruby choices • For the most part, consistent with rubocop • Run on the same input it will generate the same output • It should never change the meaning of your program

Slide 51

Slide 51 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Ruby choices • break, next, yield, return don’t use parentheses
 (super will sometimes get parentheses) • no nested ternaries • decimal numbers will get underscores after 3 digits
 octal numbers will have an “o” added if it’s not there • {} lambdas for single line, do…end for multi-line • … and many more!

Slide 52

Slide 52 text

github.com/prettier/plugin-ruby twitter.com/kddeisz alias with bare words class Foo alias :foo :bar alias bar baz end

Slide 53

Slide 53 text

github.com/prettier/plugin-ruby twitter.com/kddeisz class Foo alias foo bar alias bar baz end alias with bare words

Slide 54

Slide 54 text

github.com/prettier/plugin-ruby twitter.com/kddeisz array literals %w[alpha beta gamma delta epsilon] %i[alpha beta gamma delta epsilon] ['alpha', 'beta', 'gamma', 'delta', 'epsilon'] [:alpha, :beta, :gamma, :delta, :epsilon]

Slide 55

Slide 55 text

github.com/prettier/plugin-ruby twitter.com/kddeisz %w[alpha beta gamma delta epsilon] %i[alpha beta gamma delta epsilon] %w[alpha beta gamma delta epsilon] %i[alpha beta gamma delta epsilon] array literals

Slide 56

Slide 56 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Symbol#to_proc [1, 2, 3, 4, 5].map do |item| item.to_s end

Slide 57

Slide 57 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Symbol#to_proc [1, 2, 3, 4, 5].map(&:to_s)

Slide 58

Slide 58 text

github.com/prettier/plugin-ruby twitter.com/kddeisz do…end and {} blocks [1, 2, 3, 4, 5].each do |item| item.to_s(:format) end

Slide 59

Slide 59 text

github.com/prettier/plugin-ruby twitter.com/kddeisz do…end and {} blocks [1, 2, 3, 4, 5].each { |item| item.to_s(:format) }

Slide 60

Slide 60 text

github.com/prettier/plugin-ruby twitter.com/kddeisz ternaries foo = if bar 1 else 2 end

Slide 61

Slide 61 text

github.com/prettier/plugin-ruby twitter.com/kddeisz ternaries foo = bar ? 1 : 2

Slide 62

Slide 62 text

github.com/prettier/plugin-ruby twitter.com/kddeisz rescues foo rescue nil

Slide 63

Slide 63 text

github.com/prettier/plugin-ruby twitter.com/kddeisz rescues begin foo rescue StandardError nil end

Slide 64

Slide 64 text

github.com/prettier/plugin-ruby twitter.com/kddeisz strings 'foo' "foo" 'foo\n' "foo\n" 'foo #{bar} baz' "foo #{bar} baz" 'foo \M-\C-a' "foo \M-\C-a" "#@foo" "#@@foo" "#$foo" ?f

Slide 65

Slide 65 text

github.com/prettier/plugin-ruby twitter.com/kddeisz strings 'foo' 'foo' 'foo\n' "foo\n" 'foo #{bar} baz' "foo #{bar} baz" 'foo \M-\C-a' "foo \M-\C-a" "#{@foo}" "#{@@foo}" "#{$foo}" 'f'

Slide 66

Slide 66 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Prettier options • Increase adoption • No longer done • Great demand from the community • Time to entrench • Compatibility reasons

Slide 67

Slide 67 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Ruby options • --add-trailing-commas = false • --inline-conditionals = true • --inline-loops = true • --prefer-hash-labels = true • --prefer-single-quotes = true • --to-proc-transform = true

Slide 68

Slide 68 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Embedded parsers

Slide 69

Slide 69 text

github.com/prettier/plugin-ruby twitter.com/kddeisz • Embed Ruby code within other languages Embedded parsers

Slide 70

Slide 70 text

github.com/prettier/plugin-ruby twitter.com/kddeisz • Embed Ruby code within other languages • Embed other languages within Ruby code Embedded parsers

Slide 71

Slide 71 text

github.com/prettier/plugin-ruby twitter.com/kddeisz introduction = <<-MARKDOWN # Prettier ` @prettier/plugin-ruby ` is a [prettier]( https://prettier.io/ ) plugin for the Ruby programming language and its ecosystem. ` prettier ` is an opinionated code formatter that supports multiple languages and integrates with most editors. The idea is to eliminate discussions of style in code review and allow developers to get back to thinking about code design instead. MARKDOWN

Slide 72

Slide 72 text

github.com/prettier/plugin-ruby twitter.com/kddeisz introduction = <<-MARKDOWN # Prettier `@prettier/plugin-ruby` is a [prettier](https://prettier.io/) plugin for the Ruby programming language and its ecosystem. `prettier` is an opinionated code formatter that supports multiple languages and integrates with most editors. The idea is to eliminate discussions of style in code review and allow developers to get back to thinking about code design instead. MARKDOWN

Slide 73

Slide 73 text

github.com/prettier/plugin-ruby twitter.com/kddeisz # Prettier ```ruby d=[30644250780,9003106878, 30636278846,66641217692,4501790980, 671_24_603036,131_61973916,66_606629_920, 30642677916,30643069058];a,s=[],$*[0] s.each_byte{|b|a<<("%036b"%d[b. chr.to_i]).scan(/\d{6}/)} a.transpose.each{ |a| a.join.each_byte{\ |i|print i==49?\ ($*[1]||"#")\ :32.chr} puts } ```

Slide 74

Slide 74 text

github.com/prettier/plugin-ruby twitter.com/kddeisz # Prettier ```ruby d = [ 30_644_250_780, 9_003_106_878, 30_636_278_846, 66_641_217_692, 4_501_790_980, 671_24_603036, 131_61973916, 66_606629_920, 30_642_677_916, 30_643_069_058 ] a, s = [], $*[0] s.each_byte { |b| a << ('%036b' % d[b.chr.to_i]).scan(/\d{6}/) } a.transpose.each do |a| a.join.each_byte { |i| print i == 49 ? ($*[1] || '#') : 32.chr } puts end ```

Slide 75

Slide 75 text

github.com/prettier/plugin-ruby twitter.com/kddeisz HAML support • Support for formatting the HAML template language • Embedded formatting support for filters

Slide 76

Slide 76 text

github.com/prettier/plugin-ruby twitter.com/kddeisz :ruby (0...(comments>max ? max : comments)).each do |i| haml_io.puts li_comment(*data[ i ]) end

Slide 77

Slide 77 text

github.com/prettier/plugin-ruby twitter.com/kddeisz :ruby (0...(comments > max ? max : comments)).each do |i| haml_io.puts li_comment(*data[i]) end

Slide 78

Slide 78 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work

Slide 79

Slide 79 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed!

Slide 80

Slide 80 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed! • https://github.com/prettier/plugin-ruby/pull/512

Slide 81

Slide 81 text

github.com/prettier/plugin-ruby twitter.com/kddeisz #include #include namespace parser { Local Translate(Isolate *isolate, Local context, VALUE value) { switch (TYPE(value)) { case T_SYMBOL: return String::NewFromUtf8( isolate, rb_id2name(SYM2ID(value)), NewStringType::kNormal ).ToLocalChecked(); case T_FALSE: return False(isolate); case T_NIL: return Null(isolate); case T_FIXNUM: return Integer::New(isolate, FIX2LONG(value)); ... } return Null(isolate); } ... NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) }

Slide 82

Slide 82 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed! • https://github.com/prettier/plugin-ruby/pull/512

Slide 83

Slide 83 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed! • https://github.com/prettier/plugin-ruby/pull/512 • https://github.com/kddeisz/libdoc

Slide 84

Slide 84 text

github.com/prettier/plugin-ruby twitter.com/kddeisz /* Allocates and instantiates a new GROUP node. * * @param {doc_node_t*} child - the child doc node that this node * now owns * @returns {doc_node_t*} - a newly allocated node that will * require freeing */ doc_node_t* doc_group(doc_node_t* child) { return doc_node_make(GROUP, 1, child, NULL, NULL); } /* Allocates and instantiates a new HARD_LINE node. * * @returns {doc_node_t*} - a newly allocated node that will * require freeing */ doc_node_t* doc_hard_line() { return doc_node_make(HARD_LINE, 0, NULL, NULL, NULL); }

Slide 85

Slide 85 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed! • https://github.com/prettier/plugin-ruby/pull/512 • https://github.com/kddeisz/libdoc

Slide 86

Slide 86 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Future work • Speed! • https://github.com/prettier/plugin-ruby/pull/512 • https://github.com/kddeisz/libdoc • Better support for newer features like pattern matching

Slide 87

Slide 87 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Get involved!

Slide 88

Slide 88 text

github.com/prettier/plugin-ruby twitter.com/kddeisz Try it today!

Slide 89

Slide 89 text

Prettier for Ruby github.com/prettier/plugin-ruby twitter.com/kddeisz