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

Static typo checker

Static typo checker

Yuki Nishijima

September 19, 2017
Tweet

More Decks by Yuki Nishijima

Other Decks in Programming

Transcript

  1. 4 U B U J D  U Z Q

    P  D I F D L F S
  2. 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
  3. 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
  4. %: .        ❤

     3 V C Z       w %:.BMQIBDPNQBUJCMFXJUIEFW w %:.DPNQBUJCMFXJUIBOEMBUFS w %:.DPNQBUJCMFXJUIBOEMBUFS w %:.GPSBOZFBSMJFSWFSTJPOTPG3VCZ
  5. 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:
  6. $ 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:
  7. - 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:
  8. 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:
  9. & Y Q F S J N F O U

    B M  G F B U V S F T
  10. require 'did_you_mean/experimental' @full_name = "Yuki Nishijima" @full_anme.split(" ") # =>

    NoMethodError: undefined method `split' for nil:NilClass # Did you mean? @full_name
  11. class User def iniialize(name, ...) ... end end User.new("Yuki Nishijima",

    ...) # => ArgumentError: wrong number of arguments … 5ZQP
  12. 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 …
  13. 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']
  14. + 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?
  15. 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
  16. - 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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}" } } }
  22. 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}" } } }
  23. 1B S T F S w Ripper w UIFQBSTFSHFN w

    RubyVM::InstructionSequence CZUFDPEF 
  24. 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}, "<class:Foo>", "<compiled>", 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", "<compiled>", 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]]
  25. 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], …,]]]]
  26. 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], …,]]]]
  27. $ B M M B C M F  N

    F U I P E T • Foo.instance_methods • Foo.private_instance_methods
  28. $ 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"
  29. 1 S P C M F N T w /PUFOUJSFMZTUBUJD

    w 0OMZTLJQTFYFDVUJOHJOTUBODFNFUIPET w $MBTTFTBOENPEVMFTOFFEUPCFMPBEFE w ,FSOFMNFUIPETDBOCFDBMMBCMF #eval #exit FUD 
  30. & E J U P S  J O U

    F H S B U J P O
  31. " 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) }
  32. " 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) }
  33. 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 }) }
  34. 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 }) }
  35. 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 }) }
  36. { "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" ] } ], … }
  37. 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}" } } }
  38. 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
  39. $ V S S F O U  T U

    B U F w /PUFOUJSFMZTUBUJD w 5PPNBOZGBMTFQPTJUJWFT w /PUGBTUFOPVHIGPSMBSHFQSPKFDUT w 3BJMT
  40. 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
  41. 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
  42. 8 I Z  T M P X w 5PPNBOZEFQFOEFODJFTUPMPBE

    w 'JOEJOHTVHHFTUJPOTJTTMPX
  43. { "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
  44. $ P O D M V T J P O

    w 4UJMMJOFBSMZTUBHF CVUXPSLTXFMMGPSTNBMMQSPKFDUT w 5ZQFTZTUFNXJMMIFMQ CVUTFFNTUPPBNCJUJPVTBUUIJT QPJOU w 4UBUJDBOBMZTJT PS"45 JTFYUSFNFMZQPXFSGVM