Slide 1

Slide 1 text

3VCP$PQ .PEVMBSJUZBOE"45*OTJHIUT ,PJDIJ*50&4. *OD 3VCZ,BJHJ "QSJM UI  &IJNF1SFGFDUVSBM$POWFOUJPO)BMM The Plugin System, Ruby LSP Add-on, and Prism

Slide 2

Slide 2 text

w 044QSPHSBNNFS w 3VCP$PQDPSFUFBN w &OHJOFFSJOH.BOBHFSBOE %JTUJOHVJTIFE&OHJOFFSPG&4. *OD w 3VCZ,BJHJTQFBLFSBU  -5 5BLFPVU  5BLFPVU     BOE !LPJD

Slide 3

Slide 3 text

.BJOUBJO044&WFSZ%BZ

Slide 4

Slide 4 text

&4. *OD BHJMFFTNDPKQ

Slide 5

Slide 5 text

1SJTNBOE-SBNB5BMLT 3VCP$PQ .PEVMBSJUZBOE"45*OTJHIUT 5IF*NQMFNFOUBUJPOTPG "EWBODFE-31BSTFS"MHPSJUIN

Slide 6

Slide 6 text

%SJOLVQ4QPOTPS &4.%SJOLVQ %BZ 'SJ"QSJMBU Pre-registration required

Slide 7

Slide 7 text

&4./PWFMUZ -JNJUFE2VBOUJUZ

Slide 8

Slide 8 text

4QFBLFSTBOE"UUFOEFFT 4 Q FBLFS 4 Q FBLFS !LPJD !IBSVHVDIJZVNB !KVOL !4)(".&-*/,4 !GVHBLLCO !NIJSBUB !XBJEPJ !OTHD !LBTVNJQPO !NBJNVYY !DPMPSCPY , BSBPLF

Slide 9

Slide 9 text

Support OSS community

Slide 10

Slide 10 text

