Slide 1

Slide 1 text

4 U B U J D  U Z Q P  D I F D L F S

Slide 2

Slide 2 text

!ZVLJ $PNNJUFS .BJOUBJOFSPGUIFkaminariHFN $SFBUPSPGUIFdid_you_meanHFN : 6 , *  / * 4 ) * + * . "

Slide 3

Slide 3 text

8 I B U  E P F T  U I F  E J E @ Z P V @ N F B O  H F N  E P

Slide 4

Slide 4 text

"Yuki".starts_with?("Y") # => NoMethodError: undefined method `starts_with?’ for …

Slide 5

Slide 5 text

"Yuki".starts_with?("Y") # => NoMethodError: undefined method `starts_with?’ for … # Did you mean? start_with? >= 2.3

Slide 6

Slide 6 text

3 F D F O U  D I B O H F T  J O  U I F  d i d _ y o u _ m e a n  H F N

Slide 7

Slide 7 text

%: .        ❤  3 V C Z       w %:.BMQIBDPNQBUJCMFXJUIEFW w %:.DPNQBUJCMFXJUIBOEMBUFS w %:.DPNQBUJCMFXJUIBOEMBUFS w %:.GPSBOZFBSMJFSWFSTJPOTPG3VCZ

Slide 8

Slide 8 text

4 V H H F T U J P O T  P O  / B N F & S S P S  G S P N  S t r u c t # [ ] Struct.new(:name).new[:naem] # => NameError: no member 'naem' in struct # Did you mean? name Struct.new(:name).new[:naem] # => NameError: no member 'naem' in struct Ruby 2.4 and did_you_mean 1.1.2: Ruby 2.3 and did_you_mean 1.0.0:

Slide 9

Slide 9 text

$ B M M F E  O B N F  J T  F Y D M V E F E  G S P N  T V H H F T U J P O T name; name = 1 # => NameError: undefined local variable or method `name' for … # Did you mean? name name; name = 1 # => NameError: undefined local variable or method `name' for … Ruby 2.4 and did_you_mean 1.1.2: Ruby 2.3 and did_you_mean 1.0.0:

Slide 10

Slide 10 text

- F T T  T V H H F T U J P O T  P O  / P . F U I P E & S S P S  G S P N  n i l @users.map {|user| ... } # => NameError: undefined method `map' for nil:NilClass # Did you mean? tap @users.map {|user| ... } # => NameError: undefined method `map' for nil:NilClass Ruby 2.4 and did_you_mean 1.1.2: Ruby 2.3 and did_you_mean 1.0.0:

Slide 11

Slide 11 text

1 S J W B U F  N F U I P E T  B S F  O P  M P O H F S  T V H H F T U F E  X I F O  O P U  D B M M B C M F File.raed 'path/to/file.csv' # => NoMethodError: undefined method `raed' for File:Class # Did you mean? read # rand File.raed 'path/to/file.csv' # => NoMethodError: undefined method `raed' for File:Class # Did you mean? read Ruby 2.4 and did_you_mean 1.1.2: Ruby 2.3 and did_you_mean 1.0.0:

Slide 12

Slide 12 text

& Y Q F S J N F O U B M  G F B U V S F T

Slide 13

Slide 13 text

$ gem i did_you_mean -v=1.1.2 require 'did_you_mean/experimental'

Slide 14

Slide 14 text

@full_name = "Yuki Nishijima" @full_anme.split(" ") # => NoMethodError: undefined method `split' for nil:NilClass 5ZQP

Slide 15

Slide 15 text

require 'did_you_mean/experimental' @full_name = "Yuki Nishijima" @full_anme.split(" ") # => NoMethodError: undefined method `split' for nil:NilClass # Did you mean? @full_name

Slide 16

Slide 16 text

hash = {"name" => "Yuki"} hash.fetch("anme") # KeyError: key not found: "anme" 5ZQP

Slide 17

Slide 17 text

