srcly 0.1.19__tar.gz → 0.1.20__tar.gz
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.
- {srcly-0.1.19 → srcly-0.1.20}/PKG-INFO +1 -1
- {srcly-0.1.19 → srcly-0.1.20}/app/routers/files.py +3 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/analysis.py +171 -38
- {srcly-0.1.19 → srcly-0.1.20}/app/services/python/python_analysis.py +68 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/typescript/typescript_analysis.py +122 -12
- srcly-0.1.20/app/static/assets/index-DAI1ORsS.js +66 -0
- srcly-0.1.19/app/static/assets/index-Tx90OM1c.css → srcly-0.1.20/app/static/assets/index-Dpk19KOJ.css +1 -1
- {srcly-0.1.19 → srcly-0.1.20}/app/static/index.html +2 -2
- {srcly-0.1.19 → srcly-0.1.20}/pyproject.toml +1 -1
- srcly-0.1.19/app/static/assets/index-CeLPDK_o.js +0 -66
- {srcly-0.1.19 → srcly-0.1.20}/.gitignore +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/README.md +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/.gitignore +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/__init__.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/config.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/main.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/models.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/routers/analysis.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/run.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/analysis_types.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/cache.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/css/css_analysis.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/data_flow_analysis.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/ipynb/ipynb_analysis.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/services/markdown/markdown_analysis.py +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.19 → srcly-0.1.20}/app/static/vite.svg +0 -0
|
@@ -24,6 +24,9 @@ async def get_file_content(path: str = Query(..., description="Absolute path to
|
|
|
24
24
|
# For this local tool, we'll allow reading any file as requested, but maybe warn?
|
|
25
25
|
|
|
26
26
|
try:
|
|
27
|
+
# Log every file read request before processing so hangs/crashes are attributable.
|
|
28
|
+
print(f"📄 Reading file content: {file_path}", flush=True)
|
|
29
|
+
|
|
27
30
|
if file_path.suffix == ".ipynb":
|
|
28
31
|
return get_ipynb_analyzer().get_virtual_content(str(file_path))
|
|
29
32
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import lizard
|
|
3
|
-
import
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import time
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from app.models import Node, Metrics
|
|
@@ -14,6 +15,20 @@ from pathspec import PathSpec
|
|
|
14
15
|
# Maximum time allowed for analyzing a single file in a worker process.
|
|
15
16
|
PER_FILE_ANALYSIS_TIMEOUT_SECONDS: float = 10.0
|
|
16
17
|
|
|
18
|
+
# Max parallel workers for per-file analysis. Each file is analyzed in its own
|
|
19
|
+
# subprocess so we can enforce a *hard* timeout and terminate hung analysis.
|
|
20
|
+
def _default_max_workers() -> int:
|
|
21
|
+
"""
|
|
22
|
+
Pick a reasonable default worker count close to available CPU cores.
|
|
23
|
+
|
|
24
|
+
We keep 1 core of headroom so the main process / OS remains responsive.
|
|
25
|
+
"""
|
|
26
|
+
cpu = os.cpu_count() or 4
|
|
27
|
+
return max(1, cpu - 1)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
MAX_ANALYSIS_WORKERS: int = _default_max_workers()
|
|
31
|
+
|
|
17
32
|
_ts_analyzer = None
|
|
18
33
|
_md_analyzer = None
|
|
19
34
|
_ipynb_analyzer = None
|
|
@@ -209,13 +224,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
209
224
|
node.children.append(func_node)
|
|
210
225
|
func_sum_loc += func.nloc
|
|
211
226
|
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
misc_node = create_node("(misc/imports)", "code_fragment", f"{node.path}::__misc__")
|
|
216
|
-
misc_node.metrics.loc = remainder
|
|
217
|
-
misc_node.metrics.complexity = 0 # Glue code is usually simple
|
|
218
|
-
node.children.append(misc_node)
|
|
227
|
+
# NOTE: We no longer create a synthetic child node for module-level "glue".
|
|
228
|
+
# Any top-level/module lines should be represented as the (body) of the
|
|
229
|
+
# file/module scope (handled by the newer scope-body attribution logic).
|
|
219
230
|
|
|
220
231
|
|
|
221
232
|
def _translate_gitignore_pattern(raw_line: str, base_rel: str) -> str | None:
|
|
@@ -390,7 +401,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
390
401
|
total_function_loc += (child_metrics.average_function_length * child_metrics.function_count)
|
|
391
402
|
|
|
392
403
|
# For Folders: Sum of children
|
|
393
|
-
# For Files: We trust the attach_file_metrics logic
|
|
404
|
+
# For Files: We trust the attach_file_metrics logic.
|
|
394
405
|
if node.type == "folder":
|
|
395
406
|
node.metrics.loc = total_loc
|
|
396
407
|
node.metrics.complexity = max_complexity
|
|
@@ -446,6 +457,147 @@ def analyze_single_file(file_path: str):
|
|
|
446
457
|
# Return error info instead of crashing
|
|
447
458
|
return {"error": str(e), "filename": file_path}
|
|
448
459
|
|
|
460
|
+
|
|
461
|
+
def _analyze_file_in_subprocess(file_path: str, send_conn) -> None:
|
|
462
|
+
"""
|
|
463
|
+
Child-process entry point. Runs analysis and sends the result back over a pipe.
|
|
464
|
+
Must be top-level for multiprocessing pickling.
|
|
465
|
+
"""
|
|
466
|
+
try:
|
|
467
|
+
result = analyze_single_file(file_path)
|
|
468
|
+
except Exception as e:
|
|
469
|
+
result = {"error": str(e), "filename": file_path}
|
|
470
|
+
try:
|
|
471
|
+
send_conn.send(result)
|
|
472
|
+
except Exception:
|
|
473
|
+
# If sending fails, there's nothing useful we can do here.
|
|
474
|
+
pass
|
|
475
|
+
finally:
|
|
476
|
+
try:
|
|
477
|
+
send_conn.close()
|
|
478
|
+
except Exception:
|
|
479
|
+
pass
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _run_file_analyses_with_hard_timeouts(
|
|
483
|
+
files_to_scan: list[str],
|
|
484
|
+
timeout_seconds: float,
|
|
485
|
+
max_workers: int,
|
|
486
|
+
) -> list:
|
|
487
|
+
"""
|
|
488
|
+
Analyze files with a *hard* per-file timeout by running each file in its own
|
|
489
|
+
subprocess. This avoids the common pitfall where a ProcessPoolExecutor can
|
|
490
|
+
hang forever if a worker gets stuck, because individual tasks cannot be
|
|
491
|
+
force-killed reliably.
|
|
492
|
+
"""
|
|
493
|
+
if not files_to_scan:
|
|
494
|
+
return []
|
|
495
|
+
|
|
496
|
+
ctx = multiprocessing.get_context("spawn")
|
|
497
|
+
total_count = len(files_to_scan)
|
|
498
|
+
completed_count = 0
|
|
499
|
+
|
|
500
|
+
# Active entries: (process, file_path, start_time, recv_conn)
|
|
501
|
+
active: list[tuple[multiprocessing.Process, str, float, object]] = []
|
|
502
|
+
results: list = []
|
|
503
|
+
|
|
504
|
+
next_index = 0
|
|
505
|
+
while next_index < total_count or active:
|
|
506
|
+
# Fill up worker slots.
|
|
507
|
+
while next_index < total_count and len(active) < max_workers:
|
|
508
|
+
file_path = files_to_scan[next_index]
|
|
509
|
+
next_index += 1
|
|
510
|
+
|
|
511
|
+
# Log every file *before* it is processed so we can identify the
|
|
512
|
+
# last-started file if analysis hangs or crashes.
|
|
513
|
+
print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", flush=True)
|
|
514
|
+
|
|
515
|
+
recv_conn, send_conn = ctx.Pipe(duplex=False)
|
|
516
|
+
proc = ctx.Process(
|
|
517
|
+
target=_analyze_file_in_subprocess,
|
|
518
|
+
args=(file_path, send_conn),
|
|
519
|
+
daemon=True,
|
|
520
|
+
)
|
|
521
|
+
start_time = time.time()
|
|
522
|
+
proc.start()
|
|
523
|
+
# Close the child end in the parent process to avoid leaks.
|
|
524
|
+
try:
|
|
525
|
+
send_conn.close()
|
|
526
|
+
except Exception:
|
|
527
|
+
pass
|
|
528
|
+
|
|
529
|
+
active.append((proc, file_path, start_time, recv_conn))
|
|
530
|
+
|
|
531
|
+
# Check running workers for completion or timeout.
|
|
532
|
+
still_active: list[tuple[multiprocessing.Process, str, float, object]] = []
|
|
533
|
+
now = time.time()
|
|
534
|
+
|
|
535
|
+
for proc, file_path, start_time, recv_conn in active:
|
|
536
|
+
elapsed = now - start_time
|
|
537
|
+
|
|
538
|
+
if proc.is_alive() and elapsed <= timeout_seconds:
|
|
539
|
+
still_active.append((proc, file_path, start_time, recv_conn))
|
|
540
|
+
continue
|
|
541
|
+
|
|
542
|
+
# Either finished, or timed out.
|
|
543
|
+
if proc.is_alive() and elapsed > timeout_seconds:
|
|
544
|
+
completed_count += 1
|
|
545
|
+
print(
|
|
546
|
+
f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
|
|
547
|
+
flush=True,
|
|
548
|
+
)
|
|
549
|
+
try:
|
|
550
|
+
proc.terminate()
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
try:
|
|
554
|
+
proc.join(timeout=1.0)
|
|
555
|
+
except Exception:
|
|
556
|
+
pass
|
|
557
|
+
try:
|
|
558
|
+
recv_conn.close()
|
|
559
|
+
except Exception:
|
|
560
|
+
pass
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
# Process finished; collect result if possible.
|
|
564
|
+
try:
|
|
565
|
+
proc.join(timeout=0.0)
|
|
566
|
+
except Exception:
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
completed_count += 1
|
|
570
|
+
result = None
|
|
571
|
+
try:
|
|
572
|
+
# If the child crashed before sending anything, recv may raise EOFError.
|
|
573
|
+
result = recv_conn.recv()
|
|
574
|
+
except Exception as exc:
|
|
575
|
+
result = {"error": str(exc), "filename": file_path}
|
|
576
|
+
finally:
|
|
577
|
+
try:
|
|
578
|
+
recv_conn.close()
|
|
579
|
+
except Exception:
|
|
580
|
+
pass
|
|
581
|
+
|
|
582
|
+
if isinstance(result, dict) and "error" in result:
|
|
583
|
+
print(
|
|
584
|
+
f"❌ [{completed_count}/{total_count}] Error analyzing {file_path}: {result.get('error')}",
|
|
585
|
+
flush=True,
|
|
586
|
+
)
|
|
587
|
+
else:
|
|
588
|
+
if _should_log_file_progress(completed_count, total_count):
|
|
589
|
+
print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", flush=True)
|
|
590
|
+
results.append(result)
|
|
591
|
+
|
|
592
|
+
active = still_active
|
|
593
|
+
|
|
594
|
+
# Avoid busy-looping when workers are running.
|
|
595
|
+
if active and (next_index < total_count or active):
|
|
596
|
+
time.sleep(0.02)
|
|
597
|
+
|
|
598
|
+
return results
|
|
599
|
+
|
|
600
|
+
|
|
449
601
|
def scan_codebase(root_path: Path) -> Node:
|
|
450
602
|
print(f"🔍 Scanning: {root_path}", flush=True)
|
|
451
603
|
|
|
@@ -489,35 +641,16 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
489
641
|
if current_ignored_count > 0:
|
|
490
642
|
ignored_counts[str(root_dir_path)] = current_ignored_count
|
|
491
643
|
|
|
492
|
-
print(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
total_count = len(files_to_scan)
|
|
503
|
-
|
|
504
|
-
for future in concurrent.futures.as_completed(future_to_file):
|
|
505
|
-
file = future_to_file[future]
|
|
506
|
-
completed_count += 1
|
|
507
|
-
try:
|
|
508
|
-
# Add a timeout to prevent hanging on single files
|
|
509
|
-
result = future.result(timeout=PER_FILE_ANALYSIS_TIMEOUT_SECONDS)
|
|
510
|
-
if isinstance(result, dict) and "error" in result:
|
|
511
|
-
print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
|
|
512
|
-
else:
|
|
513
|
-
# Success
|
|
514
|
-
if _should_log_file_progress(completed_count, total_count):
|
|
515
|
-
print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
|
|
516
|
-
analysis_results.append(result)
|
|
517
|
-
except concurrent.futures.TimeoutError:
|
|
518
|
-
print(f"❌ [{completed_count}/{total_count}] Timeout analyzing {file} (skipped)", flush=True)
|
|
519
|
-
except Exception as exc:
|
|
520
|
-
print(f"❌ [{completed_count}/{total_count}] Exception analyzing {file}: {exc}", flush=True)
|
|
644
|
+
print(
|
|
645
|
+
f"📂 Analyzing {len(files_to_scan)} source files... (workers={MAX_ANALYSIS_WORKERS}, timeout={PER_FILE_ANALYSIS_TIMEOUT_SECONDS}s)",
|
|
646
|
+
flush=True,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
analysis_results = _run_file_analyses_with_hard_timeouts(
|
|
650
|
+
files_to_scan=files_to_scan,
|
|
651
|
+
timeout_seconds=PER_FILE_ANALYSIS_TIMEOUT_SECONDS,
|
|
652
|
+
max_workers=MAX_ANALYSIS_WORKERS,
|
|
653
|
+
)
|
|
521
654
|
|
|
522
655
|
tree_root = create_node("root", "folder", str(root_path))
|
|
523
656
|
node_map = {str(root_path): tree_root}
|
|
@@ -22,6 +22,9 @@ class PythonTreeSitterAnalyzer:
|
|
|
22
22
|
|
|
23
23
|
# Extract functions, classes, and other scopes
|
|
24
24
|
functions = self._extract_scopes(tree.root_node)
|
|
25
|
+
import_scope = self._compute_import_scope(tree.root_node, content)
|
|
26
|
+
if import_scope is not None:
|
|
27
|
+
functions.insert(0, import_scope)
|
|
25
28
|
|
|
26
29
|
avg_complexity = 0.0
|
|
27
30
|
if functions:
|
|
@@ -71,6 +74,71 @@ class PythonTreeSitterAnalyzer:
|
|
|
71
74
|
md_data_url_count=0
|
|
72
75
|
)
|
|
73
76
|
|
|
77
|
+
def _compute_import_scope(self, root_node: Node, content: bytes) -> FunctionMetrics | None:
|
|
78
|
+
"""
|
|
79
|
+
Create a synthetic top-level scope representing import statements.
|
|
80
|
+
|
|
81
|
+
- nloc: total LOC occupied by *all* import statements in the file
|
|
82
|
+
- start/end_line: span of the largest contiguous block of imports
|
|
83
|
+
"""
|
|
84
|
+
import_spans: List[tuple[int, int]] = []
|
|
85
|
+
lines = content.splitlines()
|
|
86
|
+
|
|
87
|
+
def only_blank_lines_between(end_line: int, start_line: int) -> bool:
|
|
88
|
+
if start_line <= end_line + 1:
|
|
89
|
+
return True
|
|
90
|
+
for line_no in range(end_line + 1, start_line):
|
|
91
|
+
idx = line_no - 1
|
|
92
|
+
if 0 <= idx < len(lines):
|
|
93
|
+
if lines[idx].strip():
|
|
94
|
+
return False
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def traverse(n: Node) -> None:
|
|
98
|
+
if n.type in {"import_statement", "import_from_statement"}:
|
|
99
|
+
start_line = n.start_point.row + 1
|
|
100
|
+
end_line = n.end_point.row + 1
|
|
101
|
+
import_spans.append((start_line, end_line))
|
|
102
|
+
for child in n.children:
|
|
103
|
+
traverse(child)
|
|
104
|
+
|
|
105
|
+
traverse(root_node)
|
|
106
|
+
|
|
107
|
+
if not import_spans:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
import_spans.sort(key=lambda s: (s[0], s[1]))
|
|
111
|
+
total_import_loc = sum(e - s + 1 for s, e in import_spans)
|
|
112
|
+
|
|
113
|
+
blocks: List[tuple[int, int, int]] = [] # (block_start, block_end, block_loc_sum)
|
|
114
|
+
cur_s, cur_e = import_spans[0]
|
|
115
|
+
cur_loc = cur_e - cur_s + 1
|
|
116
|
+
for s, e in import_spans[1:]:
|
|
117
|
+
# Allow blank lines between imports to still count as one contiguous block.
|
|
118
|
+
if only_blank_lines_between(cur_e, s):
|
|
119
|
+
cur_e = max(cur_e, e)
|
|
120
|
+
cur_loc += (e - s + 1)
|
|
121
|
+
else:
|
|
122
|
+
blocks.append((cur_s, cur_e, cur_loc))
|
|
123
|
+
cur_s, cur_e = s, e
|
|
124
|
+
cur_loc = e - s + 1
|
|
125
|
+
blocks.append((cur_s, cur_e, cur_loc))
|
|
126
|
+
|
|
127
|
+
block_s, block_e, _ = max(blocks, key=lambda b: (b[2], -b[0]))
|
|
128
|
+
|
|
129
|
+
return FunctionMetrics(
|
|
130
|
+
name="(imports)",
|
|
131
|
+
cyclomatic_complexity=0,
|
|
132
|
+
nloc=total_import_loc,
|
|
133
|
+
start_line=block_s,
|
|
134
|
+
end_line=block_e,
|
|
135
|
+
parameter_count=0,
|
|
136
|
+
max_nesting_depth=0,
|
|
137
|
+
comment_lines=0,
|
|
138
|
+
todo_count=0,
|
|
139
|
+
origin_type="imports",
|
|
140
|
+
)
|
|
141
|
+
|
|
74
142
|
def _extract_scopes(self, root_node: Node) -> List[FunctionMetrics]:
|
|
75
143
|
# We want to capture:
|
|
76
144
|
# - Functions (def)
|
|
@@ -27,6 +27,11 @@ class TreeSitterAnalyzer:
|
|
|
27
27
|
nloc = len([l for l in lines if l.strip()])
|
|
28
28
|
|
|
29
29
|
functions = self._extract_functions(tree.root_node, content)
|
|
30
|
+
import_scope = self._compute_import_scope(tree.root_node, content)
|
|
31
|
+
if import_scope is not None:
|
|
32
|
+
# Make imports visible as a top-level "scope" in treemap/scopes views.
|
|
33
|
+
functions.insert(0, import_scope)
|
|
34
|
+
|
|
30
35
|
# Post-process TSX scopes so that each function containing TSX gets a
|
|
31
36
|
# single virtual root "<fragment>" node that groups all JSX container
|
|
32
37
|
# scopes beneath it. This keeps TSX scopes together without changing
|
|
@@ -97,6 +102,79 @@ class TreeSitterAnalyzer:
|
|
|
97
102
|
ts_export_count=ts_export_count,
|
|
98
103
|
)
|
|
99
104
|
|
|
105
|
+
def _compute_import_scope(self, root_node: Node, content: bytes) -> FunctionMetrics | None:
|
|
106
|
+
"""
|
|
107
|
+
Create a synthetic top-level scope representing import statements.
|
|
108
|
+
|
|
109
|
+
- nloc: total LOC occupied by *all* import statements in the file
|
|
110
|
+
- start/end_line: span of the largest contiguous block of imports
|
|
111
|
+
"""
|
|
112
|
+
import_spans: List[tuple[int, int]] = []
|
|
113
|
+
lines = content.splitlines()
|
|
114
|
+
|
|
115
|
+
def only_blank_lines_between(end_line: int, start_line: int) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Returns True if every line strictly between end_line and start_line
|
|
118
|
+
is blank/whitespace-only. Lines are 1-based.
|
|
119
|
+
"""
|
|
120
|
+
if start_line <= end_line + 1:
|
|
121
|
+
return True
|
|
122
|
+
# Between (end_line+1) and (start_line-1), inclusive.
|
|
123
|
+
for line_no in range(end_line + 1, start_line):
|
|
124
|
+
idx = line_no - 1
|
|
125
|
+
if 0 <= idx < len(lines):
|
|
126
|
+
if lines[idx].strip():
|
|
127
|
+
return False
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
def traverse(n: Node) -> None:
|
|
131
|
+
if n.type == "import_statement":
|
|
132
|
+
start_line = n.start_point.row + 1
|
|
133
|
+
end_line = n.end_point.row + 1
|
|
134
|
+
import_spans.append((start_line, end_line))
|
|
135
|
+
for child in n.children:
|
|
136
|
+
traverse(child)
|
|
137
|
+
|
|
138
|
+
traverse(root_node)
|
|
139
|
+
|
|
140
|
+
if not import_spans:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
import_spans.sort(key=lambda s: (s[0], s[1]))
|
|
144
|
+
total_import_loc = sum(e - s + 1 for s, e in import_spans)
|
|
145
|
+
|
|
146
|
+
# Merge adjacent import statements into contiguous blocks.
|
|
147
|
+
blocks: List[tuple[int, int, int]] = [] # (block_start, block_end, block_loc_sum)
|
|
148
|
+
cur_s, cur_e = import_spans[0]
|
|
149
|
+
cur_loc = cur_e - cur_s + 1
|
|
150
|
+
for s, e in import_spans[1:]:
|
|
151
|
+
# Allow blank lines between imports to still count as one contiguous block.
|
|
152
|
+
if only_blank_lines_between(cur_e, s):
|
|
153
|
+
cur_e = max(cur_e, e)
|
|
154
|
+
cur_loc += (e - s + 1)
|
|
155
|
+
else:
|
|
156
|
+
blocks.append((cur_s, cur_e, cur_loc))
|
|
157
|
+
cur_s, cur_e = s, e
|
|
158
|
+
cur_loc = e - s + 1
|
|
159
|
+
blocks.append((cur_s, cur_e, cur_loc))
|
|
160
|
+
|
|
161
|
+
# Pick the largest block (by LOC, then by earliest start line for stability).
|
|
162
|
+
block_s, block_e, _ = max(blocks, key=lambda b: (b[2], -b[0]))
|
|
163
|
+
|
|
164
|
+
return FunctionMetrics(
|
|
165
|
+
name="(imports)",
|
|
166
|
+
cyclomatic_complexity=0,
|
|
167
|
+
nloc=total_import_loc,
|
|
168
|
+
start_line=block_s,
|
|
169
|
+
end_line=block_e,
|
|
170
|
+
parameter_count=0,
|
|
171
|
+
max_nesting_depth=0,
|
|
172
|
+
comment_lines=0,
|
|
173
|
+
todo_count=0,
|
|
174
|
+
ts_type_interface_count=0,
|
|
175
|
+
origin_type="imports",
|
|
176
|
+
)
|
|
177
|
+
|
|
100
178
|
def _extract_functions(self, root_node: Node, content: bytes) -> List[FunctionMetrics]:
|
|
101
179
|
function_types = {
|
|
102
180
|
'function_declaration',
|
|
@@ -213,7 +291,41 @@ class TreeSitterAnalyzer:
|
|
|
213
291
|
return f"{name_node.text.decode('utf-8')} (type)"
|
|
214
292
|
return "type"
|
|
215
293
|
elif node.type == 'object':
|
|
216
|
-
|
|
294
|
+
# TSX / JSX: object literal used inside a JSX attribute expression, e.g.
|
|
295
|
+
# <TapestryNode class={classy(..., { wireframe: () => ... })} />
|
|
296
|
+
# Prefer naming after the owning attribute: "class (obj)".
|
|
297
|
+
current = node
|
|
298
|
+
hops = 0
|
|
299
|
+
function_boundary_types = {
|
|
300
|
+
'function_declaration',
|
|
301
|
+
'method_definition',
|
|
302
|
+
'arrow_function',
|
|
303
|
+
'function_expression',
|
|
304
|
+
'generator_function',
|
|
305
|
+
'generator_function_declaration',
|
|
306
|
+
}
|
|
307
|
+
while current is not None and hops < 12:
|
|
308
|
+
if current.type == 'jsx_attribute':
|
|
309
|
+
name_node = current.child_by_field_name('name')
|
|
310
|
+
if name_node is None:
|
|
311
|
+
for c in current.children:
|
|
312
|
+
if c.type in {"property_identifier", "identifier", "jsx_identifier"}:
|
|
313
|
+
name_node = c
|
|
314
|
+
break
|
|
315
|
+
if name_node:
|
|
316
|
+
return f"{name_node.text.decode('utf-8')} (obj)"
|
|
317
|
+
break
|
|
318
|
+
|
|
319
|
+
# Don't cross into a nested function/container; if the object literal
|
|
320
|
+
# is inside a callback, it should be named by its local context instead.
|
|
321
|
+
if current is not node and current.type in function_boundary_types:
|
|
322
|
+
break
|
|
323
|
+
if current.type in {'program', 'statement_block'}:
|
|
324
|
+
break
|
|
325
|
+
current = current.parent
|
|
326
|
+
hops += 1
|
|
327
|
+
|
|
328
|
+
# Try to find the name from the parent assignment or property
|
|
217
329
|
parent = node.parent
|
|
218
330
|
if parent:
|
|
219
331
|
# const obj = { ... }
|
|
@@ -410,21 +522,19 @@ class TreeSitterAnalyzer:
|
|
|
410
522
|
|
|
411
523
|
def _expression_defines_function(self, expr_node: Node) -> bool:
|
|
412
524
|
# Check if the expression contains an inline function definition
|
|
413
|
-
# It might be wrapped in parenthesized_expression
|
|
525
|
+
# It might be wrapped in parenthesized_expression, and it may be nested
|
|
526
|
+
# inside other expressions (e.g. call arguments, object literals) like:
|
|
527
|
+
# class={classy(..., { wireframe: () => ... })}
|
|
414
528
|
|
|
415
|
-
def has_func(n: Node):
|
|
416
|
-
if n.type in {
|
|
529
|
+
def has_func(n: Node) -> bool:
|
|
530
|
+
if n.type in {"arrow_function", "function_expression"}:
|
|
417
531
|
return True
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return True
|
|
532
|
+
for c in n.children:
|
|
533
|
+
if has_func(c):
|
|
534
|
+
return True
|
|
422
535
|
return False
|
|
423
536
|
|
|
424
|
-
|
|
425
|
-
if has_func(child):
|
|
426
|
-
return True
|
|
427
|
-
return False
|
|
537
|
+
return has_func(expr_node)
|
|
428
538
|
|
|
429
539
|
def _calculate_complexity(self, node: Node) -> int:
|
|
430
540
|
complexity = 1
|