Share
Embed
RuboCop: LSP and Prism
RubyKaigi 2024
https://rubykaigi.org/2024/presentations/koic.html#day2
Koichi ITO
May 16, 2024
Transcript
100 slides
-
3VCP$PQ-41BOE1SJTN ,PJDIJ*50&4. *OD 3VCZ,BJHJ Towards Smarter and Faster .BZ UI
/")"$6-563"-"355)&"5&3/")"SU -
!LPJD w 044QSPHSBNNFS w 3VCP$PQDPSFUFBN w &OHJOFFSJOH.BOBHFSBOE %JTUJOHVJTIFE&OHJOFFSPG&4. *OD w
3VCZ,BJHJTQFBLFSBU -5 5BLFPVU 5BLFPVU BOE -
4 Q FBLFS 4QFBLFSTBOE"UUFOEFFT 4 Q FBLFS -5 , BSBPLF
!LPJD !IBSVHVDIJZVNB !KVOL !4)(".&-*/,4 !GVHBLLCO !NIJSBUB !TGKXS !UIBUB !LBTVNJQPO !XBJEPJ -
4QPOTPSFECZ&4. *OD BU 🚢 🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊 🌊🌊🌊🌊🌊 🐬 🐳 /JHIU$SVJTF🛳⛱🍺 5POEPDIP
/BIB 0LJOBXB 8&45."3*/& 5VF.BZBU%FQBSUVSF 4FFZPVOFYUUJNF Day 0 5IF&WFOUXBT40-%065 -
w !LPJD1BSTFSHFNDPNNJUUFS w !KVOL-SBNBDPNNJUUFS w !4)(".&-*/,4QBSTFZDPOUSJCVUPS w !GVHBLLCO3#4DPOUSJCVUPS w .FNCFST8BOUFE
1BSTFS$MVC IUUQTNBHB[JOFSVCZJTUOFUBSUJDMFT'PMMPX6Q,JSJTIJNB3VCZIUNM ߏจղੳثݚڀ෦ -
w *XBTOURVJUFTVSFXIBU-41XBT TP*XBTUIJOLJOHPGUSZJOHUPDSFBUFPOF w 5IF4UBOEBSEHFNTVQQPSUT-41 CZ!XJMM w "U3VCZ,BJHJ
UIFSFXBTBEJTDVTTJPO XJUI+VTUJO4FBSMTBCPVUUIFJEFBPGIBWJOH TPNFUIJOHTJNJMBSJO3VCP$PQ .PUJWBUJPO#BDLHSPVOE -
w 3VCZ TU w 3BJMT UI w 3VCP$PQ
OE w -41 UI )JTUPSZ -41JTB3FMBUJWFMZ/FX5FDIOPMPHZ -
-BOHVBHF4FSWFS1SPUPDPM &NBDT 7JN 74$PEF 4UFFQ 3VCZ-41 3VCP$PQ $MJFOU 4FSWFS 1SPUPDPM
JSON-RPC 1SPUPDPMTNFUIPETTVDIBTinitialize textDocument/formatting BOE workspace/executeCommandBSFBNPOHUIPTFEF fi OFEJOUIF-41 -
w 3VCZ-41 3VCZ-413BJMT Monolithic LSP w 3VCP$PQTCVJMUJO-41 w 4UBOEBSETCVJMUJO-41
w 4UFFQTCVJMUJO-41 w 5ZQF1SPGTCVJMUJO-41 3VCZT-41TBSF$PNQPTBCMF Micro LSP -
+40/31$ [jsonrpc] D[15:18:14.117] Running language server: bundle exec rubocop --lsp
[jsonrpc] e[15:18:14.118] --> initialize[1] {"jsonrpc":"2.0","id":1,"method":"initialize","params": {"processId":3303,"clientInfo":{"name":"Eglot","version":"1.17"},"rootPath":"/ Users/koic/src/github.com/rubocop/rubocop/","rootUri":"file:///Users/koic/src/ github.com/rubocop/rubocop","initializationOptions":{},"capabilities": {"workspace":{"applyEdit":true,"executeCommand": {"dynamicRegistration":false},"workspaceEdit": {"documentChanges":true},"didChangeWatchedFiles": {"dynamicRegistration":true},"symbol": {"dynamicRegistration":false},"configuration":true,"workspaceFolders":true},"te xtDocument":{"synchronization": {"dynamicRegistration":false,"willSave":true,"willSaveWaitUntil":true,"didSave" :true},"completion":{"dynamicRegistration":false,"completionItem": {"snippetSupport":false,"deprecatedSupport":true,"resolveSupport": {"properties":["documentation","details","additionalTextEdits"]},"tagSupport": {"valueSet":[1]}},"contextSupport":true},"hover": M-x eglot-events-buffer -
1MVHJO*OUFSBDUTXJUI-41 w -41DMJFOUJTJNQMFNFOUFEBT BQMVHJOGPSFBDIFEJUPS w -41TFSWFSTUBSUTBQSPDFTT GPSFBDIQSPKFDU w -41TFSWFSQSPDFTTSFRVFTUT BOESFTQPOETUPUIF-41
DMJFOU -414FSWFS Request and Response &HMPU -414FSWFS &NBDT Request and Response 1MVHJO &EJUPS*%& -
WTDPEFSVCPDPQ IUUQTHJUIVCDPNSVCPDPQWTDPEFSVCPDPQCMPCWTSDFYUFOTJPOUT-- w :PVDBOJOTUBMMUIJT74$PEF FYUFOTJPOGSPNUIF7JTVBM4UVEJP .BSLFUQMBDF w 'PS74$PEFCBTFE*%&TMJLF 74$PEJVNPS&DMJQTF5IFJB UIF
FYUFOTJPODBOCFJOTUBMMFEGSPNUIF 0QFO7493FHJTUSZ -
&YBNQMFTPG-41$MJFOU (require 'eglot) (add-to-list 'eglot-server-programs '(ruby-mode . ("bundle" "exec" "rubocop"
"--lsp"))) (add-hook 'ruby-mode-hook (lambda () (add-hook 'before-save-hook 'eglot-format-buffer nil 'local))) { "languageserver": { "rubocop": { "command": "bundle", "args" : ["exec", "rubocop", "--lsp"], "filetypes": ["ruby"], "rootPatterns": [".git", "Gemfile"], "requireRootPattern": true } }, "coc.preferences.formatOnSave": true } &NBDT FHMPU ~/.eamcs.d/init.el 7JNBOE/FPWJN DPDOWJN ~/.config/nvim/coc-settings.json -
-41JT5SJHHFSFECZ1MVHJO w -41DMJFOUJTJNQMFNFOUFEBT BQMVHJOGPSFBDIFEJUPS w -41TFSWFSTUBSUTBQSPDFTT GPSFBDIQSPKFDU w -41TFSWFSQSPDFTTSFRVFTUT BOESFTQPOETUPUIF-41
DMJFOU -414FSWFS Request and Response -414FSWFS Request and Response 1MVHJO &EJUPS*%& &HMPU &NBDT -
%FTJHOPG3VCP$PQT-41 Request (JSON-RPC) 3VCP$PQ 3VOOFS 3VCP$PQ-41 4FSWFS 3VCP$PQ-41 3PVUFT 3VCP$PQ-41
3VOUJNF Daemon 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp TUBSU SVO P ff FOTFT GPSNBU XSJUF DBMM SFRVFTU EFMFHBUF P ff FOTFT OFX@UFYU +40/ Response (JSON-RPC) 1MVHJO &EJUPS*%& -
Daemon rubocop --lsp4UBSUTB%BFNPO 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp 1MVHJO &EJUPS*%&
$ ps aux | grep rubocop user 7414 0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop IUUQTTQFBLFSEFDLDPNLPJDNBLFSVCPDPQTVQFSGBTU TMJEF -
w 50%0Ϋϥεਤʁ 3VCP$PQ$-*$PNNBOE-41 module RuboCop class CLI module Command #
Start Language Server Protocol of RuboCop. # @api private class LSP < Base self.command_name = :lsp def run RuboCop::LSP::Server.new(@config_store).start end end end $PEFTOJQQFU 3VOBTBEBFNPO $BMMFECZrubocop --lsp -
Daemon 4UBSUUIF-414FSWFS 3VCP$PQ-41 4FSWFS rubocop --lsp TUBSU 1MVHJO &EJUPS*%& $
ps aux | grep rubocop user 7414 0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop 3VCP$PQ$-* $PNNBOE -41 -
w 50%0Ϋϥεਤʁ 3VCP$PQ-414FSWFS LSP = LanguageServer::Protocol class RuboCop::LSP::Server def initialize(config_store)
RuboCop::LSP.enable @reader = ::LSP::Transport::Io::Reader.new($stdin) @writer = ::LSP::Transport::Io::Writer.new($stdout) @runtime = RuboCop::LSP::Runtime.new(config_store) @routes = Routes.new(self) end end $PEFTOJQQFU LSPJTTIPSUGPSLanguageServer::Protocol &OBCMF-41DPOUFYU -41DMJFOUJOUFSBDUTWJBstdinBOEstdout -
w 50%0Ϋϥεਤʁ 3VCP$PQ-414FSWFS class RuboCop::LSP::Server def start @reader.read do |request|
if !request.key?(:method) @routes.handle_method_missing(request) elsif (route = @routes.for(request[:method])) route.call(request) else @routes.handle_unsupported_method(request) end rescue StandardError => e # snip end $PEFTOJQQFU 1SPDFTTBSFRVFTUNFUIPE 8BJUGPSstdinJOQVUVTJOH-41*0SFBEFS 5IFrequestWBSJBCMFJTBIBTI DPOWFSUFEGSPN+40/31$ -
Daemon 3FRVFTU +40/31$ $ ps aux | grep rubocop user
7414 0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop Request (JSON-RPC) 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp TUBSU 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& -
Daemon 3PVUJOHCZ.FUIPE $ ps aux | grep rubocop user 7414
0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop Request (JSON-RPC) DBMM SFRVFTU 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp TUBSU 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT -
w 50%0Ϋϥεਤʁ 3VCP$PQ-413PVUFT class RuboCop::LSP::Routes handle 'textDocument/didChange' do |request| doc
= request[:params][:textDocument] result = diagnostic(doc[:uri], doc[:text]) @server.write(result) end handle 'textDocument/formatting' do |request| uri = request[:params][:textDocument][:uri] @server.write( id: request[:id], result: format_file(uri) ) end end 3FUSJFWFBGPSNBUUFEUFYUGSPN3VCP$PQ $PEFTOJQQFU (FUBSFTVMUPGEJBHOPTUJDGSPN3VCP$PQ -
Daemon 3VO3VCP$PQ $ ps aux | grep rubocop user 7414
0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop Request (JSON-RPC) 3VCP$PQ 3VOOFS SVO P ff FOTFT GPSNBU XSJUF DBMM SFRVFTU EFMFHBUF 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp TUBSU 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF -
w 50%0Ϋϥεਤʁ 3VCP$PQ-413VOUJNF class RuboCop::LSP::Runtime def offenses(path, text) opts =
{stdin: text, force_exclusion: true, formatters: ['json'], format: 'json'} json = redirect_stdout { run_rubocop(opts, path) } results = JSON.parse(json, symbolize_names: true) # snip end def run_rubocop(options, path) runner = RuboCop::Runner.new(options,@config_store) runner.run([path]) end $PEFTOJQQFU 3VOrubocopVTJOH3VOOFS"1* 3VOrubocop --stdin ... --format=json -
Daemon 3FTQPOTF +40/31$ $ ps aux | grep rubocop user
7414 0.0 0.2 57716 4376 ?? Ss 4:48PM 0:02.13 rubocop --lsp path/to/rubocop Request (JSON-RPC) 3VCP$PQ 3VOOFS SVO P ff FOTFT GPSNBU XSJUF DBMM SFRVFTU EFMFHBUF P ff FOTFT OFX@UFYU +40/ Response (JSON-RPC) 3VCP$PQ-41 3PVUFT 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp TUBSU 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3VOUJNF -
w 50%0Ϋϥεਤʁ 3VCP$PQ-413PVUFT class RuboCop::LSP::Routes handle 'textDocument/didChange' do |request| doc
= request[:params][:textDocument] result = diagnostic(doc[:uri], doc[:text]) @server.write(result) end handle 'textDocument/formatting' do |request| uri = request[:params][:textDocument][:uri] @server.write( id: request[:id], result: format_file(uri) ) end end $PEFTOJQQFU (FUBSFTVMUPGEJBHOPTUJDGSPN3VCP$PQ 3FUSJFWFBGPSNBUUFEUFYUGSPN3VCP$PQ 8SJUFSFTVMUUPstdoutVTJOH-41*08SJUFS 8SJUFBVUPDPSSFDUFEDPEFUPstdoutVTJOH-41*08SJUFS -
-41"DDFMFSBUFT%FW9 $* &EJUPS*%& $-* &EJUPS*%& $-* &EJUPS*%& $-* A /PSFBMUJNFGFFECBDLEVSJOHDPEJOH
switch switch switch switch context switch -
-41"DDFMFSBUFT%FW9 $* &EJUPS*%& $-* &EJUPS*%& $-* &EJUPS*%& $-* &EJUPS*%&XJUI-41 $-*
A B Smarter Faster BSFQSFTFOUTUIFEFWFMPQNFOU fl PXFOWJTJPOFEGPSUIFNBJOUPQJD &OTVSFGBTUGFFECBDLXJUIBEF fi OFEQSPUPDPM[Simple, Easy] /PSFBMUJNFGFFECBDLEVSJOHDPEJOH switch switch switch switch context switch context switch -
w %FWFMPQXJUINVMUJQMF-41DMJFOUTJOBXBZUIBUJT DIBSBDUFSJTUJDPG-41 w 3VCP$PQTCVJMUJO-41IBTCFFOEPHGPPEJOHXJUI &NBDT &HMPU BOETFFNTUPIBWFBTJHOJ fi DBOU
TIBSFDPNQBUJCMFXJUI74$PEF w "TFYQFDUFEGSPNB.JDSPTPGUTQFDJ fi DBUJPO UIFSFJT BCVOEBOUJNQMFNFOUBUJPOJOGPSNBUJPOGPS74$PEF *NQMFNFOUBUJPO5JQT -
w "VUPDPSSFDUXJUIFormat on Save w 6OMJLFSVOOJOHUFTUT BVUPDPSSFDUPO fi MFTBWF TFFNTHFOFSBMMZTBUJTGBDUPSZ
w &YFDVUFDPNNBOET*GVTJOH74$PEF SVOGSPN UIF$PNNBOE1BMFUUF 3FDPNNFOE"VUPDPSSFDUJO:PVS&EJUPS -
w 8IBUJTEF fi OFEJTNFSFMZUIFQSPUPDPM TQFDJ fi DBUJPO w 5IFEFTJHOPGMBOHVBHFTFSWFSJTMFGUUP UIFEJTDSFUJPOPGEFWFMPQFST
w %PFT/FXNFBOUIBUUIFSFBSFTUJMM TPNFSPVHIFEHFT 🤔 -41JTB3FMBUJWFMZ/FX5FDIOPMPHZ -
w 1BSUJBMMZXSJUUFOWBSJBCMFTBVUPDPSSFDU GSPNvariableUP_variable w &NQUZNFUIPETUVSOJOUPPOFMJOFSFOET w 34QFDTfitJTBVUPDPSSFDUFEUPit QSFWFOUJOHGPDVTPOBTQFDJ fi
DTQFD &YDFTTJWF"VUPDPSSFDUJO-41 &EJUPS*%& -
%J ff FSFODFTJOSFRVJSFNFOUTCFUXFFO DPNNBOEMJOFBOE-41NBZTPNFUJNFT OFFEUPCFIBOEMFEPOBDBTFCZDBTFCBTJT "1SBDUJDBM*TTVF 7BMJE4ZOUBY *OWBMJE4ZOUBY $PEFJT$PNQMFUFE "VUPDPSSFDUFE
/P"VUPDPSSFDUFE $PEFJT*ODPNQMFUFE 6OFYQFDUFEMZ"VUPDPSSFDUFE /P"VUPDPSSFDUFE 5IF/FX*TTVF -
4UBUFPG4PVSDF$PEF $PNQMFUFETPVSDFDPEF UZQJDBMMZDMFBSPGTZOUBYFSSPST DBOCFUSBEJUJPOBMMZQSPDFTTFE WJBrubocopDPNNBOE 7BMJE4ZOUBY4ZOUBYJTWBMJE CVUJUTTUJMMCFJOHXSJUUFO *OWBMJE4ZOUBY4ZOUBYJTJOWBMJEBOETUJMMCFJOHXSJUUFO
Editor Part CLI Part *UJTQSPCMFNBUJDJG DPEFJTBVUPDPSSFDUFE XIJMFJUJTCFJOHFEJUFE -
w *GJUTWJB-41 JUTBMXBZTTFFOBTTPVSDF DPEFCFJOHFEJUFE w *GJUTWJBDPNNBOE MJOF JUTTFFOBT TPVSDFDPEFXIFSF FEJUJOHJTDPNQMFUFE
%FDJEFCBTFEPO)PXUIF5PPMJT6TFE LSP CLI RuboCop::LSP.enabled? is true RuboCop::LSP.enabled? is false -
w AutoCorrect: always&OBCMFBVUPDPSSFDUJPOBMXBZT w AutoCorrect: disabled%JTBCMFBVUPDPSSFDUJPO w AutoCorrect: contextual <NEW>&OBCMF
BVUPDPSSFDUJPOXIFOMBVODIFEGSPNUIFrubocop DPNNBOE CVUJUJT/05BWBJMBCMFUISPVHI-41 5IF5ISFF7BMVFTPG"VUP$PSSFDU &OBCMF"VUP$PSSFDU %JTBCMF"VUP$PSSFDU $POUFYUVBM 3VCP$PQ USVF GBMTF 3VCP$PQ BMXBZTPSUSVF EJTBCMFEPSGBMTF DPOUFYVBM[NEW] -
"-41T/FYU4UFQ Prism supports Error Tolerance w "TBO-41EFWFMPQFS POF DPOTJEFSTIBOEMJOH"45GPS JODPNQMFUFDPEF
w $VSSFOUMZ VOQBSTBCMFDPEFJT SFHJTUFSFEBOP ff FOTFCZ Lint/SyntaxDPQ -
w *OUFSFTUFEJOXIBU&SSPS5PMFSBODFDPVME BDIJFWF CVUOPUTVQQPSUFECZUIF1BSTFS HFN w RuboCop runs 4 times
faster with Prism JOSVCPDPQSVCPDPQ w 'BTUBOEOPCPEZDPNQMBJOT .PUJWBUJPO#BDLHSPVOE -
The header of lib/prism.rb # The Prism Ruby parser. #
# "Parsing Ruby is suddenly manageable!" # - You, hopefully # module Prism 1BSTJOH3VCZJTTVEEFOMZNBOBHFBCMF w $SFBUFECZ,FWJO/FXUPO w )BOEXSJUUFO3FDVSTJWF%FTDFOU3VCZ1BSTFSJO$ w 1SPWJEF&SSPS5PMFSBODF 5SBOTMBUJPOMBZFS BOEPUIFST -
w :PVDBOVTF1SJTNBTUIFQBSTFSGPSUIF .3*QSPDFTTJOHXJUI--parser=prism 1BSTF7BSJPVT3VCJFTXJUI1SJTN $ ruby --parser=prism example.rb $ RUBYOPT='--parser=prism'
ruby example.rb w 3VCZ"1*TBSFQSPWJEFEUPQBSTFTPVSDF DPEFBOEIBOEMFQBSTJOHSFTVMUT Prism.parse('articles.map { it.title }') Prism.parse_file('example.rb') -
w 5XPNBJODBOEJEBUFTJO6OJWFSTBM1BSTFS -SBNB -3 BOE1SJTN )BOEDSBGU w 8IJDIXJMMCFUIF6OJWFSTBM1BSTFS
0OMZUJNFXJMMUFMM🕰 w &JUIFSXBZ JUTFYQFDUFEUPBEPQU UIF1SJTN"1* FH "45 IUUQTNBHB[JOFSVCZJTUOFUBSUJDMFT'PMMPX6Q,JSJTIJNB3VCZIUNM 5IF3VCZ1BSTFS$PNNVOJUZ -
1BSTFSHFNBOE1SJTNT"45T Parser gem Prism 4UBUFNFOUT/PEF 1SPHSBN/PEF $BMM/PEF "SHVNFOUT/PEF 4USJOH/PEF TFOE
TUS w 5IFTUSVDUVSF OBNF BUUSJCVUFT BOENFUIPETBSFBMM EJ ff FSFOU w "45VTFSTXJMMGBDF BNBTTJWFBNPVOUPG SFQMBDFNFOUXPSL puts 'hello' -
<<generate>> SVCZZ 5IJTDMBTTJOIFSJUJOHGSPN Racc::ParserDMBTT 1BSTFS#BTF *NQPSUQBSTFZBT3BDDQFS3VCZWFSTJPO 1BSTFS3VCZ (FOFSBUFQBSTFSDPEF CBTFEPOBTZOUBY fi
MF BDDPSEJOHUP-"-3 3VCP$PQ"45 1SPDFTTFE4PVSDF 1BSTFS3VCZ 1BSTFS3VCZ $ racc SVCZZ <<use>> <<create>> <<read>> <<read>> <<generate>> whitequark/parser -
1BSTFS#BTF 6TF1SJTNXJUIUIF1BSTFSHFNJOUFSGBDF 1SJTN5SBOTMBUJPO1BSTFS "IBOEXSJUUFO3VCZ QBSTFSJO$FYUFOTJPO "DMBTTUIBUTFSWFTBTBO "EBQUFSCFUXFFOUIF 1BSTFSHFNBOE1SJTN 1SJTN 3VCP$PQ"45
1SPDFTTFE4PVSDF 1SJTN5SBOTMBUJPO 1BSTFS <<use>> <<create>> <<use>> -
1BSTFS#BTF 1SJTN4VQQPSUT:version0QUJPO Prism.parseBMMPXTTQFDJGZJOHUIF3VCZ BOBMZTJTWFSTJPOUISPVHIUIFversionPQUJPO 1SJTN 3VCP$PQ"45 1SPDFTTFE4PVSDF 1SJTN5SBOTMBUJPO 1BSTFS <<use>>
<<create>> <<use>> Prism.parse(source, filepath: filepath, version: '3.3.1') -
1BSTFS#BTF *UTVQQPSUTNVMUJWFSTJPOT SVCZQSJTN 1SJTN5SBOTMBUJPO1BSTFS 5IFWBMVFCBTFEPO TargetRubyVersionJTQBTTFEUP UIF:versionPQUJPOPG1SJTN 1SJTN 3VCP$PQ"45 1SPDFTTFE4PVSDF
1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>> <<create>> <<use>> *UCSBODIFTJOUFSOBMMZ CBTFEPOUIFWFSTJPO -
w 4FUParserEngine: parser_prismBOE TargetRubyVersion as 3.3PSIJHIFS :PV$BO6TFUIF&YQFSJNFOUBM'FBUVSF AllCops: ParserEngine: parser_prism
TargetRubyVersion: 3.3 w "EEgem 'prism'UPZPVS(FN fi MF gem 'prism' -
⏱YJTFaster" $ cd path/to/ruby/prism $ bin/prism benchmark lib/prism.rb (snip) Comparison:
Prism: 6951.8 i/s Ripper::SexpBuilder: 2058.9 i/s - 3.38x slower Prism::Translation::RubyParser: 1573.8 i/s - 4.42x slower Prism::Translation::Parser: 767.7 i/s - 9.06x slower Prism::Translation::Ripper::SexpBuilder: 696.0 i/s - 9.99x slower RubyParser: 221.1 i/s - 31.44x slower Parser::CurrentRuby: 210.1 i/s - 33.09x slower w 1SJTNJT33xGBTUFSUIBO1BSTFS$VSSFOU3VCZ XIJUFRVBSL w 1SJTNJT9xGBTUFSUIBO5SBOTMBUJPO1BSTFS w 5SBOTMBUJPO1BSTFSJT3.65xGBTUFSUIBO1BSTFS$VSSFOU3VCZ -
w 3VCP$PQTTFSWFS NPEFCPPUVQUJNF XBTEJTDVTTFEBU 3VCZ,BJHJ 8IBUJTUIF3FBMJUZ IUUQTTQFBLFSEFDLDPNLPJDNBLFSVCPDPQTVQFSGBTU TMJEF w 'BTUFSBOBMZTJTUZQJDBMMZCFOF
fi UT FTQFDJBMMZ GPSTZTUFNTMJLF-41UIBUSFQFBUFEMZQBSTF -
&SSPS5PMFSBODFPG1SJTN $ ruby -rprism -e 'p Prism.parse("(42)").value' @ ProgramNode (location:
(1,0)-(1,4)) ├── locals: [] └── statements: @ StatementsNode (location: (1,0)-(1,4)) └── body: (length: 1) └── @ ParenthesesNode (location: (1,0)-(1,4)) ├── body: │ @ StatementsNode (location: (1,1)-(1,3)) │ └── body: (length: 1) │ └── @ IntegerNode (location: (1,1)-(1,3)) │ ├── flags: decimal │ └── value: 42 ├── opening_loc: (1,0)-(1,1) = "(" └── closing_loc: (1,3)-(1,4) = ")" $ ruby -rprism -e 'p Prism.parse("(42").value' @ ProgramNode (location: (1,0)-(1,3)) ├── locals: [] └── statements: @ StatementsNode (location: (1,0)-(1,3)) └── body: (length: 1) └── @ ParenthesesNode (location: (1,0)-(1,3)) ├── body: │ @ StatementsNode (location: (1,1)-(1,3)) │ └── body: (length: 2) │ ├── @ IntegerNode (location: (1,1)-(1,3)) │ │ ├── flags: decimal │ │ └── value: 42 │ └── @ MissingNode (location: (1,1)-(1,3)) ├── opening_loc: (1,0)-(1,1) = "(" └── closing_loc: (1,3)-(1,3) = "" Parse the valid syntax (42) Parse the invalid syntax (42 8IFOQBSTJOHJOWBMJETZOUBY /0&3303PDDVST BOEBMissingNodeJTJOTFSUFEJOTUFBE -
w 😀$PEFDPNQMFUJPO"OBMZ[JOHJOWBMJEDPEFJT FTTFOUJBMGPSUZQFCBTFEDPNQMFUJPO w 😔-JOUFS8IFOEFQSFDBUFENFUIPETJHOBUVSF JTEFUFSNJOFE UIFDPEFJTHFOFSBMMZWBMJE w 🤔'PSNBUUFS*TGPSNBUUJOHCFOF fi
DJBMEVSJOH XSJUJOH VODMFBS -41BOE&SSPS5PMFSBODF -
1BSTFS#BTF w 6TF1SJTNXJUIUIF1BSTFSHFNJOUFSGBDF 1SJTN5SBOTMBUJPO1BSTFS "IBOEXSJUUFO3VCZ QBSTFSJO$FYUFOTJPO "DMBTTUIBUTFSWFTBTBO "EBQUFSCFUXFFOUIF 1BSTFSHFNBOE1SJTN 1SJTN
3VC$PQ"45 1SPDFTTFE4PVSDF 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>> <<create>> <<use>> -
1BSTFS#BTF w 6TF1SJTNXJUIUIF1BSTFSHFNJOUFSGBDF 1SJTN5SBOTMBUJPO1BSTFS "IBOEXSJUUFO3VCZ QBSTFSJO$FYUFOTJPO "DMBTTUIBUTFSWFTBTBO "EBQUFSCFUXFFOUIF 1BSTFSHFNBOE1SJTN 1SJTN
3VC$PQ"45 1SPDFTTFE4PVSDF 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>> <<create>> <<use>> -
1BSTFS#BTF w 6TF1SJTNXJUIUIF1BSTFSHFNJOUFSGBDF 1SJTN "IBOEXSJUUFO3VCZ QBSTFSJO$FYUFOTJPO "DMBTTUIBUTFSWFTBTBO "EBQUFSCFUXFFOUIF 1BSTFSHFNBOE1SJTN 1SJTN
3VC$PQ"45 1SPDFTTFE4PVSDF 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>> <<create>> <<use>> -
<<generate>> QBSTFZ %FQFOEPO"CTUSBDUJPO BOE4UBCJMJUZ 1SJTN"1* 5SBOTMBUJPO-BZFS 3VCZQBSTFSHFOFSBUFECBTFEPOUIFQBSTFZGPSFBDI3VCZWFSTJPO 0S1SJTN"1*XJUI-SBNB (FOFSBUFQBSTFSDPEF
CBTFEPOBTZOUBY fi MF BDDPSEJOHUP14-3 3VCP$PQ"45 1SPDFTTFE4PVSDF "3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ "3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ $ lrama QBSTFZ <<use>> <<require>> <<read>> <<read>> <<generate>> ruby/ruby -
w .PSF'BTUFS w (BJO&SSPS5PMFSBODF w "MNPTUOPOFFEGPS1BSTFSHFNNBJOUFOBODF XPParser::Source::TreeRewriter w 5IFUniversal
ParserXJMMFOBCMFUIFBCJMJUZUP SVOUIFTBNFQBSTFSVOJWFSTBMMZJOUIFGVUVSF #FOF fi UTPG.JHSBUJOHUP7BOJMMB1SJTN -
w $PNQBUJCMFXJUI1SJTNT"45OPEFT -FYFS UPLFOT BOE%JBHOPTUJDT w .BLF/PEF1BUUFSOTNBDSPDPNQBUJCMFXJUI 1SJTNT"45OPEFT w &OBCMFNJHSBUJPOUISPVHIQBSBMMFMPQFSBUJPO
PGUIFTF 5IF3PBEJT-POHBOE$IBMMFOHJOH 4PNFGFBUVSFTBOE DPQT -
3VCP$PQT1BSTFS&OHJOFT 1BSTFSHFN✅ 1SJTN5SBOTMBUJPO1BSTFS✅ 1SJTN "1* ☑ 5IF/BNFPG 1BSTFS&OHJOF QBSTFS@XIJUFRVBSL QBSTFS@QSJTN
QSJTN 1BSTJOH4QFFE YdY Y 4VQQPSUGPS 1BSTJOH3VCZ 3VCZ 3VCZ 3VCZ &SSPS5PMFSBODF /FWFS4VQQPSUFE /PU4VQQPSUFE 4VQQPSUFE 6OJWFSTBM1BSTFS /P "NCJHVPVT :FT %FWFMPQNFOU 4UBUVT 4UBCMF &YQFSJNFOUBM $PODFQUVBM ✅4VQQPSUFE ☑6OTVQQPSUFE -
w (FO3FHFYQ w (FO3JQQFS w (FOXIJUFRVBSLQBSTFS w (FO1SJTN5SBOTMBUJPO1SJTN w (FO1SJTN"1*
TBD 1BSTFS(FOFSBUJPOT"HBJOTU3VCP$PQ IUUQTSVCZLBJHJPSHQSFTFOUBUJPOTCCBUTPWIUNMNBZ -
4XJUDIJOH3VCP$PQTQBTTFSUP1SJTN 4XJUDIJOH3VCZTQBTTFSUP1SJTN "1SJODJQMFPGB6OJWFSTBM1BSTFS AllCops: ParserEngine: parser_prism TargetRubyVersion: 3.3 RUBYOPT='--parser=prism'
bundle exec rubocop Use the same parser in Ruby and RuboCop!