From 9188db7e96f65b51dbf1f474925513e39dae2a6a Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Thu, 5 Sep 2024 16:32:52 +0300 Subject: [PATCH] Format using prettier --- .vscode-test.mjs | 4 +- .vscode/extensions.json | 6 +- .vscode/launch.json | 28 ++- .vscode/settings.json | 22 +-- .vscode/tasks.json | 30 ++-- CHANGELOG.md | 2 +- bun.lockb | Bin 168763 -> 169123 bytes eslint.config.js | 40 ++--- package.json | 3 +- prettier.config.js | 7 + src/control-flow/cfg.ts | 221 +++++++++++++++-------- src/control-flow/graph-ops.ts | 113 ++++++------ src/control-flow/render.ts | 111 ++++++------ src/frontend/src/main.js | 10 +- src/frontend/svelte.config.js | 4 +- src/frontend/vite.config.js | 7 +- src/test/nodecount.test.ts | 30 ++-- src/vscode/extension.ts | 319 +++++++++++++++++++--------------- tsconfig.json | 48 +++-- vsc-extension-quickstart.md | 50 +++--- webview-content/main.js | 40 ++--- webview-content/reset.css | 16 +- webview-content/vscode.css | 86 ++++----- 23 files changed, 662 insertions(+), 535 deletions(-) create mode 100644 prettier.config.js diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..f728f01 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from '@vscode/test-cli'; +import { defineConfig } from "@vscode/test-cli"; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: "out/test/**/*.test.js", }); diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d7a3ca1..e08c0ec 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,9 @@ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format - "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] + "recommendations": [ + "dbaeumer.vscode-eslint", + "connor4312.esbuild-problem-matchers", + "ms-vscode.extension-test-runner" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 90b3e9f..ccdb134 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,19 +3,15 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] -} \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 5c5ac48..64ee929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,13 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false, // set this to true to hide the "out" folder with the compiled JS files - "dist": false // set this to true to hide the "dist" folder with the compiled JS files - }, - "search.exclude": { - "out": true, // set this to false to include "out" folder in search results - "dist": true // set this to false to include "dist" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" -} \ No newline at end of file + "files.exclude": { + "out": false, // set this to true to hide the "out" folder with the compiled JS files + "dist": false // set this to true to hide the "dist" folder with the compiled JS files + }, + "search.exclude": { + "out": true, // set this to false to include "out" folder in search results + "dist": true // set this to false to include "dist" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 72b01ad..8e9c1f9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,16 +1,16 @@ { - "version": "2.0.0", - "tasks": [ - { - "type": "bun", - "script": "bun run --cwd ${workspaceFolder} ./scripts/build-with-esbuild.ts", - "problemMatcher": [], - "label": "bun: build", - "detail": "bun run ./scripts/build-with-esbuild.ts - package.json", - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "type": "bun", + "script": "bun run --cwd ${workspaceFolder} ./scripts/build-with-esbuild.ts", + "problemMatcher": [], + "label": "bun: build", + "detail": "bun run ./scripts/build-with-esbuild.ts - package.json", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b66018..88bb745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,4 +6,4 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [Unreleased] -- Initial release \ No newline at end of file +- Initial release diff --git a/bun.lockb b/bun.lockb index ef227863b6b3a109254bdeb419fd270238775a12..d99455684d5427528d0877b336397713514c8ce5 100644 GIT binary patch delta 19326 zcmeHvdt6mj{`Ov5j-Cx36mNilqUK!?1U(=iTIPUAW~GRC6ckhh0WWw7t;|bd*-DpA znL6FHGBYcrG`mcV*jQOjqfO>GQ)AxpQW9RAAoxiJmh#U*)VWd(U9a&yCvC%S%R7y*W{D7T;_YRdGYiTA^{ zV3$tMDa}X0$)!4Li=9HA=<>aD{;_CIHOFu1avKpNh`3gq;lz0`HRTtl)~= z5l+)S0<#hCfO*pVoKm#LFmkgTdmfmz%>*;j)$k*~q`7XSbM_h8h7kh&80==?DMiJ( z?BpouEnuhVvwt+&Fv1O^V$pUa+8|>MI1D^r^Ehw>>|3?A#?T8fVR(F-=S1=lxXE;6yoUrs z^BM*4Ghm*0jpn6VpE}2>Vd@;T5XTbCbPo75aBFa*2~Grxit{jVhEY%&HK!CO*j?c0 zTV=a|;EFbg3!77#S2#Pb1Wndzix{rl_ECzIc0HWj>C2^XY<6OqUjTJO7rFxPVY4pg(k?Ejf4GzW;?~* zv*ja=$5pf_ch1h9GtS(A$mgNALM49zvlUOxbNv2z-G4?waZYKeZ>D~RLE}k3L_Rrk zfm5zIm=n!h=;$xNX6v`Z#tiPY=ouu~eV@ZIRK#wUhB@pja9f2FmS^N9Putj)i=1jd z2Xmf|LX%l)(qiW{XZ@U7Pr&Bso|Wf3!ClYZ<9JP*o;M58G>*Y$W8VgM0(XbTNm#QZ zHqhJ9{pGlrJ6`TyUAZH_#h(+bHL2L$kl8@4YU_1<2$EsV|Tkyd!hyH6hY$_uPSoG03Undg|=;RG(XYxeJgQX#2f& zJ>@sZr=>Zm+mo+L*2j=A6Bh-@15*;*xV-d(Vafyd+J^2_53lPXWc8Jq-XyCAsa|$x zhk3o$C0L2D{N=Dnua$x84mySkR`u{&55Qs#4Vfq2mCJ{xT4uU0R6%XL)`*{3D`BBe z2;;D^32tqecEz0sBOp%2ZM?3TNG8br@k!Perko;Puc(mAvr?^hGYkV~Uerhi_4HcP zhd50#Bu`ici#>G7plGk_M_BP3DJw41Fp};1gU}5H3voe1f?~X8U8daEv!9i6o6|ZW zSD^`uVW9~TNmeyd?6Z)Fhp3b3qf)J;p@tFTXx+W8X|Uqu{+>zJ2BdmG_p>ANflMEr z>T=(17=5HCI?3#JyWBUrpEVZ(3nL7uaI;JwlWLuU7H?O$D%xvB;!4TZ`q^P#0PAKa z4?%wu7FxuhV|ZHMaXqAwUe`ESJ!EEblC>Hsdf5Zz`YWsi>SoxTa>clQR{EVznCzg5 zrP3Oo>Us-W8hhb#--W{JzyJ{^`-i)&5il7o)D`2k9*5=3AzEL+;*g@Hh<=;lhJjiZ zHI_l?UNKYFOh^^)ODiYUYJkgYJaP;5rL;F|LN?M1UiH2^L#`fkF!sM`GgoELSNl>1mrRYbK?djkBeHZa>%k*@iLF z9-ps}!klo%(&~n-g9Cwz5ZF<$FbcFVb)JKDvpxPNaiK=7x+%AeF%0}^YXg$#1gv4% z3GV9#+ng~LZx(!xuBTy*kd=AK?w`rS$FXIe$;oac{$8y$-if$9jjWNd`l2R##9fcT z>SHg|y-1}(N9+;4S7l9ps?{yWFfa$vAVf-(%k+X&>v?Ec#poELcODj38dgttuQh0* zGt*$jdaZk4IV%f25c}lvsi|hbB)M;DKWoS&!$`6#VRV+l8Y(j*ldR8?Vi8Q{aIYDY zEB8(7=bDpi7`MvGsYzm&OrM@=1>^d{K)dBCoaI-rIP8`^w%^0zlEFyFd#$#Uow4<^ z+cOpxXP&bTo`l5#LR6x>uJ4&AD`S$Z#3@b_{N#ZMEL>W)UW1e_jk(z*-!P(48f!k+ zYo%xlgN@j&mCK7#tz*zQ28h$}1iu1%Ka5Ng3uSt7s`Wl>XF_rVxyW3&BFtE>@l%}! zU>112t_857?E!j@Dcg%n^&l)~wlmc2r#bbxiP(ly_uWs3#ETqKQUgWm!(>3@Ro)FK}`d_ z)(Kc#y7sUWbLv2A7u~w3idh9_sTR9GpfJ;2^&o`n?+%b9q8 zkk*1!Gh((Jyr7?J+-!T)B9mNek&2NXEU&#t^@6+8zl++!C>MKOJ?0>|($hW3T7VRr zjH@0__!cY%*1o=2wXhg3d+^LYbLEP=aS591^h4N-?iE-ua6x=WdtG1ClKUqmS#9x# zjztlrfnL{GSZGw6Bx^lVvCsqTv2xEdjJsgjeRqw4h2;mYZAh`Svx%M7mc2%-?(>}{ zIx}GsEbc8%eNSl1-;UQISbgnUxJSncTK2y3m9`q%OC@}fGuUWuidPJlHA_;(W74`mReUSc?@u+O7t0m*_Y>1)4WyT( zwKP>+km*ZPP47K&#nOJRY4`A|;pv&|zSlRShDCe1nwO1A^ux{x1%Of;Wox@t?HAB-!>kj-w*607% zj{mBne9-zW zxeWjh&2rm=9{Iw9A$A-#+Bt?%pILSjz_L#QJjm3a()_gMXTUtj%zqYOxh>j$4$R{Q zj0L1KSnzq<7F}0|(fdMif}!9jZ9UtoXM8_4fcCWa_^IsP6@M{5x#H?||*?Fw1|U z&yR#t@n_nBOou;cyFN2Q2en?G*|0CPPId!-0jPfs@F26^Z)o87<|e#Q_?zZqU>9%- zV8QPILqiwulUayMKdsBvXG+iDg`Iv*=abp_ZD2|-X&u}i=Ff!d zHVeE8$A;1)oh|fDSUm09VD2>gbT$7km{om%7xM3Qy=1P|FSsi0oklzSRXdQG{2DKe z%ipwpOxwpbp9Hhv-|74_n$K!J2j)R;1g-_s|B}`(aaH<5aPchvOJ)^~@WK=LgL#es zU2lD6exS~8lB*q>)MF-t@IuEHU{=&p=htTrWNWS0XZnX}o!mfrRyzY7snvgn`F^Fl z_OH*Zy$5u1l+GtJ*;DH=S|@Y9#+w*ucE<~W+!surWW2DV6wUo8;vjRg8K`YCyPFQC z|6r|?nH-|+`b_CIt>0#bIR%Gmp*}Z|DQlc<<4)wV`nxm_*WP66BQ$4ey*{(KW3^6Z za=f<5Oy+32KC|A5j1TjC(}Bzir-B);Le0f=!cm`@UkaTSmFavk^*P%9XUuYQO)LcZ z&eH|R%$N_Rv{2i3)4)Myeg&9Y#xiX$*SrGEqds#ctc1?2hw(!DQSFERJ9k37GvYyJ z5B^(sMHRhxRvb6t|H55S#ed{(h#g7?ngOF};J7g({x{C}&*&W5pWPL4{QrO672$TM z=`XiL&f@&#mWa1T+?js4C30?+xYhr1OLU`qwqI_EFgLiH|8h%&ApBdmPFzgC+!FnA zOZ3Yv5f(!ITOlqW+FT&N+!7f(7C-&_TlzmR?Ur~?;MXgDpY-PZ)fW#Bc=+)z;`V#8 zPiLj?x%6PWW!}#VdTod~7<9Vv5Alue|ETW3=5d{lUA?DgpO5}pH?hs(+0A9;@erAL zJVX3Wt~;J3(@uEg6<9Sg{X`bNad*o?Yf0c>fXUT72t^Ynl zd?Sy;TKj`XX8w>Nj>&aDpeH|~C$Nso^dGZiEv&6SW{8ur4%X%i9y#_xhBz&^T*#6m zfAUDrPZ{Ej%>F4$x@$dhH>`8gs?Cx+VHMV9i0@@Jto%BUjHt^HKgxo-EE#gqBlpAl zNrqm`l6zq-yqJM+B-Oy0d&wiCE@g;Iviwq(>~h&755u}5yI#(c2VqrS&Jfq+Ay~_< zpnq2~gpkXxpnq4{CW+6OktbpxNTLgrWCQ=>~LKBs3LU1>Lu$w}VvKm0xNujU-gyyQ6 zLcSk_2tNodRe>Lb5Eq2~6oOT#3&LIs3tbRGRSku?4IxA|gb=368$#&P2*P0s5vpq= z2nQ)tHiFPr9ip(z4I#x1p`BXphLB)EI8C90O0*z+OJThQp`$tuL3C0B{6U@7I#L&P zj?`792Y|Y%O{DIsj?_a9YYd7~TS(DLctAZ>Hb}VxQSELIs*P1vAcUP13Iid;scH)O zO&~-xfzVqOG=UJ(6vBQAUKQFD!d?msn?gubH5BFsL5K>1kfh3kAarR4;V^|1)wLOf zgA^*8K}c1HC@gCZA*DHl0cv@32nj79oThM#N^Ak)TMFx2K)6*Mr?9pqgv^!@ex=s6 zgpk$>!W9bXD!mniS_)fRLC8>b6gCG#7#j>BQ*8-`Ffs&!Cj`P!l^p`X9SUJLg<;AH zg|L%CVJL(zrzuQQiESZ#OJRLm2zlx_ zg|(3oG9w{OQR^Zhq_u-^g+hT!ZwH~4!q#>Wrl~p#o7+Pe+a5xp+R`4v$PN%Z9Uv5` z><$pzH$m7Iq>dg~FZ?q^hQn9|IvG2EuAp5Cb737Q%iC zN`=Nk*h^tyEQEEchQi!l5TbfPcubY|g3u)n!eI*QRo6HO2Pss>LD--UQCJoaAtfHd zCbc{sLPBo{rzt$C5_?1Vmcshp5S~`YDXi@SA+ryJXVtns5YoI5u29&b(!CIBDQxvZ zcwW^}*qi`iYyyO>YD)ryk%_iCez7Td(*siR;5Oz{1>i1R){` z!mFwv2|`FRg#8q%RA@4`qMcPUlf{lE_*SQ%$M=6I_ow19rP`h)!m5&oi1-F#N7c-s z;@*a)e_qvx1H>RfZ?Rhq$P&}ckZJ10EYZf}`;z?{d^fFGRnQob+CW97i-YRyXb~wMwU98B4Yj5zQVUp_~?A6nz%!xL|nb*9MqN~i@AK}!egm=dxvOVu}sUnjca0O z7{+p~`98Fzyh3aIBz_d@!SR6Bcpp4YYY%FTk6fl{tx{_|^-Qhlr*jqdC&+wKN}Z4D z_|Xd=%XHF?t8^ZpN$^8L9*<~^4+rN0EaQ8e=Rx`=fF0r^J$`=62Ts{=V&h-XW!c$L zW(HnyY}H~@q}#DJ94~4u2XW_SM_bRQmhPD%67 zj`Ssf$Lm^)KpGRuKHkt8zg=X%_oli_i*1pvLz>5KFnb*dEYR8>Fq7?oHvq=_Ev>cJ z8XL+7jP&aOd?`8k1ds z0M&pCG+W|hQ8w_NahB{d-y2f^c!KansKm_nOD&^D8RzNTi0)zq_4MzV% zb?fV*vy01TpPKo)=ulCEB%h!^2|NXq0-3;V!0o^tz?}fUsgMo~2KbTQEx<2ysJ_g* z0QlgO4^dNr{=fjBFOUSZ1=;}XQT_>_3DQjgej?Kt@Bn;L&J}+V_zE}zU~!nn(<*kC z=vl$9H++Y(Bf$J7$T6f(0-pe%0`CLA1>Od#fY*R$fz7~^0KbRB)y~B|1>oZ4V!aDU z15yA!18)XghVK>NDsT-zE7Xx)!c}1+lV1sV26z_O3~T|o<*xzoZ4BdafZyI&2P_8e z1J0n1&w$?nTY#s4S*U9^Py}!{=eEr)nU@M)8F(e&rHLPNE&-Mbe0_Qu61-5JMFmd- z{8~jdzzz0cfEULH0SP<`bOh$Yk6XVL%I@1bN)Zx#j~fja1`Li^Ez-G@TI>(`Y!-?P40dTz)yg8K_k8axC71r_#~N6 zm$~_Iv*YIGGO=MYgJ7j}WWhi0K`l69_Sw4{H$!_?tpM|)#Xvp}a9}uF7;w&v{=j=x zu05j2FX&sOzXpx~%>PO)ei!}yE0QAsPMV{@M&QrDUw~u4-vG9opnV$P#Nq_wM7$F? z0Wc1Mz;A$405zsLU--6y!?Y5p1nvhowwNas_QbdwMgfoyOaUeX6M!M8U^I9XFcOFZ zVt`HnXLfUdlZ=a+b0Y}c6yQed4{$FI1U!HM+yM9G@8Qd@$erioaBkLgq{9yoxkGbj zr~_(&A2t63z5tvDE&-PT?&LlnUc0Wr_U$SSVbjkAZUi(20suD50=ObtaecEV!9XjZ zB@hBc0&RiTK&VcKgWCYy#=-#F9RT_0R4Ic%qJ3o8K{@=rJ)bH z?!=SSFVJ5XtY5jWQl6Ok1A$wBO!eOT=nI3(>k$X*b}&~u)5LJ3?*e#@B4+{r(=Qv^ zO8AZkj|0X6V>I%>lYksxB9IG|17$!lP|9gN3khyDg+LK79he481*pvc=*;v?4PDsI zV`bE-`Iu$q0JAmE1uq2V1M>i%Un$?#F@vJ7(gjFUTLe@9_X76-_W>&a?y*Y&?z6uA zl=%+w!(c&B)jYejF%AnmRRN1JX|b z&j2h#|KDhS8vGQn1xSYPb6{R6wgFp#7d5{M=5%@myaTZK{^4aLwySe{McYmS49`&Zp}OnhaA z`F`o(!>@N2uU+}$5mSuo6&)Lm_YL=}g(pO)8K0)so)Epv)HL<`6C&g~$EU41DJGkJ z(^dXS(MzPM$4-hVVXX!`|LmUo;AO&LikThJ{eSL zF=&W-^Tt^=KJl4H$M5>#LcCzEVcdY*ql@q=j@ZLJ0})9 z?k!Z$^TH*rsov*NLy;;VjZy0z>%e);kH&YXQ|Cp9SfO0si^uSRAA*!AcBwOnRd3&~ zKX_Wzjyn6`pBLIEi;3=oOFk~z9Ocw;=VxEMWy1?P$BQ>MYQT?Z#ye{K1<*%o<_{n( z<|+^KW~x0D=cz+KpgoQlc5Earq^L1u!~AuXoA2tSYsA3)ys)mgPCbOG!hF9Q@rUrY zzP$65-#(?o5r5-Ss_6yMTFg-0(X@YZLgy5{eZO^aYFo;t7OxKPqU*-+{;RgC_j#UE z>L|`*`j1wZFJM0Texai2z#}<<1K#iNCpa-NWsO@#tC*iKt9`#|v8TuGF&&JvL4Wt~ z{p!Wa-~DM@YS8P&f6rN?7XF0J_) zw?DGy?+%ZvVRa(Ttk;^2bt0tC4O^4Q)>t`CJ9`Dc&}m-m0xk$HWaNbAsP9lV>>soS zOKXX0cTr@T$rDxaMKOf@CK#W;LXI<|CaC_1h-0QT!f4?QP_Ej13Cs3>w2!_`FaP6n zp`=}DsIdFjA~KOH+xOETw*}q2v{R4jQKpE+ijPMGuc}p-u?&~0BbTuZeZTaP@4B?1 z&+%CtGzPx2MIQTLiVNLsfcb-K{FFVj=HVJ4m|J zjjB?&T@$y7V`}|1vDmZFV1m~?x+l9`xG1mXw z$!>#M_?aSLq4IN^k(g^8-DYN(?-x%_<&GMDIQgxCc2Ro|*sdzwh=Sd;DZOvthy98> zmhZPvvfuA>$BDspr%(gqiTJEkV=dJ1xmsmm5=~Oilb%)YQ9PmQNN=ksf6y_New?Vzv%BHJ1~WVok=PneuLE?zFF=3jiZk73k+Ix#Od=RWu3 z%(JlRH9jkMk~<@P+O*8{45XN1^fQc_&@c71`2*N~u+wvjveWa&XBD7C<})d?C_jCg zVN6OijM}gR2H1KAnE4_y{JuQHI0@UwFxr%KPcn>Jh}ZyT0n0SIbFwn0x%2a)VKd`a za%>R6{g*f4yd*8|rDzcpCy^=?_*^Ip3a zIk_2`;pyYtw{$H}fa&%Dn3Z`GTphe5#?HPe?E0|%z-)E?Fst?@`2{&SnUf7;Dq6%N zc(`5J1z=WdCYaU8O3y?6jAA1=*%qR}EFcWb_DqE%IZN}76uUw#Qw<{k`Wo1Ez!P$( zWU!z*(CfiAbcGg-MB%XA;6~tq;2>~E%?-dID8J<92s`3)a0ndU05jo6FcV(vZ}&j8 zF?NND(kD-L=j9t;X?^He!w7_59M~V6pEub(4i)l;%^o}gW)JNgw~>xGcNA&A<(zzK&uz0J&2#a||5ryzm)$7})~1t!ILndg2Vb+=(;L zKpdZI%}+4fXQ&nwCqKgpfHP5W=ujBLXv=DKsnXJul&(>gw2WD63piAMU!fQ zUj#GVNVJ~KHMHOOJoHmmdj1*cjCafQ8vacKup7|IOE0uKsVKc*CL3Q4ov9BL$dxrl z7r&2K7S1NqkGm52xgx`80DUExl^8YC_In(*?VsbGlAf36nWGq z?u2^x@Wd`fsK4d+XN3ChOdE7th~9PV(46Wry@|^_1PdKf;;^i$xa!2fsv_65b2MGM*vF1F4qAWe# zH^EhN>c?_A~Xm&gieX^hQSP~GD^&n$A%@Cd$m?YhBk7UHT!vrpw$}|nt}qE z@x$7(EAs`c{#M588o8Wd2*$`0(XnPaL!LCkC66U1n6nbmY0#?4(Dp8~`2f30hE-xN zEH=?8LnB@;$q9X<+p_i>(Lp!+49k5t0A=B@R@xEiQB3%Yr6$yzo zry|6b!^!0mJ7npI1oJYq4z||TWoXwg=7>mpp{QCO_87l+4;#x=F49S3VVER@ZjM^AnP zOZRINm$SjWC|ss>i#3xFqL;%O;8n0H3nYq(1+ld0np9LI;tmE+>gI9#Wrt@@wDI8TLT z_W(zICoI-LSoOIG3ky|SZBsl}s4GSWW+XV?N|7Tn;+^rShLK{8&~k(@Bka*Mzk+3t zW9al4Z)`2-dgg^0G!9lzc_Jj%xdB)66d5w1n>PZZ;NXy`mNOZ_;c_)(20XG^CCxC9 z)OIq{VEr|2=P%-XM+rj#RZ2&)YfI*g4NBoB3)+Jah?T+ z7K;1jv55(eH^<3_lj2P;d{T(DGGXr|z#1gen#7uqA;dL_`5f$WoEk41PL6lB&oGR; z<%x;0B43tHPB8xfoBix9*P)c#usHH2`_bHrs}a`>dNtZ*eg=!f>m@_sS^qvyH>Bsq zc*EfMU~z@JoLdel}Iin|{ zBx``iG33$BCt%qVo=rOri*p?N)gOwkrLcUVhd(bL|b-i6iKlZ*3#Tx(c{b`!bs`1E-57Hkfhy=4!a zVmI2h?uTXPgAK#n3kxF&Pi}2Z@R5o0(rGR8iLh8xY(f~y?XWmUOv!88Sy;>k0}HG9 zG><29$%4g!!A^&`a#)-YXaxqKR-Rn~+S}V@PJqS1vDS_GEG)Y`=6MAcORz3?W_Z4* zW{7i6gVoi#&^(V2z3krn239mIl#I4D#Rrf$Sk=H9td$NzqBYT+aGMmfk4V z9EMONbbP!(Hy?mC6qeO=XT6!$3WV1LgzUx4Zrr3TYn7N^!-_!~dnyFYvUe9dzv0^Q zvASystgi61r`su5Jz*JgT@RPpX0{#YZEYprXv>~Ljpo=xiga-&=-VLD=GS+1?B z)>8Qx7Ka+OjdKZy44j`JhR9T$*UHlQ36Aed4F5u5A)>l z1@X>i_wynXf{zyE2-y=VJ;KH1yfDGce88R=*euaP1xxo6_K+j6u*KN>ff@UtJ@qk# zQOBLya&pNyu0JRnKHRmqzG2;U<7{&kaDZjmED#O^s0RbILjWFR2hbQ`d{cnOZ*dLi z=o0H7(;rg_9c2gp2m3?8BDRiyAKT^sTMDX!s*lhO_?xWD|KA<|w~YRQcy@UjP#wqu zsshD1I5h*706fSX8;Z?tiqg zJo~7(ACs=P651?e$YTF46gB}Y;2D5ko3*_K%;Ps%CN1`lWPfb4d~9Ye+qF$*ZZ87V zcL0Xmw%Ff~sa^r7?g9+CdX>L5jAbyGW)HyFy#Nm~?R}bG1@j<#0S5pc2duNr^gpO= zGV^^Cp#B!XgG~KxE+a;~1Mv7q*o5DQmbcAJe?;44X8cEOlNtZ9wktEc<1>Cl3A~z{j^P{T^G#!8fYEN_5BNhm3+d#m~O#vqi2YA zCo`MI+9tEQ%{7OCx#4x!@&5$dJIX(i0jlwTt4LL(>&?4N9=*{LHl~kmQDtTi_18L? zJu^VtWKNZ#U`oSyl4)!FQ|XlgD-b*tOgj(ED@!pLfA}ndlV(nf2Vt|H7lB#oqdK0< z;9{O+#S(vJU_8!~W_mA$%~GD!dS#}*TZFyK(k7gk^%fmZX2Z9GDZQZezsK}{8Gancy*k}KVaV~)&zjT+ zpm7P7>%9II%&gwkWxc2KC38(416#X>z(+sEeWe}941SFh$KhLTf2ZwJn!g9L)6Owo zR;;tP=?k!`LA|PDZ-9A_eZXGK@}Fd8Q57eaZh~2ak3)ajLBKl5bnw*C}5G*aQW`lJES;(Y~_UJJ*O6z0>M{AqR;8<-}X6Bo& z4Q_FOPGhy~hSsCf~XM`h+rSPY#}kK;sJYCi<|B+!Bt<~5Bi_z&Kguw34tSVyrZ zK<0J2udV;5xcFaM5U(|V^M*t>?3eNA|6dCKTAM@r>l>3CXw85ntPsba0ZuaBknkXL znf;q?Onz}c;>Q9j-?{vLW5OOe2yn^132% zP5gdif=>AT#>AR?|HzFC_p{$`OzeHV@(l?)kT&=C-)~HQzcJz7`u}%hg6FwhsQ>mG zlby#leSDX6exE3>o%WS!-zSQ9v2wrSF+UQ7%)@B#U?D4xHbU=GkQNzD&pY zu-t?52h#6cviO5^Z$1?PXWbuhC!ueBq1n19W*o9emy_j+ z%f7M#*4MJ%kIAy{kG^u-kBQ=2c^%d@SZP-h#b4x>D`?3TwB)BmaayMSlq^$z@|F8x zoss6%Wa)j?S7u*L6zAj~Si50`TuT%`NcXj5nRU%q9)@*M23}8=0oQ%yoa>3=vMh&n z2v+!wL~%tH-AI;&H+?_Z~ zs*qhOl4VSVuUuV`=(ugj?<J;B7SUcA{{~>#(lf z_Fa}H5=FITTSW4*&B9mt3Yex!6(U)s2nhQrR9B`0g0}-gwgZB%+CyPCg^(%`YAJUW z2w7Di9H!u>0=*yvctM!s1);7gr*McuxD!HsRpf+F=!9^Bg1-u@3ZZpX2#c#i2vo-? z9HkIf4MLDwR1LzyY7ov*2vJ?UA;fq?SnUm=u{urR6on)cLZ~V=A*?VVR8VN9`uRZU z>jPn%4}=!#It0;D4XzGqrM8e-D^UX!rcz05)DBWxW%`2JsdQ4f+Cz#^el*5_R|i6@T2u$Z!a5MnQHWDr>OzRA3t@F#2np&mg;Nxg>OttCO6x&bQ4c}|g}YS0 z`Vjiohp??aguB&s3fCy4HGt4ZZD{~ua{~yz{t)`9RDTF5{t)(4NK|G31n&R{*#Que z)E)}EDTD+<7^K{R5V8Ux9HuZ>1vZ2b&=A6$h7g9Rateniga<(wriy|f6b3;!K_OX% z1>@8@7{cOU2&w8Ag`*VWLLiJ(i$WkQ41sWtLYnH*2trIF2&)@G7^6;8I7K0;F@$tg z+8Dx$#t_?`9CPn?cA{dnoLt5Yik%u5verkkuT*VG2`KU<(KVEg;Nk0U=M7Q#eE+yd{JJ zRn!tfVM_=nD9lh{tst~+1z~Y32u11`g`*VWT0@wn7PW@3ur-8p6y~TdVGv@%Agm69 zP^?Z4Tq2t4q-oqN0b=>!8-y%b_9gQY7d3o6hhiVcwD*LL&$0m;V^|IDzF2DfDRDm zbbugLIfX+M!XqIpQ$>*w3L_z$prBM(6ol4M5Ee&4C{@QO9HkJ~5yC39s3U}h9U+{f zuv&GAh7c1CVRbZwHR?2lQxuXqL0G3sJ3&~{2|@*h4XR&f2z@(4*wz`sCUu>{H414i z2+yi5E(n`l5PV}GY*DE(5K>|w?5D6*nOz`wcY%=I1;RG9hr(_OAzdN7pxj*{WOapb zn8FSf7z-gF7Q&oZ2s>3dg+mm=yFqwG6?KD9*bTx73T0(sabjmJImJ~EKT-55^G*`c zRm7ySxO>C{Rh@5S;EQzresR|F9^w!kFvEOP#3bj3_&tOVWBjR_o*#`5EL)c%Twcz* zi||3#O1P_sC=@si5M{w*h4)aOwLAIY0?($()R>n=oP7g(>$ZI`Vm{*O!lx8GJT^ZZ zTMsM23$zcv%oNANTH}YTG+~!-JfcND!W$5k6;R_%WPV9-nixpn&5E9mntZ`K=p|C0gUNwHW}@EafvY27Cd2HOCh5 zkr=-aWy?|!$=Yw#iEAO8$^s(kQFZj*h75qMU7uE@7CHYTH_PPUPAw@ zWtSEkLS$lgQJL0))Vf`wZgDWA8vu_zI*PxQ!9=tU&%?Dwu=s614?JkI%5RMDHH3M* z3TAW@V3yWi12Y&3>;>3{*R|Hv){6Ph7RCWBa->cGtkglRHAmRg(ims$IGv&fxE?YCs){9RlZvU`S6hj zAAh6>b$+)PhfhF*_lS{^Tna6LRsfd(e!XZ+0j2_ZKt50a;AeM6Auv;&*dy)=dlbRN zz+=GU0DolQ0IC39fD@<+c&mPUMNlz+lTZQN0!9EMfl)wHfE~gSU}rS}LIF-0PUR)2 z@D4EVa%KUA03S9b16*GtfRO+nNb%tmpKA>Q`24FM&>z4%a-% zbAW4NqrmdXQ(^l>`{Hv*%U?kB2EIf1d*BH05%6EYo4^5pKSX*3cm{YDSP$?gD_rDU z)b{~gv|OY^fIdJsfZyQw0XO0MGf)BC0&WAS1%6h9$nRim0-gb$1vUfR_m=@HfmHy1 z0BFF+dd17B~yn)4&zrOMn~B9^e$<33G=%32?XMrsn{!2JeGLd;@Uf%Le#- zmd|Xt>2Z_erp6x~Ko7DanJFEa@Jnv^INFtSGi%y{v*}@g*Cux2a{vd0GliYaS<$`B zJRowteE)***TCn%+iKBUXx}>segzB%I8nX?)&gGuCxP#PZvobqpnVqL_;DOL0fzvm z0k*#;upT%AP-B?$gdZw6Jd1%xfrkK$DgHSUfh};F1I_{_0QUizz!+cv;zoc|fn*>G zXb-diIHT(ToLpSYoD;RdwSby{32+bg1!@2WPz~UId4hL?6|X}atU+L|aE6JY2oC{xZ6Xf`e(je6 z?J@X{2B!g|fRP#*;BmlMARQPF6aslbE~oW01o8oHF_VEDU=lD9a0Aq`0XSRXDPV#& z(=v{kc{C4GpAHmgo&lZ>%mj)6+Yi%SUk zW&C5nGGGOufG2^az!HETizIkCK%X_RSF(UoU_I~@uomcvh&A9!sS!^jyc*a9 zFb)0JY2FCl0Bi=j!FLOoSBUMv^T0OEFM~Oqc7lCh!f8K@9pD$$m2%OzFYpAa8ik4LnEF1H#aEh|I?}KQ98s zF7?@Y(b4gGe^uiL)W-Aw&oAG*bo;5?X>Q9uinaN)zv>Q;PG9xMu86B~_JRkDZ~DKy z+^TORJv{$=KBCpZ&hwra`jKu4$}-gAA5fN`+V>-Rl z#4F*IK1hY~n4jmr`Gc=M^wB8u=b1XDBReFZ!^M0ARP z$E`&5`~`8JsHy5+6di*+uQ3R2cKL&ik;Ahr$IdJyL#4yPaUoGHzlgp*IY7M&7Ohmp zMN~Cf`Ck$pMR%1*TBeFGiNGMws}9z8{B+=&P1jw>4DFAIWM(@riFrYucOtxZ&Y9Aq zEU4Ysa%CHsEf>X#6`ae|e~>e!yD@sDDa z-F-=&JTEU0vs=8A)^ao35*ZO0h4MYGFYs+}ZN!B~znr5h))|*W6?X;AnW|3w1e&R) z(psP%sU!xg*I@;D-e?dKTv5E_xxbX_JkYo(b@>V^ZTsQtpB~Ig=cq+H<4&HpA6zI~ z_(ZAqw=q^8tl*vZ9()pf;Lk%|eruy$_lRihZg$n~WaM`h!{&K?!upyk_7p7M*Iicz zGfEe)TVheYs*xP|UsOzec~vwN8`aILA~5)k5!S70NzcX2cGh^SO`@0JMC0`Qe1z(7 z4RhP`c7+#r-&ip)+kePkJ?s}S;5`HF_K#|2T=4sAOh+~68v4!i#)e(r)X8r8#q$|| zb#SX4a6oUrN2;d2y(U%#d)`;jtm(;2(II22?g&hgLu1t18(4{-jKL=mth3uqADL2m z?^!%Cz%X~>B>HxY`tUkxdqsP+pL6+%qYpoQ86Hs)UDzP!So>ewBe%~w{d4oR+pV-# zjK2!KDH=3FE^Xjb7x_1PzR5=opSdd$KG+#JOXJkw8>s0}HR%RchOUp}lQF7x1?pwJ z2?FV6{b^9-t{-al)p;YwgJacs)I8`2a`uDA#*8s@)(^-X0uPtHqOH;c@!fk`1)fpF z|127+oSUMdDvzEoLvZ-Iq-lxW^H-5%S{#CtyJD<*+ly;wZ zp#^r1oraA^d2>zY(*+!H=$x^mRroE;AkP~wW(Izj(9sM= z-X4cJDjg2^qIVW)ty*?VOc2(_6)2i53v#eWBX1*0Ynn1uF~tH^dK>GuKpiIi?PSZc z90SFgWo3?e!Jd~_44TnqO{XI-+_E~LLqrVzpJTEbDjZQkp4VE`E1VnDeC?M{ImBG< zfoP|p)(O<8rrJjeR(@WN2I`D(gamnBO7Yk;bLwT^)#W^L#qwjC&Z?Qi(J{#LmW$;n z?{$4HC+Q0~=!r8{O@)KVSDPG;Bs|E0n44JA+`r6+7zH}I~{?+2k>2xiCNNXNyz>--B#f0ihi|b zdwGG1uIl(8*z*#OSIa-nJUO950zBA@sO$TMs!=r*IA0B}hTinNZsVcaqb5&&P+*8W z7uJ<(O*Qn{b7~L#P``7us;O$;j-+7EOE;c=Zt0l@^*?$DsbcJ1^5`rz#T#9bG28lP zvZP|NcyUV8UdQ3l$^J;O05P26*KW+pdL!k@O%5?dz2fZ%?fX0;Y9b;d_02b)uC>0QsuA>N#=8kvqJn8FDrdTg{> zY@*(7^{k0jWT-!xjtN1YSAv|)7%}Wbw*$S9AND=&iB(ma58QjJ89s1-Ol^e~6g1E7 z?pbFi99~%GOjE39HU(GAG3pEYtygucgNjri(i%0ZI!fNG%Bwr>7B8t;I%AD>9Dw{gc5!L&@0ns!OXaE2J diff --git a/eslint.config.js b/eslint.config.js index fbe3fba..29b506f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,26 +1,24 @@ // @ts-check -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - ignores: ["dist/*", "webview-content/*", "src/frontend"], - + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + ignores: ["dist/*", "webview-content/*", "src/frontend"], + }, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", // or "error" + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], }, - { - rules: { - "@typescript-eslint/no-unused-vars": [ - "error", // or "error" - { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" - } - ] - } - } - -); \ No newline at end of file + }, +); diff --git a/package.json b/package.json index b084b16..26a84b2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "esbuild": "^0.20.0", "esbuild-plugin-copy": "^2.1.1", "eslint": "^9.9.1", + "prettier": "3.3.3", "svelte": "^4.2.18", "tree-sitter-cli": "^0.23.0", "tree-sitter-go": "^0.23.0", @@ -81,4 +82,4 @@ "engines": { "vscode": "^1.86.0" } -} \ No newline at end of file +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..cc334f4 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,7 @@ +/** + * @see https://prettier.io/docs/en/configuration.html + * @type {import("prettier").Config} + */ +const config = {}; + +export default config; diff --git a/src/control-flow/cfg.ts b/src/control-flow/cfg.ts index 95c3f78..54df243 100644 --- a/src/control-flow/cfg.ts +++ b/src/control-flow/cfg.ts @@ -1,9 +1,35 @@ -import { MultiDirectedGraph } from 'graphology'; -import Parser from 'web-tree-sitter'; +import { MultiDirectedGraph } from "graphology"; +import Parser from "web-tree-sitter"; type Node = Parser.SyntaxNode; -type NodeType = 'LOOP_HEAD' | 'LOOP_EXIT' | 'SELECT' | 'SELECT_MERGE' | 'COMMUNICATION_CASE' | 'TYPE_CASE' | 'TYPE_SWITCH_MERGE' | 'TYPE_SWITCH_VALUE' | 'GOTO' | 'LABEL' | 'CONTINUE' | 'BREAK' | 'START' | 'END' | 'CONDITION' | 'STATEMENT' | 'RETURN' | 'EMPTY' | 'MERGE' | 'FOR_INIT' | 'FOR_CONDITION' | 'FOR_UPDATE' | 'FOR_EXIT' | 'SWITCH_CONDITION' | 'SWITCH_MERGE' | 'CASE_CONDITION'; +type NodeType = + | "LOOP_HEAD" + | "LOOP_EXIT" + | "SELECT" + | "SELECT_MERGE" + | "COMMUNICATION_CASE" + | "TYPE_CASE" + | "TYPE_SWITCH_MERGE" + | "TYPE_SWITCH_VALUE" + | "GOTO" + | "LABEL" + | "CONTINUE" + | "BREAK" + | "START" + | "END" + | "CONDITION" + | "STATEMENT" + | "RETURN" + | "EMPTY" + | "MERGE" + | "FOR_INIT" + | "FOR_CONDITION" + | "FOR_UPDATE" + | "FOR_EXIT" + | "SWITCH_CONDITION" + | "SWITCH_MERGE" + | "CASE_CONDITION"; type EdgeType = "regular" | "consequence" | "alternative"; interface GraphNode { type: NodeType; @@ -69,7 +95,7 @@ class BlockHandler { } // If we get here, then the goto didn't have a matching label. // This is a user problem, not graphing problem. - }) + }); } public update(block: BasicBlock): BasicBlock { @@ -84,13 +110,11 @@ class BlockHandler { breaks: this.breaks, continues: this.continues, gotos: this.gotos, - labels: this.labels - } + labels: this.labels, + }; } } - - export function mergeNodeAttrs(from: GraphNode, into: GraphNode): GraphNode { return { type: from.type, @@ -111,14 +135,18 @@ export class CFGBuilder { } public buildCFG(functionNode: Node): CFG { - const bodyNode = functionNode.childForFieldName('body'); + const bodyNode = functionNode.childForFieldName("body"); if (bodyNode) { const blockHandler = new BlockHandler(); - const { entry } = blockHandler.update(this.processStatements(bodyNode.namedChildren)); + const { entry } = blockHandler.update( + this.processStatements(bodyNode.namedChildren), + ); - blockHandler.processGotos((gotoNode, labelNode) => this.addEdge(gotoNode, labelNode)); + blockHandler.processGotos((gotoNode, labelNode) => + this.addEdge(gotoNode, labelNode), + ); - const startNode = this.addNode('START', 'START'); + const startNode = this.addNode("START", "START"); // `entry` will be non-null for any valid code this.addEdge(startNode, entry); this.entry = startNode; @@ -132,7 +160,11 @@ export class CFGBuilder { return id; } - private addEdge(source: string, target: string, type: EdgeType = 'regular'): void { + private addEdge( + source: string, + target: string, + type: EdgeType = "regular", + ): void { if (source === undefined) { throw new Error("Undefined source"); } else if (target === undefined) { @@ -145,67 +177,74 @@ export class CFGBuilder { private getChildFieldText(node: Node, fieldName: string): string { const child = node.childForFieldName(fieldName); - return child ? child.text : ''; + return child ? child.text : ""; } private processBlock(node: Node | null): BasicBlock { if (!node) return { entry: null, exit: null }; switch (node.type) { - case 'block': + case "block": return this.processStatements(node.namedChildren); - case 'if_statement': + case "if_statement": return this.processIfStatement(node); - case 'for_statement': + case "for_statement": return this.processForStatement(node); - case 'expression_switch_statement': + case "expression_switch_statement": return this.processSwitchStatement(node); - case 'return_statement': { - const returnNode = this.addNode('RETURN', node.text); + case "return_statement": { + const returnNode = this.addNode("RETURN", node.text); return { entry: returnNode, exit: null }; } - case 'break_statement': + case "break_statement": return this.processBreakStatement(node); - case 'continue_statement': + case "continue_statement": return this.processContinueStatement(node); - case 'labeled_statement': + case "labeled_statement": return this.processLabeledStatement(node); - case 'goto_statement': + case "goto_statement": return this.processGotoStatement(node); - case 'type_switch_statement': + case "type_switch_statement": return this.processTypeSwitchStatement(node); - case 'select_statement': + case "select_statement": return this.processSelectStatement(node); default: { - const newNode = this.addNode('STATEMENT', node.text); + const newNode = this.addNode("STATEMENT", node.text); return { entry: newNode, exit: newNode }; } } } - private processSwitchlike(switchlikeSyntax: Parser.SyntaxNode, props: SwitchlikeProps): BasicBlock { - const { nodeType, mergeType, mergeCode, caseName, caseTypeName, caseFieldName } = props; + private processSwitchlike( + switchlikeSyntax: Parser.SyntaxNode, + props: SwitchlikeProps, + ): BasicBlock { + const { + nodeType, + mergeType, + mergeCode, + caseName, + caseTypeName, + caseFieldName, + } = props; const blockHandler = new BlockHandler(); const valueNode = this.addNode( nodeType, - this.getChildFieldText(switchlikeSyntax, 'value') + this.getChildFieldText(switchlikeSyntax, "value"), ); const mergeNode = this.addNode(mergeType, mergeCode); let previous = { node: valueNode, branchType: "regular" as EdgeType }; switchlikeSyntax.namedChildren - .filter(child => child.type === caseName) + .filter((child) => child.type === caseName) .forEach((caseNode) => { const caseType = this.getChildFieldText(caseNode, caseFieldName); - const caseConditionNode = this.addNode( - caseTypeName, - caseType - ); + const caseConditionNode = this.addNode(caseTypeName, caseType); - const caseBlock = blockHandler.update(this.processStatements( - caseNode.namedChildren.slice(1) - )); + const caseBlock = blockHandler.update( + this.processStatements(caseNode.namedChildren.slice(1)), + ); if (caseBlock.entry) { this.addEdge(caseConditionNode, caseBlock.entry, "consequence"); } @@ -214,12 +253,19 @@ export class CFGBuilder { } this.addEdge(previous.node, caseConditionNode, previous.branchType); - previous = { node: caseConditionNode, branchType: "alternative" } + previous = { + node: caseConditionNode, + branchType: "alternative", + }; }); - const defaultCase = switchlikeSyntax.namedChildren.find(child => child.type === 'default_case'); + const defaultCase = switchlikeSyntax.namedChildren.find( + (child) => child.type === "default_case", + ); if (defaultCase !== undefined) { - const defaultBlock = blockHandler.update(this.processStatements(defaultCase.namedChildren)); + const defaultBlock = blockHandler.update( + this.processStatements(defaultCase.namedChildren), + ); this.addEdge(previous.node, defaultBlock.entry, previous.branchType); this.addEdge(defaultBlock.entry, mergeNode); } else { @@ -228,7 +274,7 @@ export class CFGBuilder { blockHandler.forEachBreak((breakNode) => { this.addEdge(breakNode, mergeNode); - }) + }); return blockHandler.update({ entry: valueNode, exit: mergeNode }); } @@ -243,7 +289,9 @@ export class CFGBuilder { nodeType: "SELECT", }); } - private processTypeSwitchStatement(switchSyntax: Parser.SyntaxNode): BasicBlock { + private processTypeSwitchStatement( + switchSyntax: Parser.SyntaxNode, + ): BasicBlock { return this.processSwitchlike(switchSyntax, { caseFieldName: "value", caseName: "type_case", @@ -265,18 +313,30 @@ export class CFGBuilder { } private processGotoStatement(gotoSyntax: Parser.SyntaxNode): BasicBlock { const name = gotoSyntax.firstNamedChild.text; - const gotoNode = this.addNode('GOTO', name); - return { entry: gotoNode, exit: null, gotos: [{ node: gotoNode, label: name }] } + const gotoNode = this.addNode("GOTO", name); + return { + entry: gotoNode, + exit: null, + gotos: [{ node: gotoNode, label: name }], + }; } private processLabeledStatement(labelSyntax: Parser.SyntaxNode): BasicBlock { const blockHandler = new BlockHandler(); const name = this.getChildFieldText(labelSyntax, "label"); const labelNode = this.addNode("LABEL", name); - const { entry: labeledEntry, exit: labeledExit } = blockHandler.update(this.processBlock(labelSyntax.namedChildren[1])) - if (labeledEntry) this.addEdge(labelNode, labeledEntry) - return blockHandler.update({ entry: labelNode, exit: labeledExit, labels: new Map([[name, labelNode]]) }); + const { entry: labeledEntry, exit: labeledExit } = blockHandler.update( + this.processBlock(labelSyntax.namedChildren[1]), + ); + if (labeledEntry) this.addEdge(labelNode, labeledEntry); + return blockHandler.update({ + entry: labelNode, + exit: labeledExit, + labels: new Map([[name, labelNode]]), + }); } - private processContinueStatement(_continueSyntax: Parser.SyntaxNode): BasicBlock { + private processContinueStatement( + _continueSyntax: Parser.SyntaxNode, + ): BasicBlock { const continueNode = this.addNode("CONTINUE", "CONTINUE"); return { entry: continueNode, exit: null, continues: [continueNode] }; } @@ -289,58 +349,67 @@ export class CFGBuilder { const blockHandler = new BlockHandler(); // Ignore comments - const codeStatements = statements.filter(syntax => syntax.type !== "comment"); + const codeStatements = statements.filter( + (syntax) => syntax.type !== "comment", + ); if (codeStatements.length === 0) { - const emptyNode = this.addNode('EMPTY', 'empty block'); + const emptyNode = this.addNode("EMPTY", "empty block"); return { entry: emptyNode, exit: emptyNode }; } let entry: string | null = null; let previous: string | null = null; for (const statement of codeStatements) { - const { entry: currentEntry, exit: currentExit } = blockHandler.update(this.processBlock(statement)); + const { entry: currentEntry, exit: currentExit } = blockHandler.update( + this.processBlock(statement), + ); if (!entry) entry = currentEntry; if (previous && currentEntry) this.addEdge(previous, currentEntry); previous = currentExit; } - return blockHandler.update({ entry, exit: previous }) + return blockHandler.update({ entry, exit: previous }); } - private processIfStatement(ifNode: Node, mergeNode: string | null = null): BasicBlock { + private processIfStatement( + ifNode: Node, + mergeNode: string | null = null, + ): BasicBlock { const blockHandler = new BlockHandler(); - const conditionChild = ifNode.childForFieldName('condition'); + const conditionChild = ifNode.childForFieldName("condition"); const conditionNode = this.addNode( - 'CONDITION', - conditionChild ? conditionChild.text : 'Unknown condition' + "CONDITION", + conditionChild ? conditionChild.text : "Unknown condition", ); - mergeNode ??= this.addNode('MERGE', 'MERGE'); - + mergeNode ??= this.addNode("MERGE", "MERGE"); - const consequenceChild = ifNode.childForFieldName('consequence'); + const consequenceChild = ifNode.childForFieldName("consequence"); - const { entry: thenEntry, exit: thenExit } = blockHandler.update(this.processBlock(consequenceChild)); + const { entry: thenEntry, exit: thenExit } = blockHandler.update( + this.processBlock(consequenceChild), + ); - const alternativeChild = ifNode.childForFieldName('alternative'); - const elseIf = alternativeChild?.type === "if_statement" + const alternativeChild = ifNode.childForFieldName("alternative"); + const elseIf = alternativeChild?.type === "if_statement"; const { entry: elseEntry, exit: elseExit } = (() => { if (elseIf) { - return blockHandler.update(this.processIfStatement(alternativeChild, mergeNode)); + return blockHandler.update( + this.processIfStatement(alternativeChild, mergeNode), + ); } else { return blockHandler.update(this.processBlock(alternativeChild)); } })(); - - this.addEdge(conditionNode, thenEntry || mergeNode, 'consequence'); + this.addEdge(conditionNode, thenEntry || mergeNode, "consequence"); if (thenExit) this.addEdge(thenExit, mergeNode); if (elseEntry) { - this.addEdge(conditionNode, elseEntry, 'alternative'); + this.addEdge(conditionNode, elseEntry, "alternative"); if (elseExit && !elseIf) this.addEdge(elseExit, mergeNode); } else { - this.addEdge(conditionNode, mergeNode, 'alternative'); + this.addEdge(conditionNode, mergeNode, "alternative"); } return blockHandler.update({ entry: conditionNode, exit: mergeNode }); @@ -352,7 +421,9 @@ export class CFGBuilder { // One child means only loop body, two children means loop head. case 1: { const headNode = this.addNode("LOOP_HEAD", "loop head"); - const { entry: bodyEntry, exit: bodyExit } = blockHandler.update(this.processBlock(forNode.firstNamedChild)); + const { entry: bodyEntry, exit: bodyExit } = blockHandler.update( + this.processBlock(forNode.firstNamedChild), + ); if (bodyEntry) this.addEdge(headNode, bodyEntry); if (bodyExit) this.addEdge(bodyExit, headNode); const exitNode = this.addNode("LOOP_EXIT", "loop exit"); @@ -368,7 +439,9 @@ export class CFGBuilder { // TODO: Handle the case where there is no loop condition, only init and update. case 2: { const headNode = this.addNode("LOOP_HEAD", "loop head"); - const { entry: bodyEntry, exit: bodyExit } = blockHandler.update(this.processBlock(forNode.namedChildren[1])); + const { entry: bodyEntry, exit: bodyExit } = blockHandler.update( + this.processBlock(forNode.namedChildren[1]), + ); const exitNode = this.addNode("LOOP_EXIT", "loop exit"); if (bodyEntry) { this.addEdge(headNode, bodyEntry, "consequence"); @@ -385,9 +458,9 @@ export class CFGBuilder { return blockHandler.update({ entry: headNode, exit: exitNode }); } default: - throw new Error(`Unsupported for type: ${forNode.firstNamedChild?.type}`) + throw new Error( + `Unsupported for type: ${forNode.firstNamedChild?.type}`, + ); } } - - -} \ No newline at end of file +} diff --git a/src/control-flow/graph-ops.ts b/src/control-flow/graph-ops.ts index 0eeebf5..bded9e4 100644 --- a/src/control-flow/graph-ops.ts +++ b/src/control-flow/graph-ops.ts @@ -4,76 +4,87 @@ import { bfsFromNode } from "graphology-traversal"; import type { CFG } from "./cfg"; export function distanceFromEntry(cfg: CFG): Map { - const { graph, entry } = cfg; - const levels = new Map(); + const { graph, entry } = cfg; + const levels = new Map(); - bfsFromNode(graph, entry, (node, attr, depth) => { - levels.set(node, depth); - }); + bfsFromNode(graph, entry, (node, attr, depth) => { + levels.set(node, depth); + }); - return levels; + return levels; } export type AttrMerger = (nodeAttrs: object, intoAttrs: object) => object; -function collapseNode(graph: MultiDirectedGraph, node: string, into: string, mergeAttrs?: AttrMerger) { - graph.forEachEdge(node, (edge, attributes, source, target) => { - if (target === into) { - return; - } +function collapseNode( + graph: MultiDirectedGraph, + node: string, + into: string, + mergeAttrs?: AttrMerger, +) { + graph.forEachEdge(node, (edge, attributes, source, target) => { + if (target === into) { + return; + } - const replaceNode = (n: string) => (n === node ? into : n); - const edgeNodes = [replaceNode(source), replaceNode(target)] as const; - graph.addEdge(...edgeNodes, attributes); - }) - if (mergeAttrs) { - const attrs = mergeAttrs(graph.getNodeAttributes(node), graph.getNodeAttributes(into)) - for (const [name, value] of Object.entries(attrs)) { - graph.setNodeAttribute(into, name, value); - } + const replaceNode = (n: string) => (n === node ? into : n); + const edgeNodes = [replaceNode(source), replaceNode(target)] as const; + graph.addEdge(...edgeNodes, attributes); + }); + if (mergeAttrs) { + const attrs = mergeAttrs( + graph.getNodeAttributes(node), + graph.getNodeAttributes(into), + ); + for (const [name, value] of Object.entries(attrs)) { + graph.setNodeAttribute(into, name, value); } - graph.dropNode(node); + } + graph.dropNode(node); } /** - * + * * @param graph The graph to simplify */ export function simplifyCFG(cfg: CFG, mergeAttrs?: AttrMerger): CFG { - const graph = cfg.graph.copy(); + const graph = cfg.graph.copy(); - const toCollapse: string[][] = graph.mapEdges((edge, attrs, source, target) => { - if (graph.outDegree(source) === 1 && graph.inDegree(target) === 1) { - return [source, target]; - } - return null; - }).filter(x => x) as string[][]; + const toCollapse: string[][] = graph + .mapEdges((edge, attrs, source, target) => { + if (graph.outDegree(source) === 1 && graph.inDegree(target) === 1) { + return [source, target]; + } + return null; + }) + .filter((x) => x) as string[][]; - // Sort merges based on topological order - const levels = distanceFromEntry(cfg); - toCollapse.sort((a, b) => (levels.get(a[0]) ?? 0) - (levels.get(b[0]) ?? 0)); + // Sort merges based on topological order + const levels = distanceFromEntry(cfg); + toCollapse.sort((a, b) => (levels.get(a[0]) ?? 0) - (levels.get(b[0]) ?? 0)); - let entry = cfg.entry; + let entry = cfg.entry; - try { - toCollapse.forEach(([source, target]) => { - collapseNode(graph, source, target, mergeAttrs); - if (entry === source) { - // Keep track of the entry node! - entry = target; - } - }); - } catch (error) { - console.log(error); - } + try { + toCollapse.forEach(([source, target]) => { + collapseNode(graph, source, target, mergeAttrs); + if (entry === source) { + // Keep track of the entry node! + entry = target; + } + }); + } catch (error) { + console.log(error); + } - return { graph, entry }; + return { graph, entry }; } - export function trimFor(cfg: CFG): CFG { - const { graph, entry } = cfg; - const reachable: string[] = []; + const { graph, entry } = cfg; + const reachable: string[] = []; - bfsFromNode(graph, entry, (node) => { reachable.push(node); }); + bfsFromNode(graph, entry, (node) => { + reachable.push(node); + }); - return { graph: subgraph(graph, reachable), entry }; -} \ No newline at end of file + return { graph: subgraph(graph, reachable), entry }; +} diff --git a/src/control-flow/render.ts b/src/control-flow/render.ts index 1d6d63c..1011e9b 100644 --- a/src/control-flow/render.ts +++ b/src/control-flow/render.ts @@ -1,65 +1,66 @@ import { distanceFromEntry } from "./graph-ops"; import type { CFG } from "./cfg"; - export function graphToDot(cfg: CFG, verbose: boolean = false): string { - const graph = cfg.graph; - let dotContent = `digraph "" {\n node [shape=box];\n edge [headport=n tailport=s]\n bgcolor="transparent"\n`; - const levels = distanceFromEntry(cfg); - graph.forEachNode((node) => { - - let label = ""; - if (verbose) { - label = `${node} ${graph.getNodeAttributes(node).type} ${graph.getNodeAttributes(node).code}` - } - // const label = ""; - // .replace(/"/g, '\\"') - // .replace(/\n/g, "\\n"); + const graph = cfg.graph; + let dotContent = `digraph "" {\n node [shape=box];\n edge [headport=n tailport=s]\n bgcolor="transparent"\n`; + const levels = distanceFromEntry(cfg); + graph.forEachNode((node) => { + let label = ""; + if (verbose) { + label = `${node} ${graph.getNodeAttributes(node).type} ${graph.getNodeAttributes(node).code}`; + } + // const label = ""; + // .replace(/"/g, '\\"') + // .replace(/\n/g, "\\n"); - // const label = `${graph.getNodeAttribute(node, "line") || ""}`; - // label = `${levels.get(node)}`; + // const label = `${graph.getNodeAttribute(node, "line") || ""}`; + // label = `${levels.get(node)}`; - let shape = "box"; - let fillColor = "lightgray"; - let minHeight = 0.2; - if (graph.degree(node) === 0) { - minHeight = 0.5; - } else if (graph.inDegree(node) === 0) { - shape = "invhouse"; - fillColor = "#48AB30"; - minHeight = 0.5; - } else if (graph.outDegree(node) === 0) { - shape = "house"; - fillColor = "#AB3030"; - minHeight = 0.5; - } + let shape = "box"; + let fillColor = "lightgray"; + let minHeight = 0.2; + if (graph.degree(node) === 0) { + minHeight = 0.5; + } else if (graph.inDegree(node) === 0) { + shape = "invhouse"; + fillColor = "#48AB30"; + minHeight = 0.5; + } else if (graph.outDegree(node) === 0) { + shape = "house"; + fillColor = "#AB3030"; + minHeight = 0.5; + } - const height = Math.max(graph.getNodeAttribute(node, "lines") * 0.3, minHeight); - dotContent += ` ${node} [label="${label}" shape="${shape}" fillcolor="${fillColor}" style="filled" height=${height}];\n`; - }); + const height = Math.max( + graph.getNodeAttribute(node, "lines") * 0.3, + minHeight, + ); + dotContent += ` ${node} [label="${label}" shape="${shape}" fillcolor="${fillColor}" style="filled" height=${height}];\n`; + }); - graph.forEachEdge((edge, attributes, source, target) => { - let penwidth = 1; - let color = "blue"; - switch (attributes.type) { - case "consequence": - color = "green"; - break; - case "alternative": - color = "red"; - break; - default: - color = "blue"; - } - // if (graph.getNodeAttribute(source, "line") > graph.getNodeAttribute(target, "line")) { - // penwidth = 2; - // } - if ((levels.get(source) ?? 0) > (levels.get(target) ?? 0)) { - penwidth = 2; - } - dotContent += ` ${source} -> ${target} [penwidth=${penwidth} color=${color}];\n`; - }); + graph.forEachEdge((edge, attributes, source, target) => { + let penwidth = 1; + let color = "blue"; + switch (attributes.type) { + case "consequence": + color = "green"; + break; + case "alternative": + color = "red"; + break; + default: + color = "blue"; + } + // if (graph.getNodeAttribute(source, "line") > graph.getNodeAttribute(target, "line")) { + // penwidth = 2; + // } + if ((levels.get(source) ?? 0) > (levels.get(target) ?? 0)) { + penwidth = 2; + } + dotContent += ` ${source} -> ${target} [penwidth=${penwidth} color=${color}];\n`; + }); - dotContent += "}"; - return dotContent; + dotContent += "}"; + return dotContent; } diff --git a/src/frontend/src/main.js b/src/frontend/src/main.js index 8a909a1..7ece250 100644 --- a/src/frontend/src/main.js +++ b/src/frontend/src/main.js @@ -1,8 +1,8 @@ -import './app.css' -import App from './App.svelte' +import "./app.css"; +import App from "./App.svelte"; const app = new App({ - target: document.getElementById('app'), -}) + target: document.getElementById("app"), +}); -export default app +export default app; diff --git a/src/frontend/svelte.config.js b/src/frontend/svelte.config.js index b0683fd..de2ddd6 100644 --- a/src/frontend/svelte.config.js +++ b/src/frontend/svelte.config.js @@ -1,7 +1,7 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -} +}; diff --git a/src/frontend/vite.config.js b/src/frontend/vite.config.js index 79480dc..5ac8ecf 100644 --- a/src/frontend/vite.config.js +++ b/src/frontend/vite.config.js @@ -1,8 +1,7 @@ -import { defineConfig } from 'vite' -import { svelte } from '@sveltejs/vite-plugin-svelte' - +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [svelte()], -}) +}); diff --git a/src/test/nodecount.test.ts b/src/test/nodecount.test.ts index d47e6ea..3092597 100644 --- a/src/test/nodecount.test.ts +++ b/src/test/nodecount.test.ts @@ -1,7 +1,7 @@ import { expect, test } from "bun:test"; import Parser from "web-tree-sitter"; -import goSampleCode from "./nodecount.go" with {type: "text"}; -import treeSitterGo from "../../parsers/tree-sitter-go.wasm?url" +import goSampleCode from "./nodecount.go" with { type: "text" }; +import treeSitterGo from "../../parsers/tree-sitter-go.wasm?url"; import { CFGBuilder, type CFG } from "../control-flow/cfg"; import { simplifyCFG } from "../control-flow/graph-ops"; @@ -16,7 +16,6 @@ async function initializeParser() { const parser = await initializeParser(); const tree = parser.parse(goSampleCode); - interface Requirements { nodes: number; } @@ -34,7 +33,7 @@ function parseComment(text: string): Requirements { .replaceAll(/:/gm, '":') .replaceAll(/$/gm, ",") .replaceAll(/,$/gm, ""); - return JSON.parse(`{${jsonContent}}`) + return JSON.parse(`{${jsonContent}}`); } function* iterTestFunctions(tree: Parser.Tree): Generator { @@ -48,7 +47,9 @@ function* iterTestFunctions(tree: Parser.Tree): Generator { const commentNode = tree.rootNode.children[i]; const functionNode = tree.rootNode.children[i + 1]; - if (!funcTypes.includes(functionNode.type)) { continue; } + if (!funcTypes.includes(functionNode.type)) { + continue; + } const functionName = functionNode.childForFieldName("name")?.text as string; if (commentNode.type != "comment") { @@ -56,10 +57,14 @@ function* iterTestFunctions(tree: Parser.Tree): Generator { } try { - yield { function: functionNode, reqs: parseComment(commentNode.text), name: functionName } + yield { + function: functionNode, + reqs: parseComment(commentNode.text), + name: functionName, + }; } catch (error) { if (error instanceof SyntaxError) { - throw new Error(`invalid JSON comment on ${functionName}`) + throw new Error(`invalid JSON comment on ${functionName}`); } else { throw error; } @@ -76,16 +81,15 @@ function buildSimpleCFG(functionNode: Parser.SyntaxNode): CFG { return simplifyCFG(buildCFG(functionNode)); } - - const testFunctions = [...iterTestFunctions(tree)]; -const testMap = new Map(testFunctions.map(testFunc => [testFunc.name, testFunc])); -const testNames = [...testMap.keys()] +const testMap = new Map( + testFunctions.map((testFunc) => [testFunc.name, testFunc]), +); +const testNames = [...testMap.keys()]; test.each(testNames)("Node count for %s", (name) => { const testFunc = testMap.get(name) as TestFunction; expect(testFunc).toBeDefined(); const cfg = buildSimpleCFG(testFunc.function); expect(cfg.graph.order).toBe(testFunc.reqs.nodes); - -}) \ No newline at end of file +}); diff --git a/src/vscode/extension.ts b/src/vscode/extension.ts index 74da923..a01a412 100644 --- a/src/vscode/extension.ts +++ b/src/vscode/extension.ts @@ -1,156 +1,198 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode'; -import Parser, { type SyntaxNode } from 'web-tree-sitter'; +import * as vscode from "vscode"; +import Parser, { type SyntaxNode } from "web-tree-sitter"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; -import { CFGBuilder } from '../control-flow/cfg'; -import { graphToDot } from '../control-flow/render'; -import { simplifyCFG, trimFor } from '../control-flow/graph-ops'; +import { CFGBuilder } from "../control-flow/cfg"; +import { graphToDot } from "../control-flow/render"; +import { simplifyCFG, trimFor } from "../control-flow/graph-ops"; let graphviz: Graphviz; -async function initializeParser(context: vscode.ExtensionContext, languagePath: string) { - await Parser.init({ - locateFile(scriptName: string, scriptDirectory: string) { - console.log("name", scriptName, "dir", scriptDirectory); - return vscode.Uri.joinPath(context.extensionUri, "parsers", "tree-sitter.wasm").fsPath; - }, - }); - const parser = new Parser(); - const Go = await Parser.Language.load(languagePath); - parser.setLanguage(Go); - return parser; +async function initializeParser( + context: vscode.ExtensionContext, + languagePath: string, +) { + await Parser.init({ + locateFile(scriptName: string, scriptDirectory: string) { + console.log("name", scriptName, "dir", scriptDirectory); + return vscode.Uri.joinPath( + context.extensionUri, + "parsers", + "tree-sitter.wasm", + ).fsPath; + }, + }); + const parser = new Parser(); + const Go = await Parser.Language.load(languagePath); + parser.setLanguage(Go); + return parser; } function getCurrentGoCode(): string | null { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return null; - } + const editor = vscode.window.activeTextEditor; + if (!editor) { + return null; + } - const document = editor.document; - if (document.languageId !== 'go') { - return null; - } + const document = editor.document; + if (document.languageId !== "go") { + return null; + } - return document.getText(); + return document.getText(); } // This method is called when your extension is activated // Your extension is activated the very first time the command is executed export async function activate(context: vscode.ExtensionContext) { - graphviz = await Graphviz.load(); - - const provider = new OverviewViewProvider(context.extensionUri); - - context.subscriptions.push( - vscode.window.registerWebviewViewProvider(OverviewViewProvider.viewType, provider)); - - // Use the console to output diagnostic information (console.log) and errors (console.error) - // This line of code will only be executed once when your extension is activated - console.log('Congratulations, your extension "function-graph-overview" is now active!'); - - const wasmPath = vscode.Uri.joinPath(context.extensionUri, "parsers", "tree-sitter-go.wasm"); - const parser = await initializeParser(context, wasmPath.fsPath); - - - const cursorMove = vscode.window.onDidChangeTextEditorSelection((event: vscode.TextEditorSelectionChangeEvent) => { - const editor = event.textEditor; - const position = editor.selection.active; - - const code = getCurrentGoCode() ?? ""; - const tree = parser.parse(code); - - console.log(`Cursor position changed: Line ${position.line + 1}, Column ${position.character + 1}`); - let node: SyntaxNode | null = tree.rootNode.descendantForPosition({ row: position.line, column: position.character }); - const funcTypes = [ - "function_declaration", - "method_declaration", - "func_literal", - ]; - - while (node) { - if (funcTypes.includes(node.type)) { - break; - } - node = node.parent; - } - - if (node) { - console.log(node); - const nameNode = node.childForFieldName("name"); - if (nameNode) { - const name = editor.document.getText(new vscode.Range(new vscode.Position(nameNode.startPosition.row, nameNode.startPosition.column), new vscode.Position(nameNode.endPosition.row, nameNode.endPosition.column))); - console.log("Currently in", name); - } - - const builder = new CFGBuilder(); - let cfg = builder.buildCFG(node); - cfg = trimFor(cfg); - if (vscode.workspace.getConfiguration("functionGraphOverview").get("simplify")) { - cfg = simplifyCFG(cfg); - } - const dot = graphToDot(cfg); - const svg = graphviz.dot(dot); - provider.setSVG(svg); - } - }); - - context.subscriptions.push(cursorMove); + graphviz = await Graphviz.load(); + + const provider = new OverviewViewProvider(context.extensionUri); + + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + OverviewViewProvider.viewType, + provider, + ), + ); + + // Use the console to output diagnostic information (console.log) and errors (console.error) + // This line of code will only be executed once when your extension is activated + console.log( + 'Congratulations, your extension "function-graph-overview" is now active!', + ); + + const wasmPath = vscode.Uri.joinPath( + context.extensionUri, + "parsers", + "tree-sitter-go.wasm", + ); + const parser = await initializeParser(context, wasmPath.fsPath); + + const cursorMove = vscode.window.onDidChangeTextEditorSelection( + (event: vscode.TextEditorSelectionChangeEvent) => { + const editor = event.textEditor; + const position = editor.selection.active; + + const code = getCurrentGoCode() ?? ""; + const tree = parser.parse(code); + + console.log( + `Cursor position changed: Line ${position.line + 1}, Column ${position.character + 1}`, + ); + let node: SyntaxNode | null = tree.rootNode.descendantForPosition({ + row: position.line, + column: position.character, + }); + const funcTypes = [ + "function_declaration", + "method_declaration", + "func_literal", + ]; + + while (node) { + if (funcTypes.includes(node.type)) { + break; + } + node = node.parent; + } + + if (node) { + console.log(node); + const nameNode = node.childForFieldName("name"); + if (nameNode) { + const name = editor.document.getText( + new vscode.Range( + new vscode.Position( + nameNode.startPosition.row, + nameNode.startPosition.column, + ), + new vscode.Position( + nameNode.endPosition.row, + nameNode.endPosition.column, + ), + ), + ); + console.log("Currently in", name); + } + + const builder = new CFGBuilder(); + let cfg = builder.buildCFG(node); + cfg = trimFor(cfg); + if ( + vscode.workspace + .getConfiguration("functionGraphOverview") + .get("simplify") + ) { + cfg = simplifyCFG(cfg); + } + const dot = graphToDot(cfg); + const svg = graphviz.dot(dot); + provider.setSVG(svg); + } + }, + ); + + context.subscriptions.push(cursorMove); } // This method is called when your extension is deactivated -export function deactivate() { } - +export function deactivate() {} //------------------------------------------------ class OverviewViewProvider implements vscode.WebviewViewProvider { - public static readonly viewType = "functionGraphOverview.overview"; - - private _view?: vscode.WebviewView; - - constructor( - private readonly _extensionUri: vscode.Uri, - ) { } - - public setSVG(svg: string) { - if (this._view) { - this._view.webview.postMessage({ type: "svgImage", svg }); - } - } - - - resolveWebviewView(webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken): Thenable | void { - this._view = webviewView; - - webviewView.webview.options = { - // Allow scripts in the webview - enableScripts: true, - - localResourceRoots: [ - this._extensionUri - ] - }; - - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); - } - private _getHtmlForWebview(webview: vscode.Webview): string { - // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. - const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'webview-content', 'main.js')); - - // Do the same for the stylesheet. - const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'webview-content', 'reset.css')); - const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'webview-content', 'vscode.css')); - const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'webview-content', 'main.css')); - - // Use a nonce to only allow a specific script to be run. - const nonce = getNonce(); - - - - const svg = graphviz.dot('digraph G { Hello -> World }'); - return ` + public static readonly viewType = "functionGraphOverview.overview"; + + private _view?: vscode.WebviewView; + + constructor(private readonly _extensionUri: vscode.Uri) {} + + public setSVG(svg: string) { + if (this._view) { + this._view.webview.postMessage({ type: "svgImage", svg }); + } + } + + resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ): Thenable | void { + this._view = webviewView; + + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + + localResourceRoots: [this._extensionUri], + }; + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + } + private _getHtmlForWebview(webview: vscode.Webview): string { + // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "webview-content", "main.js"), + ); + + // Do the same for the stylesheet. + const styleResetUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "webview-content", "reset.css"), + ); + const styleVSCodeUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "webview-content", "vscode.css"), + ); + const styleMainUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "webview-content", "main.css"), + ); + + // Use a nonce to only allow a specific script to be run. + const nonce = getNonce(); + + const svg = graphviz.dot("digraph G { Hello -> World }"); + return ` @@ -180,14 +222,15 @@ class OverviewViewProvider implements vscode.WebviewViewProvider { `; - } + } } function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} \ No newline at end of file + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/tsconfig.json b/tsconfig.json index dc6f6e4..cd3df1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,23 @@ { - "compilerOptions": { - "lib": [ - "ESNext" - ], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - /* Linting */ - "skipLibCheck": true, - "strict": true, - "noFallthroughCasesInSwitch": true, - "forceConsistentCasingInFileNames": true, - "paths": { - "vscode": [ - "./mocks/vscode.ts" - ] - } - } -} \ No newline at end of file + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "paths": { + "vscode": ["./mocks/vscode.ts"] + } + } +} diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md index f518bb8..e2d793c 100644 --- a/vsc-extension-quickstart.md +++ b/vsc-extension-quickstart.md @@ -2,47 +2,45 @@ ## What's in the folder -* This folder contains all of the files necessary for your extension. -* `package.json` - this is the manifest file in which you declare your extension and command. - * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -* `src/extension.ts` - this is the main file where you will provide the implementation of your command. - * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. +- This folder contains all of the files necessary for your extension. +- `package.json` - this is the manifest file in which you declare your extension and command. + - The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +- `src/extension.ts` - this is the main file where you will provide the implementation of your command. + - The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + - We pass the function containing the implementation of the command as the second parameter to `registerCommand`. ## Setup -* install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) - +- install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) ## Get up and running straight away -* Press `F5` to open a new window with your extension loaded. -* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. -* Set breakpoints in your code inside `src/extension.ts` to debug your extension. -* Find output from your extension in the debug console. +- Press `F5` to open a new window with your extension loaded. +- Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. +- Set breakpoints in your code inside `src/extension.ts` to debug your extension. +- Find output from your extension in the debug console. ## Make changes -* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. - +- You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +- You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. ## Explore the API -* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. +- You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. ## Run tests -* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) -* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. -* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` -* See the output of the test result in the Test Results view. -* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. - * The provided test runner will only consider files matching the name pattern `**.test.ts`. - * You can create folders inside the `test` folder to structure your tests any way you want. +- Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) +- Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. +- Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` +- See the output of the test result in the Test Results view. +- Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. + - The provided test runner will only consider files matching the name pattern `**.test.ts`. + - You can create folders inside the `test` folder to structure your tests any way you want. ## Go further -* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). -* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. -* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). +- Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). +- [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. +- Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). diff --git a/webview-content/main.js b/webview-content/main.js index 7ba6dde..7c2475d 100644 --- a/webview-content/main.js +++ b/webview-content/main.js @@ -3,27 +3,23 @@ // This script will be run within the webview itself // It cannot access the main VS Code APIs directly. (function () { - // Handle messages sent from the extension to the webview - window.addEventListener('message', event => { - const message = event.data; // The json data that the extension sent - switch (message.type) { - case 'svgImage': - { - displaySVG(message.svg); - break; - } - } - }); - - /** - * - * @param {string} svgMarkup - */ - function displaySVG(svgMarkup) { - const div = document.querySelector("#overview"); - div.innerHTML = svgMarkup; + // Handle messages sent from the extension to the webview + window.addEventListener("message", (event) => { + const message = event.data; // The json data that the extension sent + switch (message.type) { + case "svgImage": { + displaySVG(message.svg); + break; + } } + }); -}()); - - + /** + * + * @param {string} svgMarkup + */ + function displaySVG(svgMarkup) { + const div = document.querySelector("#overview"); + div.innerHTML = svgMarkup; + } +})(); diff --git a/webview-content/reset.css b/webview-content/reset.css index 92d0291..0e3b4cf 100644 --- a/webview-content/reset.css +++ b/webview-content/reset.css @@ -1,12 +1,12 @@ html { - box-sizing: border-box; - font-size: 13px; + box-sizing: border-box; + font-size: 13px; } *, *:before, *:after { - box-sizing: inherit; + box-sizing: inherit; } body, @@ -19,12 +19,12 @@ h6, p, ol, ul { - margin: 0; - padding: 0; - font-weight: normal; + margin: 0; + padding: 0; + font-weight: normal; } img { - max-width: 100%; - height: auto; + max-width: 100%; + height: auto; } diff --git a/webview-content/vscode.css b/webview-content/vscode.css index 5d12b7e..16493d8 100644 --- a/webview-content/vscode.css +++ b/webview-content/vscode.css @@ -1,91 +1,91 @@ :root { - --container-paddding: 20px; - --input-padding-vertical: 6px; - --input-padding-horizontal: 4px; - --input-margin-vertical: 4px; - --input-margin-horizontal: 0; + --container-paddding: 20px; + --input-padding-vertical: 6px; + --input-padding-horizontal: 4px; + --input-margin-vertical: 4px; + --input-margin-horizontal: 0; } body { - padding: 0 var(--container-paddding); - color: var(--vscode-foreground); - font-size: var(--vscode-font-size); - font-weight: var(--vscode-font-weight); - font-family: var(--vscode-font-family); - background-color: var(--vscode-editor-background); + padding: 0 var(--container-paddding); + color: var(--vscode-foreground); + font-size: var(--vscode-font-size); + font-weight: var(--vscode-font-weight); + font-family: var(--vscode-font-family); + background-color: var(--vscode-editor-background); } ol, ul { - padding-left: var(--container-paddding); + padding-left: var(--container-paddding); } body > *, form > * { - margin-block-start: var(--input-margin-vertical); - margin-block-end: var(--input-margin-vertical); + margin-block-start: var(--input-margin-vertical); + margin-block-end: var(--input-margin-vertical); } *:focus { - outline-color: var(--vscode-focusBorder) !important; + outline-color: var(--vscode-focusBorder) !important; } a { - color: var(--vscode-textLink-foreground); + color: var(--vscode-textLink-foreground); } a:hover, a:active { - color: var(--vscode-textLink-activeForeground); + color: var(--vscode-textLink-activeForeground); } code { - font-size: var(--vscode-editor-font-size); - font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); } button { - border: none; - padding: var(--input-padding-vertical) var(--input-padding-horizontal); - width: 100%; - text-align: center; - outline: 1px solid transparent; - outline-offset: 2px !important; - color: var(--vscode-button-foreground); - background: var(--vscode-button-background); + border: none; + padding: var(--input-padding-vertical) var(--input-padding-horizontal); + width: 100%; + text-align: center; + outline: 1px solid transparent; + outline-offset: 2px !important; + color: var(--vscode-button-foreground); + background: var(--vscode-button-background); } button:hover { - cursor: pointer; - background: var(--vscode-button-hoverBackground); + cursor: pointer; + background: var(--vscode-button-hoverBackground); } button:focus { - outline-color: var(--vscode-focusBorder); + outline-color: var(--vscode-focusBorder); } button.secondary { - color: var(--vscode-button-secondaryForeground); - background: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + background: var(--vscode-button-secondaryBackground); } button.secondary:hover { - background: var(--vscode-button-secondaryHoverBackground); + background: var(--vscode-button-secondaryHoverBackground); } -input:not([type='checkbox']), +input:not([type="checkbox"]), textarea { - display: block; - width: 100%; - border: none; - font-family: var(--vscode-font-family); - padding: var(--input-padding-vertical) var(--input-padding-horizontal); - color: var(--vscode-input-foreground); - outline-color: var(--vscode-input-border); - background-color: var(--vscode-input-background); + display: block; + width: 100%; + border: none; + font-family: var(--vscode-font-family); + padding: var(--input-padding-vertical) var(--input-padding-horizontal); + color: var(--vscode-input-foreground); + outline-color: var(--vscode-input-border); + background-color: var(--vscode-input-background); } input::placeholder, textarea::placeholder { - color: var(--vscode-input-placeholderForeground); + color: var(--vscode-input-placeholderForeground); }