srcly 0.1.11__tar.gz → 0.1.14__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.11 → srcly-0.1.14}/PKG-INFO +2 -1
- {srcly-0.1.11 → srcly-0.1.14}/app/models.py +19 -0
- srcly-0.1.14/app/routers/analysis.py +608 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/services/analysis.py +58 -2
- srcly-0.1.14/app/services/data_flow_analysis.py +875 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/services/tree_sitter_analysis.py +583 -2
- srcly-0.1.14/app/static/assets/index-CTNf0zED.css +1 -0
- srcly-0.1.14/app/static/assets/index-DzyR2Ke2.js +37 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/index.html +2 -2
- {srcly-0.1.11 → srcly-0.1.14}/pyproject.toml +2 -1
- srcly-0.1.11/app/routers/analysis.py +0 -99
- srcly-0.1.11/app/static/assets/index-BjRxOY6r.css +0 -1
- srcly-0.1.11/app/static/assets/index-poSkqVZY.js +0 -14
- {srcly-0.1.11 → srcly-0.1.14}/.gitignore +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/README.md +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/.gitignore +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/__init__.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/config.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/main.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/routers/files.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/run.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/services/cache.py +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.11 → srcly-0.1.14}/app/static/vite.svg +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: srcly
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Interactive codebase treemap and metrics viewer.
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: fastapi>=0.121.3
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
7
8
|
Requires-Dist: lizard>=1.19.0
|
|
8
9
|
Requires-Dist: pathspec>=0.12.1
|
|
9
10
|
Requires-Dist: pydantic>=2.12.4
|
|
@@ -48,3 +48,22 @@ class Node(BaseModel):
|
|
|
48
48
|
model_config = {
|
|
49
49
|
"populate_by_name": True
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
class DependencyNode(BaseModel):
|
|
53
|
+
id: str
|
|
54
|
+
label: str
|
|
55
|
+
# "file", "external", "export"
|
|
56
|
+
type: str = "file"
|
|
57
|
+
# For nested/compound graph layouts (e.g. exports inside file nodes)
|
|
58
|
+
parent: str | None = None
|
|
59
|
+
metrics: Metrics | None = None
|
|
60
|
+
|
|
61
|
+
class DependencyEdge(BaseModel):
|
|
62
|
+
id: str
|
|
63
|
+
source: str
|
|
64
|
+
target: str
|
|
65
|
+
label: str | None = None
|
|
66
|
+
|
|
67
|
+
class DependencyGraph(BaseModel):
|
|
68
|
+
nodes: List[DependencyNode]
|
|
69
|
+
edges: List[DependencyEdge]
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Dict, List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
|
|
8
|
+
from app.services import analysis, cache, tree_sitter_analysis
|
|
9
|
+
from app.config import IGNORE_DIRS
|
|
10
|
+
|
|
11
|
+
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
|
|
12
|
+
|
|
13
|
+
# TODO: Make this configurable via env var or request
|
|
14
|
+
ROOT_PATH = Path.cwd()
|
|
15
|
+
|
|
16
|
+
TSCONFIG_CANDIDATE_NAMES: Tuple[str, ...] = (
|
|
17
|
+
"tsconfig.json",
|
|
18
|
+
"tsconfig.app.json",
|
|
19
|
+
"tsconfig.base.json",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _strip_json_comments(text: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Best-effort removal of `//` and `/* ... */` comments from a JSON-like file.
|
|
26
|
+
|
|
27
|
+
TypeScript config files (`tsconfig.json`, `tsconfig.app.json`, etc.) are
|
|
28
|
+
JSON-with-comments. The standard `json` module cannot parse them directly,
|
|
29
|
+
so we strip comments while preserving string literals as far as is
|
|
30
|
+
reasonably practical.
|
|
31
|
+
|
|
32
|
+
This is not a full JSONC parser but is sufficient for typical tsconfig
|
|
33
|
+
usage: it understands string literals (single or double quoted), escape
|
|
34
|
+
sequences, line comments and block comments.
|
|
35
|
+
"""
|
|
36
|
+
result: list[str] = []
|
|
37
|
+
i = 0
|
|
38
|
+
n = len(text)
|
|
39
|
+
in_string = False
|
|
40
|
+
string_quote = ""
|
|
41
|
+
in_line_comment = False
|
|
42
|
+
in_block_comment = False
|
|
43
|
+
|
|
44
|
+
while i < n:
|
|
45
|
+
ch = text[i]
|
|
46
|
+
nxt = text[i + 1] if i + 1 < n else ""
|
|
47
|
+
|
|
48
|
+
if in_line_comment:
|
|
49
|
+
# End of line comment at newline; keep the newline itself.
|
|
50
|
+
if ch == "\n":
|
|
51
|
+
in_line_comment = False
|
|
52
|
+
result.append(ch)
|
|
53
|
+
i += 1
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if in_block_comment:
|
|
57
|
+
# End of block comment at */
|
|
58
|
+
if ch == "*" and nxt == "/":
|
|
59
|
+
in_block_comment = False
|
|
60
|
+
i += 2
|
|
61
|
+
else:
|
|
62
|
+
i += 1
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if in_string:
|
|
66
|
+
result.append(ch)
|
|
67
|
+
if ch == "\\":
|
|
68
|
+
# Preserve escaped character and skip over it.
|
|
69
|
+
if i + 1 < n:
|
|
70
|
+
result.append(text[i + 1])
|
|
71
|
+
i += 2
|
|
72
|
+
else:
|
|
73
|
+
i += 1
|
|
74
|
+
elif ch == string_quote:
|
|
75
|
+
in_string = False
|
|
76
|
+
i += 1
|
|
77
|
+
else:
|
|
78
|
+
i += 1
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# Not currently in string or comment.
|
|
82
|
+
if ch in ("'", '"'):
|
|
83
|
+
in_string = True
|
|
84
|
+
string_quote = ch
|
|
85
|
+
result.append(ch)
|
|
86
|
+
i += 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if ch == "/" and nxt == "/":
|
|
90
|
+
in_line_comment = True
|
|
91
|
+
i += 2
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
if ch == "/" and nxt == "*":
|
|
95
|
+
in_block_comment = True
|
|
96
|
+
i += 2
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
result.append(ch)
|
|
100
|
+
i += 1
|
|
101
|
+
|
|
102
|
+
return "".join(result)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _find_candidate_tsconfig_files(start: Path) -> List[Path]:
|
|
106
|
+
"""
|
|
107
|
+
Walk up from the given path and collect any nearby tsconfig-style files.
|
|
108
|
+
|
|
109
|
+
We keep this deliberately simple for now:
|
|
110
|
+
- Look for a small set of common tsconfig filenames.
|
|
111
|
+
- Stop at the filesystem root.
|
|
112
|
+
- Return candidates ordered from nearest directory upwards.
|
|
113
|
+
"""
|
|
114
|
+
current = start if start.is_dir() else start.parent
|
|
115
|
+
seen: set[Path] = set()
|
|
116
|
+
candidates: List[Path] = []
|
|
117
|
+
|
|
118
|
+
for parent in [current, *current.parents]:
|
|
119
|
+
for name in TSCONFIG_CANDIDATE_NAMES:
|
|
120
|
+
candidate = parent / name
|
|
121
|
+
if candidate.exists() and candidate not in seen:
|
|
122
|
+
seen.add(candidate)
|
|
123
|
+
candidates.append(candidate)
|
|
124
|
+
|
|
125
|
+
return candidates
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _load_tsconfig_paths(tsconfig_path: Path) -> Tuple[Path, Dict[str, List[str]]]:
|
|
129
|
+
"""
|
|
130
|
+
Load `compilerOptions.baseUrl` and `compilerOptions.paths` from a tsconfig.
|
|
131
|
+
|
|
132
|
+
Returns a tuple of:
|
|
133
|
+
- base_dir: the directory from which path mappings should be resolved
|
|
134
|
+
- paths: the raw `paths` mapping (alias -> list of patterns)
|
|
135
|
+
|
|
136
|
+
If anything goes wrong, we fall back to treating the tsconfig's parent
|
|
137
|
+
directory as the base and an empty paths mapping.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
with tsconfig_path.open("r", encoding="utf-8") as f:
|
|
141
|
+
raw = f.read()
|
|
142
|
+
# tsconfig files are JSON-with-comments; strip comments before parsing.
|
|
143
|
+
cleaned = _strip_json_comments(raw)
|
|
144
|
+
data = json.loads(cleaned)
|
|
145
|
+
except Exception:
|
|
146
|
+
# On any failure, fall back to treating the tsconfig directory as the
|
|
147
|
+
# base and ignoring custom path mappings. This preserves previous
|
|
148
|
+
# behaviour while allowing us to handle commented configs when parsing
|
|
149
|
+
# succeeds.
|
|
150
|
+
return tsconfig_path.parent, {}
|
|
151
|
+
|
|
152
|
+
compiler = data.get("compilerOptions") or {}
|
|
153
|
+
base_url = compiler.get("baseUrl")
|
|
154
|
+
|
|
155
|
+
if isinstance(base_url, str) and base_url.strip():
|
|
156
|
+
base_dir = (tsconfig_path.parent / base_url).resolve()
|
|
157
|
+
else:
|
|
158
|
+
base_dir = tsconfig_path.parent.resolve()
|
|
159
|
+
|
|
160
|
+
raw_paths = compiler.get("paths") or {}
|
|
161
|
+
paths: Dict[str, List[str]] = {}
|
|
162
|
+
|
|
163
|
+
if isinstance(raw_paths, dict):
|
|
164
|
+
for key, value in raw_paths.items():
|
|
165
|
+
if isinstance(key, str):
|
|
166
|
+
# Normalise to list[str]
|
|
167
|
+
if isinstance(value, list):
|
|
168
|
+
paths[key] = [v for v in value if isinstance(v, str)]
|
|
169
|
+
elif isinstance(value, str):
|
|
170
|
+
paths[key] = [value]
|
|
171
|
+
|
|
172
|
+
return base_dir, paths
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _apply_tsconfig_paths(
|
|
176
|
+
import_path: str,
|
|
177
|
+
base_dir: Path,
|
|
178
|
+
paths: Dict[str, List[str]],
|
|
179
|
+
) -> List[Path]:
|
|
180
|
+
"""
|
|
181
|
+
Apply TypeScript path aliases to an import path to get candidate filesystem paths.
|
|
182
|
+
|
|
183
|
+
We handle the most common/simple cases:
|
|
184
|
+
- Exact alias: \"@core\": [\"src/core/index.ts\"]
|
|
185
|
+
- Single trailing wildcard: \"@utils/*\": [\"src/utils/*\"]
|
|
186
|
+
|
|
187
|
+
More complex patterns (multiple '*' or wildcards in the middle) are ignored
|
|
188
|
+
for now to keep behaviour predictable.
|
|
189
|
+
"""
|
|
190
|
+
if not paths:
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
candidates: List[Path] = []
|
|
194
|
+
|
|
195
|
+
for pattern, target_patterns in paths.items():
|
|
196
|
+
if "*" in pattern:
|
|
197
|
+
# Only support a single trailing '*' pattern like '@alias/*'
|
|
198
|
+
star_index = pattern.find("*")
|
|
199
|
+
prefix = pattern[:star_index]
|
|
200
|
+
suffix = pattern[star_index + 1 :]
|
|
201
|
+
|
|
202
|
+
if not import_path.startswith(prefix) or not import_path.endswith(suffix):
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
wildcard_value = import_path[len(prefix) : len(import_path) - len(suffix)]
|
|
206
|
+
|
|
207
|
+
for target_pattern in target_patterns:
|
|
208
|
+
if "*" not in target_pattern:
|
|
209
|
+
# Simple case: just append the wildcard value if present.
|
|
210
|
+
target = target_pattern
|
|
211
|
+
if wildcard_value:
|
|
212
|
+
# Avoid duplicate slashes
|
|
213
|
+
if not target.endswith("/") and not wildcard_value.startswith("/"):
|
|
214
|
+
target = f"{target}/{wildcard_value}"
|
|
215
|
+
else:
|
|
216
|
+
target = f"{target}{wildcard_value}"
|
|
217
|
+
else:
|
|
218
|
+
# Replace the first '*' with the wildcard value.
|
|
219
|
+
target_star_index = target_pattern.find("*")
|
|
220
|
+
target_prefix = target_pattern[:target_star_index]
|
|
221
|
+
target_suffix = target_pattern[target_star_index + 1 :]
|
|
222
|
+
target = f"{target_prefix}{wildcard_value}{target_suffix}"
|
|
223
|
+
|
|
224
|
+
candidates.append((base_dir / target).resolve())
|
|
225
|
+
else:
|
|
226
|
+
# Exact match alias, e.g. '@core': ['src/core/index.ts']
|
|
227
|
+
if import_path != pattern:
|
|
228
|
+
continue
|
|
229
|
+
for target_pattern in target_patterns:
|
|
230
|
+
candidates.append((base_dir / target_pattern).resolve())
|
|
231
|
+
|
|
232
|
+
return candidates
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _resolve_internal_file(spec_path: Path, file_to_id: Dict[Path, str]) -> Optional[Path]:
|
|
236
|
+
"""
|
|
237
|
+
Given a spec_path that may or may not include an extension, try to resolve it
|
|
238
|
+
to one of the known files in file_to_id using common TS/TSX conventions.
|
|
239
|
+
"""
|
|
240
|
+
resolved = spec_path.resolve()
|
|
241
|
+
if resolved in file_to_id:
|
|
242
|
+
return resolved
|
|
243
|
+
|
|
244
|
+
# If the path has an extension that is clearly a non-code asset (CSS,
|
|
245
|
+
# images, JSON etc.), do not try to map it onto a TS/TSX module.
|
|
246
|
+
# This prevents things like `import "./index.css"` from incorrectly
|
|
247
|
+
# resolving to `index.tsx`.
|
|
248
|
+
code_exts = {".js", ".jsx", ".ts", ".tsx", ".d.ts"}
|
|
249
|
+
asset_exts = {
|
|
250
|
+
".css",
|
|
251
|
+
".scss",
|
|
252
|
+
".sass",
|
|
253
|
+
".less",
|
|
254
|
+
".styl",
|
|
255
|
+
".json",
|
|
256
|
+
".png",
|
|
257
|
+
".jpg",
|
|
258
|
+
".jpeg",
|
|
259
|
+
".gif",
|
|
260
|
+
".svg",
|
|
261
|
+
".ico",
|
|
262
|
+
".webp",
|
|
263
|
+
".bmp",
|
|
264
|
+
".avif",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
suffix = resolved.suffix
|
|
268
|
+
|
|
269
|
+
if suffix and suffix not in code_exts and suffix in asset_exts:
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
# Try common TypeScript/TSX extensions based on the stem first, which
|
|
273
|
+
# covers imports like "./foo" or "./foo.js" -> "./foo.ts(x)".
|
|
274
|
+
stem = resolved.with_suffix("")
|
|
275
|
+
for ext in (".ts", ".tsx", ".d.ts"):
|
|
276
|
+
candidate = stem.with_suffix(ext)
|
|
277
|
+
if candidate in file_to_id:
|
|
278
|
+
return candidate
|
|
279
|
+
|
|
280
|
+
# Special case: imports that include an extra "qualifier" segment in the
|
|
281
|
+
# filename such as "../data/docs.service". In many TS codebases this
|
|
282
|
+
# resolves to "docs.service.ts" or "docs.service.tsx". Tree-sitter gives
|
|
283
|
+
# us the literal specifier "docs.service" which Path.suffix reports as
|
|
284
|
+
# ".service", so the simple stem-based logic above would look for
|
|
285
|
+
# "docs.ts" instead of "docs.service.ts".
|
|
286
|
+
#
|
|
287
|
+
# For any non-asset suffix that isn't a recognised JS/TS extension,
|
|
288
|
+
# also try appending TS/TSX extensions to the *full* filename.
|
|
289
|
+
if suffix and suffix not in code_exts and suffix not in asset_exts:
|
|
290
|
+
base_name = resolved.name # e.g. "docs.service"
|
|
291
|
+
for ext in (".ts", ".tsx", ".d.ts"):
|
|
292
|
+
candidate = (resolved.parent / f"{base_name}{ext}").resolve()
|
|
293
|
+
if candidate in file_to_id:
|
|
294
|
+
return candidate
|
|
295
|
+
|
|
296
|
+
# Try index files in the target directory
|
|
297
|
+
for index_name in ("index.ts", "index.tsx"):
|
|
298
|
+
candidate = (resolved / index_name).resolve()
|
|
299
|
+
if candidate in file_to_id:
|
|
300
|
+
return candidate
|
|
301
|
+
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
@router.get("", response_model=Node)
|
|
305
|
+
async def get_analysis(path: str = None):
|
|
306
|
+
"""
|
|
307
|
+
Get the static analysis of the codebase.
|
|
308
|
+
Returns cached result if available, otherwise triggers a scan.
|
|
309
|
+
"""
|
|
310
|
+
target_path = Path(path) if path else ROOT_PATH
|
|
311
|
+
|
|
312
|
+
if not target_path.exists():
|
|
313
|
+
# Fallback or error? Let's error if explicit path is invalid
|
|
314
|
+
if path:
|
|
315
|
+
raise HTTPException(status_code=404, detail="Path not found")
|
|
316
|
+
target_path = ROOT_PATH
|
|
317
|
+
|
|
318
|
+
cached_tree = cache.load_analysis(target_path)
|
|
319
|
+
if cached_tree:
|
|
320
|
+
return cached_tree
|
|
321
|
+
|
|
322
|
+
# If no cache, run scan synchronously (for now, could be async/background)
|
|
323
|
+
tree = analysis.scan_codebase(target_path)
|
|
324
|
+
cache.save_analysis(target_path, tree)
|
|
325
|
+
return tree
|
|
326
|
+
|
|
327
|
+
@router.get("/dependencies", response_model=DependencyGraph)
|
|
328
|
+
async def get_dependencies(path: str = None):
|
|
329
|
+
"""
|
|
330
|
+
Build a dependency graph for the specified path.
|
|
331
|
+
"""
|
|
332
|
+
target_path = Path(path) if path else ROOT_PATH
|
|
333
|
+
if not target_path.exists():
|
|
334
|
+
raise HTTPException(status_code=404, detail="Path not found")
|
|
335
|
+
|
|
336
|
+
analyzer = tree_sitter_analysis.TreeSitterAnalyzer()
|
|
337
|
+
nodes = []
|
|
338
|
+
edges = []
|
|
339
|
+
|
|
340
|
+
# Map file path to node ID
|
|
341
|
+
file_to_id = {}
|
|
342
|
+
# Map node ID to file path
|
|
343
|
+
id_to_file = {}
|
|
344
|
+
|
|
345
|
+
# 1. Scan files and create nodes
|
|
346
|
+
# We only care about TS/TSX files for now
|
|
347
|
+
files_to_process = []
|
|
348
|
+
if target_path.is_file():
|
|
349
|
+
if target_path.suffix in {'.ts', '.tsx'}:
|
|
350
|
+
files_to_process.append(target_path)
|
|
351
|
+
else:
|
|
352
|
+
for root, dirs, files in os.walk(target_path):
|
|
353
|
+
# Filter ignored dirs
|
|
354
|
+
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not d.startswith(".")]
|
|
355
|
+
for file in files:
|
|
356
|
+
if file.endswith(('.ts', '.tsx')):
|
|
357
|
+
files_to_process.append(Path(root) / file)
|
|
358
|
+
|
|
359
|
+
for file_path in files_to_process:
|
|
360
|
+
file_path = file_path.resolve()
|
|
361
|
+
node_id = str(file_path) # Use absolute path as ID for simplicity
|
|
362
|
+
|
|
363
|
+
# If the file is an index.* file or uses a dynamic route-style name
|
|
364
|
+
# like [id].tsx, include its parent folder name to make the node
|
|
365
|
+
# label more informative (e.g. "components/index.tsx" or
|
|
366
|
+
# "posts/[id].tsx").
|
|
367
|
+
file_name = file_path.name
|
|
368
|
+
is_index = file_path.stem == "index"
|
|
369
|
+
has_brackets = ("[" in file_name) and ("]" in file_name)
|
|
370
|
+
|
|
371
|
+
if (is_index or has_brackets) and file_path.parent is not None:
|
|
372
|
+
label = f"{file_path.parent.name}/{file_name}"
|
|
373
|
+
else:
|
|
374
|
+
label = file_name
|
|
375
|
+
|
|
376
|
+
nodes.append(DependencyNode(id=node_id, label=label, type="file"))
|
|
377
|
+
file_to_id[file_path] = node_id
|
|
378
|
+
id_to_file[node_id] = file_path
|
|
379
|
+
|
|
380
|
+
# 2. Find candidate tsconfig files and load the first one (nearest first).
|
|
381
|
+
tsconfig_candidates = _find_candidate_tsconfig_files(target_path)
|
|
382
|
+
ts_base_dir: Optional[Path] = None
|
|
383
|
+
ts_paths: Dict[str, List[str]] = {}
|
|
384
|
+
if tsconfig_candidates:
|
|
385
|
+
ts_base_dir, ts_paths = _load_tsconfig_paths(tsconfig_candidates[0])
|
|
386
|
+
|
|
387
|
+
# 3. Extract imports and create edges
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# Refactored 2-pass approach
|
|
391
|
+
|
|
392
|
+
# Pass 1: Analyze all files to get exports and build nodes
|
|
393
|
+
file_exports = {} # file_id -> { export_name: export_node_id }
|
|
394
|
+
|
|
395
|
+
# We need to store imports for pass 2
|
|
396
|
+
file_imports = {} # file_id -> list of imports
|
|
397
|
+
|
|
398
|
+
for file_path in files_to_process:
|
|
399
|
+
file_path = file_path.resolve()
|
|
400
|
+
file_id = file_to_id[file_path]
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
imports, exports = analyzer.extract_imports_exports(str(file_path))
|
|
404
|
+
file_imports[file_id] = imports
|
|
405
|
+
|
|
406
|
+
current_file_exports = {}
|
|
407
|
+
for exp in exports:
|
|
408
|
+
exp_name = exp["name"]
|
|
409
|
+
exp_id = f"{file_id}::{exp_name}"
|
|
410
|
+
|
|
411
|
+
nodes.append(DependencyNode(
|
|
412
|
+
id=exp_id,
|
|
413
|
+
label=exp_name,
|
|
414
|
+
type="export",
|
|
415
|
+
parent=file_id
|
|
416
|
+
))
|
|
417
|
+
current_file_exports[exp_name] = exp_id
|
|
418
|
+
|
|
419
|
+
file_exports[file_id] = current_file_exports
|
|
420
|
+
|
|
421
|
+
except Exception as e:
|
|
422
|
+
print(f"Error analyzing {file_path}: {e}")
|
|
423
|
+
continue
|
|
424
|
+
|
|
425
|
+
# Pass 2: Build edges
|
|
426
|
+
for file_path in files_to_process:
|
|
427
|
+
file_path = file_path.resolve()
|
|
428
|
+
source_id = file_to_id[file_path] # This is the IMPORTING file
|
|
429
|
+
|
|
430
|
+
imports = file_imports.get(source_id, [])
|
|
431
|
+
|
|
432
|
+
for imp in imports:
|
|
433
|
+
import_path = imp["source"]
|
|
434
|
+
symbols = imp["symbols"]
|
|
435
|
+
|
|
436
|
+
target_file: Optional[Path] = None
|
|
437
|
+
|
|
438
|
+
# Resolve target
|
|
439
|
+
if import_path.startswith("."):
|
|
440
|
+
resolved = file_path.parent / import_path
|
|
441
|
+
target_file = _resolve_internal_file(resolved, file_to_id)
|
|
442
|
+
else:
|
|
443
|
+
if ts_base_dir is not None and ts_paths:
|
|
444
|
+
alias_candidates = _apply_tsconfig_paths(import_path, ts_base_dir, ts_paths)
|
|
445
|
+
for candidate in alias_candidates:
|
|
446
|
+
target_file = _resolve_internal_file(candidate, file_to_id)
|
|
447
|
+
if target_file is not None:
|
|
448
|
+
break
|
|
449
|
+
|
|
450
|
+
if target_file:
|
|
451
|
+
target_id = file_to_id[target_file] # This is the EXPORTING file
|
|
452
|
+
|
|
453
|
+
# If we have symbols, try to link from specific export nodes
|
|
454
|
+
linked_symbols = False
|
|
455
|
+
if symbols:
|
|
456
|
+
target_exports = file_exports.get(target_id, {})
|
|
457
|
+
for sym in symbols:
|
|
458
|
+
# Handle default import
|
|
459
|
+
if sym == "default":
|
|
460
|
+
# Look for a default export
|
|
461
|
+
if "default" in target_exports:
|
|
462
|
+
export_node_id = target_exports["default"]
|
|
463
|
+
edges.append(DependencyEdge(
|
|
464
|
+
id=f"{export_node_id}-{source_id}",
|
|
465
|
+
source=export_node_id,
|
|
466
|
+
target=source_id,
|
|
467
|
+
label="default"
|
|
468
|
+
))
|
|
469
|
+
linked_symbols = True
|
|
470
|
+
elif sym in target_exports:
|
|
471
|
+
export_node_id = target_exports[sym]
|
|
472
|
+
edges.append(DependencyEdge(
|
|
473
|
+
id=f"{export_node_id}-{source_id}",
|
|
474
|
+
source=export_node_id,
|
|
475
|
+
target=source_id,
|
|
476
|
+
label=None
|
|
477
|
+
))
|
|
478
|
+
linked_symbols = True
|
|
479
|
+
|
|
480
|
+
# Fallback: if we didn't extract any symbols for this import, connect all
|
|
481
|
+
# exports from the target file to the importing file.
|
|
482
|
+
#
|
|
483
|
+
# IMPORTANT: we deliberately do *not* fall back when we have a non-empty
|
|
484
|
+
# symbol list but failed to match some of them. In that case we prefer
|
|
485
|
+
# to show only the edges for the symbols we could confidently resolve,
|
|
486
|
+
# instead of pretending that every export from the target is used.
|
|
487
|
+
if not symbols and target_id in file_exports:
|
|
488
|
+
for export_name, export_node_id in file_exports[target_id].items():
|
|
489
|
+
edges.append(
|
|
490
|
+
DependencyEdge(
|
|
491
|
+
id=f"{export_node_id}-{source_id}",
|
|
492
|
+
source=export_node_id,
|
|
493
|
+
target=source_id,
|
|
494
|
+
label=None,
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# Always include a standard file-to-file edge so the client can show
|
|
499
|
+
# high-level dependencies even when export-level edges are hidden.
|
|
500
|
+
edges.append(DependencyEdge(
|
|
501
|
+
id=f"{source_id}-{target_id}", # Note: Standard direction A -> B (A depends on B)
|
|
502
|
+
# Wait, standard direction is Source (Importer) -> Target (Imported).
|
|
503
|
+
# My new edges are Export (Imported) -> Importer.
|
|
504
|
+
# This is reverse direction.
|
|
505
|
+
source=source_id,
|
|
506
|
+
target=target_id,
|
|
507
|
+
label=None
|
|
508
|
+
))
|
|
509
|
+
|
|
510
|
+
else:
|
|
511
|
+
# External
|
|
512
|
+
if not import_path.startswith("."):
|
|
513
|
+
ext_id = f"ext:{import_path}"
|
|
514
|
+
if ext_id not in id_to_file:
|
|
515
|
+
nodes.append(DependencyNode(id=ext_id, label=import_path, type="external"))
|
|
516
|
+
id_to_file[ext_id] = "external"
|
|
517
|
+
|
|
518
|
+
edges.append(DependencyEdge(
|
|
519
|
+
id=f"{source_id}-{ext_id}",
|
|
520
|
+
source=source_id,
|
|
521
|
+
target=ext_id,
|
|
522
|
+
label=None
|
|
523
|
+
))
|
|
524
|
+
|
|
525
|
+
return DependencyGraph(nodes=nodes, edges=edges)
|
|
526
|
+
|
|
527
|
+
@router.post("/refresh", response_model=Node)
|
|
528
|
+
async def refresh_analysis():
|
|
529
|
+
"""
|
|
530
|
+
Force a re-scan of the codebase.
|
|
531
|
+
"""
|
|
532
|
+
tree = analysis.scan_codebase(ROOT_PATH)
|
|
533
|
+
cache.save_analysis(ROOT_PATH, tree)
|
|
534
|
+
return tree
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _estimate_counts(root: Path) -> tuple[int, int]:
|
|
538
|
+
"""
|
|
539
|
+
Return an estimated (file_count, folder_count) for a given root directory.
|
|
540
|
+
|
|
541
|
+
This applies the same ignore rules used during a full analysis to keep the
|
|
542
|
+
estimate reasonably fast while still informative.
|
|
543
|
+
"""
|
|
544
|
+
file_count = 0
|
|
545
|
+
folder_count = 0
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
for _, dirnames, filenames in os.walk(root):
|
|
549
|
+
# Apply ignore rules for directories to keep the estimate reasonable
|
|
550
|
+
dirnames[:] = [
|
|
551
|
+
d for d in dirnames
|
|
552
|
+
if d not in IGNORE_DIRS and not d.startswith(".")
|
|
553
|
+
]
|
|
554
|
+
folder_count += len(dirnames)
|
|
555
|
+
file_count += len(filenames)
|
|
556
|
+
except Exception:
|
|
557
|
+
# If anything goes wrong, fall back to zeros but still report the path.
|
|
558
|
+
file_count = 0
|
|
559
|
+
folder_count = 0
|
|
560
|
+
|
|
561
|
+
return file_count, folder_count
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
@router.get("/context")
|
|
565
|
+
async def get_analysis_context():
|
|
566
|
+
"""
|
|
567
|
+
Return basic information about the current analysis root directory.
|
|
568
|
+
|
|
569
|
+
This is used by the client to offer one-click analysis options on first
|
|
570
|
+
load, for both the current working directory and the repository root.
|
|
571
|
+
"""
|
|
572
|
+
current_root = ROOT_PATH
|
|
573
|
+
repo_root = current_root.parent
|
|
574
|
+
|
|
575
|
+
current_file_count, current_folder_count = _estimate_counts(current_root)
|
|
576
|
+
|
|
577
|
+
repo_file_count = 0
|
|
578
|
+
repo_folder_count = 0
|
|
579
|
+
if repo_root.exists():
|
|
580
|
+
repo_file_count, repo_folder_count = _estimate_counts(repo_root)
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
"root_path": str(current_root),
|
|
584
|
+
"file_count": current_file_count,
|
|
585
|
+
"folder_count": current_folder_count,
|
|
586
|
+
"repo_root_path": str(repo_root),
|
|
587
|
+
"repo_file_count": repo_file_count,
|
|
588
|
+
"repo_folder_count": repo_folder_count,
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
@router.get("/data-flow")
|
|
592
|
+
async def get_data_flow(path: str):
|
|
593
|
+
"""
|
|
594
|
+
Analyze data flow for a specific file.
|
|
595
|
+
"""
|
|
596
|
+
target_path = Path(path)
|
|
597
|
+
if not target_path.exists() or not target_path.is_file():
|
|
598
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
599
|
+
|
|
600
|
+
from app.services.data_flow_analysis import DataFlowAnalyzer
|
|
601
|
+
analyzer = DataFlowAnalyzer()
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
graph = analyzer.analyze_file(str(target_path))
|
|
605
|
+
return graph
|
|
606
|
+
except Exception as e:
|
|
607
|
+
print(f"Error analyzing data flow for {path}: {e}")
|
|
608
|
+
raise HTTPException(status_code=500, detail=str(e))
|