PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/golang/1.22.0/src/go/doc/comment
Viewing File: /opt/golang/1.22.0/src/go/doc/comment/parse.go
// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package comment import ( "slices" "strings" "unicode" "unicode/utf8" ) // A Doc is a parsed Go doc comment. type Doc struct { // Content is the sequence of content blocks in the comment. Content []Block // Links is the link definitions in the comment. Links []*LinkDef } // A LinkDef is a single link definition. type LinkDef struct { Text string // the link text URL string // the link URL Used bool // whether the comment uses the definition } // A Block is block-level content in a doc comment, // one of [*Code], [*Heading], [*List], or [*Paragraph]. type Block interface { block() } // A Heading is a doc comment heading. type Heading struct { Text []Text // the heading text } func (*Heading) block() {} // A List is a numbered or bullet list. // Lists are always non-empty: len(Items) > 0. // In a numbered list, every Items[i].Number is a non-empty string. // In a bullet list, every Items[i].Number is an empty string. type List struct { // Items is the list items. Items []*ListItem // ForceBlankBefore indicates that the list must be // preceded by a blank line when reformatting the comment, // overriding the usual conditions. See the BlankBefore method. // // The comment parser sets ForceBlankBefore for any list // that is preceded by a blank line, to make sure // the blank line is preserved when printing. ForceBlankBefore bool // ForceBlankBetween indicates that list items must be // separated by blank lines when reformatting the comment, // overriding the usual conditions. See the BlankBetween method. // // The comment parser sets ForceBlankBetween for any list // that has a blank line between any two of its items, to make sure // the blank lines are preserved when printing. ForceBlankBetween bool } func (*List) block() {} // BlankBefore reports whether a reformatting of the comment // should include a blank line before the list. // The default rule is the same as for [BlankBetween]: // if the list item content contains any blank lines // (meaning at least one item has multiple paragraphs) // then the list itself must be preceded by a blank line. // A preceding blank line can be forced by setting [List].ForceBlankBefore. func (l *List) BlankBefore() bool { return l.ForceBlankBefore || l.BlankBetween() } // BlankBetween reports whether a reformatting of the comment // should include a blank line between each pair of list items. // The default rule is that if the list item content contains any blank lines // (meaning at least one item has multiple paragraphs) // then list items must themselves be separated by blank lines. // Blank line separators can be forced by setting [List].ForceBlankBetween. func (l *List) BlankBetween() bool { if l.ForceBlankBetween { return true } for _, item := range l.Items { if len(item.Content) != 1 { // Unreachable for parsed comments today, // since the only way to get multiple item.Content // is multiple paragraphs, which must have been // separated by a blank line. return true } } return false } // A ListItem is a single item in a numbered or bullet list. type ListItem struct { // Number is a decimal string in a numbered list // or an empty string in a bullet list. Number string // "1", "2", ...; "" for bullet list // Content is the list content. // Currently, restrictions in the parser and printer // require every element of Content to be a *Paragraph. Content []Block // Content of this item. } // A Paragraph is a paragraph of text. type Paragraph struct { Text []Text } func (*Paragraph) block() {} // A Code is a preformatted code block. type Code struct { // Text is the preformatted text, ending with a newline character. // It may be multiple lines, each of which ends with a newline character. // It is never empty, nor does it start or end with a blank line. Text string } func (*Code) block() {} // A Text is text-level content in a doc comment, // one of [Plain], [Italic], [*Link], or [*DocLink]. type Text interface { text() } // A Plain is a string rendered as plain text (not italicized). type Plain string func (Plain) text() {} // An Italic is a string rendered as italicized text. type Italic string func (Italic) text() {} // A Link is a link to a specific URL. type Link struct { Auto bool // is this an automatic (implicit) link of a literal URL? Text []Text // text of link URL string // target URL of link } func (*Link) text() {} // A DocLink is a link to documentation for a Go package or symbol. type DocLink struct { Text []Text // text of link // ImportPath, Recv, and Name identify the Go package or symbol // that is the link target. The potential combinations of // non-empty fields are: // - ImportPath: a link to another package // - ImportPath, Name: a link to a const, func, type, or var in another package // - ImportPath, Recv, Name: a link to a method in another package // - Name: a link to a const, func, type, or var in this package // - Recv, Name: a link to a method in this package ImportPath string // import path Recv string // receiver type, without any pointer star, for methods Name string // const, func, type, var, or method name } func (*DocLink) text() {} // A Parser is a doc comment parser. // The fields in the struct can be filled in before calling [Parser.Parse] // in order to customize the details of the parsing process. type Parser struct { // Words is a map of Go identifier words that // should be italicized and potentially linked. // If Words[w] is the empty string, then the word w // is only italicized. Otherwise it is linked, using // Words[w] as the link target. // Words corresponds to the [go/doc.ToHTML] words parameter. Words map[string]string // LookupPackage resolves a package name to an import path. // // If LookupPackage(name) returns ok == true, then [name] // (or [name.Sym] or [name.Sym.Method]) // is considered a documentation link to importPath's package docs. // It is valid to return "", true, in which case name is considered // to refer to the current package. // // If LookupPackage(name) returns ok == false, // then [name] (or [name.Sym] or [name.Sym.Method]) // will not be considered a documentation link, // except in the case where name is the full (but single-element) import path // of a package in the standard library, such as in [math] or [io.Reader]. // LookupPackage is still called for such names, // in order to permit references to imports of other packages // with the same package names. // // Setting LookupPackage to nil is equivalent to setting it to // a function that always returns "", false. LookupPackage func(name string) (importPath string, ok bool) // LookupSym reports whether a symbol name or method name // exists in the current package. // // If LookupSym("", "Name") returns true, then [Name] // is considered a documentation link for a const, func, type, or var. // // Similarly, if LookupSym("Recv", "Name") returns true, // then [Recv.Name] is considered a documentation link for // type Recv's method Name. // // Setting LookupSym to nil is equivalent to setting it to a function // that always returns false. LookupSym func(recv, name string) (ok bool) } // parseDoc is parsing state for a single doc comment. type parseDoc struct { *Parser *Doc links map[string]*LinkDef lines []string lookupSym func(recv, name string) bool } // lookupPkg is called to look up the pkg in [pkg], [pkg.Name], and [pkg.Name.Recv]. // If pkg has a slash, it is assumed to be the full import path and is returned with ok = true. // // Otherwise, pkg is probably a simple package name like "rand" (not "crypto/rand" or "math/rand"). // d.LookupPackage provides a way for the caller to allow resolving such names with reference // to the imports in the surrounding package. // // There is one collision between these two cases: single-element standard library names // like "math" are full import paths but don't contain slashes. We let d.LookupPackage have // the first chance to resolve it, in case there's a different package imported as math, // and otherwise we refer to a built-in list of single-element standard library package names. func (d *parseDoc) lookupPkg(pkg string) (importPath string, ok bool) { if strings.Contains(pkg, "/") { // assume a full import path if validImportPath(pkg) { return pkg, true } return "", false } if d.LookupPackage != nil { // Give LookupPackage a chance. if path, ok := d.LookupPackage(pkg); ok { return path, true } } return DefaultLookupPackage(pkg) } func isStdPkg(path string) bool { _, ok := slices.BinarySearch(stdPkgs, path) return ok } // DefaultLookupPackage is the default package lookup // function, used when [Parser.LookupPackage] is nil. // It recognizes names of the packages from the standard // library with single-element import paths, such as math, // which would otherwise be impossible to name. // // Note that the go/doc package provides a more sophisticated // lookup based on the imports used in the current package. func DefaultLookupPackage(name string) (importPath string, ok bool) { if isStdPkg(name) { return name, true } return "", false } // Parse parses the doc comment text and returns the *[Doc] form. // Comment markers (/* // and */) in the text must have already been removed. func (p *Parser) Parse(text string) *Doc { lines := unindent(strings.Split(text, "\n")) d := &parseDoc{ Parser: p, Doc: new(Doc), links: make(map[string]*LinkDef), lines: lines, lookupSym: func(recv, name string) bool { return false }, } if p.LookupSym != nil { d.lookupSym = p.LookupSym } // First pass: break into block structure and collect known links. // The text is all recorded as Plain for now. var prev span for _, s := range parseSpans(lines) { var b Block switch s.kind { default: panic("go/doc/comment: internal error: unknown span kind") case spanList: b = d.list(lines[s.start:s.end], prev.end < s.start) case spanCode: b = d.code(lines[s.start:s.end]) case spanOldHeading: b = d.oldHeading(lines[s.start]) case spanHeading: b = d.heading(lines[s.start]) case spanPara: b = d.paragraph(lines[s.start:s.end]) } if b != nil { d.Content = append(d.Content, b) } prev = s } // Second pass: interpret all the Plain text now that we know the links. for _, b := range d.Content { switch b := b.(type) { case *Paragraph: b.Text = d.parseLinkedText(string(b.Text[0].(Plain))) case *List: for _, i := range b.Items { for _, c := range i.Content { p := c.(*Paragraph) p.Text = d.parseLinkedText(string(p.Text[0].(Plain))) } } } } return d.Doc } // A span represents a single span of comment lines (lines[start:end]) // of an identified kind (code, heading, paragraph, and so on). type span struct { start int end int kind spanKind } // A spanKind describes the kind of span. type spanKind int const ( _ spanKind = iota spanCode spanHeading spanList spanOldHeading spanPara ) func parseSpans(lines []string) []span { var spans []span // The loop may process a line twice: once as unindented // and again forced indented. So the maximum expected // number of iterations is 2*len(lines). The repeating logic // can be subtle, though, and to protect against introduction // of infinite loops in future changes, we watch to see that // we are not looping too much. A panic is better than a // quiet infinite loop. watchdog := 2 * len(lines) i := 0 forceIndent := 0 Spans: for { // Skip blank lines. for i < len(lines) && lines[i] == "" { i++ } if i >= len(lines) { break } if watchdog--; watchdog < 0 { panic("go/doc/comment: internal error: not making progress") } var kind spanKind start := i end := i if i < forceIndent || indented(lines[i]) { // Indented (or force indented). // Ends before next unindented. (Blank lines are OK.) // If this is an unindented list that we are heuristically treating as indented, // then accept unindented list item lines up to the first blank lines. // The heuristic is disabled at blank lines to contain its effect // to non-gofmt'ed sections of the comment. unindentedListOK := isList(lines[i]) && i < forceIndent i++ for i < len(lines) && (lines[i] == "" || i < forceIndent || indented(lines[i]) || (unindentedListOK && isList(lines[i]))) { if lines[i] == "" { unindentedListOK = false } i++ } // Drop trailing blank lines. end = i for end > start && lines[end-1] == "" { end-- } // If indented lines are followed (without a blank line) // by an unindented line ending in a brace, // take that one line too. This fixes the common mistake // of pasting in something like // // func main() { // fmt.Println("hello, world") // } // // and forgetting to indent it. // The heuristic will never trigger on a gofmt'ed comment, // because any gofmt'ed code block or list would be // followed by a blank line or end of comment. if end < len(lines) && strings.HasPrefix(lines[end], "}") { end++ } if isList(lines[start]) { kind = spanList } else { kind = spanCode } } else { // Unindented. Ends at next blank or indented line. i++ for i < len(lines) && lines[i] != "" && !indented(lines[i]) { i++ } end = i // If unindented lines are followed (without a blank line) // by an indented line that would start a code block, // check whether the final unindented lines // should be left for the indented section. // This can happen for the common mistakes of // unindented code or unindented lists. // The heuristic will never trigger on a gofmt'ed comment, // because any gofmt'ed code block would have a blank line // preceding it after the unindented lines. if i < len(lines) && lines[i] != "" && !isList(lines[i]) { switch { case isList(lines[i-1]): // If the final unindented line looks like a list item, // this may be the first indented line wrap of // a mistakenly unindented list. // Leave all the unindented list items. forceIndent = end end-- for end > start && isList(lines[end-1]) { end-- } case strings.HasSuffix(lines[i-1], "{") || strings.HasSuffix(lines[i-1], `\`): // If the final unindented line ended in { or \ // it is probably the start of a misindented code block. // Give the user a single line fix. // Often that's enough; if not, the user can fix the others themselves. forceIndent = end end-- } if start == end && forceIndent > start { i = start continue Spans } } // Span is either paragraph or heading. if end-start == 1 && isHeading(lines[start]) { kind = spanHeading } else if end-start == 1 && isOldHeading(lines[start], lines, start) { kind = spanOldHeading } else { kind = spanPara } } spans = append(spans, span{start, end, kind}) i = end } return spans } // indented reports whether line is indented // (starts with a leading space or tab). func indented(line string) bool { return line != "" && (line[0] == ' ' || line[0] == '\t') } // unindent removes any common space/tab prefix // from each line in lines, returning a copy of lines in which // those prefixes have been trimmed from each line. // It also replaces any lines containing only spaces with blank lines (empty strings). func unindent(lines []string) []string { // Trim leading and trailing blank lines. for len(lines) > 0 && isBlank(lines[0]) { lines = lines[1:] } for len(lines) > 0 && isBlank(lines[len(lines)-1]) { lines = lines[:len(lines)-1] } if len(lines) == 0 { return nil } // Compute and remove common indentation. prefix := leadingSpace(lines[0]) for _, line := range lines[1:] { if !isBlank(line) { prefix = commonPrefix(prefix, leadingSpace(line)) } } out := make([]string, len(lines)) for i, line := range lines { line = strings.TrimPrefix(line, prefix) if strings.TrimSpace(line) == "" { line = "" } out[i] = line } for len(out) > 0 && out[0] == "" { out = out[1:] } for len(out) > 0 && out[len(out)-1] == "" { out = out[:len(out)-1] } return out } // isBlank reports whether s is a blank line. func isBlank(s string) bool { return len(s) == 0 || (len(s) == 1 && s[0] == '\n') } // commonPrefix returns the longest common prefix of a and b. func commonPrefix(a, b string) string { i := 0 for i < len(a) && i < len(b) && a[i] == b[i] { i++ } return a[0:i] } // leadingSpace returns the longest prefix of s consisting of spaces and tabs. func leadingSpace(s string) string { i := 0 for i < len(s) && (s[i] == ' ' || s[i] == '\t') { i++ } return s[:i] } // isOldHeading reports whether line is an old-style section heading. // line is all[off]. func isOldHeading(line string, all []string, off int) bool { if off <= 0 || all[off-1] != "" || off+2 >= len(all) || all[off+1] != "" || leadingSpace(all[off+2]) != "" { return false } line = strings.TrimSpace(line) // a heading must start with an uppercase letter r, _ := utf8.DecodeRuneInString(line) if !unicode.IsLetter(r) || !unicode.IsUpper(r) { return false } // it must end in a letter or digit: r, _ = utf8.DecodeLastRuneInString(line) if !unicode.IsLetter(r) && !unicode.IsDigit(r) { return false } // exclude lines with illegal characters. we allow "()," if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") { return false } // allow "'" for possessive "'s" only for b := line; ; { var ok bool if _, b, ok = strings.Cut(b, "'"); !ok { break } if b != "s" && !strings.HasPrefix(b, "s ") { return false // ' not followed by s and then end-of-word } } // allow "." when followed by non-space for b := line; ; { var ok bool if _, b, ok = strings.Cut(b, "."); !ok { break } if b == "" || strings.HasPrefix(b, " ") { return false // not followed by non-space } } return true } // oldHeading returns the *Heading for the given old-style section heading line. func (d *parseDoc) oldHeading(line string) Block { return &Heading{Text: []Text{Plain(strings.TrimSpace(line))}} } // isHeading reports whether line is a new-style section heading. func isHeading(line string) bool { return len(line) >= 2 && line[0] == '#' && (line[1] == ' ' || line[1] == '\t') && strings.TrimSpace(line) != "#" } // heading returns the *Heading for the given new-style section heading line. func (d *parseDoc) heading(line string) Block { return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}} } // code returns a code block built from the lines. func (d *parseDoc) code(lines []string) *Code { body := unindent(lines) body = append(body, "") // to get final \n from Join return &Code{Text: strings.Join(body, "\n")} } // paragraph returns a paragraph block built from the lines. // If the lines are link definitions, paragraph adds them to d and returns nil. func (d *parseDoc) paragraph(lines []string) Block { // Is this a block of known links? Handle. var defs []*LinkDef for _, line := range lines { def, ok := parseLink(line) if !ok { goto NoDefs } defs = append(defs, def) } for _, def := range defs { d.Links = append(d.Links, def) if d.links[def.Text] == nil { d.links[def.Text] = def } } return nil NoDefs: return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}} } // parseLink parses a single link definition line: // // [text]: url // // It returns the link definition and whether the line was well formed. func parseLink(line string) (*LinkDef, bool) { if line == "" || line[0] != '[' { return nil, false } i := strings.Index(line, "]:") if i < 0 || i+3 >= len(line) || (line[i+2] != ' ' && line[i+2] != '\t') { return nil, false } text := line[1:i] url := strings.TrimSpace(line[i+3:]) j := strings.Index(url, "://") if j < 0 || !isScheme(url[:j]) { return nil, false } // Line has right form and has valid scheme://. // That's good enough for us - we are not as picky // about the characters beyond the :// as we are // when extracting inline URLs from text. return &LinkDef{Text: text, URL: url}, true } // list returns a list built from the indented lines, // using forceBlankBefore as the value of the List's ForceBlankBefore field. func (d *parseDoc) list(lines []string, forceBlankBefore bool) *List { num, _, _ := listMarker(lines[0]) var ( list *List = &List{ForceBlankBefore: forceBlankBefore} item *ListItem text []string ) flush := func() { if item != nil { if para := d.paragraph(text); para != nil { item.Content = append(item.Content, para) } } text = nil } for _, line := range lines { if n, after, ok := listMarker(line); ok && (n != "") == (num != "") { // start new list item flush() item = &ListItem{Number: n} list.Items = append(list.Items, item) line = after } line = strings.TrimSpace(line) if line == "" { list.ForceBlankBetween = true flush() continue } text = append(text, strings.TrimSpace(line)) } flush() return list } // listMarker parses the line as beginning with a list marker. // If it can do that, it returns the numeric marker ("" for a bullet list), // the rest of the line, and ok == true. // Otherwise, it returns "", "", false. func listMarker(line string) (num, rest string, ok bool) { line = strings.TrimSpace(line) if line == "" { return "", "", false } // Can we find a marker? if r, n := utf8.DecodeRuneInString(line); r == '•' || r == '*' || r == '+' || r == '-' { num, rest = "", line[n:] } else if '0' <= line[0] && line[0] <= '9' { n := 1 for n < len(line) && '0' <= line[n] && line[n] <= '9' { n++ } if n >= len(line) || (line[n] != '.' && line[n] != ')') { return "", "", false } num, rest = line[:n], line[n+1:] } else { return "", "", false } if !indented(rest) || strings.TrimSpace(rest) == "" { return "", "", false } return num, rest, true } // isList reports whether the line is the first line of a list, // meaning starts with a list marker after any indentation. // (The caller is responsible for checking the line is indented, as appropriate.) func isList(line string) bool { _, _, ok := listMarker(line) return ok } // parseLinkedText parses text that is allowed to contain explicit links, // such as [math.Sin] or [Go home page], into a slice of Text items. // // A “pkg” is only assumed to be a full import path if it starts with // a domain name (a path element with a dot) or is one of the packages // from the standard library (“[os]”, “[encoding/json]”, and so on). // To avoid problems with maps, generics, and array types, doc links // must be both preceded and followed by punctuation, spaces, tabs, // or the start or end of a line. An example problem would be treating // map[ast.Expr]TypeAndValue as containing a link. func (d *parseDoc) parseLinkedText(text string) []Text { var out []Text wrote := 0 flush := func(i int) { if wrote < i { out = d.parseText(out, text[wrote:i], true) wrote = i } } start := -1 var buf []byte for i := 0; i < len(text); i++ { c := text[i] if c == '\n' || c == '\t' { c = ' ' } switch c { case '[': start = i case ']': if start >= 0 { if def, ok := d.links[string(buf)]; ok { def.Used = true flush(start) out = append(out, &Link{ Text: d.parseText(nil, text[start+1:i], false), URL: def.URL, }) wrote = i + 1 } else if link, ok := d.docLink(text[start+1:i], text[:start], text[i+1:]); ok { flush(start) link.Text = d.parseText(nil, text[start+1:i], false) out = append(out, link) wrote = i + 1 } } start = -1 buf = buf[:0] } if start >= 0 && i != start { buf = append(buf, c) } } flush(len(text)) return out } // docLink parses text, which was found inside [ ] brackets, // as a doc link if possible, returning the DocLink and ok == true // or else nil, false. // The before and after strings are the text before the [ and after the ] // on the same line. Doc links must be preceded and followed by // punctuation, spaces, tabs, or the start or end of a line. func (d *parseDoc) docLink(text, before, after string) (link *DocLink, ok bool) { if before != "" { r, _ := utf8.DecodeLastRuneInString(before) if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { return nil, false } } if after != "" { r, _ := utf8.DecodeRuneInString(after) if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { return nil, false } } text = strings.TrimPrefix(text, "*") pkg, name, ok := splitDocName(text) var recv string if ok { pkg, recv, _ = splitDocName(pkg) } if pkg != "" { if pkg, ok = d.lookupPkg(pkg); !ok { return nil, false } } else { if ok = d.lookupSym(recv, name); !ok { return nil, false } } link = &DocLink{ ImportPath: pkg, Recv: recv, Name: name, } return link, true } // If text is of the form before.Name, where Name is a capitalized Go identifier, // then splitDocName returns before, name, true. // Otherwise it returns text, "", false. func splitDocName(text string) (before, name string, foundDot bool) { i := strings.LastIndex(text, ".") name = text[i+1:] if !isName(name) { return text, "", false } if i >= 0 { before = text[:i] } return before, name, true } // parseText parses s as text and returns the result of appending // those parsed Text elements to out. // parseText does not handle explicit links like [math.Sin] or [Go home page]: // those are handled by parseLinkedText. // If autoLink is true, then parseText recognizes URLs and words from d.Words // and converts those to links as appropriate. func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text { var w strings.Builder wrote := 0 writeUntil := func(i int) { w.WriteString(s[wrote:i]) wrote = i } flush := func(i int) { writeUntil(i) if w.Len() > 0 { out = append(out, Plain(w.String())) w.Reset() } } for i := 0; i < len(s); { t := s[i:] if autoLink { if url, ok := autoURL(t); ok { flush(i) // Note: The old comment parser would look up the URL in words // and replace the target with words[URL] if it was non-empty. // That would allow creating links that display as one URL but // when clicked go to a different URL. Not sure what the point // of that is, so we're not doing that lookup here. out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url}) i += len(url) wrote = i continue } if id, ok := ident(t); ok { url, italics := d.Words[id] if !italics { i += len(id) continue } flush(i) if url == "" { out = append(out, Italic(id)) } else { out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url}) } i += len(id) wrote = i continue } } switch { case strings.HasPrefix(t, "``"): if len(t) >= 3 && t[2] == '`' { // Do not convert `` inside ```, in case people are mistakenly writing Markdown. i += 3 for i < len(t) && t[i] == '`' { i++ } break } writeUntil(i) w.WriteRune('“') i += 2 wrote = i case strings.HasPrefix(t, "''"): writeUntil(i) w.WriteRune('”') i += 2 wrote = i default: i++ } } flush(len(s)) return out } // autoURL checks whether s begins with a URL that should be hyperlinked. // If so, it returns the URL, which is a prefix of s, and ok == true. // Otherwise it returns "", false. // The caller should skip over the first len(url) bytes of s // before further processing. func autoURL(s string) (url string, ok bool) { // Find the ://. Fast path to pick off non-URL, // since we call this at every position in the string. // The shortest possible URL is ftp://x, 7 bytes. var i int switch { case len(s) < 7: return "", false case s[3] == ':': i = 3 case s[4] == ':': i = 4 case s[5] == ':': i = 5 case s[6] == ':': i = 6 default: return "", false } if i+3 > len(s) || s[i:i+3] != "://" { return "", false } // Check valid scheme. if !isScheme(s[:i]) { return "", false } // Scan host part. Must have at least one byte, // and must start and end in non-punctuation. i += 3 if i >= len(s) || !isHost(s[i]) || isPunct(s[i]) { return "", false } i++ end := i for i < len(s) && isHost(s[i]) { if !isPunct(s[i]) { end = i + 1 } i++ } i = end // At this point we are definitely returning a URL (scheme://host). // We just have to find the longest path we can add to it. // Heuristics abound. // We allow parens, braces, and brackets, // but only if they match (#5043, #22285). // We allow .,:;?! in the path but not at the end, // to avoid end-of-sentence punctuation (#18139, #16565). stk := []byte{} end = i Path: for ; i < len(s); i++ { if isPunct(s[i]) { continue } if !isPath(s[i]) { break } switch s[i] { case '(': stk = append(stk, ')') case '{': stk = append(stk, '}') case '[': stk = append(stk, ']') case ')', '}', ']': if len(stk) == 0 || stk[len(stk)-1] != s[i] { break Path } stk = stk[:len(stk)-1] } if len(stk) == 0 { end = i + 1 } } return s[:end], true } // isScheme reports whether s is a recognized URL scheme. // Note that if strings of new length (beyond 3-7) // are added here, the fast path at the top of autoURL will need updating. func isScheme(s string) bool { switch s { case "file", "ftp", "gopher", "http", "https", "mailto", "nntp": return true } return false } // isHost reports whether c is a byte that can appear in a URL host, // like www.example.com or user@[::1]:8080 func isHost(c byte) bool { // mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false. const mask = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' | 1<<'_' | 1<<'@' | 1<<'-' | 1<<'.' | 1<<'[' | 1<<']' | 1<<':' return ((uint64(1)<<c)&(mask&(1<<64-1)) | (uint64(1)<<(c-64))&(mask>>64)) != 0 } // isPunct reports whether c is a punctuation byte that can appear // inside a path but not at the end. func isPunct(c byte) bool { // mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false. const mask = 0 | 1<<'.' | 1<<',' | 1<<':' | 1<<';' | 1<<'?' | 1<<'!' return ((uint64(1)<<c)&(mask&(1<<64-1)) | (uint64(1)<<(c-64))&(mask>>64)) != 0 } // isPath reports whether c is a (non-punctuation) path byte. func isPath(c byte) bool { // mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false. const mask = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' | 1<<'$' | 1<<'\'' | 1<<'(' | 1<<')' | 1<<'*' | 1<<'+' | 1<<'&' | 1<<'#' | 1<<'=' | 1<<'@' | 1<<'~' | 1<<'_' | 1<<'/' | 1<<'-' | 1<<'[' | 1<<']' | 1<<'{' | 1<<'}' | 1<<'%' return ((uint64(1)<<c)&(mask&(1<<64-1)) | (uint64(1)<<(c-64))&(mask>>64)) != 0 } // isName reports whether s is a capitalized Go identifier (like Name). func isName(s string) bool { t, ok := ident(s) if !ok || t != s { return false } r, _ := utf8.DecodeRuneInString(s) return unicode.IsUpper(r) } // ident checks whether s begins with a Go identifier. // If so, it returns the identifier, which is a prefix of s, and ok == true. // Otherwise it returns "", false. // The caller should skip over the first len(id) bytes of s // before further processing. func ident(s string) (id string, ok bool) { // Scan [\pL_][\pL_0-9]* n := 0 for n < len(s) { if c := s[n]; c < utf8.RuneSelf { if isIdentASCII(c) && (n > 0 || c < '0' || c > '9') { n++ continue } break } r, nr := utf8.DecodeRuneInString(s[n:]) if unicode.IsLetter(r) { n += nr continue } break } return s[:n], n > 0 } // isIdentASCII reports whether c is an ASCII identifier byte. func isIdentASCII(c byte) bool { // mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false. const mask = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' | 1<<'_' return ((uint64(1)<<c)&(mask&(1<<64-1)) | (uint64(1)<<(c-64))&(mask>>64)) != 0 } // validImportPath reports whether path is a valid import path. // It is a lightly edited copy of golang.org/x/mod/module.CheckImportPath. func validImportPath(path string) bool { if !utf8.ValidString(path) { return false } if path == "" { return false } if path[0] == '-' { return false } if strings.Contains(path, "//") { return false } if path[len(path)-1] == '/' { return false } elemStart := 0 for i, r := range path { if r == '/' { if !validImportPathElem(path[elemStart:i]) { return false } elemStart = i + 1 } } return validImportPathElem(path[elemStart:]) } func validImportPathElem(elem string) bool { if elem == "" || elem[0] == '.' || elem[len(elem)-1] == '.' { return false } for i := 0; i < len(elem); i++ { if !importPathOK(elem[i]) { return false } } return true } func importPathOK(c byte) bool { // mask is a 128-bit bitmap with 1s for allowed bytes, // so that the byte c can be tested with a shift and an and. // If c > 128, then 1<<c and 1<<(c-64) will both be zero, // and this function will return false. const mask = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' | 1<<'-' | 1<<'.' | 1<<'~' | 1<<'_' | 1<<'+' return ((uint64(1)<<c)&(mask&(1<<64-1)) | (uint64(1)<<(c-64))&(mask>>64)) != 0 }