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

RuboCop: Modularity and AST Insights

RuboCop: Modularity and AST Insights

Koichi ITO

April 17, 2025
Tweet

More Decks by Koichi ITO

Other Decks in Programming

Transcript

  1. 4QFBLFSTBOE"UUFOEFFT 4 Q FBLFS 4 Q FBLFS !LPJD !IBSVHVDIJZVNB !KVOL

    !4)(".&-*/,4 !GVHBLLCO !NIJSBUB !XBJEPJ !OTHD !LBTVNJQPO !NBJNVYY !DPMPSCPY , BSBPLF
  2. w !LPJD1BSTFSHFNDPNNJUUFS w !KVOL-SBNBDPNNJUUFS w !4)(".&-*/,4QBSTFZDPOUSJCVUPS w !GVHBLLCO3#4DPOUSJCVUPS w .FNCFST8BOUFE

    1BSTFS$MVC IUUQTNBHB[JOFSVCZJTUOFUBSUJDMFT'PMMPX6Q,JSJTIJNB3VCZIUNM ߏจղੳثݚڀ෦
  3. "4VNNBSZPG3VCP$PQT1IJMPTPQIZ w 3VCP$PQJTBSVOUJNFJNQMFNFOUBUJPOPG UIF3VCZ4UZMF(VJEF w *OJUJBMMZ UIFHPBMXBTBVOJ fi FEGPSNBUUFSMJLFgofmt w

    *UCFDBNFDMFBSUIF3VCZDPNNVOJUZIBT DPO fl JDUJOHTUZMFQSFGFSFODFT FH TUSJOHRVPUJOH  w 3VCP$PQCFDBNFDPO fi HVSBCMF EFGBVMUJOHUPUIF 4UZMF(VJEFCVUBMMPXJOHDPNNPOTUZMFWBSJBOUT https://rubystyle.guide/
  4. w 3VCP$PQTVQQPSUTDPO fi HVSBCMFTUZMFT MJLFUIFTF  w 5IJTBMTPBQQMJFTUPUIJSEQBSUZFYUFOTJPOT 3VCP$PQJT$PO fi

    HVSBCMF # .rubocop.yml Style/StringLiterals: EnforcedStyle: single_quotes SupportedStyles: - single_quotes - double_quotes
  5. w 3VCP$PQEJE/05IBWFBOP ff i DJBMQMVHJOFYUFOTJPO "1*UIBUBDDPVOUFEGPSDPO fi HVSBUJPO w 8IJMFRuboCop::BaseIBEFYUFOTJPOQPJOUTLOPXO

    BTUIFDPQFYUFOTJPO"1* UIFZEJEOPUTVQQPSU DPO fi HVSBUJPOFYUFOTJPOT w "NPOLFZQBUDIDBMMFE*OKFDU PSJHJOBMMZDSFBUFEJO 3VCP$PQ34QFD IBECFDPNFBEFGBDUPTUBOEBSE .PUJWBUJPO#BDLHSPVOE
  6. w 5IJSEQBSUZFYUFOTJPOTOFFEUPCFBCMF UPDVTUPNJ[F3VCP$PQVTJOHUIFJSPXO FYUFSOBMDPO fi HVSBUJPOEBUB 8IBUUIF*OKFDU)BDL5SJFTUP4PMWF # rubocop-example/config/default.yml Example/MyCop:

    EnforcedStyle: foo SupportedStyles: - foo - bar 💡1MFBTFSFBE&YBNQMFBTBOZ DVTUPNDPQ MJLF1FSGPSNBODF 3BJMT  34QFD PSPUIFST
  7. 50%0Ϋϥεਤʁ MJCSVCPDPQFYBNQMFSC require 'rubocop' require_relative 'rubocop/example' require_relative 'rubocop/example/version' require_relative 'rubocop/example/inject'

    RuboCop::Example::Inject.defaults! require_relative 'rubocop/cop/example_cops' #.rubocop.yml require: - rubocop-example Extension gem require 'rubocop-example' *UJTF ff FDUJWFMZFRVJWBMFOUUP require 'rubocop-example' User's configuration *OKFDUIBDL
  8. *OKFDUIBDLT1SPCMFNT w 50%0Ϋϥεਤʁ class RuboCop::Example::Inject def self.default! path = CONFIG_DEFAULT.to_s

    h = ConfigLoader.load_yaml_configuration(path) conf = Config.new(h, path).tap(&:make_excludes_absolute) puts "configuration from..." if ConfigLoader.debug? new_conf = ConfigLoader.merge_with_default(conf, path) ConfigLoader.instance_variable_set( :@default_configuration, new_conf ) end -BUFSBEEFECVH fi Y #SPLFOFODBQTVMBUJPO 3FEVOEBOUEFCVHMPHHJOH
  9. hacked around 0ME3VCP$PQ*OUFSBDUT1MVHJOT  3FBEDPO fi HVSBUJPO GSPNSVCPDPQZNM  3FRVJSFQMVHJOT

     *OKFDUDPO fi HVSBUJPO GSPNUIFSFRVJSFE fi MFT QMVHJO SVCPDPQSBJMT 1. read user config SVCPDPQZNM QMVHJO SVCPDPQSTQFD SVOOFS 3VCP$PQ $PO fi H-PBEFS 3. inject 3. inject 2. require 2. require
  10. /FX3VCP$PQ*OUFSBDUT1MVHJOT  3FBEDPO fi HVSBUJPO GSPNSVCPDPQZNM  3FBEQMVHJOEBUB GSPNUIF1MVHJODMBTT 

    .FSHFQMVHJO DPO fi HVSBUJPOPOUIF 3VCP$PQTJEF QMVHJO SVCPDPQSBJMT SVCPDPQZNM QMVHJO SVCPDPQSTQFD SVOOFS 3VCP$PQ $PO fi H-PBEFS encapsulate 2. read plugin info and pass the context 2. read plugin info and pass the context 1.read user config 3. merge 3. merge
  11. w 1SPWJEFBOP ff i DJBMFYUFOTJPO"1* BTUBCMF BCTUSBDUJOUFSGBDF BOENBOBHF DPO fi

    HVSBUJPOTXJUIJO3VCP$PQ w #BTFEPO4UBOEBSE3VCZ BSFMJBCMF SVOOFSCVJMUPOMJOU@SPMMFS %FQFOEPO"CTUSBDUJPOT
  12. MJOU@SPMMFS A plugin specification for linters "Depend on Abstractions" Agile

    Software Development: Principles, Patterns, and Practices Robert C. Martin
  13. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  14. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  15. w 0MEQMVHJODPO fi HTZOUBY USJHHFSXBSOJOHT w /FXQMVHJODPO fi HTZOUBY TVQQSFTTUIFXBSOJOHT

    6TFplugins*OTUFBE # .rubocop.yml require: rubocop-example # .rubocop.yml plugins: rubocop-example
  16. w pluginsUIBUJOUFHSBUFDPQDPO fi HVSBUJPO  pluginsTIPVMECFVTFEJOTUFBEPGrequire w 6TJOHrequireGPSpluginsJTTVQQPSUFEGPS DPNQBUJCJMJUZ CVUOPUSFDPNNFOEFE

    BXBSOJOHXJMMCFTIPXO  w requireJTTUJMMVTFEGPSMPBEJOHQMBJO3VCZ fi MFT pluginsGPS1MVHJO-PBEJOH requireGPSrequire
  17. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  18.  *OIFSJU-JOU3PMMFS1MVHJO require 'lint_roller' module RuboCop module Example class Plugin

    < LintRoller::Plugin def about def supported?(context) def rules(_context) end end end *OIFSJUGSPN-JOU3PMMFS1MVHJO Define a plugin class RuboCop::Example::Plugin
  19.  $SFBUF-JOU3PMMFS"CPVU class Plugin < LintRoller::Plugin def about LintRoller::About.new( name:

    'rubocop-example', version: Version::STRING, homepage: 'https://github.com/...', description: '...' ) end def supported?(context) def rules(_context) %F fi OFBCPVUNFUIPEUIBU SFUVSOTB-JOU3PMMFS"CPVUXJUI QMVHJONFUBEBUB3BJTFTBO FYDFQUJPOJGOPUEF fi OFE 1MVHJOOBNFVTFEJOUIF QMVHJOTTFDUJPOPGUIF VTFSTSVCPDPQZNM  SFDPHOJ[FECZ3VCP$PQ
  20.  %F fi OF1MVHJOTVQQPSUFE class Plugin < LintRoller::Plugin def about

    def supported?(context) context.engine == :rubocop end def rules(_context) "'", POMZ3VCP$PQTVQQPSUT MJOU@SPMMFSGPSOPX CVUPUIFSMJOUFST MJLF3VGPBSFBMTPCFJOHDPOTJEFSFE %FGBVMUJTUSVF
  21.  $SFBUF-JOU3PMMFS3VMFT class Plugin < LintRoller::Plugin def about def supported?(context)

    def rules(_context) LintRoller::Rules.new( type: :path, config_format: :rubocop, value: "#{__dir__}/../../../config/default.yml" ) end end NOTE: There are no dependencies on RuboCop's API like those seen in the "Inject" hack %F fi OFSVMFTNFUIPEUIBUSFUVSOTB -JOU3PMMFS3VMFTXJUIUIFJOGP3VCP$PQ OFFETUPMPDBUFUIFQMVHJODPO fi H 3BJTFTBOFYDFQUJPOJGOPUEF fi OFE
  22. HFNTQFD ,FZUP"CTUSBDUJPO Gem::Specification.new do |spec| # snip spec.metadata = {

    'default_lint_roller_plugin' => 'RuboCop::Example::Plugin' } spec.add_dependency('lint_roller', '~> 1.1') end 💡Setting default_lint_roller_plugin key allows RuboCop engine and user's .rubocop.yml to avoid needing to know the plugin's custom name. 4FUUIFQMVHJOOBNFJOUIFHFNTQFD TP3VCP$PQDBOEJTDPWFSJU
  23. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  24. 3VCP$PQ1MVHJO module RuboCop module Plugin class << self def integrate_plugins(rubocop_config,

    plugins) plugins = Plugin::Loader.load(plugins) ConfigurationIntegrator. integrate_plugins_into_rubocop_config( rubocop_config, plugins ) plugins end end end *OUFSOBM"1*XIFSF3VCP$PQMPBETQMVHJOT BOENFSHFTDPO fi HT
  25. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  26. module RuboCop::Plugin class Loader def self.load(plugins) plugin_configs = normalize(plugins) plugin_configs.filter_map

    do |name, config| next unless plugin_config['enabled'] plugin_class = constantize_plugin_from(name, config) plugin_class.new(plugin_config) end end name = Gem.loaded_specs[plugin_name].metadata[ 'default_lint_roller_plugin' ] Kernel.const_get(name) Implementation Details 3VCP$PQ1MVHJO-PBEFS (FUTUIFQMVHJODPOTUGSPN QMVHJOTHFNTQFDNFUBEBUB
  27. 1MVHJO4ZTUFN$PMMBCPSBUJPO RuboCop -JOU3PMMFS $POUFYU DSFBUF DSFBUF MPBE 3VCP$PQ 1MVHJO 3VCP$PQ1MVHJO

    -PBEFS -JOU3PMMFS 1MVHJO SVMFT BCPVU TVQQPSUFE $VTUPN1MVHJO DPO fi HEFGBVMUZNM SVCPDPQZNM 3VCP$PQ1MVHJO $PO fi HVSBUJPO *OUFHSBUPS SFBE SFBE 3VCP$PQ $PO fi H-PBEFS 3VCP$PQ $PO fi H-PBEFS 3FTPMWFS Plugin lint_roller JOUFHSBUF@QMVHJOT@JOUP@SVCPDPQ@DPO fi H User $ rubocop TOJQ VTF FYFSVCPDPQ
  28. 3VCP$PQ1MVHJO$PO fi HVSBUJPO*OUFHSBUPS module RuboCop::Plugin class ConfigurationIntegrator def self.integrate_plugins_into_rubocop_config( rubocop_config,

    plugins ) default_config = ConfigLoader.default_configuration runner_context = create_context(rubocop_config) validate_plugins!(plugins, runner_context) # snip (Merging of configurations) end end &OHJOFDPMMBCPSBUFTXJUI QMVHJOGPSQSPDFTTJOH
  29. $SFBUF-JOU3PMMFS$POUFYU module RuboCop::Plugin class ConfigurationIntegrator def self.create_context(rubocop_config) LintRoller::Context.new( runner: :rubocop,

    runner_version: Version.version, engine: :rubocop, engine_version: Version.version, target_ruby_version: rubocop_config.target_ruby_version ) end end 3FUVSOTB-JOU3PMMFS$POUFYUXJUISVOUJNFJOGP MJLFUIFMJOUFSFOHJOFBOEBOBMZTJT3VCZWFSTJPO  QBTTFEUPQMVHJOT
  30. 7BMJEBUJPO module RuboCop::Plugin class ConfigurationIntegrator def self.validate_plugins!(plugins, runner_context) unsupported_plugins =

    plugins.reject { |plugin| plugin.supported?(runner_context) } return if unsupported_plugins.none? raise NotSupportedError, unsupported_plugins end end class Plugin < LintRoller::Plugin def supported?(context) context.engine == :rubocop end Plugin's Implementation
  31. w 'JYFTIBQQFOBMMBUPODFJGJUCSFBLT  FWFSZUIJOHCSFBLT  w AllCops: ExcludeEJEOPUQSPQFSMZ FYDMVEFSFMBUJWFQBUIT5IJTXBT fi

    YFEJO SVCPDPQSBJMT1SFWJPVTMZ FBDIHFN IBEUPIBOEMFJUJOEJWJEVBMMZ $FOUSBMMZ.BOBHFECZ3VCP$PQ
  32. w 5IF*OKFDUIBDLJTBOVOP ff i DJBMNPOLFZQBUDI w 0 ff i DJBMQMVHJO"1*TVQQPSUXBTJOUSPEVDFEJO

    3VCP$PQ UIF fi STUUJNFJO3VCP$PQT ZFBSIJTUPSZ w .PTUDPNNPOVTFDBTFJTTVFTBQQFBS SFTPMWFEBTPGUIFMBUFTU3VCP$PQ   *GBOZQSPCMFNTSFNBJO QMFBTFPQFOBOJTTVF 1BSU*,FZ1PJOUT
  33. 3VCP$PQTCVJMEJO-41 Built-in LSP Request (JSON-RPC) 3VCP$PQ 3VOOFS SVO DBMM SFRVFTU

    EFMFHBUF 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF P ff FOTFT GPSNBU XSJUF TUBSU IUUQTSVCZLBJHJPSHQSFTFOUBUJPOTLPJDIUNMEBZ
  34. 3VCP$PQTCVJMEJO-41 Built-in LSP Request (JSON-RPC) 3VCP$PQ 3VOOFS SVO DBMM SFRVFTU

    EFMFHBUF 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF P ff FOTFT GPSNBU XSJUF TUBSU
  35. 3FVTF3VCP$PQ-413VOUJNF Built-in LSP Request (JSON-RPC) 3VCP$PQ 3VOOFS SVO DBMM SFRVFTU

    EFMFHBUF 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF P ff FOTFT GPSNBU XSJUF TUBSU EFMFHBUF BDUJWBUF DBMMCBDL SVO@EJBHOPTUJD DBMMCBDL SVO@GPSNBUUJOH DBMMCBDL 3VCZ-41 Add-on 3VCZ-41 3VCP$PQ "EEPO 3VCZ-41 3VCP$PQ 3VOUJNF"EBQUFS %FUFDUFECZ UIF3VCZ-41 3VCP$PQ-41 UP3VCZ-41
  36. 3VCZ-413VCP$PQ"EEPO w 50%0Ϋϥεਤʁ module RubyLSP::RuboCop class Addon < RubyLSP::Addon def

    activate(global_state, message_queue) @runtime_adapter = RuntimeAdapter.new global_state.register_formatter( 'rubocop', @runtime_adapter ) # snip end def deactivate @runtime_adapter = nil end *OIFSJUGSPN3VCZ-41"EEPO *OUIFBDUJWBUFDBMMCBDLNFUIPEGSPN 3VCZ-41 SFHJTUFSBOBEBQUFSUP UIF3VCP$PQCVJMUJO-41SVOUJNF
  37. 3VCZ-413VCP$PQ3VOUJNF"EBQUFS w 50%0Ϋϥεਤʁ module RubyLSP::RuboCop class RuntimeAdapter def initialize @runtime

    = RuboCop::LSP::Runtime.new(config) end def run_diagnostic(uri, document) @runtime.offenses(uri_to_path(uri), document.source, document.encoding) end def run_formatting(uri, document) @runtime.format(uri_to_path(uri), document.source, command: '...') end $SFBUFTUIF3VOUJNFDMBTTUPIBOEMFDBMMCBDLTGSPN3VCZ-41 %F fi OFTEFMFHBUJPOGSPNUIFSVO@EJBHOPTUJDDBMMCBDL %F fi OFTEFMFHBUJPOGSPNUIFSVO@GPSNBUUJOHDBMMCBDL
  38. class Prism::Translation::Parser < ::Parser::Base def initialize(builder = Parser::Builder.new, parser: Prism)

    @parser = parser super(builder) end def tokenize(source_buffer, recover = false) # snip result = unwrap( @parser.parse_lex(source, **prism_options), offset_cache) # snip end SVCZQSJTN 5IFEFGBVMUQBSTFSDMBTTJT1SJTN  JUDBOCFTXJUDIFE "OZSFDFJWFSUIBUSFTQPOETUPQBSTFS@MFYXPSLT
  39. SVCPDPQSVCPDPQBTU w 50%0Ϋϥεਤʁ module RuboCop::AST class PrismParsed # Internal API

    def initialized(prism_result) @prism_result = prism_result end def parse_lex(_source, **_prism_options) @prism_result end end end 💡An internal class for duck typing with parse_lex. 4UPSFUIFQBSTFESFTVMUGSPN1SJTN 3FUVSOUIFQBSTFESFTVMUGSPN1SJTNXJUIPVUQBSTJOH
  40. SVCPDPQSVCPDPQBTU w 50%0Ϋϥεਤʁ class RuboCop::AST::ProcessedSource def create_parser( ruby_version, parser_engine, prism_result

    ) # The parser_class is chosen by version and engine. parser_class = Prism::Translation::Parser34 ... prism_parsed = PrismParsed.new(prism_result) parser_instance = parser_class.new( builder, parser: prism_parsed ) ... end $SFBUFBDVTUPNQBSTFS VTJOHB1SJTNSFTVMU 3FQMBDFXJUIBQBSTFSJOUFSGBDFDMBTTUIBU SFVTFTUIFHJWFO1SJTNSFTVMU
  41. w 3VCP$PQTCVJMUJO-41NJHIUBEPQU 3VCZ-41MJLFGFBUVSFT w 6OJRVFGFBUVSFTNBZBMJHOUPP w 6TFSTCFOF fi U EJ

    ff FSFOU-41QSPEVDUT TBWFF ff PSUGPSUIFJSNBJOUBJOFST IPQFGVMMZ 4IBSFE'FBUVSFT.JHIU4QSFBE
  42. w 5IJTJTOPUBSFQMBDFNFOUGPS 3VCP$PQTCVJMUJO-41 w *UTFYQFSJNFOUBMGPSOPX CVUCBDLFOE JNQSPWFNFOUTTIPVMECFOF fi U3VCZ-41UPP w

    5IFHPBMJTGPSFBDI-41UPIBOEMF DPNNVOJDBUJPO XJUI3VCP$PQQSPWJEJOH UIFTIBSFESVOUJNF 1BSU**,FZ1PJOUT
  43. w "QSPCMFNJT fi YFEOPUCFDBVTFUIFSFBSF QFPQMFJOUSPVCMF CVUCFDBVTFUIFSFBSF QFPQMFXIPXBOUUP fi YJUCZ!LBNJQP w

    Prism::Translation::ParserNJHIU CFDPNFUIFSFDPNNFOEFE EFGBVMU PQUJPO PWFSUIF1BSTFSHFNBTFBSMZBT3VCZ 1SJTN-FBETJO.BJOUFOBODF
  44. %FTJHOPGUIF1BSTFSHFN generate QBSTFZ  3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ 3VCZ1BSTFS QBSTFD

    QBSTFTP GPS3VCZ $ lrama QBSTFZ  read read generate ruby/ruby <<generate>> SVCZZ 3VCZ1BSTFS SVCZSC 3VCZ1BSTFS SVCZSC $ racc SVCZZ read read <<generate>> whitequark/parser *OIFSJUTGSPNParser::Base
  45. %FTJHOPGUIF1BSTFSHFN generate QBSTFZ  3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ 3VCZ1BSTFS QBSTFD

    QBSTFTP GPS3VCZ $ lrama QBSTFZ  read read generate ruby/ruby <<generate>> SVCZZ 3VCZ1BSTFS SVCZSC 3VCZ1BSTFS SVCZSC $ racc SVCZZ read read <<generate>> whitequark/parser porting and more tracking *OIFSJUTGSPNParser::Base )VNBOQPXFS
  46. %FTJHOPG1SJTN5SBOTMBUJPO1BSTFS 1BSTFS#BTF 1SJTN 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>>

    1BSTFS#VJMEFST %FGBVMU 1SJTN5SBOTMBUJPO 1BSTFS#VJMEFS <<use>> <<use>> 5IFEFGBVMUQBSTFSTJODF3VCZ "WBJMBCMFGSPN3VCZ SC QSPHSBN "DMBTTUIBU TFSWFTBTBO "EBQUFS CFUXFFOUIF 1BSTFSHFN BOE1SJTN 4ZOUBY FYUFOTJPOTJO 3VCZ  FH itCMPDL
  47. %FTJHOPG1SJTN5SBOTMBUJPO1BSTFS 1BSTFS#BTF 1SJTN 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS 1SJTN5SBOTMBUJPO 1BSTFS <<use>>

    1BSTFS#VJMEFST %FGBVMU 1SJTN5SBOTMBUJPO 1BSTFS#VJMEFS <<use>> <<use>> 5IFEFGBVMUQBSTFSTJODF3VCZ "WBJMBCMFGSPN3VCZ SC QSPHSBN "DMBTTUIBU TFSWFTBTBO "EBQUFS CFUXFFOUIF 1BSTFSHFN BOE1SJTN 4ZOUBY FYUFOTJPOTJO 3VCZ  FH itCMPDL %JSFDUEFQFOEFODZ
  48. QBSTFS@QSJTNCZ%FGBVMUGPS3VCZ "OBMZTJTDPEFXJUI3VCP$PQ   3VOOJOH3VCP$PQXJUI3VCZ AllCops: # Use parser_prism parser

    by default TargetRubyVersion: 3.4 $ bundle exec rubocop Use the same parser in Ruby and RuboCop!
  49. 8IBU-FWFMPG"CTUSBDUJPO%P:PV/FFE 5IFMFWFMPGBCTUSBDUJPOJO"45WBSJFTCBTFEPOVTFSOFFET VOMJLF$45 $45 $PODSFUF4ZOUBY5SFF 3VCZ4PVSDF$PEF /BUJWF1SJTN"45 "CTUSBDU4ZOUBY5SFF 5IF1BSTFSHFN"45 "CTUSBDU4ZOUBY5SFF

    RuboCop Ruby LSP, ReplTypeCompletor /PNJTTJOHJOGPSNBUJPO Abstraction Prism.parse Prism::Translation::Parser "OZUSBOTMBUPS Syntax Tree :FUBOPUIFSUIFCFTU45 Client 5IFCFTU 🤔
  50. class Prism::Translation::Parser < ::Parser::Base def initialize(builder = Parser::Builder.new, parser: Prism)

    @parser = parser super(builder) end def tokenize(source_buffer, recover = false) # snip result = unwrap( @parser.parse_lex(source, **prism_options), offset_cache) # snip end SVCZQSJTN "HBJO 5IFEFGBVMUQBSTFSDMBTTJT1SJTN  JUDBOCFTXJUDIFE "OZSFDFJWFSUIBUSFTQPOETUPQBSTFS@MFYXPSLT
  51. 3VCP$PQT1BSTFS&OHJOFT 1BSTJOH5BSHFU 1BSTFSHFN 1SJTNT1BSTFS5SBOTMBUJPO-BZFS 3VCZUP %FGBVMU 5IF4PMF#BDLFOE1BSTFS N/A 3VCZ %FGBVMU

    ParserEngine: parser_whitequark "MUFSOBUJWF ParserEngine: parser_prism 3VCZ %FGBVMUVOUJM3VCP$PQ 4ZOUBY*ODPNQBUJCJMJUZ %FGBVMUTJODF3VCP$PQ 4ZOUBY$PNQBUJCJMJUZ 3VCZ FYQFSJNFOUBM N/A %FGBVMU 5IF4PMF#BDLFOE1BSTFS Status in April 2025