require 'did_you_mean/experimental' hash = {"name" => "Yuki"} hash.fetch("anme") # KeyError: key not found: "anme" # Did you mean? "name"

Slide 18

Slide 18 text

class User def iniialize(name, ...) ... end end User.new("Yuki Nishijima", ...) # => ArgumentError: wrong number of arguments … 5ZQP

Slide 19

Slide 19 text

require 'did_you_mean/experimental' class User def iniialize(name, ...) ... end end # => warning: iniialize might be misspelled, perhaps you meant initialize? User.new("Yuki Nishijima", ...) # => ArgumentError: wrong number of arguments …

Slide 20

Slide 20 text

4 Q F M M  D I F D L F S  B T  B  Q V C M J D  " 1 * checker = DidYouMean::SpellChecker.new(dictionary: methods) checker.correct(‘sedn') # => [‘send']

Slide 21

Slide 21 text

+ 3 V C Z  T V Q Q P S U $ irb > RUBY_ENGINE # => "jruby" > RUBY_DESCRIPTION # => "jruby 9.1.3.0 (2.3.1) 2016-08-29 a2a3b29 Java HotSpot(TM) 64-Bit Server VM 25.121-b13 on 1.8.0_121-b13 +jit [darwin-x86_64]” > "Yuki".starts_with?("Y") # => NoMethodError: undefined method `starts_with?' for … Did you mean? start_with?

Slide 22

Slide 22 text

1 S P C M F N T  J O  U I F  d i d _ y o u _ m e a n  H F N

Slide 23

Slide 23 text

3 V O U J N F  M P P L V Q

Slide 24

Slide 24 text

- P B E J O H  :P V S  " Q Q M J D B U J P O w 3VCZDPSF .3* +3VCZ 3VCJOJVT FUD  w 3VCZHFNT OPUHFNT  w :PVSDPEF w %FQFOEFODJFT TUEMJC HFNT  w :PVSBQQ`TDMBTTFTNPEVMFT w .FUBQSPHSBNNJOH #define_method #eval  w 0UIFSXPSLJGOFDFTTBSZ FHMJTUFOJOHUPBQPSU

Slide 25

Slide 25 text

3 V O O J O H  :P V S  " Q Q M J D B U J P O w 8FCTFSWFSSFDFJWFTBSFRVFTU w 3FRVFTUJTQBTTFEJOUPrack w 3FRVFTUHPFTUISPVHISBDLNJEEMFXBSF w 3FRVFTUIJUTUIF3BJMTDPOUSPMMFS w $POUSPMMFSHFOFSBUFTBSFTQPOTF w %# )551DBMMT DBDIJOH MPHHJOH )5.- FUD w 4FOEFWFSZUIJOHCBDLUPUIFDMJFOU

Slide 26

Slide 26 text

w 8FCTFSWFSSFDFJWFTBSFRVFTU w 3FRVFTUJTQBTTFEJOUPrack w 3FRVFTUHPFTUISPVHIrack middleware w 3FRVFTUIJUTUIFRailsDPOUSPMMFS w $POUSPMMFSHFOFSBUFTBSFTQPOTF w %# )551DBMMT DBDIJOH MPHHJOH )5.- FUD w 4FOEFWFSZUIJOHCBDLUPUIFDMJFOU w 3VCZDPSF .3* +3VCZ 3VCJOJVT FUD  w 3VCZHFNT OPUHFNT  w :PVSDPEF w %FQFOEFODJFT TUEMJC HFNT  w :PVSDMBTTFTNPEVMFT w .FUBQSPHSBNNJOH #define_method #eval  w 0UIFSXPSLJGOFDFTTBSZ FHMJTUFOJOHUPBQPSU

Slide 27

Slide 27 text