w !LPJD1BSTFSHFNDPNNJUUFS w !KVOL-SBNBDPNNJUUFS w !4)(".&-*/,4QBSTFZDPOUSJCVUPS w !GVHBLLCO3#4DPOUSJCVUPS w .FNCFST8BOUFE 1BSTFS$MVC IUUQTNBHB[JOFSVCZJTUOFUBSUJDMFT'PMMPX6Q,JSJTIJNB3VCZIUNM ߏจղੳثݚڀ෦

Slide 11

Slide 11 text

+PJOVT w-FUTIBDLUPHFUIFS w:PVDBOXPSL SFNPUFMZGSPN BOZXIFSFJO+BQBO w4FFBHJMFFTNDPKQ 🗾

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

$POUFOUT **3VCZ-41"EEPO *1MVHJO4ZTUFN ***"45*OTJHIUT

Slide 15

Slide 15 text

rubocop/rubocop#13792 *1MVHJO4ZTUFN

Slide 16

Slide 16 text

3VCP$PQT1IJMPTPQIZ IUUQTEPDTSVCPDPQPSHSVCPDPQJOEFYIUNMQIJMPTPQIZ

Slide 17

Slide 17 text

"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/

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

*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

Slide 23

Slide 23 text

 *OKFDUIBDLJT/05 BOPG fi DJBMXBZ⚠

Slide 24

Slide 24 text

8FMM CFDBVTFUIFinject_defaults!NFUIPE JTUIFPOMZDVSSFOUXBZUPQVCMJTIB3VCP$PQ QMVHJOBOEUIFNFDIBOJTNXJMMSFTVMUJOUIF JONFNPSZSFQSFTFOUBUJPOPG3VCP$PQT EFGBVMUSVMFTFUCFJOHNVUBUFE5IJTNFBOTUIBU DIBOHJOHUIFMPBEPSEFSPGBTFUPGQMVHJOTDBO DBVTFJODPOTJTUFODZ BTDFSUBJOJOH XIJDI SVMFT BQMVHJOBEEFEPSDIBOHFEJTJNQPTTJCMF +VTUJO4FBSMTTBZT IUUQTHJUIVCDPNSVCPDPQSVCPDPQQVMMJTTVFDPNNFOU

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

w *G3VCP$PQDPNQMFUFTJUTDPO fi HVSBUJPO TFUVQBUQSPDFTTTUBSUVQ JUTIPVMECF BCMFUPSFEVDFTVDISJTLT w 5IFQSPDFTTJOH fl PXPGUIFQMVHJOTZTUFN JOUSPEVDFEJO3VCP$PQ *OWFSTJPOPG$POUSPM

Slide 27

Slide 27 text

/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

Slide 28

Slide 28 text

w 1SPWJEFBOP ff i DJBMFYUFOTJPO"1* BTUBCMF BCTUSBDUJOUFSGBDF BOENBOBHF DPO fi HVSBUJPOTXJUIJO3VCP$PQ w #BTFEPO4UBOEBSE3VCZ BSFMJBCMF SVOOFSCVJMUPOMJOU@SPMMFS %FQFOEPO"CTUSBDUJPOT

Slide 29

Slide 29 text

MJOU@SPMMFS A plugin specification for linters "Depend on Abstractions" Agile Software Development: Principles, Patterns, and Practices Robert C. Martin

Slide 30

Slide 30 text

w "TJNQMFHFNXJUIUIFMJOUFSQMVHJO JOUFSGBDFVTFECZ4UBOEBSE3VCZ w 1SPWJEFTBTUBOEBSEJ[BUJPOMBZFS  MJLF3BDLJO3BJMTBQQT w $BOTVQQPSUPUIFSMJOUFSTCFZPOE 3VCP$PQBOE4UBOEBSE3VCZJOUIFGVUVSF "CPVUMJOU@SPMMFSHFN

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

'PS1MVHJO6TFST

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

w 0MEQMVHJODPO fi HTZOUBY USJHHFSXBSOJOHT w /FXQMVHJODPO fi HTZOUBY TVQQSFTTUIFXBSOJOHT 6TFplugins*OTUFBE # .rubocop.yml require: rubocop-example # .rubocop.yml plugins: rubocop-example

Slide 35

Slide 35 text

w pluginsUIBUJOUFHSBUFDPQDPO fi HVSBUJPO  pluginsTIPVMECFVTFEJOTUFBEPGrequire w 6TJOHrequireGPSpluginsJTTVQQPSUFEGPS DPNQBUJCJMJUZ CVUOPUSFDPNNFOEFE BXBSOJOHXJMMCFTIPXO  w requireJTTUJMMVTFEGPSMPBEJOHQMBJO3VCZ fi MFT pluginsGPS1MVHJO-PBEJOH requireGPSrequire

Slide 36

Slide 36 text

'PS1MVHJO%FWFMPQFST

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

w *OIFSJUGSPNLintRoller::Plugin w *NQMFNFOUabout rules w *NQMFNFOUsupported?JGOFFEFE w 'PSEJTUSJCVUFEHFNT TQFDJGZUIFQMVHJO DMBTTJOUIFHFNTQFDNFUBEBUB *NQMFNFOU$VTUPN1MVHJO

Slide 39

Slide 39 text

 *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

Slide 40

Slide 40 text

 $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

Slide 41

Slide 41 text

 %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

Slide 42

Slide 42 text

 $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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

*OTJEF3VCP$PQT1MVHJO

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

$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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

w 'JYFTIBQQFOBMMBUPODFJGJUCSFBLT  FWFSZUIJOHCSFBLT  w AllCops: ExcludeEJEOPUQSPQFSMZ FYDMVEFSFMBUJWFQBUIT5IJTXBT fi YFEJO SVCPDPQSBJMT1SFWJPVTMZ FBDIHFN IBEUPIBOEMFJUJOEJWJEVBMMZ $FOUSBMMZ.BOBHFECZ3VCP$PQ

Slide 54

Slide 54 text

w 5PNJHSBUFBOFYJTUJOHFYUFOTJPOHFNUP BQMVHJO TFFUIF1MVHJO.JHSBUJPO(VJEF w 5PJNQMFNFOUBOFXQMVHJO JUTIFMQGVMUP VTFSVCPDPQFYUFOTJPOHFOFSBUPS 4UBSU)FSF1MVHJO%FWFWFMPQNFOU

Slide 55

Slide 55 text

SVCPDPQFYUFOTJPOHFOFSBUPS

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

rubocop/rubocop#13628 **3VCZ-41"EEPO GPS3VCP$PQ

Slide 58

Slide 58 text

w 8IFOVTJOHUIF--devcontainerPQUJPO XJUIrails new JUVTFTBOJNBHFCBTFE POUIFHJUIVCDPNSBJMTEFWDPOUBJOFSSFQP w 5IJTJNBHFJODMVEFTBOEVTFT TIPQJGZSVCZMTQBTQBSUPGUIF3BJMT%FW $POUBJOFS 5IF%FGBVMU-41JO3BJMT

Slide 59

Slide 59 text

w 3VCZ-41JOUFSOBMMZJNQMFNFOUTBDVTUPN SVOOFSFYUFOTJPOGPS3VCP$PQ w 3VCP$PQ 4UBOEBSE3VCZ BOE3VCZ-41FBDI IBWFUIFJSPXOJNQMFNFOUBUJPOPG-41 MBOHVBHFTFSWFSGVODUJPOBMJUZ w *UXPVMECFOF fi UUIFFDPTZTUFNBTBXIPMFJG 3VCP$PQT-41SVOOFSDPVMECFVOJ fi FE .PUJWBUJPO#BDLHSPVOE

Slide 60

Slide 60 text

3VCZ-41QSPWJEFT BEEPOCBTFE GFBUVSFFYUFOTJPOT BTBOJOUFHSBUFE FOWJSPONFOUGPS 3VCZEFWUPPMT 3VCZ-41"EEPO IUUQTSVCZLBJHJPSHQSFTFOUBUJPOTWJOJTUPDLIUNMEBZ

Slide 61

Slide 61 text

IUUQTTIPQJGZHJUIVCJPSVCZMTQBEEPOTIUNM

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

w 3VCZ-41XPSLTJODPPSEJOBUJPOXJUINVMUJQMF EFWFMPQNFOUUPPMT w 5IJTJODMVEFTGFBUVSFTMJLFDPEFOBWJHBUJPOBOE IPWFSEPDVNFOUBUJPO w 1BSTJOHDPEFJOFBDIUPPMJTXBTUFGVM TPSFVTFJT JEFBM w 5IBUTXIFSF1SJTNDPNFTJO 3VCZ-41)BOEMFT.VMUQMF"EEPOT

