pi-usereq 0.4.0
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.
- package/.g.conf +20 -0
- package/.github/workflows/release-npm.yml +131 -0
- package/.gitignore +270 -0
- package/CHANGELOG.md +221 -0
- package/LICENSE +674 -0
- package/README.md +260 -0
- package/TODO.md +20 -0
- package/docs/pi.dev/agent-document-manifest.json +6399 -0
- package/docs/pi.dev/coding-agent-docs/compaction.md +394 -0
- package/docs/pi.dev/coding-agent-docs/custom-provider.md +596 -0
- package/docs/pi.dev/coding-agent-docs/development.md +71 -0
- package/docs/pi.dev/coding-agent-docs/extensions.md +2262 -0
- package/docs/pi.dev/coding-agent-docs/images/doom-extension.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/exy.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/interactive-mode.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/tree-view.png +0 -0
- package/docs/pi.dev/coding-agent-docs/json.md +82 -0
- package/docs/pi.dev/coding-agent-docs/keybindings.md +175 -0
- package/docs/pi.dev/coding-agent-docs/models.md +392 -0
- package/docs/pi.dev/coding-agent-docs/packages.md +218 -0
- package/docs/pi.dev/coding-agent-docs/prompt-templates.md +67 -0
- package/docs/pi.dev/coding-agent-docs/providers.md +195 -0
- package/docs/pi.dev/coding-agent-docs/rpc.md +1377 -0
- package/docs/pi.dev/coding-agent-docs/sdk.md +1124 -0
- package/docs/pi.dev/coding-agent-docs/session.md +412 -0
- package/docs/pi.dev/coding-agent-docs/settings.md +247 -0
- package/docs/pi.dev/coding-agent-docs/shell-aliases.md +13 -0
- package/docs/pi.dev/coding-agent-docs/skills.md +232 -0
- package/docs/pi.dev/coding-agent-docs/terminal-setup.md +106 -0
- package/docs/pi.dev/coding-agent-docs/termux.md +127 -0
- package/docs/pi.dev/coding-agent-docs/themes.md +295 -0
- package/docs/pi.dev/coding-agent-docs/tmux.md +61 -0
- package/docs/pi.dev/coding-agent-docs/tree.md +231 -0
- package/docs/pi.dev/coding-agent-docs/tui.md +887 -0
- package/docs/pi.dev/coding-agent-docs/windows.md +17 -0
- package/docs/pi.dev/mom-docs/artifacts-server.md +475 -0
- package/docs/pi.dev/mom-docs/events.md +307 -0
- package/docs/pi.dev/mom-docs/new.md +970 -0
- package/docs/pi.dev/mom-docs/sandbox.md +153 -0
- package/docs/pi.dev/mom-docs/slack-bot-minimal-guide.md +399 -0
- package/docs/pi.dev/mom-docs/v86.md +319 -0
- package/docs/pi.dev/pods-docs/gml-4.5.md +189 -0
- package/docs/pi.dev/pods-docs/gpt-oss.md +233 -0
- package/docs/pi.dev/pods-docs/implementation-plan.md +183 -0
- package/docs/pi.dev/pods-docs/kimi-k2.md +197 -0
- package/docs/pi.dev/pods-docs/models.md +116 -0
- package/docs/pi.dev/pods-docs/plan.md +166 -0
- package/docs/pi.dev/pods-docs/qwen3-coder.md +132 -0
- package/images/flowchart-bw.png +0 -0
- package/images/flowchart-bw.svg +102 -0
- package/images/flowchart.md +100 -0
- package/images/flowchart.png +0 -0
- package/images/flowchart.svg +3 -0
- package/package.json +46 -0
- package/req/docs/REFERENCES.md +4554 -0
- package/req/docs/REQUIREMENTS.md +475 -0
- package/req/docs/WORKFLOW.md +1059 -0
- package/scripts/debug-extension.ts +497 -0
- package/scripts/lib/extension-debug-harness.ts +450 -0
- package/scripts/lib/recording-extension-api.ts +786 -0
- package/scripts/lib/sdk-smoke.ts +503 -0
- package/scripts/pi-usereq-debug.sh +330 -0
- package/scripts/tool-args-to-params.ts +208 -0
- package/src/cli.ts +349 -0
- package/src/core/agent-tool-json.ts +621 -0
- package/src/core/compress-files.ts +72 -0
- package/src/core/compress-payload.ts +648 -0
- package/src/core/compress.ts +464 -0
- package/src/core/config.ts +272 -0
- package/src/core/doxygen-parser.ts +318 -0
- package/src/core/errors.ts +31 -0
- package/src/core/extension-status.ts +660 -0
- package/src/core/find-constructs.ts +319 -0
- package/src/core/find-payload.ts +915 -0
- package/src/core/generate-markdown.ts +120 -0
- package/src/core/path-context.ts +196 -0
- package/src/core/pi-notify.ts +430 -0
- package/src/core/pi-usereq-tools.ts +140 -0
- package/src/core/prompts.ts +184 -0
- package/src/core/reference-payload.ts +818 -0
- package/src/core/resources.ts +63 -0
- package/src/core/runtime-project-paths.ts +99 -0
- package/src/core/settings-menu.ts +233 -0
- package/src/core/source-analyzer.ts +1721 -0
- package/src/core/static-check.ts +674 -0
- package/src/core/token-counter.ts +729 -0
- package/src/core/tool-runner.ts +717 -0
- package/src/core/utils.ts +185 -0
- package/src/index.ts +2209 -0
- package/src/resources/guidelines/Google_C++_Style_Guide.md +3711 -0
- package/src/resources/guidelines/Google_Python_Style_Guide.md +3709 -0
- package/src/resources/prompts/analyze.md +130 -0
- package/src/resources/prompts/change.md +227 -0
- package/src/resources/prompts/check.md +139 -0
- package/src/resources/prompts/cover.md +219 -0
- package/src/resources/prompts/create.md +104 -0
- package/src/resources/prompts/fix.md +221 -0
- package/src/resources/prompts/flowchart.md +220 -0
- package/src/resources/prompts/implement.md +163 -0
- package/src/resources/prompts/new.md +226 -0
- package/src/resources/prompts/readme.md +182 -0
- package/src/resources/prompts/recreate.md +213 -0
- package/src/resources/prompts/refactor.md +213 -0
- package/src/resources/prompts/references.md +100 -0
- package/src/resources/prompts/renumber.md +119 -0
- package/src/resources/prompts/workflow.md +202 -0
- package/src/resources/prompts/write.md +99 -0
- package/src/resources/sounds/Machine-alert-beep-sound-effect.mp3 +0 -0
- package/src/resources/sounds/Soft-high-tech-notification-sound-effect.mp3 +0 -0
- package/src/resources/templates/Document_Source_Code_in_Doxygen_Style.md +130 -0
- package/src/resources/templates/HDT_Test_Authoring_Guide.md +318 -0
- package/src/resources/templates/Requirements_Template.md +78 -0
- package/tests/attended-results-scenarios.ts +758 -0
- package/tests/attended-results.test.ts +39 -0
- package/tests/cli-command-option-parity.test.ts +815 -0
- package/tests/debug-extension-harness.test.ts +463 -0
- package/tests/extension-registration.test.ts +2006 -0
- package/tests/fixtures/fixture_c.c +361 -0
- package/tests/fixtures/fixture_cpp.cpp +407 -0
- package/tests/fixtures/fixture_csharp.cs +411 -0
- package/tests/fixtures/fixture_elixir.ex +409 -0
- package/tests/fixtures/fixture_go.go +341 -0
- package/tests/fixtures/fixture_haskell.hs +250 -0
- package/tests/fixtures/fixture_java.java +430 -0
- package/tests/fixtures/fixture_javascript.js +383 -0
- package/tests/fixtures/fixture_kotlin.kt +451 -0
- package/tests/fixtures/fixture_lua.lua +276 -0
- package/tests/fixtures/fixture_perl.pl +310 -0
- package/tests/fixtures/fixture_php.php +433 -0
- package/tests/fixtures/fixture_python.py +502 -0
- package/tests/fixtures/fixture_ruby.rb +345 -0
- package/tests/fixtures/fixture_rust.rs +380 -0
- package/tests/fixtures/fixture_scala.scala +398 -0
- package/tests/fixtures/fixture_shell.sh +276 -0
- package/tests/fixtures/fixture_swift.swift +397 -0
- package/tests/fixtures/fixture_typescript.ts +434 -0
- package/tests/fixtures/fixture_zig.zig +295 -0
- package/tests/fixtures_attended_results/project/compress-line-numbers.json +5 -0
- package/tests/fixtures_attended_results/project/compress.json +5 -0
- package/tests/fixtures_attended_results/project/enable-static-check-invalid-command.json +5 -0
- package/tests/fixtures_attended_results/project/enable-static-check-valid.json +5 -0
- package/tests/fixtures_attended_results/project/files-static-check.json +5 -0
- package/tests/fixtures_attended_results/project/find-line-numbers.json +5 -0
- package/tests/fixtures_attended_results/project/find.json +5 -0
- package/tests/fixtures_attended_results/project/get-base-path.json +5 -0
- package/tests/fixtures_attended_results/project/git-check-clean.json +5 -0
- package/tests/fixtures_attended_results/project/git-check-dirty.json +5 -0
- package/tests/fixtures_attended_results/project/git-path.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-create-invalid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-create-valid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-delete-nonexistent.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-delete-valid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-name.json +5 -0
- package/tests/fixtures_attended_results/project/references.json +5 -0
- package/tests/fixtures_attended_results/project/static-check.json +5 -0
- package/tests/fixtures_attended_results/project/tokens.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +5 -0
- package/tests/helpers.ts +204 -0
- package/tests/oracle-project.test.ts +63 -0
- package/tests/oracle-standalone.test.ts +48 -0
- package/tests/prompt-rendering.test.ts +66 -0
- package/tests/release-workflow.test.ts +133 -0
|
@@ -0,0 +1,1721 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Analyzes source files into language-agnostic structural elements and markdown references.
|
|
4
|
+
* @details Defines the language-spec registry, source-element model, structural analyzer, Doxygen association logic, and markdown rendering helpers used by compression, reference generation, and construct search tools. Runtime is generally linear in source size plus language-pattern count. Side effects are limited to filesystem reads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { formatDoxygenFieldsAsMarkdown, parseDoxygenComment } from "./doxygen-parser.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @brief Enumerates the normalized source-element kinds emitted by the analyzer.
|
|
14
|
+
* @details The enum lets language-specific regex matches collapse into a shared symbol taxonomy for downstream markdown generation and construct filtering. Access complexity is O(1).
|
|
15
|
+
*/
|
|
16
|
+
export enum ElementType {
|
|
17
|
+
FUNCTION = "FUNCTION",
|
|
18
|
+
METHOD = "METHOD",
|
|
19
|
+
CLASS = "CLASS",
|
|
20
|
+
STRUCT = "STRUCT",
|
|
21
|
+
ENUM = "ENUM",
|
|
22
|
+
TRAIT = "TRAIT",
|
|
23
|
+
INTERFACE = "INTERFACE",
|
|
24
|
+
MODULE = "MODULE",
|
|
25
|
+
IMPL = "IMPL",
|
|
26
|
+
MACRO = "MACRO",
|
|
27
|
+
CONSTANT = "CONSTANT",
|
|
28
|
+
VARIABLE = "VARIABLE",
|
|
29
|
+
TYPE_ALIAS = "TYPE_ALIAS",
|
|
30
|
+
IMPORT = "IMPORT",
|
|
31
|
+
DECORATOR = "DECORATOR",
|
|
32
|
+
COMMENT_SINGLE = "COMMENT_SINGLE",
|
|
33
|
+
COMMENT_MULTI = "COMMENT_MULTI",
|
|
34
|
+
COMPONENT = "COMPONENT",
|
|
35
|
+
PROTOCOL = "PROTOCOL",
|
|
36
|
+
EXTENSION = "EXTENSION",
|
|
37
|
+
UNION = "UNION",
|
|
38
|
+
NAMESPACE = "NAMESPACE",
|
|
39
|
+
PROPERTY = "PROPERTY",
|
|
40
|
+
SIGNAL = "SIGNAL",
|
|
41
|
+
TYPEDEF = "TYPEDEF",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @brief Represents one analyzed source element or comment block.
|
|
46
|
+
* @details Stores location metadata, extracted source text, normalized naming/signature data, hierarchy information, attached Doxygen fields, and body annotations used by downstream renderers. Instance initialization is O(1) aside from object assignment.
|
|
47
|
+
*/
|
|
48
|
+
export class SourceElement {
|
|
49
|
+
elementType: ElementType;
|
|
50
|
+
lineStart: number;
|
|
51
|
+
lineEnd: number;
|
|
52
|
+
extract: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
signature?: string;
|
|
55
|
+
visibility?: string;
|
|
56
|
+
parentName?: string;
|
|
57
|
+
inherits?: string;
|
|
58
|
+
depth = 0;
|
|
59
|
+
commentSource?: string;
|
|
60
|
+
bodyComments: Array<[number, number, string]> = [];
|
|
61
|
+
exitPoints: Array<[number, string]> = [];
|
|
62
|
+
doxygenFields: Record<string, string[]> = {};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @brief Initializes a source-element record.
|
|
66
|
+
* @details Copies caller-provided metadata, ensures required fields are assigned explicitly, and restores default arrays/maps for optional enrichment fields. Runtime is O(k) in the number of provided properties. Mutates instance fields only.
|
|
67
|
+
* @param[in] init {Partial<SourceElement> & Pick<SourceElement, "elementType" | "lineStart" | "lineEnd" | "extract">} Initial field set.
|
|
68
|
+
*/
|
|
69
|
+
constructor(init: Partial<SourceElement> & Pick<SourceElement, "elementType" | "lineStart" | "lineEnd" | "extract">) {
|
|
70
|
+
Object.assign(this, init);
|
|
71
|
+
this.elementType = init.elementType;
|
|
72
|
+
this.lineStart = init.lineStart;
|
|
73
|
+
this.lineEnd = init.lineEnd;
|
|
74
|
+
this.extract = init.extract;
|
|
75
|
+
this.depth = init.depth ?? 0;
|
|
76
|
+
this.bodyComments = init.bodyComments ?? [];
|
|
77
|
+
this.exitPoints = init.exitPoints ?? [];
|
|
78
|
+
this.doxygenFields = init.doxygenFields ?? {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @brief Returns the normalized public type label for the element.
|
|
83
|
+
* @details Collapses both single-line and multi-line comment variants into the shared `COMMENT` label while leaving all other element types unchanged. Runtime is O(1). No side effects occur.
|
|
84
|
+
* @return {string} Normalized type label.
|
|
85
|
+
*/
|
|
86
|
+
get typeLabel(): string {
|
|
87
|
+
switch (this.elementType) {
|
|
88
|
+
case ElementType.COMMENT_SINGLE:
|
|
89
|
+
case ElementType.COMMENT_MULTI:
|
|
90
|
+
return "COMMENT";
|
|
91
|
+
default:
|
|
92
|
+
return this.elementType;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @brief Describes language-specific parsing behavior for the source analyzer.
|
|
99
|
+
* @details Each spec defines comment syntax, string delimiters, and ordered regex patterns mapping source lines to `ElementType` values. The interface is compile-time only and introduces no runtime cost.
|
|
100
|
+
*/
|
|
101
|
+
export interface LanguageSpec {
|
|
102
|
+
name: string;
|
|
103
|
+
singleComment?: string;
|
|
104
|
+
multiCommentStart?: string;
|
|
105
|
+
multiCommentEnd?: string;
|
|
106
|
+
stringDelimiters: string[];
|
|
107
|
+
patterns: Array<[ElementType, RegExp]>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @brief Creates a regular expression from a raw pattern string.
|
|
112
|
+
* @details Wraps `new RegExp(...)` to keep the language-spec table compact and visually uniform. Runtime is O(1) relative to call-site complexity. No side effects occur.
|
|
113
|
+
* @param[in] pattern {string} Raw regular-expression pattern.
|
|
114
|
+
* @return {RegExp} Constructed regular expression.
|
|
115
|
+
*/
|
|
116
|
+
function re(pattern: string): RegExp {
|
|
117
|
+
return new RegExp(pattern);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @brief Builds the analyzer language-spec registry.
|
|
122
|
+
* @details Materializes comment syntax, string delimiters, and ordered construct-detection regexes for all supported languages and aliases. Runtime is O(l) in the number of language definitions. No side effects occur.
|
|
123
|
+
* @return {Record<string, LanguageSpec>} Language-spec map keyed by canonical names and aliases.
|
|
124
|
+
*/
|
|
125
|
+
export function buildLanguageSpecs(): Record<string, LanguageSpec> {
|
|
126
|
+
const specs: Record<string, LanguageSpec> = {};
|
|
127
|
+
|
|
128
|
+
specs.python = {
|
|
129
|
+
name: "Python",
|
|
130
|
+
singleComment: "#",
|
|
131
|
+
multiCommentStart: '"""',
|
|
132
|
+
multiCommentEnd: '"""',
|
|
133
|
+
stringDelimiters: ['"', "'", '"""', "'''"] ,
|
|
134
|
+
patterns: [
|
|
135
|
+
[ElementType.CLASS, re(String.raw`^(\s*class\s+(\w+)\s*[\(:])`)],
|
|
136
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:async\s+)?def\s+(\w+)\s*\()`)],
|
|
137
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*@((?:\w[\w.]*))\s*)`)],
|
|
138
|
+
[ElementType.IMPORT, re(String.raw`^(\s*(?:from\s+\S+\s+)?import\s+(.+))`)],
|
|
139
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*([A-Z][A-Z_0-9]+)\s*=\s*)`)],
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
specs.c = {
|
|
144
|
+
name: "C",
|
|
145
|
+
singleComment: "//",
|
|
146
|
+
multiCommentStart: "/*",
|
|
147
|
+
multiCommentEnd: "*/",
|
|
148
|
+
stringDelimiters: ['"', "'"],
|
|
149
|
+
patterns: [
|
|
150
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:typedef\s+)?struct\s+(\w+))`)],
|
|
151
|
+
[ElementType.UNION, re(String.raw`^(\s*(?:typedef\s+)?union\s+(\w+))`)],
|
|
152
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:typedef\s+)?enum\s+(\w+))`)],
|
|
153
|
+
[ElementType.TYPEDEF, re(String.raw`^(\s*typedef\s+.+?\s+(\w+)\s*;)`)],
|
|
154
|
+
[ElementType.MACRO, re(String.raw`^(\s*#\s*define\s+(\w+))`)],
|
|
155
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:static\s+|inline\s+|extern\s+|const\s+)*(?:(?:unsigned|signed|long|short|volatile|register)\s+)*(?:void|int|char|float|double|long|short|unsigned|signed|size_t|ssize_t|uint\d+_t|int\d+_t|bool|_Bool|FILE|\w+_t|\w+)\s+(?:\*+\s*)?(\w+)\s*\()`)],
|
|
156
|
+
[ElementType.IMPORT, re(String.raw`^(\s*#\s*include\s+(.+))`)],
|
|
157
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*(?:static\s+|extern\s+|const\s+)*(?:const\s+)?(?:char|int|float|double|void|long|short|unsigned|signed|size_t|bool|_Bool|\w+_t)\s*\**\s+(\w+)\s*(?:=|;|\[))`)],
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
specs.cpp = {
|
|
162
|
+
name: "C++",
|
|
163
|
+
singleComment: "//",
|
|
164
|
+
multiCommentStart: "/*",
|
|
165
|
+
multiCommentEnd: "*/",
|
|
166
|
+
stringDelimiters: ['"', "'"],
|
|
167
|
+
patterns: [
|
|
168
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:template\s*<[^>]*>\s*)?class\s+(\w+))`)],
|
|
169
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:template\s*<[^>]*>\s*)?struct\s+(\w+))`)],
|
|
170
|
+
[ElementType.ENUM, re(String.raw`^(\s*enum\s+(?:class\s+)?(\w+))`)],
|
|
171
|
+
[ElementType.NAMESPACE, re(String.raw`^(\s*namespace\s+(\w+))`)],
|
|
172
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:static\s+|inline\s+|virtual\s+|explicit\s+|constexpr\s+|consteval\s+|constinit\s+|extern\s+|const\s+)*(?:auto|void|int|char|float|double|long|short|unsigned|signed|bool|string|wstring|size_t|\w+(?:::\w+)*)\s*[&*]*\s*(\w+(?:::\w+)*)\s*\()`)],
|
|
173
|
+
[ElementType.MACRO, re(String.raw`^(\s*#\s*define\s+(\w+))`)],
|
|
174
|
+
[ElementType.IMPORT, re(String.raw`^(\s*#\s*include\s+(.+))`)],
|
|
175
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*(?:using|typedef)\s+(\w+))`)],
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
specs.rust = {
|
|
180
|
+
name: "Rust",
|
|
181
|
+
singleComment: "//",
|
|
182
|
+
multiCommentStart: "/*",
|
|
183
|
+
multiCommentEnd: "*/",
|
|
184
|
+
stringDelimiters: ['"', "'"],
|
|
185
|
+
patterns: [
|
|
186
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:extern\s+"C"\s+)?fn\s+(\w+))`)],
|
|
187
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?struct\s+(\w+))`)],
|
|
188
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?enum\s+(\w+))`)],
|
|
189
|
+
[ElementType.TRAIT, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?(?:unsafe\s+)?trait\s+(\w+))`)],
|
|
190
|
+
[ElementType.IMPL, re(String.raw`^(\s*impl(?:<[^>]*>)?\s+(?:(\w+(?:<[^>]*>)?)\s+for\s+)?(\w+))`)],
|
|
191
|
+
[ElementType.MODULE, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?mod\s+(\w+))`)],
|
|
192
|
+
[ElementType.MACRO, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?macro_rules!\s+(\w+))`)],
|
|
193
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?(?:const|static)\s+(\w+))`)],
|
|
194
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*(?:pub(?:\(\w+\))?\s+)?type\s+(\w+))`)],
|
|
195
|
+
[ElementType.IMPORT, re(String.raw`^(\s*use\s+(.+?);)`)],
|
|
196
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*#\[(\w[^\]]*)\])`)],
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
specs.javascript = {
|
|
201
|
+
name: "JavaScript",
|
|
202
|
+
singleComment: "//",
|
|
203
|
+
multiCommentStart: "/*",
|
|
204
|
+
multiCommentEnd: "*/",
|
|
205
|
+
stringDelimiters: ['"', "'", "`"],
|
|
206
|
+
patterns: [
|
|
207
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?class\s+(\w+))`)],
|
|
208
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+(\w+)\s*\()`)],
|
|
209
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>|[a-zA-Z_]\w*\s*=>))`)],
|
|
210
|
+
[ElementType.COMPONENT, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:React\.)?(?:memo|forwardRef|lazy)\s*\()`)],
|
|
211
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:export\s+)?const\s+([A-Z][A-Z_0-9]+)\s*=)`)],
|
|
212
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(.+))`)],
|
|
213
|
+
[ElementType.MODULE, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+(\w+)\s*=\s*require\s*\()`)],
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
specs.typescript = {
|
|
218
|
+
name: "TypeScript",
|
|
219
|
+
singleComment: "//",
|
|
220
|
+
multiCommentStart: "/*",
|
|
221
|
+
multiCommentEnd: "*/",
|
|
222
|
+
stringDelimiters: ['"', "'", "`"],
|
|
223
|
+
patterns: [
|
|
224
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*(?:export\s+)?interface\s+(\w+))`)],
|
|
225
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*(?:export\s+)?type\s+(\w+)\s*(?:<[^>]*>)?\s*=)`)],
|
|
226
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:export\s+)?(?:const\s+)?enum\s+(\w+))`)],
|
|
227
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+))`)],
|
|
228
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+(\w+)\s*)`)],
|
|
229
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:function|\([^)]*\)\s*(?::\s*[^=]+)?\s*=>|[a-zA-Z_]\w*\s*=>))`)],
|
|
230
|
+
[ElementType.NAMESPACE, re(String.raw`^(\s*(?:export\s+)?(?:declare\s+)?namespace\s+(\w+))`)],
|
|
231
|
+
[ElementType.MODULE, re(String.raw`^(\s*(?:export\s+)?(?:declare\s+)?module\s+(\w+))`)],
|
|
232
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(.+))`)],
|
|
233
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*@((?:\w[\w.]*))\s*)`)],
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
specs.java = {
|
|
238
|
+
name: "Java",
|
|
239
|
+
singleComment: "//",
|
|
240
|
+
multiCommentStart: "/*",
|
|
241
|
+
multiCommentEnd: "*/",
|
|
242
|
+
stringDelimiters: ['"', "'"],
|
|
243
|
+
patterns: [
|
|
244
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+))`)],
|
|
245
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?interface\s+(\w+))`)],
|
|
246
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?enum\s+(\w+))`)],
|
|
247
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:native\s+)?(?:abstract\s+)?(?:<[^>]+>\s+)?(?:void|int|char|float|double|long|short|byte|boolean|String|Object|List|Map|Set|Optional|\w+(?:<[^>]*>)?)\s*(?:\[\])?\s+(\w+)\s*\()`)],
|
|
248
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(?:static\s+)?(.+?);)`)],
|
|
249
|
+
[ElementType.MODULE, re(String.raw`^(\s*package\s+(.+?);)`)],
|
|
250
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*@((?:\w[\w.]*(?:\([^)]*\))?)))`)],
|
|
251
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?static\s+final\s+\w+\s+([A-Z_]\w*)\s*=)`)],
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
specs.go = {
|
|
256
|
+
name: "Go",
|
|
257
|
+
singleComment: "//",
|
|
258
|
+
multiCommentStart: "/*",
|
|
259
|
+
multiCommentEnd: "*/",
|
|
260
|
+
stringDelimiters: ['"', "`"],
|
|
261
|
+
patterns: [
|
|
262
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*func\s+(\w+)\s*\()`)],
|
|
263
|
+
[ElementType.METHOD, re(String.raw`^(\s*func\s+\(\s*\w+\s+\*?\w+\s*\)\s+(\w+)\s*\()`)],
|
|
264
|
+
[ElementType.STRUCT, re(String.raw`^(\s*type\s+(\w+)\s+struct\b)`)],
|
|
265
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*type\s+(\w+)\s+interface\b)`)],
|
|
266
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*type\s+(\w+)\s+(?!struct|interface)\w)`)],
|
|
267
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:const|var)\s+(\w+))`)],
|
|
268
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(.+))`)],
|
|
269
|
+
[ElementType.MODULE, re(String.raw`^(\s*package\s+(\w+))`)],
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
specs.ruby = {
|
|
274
|
+
name: "Ruby",
|
|
275
|
+
singleComment: "#",
|
|
276
|
+
multiCommentStart: "=begin",
|
|
277
|
+
multiCommentEnd: "=end",
|
|
278
|
+
stringDelimiters: ['"', "'"],
|
|
279
|
+
patterns: [
|
|
280
|
+
[ElementType.CLASS, re(String.raw`^(\s*class\s+(\w+))`)],
|
|
281
|
+
[ElementType.MODULE, re(String.raw`^(\s*module\s+(\w+))`)],
|
|
282
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*def\s+(?:self\.)?(\w+[?!=]?))`)],
|
|
283
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*([A-Z][A-Z_0-9]+)\s*=)`)],
|
|
284
|
+
[ElementType.IMPORT, re(String.raw`^(\s*require(?:_relative)?\s+(.+))`)],
|
|
285
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*attr_(?:reader|writer|accessor)\s+(.+))`)],
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
specs.php = {
|
|
290
|
+
name: "PHP",
|
|
291
|
+
singleComment: "//",
|
|
292
|
+
multiCommentStart: "/*",
|
|
293
|
+
multiCommentEnd: "*/",
|
|
294
|
+
stringDelimiters: ['"', "'"],
|
|
295
|
+
patterns: [
|
|
296
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:abstract\s+|final\s+)?class\s+(\w+))`)],
|
|
297
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*interface\s+(\w+))`)],
|
|
298
|
+
[ElementType.TRAIT, re(String.raw`^(\s*trait\s+(\w+))`)],
|
|
299
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?function\s+(\w+)\s*\()`)],
|
|
300
|
+
[ElementType.NAMESPACE, re(String.raw`^(\s*namespace\s+(.+?);)`)],
|
|
301
|
+
[ElementType.IMPORT, re(String.raw`^(\s*(?:use|require|require_once|include|include_once)\s+(.+?);)`)],
|
|
302
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:const|define)\s*\(?\s*['"]?(\w+))`)],
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
specs.swift = {
|
|
307
|
+
name: "Swift",
|
|
308
|
+
singleComment: "//",
|
|
309
|
+
multiCommentStart: "/*",
|
|
310
|
+
multiCommentEnd: "*/",
|
|
311
|
+
stringDelimiters: ['"', "'"],
|
|
312
|
+
patterns: [
|
|
313
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+|open\s+|fileprivate\s+)?(?:final\s+)?class\s+(\w+))`)],
|
|
314
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+)?struct\s+(\w+))`)],
|
|
315
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+)?enum\s+(\w+))`)],
|
|
316
|
+
[ElementType.PROTOCOL, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+)?protocol\s+(\w+))`)],
|
|
317
|
+
[ElementType.EXTENSION, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+)?extension\s+(\w+))`)],
|
|
318
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:public\s+|private\s+|internal\s+|open\s+)?(?:static\s+|class\s+)?(?:override\s+)?func\s+(\w+))`)],
|
|
319
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(\w+))`)],
|
|
320
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:public\s+|private\s+)?(?:static\s+)?let\s+(\w+)\s*(?::|\s*=))`)],
|
|
321
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*(?:public\s+|private\s+)?(?:static\s+)?var\s+(\w+)\s*(?::|\s*=))`)],
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
specs.kotlin = {
|
|
326
|
+
name: "Kotlin",
|
|
327
|
+
singleComment: "//",
|
|
328
|
+
multiCommentStart: "/*",
|
|
329
|
+
multiCommentEnd: "*/",
|
|
330
|
+
stringDelimiters: ['"', "'"],
|
|
331
|
+
patterns: [
|
|
332
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:open\s+|abstract\s+|sealed\s+|data\s+|inner\s+)*class\s+(\w+))`)],
|
|
333
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*interface\s+(\w+))`)],
|
|
334
|
+
[ElementType.ENUM, re(String.raw`^(\s*enum\s+class\s+(\w+))`)],
|
|
335
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:open\s+|override\s+)?(?:suspend\s+)?fun\s+(?:<[^>]+>\s+)?(\w+)\s*\()`)],
|
|
336
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:const\s+)?val\s+(\w+))`)],
|
|
337
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*var\s+(\w+))`)],
|
|
338
|
+
[ElementType.MODULE, re(String.raw`^(\s*(?:object|companion\s+object)\s+(\w*))`)],
|
|
339
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(.+))`)],
|
|
340
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*@((?:\w[\w.]*))\s*)`)],
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
specs.scala = {
|
|
345
|
+
name: "Scala",
|
|
346
|
+
singleComment: "//",
|
|
347
|
+
multiCommentStart: "/*",
|
|
348
|
+
multiCommentEnd: "*/",
|
|
349
|
+
stringDelimiters: ['"', "'"],
|
|
350
|
+
patterns: [
|
|
351
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:abstract\s+|sealed\s+|case\s+)?class\s+(\w+))`)],
|
|
352
|
+
[ElementType.TRAIT, re(String.raw`^(\s*trait\s+(\w+))`)],
|
|
353
|
+
[ElementType.MODULE, re(String.raw`^(\s*object\s+(\w+))`)],
|
|
354
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:override\s+)?def\s+(\w+))`)],
|
|
355
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*val\s+(\w+))`)],
|
|
356
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*var\s+(\w+))`)],
|
|
357
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*type\s+(\w+))`)],
|
|
358
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(.+))`)],
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
specs.lua = {
|
|
363
|
+
name: "Lua",
|
|
364
|
+
singleComment: "--",
|
|
365
|
+
multiCommentStart: "--[[",
|
|
366
|
+
multiCommentEnd: "]]",
|
|
367
|
+
stringDelimiters: ['"', "'"],
|
|
368
|
+
patterns: [
|
|
369
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:local\s+)?function\s+(\w[\w.:]*))\s*\(`)],
|
|
370
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:local\s+)?(\w[\w.]*)\s*=\s*function\s*\()`)],
|
|
371
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*local\s+(\w+)\s*=)`)],
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
specs.shell = {
|
|
376
|
+
name: "Shell",
|
|
377
|
+
singleComment: "#",
|
|
378
|
+
stringDelimiters: ['"', "'"],
|
|
379
|
+
patterns: [
|
|
380
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:function\s+)?(\w+)\s*\(\s*\))`)],
|
|
381
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*(?:export\s+|readonly\s+|declare\s+(?:-\w+\s+)*)?([A-Z_][A-Z_0-9]*)\s*=)`)],
|
|
382
|
+
[ElementType.IMPORT, re(String.raw`^(\s*(?:source|\\.)\s+(.+))`)],
|
|
383
|
+
],
|
|
384
|
+
};
|
|
385
|
+
specs.bash = specs.shell;
|
|
386
|
+
specs.sh = specs.shell;
|
|
387
|
+
specs.zsh = specs.shell;
|
|
388
|
+
|
|
389
|
+
specs.perl = {
|
|
390
|
+
name: "Perl",
|
|
391
|
+
singleComment: "#",
|
|
392
|
+
multiCommentStart: "=pod",
|
|
393
|
+
multiCommentEnd: "=cut",
|
|
394
|
+
stringDelimiters: ['"', "'"],
|
|
395
|
+
patterns: [
|
|
396
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*sub\s+(\w+))`)],
|
|
397
|
+
[ElementType.MODULE, re(String.raw`^(\s*package\s+(\w[\w:]*))`)],
|
|
398
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:use\s+constant\s+(\w+)))`)],
|
|
399
|
+
[ElementType.IMPORT, re(String.raw`^(\s*(?:use|require)\s+(.+?);)`)],
|
|
400
|
+
],
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
specs.haskell = {
|
|
404
|
+
name: "Haskell",
|
|
405
|
+
singleComment: "--",
|
|
406
|
+
multiCommentStart: "{-",
|
|
407
|
+
multiCommentEnd: "-}",
|
|
408
|
+
stringDelimiters: ['"', "'"],
|
|
409
|
+
patterns: [
|
|
410
|
+
[ElementType.MODULE, re(String.raw`^(\s*module\s+(\w[\w.]*))`)],
|
|
411
|
+
[ElementType.TYPE_ALIAS, re(String.raw`^(\s*type\s+(\w+))`)],
|
|
412
|
+
[ElementType.STRUCT, re(String.raw`^(\s*data\s+(\w+))`)],
|
|
413
|
+
[ElementType.CLASS, re(String.raw`^(\s*class\s+(\w+))`)],
|
|
414
|
+
[ElementType.FUNCTION, re(String.raw`^(([a-z_]\w*)\s*::)`)],
|
|
415
|
+
[ElementType.IMPORT, re(String.raw`^(\s*import\s+(?:qualified\s+)?(.+))`)],
|
|
416
|
+
],
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
specs.zig = {
|
|
420
|
+
name: "Zig",
|
|
421
|
+
singleComment: "//",
|
|
422
|
+
stringDelimiters: ['"', "'"],
|
|
423
|
+
patterns: [
|
|
424
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:pub\s+|export\s+)?fn\s+(\w+))`)],
|
|
425
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:pub\s+)?const\s+(\w+)\s*=\s*(?:extern\s+|packed\s+)?struct\b)`)],
|
|
426
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:pub\s+)?const\s+(\w+)\s*=\s*enum\b)`)],
|
|
427
|
+
[ElementType.UNION, re(String.raw`^(\s*(?:pub\s+)?const\s+(\w+)\s*=\s*(?:extern\s+|packed\s+)?union\b)`)],
|
|
428
|
+
[ElementType.IMPORT, re(String.raw`^(\s*const\s+(\w+)\s*=\s*@import\()`)],
|
|
429
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:pub\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=)`)],
|
|
430
|
+
[ElementType.VARIABLE, re(String.raw`^(\s*(?:pub\s+)?var\s+(\w+))`)],
|
|
431
|
+
],
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
specs.elixir = {
|
|
435
|
+
name: "Elixir",
|
|
436
|
+
singleComment: "#",
|
|
437
|
+
stringDelimiters: ['"', "'"],
|
|
438
|
+
patterns: [
|
|
439
|
+
[ElementType.MODULE, re(String.raw`^(\s*defmodule\s+(\w[\w.]*))`)],
|
|
440
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:def|defp|defmacro|defmacrop)\s+(\w+))`)],
|
|
441
|
+
[ElementType.PROTOCOL, re(String.raw`^(\s*defprotocol\s+(\w[\w.]*))`)],
|
|
442
|
+
[ElementType.IMPL, re(String.raw`^(\s*defimpl\s+(\w[\w.]*))`)],
|
|
443
|
+
[ElementType.STRUCT, re(String.raw`^(\s*defstruct\s+(.+))`)],
|
|
444
|
+
[ElementType.IMPORT, re(String.raw`^(\s*(?:import|alias|use|require)\s+(.+))`)],
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
specs.csharp = {
|
|
449
|
+
name: "C#",
|
|
450
|
+
singleComment: "//",
|
|
451
|
+
multiCommentStart: "/*",
|
|
452
|
+
multiCommentEnd: "*/",
|
|
453
|
+
stringDelimiters: ['"', "'"],
|
|
454
|
+
patterns: [
|
|
455
|
+
[ElementType.CLASS, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:sealed\s+|abstract\s+|partial\s+)?class\s+(\w+))`)],
|
|
456
|
+
[ElementType.INTERFACE, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?interface\s+(\w+))`)],
|
|
457
|
+
[ElementType.STRUCT, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:readonly\s+)?struct\s+(\w+))`)],
|
|
458
|
+
[ElementType.ENUM, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?enum\s+(\w+))`)],
|
|
459
|
+
[ElementType.NAMESPACE, re(String.raw`^(\s*namespace\s+(\w[\w.]*))`)],
|
|
460
|
+
[ElementType.FUNCTION, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:async\s+)?(?:virtual\s+|override\s+|abstract\s+)?(?:void|int|char|float|double|long|short|byte|bool|decimal|string|object|var|Task|IEnumerable|\w+(?:<[^>]*>)?)\s*(?:\[\])?\s+(\w+)\s*\()`)],
|
|
461
|
+
[ElementType.PROPERTY, re(String.raw`^(\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:virtual\s+|override\s+)?(?:required\s+)?\w+(?:<[^>]*>)?\s+(\w+)\s*\{)`)],
|
|
462
|
+
[ElementType.IMPORT, re(String.raw`^(\s*using\s+(.+?);)`)],
|
|
463
|
+
[ElementType.DECORATOR, re(String.raw`^(\s*\[(\w[\w.]*(?:\([^)]*\))?)\])`)],
|
|
464
|
+
[ElementType.CONSTANT, re(String.raw`^(\s*(?:public\s+|private\s+)?const\s+\w+\s+(\w+)\s*=)`)],
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
specs.cs = specs.csharp;
|
|
468
|
+
|
|
469
|
+
specs.js = specs.javascript;
|
|
470
|
+
specs.ts = specs.typescript;
|
|
471
|
+
specs.rs = specs.rust;
|
|
472
|
+
specs.py = specs.python;
|
|
473
|
+
specs.rb = specs.ruby;
|
|
474
|
+
specs.hs = specs.haskell;
|
|
475
|
+
specs.cc = specs.cpp;
|
|
476
|
+
specs.cxx = specs.cpp;
|
|
477
|
+
specs.h = specs.c;
|
|
478
|
+
specs.hpp = specs.cpp;
|
|
479
|
+
specs.kt = specs.kotlin;
|
|
480
|
+
specs.ex = specs.elixir;
|
|
481
|
+
specs.exs = specs.elixir;
|
|
482
|
+
specs.pl = specs.perl;
|
|
483
|
+
|
|
484
|
+
return specs;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* @brief Performs language-aware structural analysis and metadata enrichment on source files.
|
|
489
|
+
* @details Parses files into `SourceElement` records, derives signatures, hierarchy, visibility, inheritance, body annotations, and Doxygen fields, then exposes the enriched element list to higher-level renderers. Runtime is generally O(n * p) where n is line count and p is pattern count for the selected language. Side effects are limited to filesystem reads.
|
|
490
|
+
*/
|
|
491
|
+
export class SourceAnalyzer {
|
|
492
|
+
specs: Record<string, LanguageSpec>;
|
|
493
|
+
/**
|
|
494
|
+
* @brief Matches explicit early-exit statements inside analyzed bodies.
|
|
495
|
+
* @details The regex captures return-like constructs that downstream markdown renderers should surface as exit annotations. Evaluation cost is linear in line length.
|
|
496
|
+
*/
|
|
497
|
+
private static readonly EXIT_PATTERNS_RETURN = /^\s*(return\b.*|yield\b.*|raise\b.*|throw\b.*|panic!\(.*)/;
|
|
498
|
+
/**
|
|
499
|
+
* @brief Matches process-terminating calls that imply control-flow exit.
|
|
500
|
+
* @details The regex captures common runtime termination APIs used as implicit exits in body-annotation rendering. Evaluation cost is linear in line length.
|
|
501
|
+
*/
|
|
502
|
+
private static readonly EXIT_PATTERNS_IMPLICIT = /^\s*(sys\.exit\(.*|os\._exit\(.*|exit\(.*|process\.exit\(.*)/;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* @brief Initializes a source analyzer with the full language-spec registry.
|
|
506
|
+
* @details Builds the language-spec map eagerly so later analysis passes can perform O(1) spec lookups. Construction cost is O(l) in supported-language count. Mutates instance fields only.
|
|
507
|
+
*/
|
|
508
|
+
constructor() {
|
|
509
|
+
this.specs = buildLanguageSpecs();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* @brief Parses a source file into raw source-element records.
|
|
514
|
+
* @details Loads the file, tokenizes comments and definitions line-by-line using the selected language spec, computes approximate block spans, and returns extracted elements without higher-level enrichment. Runtime is O(n * p) where n is line count and p is pattern count. Side effects are limited to filesystem reads.
|
|
515
|
+
* @param[in] filePath {string} Source file path.
|
|
516
|
+
* @param[in] language {string} Canonical language identifier or alias.
|
|
517
|
+
* @return {SourceElement[]} Raw analyzed elements.
|
|
518
|
+
* @throws {Error} Throws when the requested language is unsupported.
|
|
519
|
+
*/
|
|
520
|
+
analyze(filePath: string, language: string): SourceElement[] {
|
|
521
|
+
const normalizedLanguage = language.toLowerCase().trim().replace(/^\./, "");
|
|
522
|
+
const spec = this.specs[normalizedLanguage];
|
|
523
|
+
if (!spec) {
|
|
524
|
+
throw new Error(`Language '${language}' not supported.`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
528
|
+
const elements: SourceElement[] = [];
|
|
529
|
+
|
|
530
|
+
let inMultilineComment = false;
|
|
531
|
+
let multilineCommentStartLine = 0;
|
|
532
|
+
let multilineCommentLines: string[] = [];
|
|
533
|
+
|
|
534
|
+
lines.forEach((line, index) => {
|
|
535
|
+
const lineNum = index + 1;
|
|
536
|
+
const stripped = line;
|
|
537
|
+
|
|
538
|
+
if (inMultilineComment) {
|
|
539
|
+
multilineCommentLines.push(stripped);
|
|
540
|
+
if (spec.multiCommentEnd && stripped.includes(spec.multiCommentEnd)) {
|
|
541
|
+
inMultilineComment = false;
|
|
542
|
+
const fullText = multilineCommentLines.join("\n");
|
|
543
|
+
const extractLines = multilineCommentLines.length > 5
|
|
544
|
+
? [...multilineCommentLines.slice(0, 4), " ..."]
|
|
545
|
+
: multilineCommentLines;
|
|
546
|
+
elements.push(new SourceElement({
|
|
547
|
+
elementType: ElementType.COMMENT_MULTI,
|
|
548
|
+
lineStart: multilineCommentStartLine,
|
|
549
|
+
lineEnd: lineNum,
|
|
550
|
+
extract: extractLines.join("\n"),
|
|
551
|
+
commentSource: fullText,
|
|
552
|
+
}));
|
|
553
|
+
multilineCommentLines = [];
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (spec.multiCommentStart && stripped.includes(spec.multiCommentStart)) {
|
|
559
|
+
const startIndex = stripped.indexOf(spec.multiCommentStart);
|
|
560
|
+
if (!this.inStringContext(stripped, startIndex, spec)) {
|
|
561
|
+
const afterStart = stripped.slice(startIndex + spec.multiCommentStart.length);
|
|
562
|
+
if (
|
|
563
|
+
spec.multiCommentEnd &&
|
|
564
|
+
afterStart.includes(spec.multiCommentEnd) &&
|
|
565
|
+
spec.multiCommentStart !== spec.multiCommentEnd
|
|
566
|
+
) {
|
|
567
|
+
elements.push(new SourceElement({
|
|
568
|
+
elementType: ElementType.COMMENT_MULTI,
|
|
569
|
+
lineStart: lineNum,
|
|
570
|
+
lineEnd: lineNum,
|
|
571
|
+
extract: stripped,
|
|
572
|
+
}));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (["\"\"\"", "'''"] .includes(spec.multiCommentStart)) {
|
|
576
|
+
if (afterStart.includes(spec.multiCommentStart)) {
|
|
577
|
+
elements.push(new SourceElement({
|
|
578
|
+
elementType: ElementType.COMMENT_MULTI,
|
|
579
|
+
lineStart: lineNum,
|
|
580
|
+
lineEnd: lineNum,
|
|
581
|
+
extract: stripped,
|
|
582
|
+
}));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
inMultilineComment = true;
|
|
587
|
+
multilineCommentStartLine = lineNum;
|
|
588
|
+
multilineCommentLines = [stripped];
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (spec.singleComment) {
|
|
594
|
+
const commentIndex = this.findComment(stripped, spec);
|
|
595
|
+
if (commentIndex !== undefined) {
|
|
596
|
+
const beforeComment = stripped.slice(0, commentIndex).trim();
|
|
597
|
+
if (!beforeComment) {
|
|
598
|
+
elements.push(new SourceElement({
|
|
599
|
+
elementType: ElementType.COMMENT_SINGLE,
|
|
600
|
+
lineStart: lineNum,
|
|
601
|
+
lineEnd: lineNum,
|
|
602
|
+
extract: stripped,
|
|
603
|
+
}));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const commentText = stripped.slice(commentIndex);
|
|
607
|
+
elements.push(new SourceElement({
|
|
608
|
+
elementType: ElementType.COMMENT_SINGLE,
|
|
609
|
+
lineStart: lineNum,
|
|
610
|
+
lineEnd: lineNum,
|
|
611
|
+
extract: commentText,
|
|
612
|
+
name: "inline",
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!stripped.trim()) return;
|
|
618
|
+
|
|
619
|
+
for (const [elementType, pattern] of spec.patterns) {
|
|
620
|
+
const match = pattern.exec(stripped);
|
|
621
|
+
if (!match) continue;
|
|
622
|
+
const name = match.length >= 3 && match[2] ? match[2] : (match[1] || undefined);
|
|
623
|
+
const singleLineTypes = new Set([
|
|
624
|
+
ElementType.IMPORT,
|
|
625
|
+
ElementType.CONSTANT,
|
|
626
|
+
ElementType.VARIABLE,
|
|
627
|
+
ElementType.DECORATOR,
|
|
628
|
+
ElementType.MACRO,
|
|
629
|
+
ElementType.TYPE_ALIAS,
|
|
630
|
+
ElementType.TYPEDEF,
|
|
631
|
+
ElementType.PROPERTY,
|
|
632
|
+
]);
|
|
633
|
+
const blockEnd = singleLineTypes.has(elementType)
|
|
634
|
+
? lineNum
|
|
635
|
+
: this.findBlockEnd(lines, index, normalizedLanguage, stripped);
|
|
636
|
+
const extractLines = lines.slice(index, blockEnd).map((value) => value);
|
|
637
|
+
const clipped = extractLines.length > 5 ? [...extractLines.slice(0, 4), " ..."] : extractLines;
|
|
638
|
+
elements.push(new SourceElement({
|
|
639
|
+
elementType,
|
|
640
|
+
lineStart: lineNum,
|
|
641
|
+
lineEnd: blockEnd,
|
|
642
|
+
extract: clipped.join("\n"),
|
|
643
|
+
name: name?.trim(),
|
|
644
|
+
}));
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
return elements;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* @brief Enriches raw analyzed elements with derived metadata.
|
|
654
|
+
* @details Normalizes names, computes signatures, hierarchy, visibility, inheritance, body annotations, and Doxygen associations. When `filePath` is omitted, body-level annotation extraction is skipped. Runtime is O(n log n) plus file-read cost for annotation extraction. Side effects are limited to filesystem reads.
|
|
655
|
+
* @param[in] elements {SourceElement[]} Raw analyzed elements.
|
|
656
|
+
* @param[in] language {string} Canonical language identifier or alias.
|
|
657
|
+
* @param[in] filePath {string | undefined} Optional source file path for body-comment extraction.
|
|
658
|
+
* @return {SourceElement[]} The same array instance after in-place enrichment.
|
|
659
|
+
*/
|
|
660
|
+
enrich(elements: SourceElement[], language: string, filePath?: string): SourceElement[] {
|
|
661
|
+
const normalizedLanguage = language.toLowerCase().trim().replace(/^\./, "");
|
|
662
|
+
this.cleanNames(elements, normalizedLanguage);
|
|
663
|
+
this.extractSignatures(elements);
|
|
664
|
+
this.detectHierarchy(elements);
|
|
665
|
+
this.extractVisibility(elements, normalizedLanguage);
|
|
666
|
+
this.extractInheritance(elements, normalizedLanguage);
|
|
667
|
+
if (filePath) {
|
|
668
|
+
this.extractBodyAnnotations(elements, normalizedLanguage, filePath);
|
|
669
|
+
this.extractDoxygenFields(elements);
|
|
670
|
+
}
|
|
671
|
+
return elements;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* @brief Refines element names using the primary language patterns.
|
|
676
|
+
* @details Replays the matching regex against each element's first extracted line and stores the most specific non-empty capture group as the normalized name. Runtime is O(n * p). Side effect: mutates `element.name` in place.
|
|
677
|
+
* @param[in,out] elements {SourceElement[]} Elements to normalize.
|
|
678
|
+
* @param[in] language {string} Canonical language identifier.
|
|
679
|
+
* @return {void} No return value.
|
|
680
|
+
*/
|
|
681
|
+
private cleanNames(elements: SourceElement[], language: string): void {
|
|
682
|
+
const spec = this.specs[language];
|
|
683
|
+
if (!spec) return;
|
|
684
|
+
for (const element of elements) {
|
|
685
|
+
if (!element.name) continue;
|
|
686
|
+
const firstLine = element.extract.split("\n")[0] ?? "";
|
|
687
|
+
for (const [candidateType, pattern] of spec.patterns) {
|
|
688
|
+
if (candidateType !== element.elementType) continue;
|
|
689
|
+
const match = pattern.exec(firstLine);
|
|
690
|
+
if (!match) continue;
|
|
691
|
+
for (let i = match.length - 1; i >= 1; i -= 1) {
|
|
692
|
+
const value = match[i];
|
|
693
|
+
if (value?.trim()) {
|
|
694
|
+
element.name = value.trim();
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* @brief Derives single-line signatures for non-comment elements.
|
|
705
|
+
* @details Uses the first extracted line, trims structural suffixes such as trailing `{`, `:`, or `;`, and stores the result as `element.signature`. Runtime is O(n). Side effect: mutates `element.signature`.
|
|
706
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
707
|
+
* @return {void} No return value.
|
|
708
|
+
*/
|
|
709
|
+
private extractSignatures(elements: SourceElement[]): void {
|
|
710
|
+
const skipTypes = new Set([ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT, ElementType.DECORATOR]);
|
|
711
|
+
for (const element of elements) {
|
|
712
|
+
if (skipTypes.has(element.elementType)) continue;
|
|
713
|
+
let signature = (element.extract.split("\n")[0] ?? "").trim();
|
|
714
|
+
for (const suffix of [" {", "{", ":", ";"]) {
|
|
715
|
+
if (signature.endsWith(suffix) && !signature.endsWith("::")) {
|
|
716
|
+
signature = signature.slice(0, -suffix.length).trimEnd();
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
element.signature = signature;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* @brief Assigns one-level parent relationships for nested elements.
|
|
726
|
+
* @details Treats classes, structs, modules, interfaces, and similar containers as parents, then attaches each contained non-container element to the nearest enclosing container. Runtime is O(n * c) where c is container count. Side effect: mutates `parentName` and `depth`.
|
|
727
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
728
|
+
* @return {void} No return value.
|
|
729
|
+
*/
|
|
730
|
+
private detectHierarchy(elements: SourceElement[]): void {
|
|
731
|
+
const containerTypes = new Set([
|
|
732
|
+
ElementType.CLASS,
|
|
733
|
+
ElementType.STRUCT,
|
|
734
|
+
ElementType.MODULE,
|
|
735
|
+
ElementType.IMPL,
|
|
736
|
+
ElementType.INTERFACE,
|
|
737
|
+
ElementType.TRAIT,
|
|
738
|
+
ElementType.NAMESPACE,
|
|
739
|
+
ElementType.ENUM,
|
|
740
|
+
ElementType.EXTENSION,
|
|
741
|
+
ElementType.PROTOCOL,
|
|
742
|
+
]);
|
|
743
|
+
const containers = elements.filter((element) => containerTypes.has(element.elementType));
|
|
744
|
+
const skipTypes = new Set([ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT]);
|
|
745
|
+
for (const element of elements) {
|
|
746
|
+
if (skipTypes.has(element.elementType) || containerTypes.has(element.elementType)) continue;
|
|
747
|
+
let best: SourceElement | undefined;
|
|
748
|
+
for (const container of containers) {
|
|
749
|
+
if (container === element) continue;
|
|
750
|
+
if (container.lineStart <= element.lineStart && container.lineEnd >= element.lineEnd) {
|
|
751
|
+
if (!best || container.lineStart > best.lineStart || (container.lineStart === best.lineStart && container.lineEnd < best.lineEnd)) {
|
|
752
|
+
best = container;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (best) {
|
|
757
|
+
element.parentName = best.name;
|
|
758
|
+
element.depth = 1;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* @brief Derives visibility metadata for applicable elements.
|
|
765
|
+
* @details Computes a per-element visibility code from the first signature line using language-specific heuristics. Runtime is O(n). Side effect: mutates `element.visibility` when a visibility code is derivable.
|
|
766
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
767
|
+
* @param[in] language {string} Canonical language identifier.
|
|
768
|
+
* @return {void} No return value.
|
|
769
|
+
*/
|
|
770
|
+
private extractVisibility(elements: SourceElement[], language: string): void {
|
|
771
|
+
for (const element of elements) {
|
|
772
|
+
if ([ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT].includes(element.elementType)) continue;
|
|
773
|
+
const signature = element.extract.split("\n")[0]?.trim() ?? "";
|
|
774
|
+
const visibility = this.parseVisibility(signature, element.name, language);
|
|
775
|
+
if (visibility) {
|
|
776
|
+
element.visibility = visibility;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* @brief Infers visibility from a signature and language rules.
|
|
783
|
+
* @details Applies language-specific heuristics such as naming conventions, access modifiers, and `pub` markers to return a compact visibility code. Runtime is O(n) in signature length. No side effects occur.
|
|
784
|
+
* @param[in] signature {string} First-line signature text.
|
|
785
|
+
* @param[in] name {string | undefined} Normalized element name.
|
|
786
|
+
* @param[in] language {string} Canonical language identifier.
|
|
787
|
+
* @return {string | undefined} Visibility code such as `pub`, `priv`, `prot`, or `undefined` when unavailable.
|
|
788
|
+
*/
|
|
789
|
+
private parseVisibility(signature: string, name: string | undefined, language: string): string | undefined {
|
|
790
|
+
if (["python", "py"].includes(language)) {
|
|
791
|
+
if (name?.startsWith("__") && !name.endsWith("__")) return "priv";
|
|
792
|
+
if (name?.startsWith("_")) return "priv";
|
|
793
|
+
return "pub";
|
|
794
|
+
}
|
|
795
|
+
if (["java", "csharp", "cs", "kotlin", "kt", "php"].includes(language)) {
|
|
796
|
+
if (/\bpublic\b/.test(signature)) return "pub";
|
|
797
|
+
if (/\bprivate\b/.test(signature)) return "priv";
|
|
798
|
+
if (/\bprotected\b/.test(signature)) return "prot";
|
|
799
|
+
if (/\binternal\b/.test(signature)) return "int";
|
|
800
|
+
return undefined;
|
|
801
|
+
}
|
|
802
|
+
if (["rust", "rs", "zig"].includes(language)) {
|
|
803
|
+
return /^\s*pub\b/.test(signature) ? "pub" : "priv";
|
|
804
|
+
}
|
|
805
|
+
if (["go"].includes(language)) {
|
|
806
|
+
return name?.[0]?.toUpperCase() === name?.[0] ? "pub" : "priv";
|
|
807
|
+
}
|
|
808
|
+
if (["swift"].includes(language)) {
|
|
809
|
+
if (/\bprivate\b/.test(signature)) return "priv";
|
|
810
|
+
if (/\bfileprivate\b/.test(signature)) return "fpriv";
|
|
811
|
+
if (/\b(?:public|open)\b/.test(signature)) return "pub";
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
if (["cpp", "cc", "cxx", "h", "hpp"].includes(language)) {
|
|
815
|
+
if (/\bpublic\b/.test(signature)) return "pub";
|
|
816
|
+
if (/\bprivate\b/.test(signature)) return "priv";
|
|
817
|
+
if (/\bprotected\b/.test(signature)) return "prot";
|
|
818
|
+
}
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* @brief Derives inheritance metadata for class-like elements.
|
|
824
|
+
* @details Inspects the first line of classes, structs, and interfaces and stores parsed inheritance text when the language-specific parser can extract it. Runtime is O(n). Side effect: mutates `element.inherits`.
|
|
825
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
826
|
+
* @param[in] language {string} Canonical language identifier.
|
|
827
|
+
* @return {void} No return value.
|
|
828
|
+
*/
|
|
829
|
+
private extractInheritance(elements: SourceElement[], language: string): void {
|
|
830
|
+
for (const element of elements) {
|
|
831
|
+
if (![ElementType.CLASS, ElementType.STRUCT, ElementType.INTERFACE].includes(element.elementType)) continue;
|
|
832
|
+
const firstLine = element.extract.split("\n")[0]?.trim() ?? "";
|
|
833
|
+
const inheritance = this.parseInheritance(firstLine, language);
|
|
834
|
+
if (inheritance) {
|
|
835
|
+
element.inherits = inheritance;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* @brief Parses inheritance syntax from one declaration line.
|
|
842
|
+
* @details Supports language-specific `extends`, `implements`, `:`, and subclass forms, returning a compact textual summary when present. Runtime is O(n) in line length. No side effects occur.
|
|
843
|
+
* @param[in] firstLine {string} First line of the declaration.
|
|
844
|
+
* @param[in] language {string} Canonical language identifier.
|
|
845
|
+
* @return {string | undefined} Parsed inheritance summary, or `undefined` when absent or unsupported.
|
|
846
|
+
*/
|
|
847
|
+
private parseInheritance(firstLine: string, language: string): string | undefined {
|
|
848
|
+
let match: RegExpExecArray | null = null;
|
|
849
|
+
if (["python", "py"].includes(language)) {
|
|
850
|
+
match = /class\s+\w+\s*\(([^)]+)\)/.exec(firstLine);
|
|
851
|
+
return match?.[1]?.trim();
|
|
852
|
+
}
|
|
853
|
+
if (["java", "typescript", "ts", "javascript", "js"].includes(language)) {
|
|
854
|
+
const parts: string[] = [];
|
|
855
|
+
match = /\bextends\s+([\w.<>, ]+)/.exec(firstLine);
|
|
856
|
+
if (match?.[1]) parts.push(match[1].trim());
|
|
857
|
+
match = /\bimplements\s+([\w.<>, ]+)/.exec(firstLine);
|
|
858
|
+
if (match?.[1]) parts.push(match[1].trim());
|
|
859
|
+
return parts.length > 0 ? parts.join(", ") : undefined;
|
|
860
|
+
}
|
|
861
|
+
if (["cpp", "cc", "cxx", "hpp", "csharp", "cs", "swift"].includes(language)) {
|
|
862
|
+
match = /(?:class|struct)\s+\w+\s*:\s*(.+?)(?:\s*\{|$)/.exec(firstLine);
|
|
863
|
+
return match?.[1]?.trim();
|
|
864
|
+
}
|
|
865
|
+
if (["kotlin", "kt"].includes(language)) {
|
|
866
|
+
match = /class\s+\w+\s*(?:\([^)]*\))?\s*:\s*(.+?)(?:\s*\{|$)/.exec(firstLine);
|
|
867
|
+
return match?.[1]?.trim();
|
|
868
|
+
}
|
|
869
|
+
if (["ruby", "rb"].includes(language)) {
|
|
870
|
+
match = /class\s+\w+\s*<\s*(\w+)/.exec(firstLine);
|
|
871
|
+
return match?.[1];
|
|
872
|
+
}
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* @brief Extracts body comments and exit points for multi-line elements.
|
|
878
|
+
* @details Reads the full file, scans each eligible element body for standalone and inline comments plus explicit or implicit exit statements, and stores the results on the element. Runtime is O(total body lines). Side effects are limited to filesystem reads and in-place element mutation.
|
|
879
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
880
|
+
* @param[in] language {string} Canonical language identifier.
|
|
881
|
+
* @param[in] filePath {string} Source file path.
|
|
882
|
+
* @return {void} No return value.
|
|
883
|
+
*/
|
|
884
|
+
private extractBodyAnnotations(elements: SourceElement[], language: string, filePath: string): void {
|
|
885
|
+
const spec = this.specs[language];
|
|
886
|
+
if (!spec) return;
|
|
887
|
+
const allLines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
888
|
+
const singleLineTypes = new Set([
|
|
889
|
+
ElementType.IMPORT,
|
|
890
|
+
ElementType.CONSTANT,
|
|
891
|
+
ElementType.VARIABLE,
|
|
892
|
+
ElementType.DECORATOR,
|
|
893
|
+
ElementType.MACRO,
|
|
894
|
+
ElementType.TYPE_ALIAS,
|
|
895
|
+
ElementType.TYPEDEF,
|
|
896
|
+
ElementType.PROPERTY,
|
|
897
|
+
ElementType.COMMENT_SINGLE,
|
|
898
|
+
ElementType.COMMENT_MULTI,
|
|
899
|
+
]);
|
|
900
|
+
|
|
901
|
+
for (const element of elements) {
|
|
902
|
+
if (singleLineTypes.has(element.elementType) || element.lineEnd <= element.lineStart) continue;
|
|
903
|
+
const bodyComments: Array<[number, number, string]> = [];
|
|
904
|
+
const exitPoints: Array<[number, string]> = [];
|
|
905
|
+
const bodyStart = element.lineStart;
|
|
906
|
+
const bodyEnd = Math.min(element.lineEnd, allLines.length);
|
|
907
|
+
let inMulti = false;
|
|
908
|
+
let multiStart = 0;
|
|
909
|
+
let multiLines: string[] = [];
|
|
910
|
+
|
|
911
|
+
for (let lineIndex = bodyStart; lineIndex < bodyEnd; lineIndex += 1) {
|
|
912
|
+
const raw = allLines[lineIndex] ?? "";
|
|
913
|
+
const stripped = raw.trim();
|
|
914
|
+
if (!stripped) continue;
|
|
915
|
+
|
|
916
|
+
if (inMulti) {
|
|
917
|
+
multiLines.push(stripped);
|
|
918
|
+
if (spec.multiCommentEnd && stripped.includes(spec.multiCommentEnd)) {
|
|
919
|
+
inMulti = false;
|
|
920
|
+
const text = multiLines.map((line) => this.cleanCommentLine(line, spec)).join(" ").trim();
|
|
921
|
+
if (text) bodyComments.push([multiStart, lineIndex + 1, text]);
|
|
922
|
+
multiLines = [];
|
|
923
|
+
}
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (spec.multiCommentStart && stripped.includes(spec.multiCommentStart)) {
|
|
928
|
+
const startPos = stripped.indexOf(spec.multiCommentStart);
|
|
929
|
+
if (!this.inStringContext(stripped, startPos, spec)) {
|
|
930
|
+
if (["\"\"\"", "'''"] .includes(spec.multiCommentStart)) {
|
|
931
|
+
const after = stripped.slice(startPos + 3);
|
|
932
|
+
if (after.includes(spec.multiCommentStart)) {
|
|
933
|
+
const text = this.cleanCommentLine(stripped, spec);
|
|
934
|
+
if (text) bodyComments.push([lineIndex + 1, lineIndex + 1, text]);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
} else if (spec.multiCommentEnd && stripped.slice(startPos + spec.multiCommentStart.length).includes(spec.multiCommentEnd)) {
|
|
938
|
+
const text = this.cleanCommentLine(stripped, spec);
|
|
939
|
+
if (text) bodyComments.push([lineIndex + 1, lineIndex + 1, text]);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
inMulti = true;
|
|
943
|
+
multiStart = lineIndex + 1;
|
|
944
|
+
multiLines = [stripped];
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (spec.singleComment) {
|
|
950
|
+
const commentPos = this.findComment(stripped, spec);
|
|
951
|
+
if (commentPos !== undefined) {
|
|
952
|
+
const before = stripped.slice(0, commentPos).trim();
|
|
953
|
+
const commentText = stripped.slice(commentPos);
|
|
954
|
+
const cleaned = this.cleanCommentLine(commentText, spec);
|
|
955
|
+
if (!before) {
|
|
956
|
+
if (cleaned) bodyComments.push([lineIndex + 1, lineIndex + 1, cleaned]);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (cleaned) bodyComments.push([lineIndex + 1, lineIndex + 1, cleaned]);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (SourceAnalyzer.EXIT_PATTERNS_RETURN.test(stripped) || SourceAnalyzer.EXIT_PATTERNS_IMPLICIT.test(stripped)) {
|
|
964
|
+
exitPoints.push([lineIndex + 1, stripped]);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
element.bodyComments = bodyComments;
|
|
969
|
+
element.exitPoints = exitPoints;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* @brief Associates parsed Doxygen comments with analyzed elements.
|
|
975
|
+
* @details Searches inline postfix comments, nearby preceding comments, and selected following postfix comments while excluding file-level comments, then stores the parsed Doxygen field map on each element. Runtime is O(n^2) in the worst case due to proximity scans across comments and elements. Side effect: mutates `element.doxygenFields`.
|
|
976
|
+
* @param[in,out] elements {SourceElement[]} Elements to enrich.
|
|
977
|
+
* @return {void} No return value.
|
|
978
|
+
*/
|
|
979
|
+
private extractDoxygenFields(elements: SourceElement[]): void {
|
|
980
|
+
const commentElements = elements.filter((element) => [ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType));
|
|
981
|
+
const nonCommentElements = elements.filter((element) => ![ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType));
|
|
982
|
+
|
|
983
|
+
const isFileLevelComment = (comment: SourceElement): boolean => {
|
|
984
|
+
const commentText = comment.commentSource || comment.extract;
|
|
985
|
+
return !!commentText && /(?<!\w)(?:@|\\)file\b/.test(commentText);
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
for (const element of elements) {
|
|
989
|
+
if ([ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType)) continue;
|
|
990
|
+
let associatedComment: SourceElement | undefined;
|
|
991
|
+
|
|
992
|
+
const sameLinePostfixCandidates = commentElements.filter(
|
|
993
|
+
(comment) =>
|
|
994
|
+
comment.lineStart === element.lineEnd &&
|
|
995
|
+
comment.name === "inline" &&
|
|
996
|
+
this.isPostfixDoxygenComment(comment.extract) &&
|
|
997
|
+
!isFileLevelComment(comment),
|
|
998
|
+
);
|
|
999
|
+
if (sameLinePostfixCandidates.length > 0) {
|
|
1000
|
+
associatedComment = sameLinePostfixCandidates.sort((a, b) => a.lineStart - b.lineStart)[0];
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (!associatedComment) {
|
|
1004
|
+
const precedingCandidates = commentElements.filter(
|
|
1005
|
+
(comment) => comment.name !== "inline" && comment.lineEnd < element.lineStart && !isFileLevelComment(comment),
|
|
1006
|
+
);
|
|
1007
|
+
const hasBlockingElement = (comment: SourceElement): boolean =>
|
|
1008
|
+
nonCommentElements.some(
|
|
1009
|
+
(other) =>
|
|
1010
|
+
other !== element &&
|
|
1011
|
+
(((other.lineStart > comment.lineEnd && other.lineStart < element.lineStart) ||
|
|
1012
|
+
(other.lineStart <= comment.lineEnd && comment.lineEnd < other.lineEnd && other.lineEnd < element.lineStart))),
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
const nearCandidates = precedingCandidates.filter((comment) => element.lineStart - comment.lineEnd <= 2);
|
|
1016
|
+
const searchPool = nearCandidates.length > 0
|
|
1017
|
+
? nearCandidates.sort((a, b) => (b.lineEnd - a.lineEnd) || (b.lineStart - a.lineStart))
|
|
1018
|
+
: precedingCandidates.sort((a, b) => (b.lineEnd - a.lineEnd) || (b.lineStart - a.lineStart));
|
|
1019
|
+
|
|
1020
|
+
for (const comment of searchPool) {
|
|
1021
|
+
const commentText = comment.commentSource || comment.extract;
|
|
1022
|
+
if (nearCandidates.length === 0 && Object.keys(parseDoxygenComment(commentText)).length === 0) continue;
|
|
1023
|
+
if (!hasBlockingElement(comment)) {
|
|
1024
|
+
associatedComment = comment;
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (!associatedComment) {
|
|
1031
|
+
const followingPostfixCandidates = commentElements.filter(
|
|
1032
|
+
(comment) =>
|
|
1033
|
+
comment.name !== "inline" &&
|
|
1034
|
+
comment.lineStart > element.lineEnd &&
|
|
1035
|
+
comment.lineStart - element.lineEnd <= 2 &&
|
|
1036
|
+
this.isPostfixDoxygenComment(comment.extract) &&
|
|
1037
|
+
!isFileLevelComment(comment),
|
|
1038
|
+
);
|
|
1039
|
+
if (followingPostfixCandidates.length > 0) {
|
|
1040
|
+
associatedComment = followingPostfixCandidates.sort((a, b) => (a.lineStart - b.lineStart) || (a.lineEnd - b.lineEnd))[0];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (associatedComment) {
|
|
1045
|
+
let commentText = associatedComment.commentSource || associatedComment.extract;
|
|
1046
|
+
if (
|
|
1047
|
+
associatedComment.name !== "inline" &&
|
|
1048
|
+
associatedComment.lineEnd < element.lineStart &&
|
|
1049
|
+
Object.keys(parseDoxygenComment(commentText)).length > 0
|
|
1050
|
+
) {
|
|
1051
|
+
const mergedComments: SourceElement[] = [associatedComment];
|
|
1052
|
+
let currentStart = associatedComment.lineStart;
|
|
1053
|
+
while (true) {
|
|
1054
|
+
const contiguousCandidates = commentElements.filter(
|
|
1055
|
+
(comment) => comment.name !== "inline" && comment.lineEnd === currentStart - 1,
|
|
1056
|
+
);
|
|
1057
|
+
if (contiguousCandidates.length === 0) break;
|
|
1058
|
+
const previousComment = contiguousCandidates.sort((a, b) => (b.lineEnd - a.lineEnd) || (b.lineStart - a.lineStart))[0];
|
|
1059
|
+
mergedComments.unshift(previousComment);
|
|
1060
|
+
currentStart = previousComment.lineStart;
|
|
1061
|
+
}
|
|
1062
|
+
commentText = mergedComments.map((comment) => comment.commentSource || comment.extract).join("\n");
|
|
1063
|
+
}
|
|
1064
|
+
element.doxygenFields = parseDoxygenComment(commentText);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* @brief Tests whether a comment uses postfix-Doxygen marker syntax.
|
|
1071
|
+
* @details Matches comment prefixes such as `//!`, `/*!`, `#!`, and similar forms used for same-line documentation attachment. Runtime is O(n) in comment length. No side effects occur.
|
|
1072
|
+
* @param[in] commentText {string} Raw comment text.
|
|
1073
|
+
* @return {boolean} `true` when the comment looks like postfix Doxygen.
|
|
1074
|
+
*/
|
|
1075
|
+
private isPostfixDoxygenComment(commentText: string): boolean {
|
|
1076
|
+
return !!commentText && /^\s*(?:#|\/\/+|--|\/\*+|;+)!?</.test(commentText);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* @brief Removes language comment markers from one comment line.
|
|
1081
|
+
* @details Strips known single-line prefixes and leading/trailing delimiter characters so body-annotation rendering receives semantic text only. Runtime is O(n). No side effects occur.
|
|
1082
|
+
* @param[in] text {string} Raw comment line.
|
|
1083
|
+
* @param[in] spec {LanguageSpec} Active language specification.
|
|
1084
|
+
* @return {string} Cleaned comment payload.
|
|
1085
|
+
*/
|
|
1086
|
+
private cleanCommentLine(text: string, spec: LanguageSpec): string {
|
|
1087
|
+
let value = text.trim();
|
|
1088
|
+
for (const prefix of ["///", "//!", "//", "#!", "##", "#", "--", ";;"]) {
|
|
1089
|
+
if (value.startsWith(prefix)) {
|
|
1090
|
+
value = value.slice(prefix.length).trim();
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
value = value.replace(/^[/*"']+|[/*"']+$/g, "").trim();
|
|
1095
|
+
return value;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* @brief Tests whether a character position falls inside a string literal for the active language.
|
|
1100
|
+
* @details Scans the line left-to-right while tracking escaped characters and active delimiters defined by the language spec. Runtime is O(n) in inspected prefix length. No side effects occur.
|
|
1101
|
+
* @param[in] line {string} Source line to inspect.
|
|
1102
|
+
* @param[in] pos {number} Zero-based character position.
|
|
1103
|
+
* @param[in] spec {LanguageSpec} Active language specification.
|
|
1104
|
+
* @return {boolean} `true` when the position is inside a string literal.
|
|
1105
|
+
*/
|
|
1106
|
+
private inStringContext(line: string, pos: number, spec: LanguageSpec): boolean {
|
|
1107
|
+
let inString = false;
|
|
1108
|
+
let currentDelimiter: string | undefined;
|
|
1109
|
+
let i = 0;
|
|
1110
|
+
const delimiters = [...spec.stringDelimiters].sort((a, b) => b.length - a.length);
|
|
1111
|
+
while (i < pos) {
|
|
1112
|
+
if (inString) {
|
|
1113
|
+
if (line[i] === "\\" && i + 1 < line.length) {
|
|
1114
|
+
i += 2;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (currentDelimiter && line.slice(i).startsWith(currentDelimiter)) {
|
|
1118
|
+
inString = false;
|
|
1119
|
+
i += currentDelimiter.length;
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
} else {
|
|
1123
|
+
if (delimiters.some((delimiter) => {
|
|
1124
|
+
if (!line.slice(i).startsWith(delimiter)) return false;
|
|
1125
|
+
inString = true;
|
|
1126
|
+
currentDelimiter = delimiter;
|
|
1127
|
+
i += delimiter.length;
|
|
1128
|
+
return true;
|
|
1129
|
+
})) {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
i += 1;
|
|
1134
|
+
}
|
|
1135
|
+
return inString;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* @brief Locates the first real single-line comment marker in a line.
|
|
1140
|
+
* @details Scans the line while respecting active string delimiters so comment markers inside strings are ignored. Runtime is O(n) in line length. No side effects occur.
|
|
1141
|
+
* @param[in] line {string} Source line to inspect.
|
|
1142
|
+
* @param[in] spec {LanguageSpec} Active language specification.
|
|
1143
|
+
* @return {number | undefined} Zero-based comment index, or `undefined` when absent.
|
|
1144
|
+
*/
|
|
1145
|
+
private findComment(line: string, spec: LanguageSpec): number | undefined {
|
|
1146
|
+
if (!spec.singleComment) return undefined;
|
|
1147
|
+
let inString = false;
|
|
1148
|
+
let currentDelimiter: string | undefined;
|
|
1149
|
+
const delimiters = [...spec.stringDelimiters].sort((a, b) => b.length - a.length);
|
|
1150
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
1151
|
+
if (inString) {
|
|
1152
|
+
if (line[i] === "\\" && i + 1 < line.length) {
|
|
1153
|
+
i += 1;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (currentDelimiter && line.slice(i).startsWith(currentDelimiter)) {
|
|
1157
|
+
inString = false;
|
|
1158
|
+
i += currentDelimiter.length - 1;
|
|
1159
|
+
}
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (line.slice(i).startsWith(spec.singleComment)) {
|
|
1163
|
+
return i;
|
|
1164
|
+
}
|
|
1165
|
+
const matched = delimiters.find((delimiter) => line.slice(i).startsWith(delimiter));
|
|
1166
|
+
if (matched) {
|
|
1167
|
+
inString = true;
|
|
1168
|
+
currentDelimiter = matched;
|
|
1169
|
+
i += matched.length - 1;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return undefined;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* @brief Estimates the inclusive end line for a block-like construct.
|
|
1177
|
+
* @details Uses language-specific indentation, brace, or `end` heuristics to bound multi-line definitions without a full parser. Runtime is O(k) where k is the scanned lookahead window. No side effects occur.
|
|
1178
|
+
* @param[in] lines {string[]} Full file lines.
|
|
1179
|
+
* @param[in] startIndex {number} Zero-based starting line index.
|
|
1180
|
+
* @param[in] language {string} Canonical language identifier.
|
|
1181
|
+
* @param[in] firstLine {string} First source line of the construct.
|
|
1182
|
+
* @return {number} One-based inclusive end line.
|
|
1183
|
+
*/
|
|
1184
|
+
private findBlockEnd(lines: string[], startIndex: number, language: string, firstLine: string): number {
|
|
1185
|
+
if (["python", "py"].includes(language)) {
|
|
1186
|
+
const indent = firstLine.length - firstLine.trimStart().length;
|
|
1187
|
+
let end = startIndex + 1;
|
|
1188
|
+
while (end < Math.min(lines.length, startIndex + 200)) {
|
|
1189
|
+
const line = (lines[end] ?? "").replace(/[\r\n]+$/, "");
|
|
1190
|
+
if (!line.trim()) {
|
|
1191
|
+
end += 1;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const lineIndent = line.length - line.trimStart().length;
|
|
1195
|
+
if (lineIndent <= indent && line.trim()) break;
|
|
1196
|
+
end += 1;
|
|
1197
|
+
}
|
|
1198
|
+
return end;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (["c", "cpp", "cc", "cxx", "h", "hpp", "rust", "rs", "javascript", "js", "typescript", "ts", "java", "go", "csharp", "cs", "swift", "kotlin", "kt", "php", "scala", "zig"].includes(language)) {
|
|
1202
|
+
let braceCount = 0;
|
|
1203
|
+
let foundOpen = false;
|
|
1204
|
+
let end = startIndex;
|
|
1205
|
+
while (end < Math.min(lines.length, startIndex + 300)) {
|
|
1206
|
+
const line = (lines[end] ?? "").replace(/[\r\n]+$/, "");
|
|
1207
|
+
for (const char of line) {
|
|
1208
|
+
if (char === "{") {
|
|
1209
|
+
braceCount += 1;
|
|
1210
|
+
foundOpen = true;
|
|
1211
|
+
} else if (char === "}") {
|
|
1212
|
+
braceCount -= 1;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (foundOpen && braceCount <= 0) return end + 1;
|
|
1216
|
+
end += 1;
|
|
1217
|
+
}
|
|
1218
|
+
return foundOpen ? end : startIndex + 1;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (["ruby", "rb", "elixir", "ex", "exs", "lua"].includes(language)) {
|
|
1222
|
+
const indent = firstLine.length - firstLine.trimStart().length;
|
|
1223
|
+
let end = startIndex + 1;
|
|
1224
|
+
while (end < Math.min(lines.length, startIndex + 200)) {
|
|
1225
|
+
const line = (lines[end] ?? "").replace(/[\r\n]+$/, "");
|
|
1226
|
+
if ((line.trim() === "end" || line.trim().startsWith("end ")) && (line.length - line.trimStart().length) <= indent) {
|
|
1227
|
+
return end + 1;
|
|
1228
|
+
}
|
|
1229
|
+
end += 1;
|
|
1230
|
+
}
|
|
1231
|
+
return startIndex + 1;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (["haskell", "hs"].includes(language)) {
|
|
1235
|
+
const indent = firstLine.length - firstLine.trimStart().length;
|
|
1236
|
+
let end = startIndex + 1;
|
|
1237
|
+
while (end < Math.min(lines.length, startIndex + 100)) {
|
|
1238
|
+
const line = (lines[end] ?? "").replace(/[\r\n]+$/, "");
|
|
1239
|
+
if (!line.trim()) {
|
|
1240
|
+
end += 1;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
const lineIndent = line.length - line.trimStart().length;
|
|
1244
|
+
if (lineIndent <= indent) break;
|
|
1245
|
+
end += 1;
|
|
1246
|
+
}
|
|
1247
|
+
return end;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return startIndex + 1;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* @brief Formats one element location for markdown output.
|
|
1256
|
+
* @details Returns either a single-line `Lx` token or an inclusive line-range token `Lx-y`. Runtime is O(1). No side effects occur.
|
|
1257
|
+
* @param[in] element {SourceElement} Source element.
|
|
1258
|
+
* @return {string} Markdown location token.
|
|
1259
|
+
*/
|
|
1260
|
+
function mdLoc(element: SourceElement): string {
|
|
1261
|
+
return element.lineStart === element.lineEnd ? `L${element.lineStart}` : `L${element.lineStart}-${element.lineEnd}`;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* @brief Maps an element type to its compact markdown kind code.
|
|
1266
|
+
* @details Converts `ElementType` values into the abbreviated tokens used by reference markdown and symbol indexes. Runtime is O(1). No side effects occur.
|
|
1267
|
+
* @param[in] element {SourceElement} Source element.
|
|
1268
|
+
* @return {string} Compact kind code.
|
|
1269
|
+
*/
|
|
1270
|
+
function mdKind(element: SourceElement): string {
|
|
1271
|
+
const mapping: Record<ElementType, string> = {
|
|
1272
|
+
[ElementType.FUNCTION]: "fn",
|
|
1273
|
+
[ElementType.METHOD]: "method",
|
|
1274
|
+
[ElementType.CLASS]: "class",
|
|
1275
|
+
[ElementType.STRUCT]: "struct",
|
|
1276
|
+
[ElementType.ENUM]: "enum",
|
|
1277
|
+
[ElementType.TRAIT]: "trait",
|
|
1278
|
+
[ElementType.INTERFACE]: "iface",
|
|
1279
|
+
[ElementType.MODULE]: "mod",
|
|
1280
|
+
[ElementType.IMPL]: "impl",
|
|
1281
|
+
[ElementType.MACRO]: "macro",
|
|
1282
|
+
[ElementType.CONSTANT]: "const",
|
|
1283
|
+
[ElementType.VARIABLE]: "var",
|
|
1284
|
+
[ElementType.TYPE_ALIAS]: "type",
|
|
1285
|
+
[ElementType.IMPORT]: "unk",
|
|
1286
|
+
[ElementType.DECORATOR]: "dec",
|
|
1287
|
+
[ElementType.COMMENT_SINGLE]: "unk",
|
|
1288
|
+
[ElementType.COMMENT_MULTI]: "unk",
|
|
1289
|
+
[ElementType.COMPONENT]: "comp",
|
|
1290
|
+
[ElementType.PROTOCOL]: "proto",
|
|
1291
|
+
[ElementType.EXTENSION]: "ext",
|
|
1292
|
+
[ElementType.UNION]: "unk",
|
|
1293
|
+
[ElementType.NAMESPACE]: "ns",
|
|
1294
|
+
[ElementType.PROPERTY]: "prop",
|
|
1295
|
+
[ElementType.SIGNAL]: "signal",
|
|
1296
|
+
[ElementType.TYPEDEF]: "typedef",
|
|
1297
|
+
};
|
|
1298
|
+
return mapping[element.elementType] ?? "unk";
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* @brief Extracts normalized plain text from a comment element.
|
|
1303
|
+
* @details Removes comment markers, drops language-specific block delimiters, joins lines with spaces, and optionally truncates the result. Runtime is O(n) in comment length. No side effects occur.
|
|
1304
|
+
* @param[in] commentElement {SourceElement} Comment element.
|
|
1305
|
+
* @param[in] maxLength {number} Optional maximum output length, where `0` disables truncation.
|
|
1306
|
+
* @return {string} Cleaned comment text.
|
|
1307
|
+
*/
|
|
1308
|
+
function extractCommentText(commentElement: SourceElement, maxLength = 0): string {
|
|
1309
|
+
const lines = commentElement.extract.split("\n");
|
|
1310
|
+
const cleaned: string[] = [];
|
|
1311
|
+
for (const line of lines) {
|
|
1312
|
+
let value = line.trim();
|
|
1313
|
+
for (const prefix of ["///", "//!", "//", "#!", "##", "#", "--", ";;"]) {
|
|
1314
|
+
if (value.startsWith(prefix)) {
|
|
1315
|
+
value = value.slice(prefix.length).trim();
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
value = value.replace(/^[/*"']+|[/*"']+$/g, "").trim();
|
|
1320
|
+
if (value && !value.startsWith("=begin") && !value.startsWith("=end")) {
|
|
1321
|
+
cleaned.push(value);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
let text = cleaned.join(" ");
|
|
1325
|
+
if (maxLength > 0 && text.length > maxLength) {
|
|
1326
|
+
text = `${text.slice(0, maxLength - 3)}...`;
|
|
1327
|
+
}
|
|
1328
|
+
return text;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* @brief Extracts cleaned individual lines from a comment element.
|
|
1333
|
+
* @details Removes comment markers and delimiter-only lines while preserving line granularity for markdown rendering. Runtime is O(n) in comment length. No side effects occur.
|
|
1334
|
+
* @param[in] commentElement {SourceElement} Comment element.
|
|
1335
|
+
* @return {string[]} Cleaned comment lines.
|
|
1336
|
+
*/
|
|
1337
|
+
function extractCommentLines(commentElement: SourceElement): string[] {
|
|
1338
|
+
return commentElement.extract
|
|
1339
|
+
.split("\n")
|
|
1340
|
+
.map((line) => {
|
|
1341
|
+
let value = line.trim();
|
|
1342
|
+
for (const prefix of ["///", "//!", "//", "#!", "##", "#", "--", ";;"]) {
|
|
1343
|
+
if (value.startsWith(prefix)) {
|
|
1344
|
+
value = value.slice(prefix.length).trim();
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
value = value.replace(/^[/*"']+|[/*"']+$/g, "").trim();
|
|
1349
|
+
return value;
|
|
1350
|
+
})
|
|
1351
|
+
.filter((line) => !!line && !line.startsWith("=begin") && !line.startsWith("=end"));
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* @brief Builds lookup structures linking comments to definitions and file descriptions.
|
|
1356
|
+
* @details Sorts elements, associates nearby non-inline comments with following definitions, collects standalone comments, and derives a compact file description from early comment text. Runtime is O(n log n). No side effects occur.
|
|
1357
|
+
* @param[in] elements {SourceElement[]} Analyzed source elements.
|
|
1358
|
+
* @return {[Record<number, SourceElement[]>, SourceElement[], string]} Attached-comment map, standalone comments, and file description.
|
|
1359
|
+
*/
|
|
1360
|
+
function buildCommentMaps(elements: SourceElement[]): [Record<number, SourceElement[]>, SourceElement[], string] {
|
|
1361
|
+
const sorted = [...elements].sort((a, b) => a.lineStart - b.lineStart);
|
|
1362
|
+
const definitionTypes = new Set(Object.values(ElementType).filter((value) => ![ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT, ElementType.DECORATOR].includes(value as ElementType)) as ElementType[]);
|
|
1363
|
+
const definitionStarts = new Set(elements.filter((element) => definitionTypes.has(element.elementType)).map((element) => element.lineStart));
|
|
1364
|
+
const importStarts = new Set(elements.filter((element) => element.elementType === ElementType.IMPORT).map((element) => element.lineStart));
|
|
1365
|
+
const comments = sorted.filter((element) => [ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType));
|
|
1366
|
+
const docForDef: Record<number, SourceElement[]> = {};
|
|
1367
|
+
const standaloneComments: SourceElement[] = [];
|
|
1368
|
+
let fileDescription = "";
|
|
1369
|
+
|
|
1370
|
+
for (const firstComment of comments) {
|
|
1371
|
+
if (firstComment.lineStart > 10) break;
|
|
1372
|
+
const text = extractCommentText(firstComment);
|
|
1373
|
+
if (text && !text.startsWith("/usr/") && !text.startsWith("usr/")) {
|
|
1374
|
+
fileDescription = text.length > 200 ? `${text.slice(0, 197)}...` : text;
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
comments.forEach((comment) => {
|
|
1380
|
+
if (comment.name === "inline") return;
|
|
1381
|
+
let attached = false;
|
|
1382
|
+
for (let gap = 1; gap < 4; gap += 1) {
|
|
1383
|
+
const targetLine = comment.lineEnd + gap;
|
|
1384
|
+
if (definitionStarts.has(targetLine)) {
|
|
1385
|
+
docForDef[targetLine] ??= [];
|
|
1386
|
+
docForDef[targetLine].push(comment);
|
|
1387
|
+
attached = true;
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
if (importStarts.has(targetLine)) break;
|
|
1391
|
+
}
|
|
1392
|
+
if (!attached && comment !== comments[0]) {
|
|
1393
|
+
standaloneComments.push(comment);
|
|
1394
|
+
} else if (!attached && comment === comments[0] && !fileDescription) {
|
|
1395
|
+
standaloneComments.push(comment);
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
return [docForDef, standaloneComments, fileDescription];
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* @brief Merges Doxygen field values into one accumulator map.
|
|
1404
|
+
* @details Appends values for matching tags without deduplication so relative source order is preserved. Runtime is O(v) in appended value count. Side effect: mutates `baseFields`.
|
|
1405
|
+
* @param[in,out] baseFields {Record<string, string[]>} Mutable destination field map.
|
|
1406
|
+
* @param[in] extraFields {Record<string, string[]>} Source field map.
|
|
1407
|
+
* @return {Record<string, string[]>} The mutated destination map.
|
|
1408
|
+
*/
|
|
1409
|
+
function mergeDoxygenFields(baseFields: Record<string, string[]>, extraFields: Record<string, string[]>): Record<string, string[]> {
|
|
1410
|
+
Object.entries(extraFields).forEach(([tag, values]) => {
|
|
1411
|
+
baseFields[tag] ??= [];
|
|
1412
|
+
baseFields[tag].push(...values);
|
|
1413
|
+
});
|
|
1414
|
+
return baseFields;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* @brief Aggregates all Doxygen fields associated with one element.
|
|
1419
|
+
* @details Starts with directly attached fields and then merges early body comments from the first three body lines when they parse as Doxygen. Runtime is O(c) in considered comment count. No external state is mutated.
|
|
1420
|
+
* @param[in] element {SourceElement} Source element.
|
|
1421
|
+
* @return {Record<string, string[]>} Aggregated Doxygen field map.
|
|
1422
|
+
*/
|
|
1423
|
+
export function collectElementDoxygenFields(element: SourceElement): Record<string, string[]> {
|
|
1424
|
+
const aggregate: Record<string, string[]> = {};
|
|
1425
|
+
if (element.doxygenFields) {
|
|
1426
|
+
mergeDoxygenFields(aggregate, element.doxygenFields);
|
|
1427
|
+
}
|
|
1428
|
+
for (const bodyComment of element.bodyComments) {
|
|
1429
|
+
const [commentLineStart, , commentText] = bodyComment;
|
|
1430
|
+
if (commentLineStart > element.lineStart + 3) continue;
|
|
1431
|
+
const parsed = parseDoxygenComment(commentText);
|
|
1432
|
+
if (Object.keys(parsed).length > 0) {
|
|
1433
|
+
mergeDoxygenFields(aggregate, parsed);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return aggregate;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* @brief Collects the first file-level Doxygen field map from analyzed elements.
|
|
1441
|
+
* @details Scans non-inline comment elements in source order for an `@file` tag and returns the parsed Doxygen map from the first matching comment. Runtime is O(n). No side effects occur.
|
|
1442
|
+
* @param[in] elements {SourceElement[]} Analyzed source elements.
|
|
1443
|
+
* @return {Record<string, string[]>} Parsed file-level Doxygen field map, or an empty map when absent.
|
|
1444
|
+
*/
|
|
1445
|
+
export function collectFileLevelDoxygenFields(elements: SourceElement[]): Record<string, string[]> {
|
|
1446
|
+
const fileTagPattern = /(?<!\w)(?:@|\\)file\b/;
|
|
1447
|
+
const commentElements = elements
|
|
1448
|
+
.filter((element) => [ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType) && element.name !== "inline")
|
|
1449
|
+
.sort((a, b) => (a.lineStart - b.lineStart) || (a.lineEnd - b.lineEnd));
|
|
1450
|
+
for (const comment of commentElements) {
|
|
1451
|
+
const text = comment.commentSource || comment.extract;
|
|
1452
|
+
if (!text || !fileTagPattern.test(text)) continue;
|
|
1453
|
+
return parseDoxygenComment(text);
|
|
1454
|
+
}
|
|
1455
|
+
return {};
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* @brief Renders analyzed source elements as the repository reference-markdown format.
|
|
1460
|
+
* @details Builds file metadata, imports, top-level definitions, child elements, comments, and a symbol index while incorporating Doxygen fields and optional legacy annotations. Runtime is O(n log n) in element count. No side effects occur.
|
|
1461
|
+
* @param[in] elements {SourceElement[]} Enriched source elements.
|
|
1462
|
+
* @param[in] filePath {string} Display file path.
|
|
1463
|
+
* @param[in] language {string} Canonical analyzer language identifier.
|
|
1464
|
+
* @param[in] specName {string} Human-readable language name.
|
|
1465
|
+
* @param[in] totalLines {number} Total source-line count.
|
|
1466
|
+
* @param[in] includeLegacyAnnotations {boolean} When `true`, include non-Doxygen comment annotations.
|
|
1467
|
+
* @return {string} Rendered markdown document for the file.
|
|
1468
|
+
*/
|
|
1469
|
+
export function formatMarkdown(
|
|
1470
|
+
elements: SourceElement[],
|
|
1471
|
+
filePath: string,
|
|
1472
|
+
language: string,
|
|
1473
|
+
specName: string,
|
|
1474
|
+
totalLines: number,
|
|
1475
|
+
includeLegacyAnnotations = true,
|
|
1476
|
+
): string {
|
|
1477
|
+
const out: string[] = [];
|
|
1478
|
+
const fileName = path.basename(filePath);
|
|
1479
|
+
const skipTypes = new Set([ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT, ElementType.DECORATOR]);
|
|
1480
|
+
const definitionCount = elements.filter((element) => !skipTypes.has(element.elementType)).length;
|
|
1481
|
+
const importCount = elements.filter((element) => element.elementType === ElementType.IMPORT).length;
|
|
1482
|
+
const [docForDef, standaloneCommentsRaw, fileDescRaw] = buildCommentMaps(elements);
|
|
1483
|
+
const standaloneComments = includeLegacyAnnotations ? standaloneCommentsRaw : [];
|
|
1484
|
+
const fileDesc = includeLegacyAnnotations ? fileDescRaw : "";
|
|
1485
|
+
const fileLevelDoxygenFields = collectFileLevelDoxygenFields(elements);
|
|
1486
|
+
|
|
1487
|
+
const commentCount = elements.filter(
|
|
1488
|
+
(element) => [ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI].includes(element.elementType) && element.name !== "inline",
|
|
1489
|
+
).length;
|
|
1490
|
+
|
|
1491
|
+
out.push(`# ${fileName} | ${specName} | ${totalLines}L | ${definitionCount} symbols | ${importCount} imports | ${commentCount} comments`);
|
|
1492
|
+
out.push(`> Path: \`${filePath}\``);
|
|
1493
|
+
if (fileDesc) out.push(`> ${fileDesc}`);
|
|
1494
|
+
if (Object.keys(fileLevelDoxygenFields).length > 0) {
|
|
1495
|
+
out.push(...formatDoxygenFieldsAsMarkdown(fileLevelDoxygenFields));
|
|
1496
|
+
}
|
|
1497
|
+
out.push("");
|
|
1498
|
+
|
|
1499
|
+
const imports = elements.filter((element) => element.elementType === ElementType.IMPORT).sort((a, b) => a.lineStart - b.lineStart);
|
|
1500
|
+
if (imports.length > 0) {
|
|
1501
|
+
out.push("## Imports");
|
|
1502
|
+
out.push("```");
|
|
1503
|
+
imports.forEach((imp) => out.push((imp.extract.split("\n")[0] ?? "").trim()));
|
|
1504
|
+
out.push("```");
|
|
1505
|
+
out.push("");
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const decoratorMap: Record<number, string> = {};
|
|
1509
|
+
elements.filter((element) => element.elementType === ElementType.DECORATOR).forEach((element) => {
|
|
1510
|
+
decoratorMap[element.lineStart] = (element.extract.split("\n")[0] ?? "").trim();
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
const definitions = elements.filter((element) => !skipTypes.has(element.elementType)).sort((a, b) => a.lineStart - b.lineStart);
|
|
1514
|
+
const topLevel = definitions.filter((element) => element.depth === 0);
|
|
1515
|
+
const childrenMap = new Map<number, SourceElement[]>();
|
|
1516
|
+
definitions.forEach((element) => {
|
|
1517
|
+
if (element.depth <= 0 || !element.parentName) return;
|
|
1518
|
+
for (const top of topLevel) {
|
|
1519
|
+
if (top.name === element.parentName && top.lineStart <= element.lineStart && top.lineEnd >= element.lineEnd) {
|
|
1520
|
+
const children = childrenMap.get(top.lineStart) ?? [];
|
|
1521
|
+
children.push(element);
|
|
1522
|
+
childrenMap.set(top.lineStart, children);
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
const inlineTypes = new Set([ElementType.CONSTANT, ElementType.VARIABLE, ElementType.TYPE_ALIAS, ElementType.TYPEDEF, ElementType.MACRO, ElementType.PROPERTY]);
|
|
1529
|
+
|
|
1530
|
+
if (topLevel.length > 0) {
|
|
1531
|
+
out.push("## Definitions");
|
|
1532
|
+
out.push("");
|
|
1533
|
+
for (const element of topLevel) {
|
|
1534
|
+
const kind = mdKind(element);
|
|
1535
|
+
let signature = element.signature || element.name || "";
|
|
1536
|
+
const location = mdLoc(element);
|
|
1537
|
+
const inherit = element.inherits ? ` : ${element.inherits}` : "";
|
|
1538
|
+
const visibility = element.visibility && !["pub", "public"].includes(element.visibility) ? ` \`${element.visibility}\`` : "";
|
|
1539
|
+
const decorator = decoratorMap[element.lineStart - 1] ? ` \`${decoratorMap[element.lineStart - 1]}\`` : "";
|
|
1540
|
+
const aggregateDoxygenFields = collectElementDoxygenFields(element);
|
|
1541
|
+
const doxygenMarkdown = Object.keys(aggregateDoxygenFields).length > 0 ? formatDoxygenFieldsAsMarkdown(aggregateDoxygenFields) : [];
|
|
1542
|
+
let docText = "";
|
|
1543
|
+
let docLinesList: string[] = [];
|
|
1544
|
+
let docLineNum = 0;
|
|
1545
|
+
if (doxygenMarkdown.length > 0 && aggregateDoxygenFields.brief?.length) {
|
|
1546
|
+
docText = aggregateDoxygenFields.brief[0]!;
|
|
1547
|
+
if (docText.length > 150) docText = `${docText.slice(0, 147)}...`;
|
|
1548
|
+
} else if (includeLegacyAnnotations && docForDef[element.lineStart]?.length) {
|
|
1549
|
+
docLinesList = extractCommentLines(docForDef[element.lineStart]![0]!);
|
|
1550
|
+
docText = docLinesList.join(" ");
|
|
1551
|
+
docLineNum = docForDef[element.lineStart]![0]!.lineStart;
|
|
1552
|
+
if (docText.length > 150) docText = `${docText.slice(0, 147)}...`;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const isInline = inlineTypes.has(element.elementType) || element.lineStart === element.lineEnd;
|
|
1556
|
+
if (isInline) {
|
|
1557
|
+
const firstLine = (element.extract.split("\n")[0] ?? "").trim();
|
|
1558
|
+
let line = `- ${kind} \`${firstLine}\`${visibility} (L${element.lineStart})`;
|
|
1559
|
+
if (includeLegacyAnnotations && docText) line += ` — ${docText}`;
|
|
1560
|
+
out.push(line);
|
|
1561
|
+
if (doxygenMarkdown.length > 0) out.push(...doxygenMarkdown);
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (element.elementType === ElementType.IMPL) {
|
|
1566
|
+
signature = ((element.extract.split("\n")[0] ?? "").trim()).replace(/\s*\{$/, "");
|
|
1567
|
+
}
|
|
1568
|
+
out.push(`### ${kind} \`${signature}\`${inherit}${visibility}${decorator} (${location})`);
|
|
1569
|
+
if (doxygenMarkdown.length > 0) {
|
|
1570
|
+
out.push(...doxygenMarkdown);
|
|
1571
|
+
} else if (includeLegacyAnnotations && docLinesList.length > 1) {
|
|
1572
|
+
docLinesList.slice(0, 5).forEach((line, index) => out.push(`L${docLineNum + index}> ${line}`));
|
|
1573
|
+
if (docLinesList.length > 5) out.push(`L${docLineNum + 5}> ...`);
|
|
1574
|
+
} else if (includeLegacyAnnotations && docText && docLineNum) {
|
|
1575
|
+
out.push(`L${docLineNum}> ${docText}`);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const children = (childrenMap.get(element.lineStart) ?? []).sort((a, b) => a.lineStart - b.lineStart);
|
|
1579
|
+
if (includeLegacyAnnotations) {
|
|
1580
|
+
const childRanges = children.map((child) => {
|
|
1581
|
+
const childDocs = docForDef[child.lineStart] ?? [];
|
|
1582
|
+
const rangeStart = childDocs.length > 0 ? Math.min(child.lineStart, childDocs[0]!.lineStart) : child.lineStart;
|
|
1583
|
+
return [rangeStart, child.lineEnd] as const;
|
|
1584
|
+
});
|
|
1585
|
+
renderBodyAnnotations(out, element, "", childRanges);
|
|
1586
|
+
}
|
|
1587
|
+
if (children.length > 0) {
|
|
1588
|
+
children.forEach((child) => {
|
|
1589
|
+
const childSignature = child.signature || child.name || "";
|
|
1590
|
+
const childLocation = mdLoc(child);
|
|
1591
|
+
const childKind = mdKind(child);
|
|
1592
|
+
const childVisibility = child.visibility && !["pub", "public"].includes(child.visibility) ? ` \`${child.visibility}\`` : "";
|
|
1593
|
+
let childDoc = "";
|
|
1594
|
+
let childDocLine = "";
|
|
1595
|
+
const childAggregate = collectElementDoxygenFields(child);
|
|
1596
|
+
const childDoxygen = Object.keys(childAggregate).length > 0 ? formatDoxygenFieldsAsMarkdown(childAggregate) : [];
|
|
1597
|
+
if (childDoxygen.length === 0 && includeLegacyAnnotations) {
|
|
1598
|
+
const childDocs = docForDef[child.lineStart] ?? [];
|
|
1599
|
+
if (childDocs.length > 0) {
|
|
1600
|
+
const childDocText = extractCommentText(childDocs[0]!, 100);
|
|
1601
|
+
if (childDocText) {
|
|
1602
|
+
childDocLine = ` L${childDocs[0]!.lineStart}>`;
|
|
1603
|
+
childDoc = ` ${childDocText}`;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
out.push(`- ${childKind} \`${childSignature}\`${childVisibility} (${childLocation})${childDocLine}${childDoc}`);
|
|
1608
|
+
if (childDoxygen.length > 0) {
|
|
1609
|
+
out.push(...childDoxygen.map((line) => ` ${line}`));
|
|
1610
|
+
}
|
|
1611
|
+
if (includeLegacyAnnotations) {
|
|
1612
|
+
renderBodyAnnotations(out, child, " ");
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
out.push("");
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (includeLegacyAnnotations && standaloneComments.length > 0) {
|
|
1621
|
+
out.push("## Comments");
|
|
1622
|
+
const groups: SourceElement[][] = [];
|
|
1623
|
+
let currentGroup: SourceElement[] = [standaloneComments[0]!];
|
|
1624
|
+
standaloneComments.slice(1).forEach((comment) => {
|
|
1625
|
+
const previous = currentGroup[currentGroup.length - 1]!;
|
|
1626
|
+
if (comment.lineStart <= previous.lineEnd + 2) {
|
|
1627
|
+
currentGroup.push(comment);
|
|
1628
|
+
} else {
|
|
1629
|
+
groups.push(currentGroup);
|
|
1630
|
+
currentGroup = [comment];
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
groups.push(currentGroup);
|
|
1634
|
+
groups.forEach((group) => {
|
|
1635
|
+
if (group.length === 1) {
|
|
1636
|
+
const comment = group[0]!;
|
|
1637
|
+
const text = extractCommentText(comment, 150);
|
|
1638
|
+
if (text) out.push(`- L${comment.lineStart}: ${text}`);
|
|
1639
|
+
} else {
|
|
1640
|
+
const startLine = group[0]!.lineStart;
|
|
1641
|
+
const endLine = group[group.length - 1]!.lineEnd;
|
|
1642
|
+
const texts = group.map((comment) => extractCommentText(comment, 100)).filter(Boolean);
|
|
1643
|
+
if (texts.length > 0) out.push(`- L${startLine}-${endLine}: ${texts.join(" | ")}`);
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
out.push("");
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
const indexable = elements
|
|
1650
|
+
.filter((element) => ![ElementType.COMMENT_SINGLE, ElementType.COMMENT_MULTI, ElementType.IMPORT, ElementType.DECORATOR].includes(element.elementType))
|
|
1651
|
+
.sort((a, b) => a.lineStart - b.lineStart);
|
|
1652
|
+
if (indexable.length > 0) {
|
|
1653
|
+
out.push("## Symbol Index");
|
|
1654
|
+
out.push("|Symbol|Kind|Vis|Lines|Sig|");
|
|
1655
|
+
out.push("|---|---|---|---|---|");
|
|
1656
|
+
indexable.forEach((element) => {
|
|
1657
|
+
const name = element.parentName ? `${element.parentName}.${element.name ?? "?"}` : (element.name ?? "?");
|
|
1658
|
+
const kind = mdKind(element);
|
|
1659
|
+
const visibility = element.visibility ?? "";
|
|
1660
|
+
const lines = element.lineStart === element.lineEnd ? `${element.lineStart}` : `${element.lineStart}-${element.lineEnd}`;
|
|
1661
|
+
let signature = "";
|
|
1662
|
+
if (
|
|
1663
|
+
[
|
|
1664
|
+
ElementType.FUNCTION,
|
|
1665
|
+
ElementType.METHOD,
|
|
1666
|
+
ElementType.CLASS,
|
|
1667
|
+
ElementType.STRUCT,
|
|
1668
|
+
ElementType.TRAIT,
|
|
1669
|
+
ElementType.INTERFACE,
|
|
1670
|
+
ElementType.IMPL,
|
|
1671
|
+
ElementType.ENUM,
|
|
1672
|
+
].includes(element.elementType) &&
|
|
1673
|
+
element.signature &&
|
|
1674
|
+
element.signature !== element.name
|
|
1675
|
+
) {
|
|
1676
|
+
signature = element.signature.length > 60 ? `${element.signature.slice(0, 57)}...` : element.signature;
|
|
1677
|
+
}
|
|
1678
|
+
out.push(`|\`${name}\`|${kind}|${visibility}|${lines}|${signature}|`);
|
|
1679
|
+
});
|
|
1680
|
+
out.push("");
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
return out.join(os.EOL);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* @brief Renders body comments and exit-point annotations for one element.
|
|
1688
|
+
* @details Merges comment and exit maps, skips excluded line ranges, and emits normalized markdown lines that summarize body-level annotations. Runtime is O(a log a) in annotation count. No side effects occur.
|
|
1689
|
+
* @param[in,out] out {string[]} Markdown output buffer.
|
|
1690
|
+
* @param[in] element {SourceElement} Source element whose body annotations should be rendered.
|
|
1691
|
+
* @param[in] indent {string} Prefix applied to each rendered annotation line.
|
|
1692
|
+
* @param[in] excludeRanges {ReadonlyArray<readonly [number, number]> | undefined} Optional line ranges to suppress.
|
|
1693
|
+
* @return {void} No return value.
|
|
1694
|
+
*/
|
|
1695
|
+
function renderBodyAnnotations(
|
|
1696
|
+
out: string[],
|
|
1697
|
+
element: SourceElement,
|
|
1698
|
+
indent = "",
|
|
1699
|
+
excludeRanges?: ReadonlyArray<readonly [number, number]>,
|
|
1700
|
+
): void {
|
|
1701
|
+
const commentMap = new Map<number, [number, number, string]>();
|
|
1702
|
+
element.bodyComments.forEach((comment) => commentMap.set(comment[0], comment));
|
|
1703
|
+
const exitMap = new Map<number, string>();
|
|
1704
|
+
element.exitPoints.forEach(([lineNum, text]) => exitMap.set(lineNum, text));
|
|
1705
|
+
const allLines = [...new Set([...commentMap.keys(), ...exitMap.keys()])].sort((a, b) => a - b);
|
|
1706
|
+
allLines.forEach((lineNum) => {
|
|
1707
|
+
if (excludeRanges?.some(([start, end]) => start <= lineNum && lineNum <= end)) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const comment = commentMap.get(lineNum);
|
|
1711
|
+
const exit = exitMap.get(lineNum);
|
|
1712
|
+
if (comment && exit) {
|
|
1713
|
+
const cleanedExit = exit.includes("#") ? exit.slice(0, exit.indexOf("#")).trim() : exit.includes("//") ? exit.slice(0, exit.indexOf("//")).trim() : exit;
|
|
1714
|
+
out.push(`${indent}L${lineNum}> \`${cleanedExit}\` — ${comment[2]}`);
|
|
1715
|
+
} else if (exit) {
|
|
1716
|
+
out.push(`${indent}L${lineNum}> \`${exit}\``);
|
|
1717
|
+
} else if (comment) {
|
|
1718
|
+
out.push(comment[0] === comment[1] ? `${indent}L${comment[0]}> ${comment[2]}` : `${indent}L${comment[0]}-${comment[1]}> ${comment[2]}`);
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|