w 3VCZDPSF .3* +3VCZ 3VCJOJVT FUD  w 3VCZHFNT OPUHFNT  w :PVSDPEF w %FQFOEFODJFT TUEMJC HFNT  w :PVSDMBTTFTNPEVMFT w .FUBQSPHSBNNJOH #define_method #eval  w 0UIFSXPSLJGOFDFTTBSZ FHMJTUFOJOHUPBQPSU w 8FCTFSWFSSFDFJWFTBSFRVFTU w 3FRVFTUJTQBTTFEJOUPrack w 3FRVFTUHPFTUISPVHIrack middleware w 3FRVFTUIJUTUIFRailsDPOUSPMMFS w $POUSPMMFSHFOFSBUFTBSFTQPOTF w %# )551DBMMT DBDIJOH MPHHJOH )5.- FUD w NoMethodErrorPDDVST w did_you_meanTVHHFTUTBNFUIPEOBNF

Slide 28

Slide 28 text

4 U B U J D  B O B M Z T J T  U P  U I F  S F T D V F

Slide 29

Slide 29 text

d i d _ y o u _ m e a n / s t a t i c require 'did_you_mean/undefined_method_detector' PROJECT_DIR = Dir.pwd at_exit { UndefinedMethodDetector.new(PROJECT_DIR) .undefined_methods .each { |method| method.called_by.each { |caller| puts "`#{method.name}' seems undefined but is called at " \ "#{caller.source_location[0]}:#{method.lineno}" } } }

Slide 30

Slide 30 text

d i d _ y o u _ m e a n / s t a t i c require 'did_you_mean/undefined_method_detector' PROJECT_DIR = Dir.pwd at_exit { UndefinedMethodDetector.new(PROJECT_DIR) .undefined_methods .each { |method| method.called_by.each { |caller| puts "`#{method.name}' seems undefined but is called at " \ "#{caller.source_location[0]}:#{method.lineno}" } } }

Slide 31

Slide 31 text

require 'did_you_mean/undefined_method_detector' class Foo def bar i_do_not_exist end end UndefinedMethodDetector.new(Dir.pwd).undefined_methods.first.name # => ‘i_do_not_exist’

Slide 32

Slide 32 text

1B S T F S

Slide 33

Slide 33 text

1B S T F S w Ripper w UIFQBSTFSHFN w RubyVM::InstructionSequence CZUFDPEF 

Slide 34

Slide 34 text

w Ripper w UIFQBSTFSHFN w RubyVM::InstructionSequence CZUFDPEF  1B S T F S

Slide 35

Slide 35 text

class Foo def bar i_do_not_exist end end

Slide 36

Slide 36 text

require ‘pp' pp RubyVM::InstructionSequence.compile(<<-CODE).to_a[13] class Foo def bar i_do_not_exist end end CODE # => bytecode

Slide 37

Slide 37 text

require ‘pp' pp RubyVM::InstructionSequence.compile(<<-CODE).to_a[13] class Foo def bar i_do_not_exist end end CODE # => bytecode [1, [:trace, 1], [:putspecialobject, 3], [:putnil], [:defineclass, :Foo, ["YARVInstructionSequence/SimpleDataFormat", 2, 3, 1, {:arg_size=>0, :local_size=>0, :stack_max=>3}, "", "", nil, 1, :class, [], {}, [], [1, [:trace, 2], 2, [:trace, 1], [:putspecialobject, 1], [:putobject, :bar], [:putiseq, ["YARVInstructionSequence/SimpleDataFormat", 2, 3, 1, {:arg_size=>0, :local_size=>0, :stack_max=>1}, "bar", "", nil, 2, :method, [], {}, [], [2, [:trace, 8], 3, [:trace, 1], [:putself], [:opt_send_without_block, {:mid=>:i_do_not_exist, :flag=>28, :orig_argc=>0}, false], 4, [:trace, 16], 3, [:leave]]]], [:opt_send_without_block, {:mid=>:"core#define_method", :flag=>16, :orig_argc=>2}, false], 5, [:trace, 4], 2, [:leave]]], 0], [:leave]]

Slide 38

Slide 38 text