Slide 68

Slide 68 text

#FGPSF3VCZ,BJHJ

Slide 69

Slide 69 text

w .POLFZQBUDIFTXFSFBQQMJFEUP 1SJTNBOE3VCP$PQ"45 w 6QTUSFBNTVQQPSUXBTTVHHFTUFE  QSPNQUJOHBQMBOUPNPWFGPSXBSE 4IPQJGZSVCZMTQ

Slide 70

Slide 70 text

w 4UBSUJOHJO3VCP$PQ 1SJTNDBOCF VTFEBTBOPQUJPOBMCBDLFOE w *UVTFTPrism::Translation::Parser UPDPOWFSU1SJTNT"45UPUIFQBSTFS HFNJOUFSGBDF 3FVTJOH"45JO3VCZ-41 AllCops: ParserEngine: parser_prism # Ruby 3.3+ Syntax

Slide 71

Slide 71 text

SVCZQSJTN IUUQTHJUIVCDPN4IPQJGZSVCZMTQQVMM *NQMFNFOU,FWJO/FXUPOTJEFB

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

SVCPDPQSVCPDPQBTU

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

w 4JODF1SJTNTQBSTFSFTVMUDBOCFSFVTFEWJB RuboCop::AST::ProcessedSource JUBMTP XPSLTXJUI3VCZ-41TRuboCopRunner  XIJDIJOIFSJUTGSPNRuboCop::Runner w 5IFHSPVOEXPSLJTEPOF /PXJUTVQUP 3VCZ-41 8PSLTXJUI3VCZ-41T3VCP$PQ3VOOFS

Slide 77

Slide 77 text

