tina4-nodejs 3.13.101 → 3.13.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -234,11 +234,11 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
234
234
  </tr>`).join("")}
235
235
  </tbody>
236
236
  </table>
237
- `)}function Xy(i,e){const t=[],n={},r={};i.forEach((s,o)=>{n[Rd(s)]=o,r[s]=o});for(const[s,o]of Object.entries(e)){const a=r[s];if(a!==void 0)for(const l of o){const O=r[l];if(O!==void 0){O!==a&&t.push([a,O]);continue}const c=l.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let d;for(let h=c.length-1;h>=0;h--){const f=c[h].toLowerCase();if(f&&f!=="js"&&f!=="py"&&f!=="rb"&&f!=="ts"&&f!=="index"&&(d=n[f],d!==void 0))break}d===void 0&&(d=n[Rd(l)]),d!==void 0&&a!==d&&t.push([a,d])}}return t}function Rd(i){const e=i.replace(/\\/g,"/").split("/").pop()||"",t=e.lastIndexOf(".");return(t>0?e.substring(0,t):e).toLowerCase()}function _y(i,e,t,n){var gi,$n,ps;const r=e.offsetWidth||900,s=Math.max(450,Math.min(650,r*.45)),o=Math.max(...i.map(G=>G.loc))||1,a=Math.max(...i.map(G=>G.dep_count||0))||1,l=Math.max(...i.map(G=>G.coupling_afferent||0))||1,O=i.some(G=>(G.coupling_afferent||0)>0),c=14,d=Math.min(70,r/10);function h(G){const I=Math.min((G.avg_complexity||0)/10,1),D=G.has_tests?0:1,de=Math.min((G.dep_count||0)/5,1),j=I*.4+D*.4+de*.2,W=Math.max(0,Math.min(1,j)),H=Math.round(120*(1-W)),te=Math.round(70+W*30),pe=Math.round(42+18*(1-W));return`hsl(${H},${te}%,${pe}%)`}function f(G){return G.loc/o*.4+(G.avg_complexity||0)/10*.4+(G.dep_count||0)/a*.2}const p=[...i].sort((G,I)=>f(G)-f(I)),m=r/2,$=s/2,g=[];let y=0,S=0;for(const G of p){const I=c+Math.sqrt(f(G))*(d-c),D=h(G);let de=!1;for(let j=0;j<800;j++){const W=m+S*Math.cos(y),H=$+S*Math.sin(y);let te=!1;for(const pe of g){const he=W-pe.x,ke=H-pe.y;if(Math.sqrt(he*he+ke*ke)<I+pe.r+2){te=!0;break}}if(!te&&W>I+2&&W<r-I-2&&H>I+25&&H<s-I-2){g.push({x:W,y:H,vx:0,vy:0,r:I,color:D,f:G}),de=!0;break}y+=.2,S+=.04}de||g.push({x:m+(Math.random()-.5)*r*.3,y:$+(Math.random()-.5)*s*.3,vx:0,vy:0,r:I,color:D,f:G})}const x=Xy(g.map(G=>G.f.path),t),w=document.createElement("canvas");w.width=r,w.height=s,w.style.cssText="display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a";const R=n==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";e.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;font-size:0.85rem">Code Landscape ${R}</h3><span style="font-size:0.65rem;color:var(--muted)">Drag bubbles | Dbl-click to drill down</span></div><div style="position:relative" id="metrics-canvas-wrap"></div><div style="margin-top:0.4rem;font-size:0.62rem;color:var(--muted);display:flex;gap:0.9rem;flex-wrap:wrap">${O?'<span>Centre = most depended-on</span><span><span style="color:hsl(210,85%,60%)">&#9679;</span> stable ring</span><span><span style="color:hsl(30,85%,60%)">&#9679;</span> unstable ring</span>':""}<span id="metrics-hover-info" class="text-mono"></span></div>`,document.getElementById("metrics-canvas-wrap").appendChild(w);const _=document.getElementById("metrics-hover-info"),A=document.createElement("div");A.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",A.innerHTML=`
237
+ `)}function Xy(i,e){const t=[],n={},r={};i.forEach((s,o)=>{n[Rd(s)]=o,r[s]=o});for(const[s,o]of Object.entries(e)){const a=r[s];if(a!==void 0)for(const l of o){const O=r[l];if(O!==void 0){O!==a&&t.push([a,O]);continue}const c=l.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let d;for(let h=c.length-1;h>=0;h--){const f=c[h].toLowerCase();if(f&&f!=="js"&&f!=="py"&&f!=="rb"&&f!=="ts"&&f!=="index"&&(d=n[f],d!==void 0))break}d===void 0&&(d=n[Rd(l)]),d!==void 0&&a!==d&&t.push([a,d])}}return t}function Rd(i){const e=i.replace(/\\/g,"/").split("/").pop()||"",t=e.lastIndexOf(".");return(t>0?e.substring(0,t):e).toLowerCase()}function _y(i,e,t,n){var gi,$n,ps;const r=e.offsetWidth||900,s=Math.max(450,Math.min(650,r*.45)),o=Math.max(...i.map(G=>G.loc))||1,a=Math.max(...i.map(G=>G.dep_count||0))||1,l=Math.max(...i.map(G=>G.coupling_afferent||0))||1,O=i.some(G=>(G.coupling_afferent||0)>0),c=14,d=Math.min(70,r/10);function h(G){const I=Math.min((G.avg_complexity||0)/10,1),D=G.has_referencing_test?0:1,de=Math.min((G.dep_count||0)/5,1),j=I*.4+D*.4+de*.2,W=Math.max(0,Math.min(1,j)),H=Math.round(120*(1-W)),te=Math.round(70+W*30),pe=Math.round(42+18*(1-W));return`hsl(${H},${te}%,${pe}%)`}function f(G){return G.loc/o*.4+(G.avg_complexity||0)/10*.4+(G.dep_count||0)/a*.2}const p=[...i].sort((G,I)=>f(G)-f(I)),m=r/2,$=s/2,g=[];let y=0,S=0;for(const G of p){const I=c+Math.sqrt(f(G))*(d-c),D=h(G);let de=!1;for(let j=0;j<800;j++){const W=m+S*Math.cos(y),H=$+S*Math.sin(y);let te=!1;for(const pe of g){const he=W-pe.x,ke=H-pe.y;if(Math.sqrt(he*he+ke*ke)<I+pe.r+2){te=!0;break}}if(!te&&W>I+2&&W<r-I-2&&H>I+25&&H<s-I-2){g.push({x:W,y:H,vx:0,vy:0,r:I,color:D,f:G}),de=!0;break}y+=.2,S+=.04}de||g.push({x:m+(Math.random()-.5)*r*.3,y:$+(Math.random()-.5)*s*.3,vx:0,vy:0,r:I,color:D,f:G})}const x=Xy(g.map(G=>G.f.path),t),w=document.createElement("canvas");w.width=r,w.height=s,w.style.cssText="display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a";const R=n==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";e.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;font-size:0.85rem">Code Landscape ${R}</h3><span style="font-size:0.65rem;color:var(--muted)">Drag bubbles | Dbl-click to drill down</span></div><div style="position:relative" id="metrics-canvas-wrap"></div><div style="margin-top:0.4rem;font-size:0.62rem;color:var(--muted);display:flex;gap:0.9rem;flex-wrap:wrap">${O?'<span>Centre = most depended-on</span><span><span style="color:hsl(210,85%,60%)">&#9679;</span> stable ring</span><span><span style="color:hsl(30,85%,60%)">&#9679;</span> unstable ring</span>':""}<span id="metrics-hover-info" class="text-mono"></span></div>`,document.getElementById("metrics-canvas-wrap").appendChild(w);const _=document.getElementById("metrics-hover-info"),A=document.createElement("div");A.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",A.innerHTML=`
238
238
  <button class="btn btn-sm" id="metrics-zoom-in" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">+</button>
239
239
  <button class="btn btn-sm" id="metrics-zoom-out" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">&minus;</button>
240
240
  <button class="btn btn-sm" id="metrics-zoom-fit" style="width:28px;height:28px;padding:0;font-size:10px;font-weight:700;line-height:1">Fit</button>
241
- `,document.getElementById("metrics-canvas-wrap").appendChild(A),(gi=document.getElementById("metrics-zoom-in"))==null||gi.addEventListener("click",()=>{le=Math.min(5,le*1.3)}),($n=document.getElementById("metrics-zoom-out"))==null||$n.addEventListener("click",()=>{le=Math.max(.3,le*.7)}),(ps=document.getElementById("metrics-zoom-fit"))==null||ps.addEventListener("click",()=>{le=1,U=0,oe=0});const P=w.getContext("2d");let v=-1,k=-1,X=0,z=0,U=0,oe=0,le=1,rt=!1,mn=0,Gt=0,us=0,ft=0;function De(){for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j],H=m-W.x,te=$-W.y,pe=.3+W.r/d*.7,he=O?.3+(W.f.coupling_afferent||0)/l*.7:pe,ke=.008*pe*he;W.vx+=H*ke,W.vy+=te*ke}for(const[j,W]of x){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20,Bt=(ke-Dt)*.002,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx+=ms,H.vy+=$s),W!==k&&(te.vx-=ms,te.vy-=$s)}for(let j=0;j<g.length;j++)for(let W=j+1;W<g.length;W++){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20;if(ke<Dt){const Bt=40*(Dt-ke)/Dt,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx-=ms,H.vy-=$s),W!==k&&(te.vx+=ms,te.vy+=$s)}}for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j];W.vx*=.65,W.vy*=.65;const H=2;W.vx=Math.max(-H,Math.min(H,W.vx)),W.vy=Math.max(-H,Math.min(H,W.vy)),W.x+=W.vx,W.y+=W.vy,W.x=Math.max(W.r+2,Math.min(r-W.r-2,W.x)),W.y=Math.max(W.r+25,Math.min(s-W.r-2,W.y))}}function st(){var G;De(),P.clearRect(0,0,r,s),P.save(),P.translate(U,oe),P.scale(le,le),P.strokeStyle="rgba(255,255,255,0.03)",P.lineWidth=1/le;for(let I=0;I<r/le;I+=50)P.beginPath(),P.moveTo(I,0),P.lineTo(I,s/le),P.stroke();for(let I=0;I<s/le;I+=50)P.beginPath(),P.moveTo(0,I),P.lineTo(r/le,I),P.stroke();for(const[I,D]of x){const de=g[I],j=g[D],W=j.x-de.x,H=j.y-de.y,te=Math.sqrt(W*W+H*H)||1,pe=v===I||v===D;P.beginPath(),P.moveTo(de.x+W/te*de.r,de.y+H/te*de.r);const he=j.x-W/te*j.r,ke=j.y-H/te*j.r;P.lineTo(he,ke),P.strokeStyle=pe?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",P.lineWidth=pe?3:1,P.stroke();const Dt=pe?12:6,Bt=Math.atan2(H,W);P.beginPath(),P.moveTo(he,ke),P.lineTo(he-Dt*Math.cos(Bt-.4),ke-Dt*Math.sin(Bt-.4)),P.lineTo(he-Dt*Math.cos(Bt+.4),ke-Dt*Math.sin(Bt+.4)),P.closePath(),P.fillStyle=P.strokeStyle,P.fill()}for(let I=0;I<g.length;I++){const D=g[I],de=I===v,j=de?D.r+4:D.r;de&&(P.beginPath(),P.arc(D.x,D.y,j+8,0,Math.PI*2),P.fillStyle="rgba(255,255,255,0.08)",P.fill()),P.beginPath(),P.arc(D.x,D.y,j,0,Math.PI*2),P.fillStyle=D.color,P.globalAlpha=de?1:.85,P.fill(),P.globalAlpha=1,P.strokeStyle=de?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",P.lineWidth=de?2.5:1.5,P.stroke();const W=D.f.instability;O&&typeof W=="number"&&(D.f.coupling_afferent||0)+(D.f.coupling_efferent||0)>0&&(P.beginPath(),P.arc(D.x,D.y,j+3,0,Math.PI*2),P.strokeStyle=`hsl(${Math.round(210-180*W)},85%,60%)`,P.lineWidth=1+W*2.5,P.stroke());const H=((G=D.f.path.split("/").pop())==null?void 0:G.replace(/\.\w+$/,""))||"?";if(j>16){const he=Math.max(8,Math.min(13,j*.38));P.fillStyle="#fff",P.font=`600 ${he}px monospace`,P.textAlign="center",P.fillText(H,D.x,D.y-2),P.fillStyle="rgba(255,255,255,0.65)",P.font=`${he-1}px monospace`,P.fillText(`${D.f.loc} LOC`,D.x,D.y+he)}const te=Math.max(9,j*.3),pe=te*.7;if(j>14&&D.f.dep_count>0){const he=D.y-j+pe+3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#ea580c",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("D",D.x,he+te*.35)}if(j>14&&D.f.has_tests){const he=D.y+j-pe-3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#16a34a",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("T",D.x,he+te*.35)}}if(P.restore(),v>=0){const I=g[v].f,D=I.coupling_afferent??0,de=I.coupling_efferent??0,j=typeof I.instability=="number"?I.instability.toFixed(2):"-";_.textContent=`${I.path} ca ${D} / ce ${de} / I ${j} / MI ${I.maintainability}`}else _.textContent&&(_.textContent="");requestAnimationFrame(st)}w.addEventListener("mousemove",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(rt){U=us+(G.clientX-mn),oe=ft+(G.clientY-Gt);return}if(k>=0){ut=!0,g[k].x=D+X,g[k].y=de+z,g[k].vx=0,g[k].vy=0;return}v=-1;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){v=j;break}}w.style.cursor=v>=0?"grab":"default"}),w.addEventListener("mousedown",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(G.button===2){rt=!0,mn=G.clientX,Gt=G.clientY,us=U,ft=oe,w.style.cursor="move";return}v>=0&&(k=v,X=g[k].x-D,z=g[k].y-de,ut=!1,w.style.cursor="grabbing")});let ut=!1;w.addEventListener("mouseup",G=>{if(rt){rt=!1,w.style.cursor="default";return}if(k>=0){ut||Xa(g[k].f.path),w.style.cursor="grab",k=-1,ut=!1;return}}),w.addEventListener("mouseleave",()=>{v=-1,k=-1,rt=!1}),w.addEventListener("dblclick",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){Xa(W.f.path);break}}}),w.addEventListener("contextmenu",G=>G.preventDefault()),requestAnimationFrame(st)}async function Xa(i){const e=document.getElementById("metrics-detail");if(!e)return;e.innerHTML='<p class="text-muted">Loading file analysis...</p>';const t=await L("/metrics/file?path="+encodeURIComponent(i));if(t.error){e.innerHTML=`<p style="color:var(--danger)">${Q(t.error)}</p>`;return}const n=t.functions||[],r=Math.max(1,...n.map(s=>s.complexity));e.innerHTML=`
241
+ `,document.getElementById("metrics-canvas-wrap").appendChild(A),(gi=document.getElementById("metrics-zoom-in"))==null||gi.addEventListener("click",()=>{le=Math.min(5,le*1.3)}),($n=document.getElementById("metrics-zoom-out"))==null||$n.addEventListener("click",()=>{le=Math.max(.3,le*.7)}),(ps=document.getElementById("metrics-zoom-fit"))==null||ps.addEventListener("click",()=>{le=1,U=0,oe=0});const P=w.getContext("2d");let v=-1,k=-1,X=0,z=0,U=0,oe=0,le=1,rt=!1,mn=0,Gt=0,us=0,ft=0;function De(){for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j],H=m-W.x,te=$-W.y,pe=.3+W.r/d*.7,he=O?.3+(W.f.coupling_afferent||0)/l*.7:pe,ke=.008*pe*he;W.vx+=H*ke,W.vy+=te*ke}for(const[j,W]of x){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20,Bt=(ke-Dt)*.002,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx+=ms,H.vy+=$s),W!==k&&(te.vx-=ms,te.vy-=$s)}for(let j=0;j<g.length;j++)for(let W=j+1;W<g.length;W++){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20;if(ke<Dt){const Bt=40*(Dt-ke)/Dt,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx-=ms,H.vy-=$s),W!==k&&(te.vx+=ms,te.vy+=$s)}}for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j];W.vx*=.65,W.vy*=.65;const H=2;W.vx=Math.max(-H,Math.min(H,W.vx)),W.vy=Math.max(-H,Math.min(H,W.vy)),W.x+=W.vx,W.y+=W.vy,W.x=Math.max(W.r+2,Math.min(r-W.r-2,W.x)),W.y=Math.max(W.r+25,Math.min(s-W.r-2,W.y))}}function st(){var G;De(),P.clearRect(0,0,r,s),P.save(),P.translate(U,oe),P.scale(le,le),P.strokeStyle="rgba(255,255,255,0.03)",P.lineWidth=1/le;for(let I=0;I<r/le;I+=50)P.beginPath(),P.moveTo(I,0),P.lineTo(I,s/le),P.stroke();for(let I=0;I<s/le;I+=50)P.beginPath(),P.moveTo(0,I),P.lineTo(r/le,I),P.stroke();for(const[I,D]of x){const de=g[I],j=g[D],W=j.x-de.x,H=j.y-de.y,te=Math.sqrt(W*W+H*H)||1,pe=v===I||v===D;P.beginPath(),P.moveTo(de.x+W/te*de.r,de.y+H/te*de.r);const he=j.x-W/te*j.r,ke=j.y-H/te*j.r;P.lineTo(he,ke),P.strokeStyle=pe?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",P.lineWidth=pe?3:1,P.stroke();const Dt=pe?12:6,Bt=Math.atan2(H,W);P.beginPath(),P.moveTo(he,ke),P.lineTo(he-Dt*Math.cos(Bt-.4),ke-Dt*Math.sin(Bt-.4)),P.lineTo(he-Dt*Math.cos(Bt+.4),ke-Dt*Math.sin(Bt+.4)),P.closePath(),P.fillStyle=P.strokeStyle,P.fill()}for(let I=0;I<g.length;I++){const D=g[I],de=I===v,j=de?D.r+4:D.r;de&&(P.beginPath(),P.arc(D.x,D.y,j+8,0,Math.PI*2),P.fillStyle="rgba(255,255,255,0.08)",P.fill()),P.beginPath(),P.arc(D.x,D.y,j,0,Math.PI*2),P.fillStyle=D.color,P.globalAlpha=de?1:.85,P.fill(),P.globalAlpha=1,P.strokeStyle=de?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",P.lineWidth=de?2.5:1.5,P.stroke();const W=D.f.instability;O&&typeof W=="number"&&(D.f.coupling_afferent||0)+(D.f.coupling_efferent||0)>0&&(P.beginPath(),P.arc(D.x,D.y,j+3,0,Math.PI*2),P.strokeStyle=`hsl(${Math.round(210-180*W)},85%,60%)`,P.lineWidth=1+W*2.5,P.stroke());const H=((G=D.f.path.split("/").pop())==null?void 0:G.replace(/\.\w+$/,""))||"?";if(j>16){const he=Math.max(8,Math.min(13,j*.38));P.fillStyle="#fff",P.font=`600 ${he}px monospace`,P.textAlign="center",P.fillText(H,D.x,D.y-2),P.fillStyle="rgba(255,255,255,0.65)",P.font=`${he-1}px monospace`,P.fillText(`${D.f.loc} LOC`,D.x,D.y+he)}const te=Math.max(9,j*.3),pe=te*.7;if(j>14&&D.f.dep_count>0){const he=D.y-j+pe+3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#ea580c",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("D",D.x,he+te*.35)}if(j>14&&D.f.has_referencing_test){const he=D.y+j-pe-3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#16a34a",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("T",D.x,he+te*.35)}}if(P.restore(),v>=0){const I=g[v].f,D=I.coupling_afferent??0,de=I.coupling_efferent??0,j=typeof I.instability=="number"?I.instability.toFixed(2):"-";_.textContent=`${I.path} ca ${D} / ce ${de} / I ${j} / MI ${I.maintainability}`}else _.textContent&&(_.textContent="");requestAnimationFrame(st)}w.addEventListener("mousemove",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(rt){U=us+(G.clientX-mn),oe=ft+(G.clientY-Gt);return}if(k>=0){ut=!0,g[k].x=D+X,g[k].y=de+z,g[k].vx=0,g[k].vy=0;return}v=-1;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){v=j;break}}w.style.cursor=v>=0?"grab":"default"}),w.addEventListener("mousedown",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(G.button===2){rt=!0,mn=G.clientX,Gt=G.clientY,us=U,ft=oe,w.style.cursor="move";return}v>=0&&(k=v,X=g[k].x-D,z=g[k].y-de,ut=!1,w.style.cursor="grabbing")});let ut=!1;w.addEventListener("mouseup",G=>{if(rt){rt=!1,w.style.cursor="default";return}if(k>=0){ut||Xa(g[k].f.path),w.style.cursor="grab",k=-1,ut=!1;return}}),w.addEventListener("mouseleave",()=>{v=-1,k=-1,rt=!1}),w.addEventListener("dblclick",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){Xa(W.f.path);break}}}),w.addEventListener("contextmenu",G=>G.preventDefault()),requestAnimationFrame(st)}async function Xa(i){const e=document.getElementById("metrics-detail");if(!e)return;e.innerHTML='<p class="text-muted">Loading file analysis...</p>';const t=await L("/metrics/file?path="+encodeURIComponent(i));if(t.error){e.innerHTML=`<p style="color:var(--danger)">${Q(t.error)}</p>`;return}const n=t.functions||[],r=Math.max(1,...n.map(s=>s.complexity));e.innerHTML=`
242
242
  <div style="background:var(--surface);border:1px solid var(--border);border-radius:0.5rem;padding:1rem">
243
243
  <div class="flex items-center" style="justify-content:space-between;margin-bottom:0.75rem">
244
244
  <h3 style="font-size:0.9rem">${Q(t.path)}</h3>
@@ -15,7 +15,7 @@ export class MetricsEngineError extends Error {
15
15
  }
16
16
  const INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
17
17
  const SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
18
- const FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_tests"];
18
+ const FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_referencing_test"];
19
19
  const FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
20
20
 
21
21
  function containsTypeScript(directory: string): boolean {
@@ -501,62 +501,59 @@ function splitOutsideQuotes(expr, sep2) {
501
501
  parts.push(expr.slice(currentStart));
502
502
  return parts;
503
503
  }
504
- function evalExpr(expr, context) {
505
- expr = expr.trim();
506
- if (expr.length >= 2) {
507
- const q = expr[0];
508
- if ((q === '"' || q === "'") && expr.endsWith(q) && !expr.slice(1, -1).includes(q)) {
509
- return expr.slice(1, -1);
510
- }
504
+ var EXPR_NOT_MATCHED = Symbol("frond-expression-not-matched");
505
+ function parenthesizedInner(expr) {
506
+ if (expr.length < 2 || expr[0] !== "(" || !expr.endsWith(")")) return null;
507
+ let depth = 0;
508
+ for (let index = 0; index < expr.length; index++) {
509
+ if (expr[index] === "(") depth++;
510
+ else if (expr[index] === ")") depth--;
511
+ if (depth === 0 && index < expr.length - 1) return null;
511
512
  }
512
- if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
513
- let depth = 0;
514
- let matched = true;
515
- for (let pi = 0; pi < expr.length; pi++) {
516
- if (expr[pi] === "(") depth++;
517
- else if (expr[pi] === ")") depth--;
518
- if (depth === 0 && pi < expr.length - 1) {
519
- matched = false;
520
- break;
521
- }
522
- }
523
- if (matched) {
524
- return evalExpr(expr.slice(1, -1), context);
525
- }
513
+ return expr.slice(1, -1);
514
+ }
515
+ function evalPrimary(expr, context) {
516
+ const quote = expr[0];
517
+ if (expr.length >= 2 && (quote === '"' || quote === "'") && expr.endsWith(quote) && !expr.slice(1, -1).includes(quote)) {
518
+ return expr.slice(1, -1);
526
519
  }
520
+ const inner = parenthesizedInner(expr);
521
+ if (inner !== null) return evalExpr(inner, context);
522
+ return EXPR_NOT_MATCHED;
523
+ }
524
+ function evalTernaryExpression(expr, context) {
527
525
  const ternaryIdx = findTernary(expr);
528
- if (ternaryIdx !== -1) {
529
- const condPart = expr.slice(0, ternaryIdx).trim();
530
- const rest = expr.slice(ternaryIdx + 1);
531
- const colonIdx = findColon(rest);
532
- if (colonIdx !== -1) {
533
- const truePart = rest.slice(0, colonIdx).trim();
534
- const falsePart = rest.slice(colonIdx + 1).trim();
535
- const cond = evalExpr(condPart, context);
536
- return cond ? evalExpr(truePart, context) : evalExpr(falsePart, context);
537
- }
538
- }
526
+ if (ternaryIdx === -1) return EXPR_NOT_MATCHED;
527
+ const rest = expr.slice(ternaryIdx + 1);
528
+ const colonIdx = findColon(rest);
529
+ if (colonIdx === -1) return EXPR_NOT_MATCHED;
530
+ const condition = evalExpr(expr.slice(0, ternaryIdx).trim(), context);
531
+ const branch = condition ? rest.slice(0, colonIdx) : rest.slice(colonIdx + 1);
532
+ return evalExpr(branch.trim(), context);
533
+ }
534
+ function evalInlineIfExpression(expr, context) {
539
535
  const ifIdx = findOutsideQuotes(expr, " if ");
540
- if (ifIdx >= 0) {
541
- const elseIdx = findOutsideQuotes(expr, " else ");
542
- if (elseIdx >= 0 && elseIdx > ifIdx) {
543
- const valuePart = expr.slice(0, ifIdx).trim();
544
- const condPart = expr.slice(ifIdx + 4, elseIdx).trim();
545
- const elsePart = expr.slice(elseIdx + 6).trim();
546
- const cond = evalExpr(condPart, context);
547
- return cond ? evalExpr(valuePart, context) : evalExpr(elsePart, context);
548
- }
549
- }
536
+ if (ifIdx < 0) return EXPR_NOT_MATCHED;
537
+ const elseIdx = findOutsideQuotes(expr, " else ");
538
+ if (elseIdx < 0 || elseIdx <= ifIdx) return EXPR_NOT_MATCHED;
539
+ const condition = evalExpr(expr.slice(ifIdx + 4, elseIdx).trim(), context);
540
+ const branch = condition ? expr.slice(0, ifIdx) : expr.slice(elseIdx + 6);
541
+ return evalExpr(branch.trim(), context);
542
+ }
543
+ function evalCoalesceExpression(expr, context) {
550
544
  const qqIdx = findOutsideQuotes(expr, "??");
551
- if (qqIdx !== -1) {
552
- const left = expr.slice(0, qqIdx).trim();
553
- const right = expr.slice(qqIdx + 2).trim();
554
- const val = evalExpr(left, context);
555
- if (val === null || val === void 0) {
556
- return evalExpr(right, context);
557
- }
558
- return val;
545
+ if (qqIdx === -1) return EXPR_NOT_MATCHED;
546
+ const value = evalExpr(expr.slice(0, qqIdx).trim(), context);
547
+ return value === null || value === void 0 ? evalExpr(expr.slice(qqIdx + 2).trim(), context) : value;
548
+ }
549
+ function evalConditional(expr, context) {
550
+ for (const evaluator of [evalTernaryExpression, evalInlineIfExpression, evalCoalesceExpression]) {
551
+ const result = evaluator(expr, context);
552
+ if (result !== EXPR_NOT_MATCHED) return result;
559
553
  }
554
+ return EXPR_NOT_MATCHED;
555
+ }
556
+ function evalConcatOrComparison(expr, context) {
560
557
  if (findOutsideQuotes(expr, "~") >= 0) {
561
558
  const parts = splitOutsideQuotes(expr, "~");
562
559
  if (parts.length > 1) {
@@ -574,6 +571,18 @@ function evalExpr(expr, context) {
574
571
  return evalComparison(expr, context);
575
572
  }
576
573
  }
574
+ return EXPR_NOT_MATCHED;
575
+ }
576
+ var ARITHMETIC_OPERATIONS = {
577
+ "+": (left, right) => left + right,
578
+ "-": (left, right) => left - right,
579
+ "*": (left, right) => left * right,
580
+ "//": (left, right) => right !== 0 ? Math.floor(left / right) : 0,
581
+ "/": (left, right) => right !== 0 ? left / right : 0,
582
+ "%": (left, right) => right !== 0 ? left % right : 0,
583
+ "**": (left, right) => left ** right
584
+ };
585
+ function evalArithmeticExpression(expr, context) {
577
586
  for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
578
587
  const pos = findOutsideQuotes(expr, op);
579
588
  if (pos >= 0) {
@@ -586,40 +595,15 @@ function evalExpr(expr, context) {
586
595
  let rNum = rVal != null ? Number(rVal) : 0;
587
596
  if (isNaN(lNum)) lNum = 0;
588
597
  if (isNaN(rNum)) rNum = 0;
589
- const opS = op.trim();
590
- const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
591
- let result;
592
- switch (opS) {
593
- case "+":
594
- result = lNum + rNum;
595
- break;
596
- case "-":
597
- result = lNum - rNum;
598
- break;
599
- case "*":
600
- result = lNum * rNum;
601
- break;
602
- case "//":
603
- result = rNum !== 0 ? Math.floor(lNum / rNum) : 0;
604
- break;
605
- case "/":
606
- result = rNum !== 0 ? lNum / rNum : 0;
607
- break;
608
- case "%":
609
- result = rNum !== 0 ? lNum % rNum : 0;
610
- break;
611
- case "**":
612
- result = lNum ** rNum;
613
- break;
614
- default:
615
- result = 0;
616
- }
617
- return bothInt && Number.isInteger(result) ? result : result;
598
+ return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
618
599
  } catch {
619
600
  return null;
620
601
  }
621
602
  }
622
603
  }
604
+ return EXPR_NOT_MATCHED;
605
+ }
606
+ function evalFilterExpression(expr, context) {
623
607
  if (findOutsideQuotes(expr, "|") >= 0) {
624
608
  const [baseExpr, filters] = parseFilterChain(expr);
625
609
  if (filters.length > 0) {
@@ -638,38 +622,58 @@ function evalExpr(expr, context) {
638
622
  return value;
639
623
  }
640
624
  }
641
- const fnMatch = expr.match(FN_CALL_RE);
642
- if (fnMatch) {
643
- const fnName = fnMatch[1];
644
- const rawArgs = fnMatch[2] || "";
645
- if (fnName.includes(".")) {
646
- const lastDot = fnName.lastIndexOf(".");
647
- const objPath = fnName.slice(0, lastDot);
648
- const methodName = fnName.slice(lastDot + 1);
649
- const obj = resolveVar(objPath, context);
650
- if (obj && typeof obj === "object" && methodName in obj) {
651
- const method = obj[methodName];
652
- if (typeof method === "function") {
653
- if (rawArgs.trim()) {
654
- const parts = splitArgs(rawArgs);
655
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
656
- return method.apply(obj, evalArgs);
657
- }
658
- return method.call(obj);
659
- }
660
- }
661
- } else {
662
- const fn = context[fnName] ?? resolveVar(fnName, context);
663
- if (typeof fn === "function") {
664
- if (rawArgs.trim()) {
665
- const parts = splitArgs(rawArgs);
666
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
667
- return fn(...evalArgs);
668
- }
669
- return fn();
670
- }
625
+ return EXPR_NOT_MATCHED;
626
+ }
627
+ function evaluateCallArgs(rawArgs, context) {
628
+ return rawArgs.trim() ? splitArgs(rawArgs).map((arg) => evalExpr(arg.trim(), context)) : [];
629
+ }
630
+ function evalDottedFunction(name, rawArgs, context) {
631
+ const lastDot = name.lastIndexOf(".");
632
+ const owner = resolveVar(name.slice(0, lastDot), context);
633
+ const member = name.slice(lastDot + 1);
634
+ if (!owner || typeof owner !== "object" || !(member in owner)) {
635
+ return EXPR_NOT_MATCHED;
636
+ }
637
+ const method = owner[member];
638
+ return typeof method === "function" ? method.apply(owner, evaluateCallArgs(rawArgs, context)) : EXPR_NOT_MATCHED;
639
+ }
640
+ function evalFunctionExpression(expr, context) {
641
+ const match = expr.match(FN_CALL_RE);
642
+ if (!match) return EXPR_NOT_MATCHED;
643
+ const name = match[1];
644
+ const rawArgs = match[2] || "";
645
+ if (name.includes(".")) return evalDottedFunction(name, rawArgs, context);
646
+ const fn = context[name] ?? resolveVar(name, context);
647
+ if (typeof fn === "function") return fn(...evaluateCallArgs(rawArgs, context));
648
+ return EXPR_NOT_MATCHED;
649
+ }
650
+ var EXPR_EVALUATORS = [
651
+ evalPrimary,
652
+ evalConditional,
653
+ evalConcatOrComparison,
654
+ evalArithmeticExpression,
655
+ evalFilterExpression,
656
+ evalFunctionExpression
657
+ ];
658
+ var expressionFormCache = /* @__PURE__ */ new Map();
659
+ function evalExpr(expr, context) {
660
+ expr = expr.trim();
661
+ const cachedForm = expressionFormCache.get(expr);
662
+ if (cachedForm !== void 0) {
663
+ if (cachedForm === -1) return resolveVar(expr, context);
664
+ const result = EXPR_EVALUATORS[cachedForm](expr, context);
665
+ return result === EXPR_NOT_MATCHED ? resolveVar(expr, context) : result;
666
+ }
667
+ for (let index = 0; index < EXPR_EVALUATORS.length; index++) {
668
+ const result = EXPR_EVALUATORS[index](expr, context);
669
+ if (result !== EXPR_NOT_MATCHED) {
670
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
671
+ expressionFormCache.set(expr, index);
672
+ return result;
671
673
  }
672
674
  }
675
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
676
+ expressionFormCache.set(expr, FN_CALL_RE.test(expr) ? EXPR_EVALUATORS.length - 1 : -1);
673
677
  return resolveVar(expr, context);
674
678
  }
675
679
  function findTernary(expr) {