class Foo def bar i_do_not_exist end end [:defineclass, :Foo, […,

Slide 39

Slide 39 text

class Foo def bar i_do_not_exist end end [:defineclass, :Foo, […, […, [:putobject, :bar], […,

Slide 40

Slide 40 text

class Foo def bar i_do_not_exist end end [:defineclass, :Foo, […, […, [:putobject, :bar], […, …, …, [:opt_send_without_block, {:mid=>:i_do_not_exist, :flag=>28, :orig_argc=>0}, false], …,]]]]

Slide 41

Slide 41 text

class Foo def bar i_do_not_exist end end [:defineclass, :Foo, […, […, [:putobject, :bar], […, …, …, [:opt_send_without_block, {:mid=>:i_do_not_exist, :flag=>28, :orig_argc=>0}, false], …,]]]]

Slide 42

Slide 42 text

$ B M M B C M F  N F U I P E T • Foo.instance_methods • Foo.private_instance_methods

Slide 43

Slide 43 text

$ B M M B C M F  N F U I P E T • Foo.instance_methods • Foo.private_instance_methods l*TO`UUIBU3VCZDPEF z"

Slide 44

Slide 44 text

require 'did_you_mean/undefined_method_detector' class Foo def bar i_do_not_exist end end UndefinedMethodDetector.new(Dir.pwd).undefined_methods.first.name # => ‘i_do_not_exist’

Slide 45

Slide 45 text

require 'did_you_mean/undefined_method_detector' class Foo def bar i_do_not_exist end end UndefinedMethodDetector.new(Dir.pwd).undefined_methods.first.name # => ‘i_do_not_exist’ # won’t be executed

Slide 46

Slide 46 text

1 S P C M F N T w /PUFOUJSFMZTUBUJD w 0OMZTLJQTFYFDVUJOHJOTUBODFNFUIPET w $MBTTFTBOENPEVMFTOFFEUPCFMPBEFE w ,FSOFMNFUIPETDBOCFDBMMBCMF #eval #exit FUD 

Slide 47

Slide 47 text

& E J U P S  J O U F H S B U J P O

Slide 48

Slide 48 text

VSCode fires a command on file save (JavaScript) returns JSON through stdout

Slide 49

Slide 49 text

" D U J W B U F  7 4 $ P E F  Q M V H J O export function activate(context: ExtensionContext) { … const window = vscode.window const subscription = window.onDidChangeActiveTextEditor(executeSpellChecking) context.subscriptions.push(subscription) }

Slide 50

Slide 50 text

" D U J W B U F  7 4 $ P E F  Q M V H J O export function activate(context: ExtensionContext) { … const window = vscode.window const subscription = window.onDidChangeActiveTextEditor(executeSpellChecking) context.subscriptions.push(subscription) }

Slide 51

Slide 51 text

function executeSpellChecking( editor: vscode.TextEditor | vscode.TextDocumentChangeEvent) { if (!editor || editor.document.isDirty) return const projectDir = vscode.workspace.rootPath const fileToCheck = editor.document.uri.path … cp.exec( `ruby -rdid_you_mean/static -Ilib ${fileToCheck}`, { cwd: projectDir }, (err, stdout, stderr) => { const result = JSON.parse(stdout) // => result from DYM // Annotate undefined methods using VSCode API }) }

Slide 52

Slide 52 text

function executeSpellChecking( editor: vscode.TextEditor | vscode.TextDocumentChangeEvent) { if (!editor || editor.document.isDirty) return const projectDir = vscode.workspace.rootPath const fileToCheck = editor.document.uri.path … cp.exec( `ruby -rdid_you_mean/static -Ilib ${fileToCheck}`, { cwd: projectDir }, (err, stdout, stderr) => { const result = JSON.parse(stdout) // => result from DYM // Annotate undefined methods using VSCode API }) }

Slide 53

Slide 53 text

function executeSpellChecking( editor: vscode.TextEditor | vscode.TextDocumentChangeEvent) { if (!editor || editor.document.isDirty) return const projectDir = vscode.workspace.rootPath const fileToCheck = editor.document.uri.path … cp.exec( `ruby -rdid_you_mean/static -Ilib ${fileToCheck}`, { cwd: projectDir }, (err, stdout, stderr) => { const result = JSON.parse(stdout) // => result from DYM // Annotate undefined methods using VSCode API }) }

Slide 54

Slide 54 text

{ "ruby_engine": "ruby", "ruby_version": "2.5.0", "ruby_description": "ruby 2.5.0dev (2017-06-28 trunk 59186) …”, "did_you_mean_version": "1.2.0-alpha", "undefined_names": { "9": [ { "undefined_name": "raiae", "symbol_type": "method", "path": "/did_you_mean/test/spell_checking/method_name_test.rb", "lineno": 9, "suggestions": [ "raise" ] } ], … }

Slide 55

Slide 55 text

require 'did_you_mean/undefined_method_detector' PROJECT_DIR = Dir.pwd at_exit { UndefinedMethodDetector.new(PROJECT_DIR) .undefined_methods .each { |method| method.called_by.each { |caller| puts "`#{method.name}' seems undefined but is called at " \ "#{caller.source_location[0]}:#{method.lineno}" } } }

Slide 56

Slide 56 text

require 'did_you_mean/undefined_method_detector' PROJECT_DIR = Dir.pwd at_exit { UndefinedMethodDetector.new(PROJECT_DIR) .undefined_methods .each { |method| method.called_by.each { |caller| puts "`#{method.name}' seems undefined but is called at " \ "#{caller.source_location[0]}:#{method.lineno}" } } } the #puts call => JSON.dump

Slide 57

Slide 57 text

$ V S S F O U  T U B U F w /PUFOUJSFMZTUBUJD w 5PPNBOZGBMTFQPTJUJWFT w /PUGBTUFOPVHIGPSMBSHFQSPKFDUT w 3BJMT

Slide 58

Slide 58 text

4 X J U D I J O H  U P  T U B U J D  B O B M Z T J T w 5FDIOJDBMMZQPTTJCMF w NBZNBLFJUJOBDDVSBUF

Slide 59

Slide 59 text

8 I Z  N B O Z  G B M T F  Q P T J U J W F T w %ZOBNJDBMMZEFpOFENFUIPET w $BMMTIBOEMFECZ#method_missing w %VDLUZQJOH

Slide 60

Slide 60 text

Not defined within the gem Found in the doc

Slide 61

Slide 61 text

Static typo checker YARD filter Editor filters out method names found in RDoc

Slide 62

Slide 62 text

8 I Z  T M P X w 5PPNBOZEFQFOEFODJFTUPMPBE w 'JOEJOHTVHHFTUJPOTJTTMPX

Slide 63

Slide 63 text

{ "ruby_engine": "ruby", "ruby_version": "2.5.0", "ruby_description": "ruby 2.5.0dev (2017-06-28 trunk 59186) …”, "did_you_mean_version": "1.2.0-alpha", "undefined_names": { "9": [ { "undefined_name": "raiae", "symbol_type": "method", "path": "/did_you_mean/test/spell_checking/method_name_test.rb", "lineno": 9, "suggestions": [ "raise" ] } ], … } Suggestion lookup is time-consuming, this may be skipped depending on the project size

Slide 64

Slide 64 text

3BJMT needs significant amount of work aside from the rest of the work

Slide 65

Slide 65 text

$ P O D M V T J P O w 4UJMMJOFBSMZTUBHF CVUXPSLTXFMMGPSTNBMMQSPKFDUT w 5ZQFTZTUFNXJMMIFMQ CVUTFFNTUPPBNCJUJPVTBUUIJT QPJOU w 4UBUJDBOBMZTJT PS"45 JTFYUSFNFMZQPXFSGVM

Slide 66

Slide 66 text

5IBOLZPV