w *NQSPWFTTPVSDFBOBMZTJTQFSGPSNBODF CZYXIFOVTJOH3VCZ-41 QBSTJOHJTTLJQQFE  w SVCPDPQBTU 'BTU&WFOXJUIB5SBOTMBUJPO-BZFS JT JUFSBUJPOTQFS TFDPOE      XJUIQBSTJOH 8JUIPVUQBSTJOH

Slide 78

Slide 78 text

 5IF"EEPOJTBO &YQFSJNFOUBM'FBUVSF

Slide 79

Slide 79 text

w 3VCP$PQTCVJMUJO-41 MBDLTTPNFGFBUVSFT QSPWJEFECZ3VCZ-41T 3VCP$PQJNQMFNFOUBUJPO w 'PSFYBNQMF UIF rubocop:disable TVQQSFTTJPOGFBUVSF /PU3FBEZUP4XJUDIUP3VCZ-41"EEPO IUUQTHJUIVCDPNSVCPDPQWTDPEFSVCPDPQJTTVFT

Slide 80

Slide 80 text

w 3VCP$PQTCVJMUJO-41NJHIUBEPQU 3VCZ-41MJLFGFBUVSFT w 6OJRVFGFBUVSFTNBZBMJHOUPP w 6TFSTCFOF fi U EJ ff FSFOU-41QSPEVDUT TBWFF ff PSUGPSUIFJSNBJOUBJOFST IPQFGVMMZ 4IBSFE'FBUVSFT.JHIU4QSFBE

Slide 81

Slide 81 text

w 5IJTJTOPUBSFQMBDFNFOUGPS 3VCP$PQTCVJMUJO-41 w *UTFYQFSJNFOUBMGPSOPX CVUCBDLFOE JNQSPWFNFOUTTIPVMECFOF fi U3VCZ-41UPP w 5IFHPBMJTGPSFBDI-41UPIBOEMF DPNNVOJDBUJPO XJUI3VCP$PQQSPWJEJOH UIFTIBSFESVOUJNF 1BSU**,FZ1PJOUT

Slide 82

Slide 82 text

In the near future ***"45*OTJHIUT

Slide 83

Slide 83 text

.PUJWBUJPO#BDLHSPVOE Two years earlier than expected :-)

Slide 84

Slide 84 text

w .BJOUBJOFSNPUJWBUJPOJTEPXO  XJUI1SJTNTFFOBTUIFGVUVSF w 1PSUJOHGPS3VCZTZOUBYMJLF itCMPDLQBSBNFUFSTJTDVSSFOUMZOPUJO QSPHSFTT w 4FFXIJUFRVBSLQBSTFS $VSSFOU4VQQPSU4UBUVTPGUIF1BSTFSHFN

Slide 85

Slide 85 text

w "QSPCMFNJT fi YFEOPUCFDBVTFUIFSFBSF QFPQMFJOUSPVCMF CVUCFDBVTFUIFSFBSF QFPQMFXIPXBOUUP fi YJUCZ!LBNJQP w Prism::Translation::ParserNJHIU CFDPNFUIFSFDPNNFOEFE EFGBVMU PQUJPO PWFSUIF1BSTFSHFNBTFBSMZBT3VCZ 1SJTN-FBETJO.BJOUFOBODF

Slide 86

Slide 86 text

w $PNQBSFEUPBTUBOEBMPOF JNQMFNFOUBUJPOCBTFEPOQBSTFZ XJUIOPEFQFOEFODJFT  1SJTNBOE1SJTN5SBOTMBUJPO1BSTFSBSF VTFGVMTJODFUIFZFWPMWFBMPOHTJEF3VCZ 5IF1BSTFSHFNBOE1SJTN

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

%FTJHOPGUIF1BSTFSHFN generate QBSTFZ  3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ 3VCZ1BSTFS QBSTFD QBSTFTP GPS3VCZ $ lrama QBSTFZ  read read generate ruby/ruby <> SVCZZ 3VCZ1BSTFS SVCZSC 3VCZ1BSTFS SVCZSC $ racc SVCZZ read read <> whitequark/parser porting and more tracking *OIFSJUTGSPNParser::Base )VNBOQPXFS

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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!

Slide 92

Slide 92 text

*TUIFOFYUTUFQGPS 3VCP$PQUPTVQQPSU OBUJWF1SJTN"45🤔

Slide 93

Slide 93 text

w $45LFFQTBMMTPVSDFJOGPSNBUJPO w "CTUSBDUJPODBVTFTMPTTPGJOGPSNBUJPO 5IF-SBNB$SFBUPS5BMLT"CPVU4ZOUBY5SFF ࠷ߴͷߏจ໦ͷઃܭ೥൛ @yui_knk Concrete Syntax Tree

Slide 94

Slide 94 text

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 🤔

Slide 95

Slide 95 text

6OJWFSTBM1BSTFS JTOPU 6OJWFSTBM"45🤔 8IFUIFSUPBEPQU1SJTNTOBUJWF"45OFFETGVSUIFSEJTDVTTJPO

Slide 96

Slide 96 text

4JHOJ fi DBOU*NQBDUPO6TFSTJT&YQFDUFE w $IBOHJOHUIF"45JO3VCP$PQNFBOT SFXSJUJOHBMMOPEFQBUUFSOTBOEDPQT w 8JUITPNBOZDVTUPNQMVHJODPQTPVUUIFSF  JUTVODMFBSJGUIFDPTUJTXPSUIJU w .JHSBUJPOXPVMECFUPVHI TPBEWBODFNFOUJO "*BTTJTUBOUTVTJOH--.UFDIOPMPHJFTMJLF -BOH$IBJO 3"( BOE.$1JTFYQFDUFE

Slide 97

Slide 97 text

w 1FSGPSNBODF*OUFSNTPGTQFFE  PQUJNJ[JOHUIFUSBOTMBUJPOMBZFSNJHIUCF XPSUIDPOTJEFSJOH'PDVTPOPWFSBMM OPU MPDBM QFSGPSNBODF w &SSPSUPMFSBODF*OXIBUTJUVBUJPOTXPVME JUCFVTFGVMJO-JOUFSBOE'PSNBUUFS 5IJOL"CPVUUIF6TFS#FOF fi UT

Slide 98

Slide 98 text

IUUQTXXXZPVUVCFDPNXBUDI W),@-G8E/3: On considering the potential of Lrama

Slide 99

Slide 99 text

w 6TF1SJTNXJUIUIF1BSTFSHFNJOUFSGBDF w 1SJTNUSBOTMBUJPODBOCFSFVTFEBTMPOHBTJU QSPWJEFTNFUIPETMJLFparseBOEparser_lex w *G-SBNBTQBSTFSSFUVSOT PSBEBQUT B 1SJTNDPNQBUJCMFTZOUBYUSFF JUT fi OFGPS 3VCP$PQ 1SJTN5SBOTMBUJPO1BSTFSJT3FVTBCMF

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

%FQFOEPO"CTUSBDUJPOT w 3VCP$PQSFMJFTPOMZPOUIF1BSTFSHFN"1* w 1BSTFSHFNT"45JOUFSGBDFJTTUJMMGPMMPXFE  FWFOJG1BSTFSHFNEFWFMPQNFOUIBTTMPXFE w 1SPHSBNUPBOJOUFSGBDF  OPUBOJNQMFNFOUBUJPOCZ(P' w *O3VCZ FWFSZUIJOHJTBOJOUFSGBDF @kakutani

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

w 5IF1BSTFSHFNJTHFUUJOHIBSEFSUPNBJOUBJO w 3VCP$PQOPXEFGBVMUTUP1SJTNTUSBOTMBUJPO MBZFSGPS3VCZ UPFOTVSFTZOUBY DPNQBUJCJMJUZBOESFEVDFNBJOUFOBODFDPTUT w 3VCZTitCMPDLQBSBNFUFSJTTVQQPSUFE JO3VCP$PQ 1BSU***,FZ1PJOUT

Slide 104

Slide 104 text

#P[IJEBS#BUTPW +VTUJO4FBSMT ,FWJO/FXUPO 7JOJDJVT4UPDL :VJDIJSP,BOFLP )BWF'VO (JU)VC!LPJD 4QFDJBM5IBOLT